sa1100-flash.c 6.3 KB
Newer Older
L
Linus Torvalds 已提交
1 2
/*
 * Flash memory access on SA11x0 based devices
3
 *
4
 * (C) 2000 Nicolas Pitre <nico@fluxnic.net>
L
Linus Torvalds 已提交
5 6 7 8 9 10 11 12
 */
#include <linux/module.h>
#include <linux/types.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/slab.h>
13
#include <linux/platform_device.h>
L
Linus Torvalds 已提交
14
#include <linux/err.h>
15
#include <linux/io.h>
L
Linus Torvalds 已提交
16 17 18 19 20 21

#include <linux/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/mtd/partitions.h>
#include <linux/mtd/concat.h>

22
#include <mach/hardware.h>
L
Linus Torvalds 已提交
23 24 25 26 27 28 29
#include <asm/sizes.h>
#include <asm/mach/flash.h>

struct sa_subdev_info {
	char name[16];
	struct map_info map;
	struct mtd_info *mtd;
30
	struct flash_platform_data *plat;
L
Linus Torvalds 已提交
31 32 33 34 35 36 37 38
};

struct sa_info {
	struct mtd_info		*mtd;
	int			num_subdev;
	struct sa_subdev_info	subdev[0];
};

39 40
static DEFINE_SPINLOCK(sa1100_vpp_lock);
static int sa1100_vpp_refcnt;
L
Linus Torvalds 已提交
41 42 43
static void sa1100_set_vpp(struct map_info *map, int on)
{
	struct sa_subdev_info *subdev = container_of(map, struct sa_subdev_info, map);
44 45 46 47 48 49 50 51 52 53 54
	unsigned long flags;

	spin_lock_irqsave(&sa1100_vpp_lock, flags);
	if (on) {
		if (++sa1100_vpp_refcnt == 1)   /* first nested 'on' */
			subdev->plat->set_vpp(1);
	} else {
		if (--sa1100_vpp_refcnt == 0)   /* last nested 'off' */
			subdev->plat->set_vpp(0);
	}
	spin_unlock_irqrestore(&sa1100_vpp_lock, flags);
L
Linus Torvalds 已提交
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
}

static void sa1100_destroy_subdev(struct sa_subdev_info *subdev)
{
	if (subdev->mtd)
		map_destroy(subdev->mtd);
	if (subdev->map.virt)
		iounmap(subdev->map.virt);
	release_mem_region(subdev->map.phys, subdev->map.size);
}

static int sa1100_probe_subdev(struct sa_subdev_info *subdev, struct resource *res)
{
	unsigned long phys;
	unsigned int size;
	int ret;

	phys = res->start;
	size = res->end - phys + 1;

	/*
	 * Retrieve the bankwidth from the MSC registers.
	 * We currently only implement CS0 and CS1 here.
	 */
	switch (phys) {
	default:
		printk(KERN_WARNING "SA1100 flash: unknown base address "
		       "0x%08lx, assuming CS0\n", phys);

	case SA1100_CS0_PHYS:
		subdev->map.bankwidth = (MSC0 & MSC_RBW) ? 2 : 4;
		break;

	case SA1100_CS1_PHYS:
		subdev->map.bankwidth = ((MSC0 >> 16) & MSC_RBW) ? 2 : 4;
		break;
	}

	if (!request_mem_region(phys, size, subdev->name)) {
		ret = -EBUSY;
		goto out;
	}

98
	if (subdev->plat->set_vpp)
L
Linus Torvalds 已提交
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
		subdev->map.set_vpp = sa1100_set_vpp;

	subdev->map.phys = phys;
	subdev->map.size = size;
	subdev->map.virt = ioremap(phys, size);
	if (!subdev->map.virt) {
		ret = -ENOMEM;
		goto err;
	}

	simple_map_init(&subdev->map);

	/*
	 * Now let's probe for the actual flash.  Do it here since
	 * specific machine settings might have been set above.
	 */
115
	subdev->mtd = do_map_probe(subdev->plat->map_name, &subdev->map);
L
Linus Torvalds 已提交
116 117 118 119 120 121
	if (subdev->mtd == NULL) {
		ret = -ENXIO;
		goto err;
	}
	subdev->mtd->owner = THIS_MODULE;

122 123
	printk(KERN_INFO "SA1100 flash: CFI device at 0x%08lx, %uMiB, %d-bit\n",
		phys, (unsigned)(subdev->mtd->size >> 20),
L
Linus Torvalds 已提交
124 125 126 127 128 129 130 131 132 133
		subdev->map.bankwidth * 8);

	return 0;

 err:
	sa1100_destroy_subdev(subdev);
 out:
	return ret;
}

134
static void sa1100_destroy(struct sa_info *info, struct flash_platform_data *plat)
L
Linus Torvalds 已提交
135 136 137 138
{
	int i;

	if (info->mtd) {
139
		mtd_device_unregister(info->mtd);
L
Linus Torvalds 已提交
140 141 142 143 144 145 146
		if (info->mtd != info->subdev[0].mtd)
			mtd_concat_destroy(info->mtd);
	}

	for (i = info->num_subdev - 1; i >= 0; i--)
		sa1100_destroy_subdev(&info->subdev[i]);
	kfree(info);
147 148 149

	if (plat->exit)
		plat->exit();
L
Linus Torvalds 已提交
150 151
}

