📄 cfi_cmdset_0002.c
字号:
/* * Common Flash Interface support: * AMD & Fujitsu Standard Vendor Command Set (ID 0x0002) * * Copyright (C) 2000 Crossnet Co. <info@crossnet.co.jp> * * 2_by_8 routines added by Simon Munton * * 4_by_16 work by Carolyn J. Smith * * Occasionally maintained by Thayne Harbaugh tharbaugh at lnxi dot com * * This code is GPL * * $Id: cfi_cmdset_0002.c,v 1.84 2003/10/04 17:13:19 dwmw2 Exp $ * */#include <linux/config.h>#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>#include <linux/mtd/compatmac.h>#include <linux/mtd/map.h>#include <linux/mtd/mtd.h>#include <linux/mtd/cfi.h>#define AMD_BOOTLOC_BUG#define FORCE_WORD_WRITE 0/* * This is an attempt to coalesce the retry logic in one place - that way * there aren't #ifdefs scattered throughout. */#ifdef CONFIG_MTD_CFI_AMDSTD_RETRY#ifndef CONFIG_MTD_CFI_AMDSTD_RETRY_MAX#define CONFIG_MTD_CFI_AMDSTD_RETRY_MAX 0#endif#define RETRY_CMD_LABEL retry_cmd: do {} while (0)#define HANDLE_WACKY_STATE() handle_wacky_state(__func__, retry_cmd_cnt, adr, datum, prev_oldstatus, prev_status, oldstatus, status)static int retry_cmd_max = CONFIG_MTD_CFI_AMDSTD_RETRY_MAX;#define DECLARE_RETRY_CMD_CNT() int retry_cmd_cnt = 0#ifdef CONFIG_MTD_CFI_AMDSTD_RETRY#define CHECK_RETRIES() do { if (++retry_cmd_cnt <= retry_cmd_max) goto retry_cmd; } while (0)#endif#else#define RETRY_CMD_LABEL do {} while (0)#define HANDLE_WACKY_STATE() handle_wacky_state(__func__, adr, datum, prev_oldstatus, prev_status, oldstatus, status)#define DECLARE_RETRY_CMD_CNT()#define CHECK_RETRIES()#endif /* !defined(CONFIG_MTD_CFI_AMDSTD_RETRY) */static int cfi_amdstd_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);static int cfi_amdstd_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);static int cfi_amdstd_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);static int cfi_amdstd_erase_chip(struct mtd_info *, struct erase_info *);static int cfi_amdstd_erase_varsize(struct mtd_info *, struct erase_info *);static int cfi_amdstd_lock_varsize(struct mtd_info *, loff_t, size_t);static int cfi_amdstd_unlock_varsize(struct mtd_info *, loff_t, size_t);static void cfi_amdstd_sync (struct mtd_info *);static int cfi_amdstd_suspend (struct mtd_info *);static void cfi_amdstd_resume (struct mtd_info *);static int cfi_amdstd_secsi_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);static void cfi_amdstd_destroy(struct mtd_info *);struct mtd_info *cfi_cmdset_0002(struct map_info *, int);static struct mtd_info *cfi_amdstd_setup (struct map_info *);static struct mtd_chip_driver cfi_amdstd_chipdrv = { .probe = NULL, /* Not usable directly */ .destroy = cfi_amdstd_destroy, .name = "cfi_cmdset_0002", .module = THIS_MODULE};/* #define DEBUG_LOCK_BITS *//* #define DEBUG_CFI_FEATURES */#ifdef DEBUG_CFI_FEATURESstatic void cfi_tell_features(struct cfi_pri_amdstd *extp){ const char* erase_suspend[3] = { "Not supported", "Read only", "Read/write" }; const char* top_bottom[6] = { "No WP", "8x8KiB sectors at top & bottom, no WP", "Bottom boot", "Top boot", "Uniform, Bottom WP", "Uniform, Top WP" }; printk(" Silicon revision: %d\n", extp->SiliconRevision >> 1); printk(" Address sensitive unlock: %s\n", (extp->SiliconRevision & 1) ? "Not required" : "Required"); if (extp->EraseSuspend < ARRAY_SIZE(erase_suspend)) printk(" Erase Suspend: %s\n", erase_suspend[extp->EraseSuspend]); else printk(" Erase Suspend: Unknown value %d\n", extp->EraseSuspend); if (extp->BlkProt == 0) printk(" Block protection: Not supported\n"); else printk(" Block protection: %d sectors per group\n", extp->BlkProt); printk(" Temporary block unprotect: %s\n", extp->TmpBlkUnprotect ? "Supported" : "Not supported"); printk(" Block protect/unprotect scheme: %d\n", extp->BlkProtUnprot); printk(" Number of simultaneous operations: %d\n", extp->SimultaneousOps); printk(" Burst mode: %s\n", extp->BurstMode ? "Supported" : "Not supported"); if (extp->PageMode == 0) printk(" Page mode: Not supported\n"); else printk(" Page mode: %d word page\n", extp->PageMode << 2); printk(" Vpp Supply Minimum Program/Erase Voltage: %d.%d V\n", extp->VppMin >> 4, extp->VppMin & 0xf); printk(" Vpp Supply Maximum Program/Erase Voltage: %d.%d V\n", extp->VppMax >> 4, extp->VppMax & 0xf); if (extp->TopBottom < ARRAY_SIZE(top_bottom)) printk(" Top/Bottom Boot Block: %s\n", top_bottom[extp->TopBottom]); else printk(" Top/Bottom Boot Block: Unknown value %d\n", extp->TopBottom);}#endif#ifdef AMD_BOOTLOC_BUG/* Wheee. Bring me the head of someone at AMD. */static void fixup_amd_bootblock(struct map_info *map, void* param){ struct cfi_private *cfi = map->fldrv_priv; struct cfi_pri_amdstd *extp = cfi->cmdset_priv; __u8 major = extp->MajorVersion; __u8 minor = extp->MinorVersion; if (((major << 8) | minor) < 0x3131) { /* CFI version 1.0 => don't trust bootloc */ if (cfi->id & 0x80) { printk(KERN_WARNING "%s: JEDEC Device ID is 0x%02X. Assuming broken CFI table.\n", map->name, cfi->id); extp->TopBottom = 3; /* top boot */ } else { extp->TopBottom = 2; /* bottom boot */ } }}#endifstatic struct cfi_fixup fixup_table[] = {#ifdef AMD_BOOTLOC_BUG { 0x0001, /* AMD */ CFI_ID_ANY, fixup_amd_bootblock, NULL },#endif { 0, 0, NULL, NULL }};struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary){ struct cfi_private *cfi = map->fldrv_priv; unsigned char bootloc; int i; 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_amdstd *extp; extp = (struct cfi_pri_amdstd*)cfi_read_pri(map, adr, sizeof(*extp), "Amd/Fujitsu"); if (!extp) return NULL; /* Install our own private info structure */ cfi->cmdset_priv = extp; cfi_fixup(map, fixup_table);#ifdef DEBUG_CFI_FEATURES /* Tell the user about it in lots of lovely detail */ cfi_tell_features(extp);#endif bootloc = extp->TopBottom; if ((bootloc != 2) && (bootloc != 3)) { printk(KERN_WARNING "%s: CFI does not contain boot " "bank location. Assuming top.\n", map->name); bootloc = 2; } if (bootloc == 3 && cfi->cfiq->NumEraseRegions > 1) { printk(KERN_WARNING "%s: Swapping erase regions for broken CFI table.\n", map->name); for (i=0; i<cfi->cfiq->NumEraseRegions / 2; i++) { int j = (cfi->cfiq->NumEraseRegions-1)-i; __u32 swap; swap = cfi->cfiq->EraseRegionInfo[i]; cfi->cfiq->EraseRegionInfo[i] = cfi->cfiq->EraseRegionInfo[j]; cfi->cfiq->EraseRegionInfo[j] = swap; } } /* * These might already be setup (more correctly) by * jedec_probe.c - still need it for cfi_probe.c path. */ if ( ! (cfi->addr_unlock1 && cfi->addr_unlock2) ) { switch (cfi->device_type) { case CFI_DEVICETYPE_X8: cfi->addr_unlock1 = 0x555; cfi->addr_unlock2 = 0x2aa; break; case CFI_DEVICETYPE_X16: cfi->addr_unlock1 = 0xaaa; if (map->buswidth == cfi->interleave) { /* X16 chip(s) in X8 mode */ cfi->addr_unlock2 = 0x555; } else { cfi->addr_unlock2 = 0x554; } break; case CFI_DEVICETYPE_X32: cfi->addr_unlock1 = 0x1555; cfi->addr_unlock2 = 0xaaa; break; default: printk(KERN_WARNING "MTD %s(): Unsupported device type %d\n", __func__, cfi->device_type); return NULL; } } } /* CFI mode */ for (i=0; i< cfi->numchips; i++) { cfi->chips[i].word_write_time = 1<<cfi->cfiq->WordWriteTimeoutTyp; cfi->chips[i].buffer_write_time = 1<<cfi->cfiq->BufWriteTimeoutTyp; cfi->chips[i].erase_time = 1<<cfi->cfiq->BlockEraseTimeoutTyp; } map->fldrv = &cfi_amdstd_chipdrv; return cfi_amdstd_setup(map);}static struct mtd_info *cfi_amdstd_setup(struct map_info *map){ struct cfi_private *cfi = map->fldrv_priv; struct mtd_info *mtd; unsigned long devsize = (1<<cfi->cfiq->DevSize) * cfi->interleave; unsigned long offset = 0; int i,j; mtd = kmalloc(sizeof(*mtd), GFP_KERNEL); printk(KERN_NOTICE "number of %s chips: %d\n", (cfi->cfi_mode == CFI_MODE_CFI)?"CFI":"JEDEC",cfi->numchips); if (!mtd) { printk(KERN_WARNING "Failed to allocate memory for MTD device\n"); goto setup_err; } memset(mtd, 0, sizeof(*mtd)); mtd->priv = map; mtd->type = MTD_NORFLASH; /* Also select the correct geometry setup too */ mtd->size = devsize * cfi->numchips; mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips; mtd->eraseregions = kmalloc(sizeof(struct mtd_erase_region_info) * mtd->numeraseregions, GFP_KERNEL); if (!mtd->eraseregions) { printk(KERN_WARNING "Failed to allocate memory for MTD erase region info\n"); goto setup_err; } 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; } 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; }#if 0 // debug for (i=0; i<mtd->numeraseregions;i++){ printk("%d: offset=0x%x,size=0x%x,blocks=%d\n", i,mtd->eraseregions[i].offset, mtd->eraseregions[i].erasesize, mtd->eraseregions[i].numblocks); }#endif switch (CFIDEV_BUSWIDTH) { case 1: case 2: case 4:#ifdef CFI_WORD_64 case 8:#endif if (mtd->numeraseregions == 1 && ((cfi->cfiq->EraseRegionInfo[0] & 0xffff) + 1) == 1) { mtd->erase = cfi_amdstd_erase_chip; } else { mtd->erase = cfi_amdstd_erase_varsize; mtd->lock = cfi_amdstd_lock_varsize; mtd->unlock = cfi_amdstd_unlock_varsize; } if ( cfi->cfiq->BufWriteTimeoutTyp && !FORCE_WORD_WRITE) { DEBUG(MTD_DEBUG_LEVEL1, "Using buffer write method\n" ); mtd->write = cfi_amdstd_write_buffers; } else { DEBUG(MTD_DEBUG_LEVEL1, "Using word write method\n" ); mtd->write = cfi_amdstd_write_words; } mtd->read = cfi_amdstd_read; break; default: printk(KERN_WARNING "MTD %s(): Unsupported buswidth %d\n", __func__, CFIDEV_BUSWIDTH); goto setup_err; break; } if (cfi->fast_prog) { /* In cfi_amdstd_write_words() we frob the protection stuff without paying any attention to the state machine. This upsets in-progress erases. So we turn this flag off for now till the code gets fixed. */ printk(KERN_NOTICE "cfi_cmdset_0002: Disabling fast programming due to code brokenness.\n"); cfi->fast_prog = 0; } /* does this chip have a secsi area? */ if(cfi->mfr==1){ switch(cfi->id){ case 0x50: case 0x53: case 0x55: case 0x56: case 0x5C: case 0x5F: /* Yes */ mtd->read_user_prot_reg = cfi_amdstd_secsi_read; mtd->read_fact_prot_reg = cfi_amdstd_secsi_read; default: ; } } mtd->sync = cfi_amdstd_sync; mtd->suspend = cfi_amdstd_suspend; mtd->resume = cfi_amdstd_resume; mtd->flags = MTD_CAP_NORFLASH; map->fldrv = &cfi_amdstd_chipdrv; mtd->name = map->name; __module_get(THIS_MODULE); return mtd; setup_err: if(mtd) { if(mtd->eraseregions) kfree(mtd->eraseregions); kfree(mtd); } kfree(cfi->cmdset_priv); kfree(cfi->cfiq); return NULL;}/* This is more work to coalesce the retry #ifdefs in one location */static inline void handle_wacky_state(const char *func,#ifdef CONFIG_MTD_CFI_AMDSTD_RETRY int retry_cmd_cnt,#endif unsigned long adr, cfi_word datum, cfi_word prev_oldstatus, cfi_word prev_status, cfi_word oldstatus, cfi_word status){#ifdef CONFIG_MTD_CFI_AMDSTD_RETRY if ( retry_cmd_cnt == retry_cmd_max ) {#endif printk(KERN_WARNING "MTD %s(): Wacky! Unable to decode failure status\n" "Possible buggy device - try "#ifdef CONFIG_MTD_CFI_AMDSTD_RETRY "increasing retry_cmd_max from %d\n"#else "enabling CONFIG_MTD_CFI_AMDSTD_RETRY\n" "in your kernel config and setting driver retry_cmd_max\n"#endif , func#ifdef CONFIG_MTD_CFI_AMDSTD_RETRY , retry_cmd_max#endif ); printk(KERN_WARNING "MTD %s(): 0x%.8lx(0x%.8x): 0x%.8x 0x%.8x 0x%.8x 0x%.8x\n", func, adr, datum, prev_oldstatus, prev_status, oldstatus, status);#ifdef CONFIG_MTD_CFI_AMDSTD_RETRY }#endif}static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode){ DECLARE_WAITQUEUE(wait, current); struct cfi_private *cfi = map->fldrv_priv; cfi_word status, oldstatus; cfi_word dq6 = CMD(1<<6); cfi_word dq2 = CMD(1<<2); unsigned long timeo; struct cfi_pri_amdstd *cfip = (struct cfi_pri_amdstd *)cfi->cmdset_priv; resettime: timeo = jiffies + HZ; retry: switch (chip->state) { case FL_STATUS: for (;;) { oldstatus = cfi_read(map, adr); status = cfi_read(map, adr); if (((oldstatus ^ status) & (dq6 | dq2)) == 0) break; if (time_after(jiffies, timeo)) { printk(KERN_ERR "Waiting for chip to be ready timed out. Status %llx\n", (long long)status); cfi_spin_unlock(chip->mutex); return -EIO; } cfi_spin_unlock(chip->mutex); cfi_udelay(1); cfi_spin_lock(chip->mutex); /* 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 (!(mode == FL_READY || mode == FL_POINT || (mode == FL_WRITING && (cfip->EraseSuspend & 0x2)) || (mode == FL_WRITING && (cfip->EraseSuspend & 0x1)))) goto sleep; /* Erase suspend */ /* FIXME - is there a way to verify suspend? */ cfi_write(map, CMD(0xB0), adr); chip->oldstate = FL_ERASING; chip->state = FL_ERASE_SUSPENDING; chip->erase_suspended = 1; for (;;) { oldstatus = cfi_read(map, adr); status = cfi_read(map, adr); if (((oldstatus ^ status) & dq2) == dq2) break; if (time_after(jiffies, timeo)) { /* Urgh. Resume and pretend we weren't here. */ /* FIXME - is there a way to verify resume? */ cfi_write(map, CMD(0x30), adr); chip->state = FL_ERASING; chip->oldstate = FL_READY; printk(KERN_ERR "Chip not ready after erase " "suspended: status = 0x%x\n", status); return -EIO; } cfi_spin_unlock(chip->mutex); cfi_udelay(1); cfi_spin_lock(chip->mutex); /* 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_POINT: /* Only if there's no operation suspended... */ if (mode == FL_READY && chip->oldstate == FL_READY) return 0; default: sleep: set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&chip->wq, &wait); cfi_spin_unlock(chip->mutex); schedule(); remove_wait_queue(&chip->wq, &wait); cfi_spin_lock(chip->mutex); goto resettime;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -