X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=drivers%2Fmtd%2Fchips%2Fcfi_cmdset_0001.c;h=f69184a92eb2389d7370d726a346602c117d51dd;hb=97bf2856c6014879bd04983a3e9dfcdac1e7fe85;hp=2b220085e00de43a8e2deaacdc479107205093b2;hpb=5273a3df6485dc2ad6aa7ddd441b9a21970f003b;p=linux-2.6.git diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c index 2b220085e..f69184a92 100644 --- a/drivers/mtd/chips/cfi_cmdset_0001.c +++ b/drivers/mtd/chips/cfi_cmdset_0001.c @@ -4,9 +4,9 @@ * * (C) 2000 Red Hat. GPL'd * - * $Id: cfi_cmdset_0001.c,v 1.126 2003/06/23 07:45:48 dwmw2 Exp $ + * $Id: cfi_cmdset_0001.c,v 1.186 2005/11/23 22:07:52 nico Exp $ + * * - * * 10/10/2000 Nicolas Pitre * - completely revamped method functions so they are aware and * independent of the flash geometry (buswidth, interleave, etc.) @@ -29,37 +29,64 @@ #include #include #include +#include +#include #include #include #include #include +/* #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 +#define MANUFACTURER_INTEL 0x0089 +#define I82802AB 0x00ad +#define I82802AC 0x00ac +#define MANUFACTURER_ST 0x0020 +#define M50LPW080 0x002F + static int cfi_intelext_read (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_read_fact_prot_reg (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 *); +static int cfi_intelext_writev(struct mtd_info *, const struct kvec *, unsigned long, loff_t, size_t *); static int cfi_intelext_erase_varsize(struct mtd_info *, struct erase_info *); static void cfi_intelext_sync (struct mtd_info *); static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, size_t len); static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, size_t len); +#ifdef CONFIG_MTD_OTP +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); +#endif static int cfi_intelext_suspend (struct mtd_info *); static void cfi_intelext_resume (struct mtd_info *); +static int cfi_intelext_reboot (struct notifier_block *, unsigned long, void *); 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 map_info *); +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, size_t *retlen, u_char **mtdbuf); static void cfi_intelext_unpoint (struct mtd_info *mtd, u_char *addr, loff_t from, size_t len); +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 *********** @@ -79,163 +106,334 @@ static struct mtd_chip_driver cfi_intelext_chipdrv = { static void cfi_tell_features(struct cfi_pri_intelext *extp) { int i; - 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"); - for (i=9; i<32; i++) { - if (extp->FeatureSupport & (1<MajorVersion, extp->MinorVersion); + 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"); + printk(" - Extended Flash Array: %s\n", extp->FeatureSupport&1024?"supported":"unsupported"); + for (i=11; i<32; i++) { + if (extp->FeatureSupport & (1<SuspendCmdSupport); printk(" - Program after Erase Suspend: %s\n", extp->SuspendCmdSupport&1?"supported":"unsupported"); for (i=1; i<8; i++) { if (extp->SuspendCmdSupport & (1<BlkStatusRegMask); printk(" - Lock Bit Active: %s\n", extp->BlkStatusRegMask&1?"yes":"no"); - printk(" - Valid Bit Active: %s\n", extp->BlkStatusRegMask&2?"yes":"no"); - for (i=2; i<16; i++) { + printk(" - Lock-Down Bit Active: %s\n", extp->BlkStatusRegMask&2?"yes":"no"); + for (i=2; i<3; i++) { if (extp->BlkStatusRegMask & (1<VccOptimal >> 8, extp->VccOptimal & 0xf); + 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<VccOptimal >> 4, extp->VccOptimal & 0xf); if (extp->VppOptimal) - printk(" Vpp Programming Supply Optimum Program/Erase Voltage: %d.%d V\n", - extp->VppOptimal >> 8, extp->VppOptimal & 0xf); + printk(" Vpp Programming Supply Optimum Program/Erase Voltage: %d.%d V\n", + extp->VppOptimal >> 4, extp->VppOptimal & 0xf); } #endif -/* This routine is made available to other mtd code via - * inter_module_register. It must only be accessed through - * inter_module_get which will bump the use count of this module. The - * addresses passed back in cfi are valid as long as the use count of - * this module is non-zero, i.e. between inter_module_get and - * inter_module_put. Keith Owens 29 Oct 2000. - */ +#ifdef CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE +/* Some Intel Strata Flash prior to FPO revision C has bugs in this area */ +static void fixup_intel_strataflash(struct mtd_info *mtd, void* param) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_amdstd *extp = cfi->cmdset_priv; + + printk(KERN_WARNING "cfi_cmdset_0001: Suspend " + "erase on write disabled.\n"); + extp->SuspendCmdSupport &= ~1; +} +#endif + +#ifdef CMDSET0001_DISABLE_WRITE_SUSPEND +static void fixup_no_write_suspend(struct mtd_info *mtd, void* param) +{ + 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 + +static void fixup_st_m28w320ct(struct mtd_info *mtd, void* param) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + cfi->cfiq->BufWriteTimeoutTyp = 0; /* Not supported */ + cfi->cfiq->BufWriteTimeoutMax = 0; /* Not supported */ +} + +static void fixup_st_m28w320cb(struct mtd_info *mtd, void* param) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + /* Note this is done after the region info is endian swapped */ + cfi->cfiq->EraseRegionInfo[1] = + (cfi->cfiq->EraseRegionInfo[1] & 0xffff0000) | 0x3e; +}; + +static void fixup_use_point(struct mtd_info *mtd, void *param) +{ + struct map_info *map = mtd->priv; + if (!mtd->point && map_is_linear(map)) { + mtd->point = cfi_intelext_point; + mtd->unpoint = cfi_intelext_unpoint; + } +} + +static void fixup_use_write_buffers(struct mtd_info *mtd, void *param) +{ + 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" ); + mtd->write = cfi_intelext_write_buffers; + mtd->writev = cfi_intelext_writev; + } +} + +static struct cfi_fixup cfi_fixup_table[] = { +#ifdef CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE + { CFI_MFR_ANY, CFI_ID_ANY, fixup_intel_strataflash, NULL }, +#endif +#ifdef CMDSET0001_DISABLE_WRITE_SUSPEND + { CFI_MFR_ANY, CFI_ID_ANY, fixup_no_write_suspend, NULL }, +#endif +#if !FORCE_WORD_WRITE + { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers, NULL }, +#endif + { CFI_MFR_ST, 0x00ba, /* M28W320CT */ fixup_st_m28w320ct, NULL }, + { CFI_MFR_ST, 0x00bb, /* M28W320CB */ fixup_st_m28w320cb, NULL }, + { 0, 0, NULL, NULL } +}; + +static struct cfi_fixup jedec_fixup_table[] = { + { MANUFACTURER_INTEL, I82802AB, fixup_use_fwh_lock, NULL, }, + { MANUFACTURER_INTEL, I82802AC, fixup_use_fwh_lock, NULL, }, + { MANUFACTURER_ST, M50LPW080, fixup_use_fwh_lock, NULL, }, + { 0, 0, NULL, NULL } +}; +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. + */ + { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_point, NULL }, + { 0, 0, NULL, NULL } +}; + +static inline struct cfi_pri_intelext * +read_pri_intelext(struct map_info *map, __u16 adr) +{ + struct cfi_pri_intelext *extp; + 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; + + if (extp->MajorVersion != '1' || + (extp->MinorVersion < '0' || extp->MinorVersion > '4')) { + printk(KERN_ERR " Unknown Intel/Sharp Extended Query " + "version %c.%c.\n", extp->MajorVersion, + extp->MinorVersion); + kfree(extp); + return NULL; + } + + /* 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); + + if (extp->MajorVersion == '1' && extp->MinorVersion >= '3') { + unsigned int extra_size = 0; + int nb_parts, i; + + /* Protection Register info */ + extra_size += (extp->NumProtectionFields - 1) * + sizeof(struct cfi_intelext_otpinfo); + + /* Burst Read info */ + extra_size += 2; + if (extp_size < sizeof(*extp) + extra_size) + goto need_more; + extra_size += extp->extra[extra_size-1]; + + /* Number of hardware-partitions */ + extra_size += 1; + if (extp_size < sizeof(*extp) + extra_size) + goto need_more; + nb_parts = extp->extra[extra_size - 1]; + + /* skip the sizeof(partregion) field in CFI 1.4 */ + if (extp->MinorVersion >= '4') + extra_size += 2; + + 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); + } + + if (extp->MinorVersion >= '4') + extra_size += sizeof(struct cfi_intelext_programming_regioninfo); + + 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", + __FUNCTION__); + return NULL; + } + goto again; + } + } + + 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; - __u32 base = cfi->chips[0].start; + + mtd = kzalloc(sizeof(*mtd), GFP_KERNEL); + 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 */ + 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->suspend = cfi_intelext_suspend; + mtd->resume = cfi_intelext_resume; + mtd->flags = MTD_CAP_NORFLASH; + mtd->name = map->name; + mtd->writesize = 1; + + mtd->reboot_notifier.notifier_call = cfi_intelext_reboot; if (cfi->cfi_mode == CFI_MODE_CFI) { - /* + /* * 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; - int ofs_factor = cfi->interleave * cfi->device_type; - //printk(" Intel/Sharp Extended Query Table at 0x%4.4X\n", adr); - if (!adr) - return NULL; - - /* Switch it into Query Mode */ - cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); - - extp = kmalloc(sizeof(*extp), GFP_KERNEL); + extp = read_pri_intelext(map, adr); if (!extp) { - printk(KERN_ERR "Failed to allocate memory\n"); - return NULL; - } - - /* Read in the Extended Query Table */ - for (i=0; iMajorVersion != '1' || - (extp->MinorVersion < '0' || extp->MinorVersion > '3')) { - printk(KERN_WARNING " Unknown IntelExt Extended Query " - "version %c.%c.\n", extp->MajorVersion, - extp->MinorVersion); - kfree(extp); + kfree(mtd); return NULL; } - - /* 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); - + + /* Install our own private info structure */ + cfi->cmdset_priv = extp; + + cfi_fixup(mtd, cfi_fixup_table); + #ifdef DEBUG_CFI_FEATURES /* Tell the user about it in lots of lovely detail */ cfi_tell_features(extp); -#endif +#endif if(extp->SuspendCmdSupport & 1) { -//#define CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE -#ifdef CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE -/* Some Intel Strata Flash prior to FPO revision C has bugs in this area */ - printk(KERN_WARNING "cfi_cmdset_0001: Suspend " - "erase on write disabled.\n"); - extp->SuspendCmdSupport &= ~1; -#else printk(KERN_NOTICE "cfi_cmdset_0001: Erase suspend on write enabled\n"); -#endif } - /* Install our own private info structure */ - cfi->cmdset_priv = extp; } + 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++) { cfi->chips[i].word_write_time = 1<cfiq->WordWriteTimeoutTyp; cfi->chips[i].buffer_write_time = 1<cfiq->BufWriteTimeoutTyp; - cfi->chips[i].erase_time = 1<cfiq->BlockEraseTimeoutTyp; + cfi->chips[i].erase_time = 1000<cfiq->BlockEraseTimeoutTyp; cfi->chips[i].ref_point_counter = 0; - } + init_waitqueue_head(&(cfi->chips[i].wq)); + } map->fldrv = &cfi_intelext_chipdrv; - - /* Make sure it's in read mode */ - cfi_send_gen_cmd(0xff, 0x55, base, map, cfi, cfi->device_type, NULL); - return cfi_intelext_setup(map); + + return cfi_intelext_setup(mtd); } +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); -static struct mtd_info *cfi_intelext_setup(struct map_info *map) +static struct mtd_info *cfi_intelext_setup(struct mtd_info *mtd) { + struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; - struct mtd_info *mtd; unsigned long offset = 0; int i,j; unsigned long devsize = (1<cfiq->DevSize) * cfi->interleave; - mtd = kmalloc(sizeof(*mtd), GFP_KERNEL); //printk(KERN_DEBUG "number of CFI chips: %d\n", cfi->numchips); - if (!mtd) { - printk(KERN_ERR "Failed to allocate memory for MTD device\n"); - goto setup_err; - } - - memset(mtd, 0, sizeof(*mtd)); - mtd->priv = map; - mtd->type = MTD_NORFLASH; mtd->size = devsize * cfi->numchips; mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips; - mtd->eraseregions = kmalloc(sizeof(struct mtd_erase_region_info) + mtd->eraseregions = kmalloc(sizeof(struct mtd_erase_region_info) * mtd->numeraseregions, GFP_KERNEL); - if (!mtd->eraseregions) { + if (!mtd->eraseregions) { printk(KERN_ERR "Failed to allocate memory for MTD erase region info\n"); goto setup_err; } - + for (i=0; icfiq->NumEraseRegions; i++) { unsigned long ernum, ersize; ersize = ((cfi->cfiq->EraseRegionInfo[i] >> 8) & ~0xff) * cfi->interleave; @@ -259,52 +457,161 @@ static struct mtd_info *cfi_intelext_setup(struct map_info *map) } for (i=0; inumeraseregions;i++){ - printk(KERN_DEBUG "%d: offset=0x%x,size=0x%x,blocks=%d\n", + printk(KERN_DEBUG "erase region %d: offset=0x%x,size=0x%x,blocks=%d\n", i,mtd->eraseregions[i].offset, mtd->eraseregions[i].erasesize, mtd->eraseregions[i].numblocks); } - /* Also select the correct geometry setup too */ - mtd->erase = cfi_intelext_erase_varsize; - mtd->read = cfi_intelext_read; +#ifdef CONFIG_MTD_OTP + 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; +#endif - if (map_is_linear(map)) { - mtd->point = cfi_intelext_point; - mtd->unpoint = cfi_intelext_unpoint; - } + /* 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; - if ( cfi->cfiq->BufWriteTimeoutTyp && !FORCE_WORD_WRITE) { - printk(KERN_INFO "Using buffer write method\n" ); - mtd->write = cfi_intelext_write_buffers; - } else { - printk(KERN_INFO "Using word write method\n" ); - mtd->write = cfi_intelext_write_words; - } - mtd->read_user_prot_reg = cfi_intelext_read_user_prot_reg; - mtd->read_fact_prot_reg = cfi_intelext_read_fact_prot_reg; - mtd->sync = cfi_intelext_sync; - mtd->lock = cfi_intelext_lock; - mtd->unlock = cfi_intelext_unlock; - mtd->suspend = cfi_intelext_suspend; - mtd->resume = cfi_intelext_resume; - mtd->flags = MTD_CAP_NORFLASH; - map->fldrv = &cfi_intelext_chipdrv; - mtd->name = map->name; __module_get(THIS_MODULE); + register_reboot_notifier(&mtd->reboot_notifier); return mtd; setup_err: if(mtd) { - if(mtd->eraseregions) - kfree(mtd->eraseregions); + kfree(mtd->eraseregions); kfree(mtd); } kfree(cfi->cmdset_priv); - kfree(cfi->cfiq); 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; + + /* + * Probing of multi-partition flash ships. + * + * 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 + */ + if (extp && extp->MajorVersion == '1' && extp->MinorVersion >= '3' + && 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 */ + offs = (extp->NumProtectionFields - 1) * + sizeof(struct cfi_intelext_otpinfo); + + /* Burst Read info */ + offs += extp->extra[offs+1]+2; + + /* Number of partition regions */ + numregions = extp->extra[offs]; + offs += 1; + + /* skip the sizeof(partregion) field in CFI 1.4 */ + if (extp->MinorVersion >= '4') + offs += 2; + + /* 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); + } + + /* Programming Region info */ + if (extp->MinorVersion >= '4') { + struct cfi_intelext_programming_regioninfo *prinfo; + prinfo = (struct cfi_intelext_programming_regioninfo *)&extp->extra[offs]; + mtd->writesize = cfi->interleave << prinfo->ProgRegShift; + MTD_PROGREGION_CTRLMODE_VALID(mtd) = cfi->interleave * prinfo->ControlValid; + MTD_PROGREGION_CTRLMODE_INVALID(mtd) = cfi->interleave * prinfo->ControlInvalid; + mtd->flags &= ~MTD_BIT_WRITEABLE; + printk(KERN_DEBUG "%s: program region size/ctrl_valid/ctrl_inval = %d/%d/%d\n", + map->name, mtd->writesize, + MTD_PROGREGION_CTRLMODE_VALID(mtd), + MTD_PROGREGION_CTRLMODE_INVALID(mtd)); + } + + /* + * 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", + __FUNCTION__, numparts); + 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; + spin_lock_init(&shared[i].lock); + 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); + spin_lock_init(&chip->_spinlock); + chip->mutex = &chip->_spinlock; + 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 *********** */ @@ -313,25 +620,85 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr { DECLARE_WAITQUEUE(wait, current); struct cfi_private *cfi = map->fldrv_priv; - cfi_word status, status_OK = CMD(0x80); + map_word status, status_OK = CMD(0x80), status_PWS = CMD(0x01); unsigned long timeo; - struct cfi_pri_intelext *cfip = (struct cfi_pri_intelext *)cfi->cmdset_priv; + struct cfi_pri_intelext *cfip = cfi->cmdset_priv; resettime: timeo = jiffies + HZ; retry: + if (chip->priv && (mode == FL_WRITING || mode == FL_ERASING || mode == FL_OTP_WRITE)) { + /* + * OK. We have possibility for contension 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. + * + * - contension 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; + spin_lock(&shared->lock); + 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. + */ + int ret = spin_trylock(contender->mutex); + spin_unlock(&shared->lock); + if (!ret) + goto retry; + spin_unlock(chip->mutex); + ret = get_chip(map, contender, contender->start, mode); + spin_lock(chip->mutex); + if (ret) { + spin_unlock(contender->mutex); + return ret; + } + timeo = jiffies + HZ; + spin_lock(&shared->lock); + spin_unlock(contender->mutex); + } + + /* We now own it */ + shared->writing = chip; + if (mode == FL_ERASING) + shared->erasing = chip; + spin_unlock(&shared->lock); + } + switch (chip->state) { case FL_STATUS: for (;;) { - status = cfi_read(map, adr); - if ((status & status_OK) == status_OK) + 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; if (time_after(jiffies, timeo)) { - printk(KERN_ERR "Waiting for chip to be ready timed out. Status %llx\n", - (long long)status); - spin_unlock(chip->mutex); + printk(KERN_ERR "%s: Waiting for chip to be ready timed out. Status %lx\n", + map->name, status.x[0]); return -EIO; } spin_unlock(chip->mutex); @@ -340,45 +707,46 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr /* Someone else might have been playing with it. */ goto retry; } - + case FL_READY: case FL_CFI_QUERY: case FL_JEDEC_QUERY: return 0; case FL_ERASING: - if (!(cfip->FeatureSupport & 2) || + if (!cfip || + !(cfip->FeatureSupport & 2) || !(mode == FL_READY || mode == FL_POINT || (mode == FL_WRITING && (cfip->SuspendCmdSupport & 1)))) goto sleep; /* Erase suspend */ - cfi_write(map, CMD(0xB0), adr); + 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 */ - cfi_write(map, CMD(0x70), adr); + map_write(map, CMD(0x70), adr); chip->oldstate = FL_ERASING; chip->state = FL_ERASE_SUSPENDING; chip->erase_suspended = 1; for (;;) { - status = cfi_read(map, adr); - if ((status & status_OK) == status_OK) + status = map_read(map, adr); + if (map_word_andequal(map, status, status_OK, status_OK)) break; if (time_after(jiffies, timeo)) { /* Urgh. Resume and pretend we weren't here. */ - cfi_write(map, CMD(0xd0), adr); + map_write(map, CMD(0xd0), adr); /* Make sure we're in 'read status' mode if it had finished */ - cfi_write(map, CMD(0x70), adr); + map_write(map, CMD(0x70), adr); chip->state = FL_ERASING; chip->oldstate = FL_READY; - printk(KERN_ERR "Chip not ready after erase " - "suspended: status = 0x%x\n", status); + printk(KERN_ERR "%s: Chip not ready after erase " + "suspended: status = 0x%lx\n", map->name, status.x[0]); return -EIO; } @@ -391,6 +759,14 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr 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; + case FL_POINT: /* Only if there's no operation suspended... */ if (mode == FL_READY && chip->oldstate == FL_READY) @@ -412,87 +788,378 @@ static void put_chip(struct map_info *map, struct flchip *chip, unsigned long ad { struct cfi_private *cfi = map->fldrv_priv; + if (chip->priv) { + struct flchip_shared *shared = chip->priv; + spin_lock(&shared->lock); + 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; + spin_lock(loaner->mutex); + spin_unlock(&shared->lock); + spin_unlock(chip->mutex); + put_chip(map, loaner, loaner->start); + spin_lock(chip->mutex); + spin_unlock(loaner->mutex); + 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. + */ + spin_unlock(&shared->lock); + wake_up(&chip->wq); + return; + } + spin_unlock(&shared->lock); + } + switch(chip->oldstate) { case FL_ERASING: chip->state = chip->oldstate; - /* What if one interleaved chip has finished and the + /* What if one interleaved chip has finished and the other hasn't? The old code would leave the finished - one in READY mode. That's bad, and caused -EROFS + one in READY mode. That's bad, and caused -EROFS errors to be returned from do_erase_oneblock because that's the only bit it checked for at the time. - As the state machine appears to explicitly allow + As the state machine appears to explicitly allow sending the 0x70 (Read Status) command to an erasing - chip and expecting it to be ignored, that's what we + chip and expecting it to be ignored, that's what we do. */ - cfi_write(map, CMD(0xd0), adr); - cfi_write(map, CMD(0x70), adr); + 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: /* We should really make set_vpp() count, rather than doing this */ DISABLE_VPP(map); break; default: - printk(KERN_ERR "put_chip() called with oldstate %d!!\n", chip->oldstate); + printk(KERN_ERR "%s: put_chip() called with oldstate %d!!\n", map->name, chip->oldstate); } wake_up(&chip->wq); } -static int do_point_onechip (struct map_info *map, struct flchip *chip, loff_t adr, size_t len) +#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) { - unsigned long cmd_addr; struct cfi_private *cfi = map->fldrv_priv; - int ret = 0; + if (chip->state != FL_POINT && chip->state != FL_READY) { + map_write(map, CMD(0xff), adr); + chip->state = FL_READY; + } + (void) map_read(map, adr); + xip_iprefetch(); + local_irq_enable(); +} - adr += chip->start; +/* + * When a delay is required for the flash operation to complete, the + * 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. + * + * Warning: this function _will_ fool interrupt latency tracing tools. + */ - /* Ensure cmd read/writes are aligned. */ - cmd_addr = adr & ~(CFIDEV_BUSWIDTH-1); +static int __xipram xip_wait_for_operation( + struct map_info *map, struct flchip *chip, + unsigned long adr, unsigned int chip_op_time ) +{ + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *cfip = cfi->cmdset_priv; + map_word status, OK = CMD(0x80); + unsigned long usec, suspended, start, done; + flstate_t oldstate, newstate; + + start = xip_currtime(); + usec = chip_op_time * 8; + if (usec == 0) + usec = 500000; + done = 0; + + 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! + */ + usec -= done; + 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. + */ + return -EIO; + } + 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); + asm volatile (".rep 8; nop; .endr"); + local_irq_enable(); + spin_unlock(chip->mutex); + asm volatile (".rep 8; nop; .endr"); + 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. + */ + spin_lock(chip->mutex); + while (chip->state != newstate) { + DECLARE_WAITQUEUE(wait, current); + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + spin_unlock(chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + spin_lock(chip->mutex); + } + /* 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); + done = xip_elapsed_since(start); + } while (!map_word_andequal(map, status, OK, OK) + && done < usec); - spin_lock(chip->mutex); + return (done >= usec) ? -ETIME : 0; +} - ret = get_chip(map, chip, cmd_addr, FL_POINT); +/* + * 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 + * a XIP setup so do it before the actual flash operation in this case + * and stub it out from INVAL_CACHE_AND_WAIT. + */ +#define XIP_INVAL_CACHED_RANGE(map, from, size) \ + INVALIDATE_CACHED_RANGE(map, from, size) - if (!ret) { - if (chip->state != FL_POINT && chip->state != FL_READY) - cfi_write(map, CMD(0xff), cmd_addr); +#define INVAL_CACHE_AND_WAIT(map, chip, cmd_adr, inval_adr, inval_len, usec) \ + xip_wait_for_operation(map, chip, cmd_adr, usec) - chip->state = FL_POINT; - chip->ref_point_counter++; - } - spin_unlock(chip->mutex); +#else - return ret; -} +#define xip_disable(map, chip, adr) +#define xip_enable(map, chip, adr) +#define XIP_INVAL_CACHED_RANGE(x...) +#define INVAL_CACHE_AND_WAIT inval_cache_and_wait_for_operation -static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf) +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, + unsigned int chip_op_time) { - struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; - unsigned long ofs; - int chipnum; - int ret = 0; - - if (from + len > mtd->size) - return -EINVAL; - - *mtdbuf = (void *)map->virt + from; - if(*mtdbuf == NULL) - return -EINVAL; /* can not point this region */ - *retlen = 0; - - /* Now lock the chip(s) to POINT state */ + map_word status, status_OK = CMD(0x80); + int chip_state = chip->state; + unsigned int timeo, sleep_time; - /* ofs: offset within the first chip that the first read should start */ - chipnum = (from >> cfi->chipshift); - ofs = from - (chipnum << cfi->chipshift); + spin_unlock(chip->mutex); + if (inval_len) + INVALIDATE_CACHED_RANGE(map, inval_adr, inval_len); + spin_lock(chip->mutex); - while (len) { - unsigned long thislen; + /* set our timeout to 8 times the expected delay */ + timeo = chip_op_time * 8; + if (!timeo) + timeo = 500000; + sleep_time = chip_op_time / 2; - if (chipnum >= cfi->numchips) + for (;;) { + status = map_read(map, cmd_adr); + if (map_word_andequal(map, status, status_OK, status_OK)) + break; + + if (!timeo) { + map_write(map, CMD(0x70), cmd_adr); + chip->state = FL_STATUS; + return -ETIME; + } + + /* OK Still waiting. Drop the lock, wait a while and retry. */ + spin_unlock(chip->mutex); + 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--; + } + spin_lock(chip->mutex); + + while (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); + spin_unlock(chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + spin_lock(chip->mutex); + } + } + + /* Done and happy. */ + chip->state = FL_STATUS; + return 0; +} + +#endif + +#define WAIT_TIMEOUT(map, chip, adr, udelay) \ + INVAL_CACHE_AND_WAIT(map, chip, adr, 0, 0, udelay); + + +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; + + /* Ensure cmd read/writes are aligned. */ + cmd_addr = adr & ~(map_bankwidth(map)-1); + + spin_lock(chip->mutex); + + 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++; + } + spin_unlock(chip->mutex); + + return ret; +} + +static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long ofs; + int chipnum; + int ret = 0; + + if (!map->virt || (from + len > mtd->size)) + return -EINVAL; + + *mtdbuf = (void *)map->virt + from; + *retlen = 0; + + /* 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); + + while (len) { + unsigned long thislen; + + if (chipnum >= cfi->numchips) break; if ((len + ofs -1) >> cfi->chipshift) @@ -506,7 +1173,7 @@ static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len, si *retlen += thislen; len -= thislen; - + ofs = 0; chipnum++; } @@ -545,7 +1212,7 @@ static void cfi_intelext_unpoint (struct mtd_info *mtd, u_char *addr, loff_t fro if(chip->ref_point_counter == 0) chip->state = FL_READY; } else - printk(KERN_ERR "Warning: unpoint called on non pointed region\n"); /* Should this give an error? */ + printk(KERN_ERR "%s: Warning: unpoint called on non pointed region\n", map->name); /* Should this give an error? */ put_chip(map, chip, chip->start); spin_unlock(chip->mutex); @@ -564,8 +1231,8 @@ static inline int do_read_onechip(struct map_info *map, struct flchip *chip, lof adr += chip->start; - /* Ensure cmd read/writes are aligned. */ - cmd_addr = adr & ~(CFIDEV_BUSWIDTH-1); + /* Ensure cmd read/writes are aligned. */ + cmd_addr = adr & ~(map_bankwidth(map)-1); spin_lock(chip->mutex); ret = get_chip(map, chip, cmd_addr, FL_READY); @@ -575,7 +1242,7 @@ static inline int do_read_onechip(struct map_info *map, struct flchip *chip, lof } if (chip->state != FL_POINT && chip->state != FL_READY) { - cfi_write(map, CMD(0xff), cmd_addr); + map_write(map, CMD(0xff), cmd_addr); chip->state = FL_READY; } @@ -620,190 +1287,82 @@ static int cfi_intelext_read (struct mtd_info *mtd, loff_t from, size_t len, siz *retlen += thislen; len -= thislen; buf += thislen; - + ofs = 0; chipnum++; } return ret; } -static int cfi_intelext_read_prot_reg (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf, int base_offst, int reg_sz) -{ - struct map_info *map = mtd->priv; - struct cfi_private *cfi = map->fldrv_priv; - struct cfi_pri_intelext *extp = cfi->cmdset_priv; - struct flchip *chip; - int ofs_factor = cfi->interleave * cfi->device_type; - int count = len; - int chip_num, offst; - int ret; - - chip_num = ((unsigned int)from/reg_sz); - offst = from - (reg_sz*chip_num)+base_offst; - - while (count) { - /* Calculate which chip & protection register offset we need */ - - if (chip_num >= cfi->numchips) - goto out; - - chip = &cfi->chips[chip_num]; - - spin_lock(chip->mutex); - ret = get_chip(map, chip, chip->start, FL_JEDEC_QUERY); - if (ret) { - spin_unlock(chip->mutex); - return (len-count)?:ret; - } - - if (chip->state != FL_JEDEC_QUERY) { - cfi_write(map, CMD(0x90), chip->start); - chip->state = FL_JEDEC_QUERY; - } - - while (count && ((offst-base_offst) < reg_sz)) { - *buf = map_read8(map,(chip->start+((extp->ProtRegAddr+1)*ofs_factor)+offst)); - buf++; - offst++; - count--; - } - - put_chip(map, chip, chip->start); - spin_unlock(chip->mutex); - - /* Move on to the next chip */ - chip_num++; - offst = base_offst; - } - - out: - return len-count; -} - -static int cfi_intelext_read_user_prot_reg (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; - struct cfi_pri_intelext *extp=cfi->cmdset_priv; - int base_offst,reg_sz; - - /* Check that we actually have some protection registers */ - if(!(extp->FeatureSupport&64)){ - printk(KERN_WARNING "%s: This flash device has no protection data to read!\n",map->name); - return 0; - } - - base_offst=(1<FactProtRegSize); - reg_sz=(1<UserProtRegSize); - - return cfi_intelext_read_prot_reg(mtd, from, len, retlen, buf, base_offst, reg_sz); -} - -static int cfi_intelext_read_fact_prot_reg (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; - struct cfi_pri_intelext *extp=cfi->cmdset_priv; - int base_offst,reg_sz; - - /* Check that we actually have some protection registers */ - if(!(extp->FeatureSupport&64)){ - printk(KERN_WARNING "%s: This flash device has no protection data to read!\n",map->name); - return 0; - } - - base_offst=0; - reg_sz=(1<FactProtRegSize); - - return cfi_intelext_read_prot_reg(mtd, from, len, retlen, buf, base_offst, reg_sz); -} - - -static int do_write_oneword(struct map_info *map, struct flchip *chip, unsigned long adr, cfi_word datum) +static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip, + unsigned long adr, map_word datum, int mode) { struct cfi_private *cfi = map->fldrv_priv; - cfi_word status, status_OK; - unsigned long timeo; - int z, ret=0; + map_word status, write_cmd; + int ret=0; adr += chip->start; - /* Let's determine this according to the interleave only once */ - status_OK = CMD(0x80); + switch (mode) { + case FL_WRITING: + write_cmd = (cfi->cfiq->P_ID != 0x0200) ? CMD(0x40) : CMD(0x41); + break; + case FL_OTP_WRITE: + write_cmd = CMD(0xc0); + break; + default: + return -EINVAL; + } spin_lock(chip->mutex); - ret = get_chip(map, chip, adr, FL_WRITING); + ret = get_chip(map, chip, adr, mode); if (ret) { spin_unlock(chip->mutex); return ret; } + XIP_INVAL_CACHED_RANGE(map, adr, map_bankwidth(map)); ENABLE_VPP(map); - cfi_write(map, CMD(0x40), adr); - cfi_write(map, datum, adr); - chip->state = FL_WRITING; - - spin_unlock(chip->mutex); - cfi_udelay(chip->word_write_time); - spin_lock(chip->mutex); + xip_disable(map, chip, adr); + map_write(map, write_cmd, adr); + map_write(map, datum, adr); + chip->state = mode; + + ret = INVAL_CACHE_AND_WAIT(map, chip, adr, + adr, map_bankwidth(map), + chip->word_write_time); + if (ret) { + xip_enable(map, chip, adr); + printk(KERN_ERR "%s: word write error (status timeout)\n", map->name); + goto out; + } - timeo = jiffies + (HZ/2); - z = 0; - for (;;) { - if (chip->state != FL_WRITING) { - /* Someone's suspended the write. Sleep */ - DECLARE_WAITQUEUE(wait, current); + /* check for errors */ + status = map_read(map, adr); + if (map_word_bitsset(map, status, CMD(0x1a))) { + unsigned long chipstatus = MERGESTATUS(status); - set_current_state(TASK_UNINTERRUPTIBLE); - add_wait_queue(&chip->wq, &wait); - spin_unlock(chip->mutex); - schedule(); - remove_wait_queue(&chip->wq, &wait); - timeo = jiffies + (HZ / 2); /* FIXME */ - spin_lock(chip->mutex); - continue; - } + /* reset status */ + map_write(map, CMD(0x50), adr); + map_write(map, CMD(0x70), adr); + xip_enable(map, chip, adr); - status = cfi_read(map, adr); - if ((status & status_OK) == status_OK) - break; - - /* OK Still waiting */ - if (time_after(jiffies, timeo)) { - chip->state = FL_STATUS; - printk(KERN_ERR "waiting for chip to be ready timed out in word write\n"); + if (chipstatus & 0x02) { + ret = -EROFS; + } else if (chipstatus & 0x08) { + printk(KERN_ERR "%s: word write error (bad VPP)\n", map->name); ret = -EIO; - goto out; + } else { + printk(KERN_ERR "%s: word write error (status 0x%lx)\n", map->name, chipstatus); + ret = -EINVAL; } - /* Latency issues. Drop the lock, wait a while and retry */ - spin_unlock(chip->mutex); - z++; - cfi_udelay(1); - spin_lock(chip->mutex); - } - if (!z) { - chip->word_write_time--; - if (!chip->word_write_time) - chip->word_write_time++; + goto out; } - if (z > 1) - chip->word_write_time++; - /* Done and happy. */ - chip->state = FL_STATUS; - /* check for lock bit */ - if (status & CMD(0x02)) { - /* clear status */ - cfi_write(map, CMD(0x50), adr); - /* put back into read status register mode */ - cfi_write(map, CMD(0x70), adr); - ret = -EROFS; - } - out: - put_chip(map, chip, adr); + xip_enable(map, chip, adr); + out: put_chip(map, chip, adr); spin_unlock(chip->mutex); - return ret; } @@ -824,126 +1383,90 @@ static int cfi_intelext_write_words (struct mtd_info *mtd, loff_t to , size_t le ofs = to - (chipnum << cfi->chipshift); /* If it's not bus-aligned, do the first byte write */ - if (ofs & (CFIDEV_BUSWIDTH-1)) { - unsigned long bus_ofs = ofs & ~(CFIDEV_BUSWIDTH-1); + if (ofs & (map_bankwidth(map)-1)) { + unsigned long bus_ofs = ofs & ~(map_bankwidth(map)-1); int gap = ofs - bus_ofs; - int i = 0, n = 0; - u_char tmp_buf[8]; - cfi_word datum; - - while (gap--) - tmp_buf[i++] = 0xff; - while (len && i < CFIDEV_BUSWIDTH) - tmp_buf[i++] = buf[n++], len--; - while (i < CFIDEV_BUSWIDTH) - tmp_buf[i++] = 0xff; - - if (cfi_buswidth_is_2()) { - datum = *(__u16*)tmp_buf; - } else if (cfi_buswidth_is_4()) { - datum = *(__u32*)tmp_buf; - } else if (cfi_buswidth_is_8()) { - datum = *(__u64*)tmp_buf; - } else { - return -EINVAL; /* should never happen, but be safe */ - } + 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], - bus_ofs, datum); - if (ret) + bus_ofs, datum, FL_WRITING); + if (ret) return ret; - + + len -= n; ofs += n; buf += n; (*retlen) += n; if (ofs >> cfi->chipshift) { - chipnum ++; + chipnum ++; ofs = 0; if (chipnum == cfi->numchips) return 0; } } - - while(len >= CFIDEV_BUSWIDTH) { - cfi_word datum; - - if (cfi_buswidth_is_1()) { - datum = *(__u8*)buf; - } else if (cfi_buswidth_is_2()) { - datum = *(__u16*)buf; - } else if (cfi_buswidth_is_4()) { - datum = *(__u32*)buf; - } else if (cfi_buswidth_is_8()) { - datum = *(__u64*)buf; - } else { - return -EINVAL; - } + + while(len >= map_bankwidth(map)) { + map_word datum = map_word_load(map, buf); ret = do_write_oneword(map, &cfi->chips[chipnum], - ofs, datum); + ofs, datum, FL_WRITING); if (ret) return ret; - ofs += CFIDEV_BUSWIDTH; - buf += CFIDEV_BUSWIDTH; - (*retlen) += CFIDEV_BUSWIDTH; - len -= CFIDEV_BUSWIDTH; + ofs += map_bankwidth(map); + buf += map_bankwidth(map); + (*retlen) += map_bankwidth(map); + len -= map_bankwidth(map); if (ofs >> cfi->chipshift) { - chipnum ++; + chipnum ++; ofs = 0; if (chipnum == cfi->numchips) return 0; } } - if (len & (CFIDEV_BUSWIDTH-1)) { - int i = 0, n = 0; - u_char tmp_buf[8]; - cfi_word datum; - - while (len--) - tmp_buf[i++] = buf[n++]; - while (i < CFIDEV_BUSWIDTH) - tmp_buf[i++] = 0xff; - - if (cfi_buswidth_is_2()) { - datum = *(__u16*)tmp_buf; - } else if (cfi_buswidth_is_4()) { - datum = *(__u32*)tmp_buf; - } else if (cfi_buswidth_is_8()) { - datum = *(__u64*)tmp_buf; - } else { - return -EINVAL; /* should never happen, but be safe */ - } + 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], - ofs, datum); - if (ret) + ofs, datum, FL_WRITING); + if (ret) return ret; - - (*retlen) += n; + + (*retlen) += len; } return 0; } -static inline int do_write_buffer(struct map_info *map, struct flchip *chip, - unsigned long adr, const u_char *buf, int len) +static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, + unsigned long adr, const struct kvec **pvec, + unsigned long *pvec_seek, int len) { struct cfi_private *cfi = map->fldrv_priv; - cfi_word status, status_OK; - unsigned long cmd_adr, timeo; - int wbufsize, z, ret=0, bytes, words; + map_word status, write_cmd, datum; + unsigned long cmd_adr; + int ret, wbufsize, word_gap, words; + const struct kvec *vec; + unsigned long vec_seek; - wbufsize = CFIDEV_INTERLEAVE << cfi->cfiq->MaxBufWriteSize; + wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; adr += chip->start; cmd_adr = adr & ~(wbufsize-1); - + /* Let's determine this according to the interleave only once */ - status_OK = CMD(0x80); + write_cmd = (cfi->cfiq->P_ID != 0x0200) ? CMD(0xe8) : CMD(0xe9); spin_lock(chip->mutex); ret = get_chip(map, chip, cmd_adr, FL_WRITING); @@ -952,349 +1475,210 @@ static inline int do_write_buffer(struct map_info *map, struct flchip *chip, return ret; } - if (chip->state != FL_STATUS) - cfi_write(map, CMD(0x70), cmd_adr); - - status = cfi_read(map, cmd_adr); + XIP_INVAL_CACHED_RANGE(map, adr, len); + ENABLE_VPP(map); + xip_disable(map, chip, cmd_adr); - /* §4.8 of the 28FxxxJ3A datasheet says "Any time SR.4 and/or SR.5 is set - [...], the device will not accept any more Write to Buffer commands". + /* §4.8 of the 28FxxxJ3A datasheet says "Any time SR.4 and/or SR.5 is set + [...], the device will not accept any more Write to Buffer commands". So we must check here and reset those bits if they're set. Otherwise we're just pissing in the wind */ - if (status & CMD(0x30)) { - printk(KERN_WARNING "SR.4 or SR.5 bits set in buffer write (status %x). Clearing.\n", status); - cfi_write(map, CMD(0x50), cmd_adr); - cfi_write(map, CMD(0x70), cmd_adr); + if (chip->state != FL_STATUS) { + map_write(map, CMD(0x70), cmd_adr); + chip->state = FL_STATUS; + } + 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); } - ENABLE_VPP(map); - chip->state = FL_WRITING_TO_BUFFER; - - z = 0; - for (;;) { - cfi_write(map, CMD(0xe8), cmd_adr); - - status = cfi_read(map, cmd_adr); - if ((status & status_OK) == status_OK) - break; - spin_unlock(chip->mutex); - cfi_udelay(1); - spin_lock(chip->mutex); + chip->state = FL_WRITING_TO_BUFFER; + map_write(map, write_cmd, cmd_adr); + ret = WAIT_TIMEOUT(map, chip, cmd_adr, 0); + 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; + status = map_read(map, cmd_adr); + 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; + } - if (++z > 20) { - /* Argh. Not ready for write to buffer */ - cfi_write(map, CMD(0x70), cmd_adr); - chip->state = FL_STATUS; - printk(KERN_ERR "Chip not ready for buffer write. Xstatus = %llx, status = %llx\n", (__u64)status, (__u64)cfi_read(map, cmd_adr)); - /* Odd. Clear status bits */ - cfi_write(map, CMD(0x50), cmd_adr); - cfi_write(map, CMD(0x70), cmd_adr); - ret = -EIO; - goto out; - } + /* Figure out the number of words to write */ + word_gap = (-adr & (map_bankwidth(map)-1)); + words = (len - word_gap + map_bankwidth(map) - 1) / map_bankwidth(map); + if (!word_gap) { + words--; + } else { + word_gap = map_bankwidth(map) - word_gap; + adr -= word_gap; + datum = map_word_ff(map); } /* Write length of data to come */ - bytes = len & (CFIDEV_BUSWIDTH-1); - words = len / CFIDEV_BUSWIDTH; - cfi_write(map, CMD(words - !bytes), cmd_adr ); + map_write(map, CMD(words), cmd_adr ); /* Write data */ - z = 0; - while(z < words * CFIDEV_BUSWIDTH) { - if (cfi_buswidth_is_1()) { - u8 *b = (u8 *)buf; - - map_write8 (map, *b++, adr+z); - buf = (const u_char *)b; - } else if (cfi_buswidth_is_2()) { - u16 *b = (u16 *)buf; - - map_write16 (map, *b++, adr+z); - buf = (const u_char *)b; - } else if (cfi_buswidth_is_4()) { - u32 *b = (u32 *)buf; - - map_write32 (map, *b++, adr+z); - buf = (const u_char *)b; - } else if (cfi_buswidth_is_8()) { - u64 *b = (u64 *)buf; - - map_write64 (map, *b++, adr+z); - buf = (const u_char *)b; - } else { - ret = -EINVAL; - goto out; + 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; + + if (!word_gap && len < map_bankwidth(map)) + datum = map_word_ff(map); + + datum = map_word_load_partial(map, datum, + vec->iov_base + vec_seek, + word_gap, n); + + len -= n; + word_gap += n; + if (!len || word_gap == map_bankwidth(map)) { + map_write(map, datum, adr); + adr += map_bankwidth(map); + word_gap = 0; } - z += CFIDEV_BUSWIDTH; - } - if (bytes) { - int i = 0, n = 0; - u_char tmp_buf[8], *tmp_p = tmp_buf; - - while (bytes--) - tmp_buf[i++] = buf[n++]; - while (i < CFIDEV_BUSWIDTH) - tmp_buf[i++] = 0xff; - if (cfi_buswidth_is_2()) { - u16 *b = (u16 *)tmp_p; - - map_write16 (map, *b++, adr+z); - tmp_p = (u_char *)b; - } else if (cfi_buswidth_is_4()) { - u32 *b = (u32 *)tmp_p; - - map_write32 (map, *b++, adr+z); - tmp_p = (u_char *)b; - } else if (cfi_buswidth_is_8()) { - u64 *b = (u64 *)tmp_p; - - map_write64 (map, *b++, adr+z); - tmp_p = (u_char *)b; - } else { - ret = -EINVAL; - goto out; + + vec_seek += n; + if (vec_seek == vec->iov_len) { + vec++; + vec_seek = 0; } - } + } while (len); + *pvec = vec; + *pvec_seek = vec_seek; + /* GO GO GO */ - cfi_write(map, CMD(0xd0), cmd_adr); + map_write(map, CMD(0xd0), cmd_adr); chip->state = FL_WRITING; - spin_unlock(chip->mutex); - cfi_udelay(chip->buffer_write_time); - spin_lock(chip->mutex); + ret = INVAL_CACHE_AND_WAIT(map, chip, cmd_adr, + adr, len, + chip->buffer_write_time); + 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; + } - timeo = jiffies + (HZ/2); - z = 0; - for (;;) { - if (chip->state != FL_WRITING) { - /* Someone's suspended the write. Sleep */ - DECLARE_WAITQUEUE(wait, current); - set_current_state(TASK_UNINTERRUPTIBLE); - add_wait_queue(&chip->wq, &wait); - spin_unlock(chip->mutex); - schedule(); - remove_wait_queue(&chip->wq, &wait); - timeo = jiffies + (HZ / 2); /* FIXME */ - spin_lock(chip->mutex); - continue; - } + /* check for errors */ + status = map_read(map, cmd_adr); + if (map_word_bitsset(map, status, CMD(0x1a))) { + unsigned long chipstatus = MERGESTATUS(status); - status = cfi_read(map, cmd_adr); - if ((status & status_OK) == status_OK) - break; + /* reset status */ + map_write(map, CMD(0x50), cmd_adr); + map_write(map, CMD(0x70), cmd_adr); + xip_enable(map, chip, cmd_adr); - /* OK Still waiting */ - if (time_after(jiffies, timeo)) { - chip->state = FL_STATUS; - printk(KERN_ERR "waiting for chip to be ready timed out in bufwrite\n"); + if (chipstatus & 0x02) { + ret = -EROFS; + } else if (chipstatus & 0x08) { + printk(KERN_ERR "%s: buffer write error (bad VPP)\n", map->name); ret = -EIO; - goto out; + } else { + printk(KERN_ERR "%s: buffer write error (status 0x%lx)\n", map->name, chipstatus); + ret = -EINVAL; } - - /* Latency issues. Drop the lock, wait a while and retry */ - spin_unlock(chip->mutex); - cfi_udelay(1); - z++; - spin_lock(chip->mutex); - } - if (!z) { - chip->buffer_write_time--; - if (!chip->buffer_write_time) - chip->buffer_write_time++; - } - if (z > 1) - chip->buffer_write_time++; - /* Done and happy. */ - chip->state = FL_STATUS; - - /* check for lock bit */ - if (status & CMD(0x02)) { - /* clear status */ - cfi_write(map, CMD(0x50), cmd_adr); - /* put back into read status register mode */ - cfi_write(map, CMD(0x70), adr); - ret = -EROFS; + goto out; } - out: - put_chip(map, chip, cmd_adr); + xip_enable(map, chip, cmd_adr); + out: put_chip(map, chip, cmd_adr); spin_unlock(chip->mutex); return ret; } -static int cfi_intelext_write_buffers (struct mtd_info *mtd, loff_t to, - size_t len, size_t *retlen, const u_char *buf) +static int cfi_intelext_writev (struct mtd_info *mtd, const struct kvec *vecs, + unsigned long count, loff_t to, size_t *retlen) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; - int wbufsize = CFIDEV_INTERLEAVE << cfi->cfiq->MaxBufWriteSize; + int wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; int ret = 0; int chipnum; - unsigned long ofs; + unsigned long ofs, vec_seek, i; + size_t len = 0; + + for (i = 0; i < count; i++) + len += vecs[i].iov_len; *retlen = 0; if (!len) return 0; chipnum = to >> cfi->chipshift; - ofs = to - (chipnum << cfi->chipshift); - - /* If it's not bus-aligned, do the first word write */ - if (ofs & (CFIDEV_BUSWIDTH-1)) { - size_t local_len = (-ofs)&(CFIDEV_BUSWIDTH-1); - if (local_len > len) - local_len = len; - ret = cfi_intelext_write_words(mtd, to, local_len, - retlen, buf); - if (ret) - return ret; - ofs += local_len; - buf += local_len; - len -= local_len; - - if (ofs >> cfi->chipshift) { - chipnum ++; - ofs = 0; - if (chipnum == cfi->numchips) - return 0; - } - } + ofs = to - (chipnum << cfi->chipshift); + vec_seek = 0; - /* Write buffer is worth it only if more than one word to write... */ - while(len) { + do { /* We must not cross write block boundaries */ int size = wbufsize - (ofs & (wbufsize-1)); if (size > len) size = len; - ret = do_write_buffer(map, &cfi->chips[chipnum], - ofs, buf, size); + ret = do_write_buffer(map, &cfi->chips[chipnum], + ofs, &vecs, &vec_seek, size); if (ret) return ret; ofs += size; - buf += size; (*retlen) += size; len -= size; if (ofs >> cfi->chipshift) { - chipnum ++; + chipnum ++; ofs = 0; if (chipnum == cfi->numchips) return 0; } - } - return 0; -} - -typedef int (*varsize_frob_t)(struct map_info *map, struct flchip *chip, - unsigned long adr, void *thunk); - -static int cfi_intelext_varsize_frob(struct mtd_info *mtd, varsize_frob_t frob, - loff_t ofs, size_t len, void *thunk) -{ - struct map_info *map = mtd->priv; - struct cfi_private *cfi = map->fldrv_priv; - unsigned long adr; - int chipnum, ret = 0; - int i, first; - struct mtd_erase_region_info *regions = mtd->eraseregions; - - if (ofs > mtd->size) - return -EINVAL; - - if ((len + ofs) > mtd->size) - return -EINVAL; - - /* Check that both start and end of the requested erase are - * aligned with the erasesize at the appropriate addresses. - */ - - i = 0; - - /* Skip all erase regions which are ended before the start of - the requested erase. Actually, to save on the calculations, - we skip to the first erase region which starts after the - start of the requested erase, and then go back one. - */ - - while (i < mtd->numeraseregions && ofs >= regions[i].offset) - i++; - i--; - - /* OK, now i is pointing at the erase region in which this - erase request starts. Check the start of the requested - erase range is aligned with the erase size which is in - effect here. - */ - - if (ofs & (regions[i].erasesize-1)) - return -EINVAL; - - /* Remember the erase region we start on */ - first = i; - - /* Next, check that the end of the requested erase is aligned - * with the erase region at that address. - */ - - while (inumeraseregions && (ofs + len) >= regions[i].offset) - i++; - - /* As before, drop back one to point at the region in which - the address actually falls - */ - i--; - - if ((ofs + len) & (regions[i].erasesize-1)) - return -EINVAL; - chipnum = ofs >> cfi->chipshift; - adr = ofs - (chipnum << cfi->chipshift); + /* Be nice and reschedule with the chip in a usable state for other + processes. */ + cond_resched(); - i=first; + } while (len); - while(len) { - ret = (*frob)(map, &cfi->chips[chipnum], adr, thunk); - - if (ret) - return ret; + return 0; +} - adr += regions[i].erasesize; - len -= regions[i].erasesize; +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; - if (adr % (1<< cfi->chipshift) == ((regions[i].offset + (regions[i].erasesize * regions[i].numblocks)) %( 1<< cfi->chipshift))) - i++; + vec.iov_base = (void *) buf; + vec.iov_len = len; - if (adr >> cfi->chipshift) { - adr = 0; - chipnum++; - - if (chipnum >= cfi->numchips) - break; - } - } - - return 0; + return cfi_intelext_writev(mtd, &vec, 1, to, retlen); } - -static int do_erase_oneblock(struct map_info *map, struct flchip *chip, unsigned long adr, void *thunk) +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; - cfi_word status, status_OK; - unsigned long timeo; + map_word status; int retries = 3; - DECLARE_WAITQUEUE(wait, current); - int ret = 0; + int ret; adr += chip->start; - /* Let's determine this according to the interleave only once */ - status_OK = CMD(0x80); - retry: spin_lock(chip->mutex); ret = get_chip(map, chip, adr, FL_ERASING); @@ -1303,114 +1687,69 @@ static int do_erase_oneblock(struct map_info *map, struct flchip *chip, unsigned return ret; } + XIP_INVAL_CACHED_RANGE(map, adr, len); ENABLE_VPP(map); + xip_disable(map, chip, adr); + /* Clear the status register first */ - cfi_write(map, CMD(0x50), adr); + map_write(map, CMD(0x50), adr); /* Now erase */ - cfi_write(map, CMD(0x20), adr); - cfi_write(map, CMD(0xD0), adr); + map_write(map, CMD(0x20), adr); + map_write(map, CMD(0xD0), adr); chip->state = FL_ERASING; chip->erase_suspended = 0; - spin_unlock(chip->mutex); - set_current_state(TASK_UNINTERRUPTIBLE); - schedule_timeout((chip->erase_time*HZ)/(2*1000)); - spin_lock(chip->mutex); - - /* FIXME. Use a timer to check this, and return immediately. */ - /* Once the state machine's known to be working I'll do that */ - - timeo = jiffies + (HZ*20); - for (;;) { - if (chip->state != FL_ERASING) { - /* Someone's suspended the erase. Sleep */ - set_current_state(TASK_UNINTERRUPTIBLE); - add_wait_queue(&chip->wq, &wait); - spin_unlock(chip->mutex); - schedule(); - remove_wait_queue(&chip->wq, &wait); - spin_lock(chip->mutex); - continue; - } - if (chip->erase_suspended) { - /* This erase was suspended and resumed. - Adjust the timeout */ - timeo = jiffies + (HZ*20); /* FIXME */ - chip->erase_suspended = 0; - } - - status = cfi_read(map, adr); - if ((status & status_OK) == status_OK) - break; - - /* OK Still waiting */ - if (time_after(jiffies, timeo)) { - cfi_write(map, CMD(0x70), adr); - chip->state = FL_STATUS; - printk(KERN_ERR "waiting for erase at %08lx to complete timed out. Xstatus = %llx, status = %llx.\n", - adr, (__u64)status, (__u64)cfi_read(map, adr)); - /* Clear status bits */ - cfi_write(map, CMD(0x50), adr); - cfi_write(map, CMD(0x70), adr); - DISABLE_VPP(map); - spin_unlock(chip->mutex); - return -EIO; - } - - /* Latency issues. Drop the lock, wait a while and retry */ - spin_unlock(chip->mutex); - set_current_state(TASK_UNINTERRUPTIBLE); - schedule_timeout(1); - spin_lock(chip->mutex); + ret = INVAL_CACHE_AND_WAIT(map, chip, adr, + adr, len, + chip->erase_time); + 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; } - - DISABLE_VPP(map); - ret = 0; /* We've broken this before. It doesn't hurt to be safe */ - cfi_write(map, CMD(0x70), adr); + map_write(map, CMD(0x70), adr); chip->state = FL_STATUS; - status = cfi_read(map, adr); - - /* check for lock bit */ - if (status & CMD(0x3a)) { - unsigned char chipstatus = status; - if (status != CMD(status & 0xff)) { - int i; - for (i = 1; i> (cfi->device_type * 8); - } - printk(KERN_WARNING "Status is not identical for all chips: 0x%llx. Merging to give 0x%02x\n", (__u64)status, chipstatus); - } + status = map_read(map, adr); + + /* check for errors */ + if (map_word_bitsset(map, status, CMD(0x3a))) { + unsigned long chipstatus = MERGESTATUS(status); + /* Reset the error bits */ - cfi_write(map, CMD(0x50), adr); - cfi_write(map, CMD(0x70), adr); - + map_write(map, CMD(0x50), adr); + map_write(map, CMD(0x70), adr); + xip_enable(map, chip, adr); + if ((chipstatus & 0x30) == 0x30) { - printk(KERN_NOTICE "Chip reports improper command sequence: status 0x%llx\n", (__u64)status); - ret = -EIO; + printk(KERN_ERR "%s: block erase error: (bad command sequence, status 0x%lx)\n", map->name, chipstatus); + ret = -EINVAL; } else if (chipstatus & 0x02) { /* Protection bit set */ ret = -EROFS; } else if (chipstatus & 0x8) { /* Voltage */ - printk(KERN_WARNING "Chip reports voltage low on erase: status 0x%llx\n", (__u64)status); + printk(KERN_ERR "%s: block erase error: (bad VPP)\n", map->name); ret = -EIO; - } else if (chipstatus & 0x20) { - if (retries--) { - printk(KERN_DEBUG "Chip erase failed at 0x%08lx: status 0x%llx. Retrying...\n", adr, (__u64)status); - timeo = jiffies + HZ; - chip->state = FL_STATUS; - spin_unlock(chip->mutex); - goto retry; - } - printk(KERN_DEBUG "Chip erase failed at 0x%08lx: status 0x%llx\n", adr, (__u64)status); + } else if (chipstatus & 0x20 && retries--) { + printk(KERN_DEBUG "block erase failed at 0x%08lx: status 0x%lx. Retrying...\n", adr, chipstatus); + put_chip(map, chip, adr); + spin_unlock(chip->mutex); + goto retry; + } else { + printk(KERN_ERR "%s: block erase failed at 0x%08lx (status 0x%lx)\n", map->name, adr, chipstatus); ret = -EIO; } + + goto out; } - wake_up(&chip->wq); + xip_enable(map, chip, adr); + out: put_chip(map, chip, adr); spin_unlock(chip->mutex); return ret; } @@ -1423,14 +1762,13 @@ int cfi_intelext_erase_varsize(struct mtd_info *mtd, struct erase_info *instr) ofs = instr->addr; len = instr->len; - ret = cfi_intelext_varsize_frob(mtd, do_erase_oneblock, ofs, len, 0); + ret = cfi_varsize_frob(mtd, do_erase_oneblock, ofs, len, NULL); if (ret) return ret; instr->state = MTD_ERASE_DONE; - if (instr->callback) - instr->callback(instr); - + mtd_erase_callback(instr); + return 0; } @@ -1451,7 +1789,7 @@ static void cfi_intelext_sync (struct mtd_info *mtd) if (!ret) { chip->oldstate = chip->state; chip->state = FL_SYNCING; - /* No need to wake_up() on this state change - + /* No need to wake_up() on this state change - * as the whole point is that nobody can do anything * with the chip now anyway. */ @@ -1465,9 +1803,10 @@ static void cfi_intelext_sync (struct mtd_info *mtd) chip = &cfi->chips[i]; spin_lock(chip->mutex); - + if (chip->state == FL_SYNCING) { chip->state = chip->oldstate; + chip->oldstate = FL_READY; wake_up(&chip->wq); } spin_unlock(chip->mutex); @@ -1475,16 +1814,22 @@ static void cfi_intelext_sync (struct mtd_info *mtd) } #ifdef DEBUG_LOCK_BITS -static int do_printlockstatus_oneblock(struct map_info *map, struct flchip *chip, unsigned long adr, void *thunk) +static int __xipram do_printlockstatus_oneblock(struct map_info *map, + struct flchip *chip, + unsigned long adr, + int len, void *thunk) { struct cfi_private *cfi = map->fldrv_priv; - int ofs_factor = cfi->interleave * cfi->device_type; + int status, ofs_factor = cfi->interleave * cfi->device_type; - cfi_send_gen_cmd(0x90, 0x55, 0, map, cfi, cfi->device_type, NULL); + adr += chip->start; + xip_disable(map, chip, adr+(2*ofs_factor)); + map_write(map, CMD(0x90), adr+(2*ofs_factor)); + chip->state = FL_JEDEC_QUERY; + status = cfi_read_query(map, adr+(2*ofs_factor)); + xip_enable(map, chip, 0); printk(KERN_DEBUG "block status register for 0x%08lx is %x\n", - adr, cfi_read_query(map, adr+(2*ofs_factor))); - cfi_send_gen_cmd(0xff, 0x55, 0, map, cfi, cfi->device_type, NULL); - + adr, status); return 0; } #endif @@ -1492,18 +1837,16 @@ static int do_printlockstatus_oneblock(struct map_info *map, struct flchip *chip #define DO_XXLOCK_ONEBLOCK_LOCK ((void *) 1) #define DO_XXLOCK_ONEBLOCK_UNLOCK ((void *) 2) -static int do_xxlock_oneblock(struct map_info *map, struct flchip *chip, unsigned long adr, void *thunk) +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; - cfi_word status, status_OK; - unsigned long timeo = jiffies + HZ; + struct cfi_pri_intelext *extp = cfi->cmdset_priv; + int udelay; int ret; adr += chip->start; - /* Let's determine this according to the interleave only once */ - status_OK = CMD(0x80); - spin_lock(chip->mutex); ret = get_chip(map, chip, adr, FL_LOCKING); if (ret) { @@ -1512,52 +1855,37 @@ static int do_xxlock_oneblock(struct map_info *map, struct flchip *chip, unsigne } ENABLE_VPP(map); - cfi_write(map, CMD(0x60), adr); + xip_disable(map, chip, adr); + map_write(map, CMD(0x60), adr); if (thunk == DO_XXLOCK_ONEBLOCK_LOCK) { - cfi_write(map, CMD(0x01), adr); + map_write(map, CMD(0x01), adr); chip->state = FL_LOCKING; } else if (thunk == DO_XXLOCK_ONEBLOCK_UNLOCK) { - cfi_write(map, CMD(0xD0), adr); + map_write(map, CMD(0xD0), adr); chip->state = FL_UNLOCKING; } else BUG(); - spin_unlock(chip->mutex); - schedule_timeout(HZ); - spin_lock(chip->mutex); - - /* FIXME. Use a timer to check this, and return immediately. */ - /* Once the state machine's known to be working I'll do that */ - - timeo = jiffies + (HZ*20); - for (;;) { + /* + * If Instant Individual Block Locking supported then no need + * to delay. + */ + udelay = (!extp || !(extp->FeatureSupport & (1 << 5))) ? 1000000/HZ : 0; - status = cfi_read(map, adr); - if ((status & status_OK) == status_OK) - break; - - /* OK Still waiting */ - if (time_after(jiffies, timeo)) { - cfi_write(map, CMD(0x70), adr); - chip->state = FL_STATUS; - printk(KERN_ERR "waiting for unlock to complete timed out. Xstatus = %llx, status = %llx.\n", (__u64)status, (__u64)cfi_read(map, adr)); - DISABLE_VPP(map); - spin_unlock(chip->mutex); - return -EIO; - } - - /* Latency issues. Drop the lock, wait a while and retry */ - spin_unlock(chip->mutex); - cfi_udelay(1); - spin_lock(chip->mutex); + ret = WAIT_TIMEOUT(map, chip, adr, udelay); + 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; } - - /* Done and happy. */ - chip->state = FL_STATUS; - put_chip(map, chip, adr); + + xip_enable(map, chip, adr); +out: put_chip(map, chip, adr); spin_unlock(chip->mutex); - return 0; + return ret; } static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, size_t len) @@ -1567,18 +1895,18 @@ static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, size_t len) #ifdef DEBUG_LOCK_BITS printk(KERN_DEBUG "%s: lock status before, ofs=0x%08llx, len=0x%08X\n", __FUNCTION__, ofs, len); - cfi_intelext_varsize_frob(mtd, do_printlockstatus_oneblock, - ofs, len, 0); + cfi_varsize_frob(mtd, do_printlockstatus_oneblock, + ofs, len, 0); #endif - ret = cfi_intelext_varsize_frob(mtd, do_xxlock_oneblock, - ofs, len, DO_XXLOCK_ONEBLOCK_LOCK); - + ret = cfi_varsize_frob(mtd, do_xxlock_oneblock, + ofs, len, DO_XXLOCK_ONEBLOCK_LOCK); + #ifdef DEBUG_LOCK_BITS - printk(KERN_DEBUG - "%s: lock status after, ret=%d\n", __FUNCTION__, ret); - cfi_intelext_varsize_frob(mtd, do_printlockstatus_oneblock, - ofs, len, 0); + printk(KERN_DEBUG "%s: lock status after, ret=%d\n", + __FUNCTION__, ret); + cfi_varsize_frob(mtd, do_printlockstatus_oneblock, + ofs, len, 0); #endif return ret; @@ -1591,22 +1919,291 @@ static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, size_t len) #ifdef DEBUG_LOCK_BITS printk(KERN_DEBUG "%s: lock status before, ofs=0x%08llx, len=0x%08X\n", __FUNCTION__, ofs, len); - cfi_intelext_varsize_frob(mtd, do_printlockstatus_oneblock, - ofs, len, 0); + cfi_varsize_frob(mtd, do_printlockstatus_oneblock, + ofs, len, 0); #endif - ret = cfi_intelext_varsize_frob(mtd, do_xxlock_oneblock, + ret = cfi_varsize_frob(mtd, do_xxlock_oneblock, ofs, len, DO_XXLOCK_ONEBLOCK_UNLOCK); - + #ifdef DEBUG_LOCK_BITS - printk(KERN_DEBUG "%s: lock status after, ret=%d\n", __FUNCTION__, ret); - cfi_intelext_varsize_frob(mtd, do_printlockstatus_oneblock, - ofs, len, 0); + printk(KERN_DEBUG "%s: lock status after, ret=%d\n", + __FUNCTION__, ret); + cfi_varsize_frob(mtd, do_printlockstatus_oneblock, + ofs, len, 0); #endif - + return ret; } +#ifdef CONFIG_MTD_OTP + +typedef int (*otp_op_t)(struct map_info *map, struct flchip *chip, + 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; + + spin_lock(chip->mutex); + ret = get_chip(map, chip, chip->start, FL_JEDEC_QUERY); + if (ret) { + spin_unlock(chip->mutex); + return ret; + } + + /* let's ensure we're not reading back cached data from array mode */ + INVALIDATE_CACHED_RANGE(map, chip->start + offset, size); + + 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 */ + INVALIDATE_CACHED_RANGE(map, chip->start + offset, size); + + put_chip(map, chip, chip->start); + spin_unlock(chip->mutex); + 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); + if (ret) + 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 */ + if (size != grpsz) + 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; + chip_num = 0; + + /* Some chips have OTP located in the _top_ partition only. + For example: Intel 28F256L18T (T means top-parameter device) */ + if (cfi->mfr == MANUFACTURER_INTEL) { + switch (cfi->id) { + case 0x880b: + case 0x880c: + case 0x880d: + chip_num = chip_step - 1; + } + } + + for ( ; chip_num < cfi->numchips; chip_num += chip_step) { + 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; + } + + while (len > 0 && groups > 0) { + 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; + data_offset += groupsize; + } 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; + data_offset += size; + } + 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); +} + +static int cfi_intelext_get_fact_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, 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 + static int cfi_intelext_suspend(struct mtd_info *mtd) { struct map_info *map = mtd->priv; @@ -1626,15 +2223,26 @@ static int cfi_intelext_suspend(struct mtd_info *mtd) case FL_CFI_QUERY: case FL_JEDEC_QUERY: if (chip->oldstate == FL_READY) { + /* place the chip in a known state before suspend */ + map_write(map, CMD(0xFF), cfi->chips[i].start); chip->oldstate = chip->state; chip->state = FL_PM_SUSPENDED; - /* No need to wake_up() on this state change - + /* No need to wake_up() on this state change - * 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. */ + printk(KERN_NOTICE "Flash device refused suspend due to active operation (state %d)\n", chip->oldstate); ret = -EAGAIN; case FL_PM_SUSPENDED: break; @@ -1647,20 +2255,21 @@ static int cfi_intelext_suspend(struct mtd_info *mtd) if (ret) { for (i--; i >=0; i--) { chip = &cfi->chips[i]; - + spin_lock(chip->mutex); - + 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); } spin_unlock(chip->mutex); } - } - + } + return ret; } @@ -1672,15 +2281,15 @@ static void cfi_intelext_resume(struct mtd_info *mtd) struct flchip *chip; for (i=0; inumchips; i++) { - + chip = &cfi->chips[i]; spin_lock(chip->mutex); - + /* Go to known state. Chip may have been power cycled */ if (chip->state == FL_PM_SUSPENDED) { - cfi_write(map, CMD(0xFF), 0); - chip->state = FL_READY; + map_write(map, CMD(0xFF), cfi->chips[i].start); + chip->oldstate = chip->state = FL_READY; wake_up(&chip->wq); } @@ -1688,35 +2297,55 @@ static void cfi_intelext_resume(struct mtd_info *mtd) } } -static void cfi_intelext_destroy(struct mtd_info *mtd) +static int cfi_intelext_reset(struct mtd_info *mtd) { struct map_info *map = mtd->priv; struct cfi_private *cfi = map->fldrv_priv; - kfree(cfi->cmdset_priv); - kfree(cfi->cfiq); - kfree(cfi); - kfree(mtd->eraseregions); -} + int i, ret; -static char im_name_1[]="cfi_cmdset_0001"; -static char im_name_3[]="cfi_cmdset_0003"; + for (i=0; i < cfi->numchips; i++) { + struct flchip *chip = &cfi->chips[i]; + + /* force the completion of any ongoing operation + and switch to array mode so any bootloader in + flash is accessible for soft reboot. */ + spin_lock(chip->mutex); + ret = get_chip(map, chip, chip->start, FL_SYNCING); + if (!ret) { + map_write(map, CMD(0xff), chip->start); + chip->state = FL_READY; + } + spin_unlock(chip->mutex); + } -int __init cfi_intelext_init(void) -{ - inter_module_register(im_name_1, THIS_MODULE, &cfi_cmdset_0001); - inter_module_register(im_name_3, THIS_MODULE, &cfi_cmdset_0001); return 0; } -static void __exit cfi_intelext_exit(void) +static int cfi_intelext_reboot(struct notifier_block *nb, unsigned long val, + void *v) { - inter_module_unregister(im_name_1); - inter_module_unregister(im_name_3); + struct mtd_info *mtd; + + mtd = container_of(nb, struct mtd_info, reboot_notifier); + cfi_intelext_reset(mtd); + return NOTIFY_DONE; } -module_init(cfi_intelext_init); -module_exit(cfi_intelext_exit); +static void cfi_intelext_destroy(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + cfi_intelext_reset(mtd); + unregister_reboot_notifier(&mtd->reboot_notifier); + kfree(cfi->cmdset_priv); + kfree(cfi->cfiq); + kfree(cfi->chips[0].priv); + kfree(cfi); + kfree(mtd->eraseregions); +} MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Woodhouse et al."); MODULE_DESCRIPTION("MTD chip driver for Intel/Sharp flash chips"); +MODULE_ALIAS("cfi_cmdset_0003"); +MODULE_ALIAS("cfi_cmdset_0200");