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