linux/drivers/mtd/chips/cfi_cmdset_0001.c

// SPDX-License-Identifier: GPL-2.0
/*
 * Common Flash Interface support:
 *   Intel Extended Vendor Command Set (ID 0x0001)
 *
 * (C) 2000 Red Hat.
 *
 *
 * 10/10/2000	Nicolas Pitre <[email protected]>
 * 	- completely revamped method functions so they are aware and
 * 	  independent of the flash geometry (buswidth, interleave, etc.)
 * 	- scalability vs code size is completely set at compile-time
 * 	  (see include/linux/mtd/cfi.h for selection)
 *	- optimized write buffer method
 * 02/05/2002	Christopher Hoover <[email protected]>/<[email protected]>
 *	- reworked lock/unlock/erase support for var size flash
 * 21/03/2007   Rodolfo Giometti <[email protected]>
 * 	- auto unlock sectors on resume for auto locking flash on power up
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.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/reboot.h>
#include <linux/bitmap.h>
#include <linux/mtd/xip.h>
#include <linux/mtd/map.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/cfi.h>

/* #define CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE */
/* #define CMDSET0001_DISABLE_WRITE_SUSPEND */

// debugging, turns off buffer write mode if set to 1
#define FORCE_WORD_WRITE

/* Intel chips */
#define I82802AB
#define I82802AC
#define PF38F4476
#define M28F00AP30
/* STMicroelectronics chips */
#define M50LPW080
#define M50FLW080A
#define M50FLW080B
/* Atmel chips */
#define AT49BV640D
#define AT49BV640DT
/* Sharp chips */
#define LH28F640BFHE_PTTL90
#define LH28F640BFHE_PBTL90
#define LH28F640BFHE_PTTL70A
#define LH28F640BFHE_PBTL70A

static int cfi_intelext_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
static int cfi_intelext_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
static int cfi_intelext_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
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, uint64_t len);
static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
static int cfi_intelext_is_locked(struct mtd_info *mtd, loff_t ofs,
				  uint64_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 *, const 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 *, size_t,
					   size_t *, struct otp_info *);
static int cfi_intelext_get_user_prot_info(struct mtd_info *, size_t,
					   size_t *, struct otp_info *);
#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 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, void **virt, resource_size_t *phys);
static int cfi_intelext_unpoint(struct mtd_info *mtd, loff_t from, size_t len);

static int chip_ready (struct map_info *map, struct flchip *chip, unsigned long adr, int mode);
static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode);
static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr);
#include "fwh_lock.h"



/*
 *  *********** SETUP AND PROBE BITS  ***********
 */

static struct mtd_chip_driver cfi_intelext_chipdrv =;

/* #define DEBUG_LOCK_BITS */
/* #define DEBUG_CFI_FEATURES */

#ifdef DEBUG_CFI_FEATURES
static void cfi_tell_features(struct cfi_pri_intelext *extp)
{
	int i;
	printk("  Extended Query version %c.%c\n", extp->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<<i))
			printk("     - Unknown Bit %X:      supported\n", i);
	}

	printk("  Supported functions after Suspend: %2.2X\n", extp->SuspendCmdSupport);
	printk("     - Program after Erase Suspend: %s\n", extp->SuspendCmdSupport&1?"supported":"unsupported");
	for (i=1; i<8; i++) {
		if (extp->SuspendCmdSupport & (1<<i))
			printk("     - Unknown Bit %X:               supported\n", i);
	}

	printk("  Block Status Register Mask: %4.4X\n", extp->BlkStatusRegMask);
	printk("     - Lock Bit Active:      %s\n", extp->BlkStatusRegMask&1?"yes":"no");
	printk("     - Lock-Down Bit Active: %s\n", extp->BlkStatusRegMask&2?"yes":"no");
	for (i=2; i<3; i++) {
		if (extp->BlkStatusRegMask & (1<<i))
			printk("     - Unknown Bit %X Active: yes\n",i);
	}
	printk("     - EFA Lock Bit:         %s\n", extp->BlkStatusRegMask&16?"yes":"no");
	printk("     - EFA Lock-Down Bit:    %s\n", extp->BlkStatusRegMask&32?"yes":"no");
	for (i=6; i<16; i++) {
		if (extp->BlkStatusRegMask & (1<<i))
			printk("     - Unknown Bit %X Active: yes\n",i);
	}

	printk("  Vcc Logic Supply Optimum Program/Erase Voltage: %d.%d V\n",
	       extp->VccOptimal >> 4, extp->VccOptimal & 0xf);
	if (extp->VppOptimal)
		printk("  Vpp Programming Supply Optimum Program/Erase Voltage: %d.%d V\n",
		       extp->VppOptimal >> 4, extp->VppOptimal & 0xf);
}
#endif

