forked from Minki/linux
mtd: core: add OTP nvmem provider support
Flash OTP regions can already be read via user space. Some boards have their serial number or MAC addresses stored in the OTP regions. Add support for them being a (read-only) nvmem provider. The API to read the OTP data is already in place. It distinguishes between factory and user OTP, thus there are up to two different providers. Signed-off-by: Michael Walle <michael@walle.cc> Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com> Link: https://lore.kernel.org/linux-mtd/20210424110608.15748-6-michael@walle.cc
This commit is contained in:
parent
96d3af22f8
commit
4b361cfa86
@ -776,6 +776,146 @@ static void mtd_set_dev_defaults(struct mtd_info *mtd)
|
|||||||
mutex_init(&mtd->master.chrdev_lock);
|
mutex_init(&mtd->master.chrdev_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ssize_t mtd_otp_size(struct mtd_info *mtd, bool is_user)
|
||||||
|
{
|
||||||
|
struct otp_info *info = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
||||||
|
ssize_t size = 0;
|
||||||
|
unsigned int i;
|
||||||
|
size_t retlen;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (is_user)
|
||||||
|
ret = mtd_get_user_prot_info(mtd, PAGE_SIZE, &retlen, info);
|
||||||
|
else
|
||||||
|
ret = mtd_get_fact_prot_info(mtd, PAGE_SIZE, &retlen, info);
|
||||||
|
if (ret)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
for (i = 0; i < retlen / sizeof(*info); i++) {
|
||||||
|
size += info->length;
|
||||||
|
info++;
|
||||||
|
}
|
||||||
|
|
||||||
|
kfree(info);
|
||||||
|
return size;
|
||||||
|
|
||||||
|
err:
|
||||||
|
kfree(info);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct nvmem_device *mtd_otp_nvmem_register(struct mtd_info *mtd,
|
||||||
|
const char *compatible,
|
||||||
|
int size,
|
||||||
|
nvmem_reg_read_t reg_read)
|
||||||
|
{
|
||||||
|
struct nvmem_device *nvmem = NULL;
|
||||||
|
struct nvmem_config config = {};
|
||||||
|
struct device_node *np;
|
||||||
|
|
||||||
|
/* DT binding is optional */
|
||||||
|
np = of_get_compatible_child(mtd->dev.of_node, compatible);
|
||||||
|
|
||||||
|
/* OTP nvmem will be registered on the physical device */
|
||||||
|
config.dev = mtd->dev.parent;
|
||||||
|
/* just reuse the compatible as name */
|
||||||
|
config.name = compatible;
|
||||||
|
config.id = NVMEM_DEVID_NONE;
|
||||||
|
config.owner = THIS_MODULE;
|
||||||
|
config.type = NVMEM_TYPE_OTP;
|
||||||
|
config.root_only = true;
|
||||||
|
config.reg_read = reg_read;
|
||||||
|
config.size = size;
|
||||||
|
config.of_node = np;
|
||||||
|
config.priv = mtd;
|
||||||
|
|
||||||
|
nvmem = nvmem_register(&config);
|
||||||
|
/* Just ignore if there is no NVMEM support in the kernel */
|
||||||
|
if (IS_ERR(nvmem) && PTR_ERR(nvmem) == -EOPNOTSUPP)
|
||||||
|
nvmem = NULL;
|
||||||
|
|
||||||
|
of_node_put(np);
|
||||||
|
|
||||||
|
return nvmem;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtd_nvmem_user_otp_reg_read(void *priv, unsigned int offset,
|
||||||
|
void *val, size_t bytes)
|
||||||
|
{
|
||||||
|
struct mtd_info *mtd = priv;
|
||||||
|
size_t retlen;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = mtd_read_user_prot_reg(mtd, offset, bytes, &retlen, val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return retlen == bytes ? 0 : -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtd_nvmem_fact_otp_reg_read(void *priv, unsigned int offset,
|
||||||
|
void *val, size_t bytes)
|
||||||
|
{
|
||||||
|
struct mtd_info *mtd = priv;
|
||||||
|
size_t retlen;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = mtd_read_fact_prot_reg(mtd, offset, bytes, &retlen, val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return retlen == bytes ? 0 : -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtd_otp_nvmem_add(struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
struct nvmem_device *nvmem;
|
||||||
|
ssize_t size;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (mtd->_get_user_prot_info && mtd->_read_user_prot_reg) {
|
||||||
|
size = mtd_otp_size(mtd, true);
|
||||||
|
if (size < 0)
|
||||||
|
return size;
|
||||||
|
|
||||||
|
if (size > 0) {
|
||||||
|
nvmem = mtd_otp_nvmem_register(mtd, "user-otp", size,
|
||||||
|
mtd_nvmem_user_otp_reg_read);
|
||||||
|
if (IS_ERR(nvmem)) {
|
||||||
|
dev_err(&mtd->dev, "Failed to register OTP NVMEM device\n");
|
||||||
|
return PTR_ERR(nvmem);
|
||||||
|
}
|
||||||
|
mtd->otp_user_nvmem = nvmem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mtd->_get_fact_prot_info && mtd->_read_fact_prot_reg) {
|
||||||
|
size = mtd_otp_size(mtd, false);
|
||||||
|
if (size < 0) {
|
||||||
|
err = size;
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size > 0) {
|
||||||
|
nvmem = mtd_otp_nvmem_register(mtd, "factory-otp", size,
|
||||||
|
mtd_nvmem_fact_otp_reg_read);
|
||||||
|
if (IS_ERR(nvmem)) {
|
||||||
|
dev_err(&mtd->dev, "Failed to register OTP NVMEM device\n");
|
||||||
|
err = PTR_ERR(nvmem);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
mtd->otp_factory_nvmem = nvmem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err:
|
||||||
|
if (mtd->otp_user_nvmem)
|
||||||
|
nvmem_unregister(mtd->otp_user_nvmem);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mtd_device_parse_register - parse partitions and register an MTD device.
|
* mtd_device_parse_register - parse partitions and register an MTD device.
|
||||||
*
|
*
|
||||||
@ -851,6 +991,8 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
|
|||||||
register_reboot_notifier(&mtd->reboot_notifier);
|
register_reboot_notifier(&mtd->reboot_notifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret = mtd_otp_nvmem_add(mtd);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if (ret && device_is_registered(&mtd->dev))
|
if (ret && device_is_registered(&mtd->dev))
|
||||||
del_mtd_device(mtd);
|
del_mtd_device(mtd);
|
||||||
@ -872,6 +1014,12 @@ int mtd_device_unregister(struct mtd_info *master)
|
|||||||
if (master->_reboot)
|
if (master->_reboot)
|
||||||
unregister_reboot_notifier(&master->reboot_notifier);
|
unregister_reboot_notifier(&master->reboot_notifier);
|
||||||
|
|
||||||
|
if (master->otp_user_nvmem)
|
||||||
|
nvmem_unregister(master->otp_user_nvmem);
|
||||||
|
|
||||||
|
if (master->otp_factory_nvmem)
|
||||||
|
nvmem_unregister(master->otp_factory_nvmem);
|
||||||
|
|
||||||
err = del_mtd_partitions(master);
|
err = del_mtd_partitions(master);
|
||||||
if (err)
|
if (err)
|
||||||
return err;
|
return err;
|
||||||
|
@ -380,6 +380,8 @@ struct mtd_info {
|
|||||||
int usecount;
|
int usecount;
|
||||||
struct mtd_debug_info dbg;
|
struct mtd_debug_info dbg;
|
||||||
struct nvmem_device *nvmem;
|
struct nvmem_device *nvmem;
|
||||||
|
struct nvmem_device *otp_user_nvmem;
|
||||||
|
struct nvmem_device *otp_factory_nvmem;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parent device from the MTD partition point of view.
|
* Parent device from the MTD partition point of view.
|
||||||
|
Loading…
Reference in New Issue
Block a user