All pastes #294011 Raw Edit

Stuff

public text v1 · immutable
#294011 ·published 2006-12-28 02:28 UTC
rendered paste body
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mmc/card.h>
#include <linux/mmc/protocol.h>
#include <linux/mtd/mtd.h>
#include <linux/scatterlist.h>

/*
 * erase a range of erase groups aligned to mtd->erase_size
 */
static int mmc_mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
{
	struct mmc_card *card = mtd->priv;
	struct mmc_command cmd[3] = { {
			.opcode = MMC_ERASE_GROUP_START,
			.arg = instr->addr,
			.flags = MMC_RSP_R1 | MMC_CMD_AC,
		}, {
			.opcode = MMC_ERASE_GROUP_END,
			.arg = instr->addr + instr->len,
			.flags = MMC_RSP_R1 | MMC_CMD_AC,
		}, {
			.opcode = MMC_ERASE,
			.flags = MMC_RSP_R1B | MMC_CMD_AC,
		},
	};
	int err, i;

	dev_dbg(card->dev, "%s: from %ld len %ld\n", __FUNCTION__, from, len);

	instr->state = MTD_ERASING;
	err = mmc_card_claim_host(card);
	if (err)
		goto error;

	for (i=0; i<3; i++) {
		err = mmc_wait_for_cmd(card->host, cmd, 5);
		if (err) {
			dev_err(&card->dev, "%s: error %d in stage %d\n",
				__FUNCTION__, err, i);
			break;
		}
	}
	mmc_card_release_host(card);
	mtd_erase_callback(instr);
error:
	instr->state = err ? MTD_ERASE_FAILED : MTD_ERASE_DONE;
	return err;
}

/*
 * check if a write command was completed correctly
 */
static int mmc_mtd_get_status(struct mmc_card *card)
{
	int err;
	struct mmc_command cmd;

	err = mmc_card_claim_host(card);
	if (err)
		goto error;

	do {
		cmd = (struct mmc_command) {
			.opcode = MMC_SEND_STATUS,
			.arg = card->rca << 16,
			.flags = MMC_RSP_R1 | MMC_CMD_AC,
		};

		err = mmc_wait_for_cmd(card->host, &cmd, 5);
		if (err) {
			dev_err(&card->dev, "error %d requesting status\n", err);
			break;
		}
	} while (!(cmd.resp[0] & R1_READY_FOR_DATA));

	mmc_card_release_host(card);
error:
	return err;
}

/*
 * transfer a block to/from the card. The block needs to be aligned
 * to mtd->writesize. If we want to implement an mtd_writev method,
 * this needs to use stream operations with an appropriate stop
 * command as well.
 */
static int mmc_mtd_transfer_low(struct mmc_card *card, loff_t off, size_t len,
			size_t *retlen, u_char *buf, int write)
{
	struct scatterlist sg;
	struct mmc_data data = {
		.blksz = 1 << card->csd.read_blkbits,
		.blocks = len >> card->csd.read_blkbits,
		.flags = write ? MMC_DATA_WRITE : MMC_DATA_READ,
		.sg = &sg,
		.sg_len = 1,
	};
	struct mmc_command cmd = {
		.arg = off,
		.flags = MMC_RSP_R1 | MMC_CMD_ADTC,
		.opcode = write ? MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK,
	};
	struct mmc_request mrq = {
		.cmd = &cmd,
		.data = &data,
	};
	int ret;

	dev_dbg(&card->dev, "%s off %ld, len %ld\n",
		write ? "write" : "read", off, len);

	sg_init_one(&sg, buf, len);
	mmc_set_data_timeout(&data, card, write);
	mmc_wait_for_req(card->host, &mrq);

	if (cmd.error || data.error) {
		dev_err(&card->dev, "error %d/%d sending read/write command\n",
		        cmd.error, data.error);
		ret = -EIO;
	}

	/* copied from the block driver, don't understand why this is needed */
	if (write)
		ret = mmc_mtd_get_status(card);

	if (!ret)
		*retlen = len;
	return ret;
}

/*
 * do some common sanity checks and locking for the actual transfer
 * function.
 */
