lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1423161845-24282-3-git-send-email-jimtingkuo@gmail.com>
Date:	Fri,  6 Feb 2015 02:44:05 +0800
From:	Jim Kuo <jimtingkuo@...il.com>
To:	David Woodhouse <dwmw2@...radead.org>,
	Brian Norris <computersforpeace@...il.com>,
	Marek Vasut <marex@...x.de>,
	Huang Shijie <b32955@...escale.com>,
	Geert Uytterhoeven <geert+renesas@...der.be>,
	Rafał Miłecki <zajec5@...il.com>,
	Ben Hutchings <ben@...adent.org.uk>,
	Kuninori Morimoto <kuninori.morimoto.gx@...esas.com>
Cc:	linux-mtd@...ts.infradead.org, linux-kernel@...r.kernel.org,
	Jim Kuo <jimtingkuo@...il.com>
Subject: [PATCH 2/2] spi-nor: Add SFDP detect method

The following are the details of patch:
- Add read/write scurity register functions to support some commands.
- Add suspend/resume commands.
- Add sfdp option to Kconfig and *_detect_sfdp function to support
  some specific commands. I also rearranged the setting steps in
  func spi_nor_scan to fit the sfdp method.
- The sfdp related functions have tested with Macronix chips, so I
  also add some commands for Macronix. Such as protection region
  (security OTP) and three types of islocked/lock/unlcok functions.
- Add some definitions and data structures to spi-nor.h to support
  above modifications.
Thanks!

Signed-off-by: Jim Kuo <jimtingkuo@...il.com>
---
 drivers/mtd/spi-nor/Kconfig   |  11 +
 drivers/mtd/spi-nor/spi-nor.c | 730 +++++++++++++++++++++++++++++++++++++++---
 include/linux/mtd/spi-nor.h   | 201 ++++++++++++
 3 files changed, 899 insertions(+), 43 deletions(-)

diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index 64a4f0e..f40548b 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -21,6 +21,17 @@ config MTD_SPI_NOR_USE_4K_SECTORS
 	  Please note that some tools/drivers/filesystems may not work with
 	  4096 B erase size (e.g. UBIFS requires 15 KiB as a minimum).
 
+config MTD_SPI_NOR_SFDP
+	bool "Serial Flash Discoverable Parameters (SFDP) Probe"
+	default n
+	help
+	  SFDP includes the most detailed information of SPI NOR flash memory.
+	  It can be used to detect flash and get devices' parameters.
+
+	  Attention that there are many flash flags can not covered by SFDP such
+	  as SST_WRITE, USE_FSR, SPI_NOR_NO_FR and SECT_4K_PMC. Please do not
+	  enable this option if your device is highly related with these flags.
+
 config SPI_FSL_QUADSPI
 	tristate "Freescale Quad SPI controller"
 	depends on ARCH_MXC
diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c
index a6c7337..2340ead 100644
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -119,6 +119,25 @@ static int read_cr(struct spi_nor *nor)
 }
 
 /*
+ * Read the security register, returning its value in the location
+ * Return the security register value.
+ * Returns negative if error occurred.
+ */
+static int read_scur(struct spi_nor *nor)
+{
+	int ret;
+	u8 val;
+
+	ret = nor->read_reg(nor, SPINOR_OP_RDSCUR, &val, 1);
+	if (ret < 0) {
+		pr_err("error %d reading SCUR\n", (int) ret);
+		return ret;
+	}
+
+	return val;
+}
+
+/*
  * Dummy Cycle calculation for different type of read.
  * It can be used to support more commands with
  * different dummy cycle requirements.
@@ -147,6 +166,15 @@ static inline int write_sr(struct spi_nor *nor, u8 val)
 }
 
 /*
+ * Set LDSO bit with Write Security Register command.
+ * Returns negative if error occurred.
+ */
+static inline int write_scur(struct spi_nor *nor)
+{
+	return nor->write_reg(nor, SPINOR_OP_WRSCUR, NULL, 0, 0);
+}
+
+/*
  * Set write enable latch with Write Enable command.
  * Returns negative if error occurred.
  */
@@ -802,19 +830,12 @@ time_out:
  * FLASH_PAGESIZE chunks.  The address range may be any size provided
  * it is within the physical boundaries.
  */
-static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
-	size_t *retlen, const u_char *buf)
+static int spi_nor_do_write(struct spi_nor *nor, loff_t to, size_t len,
+			size_t *retlen, const u_char *buf)
 {
-	struct spi_nor *nor = mtd_to_spi_nor(mtd);
 	u32 page_offset, page_size, i;
 	int ret;
 
-	dev_dbg(nor->dev, "to 0x%08x, len %zd\n", (u32)to, len);
-
-	ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_WRITE);
-	if (ret)
-		return ret;
-
 	write_enable(nor);
 
 	page_offset = to & (nor->page_size - 1);
@@ -835,7 +856,7 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
 
 			ret = spi_nor_wait_till_ready(nor);
 			if (ret)
-				goto write_err;
+				return ret;
 
 			write_enable(nor);
 
@@ -843,12 +864,416 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
 		}
 	}
 
