ipaq-flash.c 11.3 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 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 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
/*
 * Flash memory access on iPAQ Handhelds (either SA1100 or PXA250 based)
 *
 * (C) 2000 Nicolas Pitre <nico@fluxnic.net>
 * (C) 2002 Hewlett-Packard Company <jamey.hicks@hp.com>
 * (C) 2003 Christian Pellegrin <chri@ascensit.com>, <chri@infis.univ.ts.it>: concatenation of multiple flashes
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <asm/page.h>
#include <asm/mach-types.h>
#include <asm/system.h>
#include <asm/errno.h>

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

#include <mach/hardware.h>
#include <mach/h3600.h>
#include <asm/io.h>


#ifndef CONFIG_IPAQ_HANDHELD
#error This is for iPAQ Handhelds only
#endif
#ifdef CONFIG_SA1100_JORNADA56X

static void jornada56x_set_vpp(struct map_info *map, int vpp)
{
	if (vpp)
		GPSR = GPIO_GPIO26;
	else
		GPCR = GPIO_GPIO26;
	GPDR |= GPIO_GPIO26;
}

#endif

#ifdef CONFIG_SA1100_JORNADA720

static void jornada720_set_vpp(struct map_info *map, int vpp)
{
	if (vpp)
		PPSR |= 0x80;
	else
		PPSR &= ~0x80;
	PPDR |= 0x80;
}

#endif

#define MAX_IPAQ_CS 2		/* Number of CS we are going to test */

#define IPAQ_MAP_INIT(X) \
	{ \
		name:		"IPAQ flash " X, \
	}


static struct map_info ipaq_map[MAX_IPAQ_CS] = {
	IPAQ_MAP_INIT("bank 1"),
	IPAQ_MAP_INIT("bank 2")
};

static struct mtd_info *my_sub_mtd[MAX_IPAQ_CS] = {
	NULL,
	NULL
};

/*
 * Here are partition information for all known IPAQ-based devices.
 * See include/linux/mtd/partitions.h for definition of the mtd_partition
 * structure.
 *
 * The *_max_flash_size is the maximum possible mapped flash size which
 * is not necessarily the actual flash size.  It must be no more than
 * the value specified in the "struct map_desc *_io_desc" mapping
 * definition for the corresponding machine.
 *
 * Please keep these in alphabetical order, and formatted as per existing
 * entries.  Thanks.
 */

#ifdef CONFIG_IPAQ_HANDHELD
static unsigned long h3xxx_max_flash_size = 0x04000000;
static struct mtd_partition h3xxx_partitions[] = {
	{
		name:		"H3XXX boot firmware",
#ifndef CONFIG_LAB
		size:		0x00040000,
#else
		size:		0x00080000,
#endif
		offset:		0,
#ifndef CONFIG_LAB
		mask_flags:	MTD_WRITEABLE,  /* force read-only */
#endif
	},
	{
		name:		"H3XXX root jffs2",
#ifndef CONFIG_LAB
		size:		0x2000000 - 2*0x40000, /* Warning, this is fixed later */
		offset:		0x00040000,
#else
		size:		0x2000000 - 0x40000 - 0x80000, /* Warning, this is fixed later */
		offset:		0x00080000,
#endif
	},
	{
		name:		"asset",
		size:		0x40000,
		offset:		0x2000000 - 0x40000, /* Warning, this is fixed later */
		mask_flags:	MTD_WRITEABLE,  /* force read-only */
	}
};

#ifndef CONFIG_MTD_CONCAT
static struct mtd_partition h3xxx_partitions_bank2[] = {
	/* this is used only on 2 CS machines when concat is not present */
	{
		name:		"second H3XXX root jffs2",
		size:		0x1000000 - 0x40000, /* Warning, this is fixed later */
		offset:		0x00000000,
	},
	{
		name:		"second asset",
		size:		0x40000,
		offset:		0x1000000 - 0x40000, /* Warning, this is fixed later */
		mask_flags:	MTD_WRITEABLE,  /* force read-only */
	}
};
#endif

static DEFINE_SPINLOCK(ipaq_vpp_lock);

