| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* dilnetpc.c -- MTD map driver for SSV DIL/Net PC Boards "DNP" and "ADNP" | 
 | 2 |  * | 
 | 3 |  * This program is free software; you can redistribute it and/or modify | 
 | 4 |  * it under the terms of the GNU General Public License as published by | 
 | 5 |  * the Free Software Foundation; either version 2 of the License, or | 
 | 6 |  * (at your option) any later version. | 
 | 7 |  * | 
 | 8 |  * This program is distributed in the hope that it will be useful, | 
 | 9 |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | 10 |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 | 11 |  * GNU General Public License for more details. | 
 | 12 |  * | 
 | 13 |  * You should have received a copy of the GNU General Public License | 
 | 14 |  * along with this program; if not, write to the Free Software | 
 | 15 |  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA | 
 | 16 |  * | 
 | 17 |  * $Id: dilnetpc.c,v 1.17 2004/11/28 09:40:39 dwmw2 Exp $ | 
 | 18 |  * | 
 | 19 |  * The DIL/Net PC is a tiny embedded PC board made by SSV Embedded Systems | 
 | 20 |  * featuring the AMD Elan SC410 processor. There are two variants of this | 
 | 21 |  * board: DNP/1486 and ADNP/1486. The DNP version has 2 megs of flash | 
 | 22 |  * ROM (Intel 28F016S3) and 8 megs of DRAM, the ADNP version has 4 megs | 
 | 23 |  * flash and 16 megs of RAM. | 
 | 24 |  * For details, see http://www.ssv-embedded.de/ssv/pc104/p169.htm | 
 | 25 |  * and http://www.ssv-embedded.de/ssv/pc104/p170.htm | 
 | 26 |  */ | 
 | 27 |  | 
 | 28 | #include <linux/config.h> | 
 | 29 | #include <linux/module.h> | 
 | 30 | #include <linux/types.h> | 
 | 31 | #include <linux/kernel.h> | 
 | 32 | #include <linux/init.h> | 
 | 33 | #include <asm/io.h> | 
 | 34 | #include <linux/mtd/mtd.h> | 
 | 35 | #include <linux/mtd/map.h> | 
 | 36 | #include <linux/mtd/partitions.h> | 
 | 37 | #include <linux/mtd/concat.h> | 
 | 38 |  | 
 | 39 | /* | 
 | 40 | ** The DIL/NetPC keeps its BIOS in two distinct flash blocks. | 
 | 41 | ** Destroying any of these blocks transforms the DNPC into | 
 | 42 | ** a paperweight (albeit not a very useful one, considering | 
 | 43 | ** it only weighs a few grams). | 
 | 44 | ** | 
 | 45 | ** Therefore, the BIOS blocks must never be erased or written to | 
 | 46 | ** except by people who know exactly what they are doing (e.g. | 
 | 47 | ** to install a BIOS update). These partitions are marked read-only | 
 | 48 | ** by default, but can be made read/write by undefining | 
 | 49 | ** DNPC_BIOS_BLOCKS_WRITEPROTECTED: | 
 | 50 | */ | 
 | 51 | #define DNPC_BIOS_BLOCKS_WRITEPROTECTED | 
 | 52 |  | 
 | 53 | /* | 
 | 54 | ** The ID string (in ROM) is checked to determine whether we | 
 | 55 | ** are running on a DNP/1486 or ADNP/1486 | 
 | 56 | */ | 
 | 57 | #define BIOSID_BASE	0x000fe100 | 
 | 58 |  | 
 | 59 | #define ID_DNPC	"DNP1486" | 
 | 60 | #define ID_ADNP	"ADNP1486" | 
 | 61 |  | 
 | 62 | /* | 
 | 63 | ** Address where the flash should appear in CPU space | 
 | 64 | */ | 
 | 65 | #define FLASH_BASE	0x2000000 | 
 | 66 |  | 
 | 67 | /* | 
 | 68 | ** Chip Setup and Control (CSC) indexed register space | 
 | 69 | */ | 
 | 70 | #define CSC_INDEX	0x22 | 
 | 71 | #define CSC_DATA	0x23 | 
 | 72 |  | 
 | 73 | #define CSC_MMSWAR	0x30	/* MMS window C-F attributes register */ | 
 | 74 | #define CSC_MMSWDSR	0x31	/* MMS window C-F device select register */ | 
 | 75 |  | 
 | 76 | #define CSC_RBWR	0xa7	/* GPIO Read-Back/Write Register B */ | 
 | 77 |  | 
 | 78 | #define CSC_CR		0xd0	/* internal I/O device disable/Echo */ | 
 | 79 | 				/* Z-bus/configuration register */ | 
 | 80 |  | 
 | 81 | #define CSC_PCCMDCR	0xf1	/* PC card mode and DMA control register */ | 
 | 82 |  | 
 | 83 |  | 
 | 84 | /* | 
 | 85 | ** PC Card indexed register space: | 
 | 86 | */ | 
 | 87 |  | 
 | 88 | #define PCC_INDEX	0x3e0 | 
 | 89 | #define PCC_DATA	0x3e1 | 
 | 90 |  | 
 | 91 | #define PCC_AWER_B		0x46	/* Socket B Address Window enable register */ | 
 | 92 | #define PCC_MWSAR_1_Lo	0x58	/* memory window 1 start address low register */ | 
 | 93 | #define PCC_MWSAR_1_Hi	0x59	/* memory window 1 start address high register */ | 
 | 94 | #define PCC_MWEAR_1_Lo	0x5A	/* memory window 1 stop address low register */ | 
 | 95 | #define PCC_MWEAR_1_Hi	0x5B	/* memory window 1 stop address high register */ | 
 | 96 | #define PCC_MWAOR_1_Lo	0x5C	/* memory window 1 address offset low register */ | 
 | 97 | #define PCC_MWAOR_1_Hi	0x5D	/* memory window 1 address offset high register */ | 
 | 98 |  | 
 | 99 |  | 
 | 100 | /* | 
 | 101 | ** Access to SC4x0's Chip Setup and Control (CSC) | 
 | 102 | ** and PC Card (PCC) indexed registers: | 
 | 103 | */ | 
 | 104 | static inline void setcsc(int reg, unsigned char data) | 
 | 105 | { | 
 | 106 | 	outb(reg, CSC_INDEX); | 
 | 107 | 	outb(data, CSC_DATA); | 
 | 108 | } | 
 | 109 |  | 
 | 110 | static inline unsigned char getcsc(int reg) | 
 | 111 | { | 
 | 112 | 	outb(reg, CSC_INDEX); | 
 | 113 | 	return(inb(CSC_DATA)); | 
 | 114 | } | 
 | 115 |  | 
 | 116 | static inline void setpcc(int reg, unsigned char data) | 
 | 117 | { | 
 | 118 | 	outb(reg, PCC_INDEX); | 
 | 119 | 	outb(data, PCC_DATA); | 
 | 120 | } | 
 | 121 |  | 
 | 122 | static inline unsigned char getpcc(int reg) | 
 | 123 | { | 
 | 124 | 	outb(reg, PCC_INDEX); | 
 | 125 | 	return(inb(PCC_DATA)); | 
 | 126 | } | 
 | 127 |  | 
 | 128 |  | 
 | 129 | /* | 
 | 130 | ************************************************************ | 
 | 131 | ** Enable access to DIL/NetPC's flash by mapping it into | 
 | 132 | ** the SC4x0's MMS Window C. | 
 | 133 | ************************************************************ | 
 | 134 | */ | 
 | 135 | static void dnpc_map_flash(unsigned long flash_base, unsigned long flash_size) | 
 | 136 | { | 
 | 137 | 	unsigned long flash_end = flash_base + flash_size - 1; | 
 | 138 |  | 
 | 139 | 	/* | 
 | 140 | 	** enable setup of MMS windows C-F: | 
 | 141 | 	*/ | 
 | 142 | 	/* - enable PC Card indexed register space */ | 
 | 143 | 	setcsc(CSC_CR, getcsc(CSC_CR) | 0x2); | 
 | 144 | 	/* - set PC Card controller to operate in standard mode */ | 
 | 145 | 	setcsc(CSC_PCCMDCR, getcsc(CSC_PCCMDCR) & ~1); | 
 | 146 |  | 
 | 147 | 	/* | 
 | 148 | 	** Program base address and end address of window | 
 | 149 | 	** where the flash ROM should appear in CPU address space | 
 | 150 | 	*/ | 
 | 151 | 	setpcc(PCC_MWSAR_1_Lo, (flash_base >> 12) & 0xff); | 
 | 152 | 	setpcc(PCC_MWSAR_1_Hi, (flash_base >> 20) & 0x3f); | 
 | 153 | 	setpcc(PCC_MWEAR_1_Lo, (flash_end >> 12) & 0xff); | 
 | 154 | 	setpcc(PCC_MWEAR_1_Hi, (flash_end >> 20) & 0x3f); | 
 | 155 |  | 
 | 156 | 	/* program offset of first flash location to appear in this window (0) */ | 
 | 157 | 	setpcc(PCC_MWAOR_1_Lo, ((0 - flash_base) >> 12) & 0xff); | 
 | 158 | 	setpcc(PCC_MWAOR_1_Hi, ((0 - flash_base)>> 20) & 0x3f); | 
 | 159 |  | 
 | 160 | 	/* set attributes for MMS window C: non-cacheable, write-enabled */ | 
 | 161 | 	setcsc(CSC_MMSWAR, getcsc(CSC_MMSWAR) & ~0x11); | 
 | 162 |  | 
 | 163 | 	/* select physical device ROMCS0 (i.e. flash) for MMS Window C */ | 
 | 164 | 	setcsc(CSC_MMSWDSR, getcsc(CSC_MMSWDSR) & ~0x03); | 
 | 165 |  | 
 | 166 | 	/* enable memory window 1 */ | 
 | 167 | 	setpcc(PCC_AWER_B, getpcc(PCC_AWER_B) | 0x02); | 
 | 168 |  | 
 | 169 | 	/* now disable PC Card indexed register space again */ | 
 | 170 | 	setcsc(CSC_CR, getcsc(CSC_CR) & ~0x2); | 
 | 171 | } | 
 | 172 |  | 
 | 173 |  | 
 | 174 | /* | 
 | 175 | ************************************************************ | 
 | 176 | ** Disable access to DIL/NetPC's flash by mapping it into | 
 | 177 | ** the SC4x0's MMS Window C. | 
 | 178 | ************************************************************ | 
 | 179 | */ | 
 | 180 | static void dnpc_unmap_flash(void) | 
 | 181 | { | 
 | 182 | 	/* - enable PC Card indexed register space */ | 
 | 183 | 	setcsc(CSC_CR, getcsc(CSC_CR) | 0x2); | 
 | 184 |  | 
 | 185 | 	/* disable memory window 1 */ | 
 | 186 | 	setpcc(PCC_AWER_B, getpcc(PCC_AWER_B) & ~0x02); | 
 | 187 |  | 
 | 188 | 	/* now disable PC Card indexed register space again */ | 
 | 189 | 	setcsc(CSC_CR, getcsc(CSC_CR) & ~0x2); | 
 | 190 | } | 
 | 191 |  | 
 | 192 |  | 
 | 193 |  | 
 | 194 | /* | 
 | 195 | ************************************************************ | 
 | 196 | ** Enable/Disable VPP to write to flash | 
 | 197 | ************************************************************ | 
 | 198 | */ | 
 | 199 |  | 
 | 200 | static DEFINE_SPINLOCK(dnpc_spin); | 
 | 201 | static int        vpp_counter = 0; | 
 | 202 | /* | 
 | 203 | ** This is what has to be done for the DNP board .. | 
 | 204 | */ | 
 | 205 | static void dnp_set_vpp(struct map_info *not_used, int on) | 
 | 206 | { | 
 | 207 | 	spin_lock_irq(&dnpc_spin); | 
 | 208 |  | 
 | 209 | 	if (on) | 
 | 210 | 	{ | 
 | 211 | 		if(++vpp_counter == 1) | 
 | 212 | 			setcsc(CSC_RBWR, getcsc(CSC_RBWR) & ~0x4); | 
 | 213 | 	} | 
 | 214 | 	else | 
 | 215 | 	{ | 
 | 216 | 		if(--vpp_counter == 0) | 
 | 217 | 			setcsc(CSC_RBWR, getcsc(CSC_RBWR) | 0x4); | 
 | 218 | 		else if(vpp_counter < 0) | 
 | 219 | 			BUG(); | 
 | 220 | 	} | 
 | 221 | 	spin_unlock_irq(&dnpc_spin); | 
 | 222 | } | 
 | 223 |  | 
 | 224 | /* | 
 | 225 | ** .. and this the ADNP version: | 
 | 226 | */ | 
 | 227 | static void adnp_set_vpp(struct map_info *not_used, int on) | 
 | 228 | { | 
 | 229 | 	spin_lock_irq(&dnpc_spin); | 
 | 230 |  | 
 | 231 | 	if (on) | 
 | 232 | 	{ | 
 | 233 | 		if(++vpp_counter == 1) | 
 | 234 | 			setcsc(CSC_RBWR, getcsc(CSC_RBWR) & ~0x8); | 
 | 235 | 	} | 
 | 236 | 	else | 
 | 237 | 	{ | 
 | 238 | 		if(--vpp_counter == 0) | 
 | 239 | 			setcsc(CSC_RBWR, getcsc(CSC_RBWR) | 0x8); | 
 | 240 | 		else if(vpp_counter < 0) | 
 | 241 | 			BUG(); | 
 | 242 | 	} | 
 | 243 | 	spin_unlock_irq(&dnpc_spin); | 
 | 244 | } | 
 | 245 |  | 
 | 246 |  | 
 | 247 |  | 
 | 248 | #define DNP_WINDOW_SIZE		0x00200000	/*  DNP flash size is 2MiB  */ | 
 | 249 | #define ADNP_WINDOW_SIZE	0x00400000	/* ADNP flash size is 4MiB */ | 
 | 250 | #define WINDOW_ADDR		FLASH_BASE | 
 | 251 |  | 
 | 252 | static struct map_info dnpc_map = { | 
 | 253 | 	.name = "ADNP Flash Bank", | 
 | 254 | 	.size = ADNP_WINDOW_SIZE, | 
 | 255 | 	.bankwidth = 1, | 
 | 256 | 	.set_vpp = adnp_set_vpp, | 
 | 257 | 	.phys = WINDOW_ADDR | 
 | 258 | }; | 
 | 259 |  | 
 | 260 | /* | 
 | 261 | ** The layout of the flash is somewhat "strange": | 
 | 262 | ** | 
 | 263 | ** 1.  960 KiB (15 blocks) : Space for ROM Bootloader and user data | 
 | 264 | ** 2.   64 KiB (1 block)   : System BIOS | 
 | 265 | ** 3.  960 KiB (15 blocks) : User Data (DNP model) or | 
 | 266 | ** 3. 3008 KiB (47 blocks) : User Data (ADNP model) | 
 | 267 | ** 4.   64 KiB (1 block)   : System BIOS Entry | 
 | 268 | */ | 
 | 269 |  | 
 | 270 | static struct mtd_partition partition_info[]= | 
 | 271 | { | 
 | 272 | 	{  | 
 | 273 | 		.name =		"ADNP boot",  | 
 | 274 | 		.offset =	0,  | 
 | 275 | 		.size =		0xf0000, | 
 | 276 | 	}, | 
 | 277 | 	{  | 
 | 278 | 		.name =		"ADNP system BIOS",  | 
 | 279 | 		.offset =	MTDPART_OFS_NXTBLK, | 
 | 280 | 		.size =		0x10000, | 
 | 281 | #ifdef DNPC_BIOS_BLOCKS_WRITEPROTECTED | 
 | 282 | 		.mask_flags =	MTD_WRITEABLE, | 
 | 283 | #endif | 
 | 284 | 	}, | 
 | 285 | 	{ | 
 | 286 | 		.name =		"ADNP file system", | 
 | 287 | 		.offset =	MTDPART_OFS_NXTBLK, | 
 | 288 | 		.size =		0x2f0000, | 
 | 289 | 	}, | 
 | 290 | 	{ | 
 | 291 | 		.name =		"ADNP system BIOS entry",  | 
 | 292 | 		.offset =	MTDPART_OFS_NXTBLK, | 
 | 293 | 		.size =		MTDPART_SIZ_FULL, | 
 | 294 | #ifdef DNPC_BIOS_BLOCKS_WRITEPROTECTED | 
 | 295 | 		.mask_flags =	MTD_WRITEABLE, | 
 | 296 | #endif | 
 | 297 | 	}, | 
 | 298 | }; | 
 | 299 |  | 
 | 300 | #define NUM_PARTITIONS (sizeof(partition_info)/sizeof(partition_info[0])) | 
 | 301 |  | 
 | 302 | static struct mtd_info *mymtd; | 
 | 303 | static struct mtd_info *lowlvl_parts[NUM_PARTITIONS]; | 
 | 304 | static struct mtd_info *merged_mtd; | 
 | 305 |  | 
 | 306 | /* | 
 | 307 | ** "Highlevel" partition info: | 
 | 308 | ** | 
 | 309 | ** Using the MTD concat layer, we can re-arrange partitions to our | 
 | 310 | ** liking: we construct a virtual MTD device by concatenating the | 
 | 311 | ** partitions, specifying the sequence such that the boot block | 
 | 312 | ** is immediately followed by the filesystem block (i.e. the stupid | 
 | 313 | ** system BIOS block is mapped to a different place). When re-partitioning | 
 | 314 | ** this concatenated MTD device, we can set the boot block size to | 
 | 315 | ** an arbitrary (though erase block aligned) value i.e. not one that | 
 | 316 | ** is dictated by the flash's physical layout. We can thus set the | 
 | 317 | ** boot block to be e.g. 64 KB (which is fully sufficient if we want | 
 | 318 | ** to boot an etherboot image) or to -say- 1.5 MB if we want to boot | 
 | 319 | ** a large kernel image. In all cases, the remainder of the flash | 
 | 320 | ** is available as file system space. | 
 | 321 | */ | 
 | 322 |  | 
 | 323 | static struct mtd_partition higlvl_partition_info[]= | 
 | 324 | { | 
 | 325 | 	{  | 
 | 326 | 		.name =		"ADNP boot block",  | 
 | 327 | 		.offset =	0,  | 
 | 328 | 		.size =		CONFIG_MTD_DILNETPC_BOOTSIZE, | 
 | 329 | 	}, | 
 | 330 | 	{ | 
 | 331 | 		.name =		"ADNP file system space", | 
 | 332 | 		.offset =	MTDPART_OFS_NXTBLK, | 
 | 333 | 		.size =		ADNP_WINDOW_SIZE-CONFIG_MTD_DILNETPC_BOOTSIZE-0x20000, | 
 | 334 | 	}, | 
 | 335 | 	{  | 
 | 336 | 		.name =		"ADNP system BIOS + BIOS Entry",  | 
 | 337 | 		.offset =	MTDPART_OFS_NXTBLK, | 
 | 338 | 		.size =		MTDPART_SIZ_FULL, | 
 | 339 | #ifdef DNPC_BIOS_BLOCKS_WRITEPROTECTED | 
 | 340 | 		.mask_flags =	MTD_WRITEABLE, | 
 | 341 | #endif | 
 | 342 | 	}, | 
 | 343 | }; | 
 | 344 |  | 
 | 345 | #define NUM_HIGHLVL_PARTITIONS (sizeof(higlvl_partition_info)/sizeof(partition_info[0])) | 
 | 346 |  | 
 | 347 |  | 
 | 348 | static int dnp_adnp_probe(void) | 
 | 349 | { | 
 | 350 | 	char *biosid, rc = -1; | 
 | 351 |  | 
 | 352 | 	biosid = (char*)ioremap(BIOSID_BASE, 16); | 
 | 353 | 	if(biosid) | 
 | 354 | 	{ | 
 | 355 | 		if(!strcmp(biosid, ID_DNPC)) | 
 | 356 | 			rc = 1;		/* this is a DNPC  */ | 
 | 357 | 		else if(!strcmp(biosid, ID_ADNP)) | 
 | 358 | 			rc = 0;		/* this is a ADNPC */ | 
 | 359 | 	} | 
 | 360 | 	iounmap((void *)biosid); | 
 | 361 | 	return(rc); | 
 | 362 | } | 
 | 363 |  | 
 | 364 |  | 
 | 365 | static int __init init_dnpc(void) | 
 | 366 | { | 
 | 367 | 	int is_dnp; | 
 | 368 |  | 
 | 369 | 	/* | 
 | 370 | 	** determine hardware (DNP/ADNP/invalid) | 
 | 371 | 	*/	 | 
 | 372 | 	if((is_dnp = dnp_adnp_probe()) < 0) | 
 | 373 | 		return -ENXIO; | 
 | 374 |  | 
 | 375 | 	/* | 
 | 376 | 	** Things are set up for ADNP by default | 
 | 377 | 	** -> modify all that needs to be different for DNP | 
 | 378 | 	*/ | 
 | 379 | 	if(is_dnp) | 
 | 380 | 	{	/* | 
 | 381 | 		** Adjust window size, select correct set_vpp function. | 
 | 382 | 		** The partitioning scheme is identical on both DNP | 
 | 383 | 		** and ADNP except for the size of the third partition. | 
 | 384 | 		*/ | 
 | 385 | 		int i; | 
 | 386 | 		dnpc_map.size          = DNP_WINDOW_SIZE; | 
 | 387 | 		dnpc_map.set_vpp       = dnp_set_vpp; | 
 | 388 | 		partition_info[2].size = 0xf0000; | 
 | 389 |  | 
 | 390 | 		/* | 
 | 391 | 		** increment all string pointers so the leading 'A' gets skipped, | 
 | 392 | 		** thus turning all occurrences of "ADNP ..." into "DNP ..." | 
 | 393 | 		*/ | 
 | 394 | 		++dnpc_map.name; | 
 | 395 | 		for(i = 0; i < NUM_PARTITIONS; i++) | 
 | 396 | 			++partition_info[i].name; | 
 | 397 | 		higlvl_partition_info[1].size = DNP_WINDOW_SIZE -  | 
 | 398 | 			CONFIG_MTD_DILNETPC_BOOTSIZE - 0x20000; | 
 | 399 | 		for(i = 0; i < NUM_HIGHLVL_PARTITIONS; i++) | 
 | 400 | 			++higlvl_partition_info[i].name; | 
 | 401 | 	} | 
 | 402 |  | 
 | 403 | 	printk(KERN_NOTICE "DIL/Net %s flash: 0x%lx at 0x%lx\n",  | 
 | 404 | 		is_dnp ? "DNPC" : "ADNP", dnpc_map.size, dnpc_map.phys); | 
 | 405 |  | 
 | 406 | 	dnpc_map.virt = ioremap_nocache(dnpc_map.phys, dnpc_map.size); | 
 | 407 |  | 
 | 408 | 	dnpc_map_flash(dnpc_map.phys, dnpc_map.size); | 
 | 409 |  | 
 | 410 | 	if (!dnpc_map.virt) { | 
 | 411 | 		printk("Failed to ioremap_nocache\n"); | 
 | 412 | 		return -EIO; | 
 | 413 | 	} | 
 | 414 | 	simple_map_init(&dnpc_map); | 
 | 415 |  | 
 | 416 | 	printk("FLASH virtual address: 0x%p\n", dnpc_map.virt); | 
 | 417 |  | 
 | 418 | 	mymtd = do_map_probe("jedec_probe", &dnpc_map); | 
 | 419 |  | 
 | 420 | 	if (!mymtd) | 
 | 421 | 		mymtd = do_map_probe("cfi_probe", &dnpc_map); | 
 | 422 |  | 
 | 423 | 	/* | 
 | 424 | 	** If flash probes fail, try to make flashes accessible | 
 | 425 | 	** at least as ROM. Ajust erasesize in this case since | 
 | 426 | 	** the default one (128M) will break our partitioning | 
 | 427 | 	*/ | 
 | 428 | 	if (!mymtd) | 
 | 429 | 		if((mymtd = do_map_probe("map_rom", &dnpc_map))) | 
 | 430 | 			mymtd->erasesize = 0x10000; | 
 | 431 |  | 
 | 432 | 	if (!mymtd) { | 
 | 433 | 		iounmap(dnpc_map.virt); | 
 | 434 | 		return -ENXIO; | 
 | 435 | 	} | 
 | 436 | 		 | 
 | 437 | 	mymtd->owner = THIS_MODULE; | 
 | 438 |  | 
 | 439 | 	/* | 
 | 440 | 	** Supply pointers to lowlvl_parts[] array to add_mtd_partitions() | 
 | 441 | 	** -> add_mtd_partitions() will _not_ register MTD devices for | 
 | 442 | 	** the partitions, but will instead store pointers to the MTD | 
 | 443 | 	** objects it creates into our lowlvl_parts[] array. | 
 | 444 | 	** NOTE: we arrange the pointers such that the sequence of the | 
 | 445 | 	**       partitions gets re-arranged: partition #2 follows | 
 | 446 | 	**       partition #0. | 
 | 447 | 	*/ | 
 | 448 | 	partition_info[0].mtdp = &lowlvl_parts[0]; | 
 | 449 | 	partition_info[1].mtdp = &lowlvl_parts[2]; | 
 | 450 | 	partition_info[2].mtdp = &lowlvl_parts[1]; | 
 | 451 | 	partition_info[3].mtdp = &lowlvl_parts[3]; | 
 | 452 |  | 
 | 453 | 	add_mtd_partitions(mymtd, partition_info, NUM_PARTITIONS); | 
 | 454 |  | 
 | 455 | 	/* | 
 | 456 | 	** now create a virtual MTD device by concatenating the for partitions | 
 | 457 | 	** (in the sequence given by the lowlvl_parts[] array. | 
 | 458 | 	*/ | 
 | 459 | 	merged_mtd = mtd_concat_create(lowlvl_parts, NUM_PARTITIONS, "(A)DNP Flash Concatenated"); | 
 | 460 | 	if(merged_mtd) | 
 | 461 | 	{	/* | 
 | 462 | 		** now partition the new device the way we want it. This time, | 
 | 463 | 		** we do not supply mtd pointers in higlvl_partition_info, so | 
 | 464 | 		** add_mtd_partitions() will register the devices. | 
 | 465 | 		*/ | 
 | 466 | 		add_mtd_partitions(merged_mtd, higlvl_partition_info, NUM_HIGHLVL_PARTITIONS); | 
 | 467 | 	} | 
 | 468 |  | 
 | 469 | 	return 0; | 
 | 470 | } | 
 | 471 |  | 
 | 472 | static void __exit cleanup_dnpc(void) | 
 | 473 | { | 
 | 474 | 	if(merged_mtd) { | 
 | 475 | 		del_mtd_partitions(merged_mtd); | 
 | 476 | 		mtd_concat_destroy(merged_mtd); | 
 | 477 | 	} | 
 | 478 |  | 
 | 479 | 	if (mymtd) { | 
 | 480 | 		del_mtd_partitions(mymtd); | 
 | 481 | 		map_destroy(mymtd); | 
 | 482 | 	} | 
 | 483 | 	if (dnpc_map.virt) { | 
 | 484 | 		iounmap(dnpc_map.virt); | 
 | 485 | 		dnpc_unmap_flash(); | 
 | 486 | 		dnpc_map.virt = NULL; | 
 | 487 | 	} | 
 | 488 | } | 
 | 489 |  | 
 | 490 | module_init(init_dnpc); | 
 | 491 | module_exit(cleanup_dnpc); | 
 | 492 |  | 
 | 493 | MODULE_LICENSE("GPL"); | 
 | 494 | MODULE_AUTHOR("Sysgo Real-Time Solutions GmbH"); | 
 | 495 | MODULE_DESCRIPTION("MTD map driver for SSV DIL/NetPC DNP & ADNP"); |