static int mmc_mtd_transfer(struct mmc_card *card, loff_t off, size_t len,
			size_t *retlen, u_char *buf, int write)
{
	int ret;

	if (from > mtd->size)
		return -EINVAL;
	if (from + len > mtd->size)
		len = mtd->size - from;

	if (retlen)
		*retlen = 0;

	ret = mmc_card_claim_host(card);
	if (ret) {
		dev_warn(&card->dev, "%s: mmc_card_claim_host returned %d\n",
			__FUNCTION__, ret);
		ret = -EIO;
		goto error;
	}

	ret = mmc_mtd_transfer_low(card, from, len, retlen, buf, write);
	mmc_card_release_host(card);
error:
	return ret;
}

static int mmc_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
		size_t *retlen, u_char *buf)
{
	return mmc_mtd_transfer(mtd->priv, from, len, retlen, buf, 0);
}

static int mmc_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
		size_t *retlen, const u_char *buf)
{
	return mmc_mtd_transfer(mtd->priv, to, len, retlen, (u_char *)buf, 1);
}

/*
 * Initialize an mmc card. We create a new MTD device for each
 * MMC card we find. The operations are rather straightforward,
 * so we don't even need our own data structure to contain the
 * mtd_info.
 */
static int mmc_mtd_probe(struct mmc_card *card)
{
	struct mtd_info *mtd;
	int ret;

	if (!(card->csd.cmdclass & CCC_ERASE))
		return -ENODEV;

	dev_info(&card->dev, "mmc card %s found\n", card->dev.bus_id);

	mtd = kzalloc(GFP_KERNEL, sizeof *mtd);
	if (!mtd)
		return -ENOMEM;

	sprintf(mtd->name, "mmc: %s", card->dev.bus_id);

	/* Don't know if these computations are correct */
	mtd->size = card->csd.capacity << (card->csd.read_blkbits - 9);
	mtd->writesize = 1 << card->csd.write_blkbits;
	mtd->erasesize = mtd->writesize * card->csd.erase_blksize;
	dev_info(&card->dev, "size %d bytes, writesize %d, readsize %d\n",
		mtd->size, mtd->writesize, mtd->erasesize);

	/*
	 * NAND is a relatively good approximation, but with the current
	 * MTD scheme, we might need to get our own type
	 */
	mtd->type = MTD_NANDFLASH;
	mtd->flags = MTD_CAP_NANDFLASH;

	/*
	 * operations that may be worthwhile implementing:
	 *    writev, sync, lock, unlock, suspend, resume
	 * In the mean time, these should be sufficient.
	 */
	mtd->erase = mmc_mtd_erase;
	mtd->write = mmc_mtd_write;
	mtd->writev = default_mtd_writev;
	mtd->read = mmc_mtd_read;

	mtd->owner = THIS_MODULE;
	mtd->priv = card;
	mmc_set_drvdata(card, mtd);

	ret = add_mtd_device(mtd);
	if (ret)
		kfree(mtd);

	return ret;
}

/*
 * Not sure if this is safe. Can we call del_mtd_device while
 * the device is still in use? Do we have a choice?
 */
static void mmc_mtd_remove(struct mmc_card *card)
{
	struct mtd_info *mtd;

	mtd = mmc_get_drvdata(card);
	mmc_mtd_sync(mtd);
	del_mtd_device(mtd);
	kfree(mtd);
}

/*
 * This driver will match any card, so it conflicts with the
 * mmc block driver. It's only possible to load one of them
 * at a time.
 */
static struct mmc_driver mmc_mtd_driver = {
	.drv		= {
		.name	= "mmcmtd",
	},
	.probe		= mmc_mtd_probe,
	.remove		= mmc_mtd_remove,
};

static int __init mmc_blk_init(void)
{
	return mmc_register_driver(&mmc_mtd_driver);
}
module_init(mmc_blk_init);

static void __exit mmc_blk_exit(void)
{
	mmc_unregister_driver(&mmc_mtd_driver);
}
module_exit(mmc_blk_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Multimedia Card (MMC) MTD device driver");
MODULE_AUTHOR("Arnd Bergmann <arnd@arndb.de>");