Commit c6ec3e1e authored by Michael Walle's avatar Michael Walle Committed by Vignesh Raghavendra
Browse files

mtd: spi-nor: otp: implement erase for Winbond and similar flashes



Winbond flashes with OTP support provide a command to erase the OTP
data. This might come in handy during development.

This was tested with a Winbond W25Q32JW on a LS1028A SoC with the
NXP FSPI controller.

Signed-off-by: default avatarMichael Walle <michael@walle.cc>
Signed-off-by: default avatarVignesh Raghavendra <vigneshr@ti.com>
Reviewed-by: default avatarPratyush Yadav <p.yadav@ti.com>
Reviewed-by: default avatarTudor Ambarus <tudor.ambarus@microchip.com>
parent 388161ca
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -1318,7 +1318,7 @@ static u32 spi_nor_convert_addr(struct spi_nor *nor, loff_t addr)
/*
 * Initiate the erasure of a single sector
 */
static int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
{
	int i;

+4 −0
Original line number Diff line number Diff line
@@ -207,6 +207,7 @@ struct spi_nor_otp_organization {
 * @read:	read from the SPI NOR OTP area.
 * @write:	write to the SPI NOR OTP area.
 * @lock:	lock an OTP region.
 * @erase:	erase an OTP region.
 * @is_locked:	check if an OTP region of the SPI NOR is locked.
 */
struct spi_nor_otp_ops {
@@ -214,6 +215,7 @@ struct spi_nor_otp_ops {
	int (*write)(struct spi_nor *nor, loff_t addr, size_t len,
		     const u8 *buf);
	int (*lock)(struct spi_nor *nor, unsigned int region);
	int (*erase)(struct spi_nor *nor, loff_t addr);
	int (*is_locked)(struct spi_nor *nor, unsigned int region);
};

@@ -503,10 +505,12 @@ ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
			  u8 *buf);
ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len,
			   const u8 *buf);
int spi_nor_erase_sector(struct spi_nor *nor, u32 addr);

int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf);
int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, size_t len,
			   const u8 *buf);
int spi_nor_otp_erase_secr(struct spi_nor *nor, loff_t addr);
int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region);
int spi_nor_otp_is_locked_sr2(struct spi_nor *nor, unsigned int region);

+86 −0
Original line number Diff line number Diff line
@@ -120,6 +120,38 @@ int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, size_t len,
	return ret ?: written;
}

/**
 * spi_nor_otp_erase_secr() - erase a security register
 * @nor:        pointer to 'struct spi_nor'
 * @addr:       offset of the security register to be erased
 *
 * Erase a security register by using the SPINOR_OP_ESECR command.
 *
 * For more information on the term "security register", see the documentation
 * of spi_nor_otp_read_secr().
 *
 * This method is used on GigaDevice and Winbond flashes.
 *
 * Return: 0 on success, -errno otherwise
 */
int spi_nor_otp_erase_secr(struct spi_nor *nor, loff_t addr)
{
	u8 erase_opcode = nor->erase_opcode;
	int ret;

	ret = spi_nor_write_enable(nor);
	if (ret)
		return ret;

	nor->erase_opcode = SPINOR_OP_ESECR;
	ret = spi_nor_erase_sector(nor, addr);
	nor->erase_opcode = erase_opcode;
	if (ret)
		return ret;

	return spi_nor_wait_till_ready(nor);
}

static int spi_nor_otp_lock_bit_cr(unsigned int region)
{
	static const int lock_bits[] = { SR2_LB1, SR2_LB2, SR2_LB3 };
@@ -360,6 +392,59 @@ static int spi_nor_mtd_otp_write(struct mtd_info *mtd, loff_t to, size_t len,
	return spi_nor_mtd_otp_read_write(mtd, to, len, retlen, buf, true);
}

static int spi_nor_mtd_otp_erase(struct mtd_info *mtd, loff_t from, size_t len)
{
	struct spi_nor *nor = mtd_to_spi_nor(mtd);
	const struct spi_nor_otp_ops *ops = nor->params->otp.ops;
	const size_t rlen = spi_nor_otp_region_len(nor);
	unsigned int region;
	loff_t rstart;
	int ret;

	/* OTP erase is optional */
	if (!ops->erase)
		return -EOPNOTSUPP;

	if (!len)
		return 0;

	if (from < 0 || (from + len) > spi_nor_otp_size(nor))
		return -EINVAL;

	/* the user has to explicitly ask for whole regions */
	if (!IS_ALIGNED(len, rlen) || !IS_ALIGNED(from, rlen))
		return -EINVAL;

	ret = spi_nor_lock_and_prep(nor);
	if (ret)
		return ret;

	ret = spi_nor_mtd_otp_range_is_locked(nor, from, len);
	if (ret < 0) {
		goto out;
	} else if (ret) {
		ret = -EROFS;
		goto out;
	}

	while (len) {
		region = spi_nor_otp_offset_to_region(nor, from);
		rstart = spi_nor_otp_region_start(nor, region);

		ret = ops->erase(nor, rstart);
		if (ret)
			goto out;

		len -= rlen;
		from += rlen;
	}

out:
	spi_nor_unlock_and_unprep(nor);

	return ret;
}

static int spi_nor_mtd_otp_lock(struct mtd_info *mtd, loff_t from, size_t len)
{
	struct spi_nor *nor = mtd_to_spi_nor(mtd);
@@ -418,4 +503,5 @@ void spi_nor_otp_init(struct spi_nor *nor)
	mtd->_read_user_prot_reg = spi_nor_mtd_otp_read;
	mtd->_write_user_prot_reg = spi_nor_mtd_otp_write;
	mtd->_lock_user_prot_reg = spi_nor_mtd_otp_lock;
	mtd->_erase_user_prot_reg = spi_nor_mtd_otp_erase;
}
+1 −0
Original line number Diff line number Diff line
@@ -139,6 +139,7 @@ static int winbond_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
static const struct spi_nor_otp_ops winbond_otp_ops = {
	.read = spi_nor_otp_read_secr,
	.write = spi_nor_otp_write_secr,
	.erase = spi_nor_otp_erase_secr,
	.lock = spi_nor_otp_lock_sr2,
	.is_locked = spi_nor_otp_is_locked_sr2,
};