mirror of
https://github.com/torvalds/linux.git
synced 2026-05-05 23:05:25 -04:00
mtd: spinand: add OTP support
The MTD subsystem already supports accessing two OTP areas: user and
factory. User areas can be written by the user.
This patch provides the SPINAND_FACT_OTP_INFO and SPINAND_USER_OTP_INFO
macros to add parameters to spinand_info.
To implement OTP operations, the client (flash driver) is provided with
callbacks for user area:
.read(), .write(), .info(), .lock(), .erase();
and for factory area:
.read(), .info();
Signed-off-by: Martin Kurbanov <mmkurbanov@salutedevices.com>
Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
This commit is contained in:
committed by
Miquel Raynal
parent
07d0aa9393
commit
c06b1f753b
@@ -1,4 +1,5 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
spinand-objs := core.o alliancememory.o ato.o esmt.o foresee.o gigadevice.o macronix.o
|
||||
spinand-objs := core.o otp.o
|
||||
spinand-objs += alliancememory.o ato.o esmt.o foresee.o gigadevice.o macronix.o
|
||||
spinand-objs += micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o
|
||||
obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
|
||||
|
||||
@@ -1308,6 +1308,8 @@ int spinand_match_and_init(struct spinand_device *spinand,
|
||||
spinand->id.len = 1 + table[i].devid.len;
|
||||
spinand->select_target = table[i].select_target;
|
||||
spinand->set_cont_read = table[i].set_cont_read;
|
||||
spinand->fact_otp = &table[i].fact_otp;
|
||||
spinand->user_otp = &table[i].user_otp;
|
||||
|
||||
op = spinand_select_op_variant(spinand,
|
||||
info->op_variants.read_cache);
|
||||
@@ -1494,6 +1496,12 @@ static int spinand_init(struct spinand_device *spinand)
|
||||
mtd->_max_bad_blocks = nanddev_mtd_max_bad_blocks;
|
||||
mtd->_resume = spinand_mtd_resume;
|
||||
|
||||
if (spinand_user_otp_size(spinand) || spinand_fact_otp_size(spinand)) {
|
||||
ret = spinand_set_mtd_otp_ops(spinand);
|
||||
if (ret)
|
||||
goto err_cleanup_ecc_engine;
|
||||
}
|
||||
|
||||
if (nand->ecc.engine) {
|
||||
ret = mtd_ooblayout_count_freebytes(mtd);
|
||||
if (ret < 0)
|
||||
|
||||
244
drivers/mtd/nand/spi/otp.c
Normal file
244
drivers/mtd/nand/spi/otp.c
Normal file
@@ -0,0 +1,244 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (c) 2025, SaluteDevices. All Rights Reserved.
|
||||
*
|
||||
* Author: Martin Kurbanov <mmkurbanov@salutedevices.com>
|
||||
*/
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/spinand.h>
|
||||
|
||||
static size_t spinand_otp_size(struct spinand_device *spinand,
|
||||
const struct spinand_otp_layout *layout)
|
||||
{
|
||||
struct nand_device *nand = spinand_to_nand(spinand);
|
||||
size_t otp_pagesize = nanddev_page_size(nand) +
|
||||
nanddev_per_page_oobsize(nand);
|
||||
|
||||
return layout->npages * otp_pagesize;
|
||||
}
|
||||
|
||||
/**
|
||||
* spinand_fact_otp_size() - Get SPI-NAND factory OTP area size
|
||||
* @spinand: the spinand device
|
||||
*
|
||||
* Return: the OTP size.
|
||||
*/
|
||||
size_t spinand_fact_otp_size(struct spinand_device *spinand)
|
||||
{
|
||||
return spinand_otp_size(spinand, &spinand->fact_otp->layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* spinand_user_otp_size() - Get SPI-NAND user OTP area size
|
||||
* @spinand: the spinand device
|
||||
*
|
||||
* Return: the OTP size.
|
||||
*/
|
||||
size_t spinand_user_otp_size(struct spinand_device *spinand)
|
||||
{
|
||||
return spinand_otp_size(spinand, &spinand->user_otp->layout);
|
||||
}
|
||||
|
||||
static int spinand_otp_check_bounds(struct spinand_device *spinand, loff_t ofs,
|
||||
size_t len,
|
||||
const struct spinand_otp_layout *layout)
|
||||
{
|
||||
if (ofs < 0 || ofs + len > spinand_otp_size(spinand, layout))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spinand_user_otp_check_bounds(struct spinand_device *spinand,
|
||||
loff_t ofs, size_t len)
|
||||
{
|
||||
return spinand_otp_check_bounds(spinand, ofs, len,
|
||||
&spinand->user_otp->layout);
|
||||
}
|
||||
|
||||
static int spinand_mtd_otp_info(struct mtd_info *mtd, size_t len,
|
||||
size_t *retlen, struct otp_info *buf,
|
||||
bool is_fact)
|
||||
{
|
||||
struct spinand_device *spinand = mtd_to_spinand(mtd);
|
||||
int ret;
|
||||
|
||||
*retlen = 0;
|
||||
|
||||
mutex_lock(&spinand->lock);
|
||||
|
||||
if (is_fact)
|
||||
ret = spinand->fact_otp->ops->info(spinand, len, buf, retlen);
|
||||
else
|
||||
ret = spinand->user_otp->ops->info(spinand, len, buf, retlen);
|
||||
|
||||
mutex_unlock(&spinand->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spinand_mtd_fact_otp_info(struct mtd_info *mtd, size_t len,
|
||||
size_t *retlen, struct otp_info *buf)
|
||||
{
|
||||
return spinand_mtd_otp_info(mtd, len, retlen, buf, true);
|
||||
}
|
||||
|
||||
static int spinand_mtd_user_otp_info(struct mtd_info *mtd, size_t len,
|
||||
size_t *retlen, struct otp_info *buf)
|
||||
{
|
||||
return spinand_mtd_otp_info(mtd, len, retlen, buf, false);
|
||||
}
|
||||
|
||||
static int spinand_mtd_otp_read(struct mtd_info *mtd, loff_t ofs, size_t len,
|
||||
size_t *retlen, u8 *buf, bool is_fact)
|
||||
{
|
||||
struct spinand_device *spinand = mtd_to_spinand(mtd);
|
||||
int ret;
|
||||
|
||||
*retlen = 0;
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
ret = spinand_otp_check_bounds(spinand, ofs, len,
|
||||
is_fact ? &spinand->fact_otp->layout :
|
||||
&spinand->user_otp->layout);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&spinand->lock);
|
||||
|
||||
if (is_fact)
|
||||
ret = spinand->fact_otp->ops->read(spinand, ofs, len, retlen,
|
||||
buf);
|
||||
else
|
||||
ret = spinand->user_otp->ops->read(spinand, ofs, len, retlen,
|
||||
buf);
|
||||
|
||||
mutex_unlock(&spinand->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spinand_mtd_fact_otp_read(struct mtd_info *mtd, loff_t ofs,
|
||||
size_t len, size_t *retlen, u8 *buf)
|
||||
{
|
||||
return spinand_mtd_otp_read(mtd, ofs, len, retlen, buf, true);
|
||||
}
|
||||
|
||||
static int spinand_mtd_user_otp_read(struct mtd_info *mtd, loff_t ofs,
|
||||
size_t len, size_t *retlen, u8 *buf)
|
||||
{
|
||||
return spinand_mtd_otp_read(mtd, ofs, len, retlen, buf, false);
|
||||
}
|
||||
|
||||
static int spinand_mtd_user_otp_write(struct mtd_info *mtd, loff_t ofs,
|
||||
size_t len, size_t *retlen, const u8 *buf)
|
||||
{
|
||||
struct spinand_device *spinand = mtd_to_spinand(mtd);
|
||||
const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
|
||||
int ret;
|
||||
|
||||
*retlen = 0;
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
ret = spinand_user_otp_check_bounds(spinand, ofs, len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&spinand->lock);
|
||||
ret = ops->write(spinand, ofs, len, retlen, buf);
|
||||
mutex_unlock(&spinand->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spinand_mtd_user_otp_erase(struct mtd_info *mtd, loff_t ofs,
|
||||
size_t len)
|
||||
{
|
||||
struct spinand_device *spinand = mtd_to_spinand(mtd);
|
||||
const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
|
||||
int ret;
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
ret = spinand_user_otp_check_bounds(spinand, ofs, len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&spinand->lock);
|
||||
ret = ops->erase(spinand, ofs, len);
|
||||
mutex_unlock(&spinand->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spinand_mtd_user_otp_lock(struct mtd_info *mtd, loff_t ofs,
|
||||
size_t len)
|
||||
{
|
||||
struct spinand_device *spinand = mtd_to_spinand(mtd);
|
||||
const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
|
||||
int ret;
|
||||
|
||||
if (!len)
|
||||
return 0;
|
||||
|
||||
ret = spinand_user_otp_check_bounds(spinand, ofs, len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mutex_lock(&spinand->lock);
|
||||
ret = ops->lock(spinand, ofs, len);
|
||||
mutex_unlock(&spinand->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* spinand_set_mtd_otp_ops() - Setup OTP methods
|
||||
* @spinand: the spinand device
|
||||
*
|
||||
* Setup OTP methods.
|
||||
*
|
||||
* Return: 0 on success, a negative error code otherwise.
|
||||
*/
|
||||
int spinand_set_mtd_otp_ops(struct spinand_device *spinand)
|
||||
{
|
||||
struct mtd_info *mtd = spinand_to_mtd(spinand);
|
||||
const struct spinand_fact_otp_ops *fact_ops = spinand->fact_otp->ops;
|
||||
const struct spinand_user_otp_ops *user_ops = spinand->user_otp->ops;
|
||||
|
||||
if (!user_ops && !fact_ops)
|
||||
return -EINVAL;
|
||||
|
||||
if (user_ops) {
|
||||
if (user_ops->info)
|
||||
mtd->_get_user_prot_info = spinand_mtd_user_otp_info;
|
||||
|
||||
if (user_ops->read)
|
||||
mtd->_read_user_prot_reg = spinand_mtd_user_otp_read;
|
||||
|
||||
if (user_ops->write)
|
||||
mtd->_write_user_prot_reg = spinand_mtd_user_otp_write;
|
||||
|
||||
if (user_ops->lock)
|
||||
mtd->_lock_user_prot_reg = spinand_mtd_user_otp_lock;
|
||||
|
||||
if (user_ops->erase)
|
||||
mtd->_erase_user_prot_reg = spinand_mtd_user_otp_erase;
|
||||
}
|
||||
|
||||
if (fact_ops) {
|
||||
if (fact_ops->info)
|
||||
mtd->_get_fact_prot_info = spinand_mtd_fact_otp_info;
|
||||
|
||||
if (fact_ops->read)
|
||||
mtd->_read_fact_prot_reg = spinand_mtd_fact_otp_read;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user