162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * mtdram - a test mtd device
362306a36Sopenharmony_ci * Author: Alexander Larsson <alex@cendio.se>
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 1999 Alexander Larsson <alex@cendio.se>
662306a36Sopenharmony_ci * Copyright (c) 2005 Joern Engel <joern@wh.fh-wedel.de>
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * This code is GPL
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci#include <linux/ioport.h>
1562306a36Sopenharmony_ci#include <linux/vmalloc.h>
1662306a36Sopenharmony_ci#include <linux/mm.h>
1762306a36Sopenharmony_ci#include <linux/init.h>
1862306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
1962306a36Sopenharmony_ci#include <linux/mtd/mtdram.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic unsigned long total_size = CONFIG_MTDRAM_TOTAL_SIZE;
2262306a36Sopenharmony_cistatic unsigned long erase_size = CONFIG_MTDRAM_ERASE_SIZE;
2362306a36Sopenharmony_cistatic unsigned long writebuf_size = 64;
2462306a36Sopenharmony_ci#define MTDRAM_TOTAL_SIZE (total_size * 1024)
2562306a36Sopenharmony_ci#define MTDRAM_ERASE_SIZE (erase_size * 1024)
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cimodule_param(total_size, ulong, 0);
2862306a36Sopenharmony_ciMODULE_PARM_DESC(total_size, "Total device size in KiB");
2962306a36Sopenharmony_cimodule_param(erase_size, ulong, 0);
3062306a36Sopenharmony_ciMODULE_PARM_DESC(erase_size, "Device erase block size in KiB");
3162306a36Sopenharmony_cimodule_param(writebuf_size, ulong, 0);
3262306a36Sopenharmony_ciMODULE_PARM_DESC(writebuf_size, "Device write buf size in Bytes (Default: 64)");
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci// We could store these in the mtd structure, but we only support 1 device..
3562306a36Sopenharmony_cistatic struct mtd_info *mtd_info;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistatic int check_offs_len(struct mtd_info *mtd, loff_t ofs, uint64_t len)
3862306a36Sopenharmony_ci{
3962306a36Sopenharmony_ci	int ret = 0;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	/* Start address must align on block boundary */
4262306a36Sopenharmony_ci	if (mtd_mod_by_eb(ofs, mtd)) {
4362306a36Sopenharmony_ci		pr_debug("%s: unaligned address\n", __func__);
4462306a36Sopenharmony_ci		ret = -EINVAL;
4562306a36Sopenharmony_ci	}
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	/* Length must align on block boundary */
4862306a36Sopenharmony_ci	if (mtd_mod_by_eb(len, mtd)) {
4962306a36Sopenharmony_ci		pr_debug("%s: length not block aligned\n", __func__);
5062306a36Sopenharmony_ci		ret = -EINVAL;
5162306a36Sopenharmony_ci	}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	return ret;
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic int ram_erase(struct mtd_info *mtd, struct erase_info *instr)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	if (check_offs_len(mtd, instr->addr, instr->len))
5962306a36Sopenharmony_ci		return -EINVAL;
6062306a36Sopenharmony_ci	memset((char *)mtd->priv + instr->addr, 0xff, instr->len);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	return 0;
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic int ram_point(struct mtd_info *mtd, loff_t from, size_t len,
6662306a36Sopenharmony_ci		size_t *retlen, void **virt, resource_size_t *phys)
6762306a36Sopenharmony_ci{
6862306a36Sopenharmony_ci	*virt = mtd->priv + from;
6962306a36Sopenharmony_ci	*retlen = len;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	if (phys) {
7262306a36Sopenharmony_ci		/* limit retlen to the number of contiguous physical pages */
7362306a36Sopenharmony_ci		unsigned long page_ofs = offset_in_page(*virt);
7462306a36Sopenharmony_ci		void *addr = *virt - page_ofs;
7562306a36Sopenharmony_ci		unsigned long pfn1, pfn0 = vmalloc_to_pfn(addr);
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci		*phys = __pfn_to_phys(pfn0) + page_ofs;
7862306a36Sopenharmony_ci		len += page_ofs;
7962306a36Sopenharmony_ci		while (len > PAGE_SIZE) {
8062306a36Sopenharmony_ci			len -= PAGE_SIZE;
8162306a36Sopenharmony_ci			addr += PAGE_SIZE;
8262306a36Sopenharmony_ci			pfn0++;
8362306a36Sopenharmony_ci			pfn1 = vmalloc_to_pfn(addr);
8462306a36Sopenharmony_ci			if (pfn1 != pfn0) {
8562306a36Sopenharmony_ci				*retlen = addr - *virt;
8662306a36Sopenharmony_ci				break;
8762306a36Sopenharmony_ci			}
8862306a36Sopenharmony_ci		}
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	return 0;
9262306a36Sopenharmony_ci}
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistatic int ram_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	return 0;
9762306a36Sopenharmony_ci}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_cistatic int ram_read(struct mtd_info *mtd, loff_t from, size_t len,
10062306a36Sopenharmony_ci		size_t *retlen, u_char *buf)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	memcpy(buf, mtd->priv + from, len);
10362306a36Sopenharmony_ci	*retlen = len;
10462306a36Sopenharmony_ci	return 0;
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic int ram_write(struct mtd_info *mtd, loff_t to, size_t len,
10862306a36Sopenharmony_ci		size_t *retlen, const u_char *buf)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	memcpy((char *)mtd->priv + to, buf, len);
11162306a36Sopenharmony_ci	*retlen = len;
11262306a36Sopenharmony_ci	return 0;
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_cistatic void __exit cleanup_mtdram(void)
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	if (mtd_info) {
11862306a36Sopenharmony_ci		mtd_device_unregister(mtd_info);
11962306a36Sopenharmony_ci		vfree(mtd_info->priv);
12062306a36Sopenharmony_ci		kfree(mtd_info);
12162306a36Sopenharmony_ci	}
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ciint mtdram_init_device(struct mtd_info *mtd, void *mapped_address,
12562306a36Sopenharmony_ci		unsigned long size, const char *name)
12662306a36Sopenharmony_ci{
12762306a36Sopenharmony_ci	memset(mtd, 0, sizeof(*mtd));
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	/* Setup the MTD structure */
13062306a36Sopenharmony_ci	mtd->name = name;
13162306a36Sopenharmony_ci	mtd->type = MTD_RAM;
13262306a36Sopenharmony_ci	mtd->flags = MTD_CAP_RAM;
13362306a36Sopenharmony_ci	mtd->size = size;
13462306a36Sopenharmony_ci	mtd->writesize = 1;
13562306a36Sopenharmony_ci	mtd->writebufsize = writebuf_size;
13662306a36Sopenharmony_ci	mtd->erasesize = MTDRAM_ERASE_SIZE;
13762306a36Sopenharmony_ci	mtd->priv = mapped_address;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	mtd->owner = THIS_MODULE;
14062306a36Sopenharmony_ci	mtd->_erase = ram_erase;
14162306a36Sopenharmony_ci	mtd->_point = ram_point;
14262306a36Sopenharmony_ci	mtd->_unpoint = ram_unpoint;
14362306a36Sopenharmony_ci	mtd->_read = ram_read;
14462306a36Sopenharmony_ci	mtd->_write = ram_write;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	if (mtd_device_register(mtd, NULL, 0))
14762306a36Sopenharmony_ci		return -EIO;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	return 0;
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic int __init init_mtdram(void)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	void *addr;
15562306a36Sopenharmony_ci	int err;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	if (!total_size)
15862306a36Sopenharmony_ci		return -EINVAL;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	/* Allocate some memory */
16162306a36Sopenharmony_ci	mtd_info = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
16262306a36Sopenharmony_ci	if (!mtd_info)
16362306a36Sopenharmony_ci		return -ENOMEM;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	addr = vmalloc(MTDRAM_TOTAL_SIZE);
16662306a36Sopenharmony_ci	if (!addr) {
16762306a36Sopenharmony_ci		kfree(mtd_info);
16862306a36Sopenharmony_ci		mtd_info = NULL;
16962306a36Sopenharmony_ci		return -ENOMEM;
17062306a36Sopenharmony_ci	}
17162306a36Sopenharmony_ci	err = mtdram_init_device(mtd_info, addr, MTDRAM_TOTAL_SIZE, "mtdram test device");
17262306a36Sopenharmony_ci	if (err) {
17362306a36Sopenharmony_ci		vfree(addr);
17462306a36Sopenharmony_ci		kfree(mtd_info);
17562306a36Sopenharmony_ci		mtd_info = NULL;
17662306a36Sopenharmony_ci		return err;
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci	memset(mtd_info->priv, 0xff, MTDRAM_TOTAL_SIZE);
17962306a36Sopenharmony_ci	return err;
18062306a36Sopenharmony_ci}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_cimodule_init(init_mtdram);
18362306a36Sopenharmony_cimodule_exit(cleanup_mtdram);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
18662306a36Sopenharmony_ciMODULE_AUTHOR("Alexander Larsson <alexl@redhat.com>");
18762306a36Sopenharmony_ciMODULE_DESCRIPTION("Simulated MTD driver for testing");
188