/* Atmel chips don't use the same PRI format as Intel chips */
static void fixup_convert_atmel_pri(struct mtd_info *mtd)
{}

static void fixup_at49bv640dx_lock(struct mtd_info *mtd)
{}

#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)
{
	struct map_info *map = mtd->priv;
	struct cfi_private *cfi = map->fldrv_priv;
	struct cfi_pri_intelext *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)
{
	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)
{}

static void fixup_st_m28w320cb(struct mtd_info *mtd)
{
	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 int is_LH28F640BF(struct cfi_private *cfi)
{}

static void fixup_LH28F640BF(struct mtd_info *mtd)
{}

static void fixup_use_point(struct mtd_info *mtd)
{}

static void fixup_use_write_buffers(struct mtd_info *mtd)
{}

/*
 * Some chips power-up with all sectors locked by default.
 */
static void fixup_unlock_powerup_lock(struct mtd_info *mtd)
{}

static struct cfi_fixup cfi_fixup_table[] =;

static struct cfi_fixup jedec_fixup_table[] =;
static struct cfi_fixup fixup_table[] =;

static void cfi_fixup_major_minor(struct cfi_private *cfi,
						struct cfi_pri_intelext *extp)
{}

static int cfi_is_micron_28F00AP30(struct cfi_private *cfi, struct flchip *chip)
{}

static inline struct cfi_pri_intelext *
read_pri_intelext(struct map_info *map, __u16 adr)
{}

struct mtd_info *cfi_cmdset_0001(struct map_info *map, int primary)
{}
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();
EXPORT_SYMBOL_GPL();
EXPORT_SYMBOL_GPL();

static struct mtd_info *cfi_intelext_setup(struct mtd_info *mtd)
{}

static int cfi_intelext_partition_fixup(struct mtd_info *mtd,
					struct cfi_private **pcfi)
{}

/*
 *  *********** CHIP ACCESS FUNCTIONS ***********
 */
static int chip_ready (struct map_info *map, struct flchip *chip, unsigned long adr, int mode)
{}

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)
{}

#ifdef CONFIG_MTD_XIP

/*
 * No interrupt what so ever can be serviced while the flash isn't in array
 * mode.  This is ensured by the xip_disable() and xip_enable() functions
 * enclosing any code path where the flash is known not to be in array mode.
 * And within a XIP disabled code path, only functions marked with __xipram
 * may be called and nothing else (it's a good thing to inspect generated
 * assembly to make sure inline functions were actually inlined and that gcc
 * didn't emit calls to its own support functions). Also configuring MTD CFI
 * support to a single buswidth and a single interleave is also recommended.
 */

static void xip_disable(struct map_info *map, struct flchip *chip,
			unsigned long adr)
{
	/* TODO: chips with no XIP use should ignore and return */
	(void) map_read(map, adr); /* ensure mmu mapping is up to date */
	local_irq_disable();
}

static void __xipram xip_enable(struct map_info *map, struct flchip *chip,
				unsigned long adr)
{
	struct cfi_private *cfi = map->fldrv_priv;
	if (chip->state != FL_POINT && chip->state != FL_READY) {
		map_write(map, CMD(0xff), adr);
		chip->state = FL_READY;
	}
	(void) map_read(map, adr);
	xip_iprefetch();
	local_irq_enable();
}

/*
 * 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.
 */