static void h3xxx_set_vpp(struct map_info *map, int vpp)
{
	static int nest = 0;

	spin_lock(&ipaq_vpp_lock);
	if (vpp)
		nest++;
	else
		nest--;
	if (nest)
		assign_h3600_egpio(IPAQ_EGPIO_VPP_ON, 1);
	else
		assign_h3600_egpio(IPAQ_EGPIO_VPP_ON, 0);
	spin_unlock(&ipaq_vpp_lock);
}

#endif

#if defined(CONFIG_SA1100_JORNADA56X) || defined(CONFIG_SA1100_JORNADA720)
static unsigned long jornada_max_flash_size = 0x02000000;
static struct mtd_partition jornada_partitions[] = {
	{
		name:		"Jornada boot firmware",
		size:		0x00040000,
		offset:		0,
		mask_flags:	MTD_WRITEABLE,  /* force read-only */
	}, {
		name:		"Jornada root jffs2",
		size:		MTDPART_SIZ_FULL,
		offset:		0x00040000,
	}
};
#endif


static struct mtd_partition *parsed_parts;
static struct mtd_info *mymtd;

static unsigned long cs_phys[] = {
#ifdef CONFIG_ARCH_SA1100
	SA1100_CS0_PHYS,
	SA1100_CS1_PHYS,
	SA1100_CS2_PHYS,
	SA1100_CS3_PHYS,
	SA1100_CS4_PHYS,
	SA1100_CS5_PHYS,
#else
	PXA_CS0_PHYS,
	PXA_CS1_PHYS,
	PXA_CS2_PHYS,
	PXA_CS3_PHYS,
	PXA_CS4_PHYS,
	PXA_CS5_PHYS,
#endif
};

static const char *part_probes[] = { "cmdlinepart", "RedBoot", NULL };

static int __init h1900_special_case(void);