-	ret = spi_nor_wait_till_ready(nor);
-write_err:
+	return 0;
+}
+
+static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
+			size_t *retlen, const u_char *buf)
+{
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+	int ret;
+
+	dev_dbg(nor->dev, "to 0x%08x, len %zd\n", (u32)to, len);
+
+	ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_WRITE);
+	if (ret)
+		return ret;
+
+	ret = spi_nor_do_write(nor, to, len, retlen, buf);
+	if (!ret)
+		ret = spi_nor_wait_till_ready(nor);
+
 	spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE);
 	return ret;
 }
 
+/*
+ * Power suspend and resume.
+ */
+static int spi_nor_suspend(struct mtd_info *mtd)
+{
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+	return nor->write_reg(nor, SPINOR_OP_DP, NULL, 0, 0);
+}
+
+static void spi_nor_resume(struct mtd_info *mtd)
+{
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+	nor->write_reg(nor, SPINOR_OP_RDP, NULL, 0, 0);
+}
+
+/*
+ * Read/Write manufacture or user protection area (One Time Program).
+ * Enter security OTP mode before access, and exit after access.
+ * The OTP area can be locked to prevent any modification, and there
+ * is no way to unlock it.
+ */
+static int macronix_otp(struct mtd_info *mtd, loff_t addr, size_t len,
+		size_t *retlen, u_char *buf, enum spi_nor_ops ops)
+{
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+	int ret;
+
+	ret = spi_nor_lock_and_prep(nor, ops);
+	if (ret)
+		return ret;
+
+	/* enter security OTP */
+	ret = nor->write_reg(nor, SPINOR_OP_ENSO, NULL, 0, 0);
+	if (ret) {
+		spi_nor_unlock_and_unprep(nor, ops);
+		return ret;
+	}
+
+	if (ops == SPI_NOR_OPS_READ)
+		nor->read(nor, addr, len, retlen, buf);
+
+	else { /* ops == SPI_NOR_OPS_WRITE */
+
+		/* exit if OTP has been locked */
+		if (read_scur(nor) & 0x03) {
+			spi_nor_unlock_and_unprep(nor, ops);
+			return 0;
+		}
+		spi_nor_do_write(nor, addr, len, retlen, buf);
+	}
+
+	/* eixt security OTP */
+	nor->write_reg(nor, SPINOR_OP_EXSO, NULL, 0, 0);
+
+	spi_nor_unlock_and_unprep(nor, ops);
+	return 0;
+}
+
+static int macronix_get_fact_prot_info(struct mtd_info *mtd,
+	size_t len, size_t *retlen, struct otp_info *buf)
+{
+	/*
+	 * Factor area stores electronical serial number (ESN).
+	 * And the area can not be modified.
+	 */
+	buf->start  = 0;
+	buf->length = 0x10;
+	buf->locked = 1;
+	return 0;
+}
+
+static int macronix_read_fact_prot_reg(struct mtd_info *mtd,
+	loff_t from, size_t len, size_t *retlen, u_char *buf)
+{
+	if (from < 0 || from + len > 0x10)
+		return -EINVAL;
+	return macronix_otp(mtd, from, len, retlen, buf,
+				SPI_NOR_OPS_READ);
+}
+
+static int macronix_get_user_prot_info(struct mtd_info *mtd,
+	size_t len, size_t *retlen, struct otp_info *buf)
+{
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+	buf->start  = 0x10;
+	if (mtd->size > 0x800000) {
+		/* The OTP size are 0x200 if the device exceeds 8MiB. */
+		buf->length = 0x1f0;
+	} else {
+		buf->length = 0x30;
+	}
+	buf->locked = (read_scur(nor) & 0x03) ? 1 : 0;
+	return 0;
+}
+
+static int macronix_read_user_prot_reg(struct mtd_info *mtd,
+	loff_t from, size_t len, size_t *retlen, u_char *buf)
+{
+	/*
+	 * Macronix user and factor security area are the same.
+	 * If the device is larger than 8 Mbyte, it only support
+	 * factor area.
+	 */
+	if (from < 0x10 || from + len > 0x40 ||
+		(mtd->size > 0x800000 && from + len > 0x200)) {
+		return -EINVAL;
+	}
+	return macronix_otp(mtd, from, len, retlen, buf,
+				SPI_NOR_OPS_READ);
+}
+
+static int macronix_write_user_prot_reg(struct mtd_info *mtd,
+	loff_t to, size_t len, size_t *retlen, u_char *buf)
+{
+	if (to < 0x10 || to + len > 0x40 ||
+		(mtd->size > 0x800000 && to + len > 0x200)) {
+		return -EINVAL;
+	}
+	return macronix_otp(mtd, to, len, retlen, buf,
+				SPI_NOR_OPS_WRITE);
+}
+
+/*
+ * Write LDSO bit (Lock-Down Security OTP) to 1.
+ */
+static int macronix_lock_user_prot_reg(struct mtd_info *mtd,
+			loff_t from, size_t len)
+{
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+	if (len == 0)
+		return 0;
+
+	write_enable(nor);
+
+	return write_scur(nor);
+}
+
+/*
+ * Write protect selection command should be done before block lock.
+ */
+static int macronix_wpsel(struct spi_nor *nor)
+{
+	int retval;
+
+	/* read security register to check WPSEL status */
+	retval = read_scur(nor);
+	if (retval < 0)
+		return retval;
+	if ((u8)retval & 0x80)
+		return 0;
+
+	write_enable(nor);
+
+	return nor->write_reg(nor, SPINOR_OP_WPSEL, NULL, 0, 0);
+}
+
+static int macronix_gang_lock(struct mtd_info *mtd,
+			enum spi_nor_ops ops)
+{
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+
+	write_enable(nor);
+
+	if (ops == SPI_NOR_OPS_LOCK)
+		return nor->write_reg(nor, SPINOR_OP_GBLK, NULL, 0, 0);
+	else /* ops == SPI_NOR_OPS_UNLOCK */
+		return nor->write_reg(nor, SPINOR_OP_GBULK, NULL, 0, 0);
+}
+
+static int macronix_read_lock_status(struct mtd_info *mtd, loff_t ofs,
+			uint64_t len, enum macronix_lock_type type)
+{
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+	struct spi_nor_xfer_cfg cfg;
+	uint64_t end;
+	u32 move;
+	u8 status;
+	int ret;
+
+	/* do write protect selection first */
+	ret = macronix_wpsel(nor);
+	if (ret)
+		return ret;
+
+	ret = spi_nor_lock_and_prep(nor, SPI_NOR_OPS_READ);
+	if (ret)
+		return ret;
+
+	if (type == MX_SBLK) {
+		cfg.cmd = SPINOR_OP_RDBLOCK;
+		cfg.addr_pins = nor->addr_width;
+	} else {
+		if (type == MX_SPB)
+			cfg.cmd = SPINOR_OP_RDSPB;
+		else /* type == MX_DPB */
+			cfg.cmd = SPINOR_OP_RDDPB;
+		cfg.addr_pins = 4;
+	}
+
+	cfg.cmd_pins = 1;
+	cfg.mode_pins = 0;
+	cfg.dummy_cycles = 0;
+
+	end = ofs + len - 1;
+
+	for (; ofs <= end && ofs < mtd->size; ofs += move) {
+		/* align the sector/blok size */
+		if (ofs < 0x10000 || ofs >= mtd->size - 0x10000)
+			move = 0x1000;
+		else
+			move = 0x10000;
+
+		cfg.addr = ofs;
+		nor->read_xfer(nor, &cfg, &status, 1);
+
+		/* status 0xff: protected, return 1 */
+		if (status == 0xff) {
+			spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ);
+			return 1;
+		}
+	}
+
+	spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ);
+	return 0;
+}
+
+/*
+ * Macronix individual block lock. There are many types of
+ * block protection used on different flash memory.
+ * Single lock: volatile lock for E-series chips.
+ * Solid protection bit: non-volatile.
+ * Dynamic protection: volatile.
+ */
+static int macronix_block_lock(struct mtd_info *mtd, loff_t ofs,
+		uint64_t len, enum macronix_lock_type type,
+		enum spi_nor_ops ops)
+{
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+	struct spi_nor_xfer_cfg cfg;
+	uint64_t end;
+	u32 move;
+	u8 *val, valtmp;
+	size_t datalen;
+	int ret;
+
+	/* do write protect selection first */
+	ret = macronix_wpsel(nor);
+	if (ret)
+		return ret;
+
+	/* use gang command to do whole chip lock and unlock */
+	if (len == mtd->size && type != MX_SPB)
+		return macronix_gang_lock(mtd, ops);
+
+	ret = spi_nor_lock_and_prep(nor, ops);
+	if (ret)
+		return ret;
+
+	if (type == MX_DPB) {
+		cfg.cmd = SPINOR_OP_WRDPB;
+		cfg.addr_pins = 4;
+		valtmp = (ops == SPI_NOR_OPS_LOCK) ? 0xff : 0;
+		val = &valtmp;
+		datalen = 1;
+	} else {
+		if (type == MX_SBLK) {
+			cfg.cmd = (ops == SPI_NOR_OPS_LOCK) ?
+				SPINOR_OP_SBLK : SPINOR_OP_SBULK;
+			cfg.addr_pins = nor->addr_width;
+		} else { /* type == MX_SPB */
+			cfg.cmd = SPINOR_OP_WRSPB;
+			cfg.addr_pins = 4;
+		}
+		val = NULL;
+		datalen = 0;
+	}
+
+	cfg.cmd_pins = 1;
+	cfg.mode_pins = 0;
+	cfg.dummy_cycles = 0;
+	cfg.wren = 1;
+
+	/*
+	 * The lock regions of Macronix are uniform. There use
+	 * unit sector (4KB) in the first/last block, and unit
+	 * block (64KB) in middle space of chips.
+	 */
+
+	end = ofs + len - 1;
+
+	for (; ofs <= end && ofs < mtd->size; ofs += move) {
+		if (ofs < 0x10000 || ofs >= mtd->size - 0x10000)
+			move = 0x1000;
+		else
+			move = 0x10000;
+
+		cfg.addr = ofs;
+		nor->write_xfer(nor, &cfg, val, datalen);
+	}
+
+	spi_nor_unlock_and_unprep(nor, ops);
+	return 0;
+}
+
+/*
+ * Single block lock/unlock/islock command.
+ */
+static int macronix_single_lock(struct mtd_info *mtd, loff_t ofs,
+				uint64_t len)
+{
+	return macronix_block_lock(mtd, ofs, len, MX_SBLK,
+				SPI_NOR_OPS_LOCK);
+}
+
+static int macronix_single_unlock(struct mtd_info *mtd, loff_t ofs,
+				uint64_t len)
+{
+	return macronix_block_lock(mtd, ofs, len, MX_SBLK,
+				SPI_NOR_OPS_UNLOCK);
+}
+
+static int macronix_single_is_locked(struct mtd_info *mtd, loff_t ofs,
+				uint64_t len)
+{
+	return macronix_read_lock_status(mtd, ofs, len, MX_SBLK);
+}
+
+/*
+ * Solid protection bit lock/unlock/islock command.
+ */
+static int macronix_solid_lock(struct mtd_info *mtd, loff_t ofs,
+				uint64_t len)
+{
+	return macronix_block_lock(mtd, ofs, len, MX_SPB,
+				SPI_NOR_OPS_LOCK);
+}
+
+static int macronix_solid_unlock(struct mtd_info *mtd, loff_t ofs,
+				uint64_t len)
+{
+	struct spi_nor *nor = mtd_to_spi_nor(mtd);
+	int ret;
+
+	/* do write protect selection first */
+	ret = macronix_wpsel(nor);
+	if (ret)
+		return ret;
+
+	write_enable(nor);
+
+	/* erase solid bits will unlock all the blocks */
+	return nor->write_reg(nor, SPINOR_OP_ESSPB, NULL, 0, 0);
+}
+
+static int macronix_solid_is_locked(struct mtd_info *mtd, loff_t ofs,
+				uint64_t len)
+{
+	return macronix_read_lock_status(mtd, ofs, len, MX_SPB);
+}
+
+/*
+ * Dynamic protection bit lock/unlock/islock command.
+ */
+static int macronix_dynamic_lock(struct mtd_info *mtd, loff_t ofs,
+				uint64_t len)
+{
+	return macronix_block_lock(mtd, ofs, len, MX_DPB,
+				SPI_NOR_OPS_LOCK);
+}
+
+static int macronix_dynamic_unlock(struct mtd_info *mtd, loff_t ofs,
+				uint64_t len)
+{
+	return macronix_block_lock(mtd, ofs, len, MX_DPB,
+				SPI_NOR_OPS_UNLOCK);
+}
+
+static int macronix_dynamic_is_locked(struct mtd_info *mtd, loff_t ofs,
+				uint64_t len)
+{
+	return macronix_read_lock_status(mtd, ofs, len, MX_DPB);
+}
+
 static int macronix_quad_enable(struct spi_nor *nor)
 {
 	int ret, val;
@@ -931,6 +1356,202 @@ static int set_quad_mode(struct spi_nor *nor, struct flash_info *info)
 	}
 }
 
