// SPDX-License-Identifier: GPL-2.0
/*
* OTP support for SPI NOR flashes
*
* Copyright ( C ) 2021 Michael Walle < michael @ walle . cc >
*/
#include <linux/log2.h>
#include <linux/math64.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/spi-nor.h>
#include "core.h"
#define spi_nor_otp_region_len(nor) ((nor)->params->otp.org->len)
#define spi_nor_otp_n_regions(nor) ((nor)->params->otp.org->n_regions)
/**
* spi_nor_otp_read_secr ( ) - read security register
* @ nor : pointer to ' struct spi_nor '
* @ addr : offset to read from
* @ len : number of bytes to read
* @ buf : pointer to dst buffer
*
* Read a security register by using the SPINOR_OP_RSECR commands .
*
* In Winbond / GigaDevice datasheets the term " security register " stands for
* an one - time - programmable memory area , consisting of multiple bytes ( usually
* 256 ) . Thus one " security register " maps to one OTP region .
*
* This method is used on GigaDevice and Winbond flashes .
*
* Please note , the read must not span multiple registers .
*
* Return : number of bytes read successfully , - errno otherwise
*/
int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf)
{
u8 addr_nbytes, read_opcode, read_dummy;
struct spi_mem_dirmap_desc *rdesc;
enum spi_nor_protocol read_proto;
int ret;
read_opcode = nor->read_opcode;
addr_nbytes = nor->addr_nbytes;
read_dummy = nor->read_dummy;
read_proto = nor->read_proto;
rdesc = nor->dirmap.rdesc;
nor->read_opcode = SPINOR_OP_RSECR;
nor->read_dummy = 8 ;
nor->read_proto = SNOR_PROTO_1_1_1;
nor->dirmap.rdesc = NULL;
ret = spi_nor_read_data(nor, addr, len, buf);
nor->read_opcode = read_opcode;
nor->addr_nbytes = addr_nbytes;
nor->read_dummy = read_dummy;
nor->read_proto = read_proto;
nor->dirmap.rdesc = rdesc;
return ret;
}
/**
* spi_nor_otp_write_secr ( ) - write security register
* @ nor : pointer to ' struct spi_nor '
* @ addr : offset to write to
* @ len : number of bytes to write
* @ buf : pointer to src buffer
*
* Write a security register by using the SPINOR_OP_PSECR commands .
*
* 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 .
*
* Please note , the write must not span multiple registers .
*
* Return : number of bytes written successfully , - errno otherwise
*/
int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, size_t len,
const u8 *buf)
{
enum spi_nor_protocol write_proto;
struct spi_mem_dirmap_desc *wdesc;
u8 addr_nbytes, program_opcode;
int ret, written;
program_opcode = nor->program_opcode;
addr_nbytes = nor->addr_nbytes;
write_proto = nor->write_proto;
wdesc = nor->dirmap.wdesc;
nor->program_opcode = SPINOR_OP_PSECR;
nor->write_proto = SNOR_PROTO_1_1_1;
nor->dirmap.wdesc = NULL;
/*
* We only support a write to one single page . For now all winbond
* flashes only have one page per security register .
*/
ret = spi_nor_write_enable(nor);
if (ret)
goto out;
written = spi_nor_write_data(nor, addr, len, buf);
if (written < 0 )
goto out;
ret = spi_nor_wait_till_ready(nor);
out:
nor->program_opcode = program_opcode;
nor->addr_nbytes = addr_nbytes;
nor->write_proto = write_proto;
nor->dirmap.wdesc = wdesc;
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 };
if (region >= ARRAY_SIZE(lock_bits))
return -EINVAL;
return lock_bits[region];
}
/**
* spi_nor_otp_lock_sr2 ( ) - lock the OTP region
* @ nor : pointer to ' struct spi_nor '
* @ region : OTP region
*
* Lock the OTP region by writing the status register - 2 . This method is used on
* GigaDevice and Winbond flashes .
*
* Return : 0 on success , - errno otherwise .
*/
int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region)
{
u8 *cr = nor->bouncebuf;
int ret, lock_bit;
lock_bit = spi_nor_otp_lock_bit_cr(region);
if (lock_bit < 0 )
return lock_bit;
ret = spi_nor_read_cr(nor, cr);
if (ret)
return ret;
/* no need to write the register if region is already locked */
if (cr[0 ] & lock_bit)
return 0 ;
cr[0 ] |= lock_bit;
return spi_nor_write_16bit_cr_and_check(nor, cr[0 ]);
}
/**
* spi_nor_otp_is_locked_sr2 ( ) - get the OTP region lock status
* @ nor : pointer to ' struct spi_nor '
* @ region : OTP region
*
* Retrieve the OTP region lock bit by reading the status register - 2 . This
* method is used on GigaDevice and Winbond flashes .
*
* Return : 0 on success , - errno otherwise .
*/
int spi_nor_otp_is_locked_sr2(struct spi_nor *nor, unsigned int region)
{
u8 *cr = nor->bouncebuf;
int ret, lock_bit;
lock_bit = spi_nor_otp_lock_bit_cr(region);
if (lock_bit < 0 )
return lock_bit;
ret = spi_nor_read_cr(nor, cr);
if (ret)
return ret;
return cr[0 ] & lock_bit;
}
static loff_t spi_nor_otp_region_start(const struct spi_nor *nor, unsigned int region)
{
const struct spi_nor_otp_organization *org = nor->params->otp.org;
return org->base + region * org->offset;
}
static size_t spi_nor_otp_size(struct spi_nor *nor)
{
return spi_nor_otp_n_regions(nor) * spi_nor_otp_region_len(nor);
}
/* Translate the file offsets from and to OTP regions. */
static loff_t spi_nor_otp_region_to_offset(struct spi_nor *nor, unsigned int region)
{
return region * spi_nor_otp_region_len(nor);
}
static unsigned int spi_nor_otp_offset_to_region(struct spi_nor *nor, loff_t ofs)
{
return div64_u64(ofs, spi_nor_otp_region_len(nor));
}
static int spi_nor_mtd_otp_info(struct mtd_info *mtd, size_t len,
size_t *retlen, struct otp_info *buf)
{
struct spi_nor *nor = mtd_to_spi_nor(mtd);
const struct spi_nor_otp_ops *ops = nor->params->otp.ops;
unsigned int n_regions = spi_nor_otp_n_regions(nor);
unsigned int i;
int ret, locked;
if (len < n_regions * sizeof (*buf))
return -ENOSPC;
ret = spi_nor_prep_and_lock(nor);
if (ret)
return ret;
for (i = 0 ; i < n_regions; i++) {
buf->start = spi_nor_otp_region_to_offset(nor, i);
buf->length = spi_nor_otp_region_len(nor);
locked = ops->is_locked(nor, i);
if (locked < 0 ) {
ret = locked;
goto out;
}
buf->locked = !!locked;
buf++;
}
*retlen = n_regions * sizeof (*buf);
out:
spi_nor_unlock_and_unprep(nor);
return ret;
}
static int spi_nor_mtd_otp_range_is_locked(struct spi_nor *nor, loff_t ofs,
size_t len)
{
const struct spi_nor_otp_ops *ops = nor->params->otp.ops;
unsigned int region;
int locked;
/*
* If any of the affected OTP regions are locked the entire range is
* considered locked .
*/
for (region = spi_nor_otp_offset_to_region(nor, ofs);
region <= spi_nor_otp_offset_to_region(nor, ofs + len - 1 );
region++) {
locked = ops->is_locked(nor, region);
/* take the branch it is locked or in case of an error */
if (locked)
return locked;
}
return 0 ;
}
static int spi_nor_mtd_otp_read_write(struct mtd_info *mtd, loff_t ofs,
size_t total_len, size_t *retlen,
const u8 *buf, bool is_write)
{
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);
loff_t rstart, rofs;
unsigned int region;
size_t len;
int ret;
if (ofs < 0 || ofs >= spi_nor_otp_size(nor))
return 0 ;
/* don't access beyond the end */
total_len = min_t(size_t, total_len, spi_nor_otp_size(nor) - ofs);
if (!total_len)
return 0 ;
ret = spi_nor_prep_and_lock(nor);
if (ret)
return ret;
if (is_write) {
ret = spi_nor_mtd_otp_range_is_locked(nor, ofs, total_len);
if (ret < 0 ) {
goto out;
} else if (ret) {
ret = -EROFS;
goto out;
}
}
while (total_len) {
/*
* The OTP regions are mapped into a contiguous area starting
* at 0 as expected by the MTD layer . This will map the MTD
* file offsets to the address of an OTP region as used in the
* actual SPI commands .
*/
region = spi_nor_otp_offset_to_region(nor, ofs);
rstart = spi_nor_otp_region_start(nor, region);
/*
* The size of a OTP region is expected to be a power of two ,
* thus we can just mask the lower bits and get the offset into
* a region .
*/
rofs = ofs & (rlen - 1 );
/* don't access beyond one OTP region */
len = min_t(size_t, total_len, rlen - rofs);
if (is_write)
ret = ops->write(nor, rstart + rofs, len, buf);
else
ret = ops->read(nor, rstart + rofs, len, (u8 *)buf);
if (ret == 0 )
ret = -EIO;
if (ret < 0 )
goto out;
*retlen += ret;
ofs += ret;
buf += ret;
total_len -= ret;
}
ret = 0 ;
out:
spi_nor_unlock_and_unprep(nor);
return ret;
}
static int spi_nor_mtd_otp_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u8 *buf)
{
return spi_nor_mtd_otp_read_write(mtd, from, len, retlen, buf, false );
}
static int spi_nor_mtd_otp_write(struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u8 *buf)
{
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_prep_and_lock(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);
const struct spi_nor_otp_ops *ops = nor->params->otp.ops;
const size_t rlen = spi_nor_otp_region_len(nor);
unsigned int region;
int ret;
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_prep_and_lock(nor);
if (ret)
return ret;
while (len) {
region = spi_nor_otp_offset_to_region(nor, from);
ret = ops->lock(nor, region);
if (ret)
goto out;
len -= rlen;
from += rlen;
}
out:
spi_nor_unlock_and_unprep(nor);
return ret;
}
void spi_nor_set_mtd_otp_ops(struct spi_nor *nor)
{
struct mtd_info *mtd = &nor->mtd;
if (!nor->params->otp.ops)
return ;
if (WARN_ON(!is_power_of_2(spi_nor_otp_region_len(nor))))
return ;
/*
* We only support user_prot callbacks ( yet ) .
*
* Some SPI NOR flashes like Macronix ones can be ordered in two
* different variants . One with a factory locked OTP area and one where
* it is left to the user to write to it . The factory locked OTP is
* usually preprogrammed with an " electrical serial number " . We don ' t
* support these for now .
*/
mtd->_get_user_prot_info = spi_nor_mtd_otp_info;
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;
}
Messung V0.5 in Prozent C=97 H=86 G=91
¤ Dauer der Verarbeitung: 0.8 Sekunden
(vorverarbeitet am 2026-10-11)
¤
*© Formatika GbR, Deutschland