Commit e8716132 authored by Arseniy Krasnov's avatar Arseniy Krasnov Committed by Miquel Raynal
Browse files

mtd: rawnand: macronix: OTP access for MX30LFxG18AC

parent 98480a18
Loading
Loading
Loading
Loading
+167 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@
 * Author: Boris Brezillon <boris.brezillon@free-electrons.com>
 */

#include <linux/slab.h>
#include "linux/delay.h"
#include "internals.h"

@@ -31,6 +32,16 @@

#define MXIC_CMD_POWER_DOWN 0xB9

#define ONFI_FEATURE_ADDR_30LFXG18AC_OTP	0x90
#define MACRONIX_30LFXG18AC_OTP_START_PAGE	2
#define MACRONIX_30LFXG18AC_OTP_PAGES		30
#define MACRONIX_30LFXG18AC_OTP_PAGE_SIZE	2112
#define MACRONIX_30LFXG18AC_OTP_SIZE_BYTES	\
	(MACRONIX_30LFXG18AC_OTP_PAGES *	\
	 MACRONIX_30LFXG18AC_OTP_PAGE_SIZE)

#define MACRONIX_30LFXG18AC_OTP_EN		BIT(0)

struct nand_onfi_vendor_macronix {
	u8 reserved;
	u8 reliability_func;
@@ -315,6 +326,161 @@ static void macronix_nand_deep_power_down_support(struct nand_chip *chip)
	chip->ops.resume = mxic_nand_resume;
}

static int macronix_30lfxg18ac_get_otp_info(struct mtd_info *mtd, size_t len,
					    size_t *retlen,
					    struct otp_info *buf)
{
	if (len < sizeof(*buf))
		return -EINVAL;

	/* Always report that OTP is unlocked. Reason is that this
	 * type of flash chip doesn't provide way to check that OTP
	 * is locked or not: subfeature parameter is implemented as
	 * volatile register. Technically OTP region could be locked
	 * and become readonly, but as there is no way to check it,
	 * don't allow to lock it ('_lock_user_prot_reg' callback
	 * always returns -EOPNOTSUPP) and thus we report that OTP
	 * is unlocked.
	 */
	buf->locked = 0;
	buf->start = 0;
	buf->length = MACRONIX_30LFXG18AC_OTP_SIZE_BYTES;

	*retlen = sizeof(*buf);

	return 0;
}

static int macronix_30lfxg18ac_otp_enable(struct nand_chip *nand)
{
	u8 feature_buf[ONFI_SUBFEATURE_PARAM_LEN] = { 0 };

	feature_buf[0] = MACRONIX_30LFXG18AC_OTP_EN;
	return nand_set_features(nand, ONFI_FEATURE_ADDR_30LFXG18AC_OTP,
				 feature_buf);
}

static int macronix_30lfxg18ac_otp_disable(struct nand_chip *nand)
{
	u8 feature_buf[ONFI_SUBFEATURE_PARAM_LEN] = { 0 };

	return nand_set_features(nand, ONFI_FEATURE_ADDR_30LFXG18AC_OTP,
				 feature_buf);
}

static int __macronix_30lfxg18ac_rw_otp(struct mtd_info *mtd,
					loff_t offs_in_flash,
					size_t len, size_t *retlen,
					u_char *buf, bool write)
{
	struct nand_chip *nand;
	size_t bytes_handled;
	off_t offs_in_page;
	u64 page;
	int ret;

	nand = mtd_to_nand(mtd);
	nand_select_target(nand, 0);

	ret = macronix_30lfxg18ac_otp_enable(nand);
	if (ret)
		goto out_otp;

	page = offs_in_flash;
	/* 'page' will be result of division. */
	offs_in_page = do_div(page, MACRONIX_30LFXG18AC_OTP_PAGE_SIZE);
	bytes_handled = 0;

	while (bytes_handled < len &&
	       page < MACRONIX_30LFXG18AC_OTP_PAGES) {
		size_t bytes_to_handle;
		u64 phys_page = page + MACRONIX_30LFXG18AC_OTP_START_PAGE;

		bytes_to_handle = min_t(size_t, len - bytes_handled,
					MACRONIX_30LFXG18AC_OTP_PAGE_SIZE -
					offs_in_page);

		if (write)
			ret = nand_prog_page_op(nand, phys_page, offs_in_page,
						&buf[bytes_handled], bytes_to_handle);
		else
			ret = nand_read_page_op(nand, phys_page, offs_in_page,
						&buf[bytes_handled], bytes_to_handle);
		if (ret)
			goto out_otp;

		bytes_handled += bytes_to_handle;
		offs_in_page = 0;
		page++;
	}

	*retlen = bytes_handled;

out_otp:
	if (ret)
		dev_err(&mtd->dev, "failed to perform OTP IO: %i\n", ret);

	ret = macronix_30lfxg18ac_otp_disable(nand);
	if (ret)
		dev_err(&mtd->dev, "failed to leave OTP mode after %s\n",
			write ? "write" : "read");

	nand_deselect_target(nand);

	return ret;
}

static int macronix_30lfxg18ac_write_otp(struct mtd_info *mtd, loff_t to,
					 size_t len, size_t *rlen,
					 const u_char *buf)
{
	return __macronix_30lfxg18ac_rw_otp(mtd, to, len, rlen, (u_char *)buf,
					    true);
}

static int macronix_30lfxg18ac_read_otp(struct mtd_info *mtd, loff_t from,
					size_t len, size_t *rlen,
					u_char *buf)
{
	return __macronix_30lfxg18ac_rw_otp(mtd, from, len, rlen, buf, false);
}

static int macronix_30lfxg18ac_lock_otp(struct mtd_info *mtd, loff_t from,
					size_t len)
{
	/* See comment in 'macronix_30lfxg18ac_get_otp_info()'. */
	return -EOPNOTSUPP;
}

static void macronix_nand_setup_otp(struct nand_chip *chip)
{
	static const char * const supported_otp_models[] = {
		"MX30LF1G18AC",
		"MX30LF2G18AC",
		"MX30LF4G18AC",
	};
	struct mtd_info *mtd;

	if (match_string(supported_otp_models,
			 ARRAY_SIZE(supported_otp_models),
			 chip->parameters.model) < 0)
		return;

	if (!chip->parameters.supports_set_get_features)
		return;

	bitmap_set(chip->parameters.get_feature_list,
		   ONFI_FEATURE_ADDR_30LFXG18AC_OTP, 1);
	bitmap_set(chip->parameters.set_feature_list,
		   ONFI_FEATURE_ADDR_30LFXG18AC_OTP, 1);

	mtd = nand_to_mtd(chip);
	mtd->_get_user_prot_info = macronix_30lfxg18ac_get_otp_info;
	mtd->_read_user_prot_reg = macronix_30lfxg18ac_read_otp;
	mtd->_write_user_prot_reg = macronix_30lfxg18ac_write_otp;
	mtd->_lock_user_prot_reg = macronix_30lfxg18ac_lock_otp;
}

static int macronix_nand_init(struct nand_chip *chip)
{
	if (nand_is_slc(chip))
@@ -324,6 +490,7 @@ static int macronix_nand_init(struct nand_chip *chip)
	macronix_nand_onfi_init(chip);
	macronix_nand_block_protection_support(chip);
	macronix_nand_deep_power_down_support(chip);
	macronix_nand_setup_otp(chip);

	return 0;
}