| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Common Flash Interface support: | 
					
						
							|  |  |  |  *   Intel Extended Vendor Command Set (ID 0x0001) | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * (C) 2000 Red Hat. GPL'd | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2009-09-14 03:25:28 -04:00
										 |  |  |  * 10/10/2000	Nicolas Pitre <nico@fluxnic.net> | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  |  * 	- completely revamped method functions so they are aware and | 
					
						
							|  |  |  |  * 	  independent of the flash geometry (buswidth, interleave, etc.) | 
					
						
							|  |  |  |  * 	- scalability vs code size is completely set at compile-time | 
					
						
							|  |  |  |  * 	  (see include/linux/mtd/cfi.h for selection) | 
					
						
							|  |  |  |  *	- optimized write buffer method | 
					
						
							|  |  |  |  * 02/05/2002	Christopher Hoover <ch@hpl.hp.com>/<ch@murgatroid.com> | 
					
						
							|  |  |  |  *	- reworked lock/unlock/erase support for var size flash | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  |  * 21/03/2007   Rodolfo Giometti <giometti@linux.it> | 
					
						
							|  |  |  |  * 	- auto unlock sectors on resume for auto locking flash on power up | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | #include <linux/types.h>
 | 
					
						
							|  |  |  | #include <linux/kernel.h>
 | 
					
						
							|  |  |  | #include <linux/sched.h>
 | 
					
						
							|  |  |  | #include <linux/init.h>
 | 
					
						
							|  |  |  | #include <asm/io.h>
 | 
					
						
							|  |  |  | #include <asm/byteorder.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/errno.h>
 | 
					
						
							|  |  |  | #include <linux/slab.h>
 | 
					
						
							|  |  |  | #include <linux/delay.h>
 | 
					
						
							|  |  |  | #include <linux/interrupt.h>
 | 
					
						
							| 
									
										
										
										
											2005-04-01 02:59:56 +01:00
										 |  |  | #include <linux/reboot.h>
 | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | #include <linux/bitmap.h>
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #include <linux/mtd/xip.h>
 | 
					
						
							|  |  |  | #include <linux/mtd/map.h>
 | 
					
						
							|  |  |  | #include <linux/mtd/mtd.h>
 | 
					
						
							|  |  |  | #include <linux/mtd/cfi.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* #define CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE */ | 
					
						
							|  |  |  | /* #define CMDSET0001_DISABLE_WRITE_SUSPEND */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // debugging, turns off buffer write mode if set to 1
 | 
					
						
							|  |  |  | #define FORCE_WORD_WRITE 0
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-11-05 15:53:43 +01:00
										 |  |  | /* Intel chips */ | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #define I82802AB	0x00ad
 | 
					
						
							|  |  |  | #define I82802AC	0x00ac
 | 
					
						
							| 
									
										
										
										
											2009-05-17 08:02:17 -03:00
										 |  |  | #define PF38F4476	0x881c
 | 
					
						
							| 
									
										
										
										
											2009-11-05 15:53:43 +01:00
										 |  |  | /* STMicroelectronics chips */ | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #define M50LPW080       0x002F
 | 
					
						
							| 
									
										
										
										
											2008-05-13 14:45:29 -05:00
										 |  |  | #define M50FLW080A	0x0080
 | 
					
						
							|  |  |  | #define M50FLW080B	0x0081
 | 
					
						
							| 
									
										
										
										
											2009-11-05 15:53:37 +01:00
										 |  |  | /* Atmel chips */ | 
					
						
							| 
									
										
										
										
											2007-10-30 16:33:07 +01:00
										 |  |  | #define AT49BV640D	0x02de
 | 
					
						
							| 
									
										
										
										
											2009-11-05 15:53:37 +01:00
										 |  |  | #define AT49BV640DT	0x02db
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | static int cfi_intelext_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); | 
					
						
							|  |  |  | static int cfi_intelext_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *); | 
					
						
							|  |  |  | static int cfi_intelext_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *); | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | static int cfi_intelext_writev(struct mtd_info *, const struct kvec *, unsigned long, loff_t, size_t *); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | static int cfi_intelext_erase_varsize(struct mtd_info *, struct erase_info *); | 
					
						
							|  |  |  | static void cfi_intelext_sync (struct mtd_info *); | 
					
						
							| 
									
										
										
										
											2008-12-10 13:37:21 +00:00
										 |  |  | static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len); | 
					
						
							|  |  |  | static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len); | 
					
						
							| 
									
										
										
										
											2010-06-14 18:10:33 +02:00
										 |  |  | static int cfi_intelext_is_locked(struct mtd_info *mtd, loff_t ofs, | 
					
						
							|  |  |  | 				  uint64_t len); | 
					
						
							| 
									
										
										
										
											2005-03-31 00:57:33 +01:00
										 |  |  | #ifdef CONFIG_MTD_OTP
 | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | static int cfi_intelext_read_fact_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *); | 
					
						
							|  |  |  | static int cfi_intelext_read_user_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *); | 
					
						
							|  |  |  | static int cfi_intelext_write_user_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *); | 
					
						
							|  |  |  | static int cfi_intelext_lock_user_prot_reg (struct mtd_info *, loff_t, size_t); | 
					
						
							|  |  |  | static int cfi_intelext_get_fact_prot_info (struct mtd_info *, | 
					
						
							|  |  |  | 					    struct otp_info *, size_t); | 
					
						
							|  |  |  | static int cfi_intelext_get_user_prot_info (struct mtd_info *, | 
					
						
							|  |  |  | 					    struct otp_info *, size_t); | 
					
						
							| 
									
										
										
										
											2005-03-31 00:57:33 +01:00
										 |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | static int cfi_intelext_suspend (struct mtd_info *); | 
					
						
							|  |  |  | static void cfi_intelext_resume (struct mtd_info *); | 
					
						
							| 
									
										
										
										
											2005-04-01 02:59:56 +01:00
										 |  |  | static int cfi_intelext_reboot (struct notifier_block *, unsigned long, void *); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | static void cfi_intelext_destroy(struct mtd_info *); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct mtd_info *cfi_cmdset_0001(struct map_info *, int); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct mtd_info *cfi_intelext_setup (struct mtd_info *); | 
					
						
							|  |  |  | static int cfi_intelext_partition_fixup(struct mtd_info *, struct cfi_private **); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len, | 
					
						
							| 
									
										
										
										
											2008-04-29 23:26:49 -07:00
										 |  |  | 		     size_t *retlen, void **virt, resource_size_t *phys); | 
					
						
							| 
									
										
										
										
											2012-02-03 13:20:43 +02:00
										 |  |  | static int cfi_intelext_unpoint(struct mtd_info *mtd, loff_t from, size_t len); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | static int chip_ready (struct map_info *map, struct flchip *chip, unsigned long adr, int mode); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode); | 
					
						
							|  |  |  | static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr); | 
					
						
							|  |  |  | #include "fwh_lock.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  *  *********** SETUP AND PROBE BITS  *********** | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct mtd_chip_driver cfi_intelext_chipdrv = { | 
					
						
							|  |  |  | 	.probe		= NULL, /* Not usable directly */ | 
					
						
							|  |  |  | 	.destroy	= cfi_intelext_destroy, | 
					
						
							|  |  |  | 	.name		= "cfi_cmdset_0001", | 
					
						
							|  |  |  | 	.module		= THIS_MODULE | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* #define DEBUG_LOCK_BITS */ | 
					
						
							|  |  |  | /* #define DEBUG_CFI_FEATURES */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef DEBUG_CFI_FEATURES
 | 
					
						
							|  |  |  | static void cfi_tell_features(struct cfi_pri_intelext *extp) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int i; | 
					
						
							| 
									
										
										
										
											2005-08-06 05:40:46 +01:00
										 |  |  | 	printk("  Extended Query version %c.%c\n", extp->MajorVersion, extp->MinorVersion); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	printk("  Feature/Command Support:      %4.4X\n", extp->FeatureSupport); | 
					
						
							|  |  |  | 	printk("     - Chip Erase:              %s\n", extp->FeatureSupport&1?"supported":"unsupported"); | 
					
						
							|  |  |  | 	printk("     - Suspend Erase:           %s\n", extp->FeatureSupport&2?"supported":"unsupported"); | 
					
						
							|  |  |  | 	printk("     - Suspend Program:         %s\n", extp->FeatureSupport&4?"supported":"unsupported"); | 
					
						
							|  |  |  | 	printk("     - Legacy Lock/Unlock:      %s\n", extp->FeatureSupport&8?"supported":"unsupported"); | 
					
						
							|  |  |  | 	printk("     - Queued Erase:            %s\n", extp->FeatureSupport&16?"supported":"unsupported"); | 
					
						
							|  |  |  | 	printk("     - Instant block lock:      %s\n", extp->FeatureSupport&32?"supported":"unsupported"); | 
					
						
							|  |  |  | 	printk("     - Protection Bits:         %s\n", extp->FeatureSupport&64?"supported":"unsupported"); | 
					
						
							|  |  |  | 	printk("     - Page-mode read:          %s\n", extp->FeatureSupport&128?"supported":"unsupported"); | 
					
						
							|  |  |  | 	printk("     - Synchronous read:        %s\n", extp->FeatureSupport&256?"supported":"unsupported"); | 
					
						
							|  |  |  | 	printk("     - Simultaneous operations: %s\n", extp->FeatureSupport&512?"supported":"unsupported"); | 
					
						
							| 
									
										
										
										
											2005-08-06 05:40:46 +01:00
										 |  |  | 	printk("     - Extended Flash Array:    %s\n", extp->FeatureSupport&1024?"supported":"unsupported"); | 
					
						
							|  |  |  | 	for (i=11; i<32; i++) { | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 		if (extp->FeatureSupport & (1<<i)) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			printk("     - Unknown Bit %X:      supported\n", i); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	printk("  Supported functions after Suspend: %2.2X\n", extp->SuspendCmdSupport); | 
					
						
							|  |  |  | 	printk("     - Program after Erase Suspend: %s\n", extp->SuspendCmdSupport&1?"supported":"unsupported"); | 
					
						
							|  |  |  | 	for (i=1; i<8; i++) { | 
					
						
							|  |  |  | 		if (extp->SuspendCmdSupport & (1<<i)) | 
					
						
							|  |  |  | 			printk("     - Unknown Bit %X:               supported\n", i); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	printk("  Block Status Register Mask: %4.4X\n", extp->BlkStatusRegMask); | 
					
						
							|  |  |  | 	printk("     - Lock Bit Active:      %s\n", extp->BlkStatusRegMask&1?"yes":"no"); | 
					
						
							| 
									
										
										
										
											2005-08-06 05:40:46 +01:00
										 |  |  | 	printk("     - Lock-Down Bit Active: %s\n", extp->BlkStatusRegMask&2?"yes":"no"); | 
					
						
							|  |  |  | 	for (i=2; i<3; i++) { | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		if (extp->BlkStatusRegMask & (1<<i)) | 
					
						
							|  |  |  | 			printk("     - Unknown Bit %X Active: yes\n",i); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-08-06 05:40:46 +01:00
										 |  |  | 	printk("     - EFA Lock Bit:         %s\n", extp->BlkStatusRegMask&16?"yes":"no"); | 
					
						
							|  |  |  | 	printk("     - EFA Lock-Down Bit:    %s\n", extp->BlkStatusRegMask&32?"yes":"no"); | 
					
						
							|  |  |  | 	for (i=6; i<16; i++) { | 
					
						
							|  |  |  | 		if (extp->BlkStatusRegMask & (1<<i)) | 
					
						
							|  |  |  | 			printk("     - Unknown Bit %X Active: yes\n",i); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 	printk("  Vcc Logic Supply Optimum Program/Erase Voltage: %d.%d V\n", | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	       extp->VccOptimal >> 4, extp->VccOptimal & 0xf); | 
					
						
							|  |  |  | 	if (extp->VppOptimal) | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 		printk("  Vpp Programming Supply Optimum Program/Erase Voltage: %d.%d V\n", | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		       extp->VppOptimal >> 4, extp->VppOptimal & 0xf); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-10-30 16:33:07 +01:00
										 |  |  | /* Atmel chips don't use the same PRI format as Intel chips */ | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | static void fixup_convert_atmel_pri(struct mtd_info *mtd) | 
					
						
							| 
									
										
										
										
											2007-10-30 16:33:07 +01:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	struct cfi_pri_intelext *extp = cfi->cmdset_priv; | 
					
						
							|  |  |  | 	struct cfi_pri_atmel atmel_pri; | 
					
						
							|  |  |  | 	uint32_t features = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Reverse byteswapping */ | 
					
						
							|  |  |  | 	extp->FeatureSupport = cpu_to_le32(extp->FeatureSupport); | 
					
						
							|  |  |  | 	extp->BlkStatusRegMask = cpu_to_le16(extp->BlkStatusRegMask); | 
					
						
							|  |  |  | 	extp->ProtRegAddr = cpu_to_le16(extp->ProtRegAddr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	memcpy(&atmel_pri, extp, sizeof(atmel_pri)); | 
					
						
							|  |  |  | 	memset((char *)extp + 5, 0, sizeof(*extp) - 5); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	printk(KERN_ERR "atmel Features: %02x\n", atmel_pri.Features); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (atmel_pri.Features & 0x01) /* chip erase supported */ | 
					
						
							|  |  |  | 		features |= (1<<0); | 
					
						
							|  |  |  | 	if (atmel_pri.Features & 0x02) /* erase suspend supported */ | 
					
						
							|  |  |  | 		features |= (1<<1); | 
					
						
							|  |  |  | 	if (atmel_pri.Features & 0x04) /* program suspend supported */ | 
					
						
							|  |  |  | 		features |= (1<<2); | 
					
						
							|  |  |  | 	if (atmel_pri.Features & 0x08) /* simultaneous operations supported */ | 
					
						
							|  |  |  | 		features |= (1<<9); | 
					
						
							|  |  |  | 	if (atmel_pri.Features & 0x20) /* page mode read supported */ | 
					
						
							|  |  |  | 		features |= (1<<7); | 
					
						
							|  |  |  | 	if (atmel_pri.Features & 0x40) /* queued erase supported */ | 
					
						
							|  |  |  | 		features |= (1<<4); | 
					
						
							|  |  |  | 	if (atmel_pri.Features & 0x80) /* Protection bits supported */ | 
					
						
							|  |  |  | 		features |= (1<<6); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	extp->FeatureSupport = features; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* burst write mode not supported */ | 
					
						
							|  |  |  | 	cfi->cfiq->BufWriteTimeoutTyp = 0; | 
					
						
							|  |  |  | 	cfi->cfiq->BufWriteTimeoutMax = 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | static void fixup_at49bv640dx_lock(struct mtd_info *mtd) | 
					
						
							| 
									
										
										
										
											2009-11-05 15:53:37 +01:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	struct cfi_pri_intelext *cfip = cfi->cmdset_priv; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cfip->FeatureSupport |= (1 << 5); | 
					
						
							|  |  |  | 	mtd->flags |= MTD_POWERUP_LOCK; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #ifdef CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE
 | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | /* Some Intel Strata Flash prior to FPO revision C has bugs in this area */ | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | static void fixup_intel_strataflash(struct mtd_info *mtd) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							| 
									
										
										
										
											2008-05-04 14:32:58 +04:00
										 |  |  | 	struct cfi_pri_intelext *extp = cfi->cmdset_priv; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	printk(KERN_WARNING "cfi_cmdset_0001: Suspend " | 
					
						
							|  |  |  | 	                    "erase on write disabled.\n"); | 
					
						
							|  |  |  | 	extp->SuspendCmdSupport &= ~1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef CMDSET0001_DISABLE_WRITE_SUSPEND
 | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | static void fixup_no_write_suspend(struct mtd_info *mtd) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	struct cfi_pri_intelext *cfip = cfi->cmdset_priv; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (cfip && (cfip->FeatureSupport&4)) { | 
					
						
							|  |  |  | 		cfip->FeatureSupport &= ~4; | 
					
						
							|  |  |  | 		printk(KERN_WARNING "cfi_cmdset_0001: write suspend disabled\n"); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | static void fixup_st_m28w320ct(struct mtd_info *mtd) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	cfi->cfiq->BufWriteTimeoutTyp = 0;	/* Not supported */ | 
					
						
							|  |  |  | 	cfi->cfiq->BufWriteTimeoutMax = 0;	/* Not supported */ | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | static void fixup_st_m28w320cb(struct mtd_info *mtd) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	/* Note this is done after the region info is endian swapped */ | 
					
						
							|  |  |  | 	cfi->cfiq->EraseRegionInfo[1] = | 
					
						
							|  |  |  | 		(cfi->cfiq->EraseRegionInfo[1] & 0xffff0000) | 0x3e; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | static void fixup_use_point(struct mtd_info *mtd) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							| 
									
										
										
										
											2012-01-30 14:58:32 +02:00
										 |  |  | 	if (!mtd->_point && map_is_linear(map)) { | 
					
						
							|  |  |  | 		mtd->_point   = cfi_intelext_point; | 
					
						
							|  |  |  | 		mtd->_unpoint = cfi_intelext_unpoint; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | static void fixup_use_write_buffers(struct mtd_info *mtd) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	if (cfi->cfiq->BufWriteTimeoutTyp) { | 
					
						
							|  |  |  | 		printk(KERN_INFO "Using buffer write method\n" ); | 
					
						
							| 
									
										
										
										
											2012-01-30 14:58:32 +02:00
										 |  |  | 		mtd->_write = cfi_intelext_write_buffers; | 
					
						
							|  |  |  | 		mtd->_writev = cfi_intelext_writev; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Some chips power-up with all sectors locked by default. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | static void fixup_unlock_powerup_lock(struct mtd_info *mtd) | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2008-01-30 10:25:49 -08:00
										 |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	struct cfi_pri_intelext *cfip = cfi->cmdset_priv; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (cfip->FeatureSupport&32) { | 
					
						
							|  |  |  | 		printk(KERN_INFO "Using auto-unlock on power-up/resume\n" ); | 
					
						
							|  |  |  | 		mtd->flags |= MTD_POWERUP_LOCK; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | static struct cfi_fixup cfi_fixup_table[] = { | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | 	{ CFI_MFR_ATMEL, CFI_ID_ANY, fixup_convert_atmel_pri }, | 
					
						
							|  |  |  | 	{ CFI_MFR_ATMEL, AT49BV640D, fixup_at49bv640dx_lock }, | 
					
						
							|  |  |  | 	{ CFI_MFR_ATMEL, AT49BV640DT, fixup_at49bv640dx_lock }, | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #ifdef CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE
 | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | 	{ CFI_MFR_ANY, CFI_ID_ANY, fixup_intel_strataflash }, | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | #ifdef CMDSET0001_DISABLE_WRITE_SUSPEND
 | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | 	{ CFI_MFR_ANY, CFI_ID_ANY, fixup_no_write_suspend }, | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | #if !FORCE_WORD_WRITE
 | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | 	{ CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers }, | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | 	{ CFI_MFR_ST, 0x00ba, /* M28W320CT */ fixup_st_m28w320ct }, | 
					
						
							|  |  |  | 	{ CFI_MFR_ST, 0x00bb, /* M28W320CB */ fixup_st_m28w320cb }, | 
					
						
							|  |  |  | 	{ CFI_MFR_INTEL, CFI_ID_ANY, fixup_unlock_powerup_lock }, | 
					
						
							|  |  |  | 	{ 0, 0, NULL } | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct cfi_fixup jedec_fixup_table[] = { | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | 	{ CFI_MFR_INTEL, I82802AB,   fixup_use_fwh_lock }, | 
					
						
							|  |  |  | 	{ CFI_MFR_INTEL, I82802AC,   fixup_use_fwh_lock }, | 
					
						
							|  |  |  | 	{ CFI_MFR_ST,    M50LPW080,  fixup_use_fwh_lock }, | 
					
						
							|  |  |  | 	{ CFI_MFR_ST,    M50FLW080A, fixup_use_fwh_lock }, | 
					
						
							|  |  |  | 	{ CFI_MFR_ST,    M50FLW080B, fixup_use_fwh_lock }, | 
					
						
							|  |  |  | 	{ 0, 0, NULL } | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | static struct cfi_fixup fixup_table[] = { | 
					
						
							|  |  |  | 	/* The CFI vendor ids and the JEDEC vendor IDs appear
 | 
					
						
							|  |  |  | 	 * to be common.  It is like the devices id's are as | 
					
						
							|  |  |  | 	 * well.  This table is to pick all cases where | 
					
						
							|  |  |  | 	 * we know that is the case. | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2010-11-17 12:35:50 +01:00
										 |  |  | 	{ CFI_MFR_ANY, CFI_ID_ANY, fixup_use_point }, | 
					
						
							|  |  |  | 	{ 0, 0, NULL } | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-05-17 08:02:17 -03:00
										 |  |  | static void cfi_fixup_major_minor(struct cfi_private *cfi, | 
					
						
							|  |  |  | 						struct cfi_pri_intelext *extp) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2009-11-05 15:53:43 +01:00
										 |  |  | 	if (cfi->mfr == CFI_MFR_INTEL && | 
					
						
							| 
									
										
										
										
											2009-05-17 08:02:17 -03:00
										 |  |  | 			cfi->id == PF38F4476 && extp->MinorVersion == '3') | 
					
						
							|  |  |  | 		extp->MinorVersion = '1'; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | static inline struct cfi_pri_intelext * | 
					
						
							|  |  |  | read_pri_intelext(struct map_info *map, __u16 adr) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2009-05-17 08:02:17 -03:00
										 |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	struct cfi_pri_intelext *extp; | 
					
						
							| 
									
										
										
										
											2009-05-17 08:02:08 -03:00
										 |  |  | 	unsigned int extra_size = 0; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	unsigned int extp_size = sizeof(*extp); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  again: | 
					
						
							|  |  |  | 	extp = (struct cfi_pri_intelext *)cfi_read_pri(map, adr, extp_size, "Intel/Sharp"); | 
					
						
							|  |  |  | 	if (!extp) | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-05-17 08:02:17 -03:00
										 |  |  | 	cfi_fixup_major_minor(cfi, extp); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-07-20 22:01:17 +01:00
										 |  |  | 	if (extp->MajorVersion != '1' || | 
					
						
							| 
									
										
										
										
											2007-11-23 09:31:56 +00:00
										 |  |  | 	    (extp->MinorVersion < '0' || extp->MinorVersion > '5')) { | 
					
						
							| 
									
										
										
										
											2005-07-20 22:01:17 +01:00
										 |  |  | 		printk(KERN_ERR "  Unknown Intel/Sharp Extended Query " | 
					
						
							|  |  |  | 		       "version %c.%c.\n",  extp->MajorVersion, | 
					
						
							|  |  |  | 		       extp->MinorVersion); | 
					
						
							|  |  |  | 		kfree(extp); | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	/* Do some byteswapping if necessary */ | 
					
						
							|  |  |  | 	extp->FeatureSupport = le32_to_cpu(extp->FeatureSupport); | 
					
						
							|  |  |  | 	extp->BlkStatusRegMask = le16_to_cpu(extp->BlkStatusRegMask); | 
					
						
							|  |  |  | 	extp->ProtRegAddr = le16_to_cpu(extp->ProtRegAddr); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-05-17 08:02:08 -03:00
										 |  |  | 	if (extp->MinorVersion >= '0') { | 
					
						
							|  |  |  | 		extra_size = 0; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		/* Protection Register info */ | 
					
						
							| 
									
										
										
										
											2005-02-05 02:06:19 +00:00
										 |  |  | 		extra_size += (extp->NumProtectionFields - 1) * | 
					
						
							|  |  |  | 			      sizeof(struct cfi_intelext_otpinfo); | 
					
						
							| 
									
										
										
										
											2009-05-17 08:02:08 -03:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-05-17 08:02:08 -03:00
										 |  |  | 	if (extp->MinorVersion >= '1') { | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		/* Burst Read info */ | 
					
						
							| 
									
										
										
										
											2005-10-25 21:28:43 +01:00
										 |  |  | 		extra_size += 2; | 
					
						
							|  |  |  | 		if (extp_size < sizeof(*extp) + extra_size) | 
					
						
							|  |  |  | 			goto need_more; | 
					
						
							| 
									
										
										
										
											2009-05-17 08:02:08 -03:00
										 |  |  | 		extra_size += extp->extra[extra_size - 1]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (extp->MinorVersion >= '3') { | 
					
						
							|  |  |  | 		int nb_parts, i; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		/* Number of hardware-partitions */ | 
					
						
							|  |  |  | 		extra_size += 1; | 
					
						
							|  |  |  | 		if (extp_size < sizeof(*extp) + extra_size) | 
					
						
							|  |  |  | 			goto need_more; | 
					
						
							|  |  |  | 		nb_parts = extp->extra[extra_size - 1]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:40:46 +01:00
										 |  |  | 		/* skip the sizeof(partregion) field in CFI 1.4 */ | 
					
						
							|  |  |  | 		if (extp->MinorVersion >= '4') | 
					
						
							|  |  |  | 			extra_size += 2; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		for (i = 0; i < nb_parts; i++) { | 
					
						
							|  |  |  | 			struct cfi_intelext_regioninfo *rinfo; | 
					
						
							|  |  |  | 			rinfo = (struct cfi_intelext_regioninfo *)&extp->extra[extra_size]; | 
					
						
							|  |  |  | 			extra_size += sizeof(*rinfo); | 
					
						
							|  |  |  | 			if (extp_size < sizeof(*extp) + extra_size) | 
					
						
							|  |  |  | 				goto need_more; | 
					
						
							|  |  |  | 			rinfo->NumIdentPartitions=le16_to_cpu(rinfo->NumIdentPartitions); | 
					
						
							|  |  |  | 			extra_size += (rinfo->NumBlockTypes - 1) | 
					
						
							|  |  |  | 				      * sizeof(struct cfi_intelext_blockinfo); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:40:46 +01:00
										 |  |  | 		if (extp->MinorVersion >= '4') | 
					
						
							|  |  |  | 			extra_size += sizeof(struct cfi_intelext_programming_regioninfo); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		if (extp_size < sizeof(*extp) + extra_size) { | 
					
						
							|  |  |  | 			need_more: | 
					
						
							|  |  |  | 			extp_size = sizeof(*extp) + extra_size; | 
					
						
							|  |  |  | 			kfree(extp); | 
					
						
							|  |  |  | 			if (extp_size > 4096) { | 
					
						
							|  |  |  | 				printk(KERN_ERR | 
					
						
							|  |  |  | 					"%s: cfi_pri_intelext is too fat\n", | 
					
						
							| 
									
										
										
										
											2008-04-18 13:44:19 -07:00
										 |  |  | 					__func__); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 				return NULL; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			goto again; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return extp; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct mtd_info *cfi_cmdset_0001(struct map_info *map, int primary) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	struct mtd_info *mtd; | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-11-15 21:10:29 +02:00
										 |  |  | 	mtd = kzalloc(sizeof(*mtd), GFP_KERNEL); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (!mtd) { | 
					
						
							|  |  |  | 		printk(KERN_ERR "Failed to allocate memory for MTD device\n"); | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	mtd->priv = map; | 
					
						
							|  |  |  | 	mtd->type = MTD_NORFLASH; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Fill in the default mtd operations */ | 
					
						
							| 
									
										
										
										
											2012-01-30 14:58:32 +02:00
										 |  |  | 	mtd->_erase   = cfi_intelext_erase_varsize; | 
					
						
							|  |  |  | 	mtd->_read    = cfi_intelext_read; | 
					
						
							|  |  |  | 	mtd->_write   = cfi_intelext_write_words; | 
					
						
							|  |  |  | 	mtd->_sync    = cfi_intelext_sync; | 
					
						
							|  |  |  | 	mtd->_lock    = cfi_intelext_lock; | 
					
						
							|  |  |  | 	mtd->_unlock  = cfi_intelext_unlock; | 
					
						
							|  |  |  | 	mtd->_is_locked = cfi_intelext_is_locked; | 
					
						
							|  |  |  | 	mtd->_suspend = cfi_intelext_suspend; | 
					
						
							|  |  |  | 	mtd->_resume  = cfi_intelext_resume; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	mtd->flags   = MTD_CAP_NORFLASH; | 
					
						
							|  |  |  | 	mtd->name    = map->name; | 
					
						
							| 
									
										
										
										
											2006-06-22 18:15:48 +04:00
										 |  |  | 	mtd->writesize = 1; | 
					
						
							| 
									
										
										
										
											2011-02-10 16:01:46 +01:00
										 |  |  | 	mtd->writebufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; | 
					
						
							| 
									
										
										
										
											2005-04-01 02:59:56 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	mtd->reboot_notifier.notifier_call = cfi_intelext_reboot; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (cfi->cfi_mode == CFI_MODE_CFI) { | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 		/*
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		 * It's a real CFI chip, not one for which the probe | 
					
						
							|  |  |  | 		 * routine faked a CFI structure. So we read the feature | 
					
						
							|  |  |  | 		 * table from it. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		__u16 adr = primary?cfi->cfiq->P_ADR:cfi->cfiq->A_ADR; | 
					
						
							|  |  |  | 		struct cfi_pri_intelext *extp; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		extp = read_pri_intelext(map, adr); | 
					
						
							|  |  |  | 		if (!extp) { | 
					
						
							|  |  |  | 			kfree(mtd); | 
					
						
							|  |  |  | 			return NULL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Install our own private info structure */ | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 		cfi->cmdset_priv = extp; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		cfi_fixup(mtd, cfi_fixup_table); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef DEBUG_CFI_FEATURES
 | 
					
						
							|  |  |  | 		/* Tell the user about it in lots of lovely detail */ | 
					
						
							|  |  |  | 		cfi_tell_features(extp); | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		if(extp->SuspendCmdSupport & 1) { | 
					
						
							|  |  |  | 			printk(KERN_NOTICE "cfi_cmdset_0001: Erase suspend on write enabled\n"); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	else if (cfi->cfi_mode == CFI_MODE_JEDEC) { | 
					
						
							|  |  |  | 		/* Apply jedec specific fixups */ | 
					
						
							|  |  |  | 		cfi_fixup(mtd, jedec_fixup_table); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	/* Apply generic fixups */ | 
					
						
							|  |  |  | 	cfi_fixup(mtd, fixup_table); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i=0; i< cfi->numchips; i++) { | 
					
						
							| 
									
										
										
										
											2007-02-09 14:39:10 +00:00
										 |  |  | 		if (cfi->cfiq->WordWriteTimeoutTyp) | 
					
						
							|  |  |  | 			cfi->chips[i].word_write_time = | 
					
						
							|  |  |  | 				1<<cfi->cfiq->WordWriteTimeoutTyp; | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			cfi->chips[i].word_write_time = 50000; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (cfi->cfiq->BufWriteTimeoutTyp) | 
					
						
							|  |  |  | 			cfi->chips[i].buffer_write_time = | 
					
						
							|  |  |  | 				1<<cfi->cfiq->BufWriteTimeoutTyp; | 
					
						
							|  |  |  | 		/* No default; if it isn't specified, we won't use it */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (cfi->cfiq->BlockEraseTimeoutTyp) | 
					
						
							|  |  |  | 			cfi->chips[i].erase_time = | 
					
						
							|  |  |  | 				1000<<cfi->cfiq->BlockEraseTimeoutTyp; | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			cfi->chips[i].erase_time = 2000000; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-08-05 18:37:41 +02:00
										 |  |  | 		if (cfi->cfiq->WordWriteTimeoutTyp && | 
					
						
							|  |  |  | 		    cfi->cfiq->WordWriteTimeoutMax) | 
					
						
							|  |  |  | 			cfi->chips[i].word_write_time_max = | 
					
						
							|  |  |  | 				1<<(cfi->cfiq->WordWriteTimeoutTyp + | 
					
						
							|  |  |  | 				    cfi->cfiq->WordWriteTimeoutMax); | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			cfi->chips[i].word_write_time_max = 50000 * 8; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (cfi->cfiq->BufWriteTimeoutTyp && | 
					
						
							|  |  |  | 		    cfi->cfiq->BufWriteTimeoutMax) | 
					
						
							|  |  |  | 			cfi->chips[i].buffer_write_time_max = | 
					
						
							|  |  |  | 				1<<(cfi->cfiq->BufWriteTimeoutTyp + | 
					
						
							|  |  |  | 				    cfi->cfiq->BufWriteTimeoutMax); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (cfi->cfiq->BlockEraseTimeoutTyp && | 
					
						
							|  |  |  | 		    cfi->cfiq->BlockEraseTimeoutMax) | 
					
						
							|  |  |  | 			cfi->chips[i].erase_time_max = | 
					
						
							|  |  |  | 				1000<<(cfi->cfiq->BlockEraseTimeoutTyp + | 
					
						
							|  |  |  | 				       cfi->cfiq->BlockEraseTimeoutMax); | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			cfi->chips[i].erase_time_max = 2000000 * 8; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		cfi->chips[i].ref_point_counter = 0; | 
					
						
							| 
									
										
										
										
											2006-02-24 13:04:09 -08:00
										 |  |  | 		init_waitqueue_head(&(cfi->chips[i].wq)); | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	map->fldrv = &cfi_intelext_chipdrv; | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return cfi_intelext_setup(mtd); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2006-05-08 22:35:05 +01:00
										 |  |  | struct mtd_info *cfi_cmdset_0003(struct map_info *map, int primary) __attribute__((alias("cfi_cmdset_0001"))); | 
					
						
							|  |  |  | struct mtd_info *cfi_cmdset_0200(struct map_info *map, int primary) __attribute__((alias("cfi_cmdset_0001"))); | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(cfi_cmdset_0001); | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(cfi_cmdset_0003); | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(cfi_cmdset_0200); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | static struct mtd_info *cfi_intelext_setup(struct mtd_info *mtd) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	unsigned long offset = 0; | 
					
						
							|  |  |  | 	int i,j; | 
					
						
							|  |  |  | 	unsigned long devsize = (1<<cfi->cfiq->DevSize) * cfi->interleave; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	//printk(KERN_DEBUG "number of CFI chips: %d\n", cfi->numchips);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mtd->size = devsize * cfi->numchips; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips; | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 	mtd->eraseregions = kmalloc(sizeof(struct mtd_erase_region_info) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			* mtd->numeraseregions, GFP_KERNEL); | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 	if (!mtd->eraseregions) { | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		printk(KERN_ERR "Failed to allocate memory for MTD erase region info\n"); | 
					
						
							|  |  |  | 		goto setup_err; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	for (i=0; i<cfi->cfiq->NumEraseRegions; i++) { | 
					
						
							|  |  |  | 		unsigned long ernum, ersize; | 
					
						
							|  |  |  | 		ersize = ((cfi->cfiq->EraseRegionInfo[i] >> 8) & ~0xff) * cfi->interleave; | 
					
						
							|  |  |  | 		ernum = (cfi->cfiq->EraseRegionInfo[i] & 0xffff) + 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (mtd->erasesize < ersize) { | 
					
						
							|  |  |  | 			mtd->erasesize = ersize; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for (j=0; j<cfi->numchips; j++) { | 
					
						
							|  |  |  | 			mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].offset = (j*devsize)+offset; | 
					
						
							|  |  |  | 			mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].erasesize = ersize; | 
					
						
							|  |  |  | 			mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].numblocks = ernum; | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | 			mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].lockmap = kmalloc(ernum / 8 + 1, GFP_KERNEL); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		offset += (ersize * ernum); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (offset != devsize) { | 
					
						
							|  |  |  | 		/* Argh */ | 
					
						
							|  |  |  | 		printk(KERN_WARNING "Sum of regions (%lx) != total size of set of interleaved chips (%lx)\n", offset, devsize); | 
					
						
							|  |  |  | 		goto setup_err; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i=0; i<mtd->numeraseregions;i++){ | 
					
						
							| 
									
										
										
										
											2008-12-10 13:37:21 +00:00
										 |  |  | 		printk(KERN_DEBUG "erase region %d: offset=0x%llx,size=0x%x,blocks=%d\n", | 
					
						
							|  |  |  | 		       i,(unsigned long long)mtd->eraseregions[i].offset, | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		       mtd->eraseregions[i].erasesize, | 
					
						
							|  |  |  | 		       mtd->eraseregions[i].numblocks); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | #ifdef CONFIG_MTD_OTP
 | 
					
						
							| 
									
										
										
										
											2012-01-30 14:58:32 +02:00
										 |  |  | 	mtd->_read_fact_prot_reg = cfi_intelext_read_fact_prot_reg; | 
					
						
							|  |  |  | 	mtd->_read_user_prot_reg = cfi_intelext_read_user_prot_reg; | 
					
						
							|  |  |  | 	mtd->_write_user_prot_reg = cfi_intelext_write_user_prot_reg; | 
					
						
							|  |  |  | 	mtd->_lock_user_prot_reg = cfi_intelext_lock_user_prot_reg; | 
					
						
							|  |  |  | 	mtd->_get_fact_prot_info = cfi_intelext_get_fact_prot_info; | 
					
						
							|  |  |  | 	mtd->_get_user_prot_info = cfi_intelext_get_user_prot_info; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* This function has the potential to distort the reality
 | 
					
						
							|  |  |  | 	   a bit and therefore should be called last. */ | 
					
						
							|  |  |  | 	if (cfi_intelext_partition_fixup(mtd, &cfi) != 0) | 
					
						
							|  |  |  | 		goto setup_err; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	__module_get(THIS_MODULE); | 
					
						
							| 
									
										
										
										
											2005-04-01 02:59:56 +01:00
										 |  |  | 	register_reboot_notifier(&mtd->reboot_notifier); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return mtd; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  setup_err: | 
					
						
							| 
									
										
										
										
											2010-01-10 10:01:19 +01:00
										 |  |  | 	kfree(mtd->eraseregions); | 
					
						
							|  |  |  | 	kfree(mtd); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	kfree(cfi->cmdset_priv); | 
					
						
							|  |  |  | 	return NULL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cfi_intelext_partition_fixup(struct mtd_info *mtd, | 
					
						
							|  |  |  | 					struct cfi_private **pcfi) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = *pcfi; | 
					
						
							|  |  |  | 	struct cfi_pri_intelext *extp = cfi->cmdset_priv; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/*
 | 
					
						
							| 
									
										
										
										
											2007-07-05 02:18:34 +02:00
										 |  |  | 	 * Probing of multi-partition flash chips. | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	 * | 
					
						
							|  |  |  | 	 * To support multiple partitions when available, we simply arrange | 
					
						
							|  |  |  | 	 * for each of them to have their own flchip structure even if they | 
					
						
							|  |  |  | 	 * are on the same physical chip.  This means completely recreating | 
					
						
							|  |  |  | 	 * a new cfi_private structure right here which is a blatent code | 
					
						
							|  |  |  | 	 * layering violation, but this is still the least intrusive | 
					
						
							|  |  |  | 	 * arrangement at this point. This can be rearranged in the future | 
					
						
							|  |  |  | 	 * if someone feels motivated enough.  --nico | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2005-08-06 05:40:46 +01:00
										 |  |  | 	if (extp && extp->MajorVersion == '1' && extp->MinorVersion >= '3' | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	    && extp->FeatureSupport & (1 << 9)) { | 
					
						
							|  |  |  | 		struct cfi_private *newcfi; | 
					
						
							|  |  |  | 		struct flchip *chip; | 
					
						
							|  |  |  | 		struct flchip_shared *shared; | 
					
						
							|  |  |  | 		int offs, numregions, numparts, partshift, numvirtchips, i, j; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Protection Register info */ | 
					
						
							| 
									
										
										
										
											2005-02-05 02:06:19 +00:00
										 |  |  | 		offs = (extp->NumProtectionFields - 1) * | 
					
						
							|  |  |  | 		       sizeof(struct cfi_intelext_otpinfo); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		/* Burst Read info */ | 
					
						
							| 
									
										
										
										
											2005-10-25 21:28:43 +01:00
										 |  |  | 		offs += extp->extra[offs+1]+2; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		/* Number of partition regions */ | 
					
						
							|  |  |  | 		numregions = extp->extra[offs]; | 
					
						
							|  |  |  | 		offs += 1; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:40:46 +01:00
										 |  |  | 		/* skip the sizeof(partregion) field in CFI 1.4 */ | 
					
						
							|  |  |  | 		if (extp->MinorVersion >= '4') | 
					
						
							|  |  |  | 			offs += 2; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		/* Number of hardware partitions */ | 
					
						
							|  |  |  | 		numparts = 0; | 
					
						
							|  |  |  | 		for (i = 0; i < numregions; i++) { | 
					
						
							|  |  |  | 			struct cfi_intelext_regioninfo *rinfo; | 
					
						
							|  |  |  | 			rinfo = (struct cfi_intelext_regioninfo *)&extp->extra[offs]; | 
					
						
							|  |  |  | 			numparts += rinfo->NumIdentPartitions; | 
					
						
							|  |  |  | 			offs += sizeof(*rinfo) | 
					
						
							|  |  |  | 				+ (rinfo->NumBlockTypes - 1) * | 
					
						
							|  |  |  | 				  sizeof(struct cfi_intelext_blockinfo); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-04-23 01:40:52 +02:00
										 |  |  | 		if (!numparts) | 
					
						
							|  |  |  | 			numparts = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:40:46 +01:00
										 |  |  | 		/* Programming Region info */ | 
					
						
							|  |  |  | 		if (extp->MinorVersion >= '4') { | 
					
						
							|  |  |  | 			struct cfi_intelext_programming_regioninfo *prinfo; | 
					
						
							|  |  |  | 			prinfo = (struct cfi_intelext_programming_regioninfo *)&extp->extra[offs]; | 
					
						
							| 
									
										
										
										
											2006-05-22 23:18:05 +02:00
										 |  |  | 			mtd->writesize = cfi->interleave << prinfo->ProgRegShift; | 
					
						
							| 
									
										
										
										
											2006-05-22 23:18:29 +02:00
										 |  |  | 			mtd->flags &= ~MTD_BIT_WRITEABLE; | 
					
						
							| 
									
										
										
										
											2005-08-06 05:40:46 +01:00
										 |  |  | 			printk(KERN_DEBUG "%s: program region size/ctrl_valid/ctrl_inval = %d/%d/%d\n", | 
					
						
							| 
									
										
										
										
											2006-05-22 23:18:05 +02:00
										 |  |  | 			       map->name, mtd->writesize, | 
					
						
							| 
									
										
										
										
											2007-01-30 10:45:55 +02:00
										 |  |  | 			       cfi->interleave * prinfo->ControlValid, | 
					
						
							|  |  |  | 			       cfi->interleave * prinfo->ControlInvalid); | 
					
						
							| 
									
										
										
										
											2005-08-06 05:40:46 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * All functions below currently rely on all chips having | 
					
						
							|  |  |  | 		 * the same geometry so we'll just assume that all hardware | 
					
						
							|  |  |  | 		 * partitions are of the same size too. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		partshift = cfi->chipshift - __ffs(numparts); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if ((1 << partshift) < mtd->erasesize) { | 
					
						
							|  |  |  | 			printk( KERN_ERR | 
					
						
							|  |  |  | 				"%s: bad number of hw partitions (%d)\n", | 
					
						
							| 
									
										
										
										
											2008-04-18 13:44:19 -07:00
										 |  |  | 				__func__, numparts); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			return -EINVAL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		numvirtchips = cfi->numchips * numparts; | 
					
						
							|  |  |  | 		newcfi = kmalloc(sizeof(struct cfi_private) + numvirtchips * sizeof(struct flchip), GFP_KERNEL); | 
					
						
							|  |  |  | 		if (!newcfi) | 
					
						
							|  |  |  | 			return -ENOMEM; | 
					
						
							|  |  |  | 		shared = kmalloc(sizeof(struct flchip_shared) * cfi->numchips, GFP_KERNEL); | 
					
						
							|  |  |  | 		if (!shared) { | 
					
						
							|  |  |  | 			kfree(newcfi); | 
					
						
							|  |  |  | 			return -ENOMEM; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		memcpy(newcfi, cfi, sizeof(struct cfi_private)); | 
					
						
							|  |  |  | 		newcfi->numchips = numvirtchips; | 
					
						
							|  |  |  | 		newcfi->chipshift = partshift; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		chip = &newcfi->chips[0]; | 
					
						
							|  |  |  | 		for (i = 0; i < cfi->numchips; i++) { | 
					
						
							|  |  |  | 			shared[i].writing = shared[i].erasing = NULL; | 
					
						
							| 
									
										
										
										
											2010-08-05 09:19:26 +02:00
										 |  |  | 			mutex_init(&shared[i].lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			for (j = 0; j < numparts; j++) { | 
					
						
							|  |  |  | 				*chip = cfi->chips[i]; | 
					
						
							|  |  |  | 				chip->start += j << partshift; | 
					
						
							|  |  |  | 				chip->priv = &shared[i]; | 
					
						
							|  |  |  | 				/* those should be reset too since
 | 
					
						
							|  |  |  | 				   they create memory references. */ | 
					
						
							|  |  |  | 				init_waitqueue_head(&chip->wq); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 				mutex_init(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 				chip++; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		printk(KERN_DEBUG "%s: %d set(s) of %d interleaved chips " | 
					
						
							|  |  |  | 				  "--> %d partitions of %d KiB\n", | 
					
						
							|  |  |  | 				  map->name, cfi->numchips, cfi->interleave, | 
					
						
							|  |  |  | 				  newcfi->numchips, 1<<(newcfi->chipshift-10)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		map->fldrv_priv = newcfi; | 
					
						
							|  |  |  | 		*pcfi = newcfi; | 
					
						
							|  |  |  | 		kfree(cfi); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  *  *********** CHIP ACCESS FUNCTIONS *********** | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | static int chip_ready (struct map_info *map, struct flchip *chip, unsigned long adr, int mode) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	DECLARE_WAITQUEUE(wait, current); | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	map_word status, status_OK = CMD(0x80), status_PWS = CMD(0x01); | 
					
						
							|  |  |  | 	struct cfi_pri_intelext *cfip = cfi->cmdset_priv; | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | 	unsigned long timeo = jiffies + HZ; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-09-25 17:53:24 +04:00
										 |  |  | 	/* Prevent setting state FL_SYNCING for chip in suspended state. */ | 
					
						
							|  |  |  | 	if (mode == FL_SYNCING && chip->oldstate != FL_READY) | 
					
						
							|  |  |  | 		goto sleep; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	switch (chip->state) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case FL_STATUS: | 
					
						
							|  |  |  | 		for (;;) { | 
					
						
							|  |  |  | 			status = map_read(map, adr); | 
					
						
							|  |  |  | 			if (map_word_andequal(map, status, status_OK, status_OK)) | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* At this point we're fine with write operations
 | 
					
						
							|  |  |  | 			   in other partitions as they don't conflict. */ | 
					
						
							|  |  |  | 			if (chip->priv && map_word_andequal(map, status, status_PWS, status_PWS)) | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			cfi_udelay(1); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			/* Someone else might have been playing with it. */ | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | 			return -EAGAIN; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2008-04-04 14:30:01 -07:00
										 |  |  | 		/* Fall through */ | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	case FL_READY: | 
					
						
							|  |  |  | 	case FL_CFI_QUERY: | 
					
						
							|  |  |  | 	case FL_JEDEC_QUERY: | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case FL_ERASING: | 
					
						
							|  |  |  | 		if (!cfip || | 
					
						
							|  |  |  | 		    !(cfip->FeatureSupport & 2) || | 
					
						
							|  |  |  | 		    !(mode == FL_READY || mode == FL_POINT || | 
					
						
							|  |  |  | 		     (mode == FL_WRITING && (cfip->SuspendCmdSupport & 1)))) | 
					
						
							|  |  |  | 			goto sleep; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* Erase suspend */ | 
					
						
							|  |  |  | 		map_write(map, CMD(0xB0), adr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* If the flash has finished erasing, then 'erase suspend'
 | 
					
						
							|  |  |  | 		 * appears to make some (28F320) flash devices switch to | 
					
						
							|  |  |  | 		 * 'read' mode.  Make sure that we switch to 'read status' | 
					
						
							|  |  |  | 		 * mode so we get the right data. --rmk | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		map_write(map, CMD(0x70), adr); | 
					
						
							|  |  |  | 		chip->oldstate = FL_ERASING; | 
					
						
							|  |  |  | 		chip->state = FL_ERASE_SUSPENDING; | 
					
						
							|  |  |  | 		chip->erase_suspended = 1; | 
					
						
							|  |  |  | 		for (;;) { | 
					
						
							|  |  |  | 			status = map_read(map, adr); | 
					
						
							|  |  |  | 			if (map_word_andequal(map, status, status_OK, status_OK)) | 
					
						
							|  |  |  | 			        break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (time_after(jiffies, timeo)) { | 
					
						
							| 
									
										
										
										
											2011-05-19 15:58:15 +09:00
										 |  |  | 				/* Urgh. Resume and pretend we weren't here.
 | 
					
						
							|  |  |  | 				 * Make sure we're in 'read status' mode if it had finished */ | 
					
						
							|  |  |  | 				put_chip(map, chip, adr); | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 				printk(KERN_ERR "%s: Chip not ready after erase " | 
					
						
							|  |  |  | 				       "suspended: status = 0x%lx\n", map->name, status.x[0]); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 				return -EIO; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			cfi_udelay(1); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			/* Nobody will touch it while it's in state FL_ERASE_SUSPENDING.
 | 
					
						
							|  |  |  | 			   So we can just loop here. */ | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		chip->state = FL_STATUS; | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case FL_XIP_WHILE_ERASING: | 
					
						
							|  |  |  | 		if (mode != FL_READY && mode != FL_POINT && | 
					
						
							|  |  |  | 		    (mode != FL_WRITING || !cfip || !(cfip->SuspendCmdSupport&1))) | 
					
						
							|  |  |  | 			goto sleep; | 
					
						
							|  |  |  | 		chip->oldstate = chip->state; | 
					
						
							|  |  |  | 		chip->state = FL_READY; | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-04-04 14:30:01 -07:00
										 |  |  | 	case FL_SHUTDOWN: | 
					
						
							|  |  |  | 		/* The machine is rebooting now,so no one can get chip anymore */ | 
					
						
							|  |  |  | 		return -EIO; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	case FL_POINT: | 
					
						
							|  |  |  | 		/* Only if there's no operation suspended... */ | 
					
						
							|  |  |  | 		if (mode == FL_READY && chip->oldstate == FL_READY) | 
					
						
							|  |  |  | 			return 0; | 
					
						
							| 
									
										
										
										
											2008-04-04 14:30:01 -07:00
										 |  |  | 		/* Fall through */ | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 	sleep: | 
					
						
							|  |  |  | 		set_current_state(TASK_UNINTERRUPTIBLE); | 
					
						
							|  |  |  | 		add_wait_queue(&chip->wq, &wait); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		schedule(); | 
					
						
							|  |  |  | 		remove_wait_queue(&chip->wq, &wait); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | 		return -EAGAIN; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							| 
									
										
										
										
											2007-11-07 11:58:07 +03:00
										 |  |  | 	DECLARE_WAITQUEUE(wait, current); | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |  retry: | 
					
						
							| 
									
										
										
										
											2008-09-25 17:53:24 +04:00
										 |  |  | 	if (chip->priv && | 
					
						
							|  |  |  | 	    (mode == FL_WRITING || mode == FL_ERASING || mode == FL_OTP_WRITE | 
					
						
							|  |  |  | 	    || mode == FL_SHUTDOWN) && chip->state != FL_SYNCING) { | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | 		/*
 | 
					
						
							|  |  |  | 		 * OK. We have possibility for contention on the write/erase | 
					
						
							|  |  |  | 		 * operations which are global to the real chip and not per | 
					
						
							|  |  |  | 		 * partition.  So let's fight it over in the partition which | 
					
						
							|  |  |  | 		 * currently has authority on the operation. | 
					
						
							|  |  |  | 		 * | 
					
						
							|  |  |  | 		 * The rules are as follows: | 
					
						
							|  |  |  | 		 * | 
					
						
							|  |  |  | 		 * - any write operation must own shared->writing. | 
					
						
							|  |  |  | 		 * | 
					
						
							|  |  |  | 		 * - any erase operation must own _both_ shared->writing and | 
					
						
							|  |  |  | 		 *   shared->erasing. | 
					
						
							|  |  |  | 		 * | 
					
						
							|  |  |  | 		 * - contention arbitration is handled in the owner's context. | 
					
						
							|  |  |  | 		 * | 
					
						
							|  |  |  | 		 * The 'shared' struct can be read and/or written only when | 
					
						
							|  |  |  | 		 * its lock is taken. | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		struct flchip_shared *shared = chip->priv; | 
					
						
							|  |  |  | 		struct flchip *contender; | 
					
						
							| 
									
										
										
										
											2010-08-05 09:19:26 +02:00
										 |  |  | 		mutex_lock(&shared->lock); | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | 		contender = shared->writing; | 
					
						
							|  |  |  | 		if (contender && contender != chip) { | 
					
						
							|  |  |  | 			/*
 | 
					
						
							|  |  |  | 			 * The engine to perform desired operation on this | 
					
						
							|  |  |  | 			 * partition is already in use by someone else. | 
					
						
							|  |  |  | 			 * Let's fight over it in the context of the chip | 
					
						
							|  |  |  | 			 * currently using it.  If it is possible to suspend, | 
					
						
							|  |  |  | 			 * that other partition will do just that, otherwise | 
					
						
							|  |  |  | 			 * it'll happily send us to sleep.  In any case, when | 
					
						
							|  |  |  | 			 * get_chip returns success we're clear to go ahead. | 
					
						
							|  |  |  | 			 */ | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			ret = mutex_trylock(&contender->mutex); | 
					
						
							| 
									
										
										
										
											2010-08-05 09:19:26 +02:00
										 |  |  | 			mutex_unlock(&shared->lock); | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | 			if (!ret) | 
					
						
							|  |  |  | 				goto retry; | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | 			ret = chip_ready(map, contender, contender->start, mode); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			if (ret == -EAGAIN) { | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 				mutex_unlock(&contender->mutex); | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | 				goto retry; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if (ret) { | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 				mutex_unlock(&contender->mutex); | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | 				return ret; | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2010-08-05 09:19:26 +02:00
										 |  |  | 			mutex_lock(&shared->lock); | 
					
						
							| 
									
										
										
										
											2008-09-25 17:53:24 +04:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			/* We should not own chip if it is already
 | 
					
						
							|  |  |  | 			 * in FL_SYNCING state. Put contender and retry. */ | 
					
						
							|  |  |  | 			if (chip->state == FL_SYNCING) { | 
					
						
							|  |  |  | 				put_chip(map, contender, contender->start); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 				mutex_unlock(&contender->mutex); | 
					
						
							| 
									
										
										
										
											2008-09-25 17:53:24 +04:00
										 |  |  | 				goto retry; | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			mutex_unlock(&contender->mutex); | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-11-07 11:58:07 +03:00
										 |  |  | 		/* Check if we already have suspended erase
 | 
					
						
							|  |  |  | 		 * on this chip. Sleep. */ | 
					
						
							|  |  |  | 		if (mode == FL_ERASING && shared->erasing | 
					
						
							|  |  |  | 		    && shared->erasing->oldstate == FL_ERASING) { | 
					
						
							| 
									
										
										
										
											2010-08-05 09:19:26 +02:00
										 |  |  | 			mutex_unlock(&shared->lock); | 
					
						
							| 
									
										
										
										
											2007-11-07 11:58:07 +03:00
										 |  |  | 			set_current_state(TASK_UNINTERRUPTIBLE); | 
					
						
							|  |  |  | 			add_wait_queue(&chip->wq, &wait); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2007-11-07 11:58:07 +03:00
										 |  |  | 			schedule(); | 
					
						
							|  |  |  | 			remove_wait_queue(&chip->wq, &wait); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2007-11-07 11:58:07 +03:00
										 |  |  | 			goto retry; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | 		/* We now own it */ | 
					
						
							|  |  |  | 		shared->writing = chip; | 
					
						
							|  |  |  | 		if (mode == FL_ERASING) | 
					
						
							|  |  |  | 			shared->erasing = chip; | 
					
						
							| 
									
										
										
										
											2010-08-05 09:19:26 +02:00
										 |  |  | 		mutex_unlock(&shared->lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2007-10-22 17:55:20 +01:00
										 |  |  | 	ret = chip_ready(map, chip, adr, mode); | 
					
						
							|  |  |  | 	if (ret == -EAGAIN) | 
					
						
							|  |  |  | 		goto retry; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (chip->priv) { | 
					
						
							|  |  |  | 		struct flchip_shared *shared = chip->priv; | 
					
						
							| 
									
										
										
										
											2010-08-05 09:19:26 +02:00
										 |  |  | 		mutex_lock(&shared->lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		if (shared->writing == chip && chip->oldstate == FL_READY) { | 
					
						
							|  |  |  | 			/* We own the ability to write, but we're done */ | 
					
						
							|  |  |  | 			shared->writing = shared->erasing; | 
					
						
							|  |  |  | 			if (shared->writing && shared->writing != chip) { | 
					
						
							|  |  |  | 				/* give back ownership to who we loaned it from */ | 
					
						
							|  |  |  | 				struct flchip *loaner = shared->writing; | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 				mutex_lock(&loaner->mutex); | 
					
						
							| 
									
										
										
										
											2010-08-05 09:19:26 +02:00
										 |  |  | 				mutex_unlock(&shared->lock); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 				mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 				put_chip(map, loaner, loaner->start); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 				mutex_lock(&chip->mutex); | 
					
						
							|  |  |  | 				mutex_unlock(&loaner->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 				wake_up(&chip->wq); | 
					
						
							|  |  |  | 				return; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			shared->erasing = NULL; | 
					
						
							|  |  |  | 			shared->writing = NULL; | 
					
						
							|  |  |  | 		} else if (shared->erasing == chip && shared->writing != chip) { | 
					
						
							|  |  |  | 			/*
 | 
					
						
							|  |  |  | 			 * We own the ability to erase without the ability | 
					
						
							|  |  |  | 			 * to write, which means the erase was suspended | 
					
						
							|  |  |  | 			 * and some other partition is currently writing. | 
					
						
							|  |  |  | 			 * Don't let the switch below mess things up since | 
					
						
							|  |  |  | 			 * we don't have ownership to resume anything. | 
					
						
							|  |  |  | 			 */ | 
					
						
							| 
									
										
										
										
											2010-08-05 09:19:26 +02:00
										 |  |  | 			mutex_unlock(&shared->lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			wake_up(&chip->wq); | 
					
						
							|  |  |  | 			return; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2010-08-05 09:19:26 +02:00
										 |  |  | 		mutex_unlock(&shared->lock); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch(chip->oldstate) { | 
					
						
							|  |  |  | 	case FL_ERASING: | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 		/* What if one interleaved chip has finished and the
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		   other hasn't? The old code would leave the finished | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 		   one in READY mode. That's bad, and caused -EROFS | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		   errors to be returned from do_erase_oneblock because | 
					
						
							|  |  |  | 		   that's the only bit it checked for at the time. | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 		   As the state machine appears to explicitly allow | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		   sending the 0x70 (Read Status) command to an erasing | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 		   chip and expecting it to be ignored, that's what we | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		   do. */ | 
					
						
							|  |  |  | 		map_write(map, CMD(0xd0), adr); | 
					
						
							|  |  |  | 		map_write(map, CMD(0x70), adr); | 
					
						
							|  |  |  | 		chip->oldstate = FL_READY; | 
					
						
							|  |  |  | 		chip->state = FL_ERASING; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case FL_XIP_WHILE_ERASING: | 
					
						
							|  |  |  | 		chip->state = chip->oldstate; | 
					
						
							|  |  |  | 		chip->oldstate = FL_READY; | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case FL_READY: | 
					
						
							|  |  |  | 	case FL_STATUS: | 
					
						
							|  |  |  | 	case FL_JEDEC_QUERY: | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	default: | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 		printk(KERN_ERR "%s: put_chip() called with oldstate %d!!\n", map->name, chip->oldstate); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	wake_up(&chip->wq); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef CONFIG_MTD_XIP
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * No interrupt what so ever can be serviced while the flash isn't in array | 
					
						
							|  |  |  |  * mode.  This is ensured by the xip_disable() and xip_enable() functions | 
					
						
							|  |  |  |  * enclosing any code path where the flash is known not to be in array mode. | 
					
						
							|  |  |  |  * And within a XIP disabled code path, only functions marked with __xipram | 
					
						
							|  |  |  |  * may be called and nothing else (it's a good thing to inspect generated | 
					
						
							|  |  |  |  * assembly to make sure inline functions were actually inlined and that gcc | 
					
						
							|  |  |  |  * didn't emit calls to its own support functions). Also configuring MTD CFI | 
					
						
							|  |  |  |  * support to a single buswidth and a single interleave is also recommended. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void xip_disable(struct map_info *map, struct flchip *chip, | 
					
						
							|  |  |  | 			unsigned long adr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/* TODO: chips with no XIP use should ignore and return */ | 
					
						
							|  |  |  | 	(void) map_read(map, adr); /* ensure mmu mapping is up to date */ | 
					
						
							|  |  |  | 	local_irq_disable(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void __xipram xip_enable(struct map_info *map, struct flchip *chip, | 
					
						
							|  |  |  | 				unsigned long adr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	if (chip->state != FL_POINT && chip->state != FL_READY) { | 
					
						
							|  |  |  | 		map_write(map, CMD(0xff), adr); | 
					
						
							|  |  |  | 		chip->state = FL_READY; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	(void) map_read(map, adr); | 
					
						
							| 
									
										
										
										
											2005-07-07 16:50:16 +02:00
										 |  |  | 	xip_iprefetch(); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	local_irq_enable(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * When a delay is required for the flash operation to complete, the | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  |  * xip_wait_for_operation() function is polling for both the given timeout | 
					
						
							|  |  |  |  * and pending (but still masked) hardware interrupts.  Whenever there is an | 
					
						
							|  |  |  |  * interrupt pending then the flash erase or write operation is suspended, | 
					
						
							|  |  |  |  * array mode restored and interrupts unmasked.  Task scheduling might also | 
					
						
							|  |  |  |  * happen at that point.  The CPU eventually returns from the interrupt or | 
					
						
							|  |  |  |  * the call to schedule() and the suspended flash operation is resumed for | 
					
						
							|  |  |  |  * the remaining of the delay period. | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Warning: this function _will_ fool interrupt latency tracing tools. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | static int __xipram xip_wait_for_operation( | 
					
						
							|  |  |  | 		struct map_info *map, struct flchip *chip, | 
					
						
							| 
									
										
										
										
											2008-08-05 18:37:41 +02:00
										 |  |  | 		unsigned long adr, unsigned int chip_op_time_max) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	struct cfi_pri_intelext *cfip = cfi->cmdset_priv; | 
					
						
							|  |  |  | 	map_word status, OK = CMD(0x80); | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	unsigned long usec, suspended, start, done; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	flstate_t oldstate, newstate; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  |        	start = xip_currtime(); | 
					
						
							| 
									
										
										
										
											2008-08-05 18:37:41 +02:00
										 |  |  | 	usec = chip_op_time_max; | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	if (usec == 0) | 
					
						
							|  |  |  | 		usec = 500000; | 
					
						
							|  |  |  | 	done = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	do { | 
					
						
							|  |  |  | 		cpu_relax(); | 
					
						
							|  |  |  | 		if (xip_irqpending() && cfip && | 
					
						
							|  |  |  | 		    ((chip->state == FL_ERASING && (cfip->FeatureSupport&2)) || | 
					
						
							|  |  |  | 		     (chip->state == FL_WRITING && (cfip->FeatureSupport&4))) && | 
					
						
							|  |  |  | 		    (cfi_interleave_is_1(cfi) || chip->oldstate == FL_READY)) { | 
					
						
							|  |  |  | 			/*
 | 
					
						
							|  |  |  | 			 * Let's suspend the erase or write operation when | 
					
						
							|  |  |  | 			 * supported.  Note that we currently don't try to | 
					
						
							|  |  |  | 			 * suspend interleaved chips if there is already | 
					
						
							|  |  |  | 			 * another operation suspended (imagine what happens | 
					
						
							|  |  |  | 			 * when one chip was already done with the current | 
					
						
							|  |  |  | 			 * operation while another chip suspended it, then | 
					
						
							|  |  |  | 			 * we resume the whole thing at once).  Yes, it | 
					
						
							|  |  |  | 			 * can happen! | 
					
						
							|  |  |  | 			 */ | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 			usec -= done; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			map_write(map, CMD(0xb0), adr); | 
					
						
							|  |  |  | 			map_write(map, CMD(0x70), adr); | 
					
						
							|  |  |  | 			suspended = xip_currtime(); | 
					
						
							|  |  |  | 			do { | 
					
						
							|  |  |  | 				if (xip_elapsed_since(suspended) > 100000) { | 
					
						
							|  |  |  | 					/*
 | 
					
						
							|  |  |  | 					 * The chip doesn't want to suspend | 
					
						
							|  |  |  | 					 * after waiting for 100 msecs. | 
					
						
							|  |  |  | 					 * This is a critical error but there | 
					
						
							|  |  |  | 					 * is not much we can do here. | 
					
						
							|  |  |  | 					 */ | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 					return -EIO; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				status = map_read(map, adr); | 
					
						
							|  |  |  | 			} while (!map_word_andequal(map, status, OK, OK)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* Suspend succeeded */ | 
					
						
							|  |  |  | 			oldstate = chip->state; | 
					
						
							|  |  |  | 			if (oldstate == FL_ERASING) { | 
					
						
							|  |  |  | 				if (!map_word_bitsset(map, status, CMD(0x40))) | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 				newstate = FL_XIP_WHILE_ERASING; | 
					
						
							|  |  |  | 				chip->erase_suspended = 1; | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				if (!map_word_bitsset(map, status, CMD(0x04))) | 
					
						
							|  |  |  | 					break; | 
					
						
							|  |  |  | 				newstate = FL_XIP_WHILE_WRITING; | 
					
						
							|  |  |  | 				chip->write_suspended = 1; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			chip->state = newstate; | 
					
						
							|  |  |  | 			map_write(map, CMD(0xff), adr); | 
					
						
							|  |  |  | 			(void) map_read(map, adr); | 
					
						
							| 
									
										
										
										
											2008-02-27 01:42:39 +02:00
										 |  |  | 			xip_iprefetch(); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			local_irq_enable(); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2008-02-27 01:42:39 +02:00
										 |  |  | 			xip_iprefetch(); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			cond_resched(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/*
 | 
					
						
							|  |  |  | 			 * We're back.  However someone else might have | 
					
						
							|  |  |  | 			 * decided to go write to the chip if we are in | 
					
						
							|  |  |  | 			 * a suspended erase state.  If so let's wait | 
					
						
							|  |  |  | 			 * until it's done. | 
					
						
							|  |  |  | 			 */ | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			while (chip->state != newstate) { | 
					
						
							|  |  |  | 				DECLARE_WAITQUEUE(wait, current); | 
					
						
							|  |  |  | 				set_current_state(TASK_UNINTERRUPTIBLE); | 
					
						
							|  |  |  | 				add_wait_queue(&chip->wq, &wait); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 				mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 				schedule(); | 
					
						
							|  |  |  | 				remove_wait_queue(&chip->wq, &wait); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 				mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			/* Disallow XIP again */ | 
					
						
							|  |  |  | 			local_irq_disable(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* Resume the write or erase operation */ | 
					
						
							|  |  |  | 			map_write(map, CMD(0xd0), adr); | 
					
						
							|  |  |  | 			map_write(map, CMD(0x70), adr); | 
					
						
							|  |  |  | 			chip->state = oldstate; | 
					
						
							|  |  |  | 			start = xip_currtime(); | 
					
						
							|  |  |  | 		} else if (usec >= 1000000/HZ) { | 
					
						
							|  |  |  | 			/*
 | 
					
						
							|  |  |  | 			 * Try to save on CPU power when waiting delay | 
					
						
							|  |  |  | 			 * is at least a system timer tick period. | 
					
						
							|  |  |  | 			 * No need to be extremely accurate here. | 
					
						
							|  |  |  | 			 */ | 
					
						
							|  |  |  | 			xip_cpu_idle(); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		status = map_read(map, adr); | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 		done = xip_elapsed_since(start); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} while (!map_word_andequal(map, status, OK, OK) | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 		 && done < usec); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	return (done >= usec) ? -ETIME : 0; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | /*
 | 
					
						
							|  |  |  |  * The INVALIDATE_CACHED_RANGE() macro is normally used in parallel while | 
					
						
							|  |  |  |  * the flash is actively programming or erasing since we have to poll for | 
					
						
							|  |  |  |  * the operation to complete anyway.  We can't do that in a generic way with | 
					
						
							| 
									
										
										
										
											2005-05-19 18:05:47 +01:00
										 |  |  |  * a XIP setup so do it before the actual flash operation in this case | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  |  * and stub it out from INVAL_CACHE_AND_WAIT. | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2005-05-19 18:05:47 +01:00
										 |  |  | #define XIP_INVAL_CACHED_RANGE(map, from, size)  \
 | 
					
						
							|  |  |  | 	INVALIDATE_CACHED_RANGE(map, from, size) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-08-05 18:37:41 +02:00
										 |  |  | #define INVAL_CACHE_AND_WAIT(map, chip, cmd_adr, inval_adr, inval_len, usec, usec_max) \
 | 
					
						
							|  |  |  | 	xip_wait_for_operation(map, chip, cmd_adr, usec_max) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define xip_disable(map, chip, adr)
 | 
					
						
							|  |  |  | #define xip_enable(map, chip, adr)
 | 
					
						
							|  |  |  | #define XIP_INVAL_CACHED_RANGE(x...)
 | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | #define INVAL_CACHE_AND_WAIT inval_cache_and_wait_for_operation
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int inval_cache_and_wait_for_operation( | 
					
						
							|  |  |  | 		struct map_info *map, struct flchip *chip, | 
					
						
							|  |  |  | 		unsigned long cmd_adr, unsigned long inval_adr, int inval_len, | 
					
						
							| 
									
										
										
										
											2008-08-05 18:37:41 +02:00
										 |  |  | 		unsigned int chip_op_time, unsigned int chip_op_time_max) | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	map_word status, status_OK = CMD(0x80); | 
					
						
							| 
									
										
										
										
											2006-06-28 19:22:07 +01:00
										 |  |  | 	int chip_state = chip->state; | 
					
						
							| 
									
										
										
										
											2008-07-16 15:28:56 +01:00
										 |  |  | 	unsigned int timeo, sleep_time, reset_timeo; | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	if (inval_len) | 
					
						
							|  |  |  | 		INVALIDATE_CACHED_RANGE(map, inval_adr, inval_len); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-08-05 18:37:41 +02:00
										 |  |  | 	timeo = chip_op_time_max; | 
					
						
							| 
									
										
										
										
											2006-06-28 19:22:07 +01:00
										 |  |  | 	if (!timeo) | 
					
						
							|  |  |  | 		timeo = 500000; | 
					
						
							| 
									
										
										
										
											2008-07-16 15:28:56 +01:00
										 |  |  | 	reset_timeo = timeo; | 
					
						
							| 
									
										
										
										
											2006-06-28 19:22:07 +01:00
										 |  |  | 	sleep_time = chip_op_time / 2; | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	for (;;) { | 
					
						
							| 
									
										
										
										
											2011-02-07 17:07:11 +01:00
										 |  |  | 		if (chip->state != chip_state) { | 
					
						
							|  |  |  | 			/* Someone's suspended the operation: sleep */ | 
					
						
							|  |  |  | 			DECLARE_WAITQUEUE(wait, current); | 
					
						
							|  |  |  | 			set_current_state(TASK_UNINTERRUPTIBLE); | 
					
						
							|  |  |  | 			add_wait_queue(&chip->wq, &wait); | 
					
						
							|  |  |  | 			mutex_unlock(&chip->mutex); | 
					
						
							|  |  |  | 			schedule(); | 
					
						
							|  |  |  | 			remove_wait_queue(&chip->wq, &wait); | 
					
						
							|  |  |  | 			mutex_lock(&chip->mutex); | 
					
						
							|  |  |  | 			continue; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 		status = map_read(map, cmd_adr); | 
					
						
							|  |  |  | 		if (map_word_andequal(map, status, status_OK, status_OK)) | 
					
						
							|  |  |  | 			break; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-02-07 17:07:11 +01:00
										 |  |  | 		if (chip->erase_suspended && chip_state == FL_ERASING)  { | 
					
						
							| 
									
										
										
										
											2011-03-30 22:57:33 -03:00
										 |  |  | 			/* Erase suspend occurred while sleep: reset timeout */ | 
					
						
							| 
									
										
										
										
											2011-02-07 17:07:11 +01:00
										 |  |  | 			timeo = reset_timeo; | 
					
						
							|  |  |  | 			chip->erase_suspended = 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if (chip->write_suspended && chip_state == FL_WRITING)  { | 
					
						
							| 
									
										
										
										
											2011-03-30 22:57:33 -03:00
										 |  |  | 			/* Write suspend occurred while sleep: reset timeout */ | 
					
						
							| 
									
										
										
										
											2011-02-07 17:07:11 +01:00
										 |  |  | 			timeo = reset_timeo; | 
					
						
							|  |  |  | 			chip->write_suspended = 0; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2006-06-28 19:22:07 +01:00
										 |  |  | 		if (!timeo) { | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 			map_write(map, CMD(0x70), cmd_adr); | 
					
						
							|  |  |  | 			chip->state = FL_STATUS; | 
					
						
							|  |  |  | 			return -ETIME; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-06-28 19:22:07 +01:00
										 |  |  | 		/* OK Still waiting. Drop the lock, wait a while and retry. */ | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2006-06-28 19:22:07 +01:00
										 |  |  | 		if (sleep_time >= 1000000/HZ) { | 
					
						
							|  |  |  | 			/*
 | 
					
						
							|  |  |  | 			 * Half of the normal delay still remaining | 
					
						
							|  |  |  | 			 * can be performed with a sleeping delay instead | 
					
						
							|  |  |  | 			 * of busy waiting. | 
					
						
							|  |  |  | 			 */ | 
					
						
							|  |  |  | 			msleep(sleep_time/1000); | 
					
						
							|  |  |  | 			timeo -= sleep_time; | 
					
						
							|  |  |  | 			sleep_time = 1000000/HZ; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			udelay(1); | 
					
						
							|  |  |  | 			cond_resched(); | 
					
						
							|  |  |  | 			timeo--; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2006-06-28 19:22:07 +01:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/* Done and happy. */ | 
					
						
							|  |  |  |  	chip->state = FL_STATUS; | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2005-05-19 18:05:47 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-08-05 18:37:41 +02:00
										 |  |  | #define WAIT_TIMEOUT(map, chip, adr, udelay, udelay_max) \
 | 
					
						
							|  |  |  | 	INVAL_CACHE_AND_WAIT(map, chip, adr, 0, 0, udelay, udelay_max); | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | static int do_point_onechip (struct map_info *map, struct flchip *chip, loff_t adr, size_t len) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned long cmd_addr; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	int ret = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	adr += chip->start; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 	/* Ensure cmd read/writes are aligned. */ | 
					
						
							|  |  |  | 	cmd_addr = adr & ~(map_bankwidth(map)-1); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	ret = get_chip(map, chip, cmd_addr, FL_POINT); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!ret) { | 
					
						
							|  |  |  | 		if (chip->state != FL_POINT && chip->state != FL_READY) | 
					
						
							|  |  |  | 			map_write(map, CMD(0xff), cmd_addr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		chip->state = FL_POINT; | 
					
						
							|  |  |  | 		chip->ref_point_counter++; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-04-29 23:26:49 -07:00
										 |  |  | static int cfi_intelext_point(struct mtd_info *mtd, loff_t from, size_t len, | 
					
						
							|  |  |  | 		size_t *retlen, void **virt, resource_size_t *phys) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							| 
									
										
										
										
											2007-01-12 18:05:10 -05:00
										 |  |  | 	unsigned long ofs, last_end = 0; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	int chipnum; | 
					
						
							|  |  |  | 	int ret = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-03 16:23:52 +02:00
										 |  |  | 	if (!map->virt) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		return -EINVAL; | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	/* Now lock the chip(s) to POINT state */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* ofs: offset within the first chip that the first read should start */ | 
					
						
							|  |  |  | 	chipnum = (from >> cfi->chipshift); | 
					
						
							|  |  |  | 	ofs = from - (chipnum << cfi->chipshift); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-04-29 23:26:49 -07:00
										 |  |  | 	*virt = map->virt + cfi->chips[chipnum].start + ofs; | 
					
						
							|  |  |  | 	if (phys) | 
					
						
							|  |  |  | 		*phys = map->phys + cfi->chips[chipnum].start + ofs; | 
					
						
							| 
									
										
										
										
											2007-01-12 18:05:10 -05:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	while (len) { | 
					
						
							|  |  |  | 		unsigned long thislen; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (chipnum >= cfi->numchips) | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-01-12 18:05:10 -05:00
										 |  |  | 		/* We cannot point across chips that are virtually disjoint */ | 
					
						
							|  |  |  | 		if (!last_end) | 
					
						
							|  |  |  | 			last_end = cfi->chips[chipnum].start; | 
					
						
							|  |  |  | 		else if (cfi->chips[chipnum].start != last_end) | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		if ((len + ofs -1) >> cfi->chipshift) | 
					
						
							|  |  |  | 			thislen = (1<<cfi->chipshift) - ofs; | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			thislen = len; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ret = do_point_onechip(map, &cfi->chips[chipnum], ofs, thislen); | 
					
						
							|  |  |  | 		if (ret) | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		*retlen += thislen; | 
					
						
							|  |  |  | 		len -= thislen; | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		ofs = 0; | 
					
						
							| 
									
										
										
										
											2007-01-12 18:05:10 -05:00
										 |  |  | 		last_end += 1 << cfi->chipshift; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		chipnum++; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-03 13:20:43 +02:00
										 |  |  | static int cfi_intelext_unpoint(struct mtd_info *mtd, loff_t from, size_t len) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	unsigned long ofs; | 
					
						
							| 
									
										
										
										
											2012-02-03 13:20:43 +02:00
										 |  |  | 	int chipnum, err = 0; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/* Now unlock the chip(s) POINT state */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* ofs: offset within the first chip that the first read should start */ | 
					
						
							|  |  |  | 	chipnum = (from >> cfi->chipshift); | 
					
						
							|  |  |  | 	ofs = from - (chipnum <<  cfi->chipshift); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-02-03 13:20:43 +02:00
										 |  |  | 	while (len && !err) { | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		unsigned long thislen; | 
					
						
							|  |  |  | 		struct flchip *chip; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		chip = &cfi->chips[chipnum]; | 
					
						
							|  |  |  | 		if (chipnum >= cfi->numchips) | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if ((len + ofs -1) >> cfi->chipshift) | 
					
						
							|  |  |  | 			thislen = (1<<cfi->chipshift) - ofs; | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			thislen = len; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		if (chip->state == FL_POINT) { | 
					
						
							|  |  |  | 			chip->ref_point_counter--; | 
					
						
							|  |  |  | 			if(chip->ref_point_counter == 0) | 
					
						
							|  |  |  | 				chip->state = FL_READY; | 
					
						
							| 
									
										
										
										
											2012-02-03 13:20:43 +02:00
										 |  |  | 		} else { | 
					
						
							|  |  |  | 			printk(KERN_ERR "%s: Error: unpoint called on non pointed region\n", map->name); | 
					
						
							|  |  |  | 			err = -EINVAL; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		put_chip(map, chip, chip->start); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		len -= thislen; | 
					
						
							|  |  |  | 		ofs = 0; | 
					
						
							|  |  |  | 		chipnum++; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2012-02-03 13:20:43 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return err; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline int do_read_onechip(struct map_info *map, struct flchip *chip, loff_t adr, size_t len, u_char *buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned long cmd_addr; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	adr += chip->start; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 	/* Ensure cmd read/writes are aligned. */ | 
					
						
							|  |  |  | 	cmd_addr = adr & ~(map_bankwidth(map)-1); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	ret = get_chip(map, chip, cmd_addr, FL_READY); | 
					
						
							|  |  |  | 	if (ret) { | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (chip->state != FL_POINT && chip->state != FL_READY) { | 
					
						
							|  |  |  | 		map_write(map, CMD(0xff), cmd_addr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		chip->state = FL_READY; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	map_copy_from(map, buf, adr, len); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	put_chip(map, chip, cmd_addr); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cfi_intelext_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	unsigned long ofs; | 
					
						
							|  |  |  | 	int chipnum; | 
					
						
							|  |  |  | 	int ret = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* ofs: offset within the first chip that the first read should start */ | 
					
						
							|  |  |  | 	chipnum = (from >> cfi->chipshift); | 
					
						
							|  |  |  | 	ofs = from - (chipnum <<  cfi->chipshift); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	while (len) { | 
					
						
							|  |  |  | 		unsigned long thislen; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (chipnum >= cfi->numchips) | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if ((len + ofs -1) >> cfi->chipshift) | 
					
						
							|  |  |  | 			thislen = (1<<cfi->chipshift) - ofs; | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			thislen = len; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ret = do_read_onechip(map, &cfi->chips[chipnum], ofs, thislen, buf); | 
					
						
							|  |  |  | 		if (ret) | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		*retlen += thislen; | 
					
						
							|  |  |  | 		len -= thislen; | 
					
						
							|  |  |  | 		buf += thislen; | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		ofs = 0; | 
					
						
							|  |  |  | 		chipnum++; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip, | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 				     unsigned long adr, map_word datum, int mode) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	map_word status, write_cmd; | 
					
						
							|  |  |  | 	int ret=0; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	adr += chip->start; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 	switch (mode) { | 
					
						
							| 
									
										
										
										
											2005-08-06 05:40:46 +01:00
										 |  |  | 	case FL_WRITING: | 
					
						
							| 
									
										
										
										
											2010-10-26 10:55:29 +01:00
										 |  |  | 		write_cmd = (cfi->cfiq->P_ID != P_ID_INTEL_PERFORMANCE) ? CMD(0x40) : CMD(0x41); | 
					
						
							| 
									
										
										
										
											2005-08-06 05:40:46 +01:00
										 |  |  | 		break; | 
					
						
							|  |  |  | 	case FL_OTP_WRITE: | 
					
						
							|  |  |  | 		write_cmd = CMD(0xc0); | 
					
						
							|  |  |  | 		break; | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 	ret = get_chip(map, chip, adr, mode); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (ret) { | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	XIP_INVAL_CACHED_RANGE(map, adr, map_bankwidth(map)); | 
					
						
							|  |  |  | 	ENABLE_VPP(map); | 
					
						
							|  |  |  | 	xip_disable(map, chip, adr); | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 	map_write(map, write_cmd, adr); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	map_write(map, datum, adr); | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 	chip->state = mode; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	ret = INVAL_CACHE_AND_WAIT(map, chip, adr, | 
					
						
							|  |  |  | 				   adr, map_bankwidth(map), | 
					
						
							| 
									
										
										
										
											2008-08-05 18:37:41 +02:00
										 |  |  | 				   chip->word_write_time, | 
					
						
							|  |  |  | 				   chip->word_write_time_max); | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	if (ret) { | 
					
						
							|  |  |  | 		xip_enable(map, chip, adr); | 
					
						
							|  |  |  | 		printk(KERN_ERR "%s: word write error (status timeout)\n", map->name); | 
					
						
							|  |  |  | 		goto out; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 	/* check for errors */ | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	status = map_read(map, adr); | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 	if (map_word_bitsset(map, status, CMD(0x1a))) { | 
					
						
							|  |  |  | 		unsigned long chipstatus = MERGESTATUS(status); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* reset status */ | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		map_write(map, CMD(0x50), adr); | 
					
						
							|  |  |  | 		map_write(map, CMD(0x70), adr); | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 		xip_enable(map, chip, adr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (chipstatus & 0x02) { | 
					
						
							|  |  |  | 			ret = -EROFS; | 
					
						
							|  |  |  | 		} else if (chipstatus & 0x08) { | 
					
						
							|  |  |  | 			printk(KERN_ERR "%s: word write error (bad VPP)\n", map->name); | 
					
						
							|  |  |  | 			ret = -EIO; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			printk(KERN_ERR "%s: word write error (status 0x%lx)\n", map->name, chipstatus); | 
					
						
							|  |  |  | 			ret = -EINVAL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		goto out; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	xip_enable(map, chip, adr); | 
					
						
							| 
									
										
										
										
											2012-03-07 14:10:33 +00:00
										 |  |  |  out:	DISABLE_VPP(map); | 
					
						
							|  |  |  | 	put_chip(map, chip, adr); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cfi_intelext_write_words (struct mtd_info *mtd, loff_t to , size_t len, size_t *retlen, const u_char *buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	int ret = 0; | 
					
						
							|  |  |  | 	int chipnum; | 
					
						
							|  |  |  | 	unsigned long ofs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	chipnum = to >> cfi->chipshift; | 
					
						
							|  |  |  | 	ofs = to  - (chipnum << cfi->chipshift); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* If it's not bus-aligned, do the first byte write */ | 
					
						
							|  |  |  | 	if (ofs & (map_bankwidth(map)-1)) { | 
					
						
							|  |  |  | 		unsigned long bus_ofs = ofs & ~(map_bankwidth(map)-1); | 
					
						
							|  |  |  | 		int gap = ofs - bus_ofs; | 
					
						
							|  |  |  | 		int n; | 
					
						
							|  |  |  | 		map_word datum; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		n = min_t(int, len, map_bankwidth(map)-gap); | 
					
						
							|  |  |  | 		datum = map_word_ff(map); | 
					
						
							|  |  |  | 		datum = map_word_load_partial(map, datum, buf, gap, n); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ret = do_write_oneword(map, &cfi->chips[chipnum], | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 					       bus_ofs, datum, FL_WRITING); | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 		if (ret) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		len -= n; | 
					
						
							|  |  |  | 		ofs += n; | 
					
						
							|  |  |  | 		buf += n; | 
					
						
							|  |  |  | 		(*retlen) += n; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (ofs >> cfi->chipshift) { | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 			chipnum ++; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			ofs = 0; | 
					
						
							|  |  |  | 			if (chipnum == cfi->numchips) | 
					
						
							|  |  |  | 				return 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	while(len >= map_bankwidth(map)) { | 
					
						
							|  |  |  | 		map_word datum = map_word_load(map, buf); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ret = do_write_oneword(map, &cfi->chips[chipnum], | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 				       ofs, datum, FL_WRITING); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		if (ret) | 
					
						
							|  |  |  | 			return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ofs += map_bankwidth(map); | 
					
						
							|  |  |  | 		buf += map_bankwidth(map); | 
					
						
							|  |  |  | 		(*retlen) += map_bankwidth(map); | 
					
						
							|  |  |  | 		len -= map_bankwidth(map); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (ofs >> cfi->chipshift) { | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 			chipnum ++; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			ofs = 0; | 
					
						
							|  |  |  | 			if (chipnum == cfi->numchips) | 
					
						
							|  |  |  | 				return 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (len & (map_bankwidth(map)-1)) { | 
					
						
							|  |  |  | 		map_word datum; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		datum = map_word_ff(map); | 
					
						
							|  |  |  | 		datum = map_word_load_partial(map, datum, buf, 0, len); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ret = do_write_oneword(map, &cfi->chips[chipnum], | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 				       ofs, datum, FL_WRITING); | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 		if (ret) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			return ret; | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		(*retlen) += len; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 				    unsigned long adr, const struct kvec **pvec, | 
					
						
							|  |  |  | 				    unsigned long *pvec_seek, int len) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	map_word status, write_cmd, datum; | 
					
						
							|  |  |  | 	unsigned long cmd_adr; | 
					
						
							|  |  |  | 	int ret, wbufsize, word_gap, words; | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 	const struct kvec *vec; | 
					
						
							|  |  |  | 	unsigned long vec_seek; | 
					
						
							| 
									
										
										
										
											2008-01-11 10:24:11 +00:00
										 |  |  | 	unsigned long initial_adr; | 
					
						
							|  |  |  | 	int initial_len = len; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; | 
					
						
							|  |  |  | 	adr += chip->start; | 
					
						
							| 
									
										
										
										
											2008-01-11 10:24:11 +00:00
										 |  |  | 	initial_adr = adr; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	cmd_adr = adr & ~(wbufsize-1); | 
					
						
							| 
									
										
										
										
											2005-08-06 05:40:46 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	/* Let's determine this according to the interleave only once */ | 
					
						
							| 
									
										
										
										
											2010-10-26 10:55:29 +01:00
										 |  |  | 	write_cmd = (cfi->cfiq->P_ID != P_ID_INTEL_PERFORMANCE) ? CMD(0xe8) : CMD(0xe9); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	ret = get_chip(map, chip, cmd_adr, FL_WRITING); | 
					
						
							|  |  |  | 	if (ret) { | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-01-11 10:24:11 +00:00
										 |  |  | 	XIP_INVAL_CACHED_RANGE(map, initial_adr, initial_len); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	ENABLE_VPP(map); | 
					
						
							|  |  |  | 	xip_disable(map, chip, cmd_adr); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-05-14 01:51:54 +01:00
										 |  |  | 	/* §4.8 of the 28FxxxJ3A datasheet says "Any time SR.4 and/or SR.5 is set
 | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 	   [...], the device will not accept any more Write to Buffer commands". | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	   So we must check here and reset those bits if they're set. Otherwise | 
					
						
							|  |  |  | 	   we're just pissing in the wind */ | 
					
						
							| 
									
										
										
										
											2006-03-29 23:31:42 +01:00
										 |  |  | 	if (chip->state != FL_STATUS) { | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		map_write(map, CMD(0x70), cmd_adr); | 
					
						
							| 
									
										
										
										
											2006-03-29 23:31:42 +01:00
										 |  |  | 		chip->state = FL_STATUS; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	status = map_read(map, cmd_adr); | 
					
						
							|  |  |  | 	if (map_word_bitsset(map, status, CMD(0x30))) { | 
					
						
							|  |  |  | 		xip_enable(map, chip, cmd_adr); | 
					
						
							|  |  |  | 		printk(KERN_WARNING "SR.4 or SR.5 bits set in buffer write (status %lx). Clearing.\n", status.x[0]); | 
					
						
							|  |  |  | 		xip_disable(map, chip, cmd_adr); | 
					
						
							|  |  |  | 		map_write(map, CMD(0x50), cmd_adr); | 
					
						
							|  |  |  | 		map_write(map, CMD(0x70), cmd_adr); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	chip->state = FL_WRITING_TO_BUFFER; | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	map_write(map, write_cmd, cmd_adr); | 
					
						
							| 
									
										
										
										
											2008-08-05 18:37:41 +02:00
										 |  |  | 	ret = WAIT_TIMEOUT(map, chip, cmd_adr, 0, 0); | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	if (ret) { | 
					
						
							|  |  |  | 		/* Argh. Not ready for write to buffer */ | 
					
						
							|  |  |  | 		map_word Xstatus = map_read(map, cmd_adr); | 
					
						
							|  |  |  | 		map_write(map, CMD(0x70), cmd_adr); | 
					
						
							|  |  |  | 		chip->state = FL_STATUS; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		status = map_read(map, cmd_adr); | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 		map_write(map, CMD(0x50), cmd_adr); | 
					
						
							|  |  |  | 		map_write(map, CMD(0x70), cmd_adr); | 
					
						
							|  |  |  | 		xip_enable(map, chip, cmd_adr); | 
					
						
							|  |  |  | 		printk(KERN_ERR "%s: Chip not ready for buffer write. Xstatus = %lx, status = %lx\n", | 
					
						
							|  |  |  | 				map->name, Xstatus.x[0], status.x[0]); | 
					
						
							|  |  |  | 		goto out; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 	/* Figure out the number of words to write */ | 
					
						
							|  |  |  | 	word_gap = (-adr & (map_bankwidth(map)-1)); | 
					
						
							| 
									
										
										
										
											2008-08-02 17:14:21 +02:00
										 |  |  | 	words = DIV_ROUND_UP(len - word_gap, map_bankwidth(map)); | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 	if (!word_gap) { | 
					
						
							|  |  |  | 		words--; | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		word_gap = map_bankwidth(map) - word_gap; | 
					
						
							|  |  |  | 		adr -= word_gap; | 
					
						
							|  |  |  | 		datum = map_word_ff(map); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	/* Write length of data to come */ | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 	map_write(map, CMD(words), cmd_adr ); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/* Write data */ | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 	vec = *pvec; | 
					
						
							|  |  |  | 	vec_seek = *pvec_seek; | 
					
						
							|  |  |  | 	do { | 
					
						
							|  |  |  | 		int n = map_bankwidth(map) - word_gap; | 
					
						
							|  |  |  | 		if (n > vec->iov_len - vec_seek) | 
					
						
							|  |  |  | 			n = vec->iov_len - vec_seek; | 
					
						
							|  |  |  | 		if (n > len) | 
					
						
							|  |  |  | 			n = len; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 		if (!word_gap && len < map_bankwidth(map)) | 
					
						
							|  |  |  | 			datum = map_word_ff(map); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 		datum = map_word_load_partial(map, datum, | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 					      vec->iov_base + vec_seek, | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 					      word_gap, n); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 		len -= n; | 
					
						
							|  |  |  | 		word_gap += n; | 
					
						
							|  |  |  | 		if (!len || word_gap == map_bankwidth(map)) { | 
					
						
							|  |  |  | 			map_write(map, datum, adr); | 
					
						
							|  |  |  | 			adr += map_bankwidth(map); | 
					
						
							|  |  |  | 			word_gap = 0; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 		vec_seek += n; | 
					
						
							|  |  |  | 		if (vec_seek == vec->iov_len) { | 
					
						
							|  |  |  | 			vec++; | 
					
						
							|  |  |  | 			vec_seek = 0; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} while (len); | 
					
						
							|  |  |  | 	*pvec = vec; | 
					
						
							|  |  |  | 	*pvec_seek = vec_seek; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	/* GO GO GO */ | 
					
						
							|  |  |  | 	map_write(map, CMD(0xd0), cmd_adr); | 
					
						
							|  |  |  | 	chip->state = FL_WRITING; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	ret = INVAL_CACHE_AND_WAIT(map, chip, cmd_adr, | 
					
						
							| 
									
										
										
										
											2008-01-11 10:24:11 +00:00
										 |  |  | 				   initial_adr, initial_len, | 
					
						
							| 
									
										
										
										
											2008-08-05 18:37:41 +02:00
										 |  |  | 				   chip->buffer_write_time, | 
					
						
							|  |  |  | 				   chip->buffer_write_time_max); | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	if (ret) { | 
					
						
							|  |  |  | 		map_write(map, CMD(0x70), cmd_adr); | 
					
						
							|  |  |  | 		chip->state = FL_STATUS; | 
					
						
							|  |  |  | 		xip_enable(map, chip, cmd_adr); | 
					
						
							|  |  |  | 		printk(KERN_ERR "%s: buffer write error (status timeout)\n", map->name); | 
					
						
							|  |  |  | 		goto out; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 	/* check for errors */ | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	status = map_read(map, cmd_adr); | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 	if (map_word_bitsset(map, status, CMD(0x1a))) { | 
					
						
							|  |  |  | 		unsigned long chipstatus = MERGESTATUS(status); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* reset status */ | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		map_write(map, CMD(0x50), cmd_adr); | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 		map_write(map, CMD(0x70), cmd_adr); | 
					
						
							|  |  |  | 		xip_enable(map, chip, cmd_adr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (chipstatus & 0x02) { | 
					
						
							|  |  |  | 			ret = -EROFS; | 
					
						
							|  |  |  | 		} else if (chipstatus & 0x08) { | 
					
						
							|  |  |  | 			printk(KERN_ERR "%s: buffer write error (bad VPP)\n", map->name); | 
					
						
							|  |  |  | 			ret = -EIO; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			printk(KERN_ERR "%s: buffer write error (status 0x%lx)\n", map->name, chipstatus); | 
					
						
							|  |  |  | 			ret = -EINVAL; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		goto out; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	xip_enable(map, chip, cmd_adr); | 
					
						
							| 
									
										
										
										
											2012-03-07 14:10:33 +00:00
										 |  |  |  out:	DISABLE_VPP(map); | 
					
						
							|  |  |  | 	put_chip(map, chip, cmd_adr); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | static int cfi_intelext_writev (struct mtd_info *mtd, const struct kvec *vecs, | 
					
						
							|  |  |  | 				unsigned long count, loff_t to, size_t *retlen) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	int wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; | 
					
						
							|  |  |  | 	int ret = 0; | 
					
						
							|  |  |  | 	int chipnum; | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 	unsigned long ofs, vec_seek, i; | 
					
						
							|  |  |  | 	size_t len = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i = 0; i < count; i++) | 
					
						
							|  |  |  | 		len += vecs[i].iov_len; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if (!len) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	chipnum = to >> cfi->chipshift; | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 	ofs = to - (chipnum << cfi->chipshift); | 
					
						
							|  |  |  | 	vec_seek = 0; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 	do { | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		/* We must not cross write block boundaries */ | 
					
						
							|  |  |  | 		int size = wbufsize - (ofs & (wbufsize-1)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (size > len) | 
					
						
							|  |  |  | 			size = len; | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 		ret = do_write_buffer(map, &cfi->chips[chipnum], | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 				      ofs, &vecs, &vec_seek, size); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		if (ret) | 
					
						
							|  |  |  | 			return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ofs += size; | 
					
						
							|  |  |  | 		(*retlen) += size; | 
					
						
							|  |  |  | 		len -= size; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (ofs >> cfi->chipshift) { | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 			chipnum ++; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			ofs = 0; | 
					
						
							|  |  |  | 			if (chipnum == cfi->numchips) | 
					
						
							|  |  |  | 				return 0; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2005-12-06 17:28:19 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		/* Be nice and reschedule with the chip in a usable state for other
 | 
					
						
							|  |  |  | 		   processes. */ | 
					
						
							|  |  |  | 		cond_resched(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | 	} while (len); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:46:59 +01:00
										 |  |  | static int cfi_intelext_write_buffers (struct mtd_info *mtd, loff_t to, | 
					
						
							|  |  |  | 				       size_t len, size_t *retlen, const u_char *buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct kvec vec; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	vec.iov_base = (void *) buf; | 
					
						
							|  |  |  | 	vec.iov_len = len; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return cfi_intelext_writev(mtd, &vec, 1, to, retlen); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip, | 
					
						
							|  |  |  | 				      unsigned long adr, int len, void *thunk) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	map_word status; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	int retries = 3; | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	int ret; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	adr += chip->start; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  retry: | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	ret = get_chip(map, chip, adr, FL_ERASING); | 
					
						
							|  |  |  | 	if (ret) { | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	XIP_INVAL_CACHED_RANGE(map, adr, len); | 
					
						
							|  |  |  | 	ENABLE_VPP(map); | 
					
						
							|  |  |  | 	xip_disable(map, chip, adr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Clear the status register first */ | 
					
						
							|  |  |  | 	map_write(map, CMD(0x50), adr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Now erase */ | 
					
						
							|  |  |  | 	map_write(map, CMD(0x20), adr); | 
					
						
							|  |  |  | 	map_write(map, CMD(0xD0), adr); | 
					
						
							|  |  |  | 	chip->state = FL_ERASING; | 
					
						
							|  |  |  | 	chip->erase_suspended = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	ret = INVAL_CACHE_AND_WAIT(map, chip, adr, | 
					
						
							|  |  |  | 				   adr, len, | 
					
						
							| 
									
										
										
										
											2008-08-05 18:37:41 +02:00
										 |  |  | 				   chip->erase_time, | 
					
						
							|  |  |  | 				   chip->erase_time_max); | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	if (ret) { | 
					
						
							|  |  |  | 		map_write(map, CMD(0x70), adr); | 
					
						
							|  |  |  | 		chip->state = FL_STATUS; | 
					
						
							|  |  |  | 		xip_enable(map, chip, adr); | 
					
						
							|  |  |  | 		printk(KERN_ERR "%s: block erase error: (status timeout)\n", map->name); | 
					
						
							|  |  |  | 		goto out; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* We've broken this before. It doesn't hurt to be safe */ | 
					
						
							|  |  |  | 	map_write(map, CMD(0x70), adr); | 
					
						
							|  |  |  | 	chip->state = FL_STATUS; | 
					
						
							|  |  |  | 	status = map_read(map, adr); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 	/* check for errors */ | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	if (map_word_bitsset(map, status, CMD(0x3a))) { | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 		unsigned long chipstatus = MERGESTATUS(status); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		/* Reset the error bits */ | 
					
						
							|  |  |  | 		map_write(map, CMD(0x50), adr); | 
					
						
							|  |  |  | 		map_write(map, CMD(0x70), adr); | 
					
						
							|  |  |  | 		xip_enable(map, chip, adr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if ((chipstatus & 0x30) == 0x30) { | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 			printk(KERN_ERR "%s: block erase error: (bad command sequence, status 0x%lx)\n", map->name, chipstatus); | 
					
						
							|  |  |  | 			ret = -EINVAL; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		} else if (chipstatus & 0x02) { | 
					
						
							|  |  |  | 			/* Protection bit set */ | 
					
						
							|  |  |  | 			ret = -EROFS; | 
					
						
							|  |  |  | 		} else if (chipstatus & 0x8) { | 
					
						
							|  |  |  | 			/* Voltage */ | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 			printk(KERN_ERR "%s: block erase error: (bad VPP)\n", map->name); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			ret = -EIO; | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 		} else if (chipstatus & 0x20 && retries--) { | 
					
						
							|  |  |  | 			printk(KERN_DEBUG "block erase failed at 0x%08lx: status 0x%lx. Retrying...\n", adr, chipstatus); | 
					
						
							| 
									
										
										
										
											2012-03-07 14:10:33 +00:00
										 |  |  | 			DISABLE_VPP(map); | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 			put_chip(map, chip, adr); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 			goto retry; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			printk(KERN_ERR "%s: block erase failed at 0x%08lx (status 0x%lx)\n", map->name, adr, chipstatus); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			ret = -EIO; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		goto out; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-08-06 05:16:52 +01:00
										 |  |  | 	xip_enable(map, chip, adr); | 
					
						
							| 
									
										
										
										
											2012-03-07 14:10:33 +00:00
										 |  |  |  out:	DISABLE_VPP(map); | 
					
						
							|  |  |  | 	put_chip(map, chip, adr); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-05-28 20:11:37 +01:00
										 |  |  | static int cfi_intelext_erase_varsize(struct mtd_info *mtd, struct erase_info *instr) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	unsigned long ofs, len; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ofs = instr->addr; | 
					
						
							|  |  |  | 	len = instr->len; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = cfi_varsize_frob(mtd, do_erase_oneblock, ofs, len, NULL); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	instr->state = MTD_ERASE_DONE; | 
					
						
							|  |  |  | 	mtd_erase_callback(instr); | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void cfi_intelext_sync (struct mtd_info *mtd) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 	struct flchip *chip; | 
					
						
							|  |  |  | 	int ret = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i=0; !ret && i<cfi->numchips; i++) { | 
					
						
							|  |  |  | 		chip = &cfi->chips[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		ret = get_chip(map, chip, chip->start, FL_SYNCING); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!ret) { | 
					
						
							|  |  |  | 			chip->oldstate = chip->state; | 
					
						
							|  |  |  | 			chip->state = FL_SYNCING; | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 			/* No need to wake_up() on this state change -
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			 * as the whole point is that nobody can do anything | 
					
						
							|  |  |  | 			 * with the chip now anyway. | 
					
						
							|  |  |  | 			 */ | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Unlock the chips again */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i--; i >=0; i--) { | 
					
						
							|  |  |  | 		chip = &cfi->chips[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		if (chip->state == FL_SYNCING) { | 
					
						
							|  |  |  | 			chip->state = chip->oldstate; | 
					
						
							| 
									
										
										
										
											2005-03-16 22:41:09 +00:00
										 |  |  | 			chip->oldstate = FL_READY; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			wake_up(&chip->wq); | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | static int __xipram do_getlockstatus_oneblock(struct map_info *map, | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 						struct flchip *chip, | 
					
						
							|  |  |  | 						unsigned long adr, | 
					
						
							|  |  |  | 						int len, void *thunk) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	int status, ofs_factor = cfi->interleave * cfi->device_type; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-27 21:01:52 +01:00
										 |  |  | 	adr += chip->start; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	xip_disable(map, chip, adr+(2*ofs_factor)); | 
					
						
							| 
									
										
										
										
											2005-04-27 21:01:52 +01:00
										 |  |  | 	map_write(map, CMD(0x90), adr+(2*ofs_factor)); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	chip->state = FL_JEDEC_QUERY; | 
					
						
							|  |  |  | 	status = cfi_read_query(map, adr+(2*ofs_factor)); | 
					
						
							|  |  |  | 	xip_enable(map, chip, 0); | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | 	return status; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef DEBUG_LOCK_BITS
 | 
					
						
							|  |  |  | static int __xipram do_printlockstatus_oneblock(struct map_info *map, | 
					
						
							|  |  |  | 						struct flchip *chip, | 
					
						
							|  |  |  | 						unsigned long adr, | 
					
						
							|  |  |  | 						int len, void *thunk) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	printk(KERN_DEBUG "block status register for 0x%08lx is %x\n", | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | 	       adr, do_getlockstatus_oneblock(map, chip, adr, len, thunk)); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define DO_XXLOCK_ONEBLOCK_LOCK		((void *) 1)
 | 
					
						
							|  |  |  | #define DO_XXLOCK_ONEBLOCK_UNLOCK	((void *) 2)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __xipram do_xxlock_oneblock(struct map_info *map, struct flchip *chip, | 
					
						
							|  |  |  | 				       unsigned long adr, int len, void *thunk) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							| 
									
										
										
										
											2005-03-29 23:06:40 +01:00
										 |  |  | 	struct cfi_pri_intelext *extp = cfi->cmdset_priv; | 
					
						
							| 
									
										
										
										
											2012-08-28 11:34:13 +02:00
										 |  |  | 	int mdelay; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	adr += chip->start; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	ret = get_chip(map, chip, adr, FL_LOCKING); | 
					
						
							|  |  |  | 	if (ret) { | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ENABLE_VPP(map); | 
					
						
							|  |  |  | 	xip_disable(map, chip, adr); | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	map_write(map, CMD(0x60), adr); | 
					
						
							|  |  |  | 	if (thunk == DO_XXLOCK_ONEBLOCK_LOCK) { | 
					
						
							|  |  |  | 		map_write(map, CMD(0x01), adr); | 
					
						
							|  |  |  | 		chip->state = FL_LOCKING; | 
					
						
							|  |  |  | 	} else if (thunk == DO_XXLOCK_ONEBLOCK_UNLOCK) { | 
					
						
							|  |  |  | 		map_write(map, CMD(0xD0), adr); | 
					
						
							|  |  |  | 		chip->state = FL_UNLOCKING; | 
					
						
							|  |  |  | 	} else | 
					
						
							|  |  |  | 		BUG(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-03-29 23:06:40 +01:00
										 |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * If Instant Individual Block Locking supported then no need | 
					
						
							|  |  |  | 	 * to delay. | 
					
						
							|  |  |  | 	 */ | 
					
						
							| 
									
										
										
										
											2012-08-28 11:34:13 +02:00
										 |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * Unlocking may take up to 1.4 seconds on some Intel flashes. So | 
					
						
							|  |  |  | 	 * lets use a max of 1.5 seconds (1500ms) as timeout. | 
					
						
							|  |  |  | 	 * | 
					
						
							|  |  |  | 	 * See "Clear Block Lock-Bits Time" on page 40 in | 
					
						
							|  |  |  | 	 * "3 Volt Intel StrataFlash Memory" 28F128J3,28F640J3,28F320J3 manual | 
					
						
							|  |  |  | 	 * from February 2003 | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	mdelay = (!extp || !(extp->FeatureSupport & (1 << 5))) ? 1500 : 0; | 
					
						
							| 
									
										
										
										
											2005-03-29 23:06:40 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-08-28 11:34:13 +02:00
										 |  |  | 	ret = WAIT_TIMEOUT(map, chip, adr, mdelay, mdelay * 1000); | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	if (ret) { | 
					
						
							|  |  |  | 		map_write(map, CMD(0x70), adr); | 
					
						
							|  |  |  | 		chip->state = FL_STATUS; | 
					
						
							|  |  |  | 		xip_enable(map, chip, adr); | 
					
						
							|  |  |  | 		printk(KERN_ERR "%s: block unlock error: (status timeout)\n", map->name); | 
					
						
							|  |  |  | 		goto out; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	xip_enable(map, chip, adr); | 
					
						
							| 
									
										
										
										
											2012-03-07 14:10:33 +00:00
										 |  |  |  out:	DISABLE_VPP(map); | 
					
						
							|  |  |  | 	put_chip(map, chip, adr); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2006-03-30 15:52:41 +01:00
										 |  |  | 	return ret; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-12-10 13:37:21 +00:00
										 |  |  | static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef DEBUG_LOCK_BITS
 | 
					
						
							|  |  |  | 	printk(KERN_DEBUG "%s: lock status before, ofs=0x%08llx, len=0x%08X\n", | 
					
						
							| 
									
										
										
										
											2008-04-18 13:44:19 -07:00
										 |  |  | 	       __func__, ofs, len); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	cfi_varsize_frob(mtd, do_printlockstatus_oneblock, | 
					
						
							| 
									
										
										
										
											2007-06-28 23:00:09 +01:00
										 |  |  | 		ofs, len, NULL); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 	ret = cfi_varsize_frob(mtd, do_xxlock_oneblock, | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		ofs, len, DO_XXLOCK_ONEBLOCK_LOCK); | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #ifdef DEBUG_LOCK_BITS
 | 
					
						
							|  |  |  | 	printk(KERN_DEBUG "%s: lock status after, ret=%d\n", | 
					
						
							| 
									
										
										
										
											2008-04-18 13:44:19 -07:00
										 |  |  | 	       __func__, ret); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	cfi_varsize_frob(mtd, do_printlockstatus_oneblock, | 
					
						
							| 
									
										
										
										
											2007-06-28 23:00:09 +01:00
										 |  |  | 		ofs, len, NULL); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-12-10 13:37:21 +00:00
										 |  |  | static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef DEBUG_LOCK_BITS
 | 
					
						
							|  |  |  | 	printk(KERN_DEBUG "%s: lock status before, ofs=0x%08llx, len=0x%08X\n", | 
					
						
							| 
									
										
										
										
											2008-04-18 13:44:19 -07:00
										 |  |  | 	       __func__, ofs, len); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	cfi_varsize_frob(mtd, do_printlockstatus_oneblock, | 
					
						
							| 
									
										
										
										
											2007-06-28 23:00:09 +01:00
										 |  |  | 		ofs, len, NULL); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = cfi_varsize_frob(mtd, do_xxlock_oneblock, | 
					
						
							|  |  |  | 					ofs, len, DO_XXLOCK_ONEBLOCK_UNLOCK); | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #ifdef DEBUG_LOCK_BITS
 | 
					
						
							|  |  |  | 	printk(KERN_DEBUG "%s: lock status after, ret=%d\n", | 
					
						
							| 
									
										
										
										
											2008-04-18 13:44:19 -07:00
										 |  |  | 	       __func__, ret); | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 	cfi_varsize_frob(mtd, do_printlockstatus_oneblock, | 
					
						
							| 
									
										
										
										
											2007-06-28 23:00:09 +01:00
										 |  |  | 		ofs, len, NULL); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | #endif
 | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-06-14 18:10:33 +02:00
										 |  |  | static int cfi_intelext_is_locked(struct mtd_info *mtd, loff_t ofs, | 
					
						
							|  |  |  | 				  uint64_t len) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return cfi_varsize_frob(mtd, do_getlockstatus_oneblock, | 
					
						
							|  |  |  | 				ofs, len, NULL) ? 1 : 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | #ifdef CONFIG_MTD_OTP
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | typedef int (*otp_op_t)(struct map_info *map, struct flchip *chip, | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 			u_long data_offset, u_char *buf, u_int size, | 
					
						
							|  |  |  | 			u_long prot_offset, u_int groupno, u_int groupsize); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __xipram | 
					
						
							|  |  |  | do_otp_read(struct map_info *map, struct flchip *chip, u_long offset, | 
					
						
							|  |  |  | 	    u_char *buf, u_int size, u_long prot, u_int grpno, u_int grpsz) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 	ret = get_chip(map, chip, chip->start, FL_JEDEC_QUERY); | 
					
						
							|  |  |  | 	if (ret) { | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 		return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* let's ensure we're not reading back cached data from array mode */ | 
					
						
							| 
									
										
										
										
											2005-05-19 18:05:47 +01:00
										 |  |  | 	INVALIDATE_CACHED_RANGE(map, chip->start + offset, size); | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	xip_disable(map, chip, chip->start); | 
					
						
							|  |  |  | 	if (chip->state != FL_JEDEC_QUERY) { | 
					
						
							|  |  |  | 		map_write(map, CMD(0x90), chip->start); | 
					
						
							|  |  |  | 		chip->state = FL_JEDEC_QUERY; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	map_copy_from(map, buf, chip->start + offset, size); | 
					
						
							|  |  |  | 	xip_enable(map, chip, chip->start); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* then ensure we don't keep OTP data in the cache */ | 
					
						
							| 
									
										
										
										
											2005-05-19 18:05:47 +01:00
										 |  |  | 	INVALIDATE_CACHED_RANGE(map, chip->start + offset, size); | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	put_chip(map, chip, chip->start); | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 	mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int | 
					
						
							|  |  |  | do_otp_write(struct map_info *map, struct flchip *chip, u_long offset, | 
					
						
							|  |  |  | 	     u_char *buf, u_int size, u_long prot, u_int grpno, u_int grpsz) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	while (size) { | 
					
						
							|  |  |  | 		unsigned long bus_ofs = offset & ~(map_bankwidth(map)-1); | 
					
						
							|  |  |  | 		int gap = offset - bus_ofs; | 
					
						
							|  |  |  | 		int n = min_t(int, size, map_bankwidth(map)-gap); | 
					
						
							|  |  |  | 		map_word datum = map_word_ff(map); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		datum = map_word_load_partial(map, datum, buf, gap, n); | 
					
						
							|  |  |  | 		ret = do_write_oneword(map, chip, bus_ofs, datum, FL_OTP_WRITE); | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 		if (ret) | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 			return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		offset += n; | 
					
						
							|  |  |  | 		buf += n; | 
					
						
							|  |  |  | 		size -= n; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int | 
					
						
							|  |  |  | do_otp_lock(struct map_info *map, struct flchip *chip, u_long offset, | 
					
						
							|  |  |  | 	    u_char *buf, u_int size, u_long prot, u_int grpno, u_int grpsz) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	map_word datum; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* make sure area matches group boundaries */ | 
					
						
							| 
									
										
										
										
											2005-02-17 20:35:04 +00:00
										 |  |  | 	if (size != grpsz) | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 		return -EXDEV; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	datum = map_word_ff(map); | 
					
						
							|  |  |  | 	datum = map_word_clr(map, datum, CMD(1 << grpno)); | 
					
						
							|  |  |  | 	return do_write_oneword(map, chip, prot, datum, FL_OTP_WRITE); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cfi_intelext_otp_walk(struct mtd_info *mtd, loff_t from, size_t len, | 
					
						
							|  |  |  | 				 size_t *retlen, u_char *buf, | 
					
						
							|  |  |  | 				 otp_op_t action, int user_regs) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	struct cfi_pri_intelext *extp = cfi->cmdset_priv; | 
					
						
							|  |  |  | 	struct flchip *chip; | 
					
						
							|  |  |  | 	struct cfi_intelext_otpinfo *otp; | 
					
						
							|  |  |  | 	u_long devsize, reg_prot_offset, data_offset; | 
					
						
							|  |  |  | 	u_int chip_num, chip_step, field, reg_fact_size, reg_user_size; | 
					
						
							|  |  |  | 	u_int groups, groupno, groupsize, reg_fact_groups, reg_user_groups; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	*retlen = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Check that we actually have some OTP registers */ | 
					
						
							|  |  |  | 	if (!extp || !(extp->FeatureSupport & 64) || !extp->NumProtectionFields) | 
					
						
							|  |  |  | 		return -ENODATA; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* we need real chips here not virtual ones */ | 
					
						
							|  |  |  | 	devsize = (1 << cfi->cfiq->DevSize) * cfi->interleave; | 
					
						
							|  |  |  | 	chip_step = devsize >> cfi->chipshift; | 
					
						
							| 
									
										
										
										
											2005-04-01 17:36:29 +01:00
										 |  |  | 	chip_num = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Some chips have OTP located in the _top_ partition only.
 | 
					
						
							|  |  |  | 	   For example: Intel 28F256L18T (T means top-parameter device) */ | 
					
						
							| 
									
										
										
										
											2009-11-05 15:53:43 +01:00
										 |  |  | 	if (cfi->mfr == CFI_MFR_INTEL) { | 
					
						
							| 
									
										
										
										
											2005-04-01 17:36:29 +01:00
										 |  |  | 		switch (cfi->id) { | 
					
						
							|  |  |  | 		case 0x880b: | 
					
						
							|  |  |  | 		case 0x880c: | 
					
						
							|  |  |  | 		case 0x880d: | 
					
						
							|  |  |  | 			chip_num = chip_step - 1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-01 17:36:29 +01:00
										 |  |  | 	for ( ; chip_num < cfi->numchips; chip_num += chip_step) { | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 		chip = &cfi->chips[chip_num]; | 
					
						
							|  |  |  | 		otp = (struct cfi_intelext_otpinfo *)&extp->extra[0]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* first OTP region */ | 
					
						
							|  |  |  | 		field = 0; | 
					
						
							|  |  |  | 		reg_prot_offset = extp->ProtRegAddr; | 
					
						
							|  |  |  | 		reg_fact_groups = 1; | 
					
						
							|  |  |  | 		reg_fact_size = 1 << extp->FactProtRegSize; | 
					
						
							|  |  |  | 		reg_user_groups = 1; | 
					
						
							|  |  |  | 		reg_user_size = 1 << extp->UserProtRegSize; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		while (len > 0) { | 
					
						
							|  |  |  | 			/* flash geometry fixup */ | 
					
						
							|  |  |  | 			data_offset = reg_prot_offset + 1; | 
					
						
							|  |  |  | 			data_offset *= cfi->interleave * cfi->device_type; | 
					
						
							|  |  |  | 			reg_prot_offset *= cfi->interleave * cfi->device_type; | 
					
						
							|  |  |  | 			reg_fact_size *= cfi->interleave; | 
					
						
							|  |  |  | 			reg_user_size *= cfi->interleave; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (user_regs) { | 
					
						
							|  |  |  | 				groups = reg_user_groups; | 
					
						
							|  |  |  | 				groupsize = reg_user_size; | 
					
						
							|  |  |  | 				/* skip over factory reg area */ | 
					
						
							|  |  |  | 				groupno = reg_fact_groups; | 
					
						
							|  |  |  | 				data_offset += reg_fact_groups * reg_fact_size; | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				groups = reg_fact_groups; | 
					
						
							|  |  |  | 				groupsize = reg_fact_size; | 
					
						
							|  |  |  | 				groupno = 0; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-02-17 20:35:04 +00:00
										 |  |  | 			while (len > 0 && groups > 0) { | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 				if (!action) { | 
					
						
							|  |  |  | 					/*
 | 
					
						
							|  |  |  | 					 * Special case: if action is NULL | 
					
						
							|  |  |  | 					 * we fill buf with otp_info records. | 
					
						
							|  |  |  | 					 */ | 
					
						
							|  |  |  | 					struct otp_info *otpinfo; | 
					
						
							|  |  |  | 					map_word lockword; | 
					
						
							|  |  |  | 					len -= sizeof(struct otp_info); | 
					
						
							|  |  |  | 					if (len <= 0) | 
					
						
							|  |  |  | 						return -ENOSPC; | 
					
						
							|  |  |  | 					ret = do_otp_read(map, chip, | 
					
						
							|  |  |  | 							  reg_prot_offset, | 
					
						
							|  |  |  | 							  (u_char *)&lockword, | 
					
						
							|  |  |  | 							  map_bankwidth(map), | 
					
						
							|  |  |  | 							  0, 0,  0); | 
					
						
							|  |  |  | 					if (ret) | 
					
						
							|  |  |  | 						return ret; | 
					
						
							|  |  |  | 					otpinfo = (struct otp_info *)buf; | 
					
						
							|  |  |  | 					otpinfo->start = from; | 
					
						
							|  |  |  | 					otpinfo->length = groupsize; | 
					
						
							|  |  |  | 					otpinfo->locked = | 
					
						
							|  |  |  | 					   !map_word_bitsset(map, lockword, | 
					
						
							|  |  |  | 							     CMD(1 << groupno)); | 
					
						
							|  |  |  | 					from += groupsize; | 
					
						
							|  |  |  | 					buf += sizeof(*otpinfo); | 
					
						
							|  |  |  | 					*retlen += sizeof(*otpinfo); | 
					
						
							|  |  |  | 				} else if (from >= groupsize) { | 
					
						
							|  |  |  | 					from -= groupsize; | 
					
						
							| 
									
										
										
										
											2005-02-17 20:35:04 +00:00
										 |  |  | 					data_offset += groupsize; | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 				} else { | 
					
						
							|  |  |  | 					int size = groupsize; | 
					
						
							|  |  |  | 					data_offset += from; | 
					
						
							|  |  |  | 					size -= from; | 
					
						
							|  |  |  | 					from = 0; | 
					
						
							|  |  |  | 					if (size > len) | 
					
						
							|  |  |  | 						size = len; | 
					
						
							|  |  |  | 					ret = action(map, chip, data_offset, | 
					
						
							|  |  |  | 						     buf, size, reg_prot_offset, | 
					
						
							|  |  |  | 						     groupno, groupsize); | 
					
						
							|  |  |  | 					if (ret < 0) | 
					
						
							|  |  |  | 						return ret; | 
					
						
							|  |  |  | 					buf += size; | 
					
						
							|  |  |  | 					len -= size; | 
					
						
							|  |  |  | 					*retlen += size; | 
					
						
							| 
									
										
										
										
											2005-02-17 20:35:04 +00:00
										 |  |  | 					data_offset += size; | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 				} | 
					
						
							|  |  |  | 				groupno++; | 
					
						
							|  |  |  | 				groups--; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			/* next OTP region */ | 
					
						
							|  |  |  | 			if (++field == extp->NumProtectionFields) | 
					
						
							|  |  |  | 				break; | 
					
						
							|  |  |  | 			reg_prot_offset = otp->ProtRegAddr; | 
					
						
							|  |  |  | 			reg_fact_groups = otp->FactGroups; | 
					
						
							|  |  |  | 			reg_fact_size = 1 << otp->FactProtRegSize; | 
					
						
							|  |  |  | 			reg_user_groups = otp->UserGroups; | 
					
						
							|  |  |  | 			reg_user_size = 1 << otp->UserProtRegSize; | 
					
						
							|  |  |  | 			otp++; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cfi_intelext_read_fact_prot_reg(struct mtd_info *mtd, loff_t from, | 
					
						
							|  |  |  | 					   size_t len, size_t *retlen, | 
					
						
							|  |  |  | 					    u_char *buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return cfi_intelext_otp_walk(mtd, from, len, retlen, | 
					
						
							|  |  |  | 				     buf, do_otp_read, 0); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cfi_intelext_read_user_prot_reg(struct mtd_info *mtd, loff_t from, | 
					
						
							|  |  |  | 					   size_t len, size_t *retlen, | 
					
						
							|  |  |  | 					    u_char *buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return cfi_intelext_otp_walk(mtd, from, len, retlen, | 
					
						
							|  |  |  | 				     buf, do_otp_read, 1); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cfi_intelext_write_user_prot_reg(struct mtd_info *mtd, loff_t from, | 
					
						
							|  |  |  | 					    size_t len, size_t *retlen, | 
					
						
							|  |  |  | 					     u_char *buf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return cfi_intelext_otp_walk(mtd, from, len, retlen, | 
					
						
							|  |  |  | 				     buf, do_otp_write, 1); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cfi_intelext_lock_user_prot_reg(struct mtd_info *mtd, | 
					
						
							|  |  |  | 					   loff_t from, size_t len) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	size_t retlen; | 
					
						
							|  |  |  | 	return cfi_intelext_otp_walk(mtd, from, len, &retlen, | 
					
						
							|  |  |  | 				     NULL, do_otp_lock, 1); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | static int cfi_intelext_get_fact_prot_info(struct mtd_info *mtd, | 
					
						
							| 
									
										
										
										
											2005-02-08 17:11:19 +00:00
										 |  |  | 					   struct otp_info *buf, size_t len) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	size_t retlen; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = cfi_intelext_otp_walk(mtd, 0, len, &retlen, (u_char *)buf, NULL, 0); | 
					
						
							|  |  |  | 	return ret ? : retlen; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cfi_intelext_get_user_prot_info(struct mtd_info *mtd, | 
					
						
							|  |  |  | 					   struct otp_info *buf, size_t len) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	size_t retlen; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = cfi_intelext_otp_walk(mtd, 0, len, &retlen, (u_char *)buf, NULL, 1); | 
					
						
							|  |  |  | 	return ret ? : retlen; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | static void cfi_intelext_save_locks(struct mtd_info *mtd) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct mtd_erase_region_info *region; | 
					
						
							|  |  |  | 	int block, status, i; | 
					
						
							|  |  |  | 	unsigned long adr; | 
					
						
							|  |  |  | 	size_t len; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i = 0; i < mtd->numeraseregions; i++) { | 
					
						
							|  |  |  | 		region = &mtd->eraseregions[i]; | 
					
						
							|  |  |  | 		if (!region->lockmap) | 
					
						
							|  |  |  | 			continue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for (block = 0; block < region->numblocks; block++){ | 
					
						
							|  |  |  | 			len = region->erasesize; | 
					
						
							|  |  |  | 			adr = region->offset + block * len; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			status = cfi_varsize_frob(mtd, | 
					
						
							| 
									
										
										
										
											2007-05-28 20:11:37 +01:00
										 |  |  | 					do_getlockstatus_oneblock, adr, len, NULL); | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | 			if (status) | 
					
						
							|  |  |  | 				set_bit(block, region->lockmap); | 
					
						
							|  |  |  | 			else | 
					
						
							|  |  |  | 				clear_bit(block, region->lockmap); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | static int cfi_intelext_suspend(struct mtd_info *mtd) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | 	struct cfi_pri_intelext *extp = cfi->cmdset_priv; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	int i; | 
					
						
							|  |  |  | 	struct flchip *chip; | 
					
						
							|  |  |  | 	int ret = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-01-30 10:25:49 -08:00
										 |  |  | 	if ((mtd->flags & MTD_POWERUP_LOCK) | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | 	    && extp && (extp->FeatureSupport & (1 << 5))) | 
					
						
							|  |  |  | 		cfi_intelext_save_locks(mtd); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	for (i=0; !ret && i<cfi->numchips; i++) { | 
					
						
							|  |  |  | 		chip = &cfi->chips[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		switch (chip->state) { | 
					
						
							|  |  |  | 		case FL_READY: | 
					
						
							|  |  |  | 		case FL_STATUS: | 
					
						
							|  |  |  | 		case FL_CFI_QUERY: | 
					
						
							|  |  |  | 		case FL_JEDEC_QUERY: | 
					
						
							|  |  |  | 			if (chip->oldstate == FL_READY) { | 
					
						
							| 
									
										
										
										
											2006-10-19 19:33:19 +03:00
										 |  |  | 				/* place the chip in a known state before suspend */ | 
					
						
							|  |  |  | 				map_write(map, CMD(0xFF), cfi->chips[i].start); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 				chip->oldstate = chip->state; | 
					
						
							|  |  |  | 				chip->state = FL_PM_SUSPENDED; | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 				/* No need to wake_up() on this state change -
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 				 * as the whole point is that nobody can do anything | 
					
						
							|  |  |  | 				 * with the chip now anyway. | 
					
						
							|  |  |  | 				 */ | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				/* There seems to be an operation pending. We must wait for it. */ | 
					
						
							|  |  |  | 				printk(KERN_NOTICE "Flash device refused suspend due to pending operation (oldstate %d)\n", chip->oldstate); | 
					
						
							|  |  |  | 				ret = -EAGAIN; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			/* Should we actually wait? Once upon a time these routines weren't
 | 
					
						
							|  |  |  | 			   allowed to. Or should we return -EAGAIN, because the upper layers | 
					
						
							|  |  |  | 			   ought to have already shut down anything which was using the device | 
					
						
							|  |  |  | 			   anyway? The latter for now. */ | 
					
						
							| 
									
										
										
										
											2012-02-15 11:48:34 +00:00
										 |  |  | 			printk(KERN_NOTICE "Flash device refused suspend due to active operation (state %d)\n", chip->state); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			ret = -EAGAIN; | 
					
						
							|  |  |  | 		case FL_PM_SUSPENDED: | 
					
						
							|  |  |  | 			break; | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Unlock the chips again */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ret) { | 
					
						
							|  |  |  | 		for (i--; i >=0; i--) { | 
					
						
							|  |  |  | 			chip = &cfi->chips[i]; | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 			if (chip->state == FL_PM_SUSPENDED) { | 
					
						
							|  |  |  | 				/* No need to force it into a known state here,
 | 
					
						
							|  |  |  | 				   because we're returning failure, and it didn't | 
					
						
							|  |  |  | 				   get power cycled */ | 
					
						
							|  |  |  | 				chip->state = chip->oldstate; | 
					
						
							|  |  |  | 				chip->oldstate = FL_READY; | 
					
						
							|  |  |  | 				wake_up(&chip->wq); | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 			mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | static void cfi_intelext_restore_locks(struct mtd_info *mtd) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct mtd_erase_region_info *region; | 
					
						
							|  |  |  | 	int block, i; | 
					
						
							|  |  |  | 	unsigned long adr; | 
					
						
							|  |  |  | 	size_t len; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i = 0; i < mtd->numeraseregions; i++) { | 
					
						
							|  |  |  | 		region = &mtd->eraseregions[i]; | 
					
						
							|  |  |  | 		if (!region->lockmap) | 
					
						
							|  |  |  | 			continue; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-03-23 15:02:05 -07:00
										 |  |  | 		for_each_clear_bit(block, region->lockmap, region->numblocks) { | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | 			len = region->erasesize; | 
					
						
							|  |  |  | 			adr = region->offset + block * len; | 
					
						
							| 
									
										
										
										
											2012-03-23 15:02:05 -07:00
										 |  |  | 			cfi_intelext_unlock(mtd, adr, len); | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | static void cfi_intelext_resume(struct mtd_info *mtd) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | 	struct cfi_pri_intelext *extp = cfi->cmdset_priv; | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	int i; | 
					
						
							|  |  |  | 	struct flchip *chip; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i=0; i<cfi->numchips; i++) { | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		chip = &cfi->chips[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 		/* Go to known state. Chip may have been power cycled */ | 
					
						
							|  |  |  | 		if (chip->state == FL_PM_SUSPENDED) { | 
					
						
							|  |  |  | 			map_write(map, CMD(0xFF), cfi->chips[i].start); | 
					
						
							|  |  |  | 			chip->oldstate = chip->state = FL_READY; | 
					
						
							|  |  |  | 			wake_up(&chip->wq); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-01-30 10:25:49 -08:00
										 |  |  | 	if ((mtd->flags & MTD_POWERUP_LOCK) | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | 	    && extp && (extp->FeatureSupport & (1 << 5))) | 
					
						
							|  |  |  | 		cfi_intelext_restore_locks(mtd); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-01 02:59:56 +01:00
										 |  |  | static int cfi_intelext_reset(struct mtd_info *mtd) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							|  |  |  | 	int i, ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i=0; i < cfi->numchips; i++) { | 
					
						
							|  |  |  | 		struct flchip *chip = &cfi->chips[i]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* force the completion of any ongoing operation
 | 
					
						
							| 
									
										
										
										
											2005-11-07 11:15:37 +00:00
										 |  |  | 		   and switch to array mode so any bootloader in | 
					
						
							| 
									
										
										
										
											2005-04-01 02:59:56 +01:00
										 |  |  | 		   flash is accessible for soft reboot. */ | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_lock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2007-10-02 13:56:04 -07:00
										 |  |  | 		ret = get_chip(map, chip, chip->start, FL_SHUTDOWN); | 
					
						
							| 
									
										
										
										
											2005-04-01 02:59:56 +01:00
										 |  |  | 		if (!ret) { | 
					
						
							|  |  |  | 			map_write(map, CMD(0xff), chip->start); | 
					
						
							| 
									
										
										
										
											2007-10-02 13:56:04 -07:00
										 |  |  | 			chip->state = FL_SHUTDOWN; | 
					
						
							| 
									
										
										
										
											2009-10-23 16:02:42 -04:00
										 |  |  | 			put_chip(map, chip, chip->start); | 
					
						
							| 
									
										
										
										
											2005-04-01 02:59:56 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2010-04-18 22:46:44 +02:00
										 |  |  | 		mutex_unlock(&chip->mutex); | 
					
						
							| 
									
										
										
										
											2005-04-01 02:59:56 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int cfi_intelext_reboot(struct notifier_block *nb, unsigned long val, | 
					
						
							|  |  |  | 			       void *v) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct mtd_info *mtd; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mtd = container_of(nb, struct mtd_info, reboot_notifier); | 
					
						
							|  |  |  | 	cfi_intelext_reset(mtd); | 
					
						
							|  |  |  | 	return NOTIFY_DONE; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | static void cfi_intelext_destroy(struct mtd_info *mtd) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct map_info *map = mtd->priv; | 
					
						
							|  |  |  | 	struct cfi_private *cfi = map->fldrv_priv; | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | 	struct mtd_erase_region_info *region; | 
					
						
							|  |  |  | 	int i; | 
					
						
							| 
									
										
										
										
											2005-04-01 02:59:56 +01:00
										 |  |  | 	cfi_intelext_reset(mtd); | 
					
						
							|  |  |  | 	unregister_reboot_notifier(&mtd->reboot_notifier); | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	kfree(cfi->cmdset_priv); | 
					
						
							|  |  |  | 	kfree(cfi->cfiq); | 
					
						
							|  |  |  | 	kfree(cfi->chips[0].priv); | 
					
						
							|  |  |  | 	kfree(cfi); | 
					
						
							| 
									
										
										
										
											2007-03-26 21:45:43 -08:00
										 |  |  | 	for (i = 0; i < mtd->numeraseregions; i++) { | 
					
						
							|  |  |  | 		region = &mtd->eraseregions[i]; | 
					
						
							|  |  |  | 		if (region->lockmap) | 
					
						
							|  |  |  | 			kfree(region->lockmap); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2005-04-16 15:20:36 -07:00
										 |  |  | 	kfree(mtd->eraseregions); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MODULE_LICENSE("GPL"); | 
					
						
							|  |  |  | MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org> et al."); | 
					
						
							|  |  |  | MODULE_DESCRIPTION("MTD chip driver for Intel/Sharp flash chips"); | 
					
						
							| 
									
										
										
										
											2006-05-08 22:35:05 +01:00
										 |  |  | MODULE_ALIAS("cfi_cmdset_0003"); | 
					
						
							|  |  |  | MODULE_ALIAS("cfi_cmdset_0200"); |