162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Common Flash Interface support: 462306a36Sopenharmony_ci * Generic utility functions not dependent on command set 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * Copyright (C) 2002 Red Hat 762306a36Sopenharmony_ci * Copyright (C) 2003 STMicroelectronics Limited 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#include <linux/module.h> 1162306a36Sopenharmony_ci#include <linux/types.h> 1262306a36Sopenharmony_ci#include <linux/kernel.h> 1362306a36Sopenharmony_ci#include <asm/io.h> 1462306a36Sopenharmony_ci#include <asm/byteorder.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include <linux/errno.h> 1762306a36Sopenharmony_ci#include <linux/slab.h> 1862306a36Sopenharmony_ci#include <linux/delay.h> 1962306a36Sopenharmony_ci#include <linux/interrupt.h> 2062306a36Sopenharmony_ci#include <linux/mtd/xip.h> 2162306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 2262306a36Sopenharmony_ci#include <linux/mtd/map.h> 2362306a36Sopenharmony_ci#include <linux/mtd/cfi.h> 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_civoid cfi_udelay(int us) 2662306a36Sopenharmony_ci{ 2762306a36Sopenharmony_ci if (us >= 1000) { 2862306a36Sopenharmony_ci msleep(DIV_ROUND_UP(us, 1000)); 2962306a36Sopenharmony_ci } else { 3062306a36Sopenharmony_ci udelay(us); 3162306a36Sopenharmony_ci cond_resched(); 3262306a36Sopenharmony_ci } 3362306a36Sopenharmony_ci} 3462306a36Sopenharmony_ciEXPORT_SYMBOL(cfi_udelay); 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci/* 3762306a36Sopenharmony_ci * Returns the command address according to the given geometry. 3862306a36Sopenharmony_ci */ 3962306a36Sopenharmony_ciuint32_t cfi_build_cmd_addr(uint32_t cmd_ofs, 4062306a36Sopenharmony_ci struct map_info *map, struct cfi_private *cfi) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci unsigned bankwidth = map_bankwidth(map); 4362306a36Sopenharmony_ci unsigned interleave = cfi_interleave(cfi); 4462306a36Sopenharmony_ci unsigned type = cfi->device_type; 4562306a36Sopenharmony_ci uint32_t addr; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci addr = (cmd_ofs * type) * interleave; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci /* Modify the unlock address if we are in compatibility mode. 5062306a36Sopenharmony_ci * For 16bit devices on 8 bit busses 5162306a36Sopenharmony_ci * and 32bit devices on 16 bit busses 5262306a36Sopenharmony_ci * set the low bit of the alternating bit sequence of the address. 5362306a36Sopenharmony_ci */ 5462306a36Sopenharmony_ci if (((type * interleave) > bankwidth) && ((cmd_ofs & 0xff) == 0xaa)) 5562306a36Sopenharmony_ci addr |= (type >> 1)*interleave; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci return addr; 5862306a36Sopenharmony_ci} 5962306a36Sopenharmony_ciEXPORT_SYMBOL(cfi_build_cmd_addr); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci/* 6262306a36Sopenharmony_ci * Transforms the CFI command for the given geometry (bus width & interleave). 6362306a36Sopenharmony_ci * It looks too long to be inline, but in the common case it should almost all 6462306a36Sopenharmony_ci * get optimised away. 6562306a36Sopenharmony_ci */ 6662306a36Sopenharmony_cimap_word cfi_build_cmd(u_long cmd, struct map_info *map, struct cfi_private *cfi) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci map_word val = { {0} }; 6962306a36Sopenharmony_ci int wordwidth, words_per_bus, chip_mode, chips_per_word; 7062306a36Sopenharmony_ci unsigned long onecmd; 7162306a36Sopenharmony_ci int i; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci /* We do it this way to give the compiler a fighting chance 7462306a36Sopenharmony_ci of optimising away all the crap for 'bankwidth' larger than 7562306a36Sopenharmony_ci an unsigned long, in the common case where that support is 7662306a36Sopenharmony_ci disabled */ 7762306a36Sopenharmony_ci if (map_bankwidth_is_large(map)) { 7862306a36Sopenharmony_ci wordwidth = sizeof(unsigned long); 7962306a36Sopenharmony_ci words_per_bus = (map_bankwidth(map)) / wordwidth; // i.e. normally 1 8062306a36Sopenharmony_ci } else { 8162306a36Sopenharmony_ci wordwidth = map_bankwidth(map); 8262306a36Sopenharmony_ci words_per_bus = 1; 8362306a36Sopenharmony_ci } 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci chip_mode = map_bankwidth(map) / cfi_interleave(cfi); 8662306a36Sopenharmony_ci chips_per_word = wordwidth * cfi_interleave(cfi) / map_bankwidth(map); 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci /* First, determine what the bit-pattern should be for a single 8962306a36Sopenharmony_ci device, according to chip mode and endianness... */ 9062306a36Sopenharmony_ci switch (chip_mode) { 9162306a36Sopenharmony_ci default: BUG(); 9262306a36Sopenharmony_ci case 1: 9362306a36Sopenharmony_ci onecmd = cmd; 9462306a36Sopenharmony_ci break; 9562306a36Sopenharmony_ci case 2: 9662306a36Sopenharmony_ci onecmd = cpu_to_cfi16(map, cmd); 9762306a36Sopenharmony_ci break; 9862306a36Sopenharmony_ci case 4: 9962306a36Sopenharmony_ci onecmd = cpu_to_cfi32(map, cmd); 10062306a36Sopenharmony_ci break; 10162306a36Sopenharmony_ci } 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci /* Now replicate it across the size of an unsigned long, or 10462306a36Sopenharmony_ci just to the bus width as appropriate */ 10562306a36Sopenharmony_ci switch (chips_per_word) { 10662306a36Sopenharmony_ci default: BUG(); 10762306a36Sopenharmony_ci#if BITS_PER_LONG >= 64 10862306a36Sopenharmony_ci case 8: 10962306a36Sopenharmony_ci onecmd |= (onecmd << (chip_mode * 32)); 11062306a36Sopenharmony_ci fallthrough; 11162306a36Sopenharmony_ci#endif 11262306a36Sopenharmony_ci case 4: 11362306a36Sopenharmony_ci onecmd |= (onecmd << (chip_mode * 16)); 11462306a36Sopenharmony_ci fallthrough; 11562306a36Sopenharmony_ci case 2: 11662306a36Sopenharmony_ci onecmd |= (onecmd << (chip_mode * 8)); 11762306a36Sopenharmony_ci fallthrough; 11862306a36Sopenharmony_ci case 1: 11962306a36Sopenharmony_ci ; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci /* And finally, for the multi-word case, replicate it 12362306a36Sopenharmony_ci in all words in the structure */ 12462306a36Sopenharmony_ci for (i=0; i < words_per_bus; i++) { 12562306a36Sopenharmony_ci val.x[i] = onecmd; 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci return val; 12962306a36Sopenharmony_ci} 13062306a36Sopenharmony_ciEXPORT_SYMBOL(cfi_build_cmd); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ciunsigned long cfi_merge_status(map_word val, struct map_info *map, 13362306a36Sopenharmony_ci struct cfi_private *cfi) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci int wordwidth, words_per_bus, chip_mode, chips_per_word; 13662306a36Sopenharmony_ci unsigned long onestat, res = 0; 13762306a36Sopenharmony_ci int i; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci /* We do it this way to give the compiler a fighting chance 14062306a36Sopenharmony_ci of optimising away all the crap for 'bankwidth' larger than 14162306a36Sopenharmony_ci an unsigned long, in the common case where that support is 14262306a36Sopenharmony_ci disabled */ 14362306a36Sopenharmony_ci if (map_bankwidth_is_large(map)) { 14462306a36Sopenharmony_ci wordwidth = sizeof(unsigned long); 14562306a36Sopenharmony_ci words_per_bus = (map_bankwidth(map)) / wordwidth; // i.e. normally 1 14662306a36Sopenharmony_ci } else { 14762306a36Sopenharmony_ci wordwidth = map_bankwidth(map); 14862306a36Sopenharmony_ci words_per_bus = 1; 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci chip_mode = map_bankwidth(map) / cfi_interleave(cfi); 15262306a36Sopenharmony_ci chips_per_word = wordwidth * cfi_interleave(cfi) / map_bankwidth(map); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci onestat = val.x[0]; 15562306a36Sopenharmony_ci /* Or all status words together */ 15662306a36Sopenharmony_ci for (i=1; i < words_per_bus; i++) { 15762306a36Sopenharmony_ci onestat |= val.x[i]; 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci res = onestat; 16162306a36Sopenharmony_ci switch(chips_per_word) { 16262306a36Sopenharmony_ci default: BUG(); 16362306a36Sopenharmony_ci#if BITS_PER_LONG >= 64 16462306a36Sopenharmony_ci case 8: 16562306a36Sopenharmony_ci res |= (onestat >> (chip_mode * 32)); 16662306a36Sopenharmony_ci fallthrough; 16762306a36Sopenharmony_ci#endif 16862306a36Sopenharmony_ci case 4: 16962306a36Sopenharmony_ci res |= (onestat >> (chip_mode * 16)); 17062306a36Sopenharmony_ci fallthrough; 17162306a36Sopenharmony_ci case 2: 17262306a36Sopenharmony_ci res |= (onestat >> (chip_mode * 8)); 17362306a36Sopenharmony_ci fallthrough; 17462306a36Sopenharmony_ci case 1: 17562306a36Sopenharmony_ci ; 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci /* Last, determine what the bit-pattern should be for a single 17962306a36Sopenharmony_ci device, according to chip mode and endianness... */ 18062306a36Sopenharmony_ci switch (chip_mode) { 18162306a36Sopenharmony_ci case 1: 18262306a36Sopenharmony_ci break; 18362306a36Sopenharmony_ci case 2: 18462306a36Sopenharmony_ci res = cfi16_to_cpu(map, res); 18562306a36Sopenharmony_ci break; 18662306a36Sopenharmony_ci case 4: 18762306a36Sopenharmony_ci res = cfi32_to_cpu(map, res); 18862306a36Sopenharmony_ci break; 18962306a36Sopenharmony_ci default: BUG(); 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci return res; 19262306a36Sopenharmony_ci} 19362306a36Sopenharmony_ciEXPORT_SYMBOL(cfi_merge_status); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci/* 19662306a36Sopenharmony_ci * Sends a CFI command to a bank of flash for the given geometry. 19762306a36Sopenharmony_ci * 19862306a36Sopenharmony_ci * Returns the offset in flash where the command was written. 19962306a36Sopenharmony_ci * If prev_val is non-null, it will be set to the value at the command address, 20062306a36Sopenharmony_ci * before the command was written. 20162306a36Sopenharmony_ci */ 20262306a36Sopenharmony_ciuint32_t cfi_send_gen_cmd(u_char cmd, uint32_t cmd_addr, uint32_t base, 20362306a36Sopenharmony_ci struct map_info *map, struct cfi_private *cfi, 20462306a36Sopenharmony_ci int type, map_word *prev_val) 20562306a36Sopenharmony_ci{ 20662306a36Sopenharmony_ci map_word val; 20762306a36Sopenharmony_ci uint32_t addr = base + cfi_build_cmd_addr(cmd_addr, map, cfi); 20862306a36Sopenharmony_ci val = cfi_build_cmd(cmd, map, cfi); 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci if (prev_val) 21162306a36Sopenharmony_ci *prev_val = map_read(map, addr); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci map_write(map, val, addr); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci return addr - base; 21662306a36Sopenharmony_ci} 21762306a36Sopenharmony_ciEXPORT_SYMBOL(cfi_send_gen_cmd); 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ciint __xipram cfi_qry_present(struct map_info *map, __u32 base, 22062306a36Sopenharmony_ci struct cfi_private *cfi) 22162306a36Sopenharmony_ci{ 22262306a36Sopenharmony_ci int osf = cfi->interleave * cfi->device_type; /* scale factor */ 22362306a36Sopenharmony_ci map_word val[3]; 22462306a36Sopenharmony_ci map_word qry[3]; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci qry[0] = cfi_build_cmd('Q', map, cfi); 22762306a36Sopenharmony_ci qry[1] = cfi_build_cmd('R', map, cfi); 22862306a36Sopenharmony_ci qry[2] = cfi_build_cmd('Y', map, cfi); 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci val[0] = map_read(map, base + osf*0x10); 23162306a36Sopenharmony_ci val[1] = map_read(map, base + osf*0x11); 23262306a36Sopenharmony_ci val[2] = map_read(map, base + osf*0x12); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci if (!map_word_equal(map, qry[0], val[0])) 23562306a36Sopenharmony_ci return 0; 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci if (!map_word_equal(map, qry[1], val[1])) 23862306a36Sopenharmony_ci return 0; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci if (!map_word_equal(map, qry[2], val[2])) 24162306a36Sopenharmony_ci return 0; 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci return 1; /* "QRY" found */ 24462306a36Sopenharmony_ci} 24562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cfi_qry_present); 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ciint __xipram cfi_qry_mode_on(uint32_t base, struct map_info *map, 24862306a36Sopenharmony_ci struct cfi_private *cfi) 24962306a36Sopenharmony_ci{ 25062306a36Sopenharmony_ci cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); 25162306a36Sopenharmony_ci cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); 25262306a36Sopenharmony_ci if (cfi_qry_present(map, base, cfi)) 25362306a36Sopenharmony_ci return 1; 25462306a36Sopenharmony_ci /* QRY not found probably we deal with some odd CFI chips */ 25562306a36Sopenharmony_ci /* Some revisions of some old Intel chips? */ 25662306a36Sopenharmony_ci cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); 25762306a36Sopenharmony_ci cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); 25862306a36Sopenharmony_ci cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); 25962306a36Sopenharmony_ci if (cfi_qry_present(map, base, cfi)) 26062306a36Sopenharmony_ci return 1; 26162306a36Sopenharmony_ci /* ST M29DW chips */ 26262306a36Sopenharmony_ci cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); 26362306a36Sopenharmony_ci cfi_send_gen_cmd(0x98, 0x555, base, map, cfi, cfi->device_type, NULL); 26462306a36Sopenharmony_ci if (cfi_qry_present(map, base, cfi)) 26562306a36Sopenharmony_ci return 1; 26662306a36Sopenharmony_ci /* some old SST chips, e.g. 39VF160x/39VF320x */ 26762306a36Sopenharmony_ci cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); 26862306a36Sopenharmony_ci cfi_send_gen_cmd(0xAA, 0x5555, base, map, cfi, cfi->device_type, NULL); 26962306a36Sopenharmony_ci cfi_send_gen_cmd(0x55, 0x2AAA, base, map, cfi, cfi->device_type, NULL); 27062306a36Sopenharmony_ci cfi_send_gen_cmd(0x98, 0x5555, base, map, cfi, cfi->device_type, NULL); 27162306a36Sopenharmony_ci if (cfi_qry_present(map, base, cfi)) 27262306a36Sopenharmony_ci return 1; 27362306a36Sopenharmony_ci /* SST 39VF640xB */ 27462306a36Sopenharmony_ci cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); 27562306a36Sopenharmony_ci cfi_send_gen_cmd(0xAA, 0x555, base, map, cfi, cfi->device_type, NULL); 27662306a36Sopenharmony_ci cfi_send_gen_cmd(0x55, 0x2AA, base, map, cfi, cfi->device_type, NULL); 27762306a36Sopenharmony_ci cfi_send_gen_cmd(0x98, 0x555, base, map, cfi, cfi->device_type, NULL); 27862306a36Sopenharmony_ci if (cfi_qry_present(map, base, cfi)) 27962306a36Sopenharmony_ci return 1; 28062306a36Sopenharmony_ci /* QRY not found */ 28162306a36Sopenharmony_ci return 0; 28262306a36Sopenharmony_ci} 28362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cfi_qry_mode_on); 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_civoid __xipram cfi_qry_mode_off(uint32_t base, struct map_info *map, 28662306a36Sopenharmony_ci struct cfi_private *cfi) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); 28962306a36Sopenharmony_ci cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); 29062306a36Sopenharmony_ci /* M29W128G flashes require an additional reset command 29162306a36Sopenharmony_ci when exit qry mode */ 29262306a36Sopenharmony_ci if ((cfi->mfr == CFI_MFR_ST) && (cfi->id == 0x227E || cfi->id == 0x7E)) 29362306a36Sopenharmony_ci cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(cfi_qry_mode_off); 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_cistruct cfi_extquery * 29862306a36Sopenharmony_ci__xipram cfi_read_pri(struct map_info *map, __u16 adr, __u16 size, const char* name) 29962306a36Sopenharmony_ci{ 30062306a36Sopenharmony_ci struct cfi_private *cfi = map->fldrv_priv; 30162306a36Sopenharmony_ci __u32 base = 0; // cfi->chips[0].start; 30262306a36Sopenharmony_ci int ofs_factor = cfi->interleave * cfi->device_type; 30362306a36Sopenharmony_ci int i; 30462306a36Sopenharmony_ci struct cfi_extquery *extp = NULL; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci if (!adr) 30762306a36Sopenharmony_ci goto out; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci printk(KERN_INFO "%s Extended Query Table at 0x%4.4X\n", name, adr); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci extp = kmalloc(size, GFP_KERNEL); 31262306a36Sopenharmony_ci if (!extp) 31362306a36Sopenharmony_ci goto out; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci#ifdef CONFIG_MTD_XIP 31662306a36Sopenharmony_ci local_irq_disable(); 31762306a36Sopenharmony_ci#endif 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci /* Switch it into Query Mode */ 32062306a36Sopenharmony_ci cfi_qry_mode_on(base, map, cfi); 32162306a36Sopenharmony_ci /* Read in the Extended Query Table */ 32262306a36Sopenharmony_ci for (i=0; i<size; i++) { 32362306a36Sopenharmony_ci ((unsigned char *)extp)[i] = 32462306a36Sopenharmony_ci cfi_read_query(map, base+((adr+i)*ofs_factor)); 32562306a36Sopenharmony_ci } 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci /* Make sure it returns to read mode */ 32862306a36Sopenharmony_ci cfi_qry_mode_off(base, map, cfi); 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci#ifdef CONFIG_MTD_XIP 33162306a36Sopenharmony_ci (void) map_read(map, base); 33262306a36Sopenharmony_ci xip_iprefetch(); 33362306a36Sopenharmony_ci local_irq_enable(); 33462306a36Sopenharmony_ci#endif 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci out: return extp; 33762306a36Sopenharmony_ci} 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ciEXPORT_SYMBOL(cfi_read_pri); 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_civoid cfi_fixup(struct mtd_info *mtd, struct cfi_fixup *fixups) 34262306a36Sopenharmony_ci{ 34362306a36Sopenharmony_ci struct map_info *map = mtd->priv; 34462306a36Sopenharmony_ci struct cfi_private *cfi = map->fldrv_priv; 34562306a36Sopenharmony_ci struct cfi_fixup *f; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci for (f=fixups; f->fixup; f++) { 34862306a36Sopenharmony_ci if (((f->mfr == CFI_MFR_ANY) || (f->mfr == cfi->mfr)) && 34962306a36Sopenharmony_ci ((f->id == CFI_ID_ANY) || (f->id == cfi->id))) { 35062306a36Sopenharmony_ci f->fixup(mtd); 35162306a36Sopenharmony_ci } 35262306a36Sopenharmony_ci } 35362306a36Sopenharmony_ci} 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ciEXPORT_SYMBOL(cfi_fixup); 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ciint cfi_varsize_frob(struct mtd_info *mtd, varsize_frob_t frob, 35862306a36Sopenharmony_ci loff_t ofs, size_t len, void *thunk) 35962306a36Sopenharmony_ci{ 36062306a36Sopenharmony_ci struct map_info *map = mtd->priv; 36162306a36Sopenharmony_ci struct cfi_private *cfi = map->fldrv_priv; 36262306a36Sopenharmony_ci unsigned long adr; 36362306a36Sopenharmony_ci int chipnum, ret = 0; 36462306a36Sopenharmony_ci int i, first; 36562306a36Sopenharmony_ci struct mtd_erase_region_info *regions = mtd->eraseregions; 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci /* Check that both start and end of the requested erase are 36862306a36Sopenharmony_ci * aligned with the erasesize at the appropriate addresses. 36962306a36Sopenharmony_ci */ 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci i = 0; 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci /* Skip all erase regions which are ended before the start of 37462306a36Sopenharmony_ci the requested erase. Actually, to save on the calculations, 37562306a36Sopenharmony_ci we skip to the first erase region which starts after the 37662306a36Sopenharmony_ci start of the requested erase, and then go back one. 37762306a36Sopenharmony_ci */ 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci while (i < mtd->numeraseregions && ofs >= regions[i].offset) 38062306a36Sopenharmony_ci i++; 38162306a36Sopenharmony_ci i--; 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci /* OK, now i is pointing at the erase region in which this 38462306a36Sopenharmony_ci erase request starts. Check the start of the requested 38562306a36Sopenharmony_ci erase range is aligned with the erase size which is in 38662306a36Sopenharmony_ci effect here. 38762306a36Sopenharmony_ci */ 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_ci if (ofs & (regions[i].erasesize-1)) 39062306a36Sopenharmony_ci return -EINVAL; 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci /* Remember the erase region we start on */ 39362306a36Sopenharmony_ci first = i; 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci /* Next, check that the end of the requested erase is aligned 39662306a36Sopenharmony_ci * with the erase region at that address. 39762306a36Sopenharmony_ci */ 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci while (i<mtd->numeraseregions && (ofs + len) >= regions[i].offset) 40062306a36Sopenharmony_ci i++; 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci /* As before, drop back one to point at the region in which 40362306a36Sopenharmony_ci the address actually falls 40462306a36Sopenharmony_ci */ 40562306a36Sopenharmony_ci i--; 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci if ((ofs + len) & (regions[i].erasesize-1)) 40862306a36Sopenharmony_ci return -EINVAL; 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci chipnum = ofs >> cfi->chipshift; 41162306a36Sopenharmony_ci adr = ofs - (chipnum << cfi->chipshift); 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci i=first; 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_ci while(len) { 41662306a36Sopenharmony_ci int size = regions[i].erasesize; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci ret = (*frob)(map, &cfi->chips[chipnum], adr, size, thunk); 41962306a36Sopenharmony_ci 42062306a36Sopenharmony_ci if (ret) 42162306a36Sopenharmony_ci return ret; 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci adr += size; 42462306a36Sopenharmony_ci ofs += size; 42562306a36Sopenharmony_ci len -= size; 42662306a36Sopenharmony_ci 42762306a36Sopenharmony_ci if (ofs == regions[i].offset + size * regions[i].numblocks) 42862306a36Sopenharmony_ci i++; 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci if (adr >> cfi->chipshift) { 43162306a36Sopenharmony_ci adr = 0; 43262306a36Sopenharmony_ci chipnum++; 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci if (chipnum >= cfi->numchips) 43562306a36Sopenharmony_ci break; 43662306a36Sopenharmony_ci } 43762306a36Sopenharmony_ci } 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci return 0; 44062306a36Sopenharmony_ci} 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ciEXPORT_SYMBOL(cfi_varsize_frob); 44362306a36Sopenharmony_ci 44462306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 445