static int __init ipaq_mtd_init(void)
{
	struct mtd_partition *parts = NULL;
	int nb_parts = 0;
	int parsed_nr_parts = 0;
	const char *part_type;
	int i; /* used when we have >1 flash chips */
	unsigned long tot_flashsize = 0; /* used when we have >1 flash chips */

	/* Default flash bankwidth */
	// ipaq_map.bankwidth = (MSC0 & MSC_RBW) ? 2 : 4;

	if (machine_is_h1900())
	{
		/* For our intents, the h1900 is not a real iPAQ, so we special-case it. */
		return h1900_special_case();
	}

	if (machine_is_h3100() || machine_is_h1900())
		for(i=0; i<MAX_IPAQ_CS; i++)
			ipaq_map[i].bankwidth = 2;
	else
		for(i=0; i<MAX_IPAQ_CS; i++)
			ipaq_map[i].bankwidth = 4;

	/*
	 * Static partition definition selection
	 */
	part_type = "static";

	simple_map_init(&ipaq_map[0]);
	simple_map_init(&ipaq_map[1]);

#ifdef CONFIG_IPAQ_HANDHELD
	if (machine_is_ipaq()) {
		parts = h3xxx_partitions;
		nb_parts = ARRAY_SIZE(h3xxx_partitions);
		for(i=0; i<MAX_IPAQ_CS; i++) {
			ipaq_map[i].size = h3xxx_max_flash_size;
			ipaq_map[i].set_vpp = h3xxx_set_vpp;
			ipaq_map[i].phys = cs_phys[i];
			ipaq_map[i].virt = ioremap(cs_phys[i], 0x04000000);
			if (machine_is_h3100 () || machine_is_h1900())
				ipaq_map[i].bankwidth = 2;
		}
		if (machine_is_h3600()) {
			/* No asset partition here */
			h3xxx_partitions[1].size += 0x40000;
			nb_parts--;
		}
	}
#endif
#ifdef CONFIG_ARCH_H5400
	if (machine_is_h5400()) {
		ipaq_map[0].size = 0x02000000;
		ipaq_map[1].size = 0x02000000;
		ipaq_map[1].phys = 0x02000000;
		ipaq_map[1].virt = ipaq_map[0].virt + 0x02000000;
	}
#endif
#ifdef CONFIG_ARCH_H1900
	if (machine_is_h1900()) {
		ipaq_map[0].size = 0x00400000;
		ipaq_map[1].size = 0x02000000;
		ipaq_map[1].phys = 0x00080000;
		ipaq_map[1].virt = ipaq_map[0].virt + 0x00080000;
	}
#endif

#ifdef CONFIG_SA1100_JORNADA56X
	if (machine_is_jornada56x()) {
		parts = jornada_partitions;
		nb_parts = ARRAY_SIZE(jornada_partitions);
		ipaq_map[0].size = jornada_max_flash_size;
		ipaq_map[0].set_vpp = jornada56x_set_vpp;
		ipaq_map[0].virt = (__u32)ioremap(0x0, 0x04000000);
	}
#endif
#ifdef CONFIG_SA1100_JORNADA720
	if (machine_is_jornada720()) {
		parts = jornada_partitions;
		nb_parts = ARRAY_SIZE(jornada_partitions);
		ipaq_map[0].size = jornada_max_flash_size;
		ipaq_map[0].set_vpp = jornada720_set_vpp;
	}
#endif


	if (machine_is_ipaq()) { /* for iPAQs only */
		for(i=0; i<MAX_IPAQ_CS; i++) {
			printk(KERN_NOTICE "iPAQ flash: probing %d-bit flash bus, window=%lx with CFI.\n", ipaq_map[i].bankwidth*8, ipaq_map[i].virt);
			my_sub_mtd[i] = do_map_probe("cfi_probe", &ipaq_map[i]);
			if (!my_sub_mtd[i]) {
				printk(KERN_NOTICE "iPAQ flash: probing %d-bit flash bus, window=%lx with JEDEC.\n", ipaq_map[i].bankwidth*8, ipaq_map[i].virt);
				my_sub_mtd[i] = do_map_probe("jedec_probe", &ipaq_map[i]);
			}
			if (!my_sub_mtd[i]) {
				printk(KERN_NOTICE "iPAQ flash: failed to find flash.\n");
				if (i)
					break;
				else
					return -ENXIO;
			} else
				printk(KERN_NOTICE "iPAQ flash: found %d bytes\n", my_sub_mtd[i]->size);

			/* do we really need this debugging? --joshua 20030703 */
			// printk("my_sub_mtd[%d]=%p\n", i, my_sub_mtd[i]);
			my_sub_mtd[i]->owner = THIS_MODULE;
			tot_flashsize += my_sub_mtd[i]->size;
		}
#ifdef CONFIG_MTD_CONCAT
		/* fix the asset location */
#	ifdef CONFIG_LAB
		h3xxx_partitions[1].size = tot_flashsize - 0x40000 - 0x80000 /* extra big boot block */;
#	else
		h3xxx_partitions[1].size = tot_flashsize - 2 * 0x40000;
#	endif
		h3xxx_partitions[2].offset = tot_flashsize - 0x40000;
		/* and concat the devices */
		mymtd = mtd_concat_create(&my_sub_mtd[0], i,
					  "ipaq");
		if (!mymtd) {
			printk("Cannot create iPAQ concat device\n");
			return -ENXIO;
		}
#else
		mymtd = my_sub_mtd[0];

		/*
		 *In the very near future, command line partition parsing
		 * will use the device name as 'mtd-id' instead of a value
		 * passed to the parse_cmdline_partitions() routine. Since
		 * the bootldr says 'ipaq', make sure it continues to work.
		 */
		mymtd->name = "ipaq";

		if ((machine_is_h3600())) {
#	ifdef CONFIG_LAB
			h3xxx_partitions[1].size = my_sub_mtd[0]->size - 0x80000;
#	else
			h3xxx_partitions[1].size = my_sub_mtd[0]->size - 0x40000;
#	endif
			nb_parts = 2;
		} else {
#	ifdef CONFIG_LAB
			h3xxx_partitions[1].size = my_sub_mtd[0]->size - 0x40000 - 0x80000; /* extra big boot block */
#	else
			h3xxx_partitions[1].size = my_sub_mtd[0]->size - 2*0x40000;
#	endif
			h3xxx_partitions[2].offset = my_sub_mtd[0]->size - 0x40000;
		}

		if (my_sub_mtd[1]) {
#	ifdef CONFIG_LAB
			h3xxx_partitions_bank2[0].size = my_sub_mtd[1]->size - 0x80000;
#	else
			h3xxx_partitions_bank2[0].size = my_sub_mtd[1]->size - 0x40000;
#	endif
			h3xxx_partitions_bank2[1].offset = my_sub_mtd[1]->size - 0x40000;
		}
#endif
	}
	else {
		/*
		 * Now let's probe for the actual flash.  Do it here since
		 * specific machine settings might have been set above.
		 */
		printk(KERN_NOTICE "IPAQ flash: probing %d-bit flash bus, window=%lx\n", ipaq_map[0].bankwidth*8, ipaq_map[0].virt);
		mymtd = do_map_probe("cfi_probe", &ipaq_map[0]);
		if (!mymtd)
			return -ENXIO;
		mymtd->owner = THIS_MODULE;
	}


	/*
	 * Dynamic partition selection stuff (might override the static ones)
	 */

	 i = parse_mtd_partitions(mymtd, part_probes, &parsed_parts, 0);

	 if (i > 0) {
		 nb_parts = parsed_nr_parts = i;
		 parts = parsed_parts;
		 part_type = "dynamic";
	 }

	 if (!parts) {
		 printk(KERN_NOTICE "IPAQ flash: no partition info available, registering whole flash at once\n");
		 add_mtd_device(mymtd);
#ifndef CONFIG_MTD_CONCAT
		 if (my_sub_mtd[1])
			 add_mtd_device(my_sub_mtd[1]);
#endif
	 } else {
		 printk(KERN_NOTICE "Using %s partition definition\n", part_type);
		 add_mtd_partitions(mymtd, parts, nb_parts);
#ifndef CONFIG_MTD_CONCAT
		 if (my_sub_mtd[1])
			 add_mtd_partitions(my_sub_mtd[1], h3xxx_partitions_bank2, ARRAY_SIZE(h3xxx_partitions_bank2));
#endif
	 }

	 return 0;
}

static void __exit ipaq_mtd_cleanup(void)
{
	int i;

	if (mymtd) {
		del_mtd_partitions(mymtd);
#ifndef CONFIG_MTD_CONCAT
		if (my_sub_mtd[1])
			del_mtd_partitions(my_sub_mtd[1]);
#endif
		map_destroy(mymtd);
#ifdef CONFIG_MTD_CONCAT
		for(i=0; i<MAX_IPAQ_CS; i++)
#else
			for(i=1; i<MAX_IPAQ_CS; i++)
#endif
			{
				if (my_sub_mtd[i])
					map_destroy(my_sub_mtd[i]);
			}
		kfree(parsed_parts);
	}
}

static int __init h1900_special_case(void)
{
	/* The iPAQ h1900 is a special case - it has weird ROM. */
	simple_map_init(&ipaq_map[0]);
	ipaq_map[0].size = 0x80000;
	ipaq_map[0].set_vpp = h3xxx_set_vpp;
	ipaq_map[0].phys = 0x0;
	ipaq_map[0].virt = ioremap(0x0, 0x04000000);
	ipaq_map[0].bankwidth = 2;

	printk(KERN_NOTICE "iPAQ flash: probing %d-bit flash bus, window=%lx with JEDEC.\n", ipaq_map[0].bankwidth*8, ipaq_map[0].virt);
	mymtd = do_map_probe("jedec_probe", &ipaq_map[0]);
	if (!mymtd)
		return -ENODEV;
	add_mtd_device(mymtd);
	printk(KERN_NOTICE "iPAQ flash: registered h1910 flash\n");

	return 0;
}

module_init(ipaq_mtd_init);
module_exit(ipaq_mtd_cleanup);

MODULE_AUTHOR("Jamey Hicks");
MODULE_DESCRIPTION("IPAQ CFI map driver");
MODULE_LICENSE("MIT");