diff options
Diffstat (limited to 'drivers/mmc')
| -rw-r--r-- | drivers/mmc/card/Kconfig | 12 | ||||
| -rw-r--r-- | drivers/mmc/card/Makefile | 1 | ||||
| -rw-r--r-- | drivers/mmc/card/block.c | 2 | ||||
| -rw-r--r-- | drivers/mmc/card/mmc_test.c | 892 | ||||
| -rw-r--r-- | drivers/mmc/host/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/mmc/host/at91_mci.c | 5 | ||||
| -rw-r--r-- | drivers/mmc/host/mmci.c | 14 | ||||
| -rw-r--r-- | drivers/mmc/host/omap.c | 12 | ||||
| -rw-r--r-- | drivers/mmc/host/pxamci.c | 13 | ||||
| -rw-r--r-- | drivers/mmc/host/sdhci.c | 34 | ||||
| -rw-r--r-- | drivers/mmc/host/sdhci.h | 2 | ||||
| -rw-r--r-- | drivers/mmc/host/wbsd.c | 21 | 
12 files changed, 986 insertions, 24 deletions
diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig index aa8a4e46194..dd0f398ee2f 100644 --- a/drivers/mmc/card/Kconfig +++ b/drivers/mmc/card/Kconfig @@ -39,3 +39,15 @@ config SDIO_UART  	  SDIO function driver for SDIO cards that implements the UART  	  class, as well as the GPS class which appears like a UART. +config MMC_TEST +	tristate "MMC host test driver" +	default n +	help +	  Development driver that performs a series of reads and writes +	  to a memory card in order to expose certain well known bugs +	  in host controllers. The tests are executed by writing to the +	  "test" file in sysfs under each card. Note that whatever is +	  on your card will be overwritten by these tests. + +	  This driver is only of interest to those developing or +	  testing a host driver. Most people should say N here. diff --git a/drivers/mmc/card/Makefile b/drivers/mmc/card/Makefile index fc5a784cfa1..0d407514f67 100644 --- a/drivers/mmc/card/Makefile +++ b/drivers/mmc/card/Makefile @@ -8,6 +8,7 @@ endif  obj-$(CONFIG_MMC_BLOCK)		+= mmc_block.o  mmc_block-objs			:= block.o queue.o +obj-$(CONFIG_MMC_TEST)		+= mmc_test.o  obj-$(CONFIG_SDIO_UART)		+= sdio_uart.o diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 91ded3e8240..f9ad960d7c1 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -46,7 +46,7 @@  #define MMC_SHIFT	3  #define MMC_NUM_MINORS	(256 >> MMC_SHIFT) -static unsigned long dev_use[MMC_NUM_MINORS/(8*sizeof(unsigned long))]; +static DECLARE_BITMAP(dev_use, MMC_NUM_MINORS);  /*   * There is one mmc_blk_data per slot. diff --git a/drivers/mmc/card/mmc_test.c b/drivers/mmc/card/mmc_test.c new file mode 100644 index 00000000000..ffadee549a4 --- /dev/null +++ b/drivers/mmc/card/mmc_test.c @@ -0,0 +1,892 @@ +/* + *  linux/drivers/mmc/card/mmc_test.c + * + *  Copyright 2007 Pierre Ossman + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#include <linux/mmc/core.h> +#include <linux/mmc/card.h> +#include <linux/mmc/host.h> +#include <linux/mmc/mmc.h> + +#include <linux/scatterlist.h> + +#define RESULT_OK		0 +#define RESULT_FAIL		1 +#define RESULT_UNSUP_HOST	2 +#define RESULT_UNSUP_CARD	3 + +#define BUFFER_SIZE	(PAGE_SIZE * 4) + +struct mmc_test_card { +	struct mmc_card	*card; + +	u8		*buffer; +}; + +/*******************************************************************/ +/*  Helper functions                                               */ +/*******************************************************************/ + +static int mmc_test_set_blksize(struct mmc_test_card *test, unsigned size) +{ +	struct mmc_command cmd; +	int ret; + +	cmd.opcode = MMC_SET_BLOCKLEN; +	cmd.arg = size; +	cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; +	ret = mmc_wait_for_cmd(test->card->host, &cmd, 0); +	if (ret) +		return ret; + +	return 0; +} + +static int __mmc_test_transfer(struct mmc_test_card *test, int write, +	unsigned broken_xfer, u8 *buffer, unsigned addr, +	unsigned blocks, unsigned blksz) +{ +	int ret, busy; + +	struct mmc_request mrq; +	struct mmc_command cmd; +	struct mmc_command stop; +	struct mmc_data data; + +	struct scatterlist sg; + +	memset(&mrq, 0, sizeof(struct mmc_request)); + +	mrq.cmd = &cmd; +	mrq.data = &data; + +	memset(&cmd, 0, sizeof(struct mmc_command)); + +	if (broken_xfer) { +		if (blocks > 1) { +			cmd.opcode = write ? +				MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK; +		} else { +			cmd.opcode = MMC_SEND_STATUS; +		} +	} else { +		if (blocks > 1) { +			cmd.opcode = write ? +				MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK; +		} else { +			cmd.opcode = write ? +				MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK; +		} +	} + +	if (broken_xfer && blocks == 1) +		cmd.arg = test->card->rca << 16; +	else +		cmd.arg = addr; +	cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; + +	memset(&stop, 0, sizeof(struct mmc_command)); + +	if (!broken_xfer && (blocks > 1)) { +		stop.opcode = MMC_STOP_TRANSMISSION; +		stop.arg = 0; +		stop.flags = MMC_RSP_R1B | MMC_CMD_AC; + +		mrq.stop = &stop; +	} + +	memset(&data, 0, sizeof(struct mmc_data)); + +	data.blksz = blksz; +	data.blocks = blocks; +	data.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ; +	data.sg = &sg; +	data.sg_len = 1; + +	sg_init_one(&sg, buffer, blocks * blksz); + +	mmc_set_data_timeout(&data, test->card); + +	mmc_wait_for_req(test->card->host, &mrq); + +	ret = 0; + +	if (broken_xfer) { +		if (!ret && cmd.error) +			ret = cmd.error; +		if (!ret && data.error == 0) +			ret = RESULT_FAIL; +		if (!ret && data.error != -ETIMEDOUT) +			ret = data.error; +		if (!ret && stop.error) +			ret = stop.error; +		if (blocks > 1) { +			if (!ret && data.bytes_xfered > blksz) +				ret = RESULT_FAIL; +		} else { +			if (!ret && data.bytes_xfered > 0) +				ret = RESULT_FAIL; +		} +	} else { +		if (!ret && cmd.error) +			ret = cmd.error; +		if (!ret && data.error) +			ret = data.error; +		if (!ret && stop.error) +			ret = stop.error; +		if (!ret && data.bytes_xfered != blocks * blksz) +			ret = RESULT_FAIL; +	} + +	if (ret == -EINVAL) +		ret = RESULT_UNSUP_HOST; + +	busy = 0; +	do { +		int ret2; + +		memset(&cmd, 0, sizeof(struct mmc_command)); + +		cmd.opcode = MMC_SEND_STATUS; +		cmd.arg = test->card->rca << 16; +		cmd.flags = MMC_RSP_R1 | MMC_CMD_AC; + +		ret2 = mmc_wait_for_cmd(test->card->host, &cmd, 0); +		if (ret2) +			break; + +		if (!busy && !(cmd.resp[0] & R1_READY_FOR_DATA)) { +			busy = 1; +			printk(KERN_INFO "%s: Warning: Host did not " +				"wait for busy state to end.\n", +				mmc_hostname(test->card->host)); +		} +	} while (!(cmd.resp[0] & R1_READY_FOR_DATA)); + +	return ret; +} + +static int mmc_test_transfer(struct mmc_test_card *test, int write, +	u8 *buffer, unsigned addr, unsigned blocks, unsigned blksz) +{ +	return __mmc_test_transfer(test, write, 0, buffer, +			addr, blocks, blksz); +} + +static int mmc_test_prepare_verify(struct mmc_test_card *test, int write) +{ +	int ret, i; + +	ret = mmc_test_set_blksize(test, 512); +	if (ret) +		return ret; + +	if (write) +		memset(test->buffer, 0xDF, BUFFER_SIZE); +	else { +		for (i = 0;i < BUFFER_SIZE;i++) +			test->buffer[i] = i; +	} + +	for (i = 0;i < BUFFER_SIZE / 512;i++) { +		ret = mmc_test_transfer(test, 1, test->buffer + i * 512, +			i * 512, 1, 512); +		if (ret) +			return ret; +	} + +	return 0; +} + +static int mmc_test_prepare_verify_write(struct mmc_test_card *test) +{ +	return mmc_test_prepare_verify(test, 1); +} + +static int mmc_test_prepare_verify_read(struct mmc_test_card *test) +{ +	return mmc_test_prepare_verify(test, 0); +} + +static int mmc_test_verified_transfer(struct mmc_test_card *test, int write, +	u8 *buffer, unsigned addr, unsigned blocks, unsigned blksz) +{ +	int ret, i, sectors; + +	/* +	 * It is assumed that the above preparation has been done. +	 */ + +	memset(test->buffer, 0, BUFFER_SIZE); + +	if (write) { +		for (i = 0;i < blocks * blksz;i++) +			buffer[i] = i; +	} + +	ret = mmc_test_set_blksize(test, blksz); +	if (ret) +		return ret; + +	ret = mmc_test_transfer(test, write, buffer, addr, blocks, blksz); +	if (ret) +		return ret; + +	if (write) { +		ret = mmc_test_set_blksize(test, 512); +		if (ret) +			return ret; + +		sectors = (blocks * blksz + 511) / 512; +		if ((sectors * 512) == (blocks * blksz)) +			sectors++; + +		if ((sectors * 512) > BUFFER_SIZE) +			return -EINVAL; + +		memset(test->buffer, 0, sectors * 512); + +		for (i = 0;i < sectors;i++) { +			ret = mmc_test_transfer(test, 0, +				test->buffer + i * 512, +				addr + i * 512, 1, 512); +			if (ret) +				return ret; +		} + +		for (i = 0;i < blocks * blksz;i++) { +			if (test->buffer[i] != (u8)i) +				return RESULT_FAIL; +		} + +		for (;i < sectors * 512;i++) { +			if (test->buffer[i] != 0xDF) +				return RESULT_FAIL; +		} +	} else { +		for (i = 0;i < blocks * blksz;i++) { +			if (buffer[i] != (u8)i) +				return RESULT_FAIL; +		} +	} + +	return 0; +} + +static int mmc_test_cleanup_verify(struct mmc_test_card *test) +{ +	int ret, i; + +	ret = mmc_test_set_blksize(test, 512); +	if (ret) +		return ret; + +	memset(test->buffer, 0, BUFFER_SIZE); + +	for (i = 0;i < BUFFER_SIZE / 512;i++) { +		ret = mmc_test_transfer(test, 1, test->buffer + i * 512, +			i * 512, 1, 512); +		if (ret) +			return ret; +	} + +	return 0; +} + +/*******************************************************************/ +/*  Tests                                                          */ +/*******************************************************************/ + +struct mmc_test_case { +	const char *name; + +	int (*prepare)(struct mmc_test_card *); +	int (*run)(struct mmc_test_card *); +	int (*cleanup)(struct mmc_test_card *); +}; + +static int mmc_test_basic_write(struct mmc_test_card *test) +{ +	int ret; + +	ret = mmc_test_set_blksize(test, 512); +	if (ret) +		return ret; + +	ret = mmc_test_transfer(test, 1, test->buffer, 0, 1, 512); +	if (ret) +		return ret; + +	return 0; +} + +static int mmc_test_basic_read(struct mmc_test_card *test) +{ +	int ret; + +	ret = mmc_test_set_blksize(test, 512); +	if (ret) +		return ret; + +	ret = mmc_test_transfer(test, 0, test->buffer, 0, 1, 512); +	if (ret) +		return ret; + +	return 0; +} + +static int mmc_test_verify_write(struct mmc_test_card *test) +{ +	int ret; + +	ret = mmc_test_verified_transfer(test, 1, test->buffer, 0, 1, 512); +	if (ret) +		return ret; + +	return 0; +} + +static int mmc_test_verify_read(struct mmc_test_card *test) +{ +	int ret; + +	ret = mmc_test_verified_transfer(test, 0, test->buffer, 0, 1, 512); +	if (ret) +		return ret; + +	return 0; +} + +static int mmc_test_multi_write(struct mmc_test_card *test) +{ +	int ret; +	unsigned int size; + +	if (test->card->host->max_blk_count == 1) +		return RESULT_UNSUP_HOST; + +	size = PAGE_SIZE * 2; +	size = min(size, test->card->host->max_req_size); +	size = min(size, test->card->host->max_seg_size); +	size = min(size, test->card->host->max_blk_count * 512); + +	if (size < 1024) +		return RESULT_UNSUP_HOST; + +	ret = mmc_test_verified_transfer(test, 1, test->buffer, 0, +		size / 512, 512); +	if (ret) +		return ret; + +	return 0; +} + +static int mmc_test_multi_read(struct mmc_test_card *test) +{ +	int ret; +	unsigned int size; + +	if (test->card->host->max_blk_count == 1) +		return RESULT_UNSUP_HOST; + +	size = PAGE_SIZE * 2; +	size = min(size, test->card->host->max_req_size); +	size = min(size, test->card->host->max_seg_size); +	size = min(size, test->card->host->max_blk_count * 512); + +	if (size < 1024) +		return RESULT_UNSUP_HOST; + +	ret = mmc_test_verified_transfer(test, 0, test->buffer, 0, +		size / 512, 512); +	if (ret) +		return ret; + +	return 0; +} + +static int mmc_test_pow2_write(struct mmc_test_card *test) +{ +	int ret, i; + +	if (!test->card->csd.write_partial) +		return RESULT_UNSUP_CARD; + +	for (i = 1; i < 512;i <<= 1) { +		ret = mmc_test_verified_transfer(test, 1, +			test->buffer, 0, 1, i); +		if (ret) +			return ret; +	} + +	return 0; +} + +static int mmc_test_pow2_read(struct mmc_test_card *test) +{ +	int ret, i; + +	if (!test->card->csd.read_partial) +		return RESULT_UNSUP_CARD; + +	for (i = 1; i < 512;i <<= 1) { +		ret = mmc_test_verified_transfer(test, 0, +			test->buffer, 0, 1, i); +		if (ret) +			return ret; +	} + +	return 0; +} + +static int mmc_test_weird_write(struct mmc_test_card *test) +{ +	int ret, i; + +	if (!test->card->csd.write_partial) +		return RESULT_UNSUP_CARD; + +	for (i = 3; i < 512;i += 7) { +		ret = mmc_test_verified_transfer(test, 1, +			test->buffer, 0, 1, i); +		if (ret) +			return ret; +	} + +	return 0; +} + +static int mmc_test_weird_read(struct mmc_test_card *test) +{ +	int ret, i; + +	if (!test->card->csd.read_partial) +		return RESULT_UNSUP_CARD; + +	for (i = 3; i < 512;i += 7) { +		ret = mmc_test_verified_transfer(test, 0, +			test->buffer, 0, 1, i); +		if (ret) +			return ret; +	} + +	return 0; +} + +static int mmc_test_align_write(struct mmc_test_card *test) +{ +	int ret, i; + +	for (i = 1;i < 4;i++) { +		ret = mmc_test_verified_transfer(test, 1, test->buffer + i, +			0, 1, 512); +		if (ret) +			return ret; +	} + +	return 0; +} + +static int mmc_test_align_read(struct mmc_test_card *test) +{ +	int ret, i; + +	for (i = 1;i < 4;i++) { +		ret = mmc_test_verified_transfer(test, 0, test->buffer + i, +			0, 1, 512); +		if (ret) +			return ret; +	} + +	return 0; +} + +static int mmc_test_align_multi_write(struct mmc_test_card *test) +{ +	int ret, i; +	unsigned int size; + +	if (test->card->host->max_blk_count == 1) +		return RESULT_UNSUP_HOST; + +	size = PAGE_SIZE * 2; +	size = min(size, test->card->host->max_req_size); +	size = min(size, test->card->host->max_seg_size); +	size = min(size, test->card->host->max_blk_count * 512); + +	if (size < 1024) +		return RESULT_UNSUP_HOST; + +	for (i = 1;i < 4;i++) { +		ret = mmc_test_verified_transfer(test, 1, test->buffer + i, +			0, size / 512, 512); +		if (ret) +			return ret; +	} + +	return 0; +} + +static int mmc_test_align_multi_read(struct mmc_test_card *test) +{ +	int ret, i; +	unsigned int size; + +	if (test->card->host->max_blk_count == 1) +		return RESULT_UNSUP_HOST; + +	size = PAGE_SIZE * 2; +	size = min(size, test->card->host->max_req_size); +	size = min(size, test->card->host->max_seg_size); +	size = min(size, test->card->host->max_blk_count * 512); + +	if (size < 1024) +		return RESULT_UNSUP_HOST; + +	for (i = 1;i < 4;i++) { +		ret = mmc_test_verified_transfer(test, 0, test->buffer + i, +			0, size / 512, 512); +		if (ret) +			return ret; +	} + +	return 0; +} + +static int mmc_test_xfersize_write(struct mmc_test_card *test) +{ +	int ret; + +	ret = mmc_test_set_blksize(test, 512); +	if (ret) +		return ret; + +	ret = __mmc_test_transfer(test, 1, 1, test->buffer, 0, 1, 512); +	if (ret) +		return ret; + +	return 0; +} + +static int mmc_test_xfersize_read(struct mmc_test_card *test) +{ +	int ret; + +	ret = mmc_test_set_blksize(test, 512); +	if (ret) +		return ret; + +	ret = __mmc_test_transfer(test, 0, 1, test->buffer, 0, 1, 512); +	if (ret) +		return ret; + +	return 0; +} + +static int mmc_test_multi_xfersize_write(struct mmc_test_card *test) +{ +	int ret; + +	if (test->card->host->max_blk_count == 1) +		return RESULT_UNSUP_HOST; + +	ret = mmc_test_set_blksize(test, 512); +	if (ret) +		return ret; + +	ret = __mmc_test_transfer(test, 1, 1, test->buffer, 0, 2, 512); +	if (ret) +		return ret; + +	return 0; +} + +static int mmc_test_multi_xfersize_read(struct mmc_test_card *test) +{ +	int ret; + +	if (test->card->host->max_blk_count == 1) +		return RESULT_UNSUP_HOST; + +	ret = mmc_test_set_blksize(test, 512); +	if (ret) +		return ret; + +	ret = __mmc_test_transfer(test, 0, 1, test->buffer, 0, 2, 512); +	if (ret) +		return ret; + +	return 0; +} + +static const struct mmc_test_case mmc_test_cases[] = { +	{ +		.name = "Basic write (no data verification)", +		.run = mmc_test_basic_write, +	}, + +	{ +		.name = "Basic read (no data verification)", +		.run = mmc_test_basic_read, +	}, + +	{ +		.name = "Basic write (with data verification)", +		.prepare = mmc_test_prepare_verify_write, +		.run = mmc_test_verify_write, +		.cleanup = mmc_test_cleanup_verify, +	}, + +	{ +		.name = "Basic read (with data verification)", +		.prepare = mmc_test_prepare_verify_read, +		.run = mmc_test_verify_read, +		.cleanup = mmc_test_cleanup_verify, +	}, + +	{ +		.name = "Multi-block write", +		.prepare = mmc_test_prepare_verify_write, +		.run = mmc_test_multi_write, +		.cleanup = mmc_test_cleanup_verify, +	}, + +	{ +		.name = "Multi-block read", +		.prepare = mmc_test_prepare_verify_read, +		.run = mmc_test_multi_read, +		.cleanup = mmc_test_cleanup_verify, +	}, + +	{ +		.name = "Power of two block writes", +		.prepare = mmc_test_prepare_verify_write, +		.run = mmc_test_pow2_write, +		.cleanup = mmc_test_cleanup_verify, +	}, + +	{ +		.name = "Power of two block reads", +		.prepare = mmc_test_prepare_verify_read, +		.run = mmc_test_pow2_read, +		.cleanup = mmc_test_cleanup_verify, +	}, + +	{ +		.name = "Weird sized block writes", +		.prepare = mmc_test_prepare_verify_write, +		.run = mmc_test_weird_write, +		.cleanup = mmc_test_cleanup_verify, +	}, + +	{ +		.name = "Weird sized block reads", +		.prepare = mmc_test_prepare_verify_read, +		.run = mmc_test_weird_read, +		.cleanup = mmc_test_cleanup_verify, +	}, + +	{ +		.name = "Badly aligned write", +		.prepare = mmc_test_prepare_verify_write, +		.run = mmc_test_align_write, +		.cleanup = mmc_test_cleanup_verify, +	}, + +	{ +		.name = "Badly aligned read", +		.prepare = mmc_test_prepare_verify_read, +		.run = mmc_test_align_read, +		.cleanup = mmc_test_cleanup_verify, +	}, + +	{ +		.name = "Badly aligned multi-block write", +		.prepare = mmc_test_prepare_verify_write, +		.run = mmc_test_align_multi_write, +		.cleanup = mmc_test_cleanup_verify, +	}, + +	{ +		.name = "Badly aligned multi-block read", +		.prepare = mmc_test_prepare_verify_read, +		.run = mmc_test_align_multi_read, +		.cleanup = mmc_test_cleanup_verify, +	}, + +	{ +		.name = "Correct xfer_size at write (start failure)", +		.run = mmc_test_xfersize_write, +	}, + +	{ +		.name = "Correct xfer_size at read (start failure)", +		.run = mmc_test_xfersize_read, +	}, + +	{ +		.name = "Correct xfer_size at write (midway failure)", +		.run = mmc_test_multi_xfersize_write, +	}, + +	{ +		.name = "Correct xfer_size at read (midway failure)", +		.run = mmc_test_multi_xfersize_read, +	}, +}; + +static struct mutex mmc_test_lock; + +static void mmc_test_run(struct mmc_test_card *test) +{ +	int i, ret; + +	printk(KERN_INFO "%s: Starting tests of card %s...\n", +		mmc_hostname(test->card->host), mmc_card_id(test->card)); + +	mmc_claim_host(test->card->host); + +	for (i = 0;i < ARRAY_SIZE(mmc_test_cases);i++) { +		printk(KERN_INFO "%s: Test case %d. %s...\n", +			mmc_hostname(test->card->host), i + 1, +			mmc_test_cases[i].name); + +		if (mmc_test_cases[i].prepare) { +			ret = mmc_test_cases[i].prepare(test); +			if (ret) { +				printk(KERN_INFO "%s: Result: Prepare " +					"stage failed! (%d)\n", +					mmc_hostname(test->card->host), +					ret); +				continue; +			} +		} + +		ret = mmc_test_cases[i].run(test); +		switch (ret) { +		case RESULT_OK: +			printk(KERN_INFO "%s: Result: OK\n", +				mmc_hostname(test->card->host)); +			break; +		case RESULT_FAIL: +			printk(KERN_INFO "%s: Result: FAILED\n", +				mmc_hostname(test->card->host)); +			break; +		case RESULT_UNSUP_HOST: +			printk(KERN_INFO "%s: Result: UNSUPPORTED " +				"(by host)\n", +				mmc_hostname(test->card->host)); +			break; +		case RESULT_UNSUP_CARD: +			printk(KERN_INFO "%s: Result: UNSUPPORTED " +				"(by card)\n", +				mmc_hostname(test->card->host)); +			break; +		default: +			printk(KERN_INFO "%s: Result: ERROR (%d)\n", +				mmc_hostname(test->card->host), ret); +		} + +		if (mmc_test_cases[i].cleanup) { +			ret = mmc_test_cases[i].cleanup(test); +			if (ret) { +				printk(KERN_INFO "%s: Warning: Cleanup " +					"stage failed! (%d)\n", +					mmc_hostname(test->card->host), +					ret); +			} +		} +	} + +	mmc_release_host(test->card->host); + +	printk(KERN_INFO "%s: Tests completed.\n", +		mmc_hostname(test->card->host)); +} + +static ssize_t mmc_test_show(struct device *dev, +	struct device_attribute *attr, char *buf) +{ +	mutex_lock(&mmc_test_lock); +	mutex_unlock(&mmc_test_lock); + +	return 0; +} + +static ssize_t mmc_test_store(struct device *dev, +	struct device_attribute *attr, const char *buf, size_t count) +{ +	struct mmc_card *card; +	struct mmc_test_card *test; + +	card = container_of(dev, struct mmc_card, dev); + +	test = kzalloc(sizeof(struct mmc_test_card), GFP_KERNEL); +	if (!test) +		return -ENOMEM; + +	test->card = card; + +	test->buffer = kzalloc(BUFFER_SIZE, GFP_KERNEL); +	if (test->buffer) { +		mutex_lock(&mmc_test_lock); +		mmc_test_run(test); +		mutex_unlock(&mmc_test_lock); +	} + +	kfree(test->buffer); +	kfree(test); + +	return count; +} + +static DEVICE_ATTR(test, S_IWUSR | S_IRUGO, mmc_test_show, mmc_test_store); + +static int mmc_test_probe(struct mmc_card *card) +{ +	int ret; + +	mutex_init(&mmc_test_lock); + +	ret = device_create_file(&card->dev, &dev_attr_test); +	if (ret) +		return ret; + +	return 0; +} + +static void mmc_test_remove(struct mmc_card *card) +{ +	device_remove_file(&card->dev, &dev_attr_test); +} + +static struct mmc_driver mmc_driver = { +	.drv		= { +		.name	= "mmc_test", +	}, +	.probe		= mmc_test_probe, +	.remove		= mmc_test_remove, +}; + +static int __init mmc_test_init(void) +{ +	return mmc_register_driver(&mmc_driver); +} + +static void __exit mmc_test_exit(void) +{ +	mmc_unregister_driver(&mmc_driver); +} + +module_init(mmc_test_init); +module_exit(mmc_test_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Multimedia Card (MMC) host test driver"); +MODULE_AUTHOR("Pierre Ossman"); diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index 3b3cd0e7471..dead61754ad 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -119,7 +119,7 @@ config MMC_TIFM_SD  config MMC_SPI  	tristate "MMC/SD over SPI" -	depends on MMC && SPI_MASTER && !HIGHMEM +	depends on MMC && SPI_MASTER && !HIGHMEM && HAS_DMA  	select CRC7  	select CRC_ITU_T  	help diff --git a/drivers/mmc/host/at91_mci.c b/drivers/mmc/host/at91_mci.c index a28fc2f68ce..8979ad330a4 100644 --- a/drivers/mmc/host/at91_mci.c +++ b/drivers/mmc/host/at91_mci.c @@ -663,9 +663,12 @@ static void at91_mci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)  				gpio_set_value(host->board->vcc_pin, 0);  				break;  			case MMC_POWER_UP: -			case MMC_POWER_ON:  				gpio_set_value(host->board->vcc_pin, 1);  				break; +			case MMC_POWER_ON: +				break; +			default: +				WARN_ON(1);  		}  	}  } diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 626ac083f4e..da5fecad74d 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -425,7 +425,7 @@ static void mmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)  			host->cclk = host->mclk;  		} else {  			clk = host->mclk / (2 * ios->clock) - 1; -			if (clk > 256) +			if (clk >= 256)  				clk = 255;  			host->cclk = host->mclk / (2 * (clk + 1));  		} @@ -512,6 +512,18 @@ static int mmci_probe(struct amba_device *dev, void *id)  	host->plat = plat;  	host->mclk = clk_get_rate(host->clk); +	/* +	 * According to the spec, mclk is max 100 MHz, +	 * so we try to adjust the clock down to this, +	 * (if possible). +	 */ +	if (host->mclk > 100000000) { +		ret = clk_set_rate(host->clk, 100000000); +		if (ret < 0) +			goto clk_disable; +		host->mclk = clk_get_rate(host->clk); +		DBG(host, "eventual mclk rate: %u Hz\n", host->mclk); +	}  	host->mmc = mmc;  	host->base = ioremap(dev->res.start, SZ_4K);  	if (!host->base) { diff --git a/drivers/mmc/host/omap.c b/drivers/mmc/host/omap.c index 14759e9f42a..549517c3567 100644 --- a/drivers/mmc/host/omap.c +++ b/drivers/mmc/host/omap.c @@ -1003,7 +1003,7 @@ static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data)  static int mmc_omap_get_dma_channel(struct mmc_omap_host *host, struct mmc_data *data)  { -	const char *dev_name; +	const char *dma_dev_name;  	int sync_dev, dma_ch, is_read, r;  	is_read = !(data->flags & MMC_DATA_WRITE); @@ -1018,21 +1018,21 @@ static int mmc_omap_get_dma_channel(struct mmc_omap_host *host, struct mmc_data  	if (is_read) {  		if (host->id == 1) {  			sync_dev = OMAP_DMA_MMC_RX; -			dev_name = "MMC1 read"; +			dma_dev_name = "MMC1 read";  		} else {  			sync_dev = OMAP_DMA_MMC2_RX; -			dev_name = "MMC2 read"; +			dma_dev_name = "MMC2 read";  		}  	} else {  		if (host->id == 1) {  			sync_dev = OMAP_DMA_MMC_TX; -			dev_name = "MMC1 write"; +			dma_dev_name = "MMC1 write";  		} else {  			sync_dev = OMAP_DMA_MMC2_TX; -			dev_name = "MMC2 write"; +			dma_dev_name = "MMC2 write";  		}  	} -	r = omap_request_dma(sync_dev, dev_name, mmc_omap_dma_cb, +	r = omap_request_dma(sync_dev, dma_dev_name, mmc_omap_dma_cb,  			     host, &dma_ch);  	if (r != 0) {  		dev_dbg(mmc_dev(host->mmc), "omap_request_dma() failed with %d\n", r); diff --git a/drivers/mmc/host/pxamci.c b/drivers/mmc/host/pxamci.c index 65210fca37e..d89475d3698 100644 --- a/drivers/mmc/host/pxamci.c +++ b/drivers/mmc/host/pxamci.c @@ -114,6 +114,7 @@ static void pxamci_setup_data(struct pxamci_host *host, struct mmc_data *data)  	unsigned int nob = data->blocks;  	unsigned long long clks;  	unsigned int timeout; +	bool dalgn = 0;  	u32 dcmd;  	int i; @@ -152,6 +153,9 @@ static void pxamci_setup_data(struct pxamci_host *host, struct mmc_data *data)  		host->sg_cpu[i].dcmd = dcmd | length;  		if (length & 31 && !(data->flags & MMC_DATA_READ))  			host->sg_cpu[i].dcmd |= DCMD_ENDIRQEN; +		/* Not aligned to 8-byte boundary? */ +		if (sg_dma_address(&data->sg[i]) & 0x7) +			dalgn = 1;  		if (data->flags & MMC_DATA_READ) {  			host->sg_cpu[i].dsadr = host->res->start + MMC_RXFIFO;  			host->sg_cpu[i].dtadr = sg_dma_address(&data->sg[i]); @@ -165,6 +169,15 @@ static void pxamci_setup_data(struct pxamci_host *host, struct mmc_data *data)  	host->sg_cpu[host->dma_len - 1].ddadr = DDADR_STOP;  	wmb(); +	/* +	 * The PXA27x DMA controller encounters overhead when working with +	 * unaligned (to 8-byte boundaries) data, so switch on byte alignment +	 * mode only if we have unaligned data. +	 */ +	if (dalgn) +		DALGN |= (1 << host->dma); +	else +		DALGN &= (1 << host->dma);  	DDADR(host->dma) = host->sg_dma;  	DCSR(host->dma) = DCSR_RUN;  } diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c index 07c2048b230..b413aa6c246 100644 --- a/drivers/mmc/host/sdhci.c +++ b/drivers/mmc/host/sdhci.c @@ -55,6 +55,10 @@ static unsigned int debug_quirks = 0;  #define SDHCI_QUIRK_32BIT_DMA_SIZE			(1<<7)  /* Controller needs to be reset after each request to stay stable */  #define SDHCI_QUIRK_RESET_AFTER_REQUEST			(1<<8) +/* Controller needs voltage and power writes to happen separately */ +#define SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER		(1<<9) +/* Controller has an off-by-one issue with timeout value */ +#define SDHCI_QUIRK_INCR_TIMEOUT_CONTROL		(1<<10)  static const struct pci_device_id pci_ids[] __devinitdata = {  	{ @@ -115,7 +119,8 @@ static const struct pci_device_id pci_ids[] __devinitdata = {  		.subvendor      = PCI_ANY_ID,  		.subdevice      = PCI_ANY_ID,  		.driver_data    = SDHCI_QUIRK_SINGLE_POWER_WRITE | -				  SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS, +				  SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS | +				  SDHCI_QUIRK_BROKEN_DMA,  	},  	{ @@ -124,7 +129,17 @@ static const struct pci_device_id pci_ids[] __devinitdata = {  		.subvendor      = PCI_ANY_ID,  		.subdevice      = PCI_ANY_ID,  		.driver_data    = SDHCI_QUIRK_SINGLE_POWER_WRITE | -				  SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS, +				  SDHCI_QUIRK_RESET_CMD_DATA_ON_IOS | +				  SDHCI_QUIRK_BROKEN_DMA, +	}, + +	{ +		.vendor         = PCI_VENDOR_ID_MARVELL, +		.device         = PCI_DEVICE_ID_MARVELL_CAFE_SD, +		.subvendor      = PCI_ANY_ID, +		.subdevice      = PCI_ANY_ID, +		.driver_data    = SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER | +				  SDHCI_QUIRK_INCR_TIMEOUT_CONTROL,  	},  	{ @@ -469,6 +484,13 @@ static void sdhci_prepare_data(struct sdhci_host *host, struct mmc_data *data)  			break;  	} +	/* +	 * Compensate for an off-by-one error in the CaFe hardware; otherwise, +	 * a too-small count gives us interrupt timeouts. +	 */ +	if ((host->chip->quirks & SDHCI_QUIRK_INCR_TIMEOUT_CONTROL)) +		count++; +  	if (count >= 0xF) {  		printk(KERN_WARNING "%s: Too large timeout requested!\n",  			mmc_hostname(host->mmc)); @@ -774,6 +796,14 @@ static void sdhci_set_power(struct sdhci_host *host, unsigned short power)  		BUG();  	} +	/* +	 * At least the CaFe chip gets confused if we set the voltage +	 * and set turn on power at the same time, so set the voltage first. +	 */ +	if ((host->chip->quirks & SDHCI_QUIRK_NO_SIMULT_VDD_AND_POWER)) +		writeb(pwr & ~SDHCI_POWER_ON, +				host->ioaddr + SDHCI_POWER_CONTROL); +  	writeb(pwr, host->ioaddr + SDHCI_POWER_CONTROL);  out: diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h index 7fb02e177a3..299118de893 100644 --- a/drivers/mmc/host/sdhci.h +++ b/drivers/mmc/host/sdhci.h @@ -187,7 +187,7 @@ struct sdhci_host {  	struct mmc_request	*mrq;		/* Current request */  	struct mmc_command	*cmd;		/* Current command */  	struct mmc_data		*data;		/* Current data request */ -	int			data_early:1;	/* Data finished before cmd */ +	unsigned int		data_early:1;	/* Data finished before cmd */  	struct scatterlist	*cur_sg;	/* We're working on this */  	int			num_sg;		/* Entries left */ diff --git a/drivers/mmc/host/wbsd.c b/drivers/mmc/host/wbsd.c index be624a049c6..c303e7f57ab 100644 --- a/drivers/mmc/host/wbsd.c +++ b/drivers/mmc/host/wbsd.c @@ -1457,17 +1457,7 @@ static int __devinit wbsd_request_irq(struct wbsd_host *host, int irq)  	int ret;  	/* -	 * Allocate interrupt. -	 */ - -	ret = request_irq(irq, wbsd_irq, IRQF_SHARED, DRIVER_NAME, host); -	if (ret) -		return ret; - -	host->irq = irq; - -	/* -	 * Set up tasklets. +	 * Set up tasklets. Must be done before requesting interrupt.  	 */  	tasklet_init(&host->card_tasklet, wbsd_tasklet_card,  			(unsigned long)host); @@ -1480,6 +1470,15 @@ static int __devinit wbsd_request_irq(struct wbsd_host *host, int irq)  	tasklet_init(&host->finish_tasklet, wbsd_tasklet_finish,  			(unsigned long)host); +	/* +	 * Allocate interrupt. +	 */ +	ret = request_irq(irq, wbsd_irq, IRQF_SHARED, DRIVER_NAME, host); +	if (ret) +		return ret; + +	host->irq = irq; +  	return 0;  }  |