static int __xipram xip_wait_for_operation(
		struct map_info *map, struct flchip *chip,
		unsigned long adr, unsigned int chip_op_time_max)
{
	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_max;
	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);
			xip_iprefetch();
			local_irq_enable();
			mutex_unlock(&chip->mutex);
			xip_iprefetch();
			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.
			 */
			mutex_lock(&chip->mutex);
			while (chip->state != newstate) {
				DECLARE_WAITQUEUE(wait, current);
				set_current_state(TASK_UNINTERRUPTIBLE);
				add_wait_queue(&chip->wq, &wait);
				mutex_unlock(&chip->mutex);
				schedule();
				remove_wait_queue(&chip->wq, &wait);
				mutex_lock(&chip->mutex);
			}
			/* 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);

	return (done >= usec) ? -ETIME : 0;
}

/*
 * 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

#define INVAL_CACHE_AND_WAIT

#else

#define xip_disable(map, chip, adr)
#define xip_enable(map, chip, adr)
#define XIP_INVAL_CACHED_RANGE(x...)
#define INVAL_CACHE_AND_WAIT

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, unsigned int chip_op_time_max)
{}

#endif

#define WAIT_TIMEOUT(map, chip, adr, udelay, udelay_max)


static int do_point_onechip (struct map_info *map, struct flchip *chip, loff_t adr, size_t len)
{}

static int cfi_intelext_point(struct mtd_info *mtd, loff_t from, size_t len,
		size_t *retlen, void **virt, resource_size_t *phys)
{}

static int cfi_intelext_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
{}

static inline int do_read_onechip(struct map_info *map, struct flchip *chip, loff_t adr, size_t len, u_char *buf)
{}

static int cfi_intelext_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf)
{}

static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
				     unsigned long adr, map_word datum, int mode)
{}


static int cfi_intelext_write_words (struct mtd_info *mtd, loff_t to , size_t len, size_t *retlen, const u_char *buf)
{}


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)
{}

static int cfi_intelext_writev (struct mtd_info *mtd, const struct kvec *vecs,
				unsigned long count, loff_t to, size_t *retlen)
{}

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 __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip,
				      unsigned long adr, int len, void *thunk)
{}

static int cfi_intelext_erase_varsize(struct mtd_info *mtd, struct erase_info *instr)
{}

static void cfi_intelext_sync (struct mtd_info *mtd)
{}

static int __xipram do_getlockstatus_oneblock(struct map_info *map,
						struct flchip *chip,
						unsigned long adr,
						int len, void *thunk)
{}

#ifdef DEBUG_LOCK_BITS
static int __xipram do_printlockstatus_oneblock(struct map_info *map,
						struct flchip *chip,
						unsigned long adr,
						int len, void *thunk)
{
	printk(KERN_DEBUG "block status register for 0x%08lx is %x\n",
	       adr, do_getlockstatus_oneblock(map, chip, adr, len, thunk));
	return 0;
}
#endif

#define DO_XXLOCK_ONEBLOCK_LOCK
#define DO_XXLOCK_ONEBLOCK_UNLOCK

static int __xipram do_xxlock_oneblock(struct map_info *map, struct flchip *chip,
				       unsigned long adr, int len, void *thunk)
{}

static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
{}

static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
{}

static int cfi_intelext_is_locked(struct mtd_info *mtd, loff_t ofs,
				  uint64_t len)
{}

#ifdef CONFIG_MTD_OTP

otp_op_t;

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)
{}

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)
{}

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)
{}

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)
{}

static int cfi_intelext_read_fact_prot_reg(struct mtd_info *mtd, loff_t from,
					   size_t len, size_t *retlen,
					    u_char *buf)
{}

static int cfi_intelext_read_user_prot_reg(struct mtd_info *mtd, loff_t from,
					   size_t len, size_t *retlen,
					    u_char *buf)
{}

static int cfi_intelext_write_user_prot_reg(struct mtd_info *mtd, loff_t from,
					    size_t len, size_t *retlen,
					    const u_char *buf)
{}

static int cfi_intelext_lock_user_prot_reg(struct mtd_info *mtd,
					   loff_t from, size_t len)
{}

static int cfi_intelext_get_fact_prot_info(struct mtd_info *mtd, size_t len,
					   size_t *retlen, struct otp_info *buf)

{}

static int cfi_intelext_get_user_prot_info(struct mtd_info *mtd, size_t len,
					   size_t *retlen, struct otp_info *buf)
{}

#endif

static void cfi_intelext_save_locks(struct mtd_info *mtd)
{}

static int cfi_intelext_suspend(struct mtd_info *mtd)
{}

static void cfi_intelext_restore_locks(struct mtd_info *mtd)
{}

static void cfi_intelext_resume(struct mtd_info *mtd)
{}

static int cfi_intelext_reset(struct mtd_info *mtd)
{}

static int cfi_intelext_reboot(struct notifier_block *nb, unsigned long val,
			       void *v)
{}

static void cfi_intelext_destroy(struct mtd_info *mtd)
{}

MODULE_LICENSE();
MODULE_AUTHOR();
MODULE_DESCRIPTION();
MODULE_ALIAS();
MODULE_ALIAS();