From 986172ece10eee928ce66597d76f3f40ac3d25f7 Mon Sep 17 00:00:00 2001
From: Yunhui Cui <yunhui.cui@nxp.com>
Date: Mon, 8 Aug 2016 14:24:13 +0800
Subject: [PATCH 86/93] driver: spi: add spansion s25fs-s family
 protect/unprotect

In order to support spansion s25fs512s flash protect/unprotect:

[1] Fill callbak flash->lock/unlock/is_locked by spansion_lock/
unlock/is_locked.

[2] Achieve protect/unprotected by operating sr1nv, cr1nv.

Signed-off-by: Yunhui Cui <yunhui.cui@nxp.com>
---
 drivers/mtd/spi/spi_flash.c |  194 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 194 insertions(+)

diff --git a/drivers/mtd/spi/spi_flash.c b/drivers/mtd/spi/spi_flash.c
index e04bd55..87a92e9 100644
--- a/drivers/mtd/spi/spi_flash.c
+++ b/drivers/mtd/spi/spi_flash.c
@@ -877,6 +877,193 @@ int stm_unlock(struct spi_flash *flash, u32 ofs, size_t len)
 }
 #endif
 
+#if defined(CONFIG_SPI_FLASH_SPANSION)
+/*
+ * Return 1 if the entire region is locked, 0 otherwise
+ */
+static int spansion_is_locked_sr(struct spi_flash *flash, u32 ofs, u32 len,
+			    u8 sr)
+{
+	loff_t lock_offs;
+	u32 lock_len;
+
+	stm_get_locked_range(flash, sr, &lock_offs, &lock_len);
+
+	return (ofs + len <= lock_offs + lock_len) && (ofs >= lock_offs);
+}
+
+/*
+ * Check if a region of the flash is (completely) locked. See spansion_lock() for
+ * more info.
+ *
+ * Returns 1 if entire region is locked, 0 if any portion is unlocked, and
+ * negative on errors.
+ */
+int spansion_is_locked(struct spi_flash *flash, u32 ofs, size_t len)
+{
+	u8 cmd[4];
+	u32 sr1nv_offset = 0x0;
+	u8 sr1nv;
+	int ret;
+
+	cmd[0] = CMD_SPANSION_RDAR;
+	cmd[1] = sr1nv_offset >> 16;
+	cmd[2] = sr1nv_offset >> 8;
+	cmd[3] = sr1nv_offset >> 0;
+
+	ret = spi_flash_cmd_read(flash->spi, cmd, 4, &sr1nv, 1);
+	if (ret)
+		return -EIO;
+
+	return spansion_is_locked_sr(flash, ofs, len, sr1nv);
+}
+
+/*
+ * Lock a region of the flash. Compatible with Spansion s25fs-s family flash.
+ * Supports only the block protection bits BP{0,1,2} in the Status Register-1
+ * Non-Volatile(SR1NV).
+ *
+ * Sample table portion for 64MB flash (S25FS512S):
+ * Configuration Register-1 Non-Volatile(CR1NV[5])== 0
+ *
+ *  |  BP2  |  BP1  |  BP0  |  Prot Length  | Protected Portion
+ *  ------------------------------------------------------------
+ *  |   0   |   0   |   0   |  NONE          | NONE
+ *  |   0   |   0   |   1   |  1  MB         | Upper 1/64
+ *  |   0   |   1   |   0   |  2  MB         | Upper 1/32
+ *  |   0   |   1   |   1   |  4  MB         | Upper 1/16
+ *  |   1   |   0   |   0   |  8  MB         | Upper 1/8
+ *  |   1   |   0   |   1   |  16 MB         | Upper 1/4
+ *  |   1   |   1   |   0   |  32 MB         | Upper 1/2
+ *  |   1   |   1   |   1   |  64 MB         | ALL
+ *
+ * When CR1NV[5] == 1, the Lower memory array are protected.
+ *
+ * Returns negative on errors, 0 on success.
+ */
+int spansion_lock(struct spi_flash *flash, u32 ofs, size_t len)
+{
+	u8 status_old, status_new;
+	u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
+	u8 shift = ffs(mask) - 1, pow, val;
+	int ret;
+	u8 cmd[4];
+	u32 sr1nv_offset = 0x0;
+	u8 sr1nv;
+
+	cmd[0] = CMD_SPANSION_RDAR;
+	cmd[1] = sr1nv_offset >> 16;
+	cmd[2] = sr1nv_offset >> 8;
+	cmd[3] = sr1nv_offset >> 0;
+
+	ret = spi_flash_cmd_read(flash->spi, cmd, 4, &sr1nv, 1);
+	if (ret)
+		return -EIO;
+	status_old = sr1nv;
+
+	/* SPI NOR always locks to the end */
+	if (ofs + len != flash->size) {
+		/* Does combined region extend to end? */
+		if (!stm_is_locked_sr(flash, ofs + len, flash->size - ofs - len,
+				      status_old))
+			return -EINVAL;
+		len = flash->size - ofs;
+	}
+
+	/*
+	 * Need smallest pow such that:
+	 *
+	 *   1 / (2^pow) <= (len / size)
+	 *
+	 * so (assuming power-of-2 size) we do:
+	 *
+	 *   pow = ceil(log2(size / len)) = log2(size) - floor(log2(len))
+	 */
+	pow = ilog2(flash->size) - ilog2(len);
+	val = mask - (pow << shift);
+	if (val & ~mask)
+		return -EINVAL;
+
+	/* Don't "lock" with no region! */
+	if (!(val & mask))
+		return -EINVAL;
+
+	status_new = (status_old & ~mask) | val;
+
+	/* Only modify protection if it will not unlock other areas */
+	if ((status_new & mask) <= (status_old & mask))
+		return -EINVAL;
+
+	cmd[0] = CMD_SPANSION_WRAR;
+	ret = spi_flash_cmd_write(flash->spi, cmd, 4, &status_new, 1);
+	if (ret)
+		return -EIO;
+
+	return 0;
+}
+
+/*
+ * Unlock a region of the flash. See spansion_lock() for more info
+ *
+ * Returns negative on errors, 0 on success.
+ */
+int spansion_unlock(struct spi_flash *flash, u32 ofs, size_t len)
+{
+	uint8_t status_old, status_new;
+	u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
+	u8 shift = ffs(mask) - 1, pow, val;
+	int ret;
+
+	u8 cmd[4];
+	u32 sr1nv_offset = 0x0;
+	u8 sr1nv;
+
+	cmd[0] = CMD_SPANSION_RDAR;
+	cmd[1] = sr1nv_offset >> 16;
+	cmd[2] = sr1nv_offset >> 8;
+	cmd[3] = sr1nv_offset >> 0;
+
+	ret = spi_flash_cmd_read(flash->spi, cmd, 4, &sr1nv, 1);
+	if (ret)
+		return -EIO;
+	status_old = sr1nv;
+
+	/* Cannot unlock; would unlock larger region than requested */
+	if (spansion_is_locked_sr(flash, ofs - flash->erase_size, flash->erase_size,
+			     status_old))
+		return -EINVAL;
+	/*
+	 * Need largest pow such that:
+	 *
+	 *   1 / (2^pow) >= (len / size)
+	 *
+	 * so (assuming power-of-2 size) we do:
+	 *
+	 *   pow = floor(log2(size / len)) = log2(size) - ceil(log2(len))
+	 */
+	pow = ilog2(flash->size) - order_base_2(flash->size - (ofs + len));
+	if (ofs + len == flash->size) {
+		val = 0; /* fully unlocked */
+	} else {
+		val = mask - (pow << shift);
+		/* Some power-of-two sizes are not supported */
+		if (val & ~mask)
+			return -EINVAL;
+	}
+	status_new = (status_old & ~mask) | val;
+
+	/* Only modify protection if it will not lock other areas */
+	if ((status_new & mask) >= (status_old & mask))
+		return -EINVAL;
+
+	cmd[0] = CMD_SPANSION_WRAR;
+	ret = spi_flash_cmd_write(flash->spi, cmd, 4, &status_new, 1);
+	if (ret)
+		return -EIO;
+
+	return 0;
+}
+#endif
 
 #ifdef CONFIG_SPI_FLASH_MACRONIX
 static int spi_flash_set_qeb_mxic(struct spi_flash *flash)
@@ -1132,6 +1319,13 @@ int spi_flash_scan(struct spi_flash *flash)
 		flash->flash_is_locked = stm_is_locked;
 #endif
 		break;
+#if defined(CONFIG_SPI_FLASH_SPANSION)
+	case SPI_FLASH_CFI_MFR_SPANSION:
+		flash->flash_lock = spansion_lock;
+		flash->flash_unlock = spansion_unlock;
+		flash->flash_is_locked = spansion_is_locked;
+#endif
+		break;
 	default:
 		debug("SF: Lock ops not supported for %02x flash\n", idcode[0]);
 	}
-- 
1.7.9.5