+/*
+ * Read SFDP ia an fundamental but important command for doing
+ * flash scan and probe.
+ */
+static int sfdp_read(struct spi_nor *nor, loff_t from, size_t len,
+		void *buf)
+{
+	struct spi_nor_xfer_cfg cfg;
+
+	cfg.cmd = SPINOR_OP_RDSFDP;
+	cfg.cmd_pins = 1;
+	cfg.addr = from;
+	cfg.addr_pins = 3;
+	cfg.mode_pins = 0;
+	cfg.dummy_cycles = 8;
+
+	return nor->read_xfer(nor, &cfg, (u8 *)buf, len);
+}
+
+/*
+ * Setup Macronix device with sfdp parameters.
+ */
+static void macronix_detect_sfdp(struct spi_nor *nor,
+			struct sfdp_param_header *facthdr)
+{
+	struct sfdp_macronix_params mxchip;
+	struct mtd_info *mtd = nor->mtd;
+	u32 ptr, len;
+
+	/* get macronix parameters */
+	ptr = facthdr->pointer[0] + (facthdr->pointer[1] << 8) +
+			(facthdr->pointer[2] << 16);
+	len = (facthdr->length * 4/*DWORDs*/ < sizeof(mxchip)) ?
+			(facthdr->length * 4) : sizeof(mxchip);
+	if (sfdp_read(nor, ptr, len, &mxchip))
+		return;
+
+	/* security OTP support */
+	if (mxchip.protection_param & 0x0800) {
+		mtd->_get_fact_prot_info  = macronix_get_fact_prot_info;
+		mtd->_read_fact_prot_reg  = macronix_read_fact_prot_reg;
+		mtd->_get_user_prot_info  = macronix_get_user_prot_info;
+		mtd->_read_user_prot_reg  = macronix_read_user_prot_reg;
+		mtd->_write_user_prot_reg = macronix_write_user_prot_reg;
+		mtd->_lock_user_prot_reg  = macronix_lock_user_prot_reg;
+	}
+
+	/* individual block lock support */
+	if (mxchip.protection_param & 0x0001) {
+
+		switch ((mxchip.protection_param >> 2) & 0xff) {
+		case SPINOR_OP_SBLK:
+			mtd->_lock      = macronix_single_lock;
+			mtd->_unlock    = macronix_single_unlock;
+			mtd->_is_locked = macronix_single_is_locked;
+			break;
+		case SPINOR_OP_WRSPB:
+			mtd->_lock      = macronix_solid_lock;
+			mtd->_unlock    = macronix_solid_unlock;
+			mtd->_is_locked = macronix_solid_is_locked;
+			break;
+		case SPINOR_OP_WRDPB:
+			mtd->_lock      = macronix_dynamic_lock;
+			mtd->_unlock    = macronix_dynamic_unlock;
+			mtd->_is_locked = macronix_dynamic_is_locked;
+			break;
+		default:
+			pr_err("Can not find Macronix protection parameters.\n");
+			return;
+		}
+
+		if (!(mxchip.protection_param & 0x0400))
+			mtd->_unlock(mtd, 0, mtd->size);
+	}
+}
+
+/*
+ * Check if the SPI NOR is SFDP compliant, returns 1 if it is, 0 otherwise.
+ * Directly update mtd information.
+ */
+static int spi_nor_detect_sfdp(struct spi_nor *nor, enum read_mode mode)
+{
+	struct spi_nor_sfdp_params sfdp;
+	struct sfdp_jedec_params jedec;
+	struct sfdp_param_header *jedechdr = NULL, *facthdr = NULL;
+	struct mtd_info *mtd = nor->mtd;
+	struct flash_info info;
+	u32 ptr, len;
+	int i, sect;
+
+	/* get JEDEC parameters */
+	sfdp_read(nor, 0, 4/*sizeof(sfdp.signature)*/, &sfdp.signature);
+	if (sfdp.signature != SFDP_SIGNATURE)
+		return 0;
+	if (sfdp_read(nor, 4, 4, &sfdp.minor_rev))
+		return 0;
+
+	if (sfdp.num_of_param_header < 1)
+		return 0;
+
+	if (sfdp_read(nor, 8,/*sizeof(sfdp.signature) + revision + header_num*/
+		sizeof(struct sfdp_param_header)*(sfdp.num_of_param_header+1),
+			sfdp.header)) {
+		return 0;
+	}
+
+	/*
+	 * Get JEDEC and manufacture parameter header.
+	 * Attention that num_of_param_header is zero base.
+	 */
+	for (i = 0; i <= sfdp.num_of_param_header; i++) {
+		if (sfdp.header[i].id == 0)
+			jedechdr = &sfdp.header[i];
+		else if (sfdp.header[i].id != 0xFF)
+			facthdr = &sfdp.header[i];
+		if (jedechdr && facthdr)
+			break;
+	}
+	if (!jedechdr || !facthdr || jedechdr->major_rev == 0)
+		return 0;
+
+	/* read JEDEC parameters */
+	ptr = jedechdr->pointer[0] + (jedechdr->pointer[1] << 8) +
+			(jedechdr->pointer[2] << 16);
+	len = (jedechdr->length * 4/*DWORDs*/ < sizeof(jedec)) ?
+		jedechdr->length * 4 : sizeof(jedec);
+	if (sfdp_read(nor, ptr, len, &jedec))
+		return 0;
+
+	mtd->writesize = (jedec.f_param & 0x04) ? 64 : 1;
+	mtd->size = jedec.flash_density + 1;
+
+	/* max number of sector type = 4 */
+#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
+	for (sect = 0; sect < 4; sect++)
+		if (jedec.e_type[sect].size_power == 12)
+			break;
+	if (sect == 4)
+#endif
+		/* use largest sector as erase unit */
+		for (sect = 0, i = 1; i < 4; i++) {
+			if (jedec.e_type[i].size_power >
+					jedec.e_type[sect].size_power)
+				sect = i;
+		}
+
+	nor->erase_opcode = jedec.e_type[sect].opcode;
+	mtd->erasesize = (1 << jedec.e_type[sect].size_power);
+	if (nor->erase_opcode == 0xff)
+		mtd->flags |= MTD_NO_ERASE;
+
+	if (mode == SPI_NOR_QUAD && jedec.fr_support & 0x20) {
+		if (set_quad_mode(nor, &info))
+			return 0;
+		nor->flash_read = SPI_NOR_QUAD;
+	} else if (mode == SPI_NOR_DUAL && jedec.fr_support & 0x01) {
+		nor->flash_read = SPI_NOR_DUAL;
+	}
+
+	nor->addr_width = ((jedec.fr_support >> 1) & 0x03) ? 4 : 3;
+
+	/*
+	 * Most of JEDEC 1.6 parameters are no use to mtd.
+	 * They are reserved in the struct 'spi_nor_sfdp_params'
+	 * for future usage.
+	 */
+
+	if (jedechdr->major_rev > 1 || jedechdr->minor_rev >= 6) {
+		nor->page_size = 1 << ((jedec.p_param >> 4) & 0x0F);
+		mtd->writebufsize = nor->page_size;
+	}
+
+	dev_info(nor->dev, "sfdp %u.%u compliant, jedec rev %u.%u\n",
+				sfdp.major_rev, sfdp.minor_rev,
+				jedechdr->major_rev, jedechdr->minor_rev);
+
+	/*
+	 * Get the manufacture defined parameters to mtd:
+	 * protection area, lock, unlock and islock function.
+	 */
+
+	switch (facthdr->id) {
+	case CFI_MFR_MACRONIX:
+		macronix_detect_sfdp(nor, facthdr);
+		break;
+
+	case CFI_MFR_ST:
+		/* nor protection support for STmicro chips */
+		mtd->_lock = spi_nor_lock;
+		mtd->_unlock = spi_nor_unlock;
+		break;
+	}
+
+	return 1;
+}
+
 static int spi_nor_check(struct spi_nor *nor)
 {
 	if (!nor->dev || !nor->read || !nor->write ||
@@ -1000,7 +1621,8 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 
 	if (JEDEC_MFR(info) == CFI_MFR_ATMEL ||
 	    JEDEC_MFR(info) == CFI_MFR_INTEL ||
-	    JEDEC_MFR(info) == CFI_MFR_SST) {
+	    JEDEC_MFR(info) == CFI_MFR_SST ||
+	    JEDEC_MFR(info) == CFI_MFR_MACRONIX) {
 		write_enable(nor);
 		write_sr(nor, 0);
 	}
@@ -1008,17 +1630,11 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	if (!mtd->name)
 		mtd->name = dev_name(dev);
 	mtd->type = MTD_NORFLASH;
-	mtd->writesize = 1;
 	mtd->flags = MTD_CAP_NORFLASH;
-	mtd->size = info->sector_size * info->n_sectors;
 	mtd->_erase = spi_nor_erase;
 	mtd->_read = spi_nor_read;
-
-	/* nor protection support for STmicro chips */
-	if (JEDEC_MFR(info) == CFI_MFR_ST) {
-		mtd->_lock = spi_nor_lock;
-		mtd->_unlock = spi_nor_unlock;
-	}
+	mtd->_suspend = spi_nor_suspend;
+	mtd->_resume = spi_nor_resume;
 
 	/* sst nor chips use AAI word program */
 	if (info->flags & SST_WRITE)
@@ -1029,21 +1645,6 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	if (info->flags & USE_FSR)
 		nor->flags |= SNOR_F_USE_FSR;
 
-#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
-	/* prefer "small sector" erase if possible */
-	if (info->flags & SECT_4K) {
-		nor->erase_opcode = SPINOR_OP_BE_4K;
-		mtd->erasesize = 4096;
-	} else if (info->flags & SECT_4K_PMC) {
-		nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
-		mtd->erasesize = 4096;
-	} else
-#endif
-	{
-		nor->erase_opcode = SPINOR_OP_SE;
-		mtd->erasesize = info->sector_size;
-	}
-
 	if (info->flags & SPI_NOR_NO_ERASE)
 		mtd->flags |= MTD_NO_ERASE;
 
@@ -1066,6 +1667,45 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 	if (info->flags & SPI_NOR_NO_FR)
 		nor->flash_read = SPI_NOR_NORMAL;
 
+	/*
+	 * We prepare to use SFDP to replace flash info struct, but
+	 * there are many flags not covered by SFDP such as SST_WRITE,
+	 * USE_FSR, SPI_NOR_NO_FR and SECT_4K_PMC. This part still need
+	 * to be modified by each flash manufacturer. Finally, we hope
+	 * we can setup spi-nor all from SFDP.
+	 */
+
+#ifndef CONFIG_MTD_SPI_NOR_SFDP
+	if (0)
+#endif
+		/* If the chip is SFDP compliant, update the mtd info. */
+		if (spi_nor_detect_sfdp(nor, mode))
+			goto scan_done;
+
+	mtd->writesize = 1;
+	mtd->size = info->sector_size * info->n_sectors;
+
+	/* nor protection support for STmicro chips */
+	if (JEDEC_MFR(info) == CFI_MFR_ST) {
+		mtd->_lock = spi_nor_lock;
+		mtd->_unlock = spi_nor_unlock;
+	}
+
+#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
+	/* prefer "small sector" erase if possible */
+	if (info->flags & SECT_4K) {
+		nor->erase_opcode = SPINOR_OP_BE_4K;
+		mtd->erasesize = 4096;
+	} else if (info->flags & SECT_4K_PMC) {
+		nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
+		mtd->erasesize = 4096;
+	} else
+#endif
+	{
+		nor->erase_opcode = SPINOR_OP_SE;
+		mtd->erasesize = info->sector_size;
+	}
+
 	/* Quad/Dual-read mode takes precedence over fast/normal */
 	if (mode == SPI_NOR_QUAD && info->flags & SPI_NOR_QUAD_READ) {
 		ret = set_quad_mode(nor, info);
@@ -1078,6 +1718,16 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 		nor->flash_read = SPI_NOR_DUAL;
 	}
 
+	if (info->addr_width)
+		nor->addr_width = info->addr_width;
+	else if (mtd->size > 0x1000000) {
+		/* enable 4-byte addressing if the device exceeds 16MiB */
+		nor->addr_width = 4;
+	} else {
+		nor->addr_width = 3;
+	}
+scan_done:
+
 	/* Default commands */
 	switch (nor->flash_read) {
 	case SPI_NOR_QUAD:
@@ -1099,11 +1749,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 
 	nor->program_opcode = SPINOR_OP_PP;
 
-	if (info->addr_width)
-		nor->addr_width = info->addr_width;
-	else if (mtd->size > 0x1000000) {
-		/* enable 4-byte addressing if the device exceeds 16MiB */
-		nor->addr_width = 4;
+	if (info->addr_width == 4) {
 		if (JEDEC_MFR(info) == CFI_MFR_AMD) {
 			/* Dedicated 4-byte command set */
 			switch (nor->flash_read) {
@@ -1126,8 +1772,6 @@ int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
 			mtd->erasesize = info->sector_size;
 		} else
 			set_4byte(nor, info, 1);
-	} else {
-		nor->addr_width = 3;
 	}
 
 	nor->read_dummy = spi_nor_read_dummy_cycles(nor);
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 63aeccf..8f8f892 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -35,6 +35,11 @@
 #define SPINOR_OP_RDID		0x9f	/* Read JEDEC ID */
 #define SPINOR_OP_RDCR		0x35	/* Read configuration register */
 #define SPINOR_OP_RDFSR		0x70	/* Read flag status register */
+#define SPINOR_OP_WRSCUR	0x2f	/* Write security register */
+#define SPINOR_OP_RDSCUR	0x2b	/* Read security register */
+#define SPINOR_OP_SUSPEND	0xb0	/* Erase/program suspend */
+#define SPINOR_OP_RESUME	0x30	/* Erase/program resume */
+#define SPINOR_OP_RDSFDP	0x5a	/* Read SFDP */
 
 /* 4-byte address opcodes - used on Spansion and some Macronix flashes. */
 #define SPINOR_OP_READ4		0x13	/* Read data bytes (low frequency) */
@@ -53,6 +58,23 @@
 #define SPINOR_OP_EN4B		0xb7	/* Enter 4-byte mode */
 #define SPINOR_OP_EX4B		0xe9	/* Exit 4-byte mode */
 
+/* Used for Macronix flashes. */
+#define SPINOR_OP_DP		0xb9	/* Deep power-down */
+#define SPINOR_OP_RDP		0xab	/* Release from deep power-down */
+#define SPINOR_OP_ENSO		0xb1	/* Enter secured OTP */
+#define SPINOR_OP_EXSO		0xc1	/* Exit secured OTP */
+#define SPINOR_OP_WPSEL		0x68	/* Write protect selection */
+#define SPINOR_OP_SBLK		0x36	/* Single block lock */
+#define SPINOR_OP_SBULK		0x39	/* Single block unlock */
+#define SPINOR_OP_GBLK		0x7e	/* Gang block lock */
+#define SPINOR_OP_GBULK		0x98	/* Gang block unlock */
+#define SPINOR_OP_RDBLOCK	0x3c	/* Read block lock status */
+#define SPINOR_OP_WRSPB		0xe3	/* Write solid protection bit */
+#define SPINOR_OP_RDSPB		0xe2	/* Read solid protection bit */
+#define SPINOR_OP_ESSPB		0xe4	/* Rease solid protection bit */
+#define SPINOR_OP_WRDPB		0xe1	/* Write dynamic protection bit */
+#define SPINOR_OP_RDDPB		0xe0	/* Write dynamic protection bit */
+
 /* Used for Spansion flashes only. */
 #define SPINOR_OP_BRWR		0x17	/* Bank register write */
 
@@ -80,6 +102,185 @@ enum read_mode {
 	SPI_NOR_QUAD,
 };
 
+enum macronix_lock_type {
+	MX_SBLK = 0,
+	MX_SPB,
+	MX_DPB,
+};
+
+/**
+ * struct sfdp_vendor_macronix - Macronix SFDP parameter table
+ * @vcc_supply_max_voltage:	Maximun voltage of chip
+ * @vcc_supply_min_voltage:	Minimun voltage of chip
+ * @f_param:			Macronix flash parameters:
+ *				 Bit 0: support of H/W Reset# pin
+ *				 Bit 1: support of H/W Hold# pin
+ *				 Bit 2: support of deep power down mode
+ *				 Bit 3: support of S/W Reset
+ *				 Bit 4~11: software reset instruction
+ *				 Bit 12: support of program suspend/resume
+ *				 Bit 13: support of erase suspend/resume
+ *				 Bit 15: support of wrap-around read
+ * @wrap_around_read_opcode:	Wrap around read instrction
+ * @wrap_around_read_length:	Wrap around read length
+ * @protection_param:		Protection related parameters:
+ *				 Bit 0: support of individual block lock
+ *				 Bit 1: individual lock bits
+ *				 Bit 2~9: individual block lock opcode
+ *				 Bit 10: default status of individual lock
+ *				 Bit 11: support of security OTP
+ *				 Bit 12: support of read lock
+ *				 Bit 13: support of permanent lock
+ */
+struct sfdp_macronix_params {
+	u16 vcc_supply_max_voltage;
+	u16 vcc_supply_min_voltage;
+	u16 f_param;
+	u8 wrap_around_read_opcode;
+	u8 wrap_around_read_length;
+	u16 protection_param;
+	u8 reserved[6];
+};
+
+/**
+ * struct sfdp_erase_type - SFDP erase type parameter
+ * @size_power:		Erase size (2 to the n-th power)
+ * @opcode:		Erase instruction
+ */
+struct sfdp_erase_type {
+	u8 size_power;
+	u8 opcode;
+};
+
+/**
+ * struct sfdp_jedec_params - Basic JEDEC parameters of SFDP
+ * @f_param:		JEDEC flash parameters:
+ *			 Bit 0~1: Block/sector erase size
+ *			 Bit 2: write granularity (0 - 1B, 1 - 64B)
+ *			 Bit 3~4: command of write enable
+ * @be_4k_opcode:	4KByte block/sector erase instruction
+ * @fr_support:		Fast read supports:
+ *			 Bit 0: support of fast read 1-1-2
+ *			 Bit 1~2: address width
+ *			 Bit 3: support of DTR
+ *			 Bit 4~6: support of fast read 1-2-2, 1-4-4, 1-1-4
+ * @flash_density:	Flash memory density
+ * @fr_XXX_param:	(X-X-X) Fast read parameters:
+ *			 Bit 0~4: number of dummy clocks
+ *			 Bit 5~7: number of mode clocks
+ * @fr_XXX_opcode:	(X-X-X) Fast read instruction
+ * @fr_222_444_support:	Fast read (2-2-2/4-4-4) supports:
+ *			 Bit 0: support of fast read 2-2-2
+ *			 Bit 4: support of fast read 4-4-4
+ * @erase_type:		Erase type (size & instruction)
+ * @e_param:		Erase parameters (timing):
+ *			 Bit 0~3: multiplier from typical to max erase time
+ *			 Bit 4~32: typical time of each type of erase
+ * @p_param:		Program parameters:
+ *			 Bit 0~3: multiplier from typical to max program time
+ *			 Bit 4~7: page size (2 to the n-th power)
+ *			 Bit 8~30: typical time of PP, BP and chip erase
+ * @susp_param:		Suspend/Resume parameters:
+ *			 Bit 0~7: prohibited operations during suspend
+ *			 Bit 9~30: timing defininition
+ *			 Bit 31: support of suspend/resume
+ * @p_resu_opcode:	Program resume instruction
+ * @p_susp_opcode:	Program suspend instruction
+ * @resu_opcode:	Write/Erase resume instruction
+ * @susp_opcode:	Write/Erase suspend instruction
+ * @dp_param:		Deep powerdown parameters:
+ *			 Bit 2~14: delay and SR polliong
+ *			 Bit 15~22: exit deep powerdown instruction
+ *			 Bit 23~30: enter deep powerdown instruction
+ *			 Bit 31: support of deep powerdown
+ * @fr_quad_param:	(X-4-4) Fast read parameters:
+ *			 Bit 0~8: 4-4-4 mode disable/enable sequences
+ *			 Bit 9: support of fast read 0-4-4
+ *			 Bit 10~19: 0-4-4 mode exit/entry method
+ *			 Bit 20~22: quad enable requirements (QER)
+ *			 Bit 23: HOLD or RESET disable
+ * @en4b_param:		Method of enter 4-byte address
+ * @ex4b_softreset_param: method of exit 4-byte address and software reset
+ * @sr_param:		Volatile/Non-volatile status register 1 parameters
+ */
+struct sfdp_jedec_params {
+	u8 f_param;
+	u8 be_4k_opcode;
+	u8 fr_support;
+	u8 reserved1;
+	u32 flash_density;
+
+	u8 fr_144_param;
+	u8 fr_144_opcode;
+	u8 fr_114_param;
+	u8 fr_114_opcode;
+	u8 fr_112_param;
+	u8 fr_112_opcode;
+	u8 fr_122_param;
+	u8 fr_122_opcode;
+
+	u8 fr_222_444_support;
+	u8 reserved2[5];
+	u8 fr_222_param;
+	u8 fr_222_opcode;
+	u8 reserved3[2];
+	u8 fr_444_param;
+	u8 fr_444_opcode;
+
+	struct sfdp_erase_type e_type[4];
+
+	u32 e_param;
+	u32 p_param;
+	u32 susp_param;
+	u8 p_resu_opcode;
+	u8 p_susp_opcode;
+	u8 resu_opcode;
+	u8 susp_opcode;
+	u32 dp_param;
+	u32 fr_quad_param;
+	u8 en4b_param;
+	u16 ex4b_softreset_param;
+	u8 sr_param;
+};
+
+/**
+ * struct sfdp_param_header - SFDP parameter header
+ * @id:			Parameter ID LSB (manufacture ID)
+ * @minor_rev:		Parameter table minor revision number
+ * @major_rev:		Parameter table major revision number
+ * @length:		Parameter table length (DWORDs)
+ * @pointer:		Parameter table pointer (start address)
+ * @id_reserved:	Parameter ID MSB
+ */
+struct sfdp_param_header {
+	u8 id;
+	u8 minor_rev;
+	u8 major_rev;
+	u8 length;
+	u8 pointer[3];
+	u8 id_reserved;
+};
+
+/* SFDP signature (ASCII: 'S' 'F' 'D' 'P') */
+#define SFDP_SIGNATURE		0x50444653
+
+/**
+ * struct spi_nor_sfdp_params - Serial Flash Discoverable Parameters
+ * @signature:		SFDP signature for identification
+ * @minor_rev:		SFDP minor revision number
+ * @major_rev:		SFDP major revision number
+ * @num_of_param_header: Number of parameter headers (0 indicates 1 header)
+ * @header:		Header of JEDEC or vendor's parameters
+ */
+struct spi_nor_sfdp_params {
+	u32 signature;
+	u8 minor_rev;
+	u8 major_rev;
+	u8 num_of_param_header;
+	u8 reserved0;
+	struct sfdp_param_header header[5];
+};
+
 /**
  * struct spi_nor_xfer_cfg - Structure for defining a Serial Flash transfer
  * @wren:		command for "Write Enable", or 0x00 for not required
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