152 153
static struct sa_info *sa1100_setup_mtd(struct platform_device *pdev,
					struct flash_platform_data *plat)
L
Linus Torvalds 已提交
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
{
	struct sa_info *info;
	int nr, size, i, ret = 0;

	/*
	 * Count number of devices.
	 */
	for (nr = 0; ; nr++)
		if (!platform_get_resource(pdev, IORESOURCE_MEM, nr))
			break;

	if (nr == 0) {
		ret = -ENODEV;
		goto out;
	}

	size = sizeof(struct sa_info) + sizeof(struct sa_subdev_info) * nr;

	/*
	 * Allocate the map_info structs in one go.
	 */
175
	info = kzalloc(size, GFP_KERNEL);
L
Linus Torvalds 已提交
176 177 178 179 180
	if (!info) {
		ret = -ENOMEM;
		goto out;
	}

181 182 183 184 185 186
	if (plat->init) {
		ret = plat->init();
		if (ret)
			goto err;
	}

L
Linus Torvalds 已提交
187 188 189 190 191 192 193 194 195 196 197 198
	/*
	 * Claim and then map the memory regions.
	 */
	for (i = 0; i < nr; i++) {
		struct sa_subdev_info *subdev = &info->subdev[i];
		struct resource *res;

		res = platform_get_resource(pdev, IORESOURCE_MEM, i);
		if (!res)
			break;

		subdev->map.name = subdev->name;
199
		sprintf(subdev->name, "%s-%d", plat->name, i);
200
		subdev->plat = plat;
L
Linus Torvalds 已提交
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220

		ret = sa1100_probe_subdev(subdev, res);
		if (ret)
			break;
	}

	info->num_subdev = i;

	/*
	 * ENXIO is special.  It means we didn't find a chip when we probed.
	 */
	if (ret != 0 && !(ret == -ENXIO && info->num_subdev > 0))
		goto err;

	/*
	 * If we found one device, don't bother with concat support.  If
	 * we found multiple devices, use concat if we have it available,
	 * otherwise fail.  Either way, it'll be called "sa1100".
	 */
	if (info->num_subdev == 1) {
221
		strcpy(info->subdev[0].name, plat->name);
L
Linus Torvalds 已提交
222 223 224 225 226 227 228 229 230 231 232
		info->mtd = info->subdev[0].mtd;
		ret = 0;
	} else if (info->num_subdev > 1) {
		struct mtd_info *cdev[nr];
		/*
		 * We detected multiple devices.  Concatenate them together.
		 */
		for (i = 0; i < info->num_subdev; i++)
			cdev[i] = info->subdev[i].mtd;

		info->mtd = mtd_concat_create(cdev, info->num_subdev,
233
					      plat->name);
L
Linus Torvalds 已提交
234 235 236 237 238 239 240 241
		if (info->mtd == NULL)
			ret = -ENXIO;
	}

	if (ret == 0)
		return info;

 err:
242
	sa1100_destroy(info, plat);
L
Linus Torvalds 已提交
243 244 245 246
 out:
	return ERR_PTR(ret);
}

247
static const char * const part_probes[] = { "cmdlinepart", "RedBoot", NULL };
L
Linus Torvalds 已提交
248

B
Bill Pemberton 已提交
249
static int sa1100_mtd_probe(struct platform_device *pdev)
L
Linus Torvalds 已提交
250
{
251
	struct flash_platform_data *plat = pdev->dev.platform_data;
L
Linus Torvalds 已提交
252
	struct sa_info *info;
253
	int err;
L
Linus Torvalds 已提交
254

255
	if (!plat)
L
Linus Torvalds 已提交
256 257
		return -ENODEV;

258
	info = sa1100_setup_mtd(pdev, plat);
L
Linus Torvalds 已提交
259 260 261 262 263 264 265 266
	if (IS_ERR(info)) {
		err = PTR_ERR(info);
		goto out;
	}

	/*
	 * Partition selection stuff.
	 */
267 268
	mtd_device_parse_register(info->mtd, part_probes, NULL, plat->parts,
				  plat->nr_parts);
269

270
	platform_set_drvdata(pdev, info);
L
Linus Torvalds 已提交
271 272 273 274 275 276
	err = 0;

 out:
	return err;
}

277
static int __exit sa1100_mtd_remove(struct platform_device *pdev)
L
Linus Torvalds 已提交
278
{
279 280
	struct sa_info *info = platform_get_drvdata(pdev);
	struct flash_platform_data *plat = pdev->dev.platform_data;
281 282 283

	sa1100_destroy(info, plat);

L
Linus Torvalds 已提交
284 285 286
	return 0;
}

287
static struct platform_driver sa1100_mtd_driver = {
L
Linus Torvalds 已提交
288 289
	.probe		= sa1100_mtd_probe,
	.remove		= __exit_p(sa1100_mtd_remove),
290
	.driver		= {
291
		.name	= "sa1100-mtd",
292
		.owner	= THIS_MODULE,
293
	},
L
Linus Torvalds 已提交
294 295
};

296
module_platform_driver(sa1100_mtd_driver);
L
Linus Torvalds 已提交
297 298 299 300

MODULE_AUTHOR("Nicolas Pitre");
MODULE_DESCRIPTION("SA1100 CFI map driver");
MODULE_LICENSE("GPL");
301
MODULE_ALIAS("platform:sa1100-mtd");