162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * OTP support for SPI NOR flashes 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2021 Michael Walle <michael@walle.cc> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/log2.h> 962306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 1062306a36Sopenharmony_ci#include <linux/mtd/spi-nor.h> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include "core.h" 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#define spi_nor_otp_region_len(nor) ((nor)->params->otp.org->len) 1562306a36Sopenharmony_ci#define spi_nor_otp_n_regions(nor) ((nor)->params->otp.org->n_regions) 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci/** 1862306a36Sopenharmony_ci * spi_nor_otp_read_secr() - read security register 1962306a36Sopenharmony_ci * @nor: pointer to 'struct spi_nor' 2062306a36Sopenharmony_ci * @addr: offset to read from 2162306a36Sopenharmony_ci * @len: number of bytes to read 2262306a36Sopenharmony_ci * @buf: pointer to dst buffer 2362306a36Sopenharmony_ci * 2462306a36Sopenharmony_ci * Read a security register by using the SPINOR_OP_RSECR commands. 2562306a36Sopenharmony_ci * 2662306a36Sopenharmony_ci * In Winbond/GigaDevice datasheets the term "security register" stands for 2762306a36Sopenharmony_ci * an one-time-programmable memory area, consisting of multiple bytes (usually 2862306a36Sopenharmony_ci * 256). Thus one "security register" maps to one OTP region. 2962306a36Sopenharmony_ci * 3062306a36Sopenharmony_ci * This method is used on GigaDevice and Winbond flashes. 3162306a36Sopenharmony_ci * 3262306a36Sopenharmony_ci * Please note, the read must not span multiple registers. 3362306a36Sopenharmony_ci * 3462306a36Sopenharmony_ci * Return: number of bytes read successfully, -errno otherwise 3562306a36Sopenharmony_ci */ 3662306a36Sopenharmony_ciint spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf) 3762306a36Sopenharmony_ci{ 3862306a36Sopenharmony_ci u8 addr_nbytes, read_opcode, read_dummy; 3962306a36Sopenharmony_ci struct spi_mem_dirmap_desc *rdesc; 4062306a36Sopenharmony_ci enum spi_nor_protocol read_proto; 4162306a36Sopenharmony_ci int ret; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci read_opcode = nor->read_opcode; 4462306a36Sopenharmony_ci addr_nbytes = nor->addr_nbytes; 4562306a36Sopenharmony_ci read_dummy = nor->read_dummy; 4662306a36Sopenharmony_ci read_proto = nor->read_proto; 4762306a36Sopenharmony_ci rdesc = nor->dirmap.rdesc; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci nor->read_opcode = SPINOR_OP_RSECR; 5062306a36Sopenharmony_ci nor->read_dummy = 8; 5162306a36Sopenharmony_ci nor->read_proto = SNOR_PROTO_1_1_1; 5262306a36Sopenharmony_ci nor->dirmap.rdesc = NULL; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci ret = spi_nor_read_data(nor, addr, len, buf); 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci nor->read_opcode = read_opcode; 5762306a36Sopenharmony_ci nor->addr_nbytes = addr_nbytes; 5862306a36Sopenharmony_ci nor->read_dummy = read_dummy; 5962306a36Sopenharmony_ci nor->read_proto = read_proto; 6062306a36Sopenharmony_ci nor->dirmap.rdesc = rdesc; 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci return ret; 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci/** 6662306a36Sopenharmony_ci * spi_nor_otp_write_secr() - write security register 6762306a36Sopenharmony_ci * @nor: pointer to 'struct spi_nor' 6862306a36Sopenharmony_ci * @addr: offset to write to 6962306a36Sopenharmony_ci * @len: number of bytes to write 7062306a36Sopenharmony_ci * @buf: pointer to src buffer 7162306a36Sopenharmony_ci * 7262306a36Sopenharmony_ci * Write a security register by using the SPINOR_OP_PSECR commands. 7362306a36Sopenharmony_ci * 7462306a36Sopenharmony_ci * For more information on the term "security register", see the documentation 7562306a36Sopenharmony_ci * of spi_nor_otp_read_secr(). 7662306a36Sopenharmony_ci * 7762306a36Sopenharmony_ci * This method is used on GigaDevice and Winbond flashes. 7862306a36Sopenharmony_ci * 7962306a36Sopenharmony_ci * Please note, the write must not span multiple registers. 8062306a36Sopenharmony_ci * 8162306a36Sopenharmony_ci * Return: number of bytes written successfully, -errno otherwise 8262306a36Sopenharmony_ci */ 8362306a36Sopenharmony_ciint spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, size_t len, 8462306a36Sopenharmony_ci const u8 *buf) 8562306a36Sopenharmony_ci{ 8662306a36Sopenharmony_ci enum spi_nor_protocol write_proto; 8762306a36Sopenharmony_ci struct spi_mem_dirmap_desc *wdesc; 8862306a36Sopenharmony_ci u8 addr_nbytes, program_opcode; 8962306a36Sopenharmony_ci int ret, written; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci program_opcode = nor->program_opcode; 9262306a36Sopenharmony_ci addr_nbytes = nor->addr_nbytes; 9362306a36Sopenharmony_ci write_proto = nor->write_proto; 9462306a36Sopenharmony_ci wdesc = nor->dirmap.wdesc; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci nor->program_opcode = SPINOR_OP_PSECR; 9762306a36Sopenharmony_ci nor->write_proto = SNOR_PROTO_1_1_1; 9862306a36Sopenharmony_ci nor->dirmap.wdesc = NULL; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci /* 10162306a36Sopenharmony_ci * We only support a write to one single page. For now all winbond 10262306a36Sopenharmony_ci * flashes only have one page per security register. 10362306a36Sopenharmony_ci */ 10462306a36Sopenharmony_ci ret = spi_nor_write_enable(nor); 10562306a36Sopenharmony_ci if (ret) 10662306a36Sopenharmony_ci goto out; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci written = spi_nor_write_data(nor, addr, len, buf); 10962306a36Sopenharmony_ci if (written < 0) 11062306a36Sopenharmony_ci goto out; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci ret = spi_nor_wait_till_ready(nor); 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ciout: 11562306a36Sopenharmony_ci nor->program_opcode = program_opcode; 11662306a36Sopenharmony_ci nor->addr_nbytes = addr_nbytes; 11762306a36Sopenharmony_ci nor->write_proto = write_proto; 11862306a36Sopenharmony_ci nor->dirmap.wdesc = wdesc; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci return ret ?: written; 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci/** 12462306a36Sopenharmony_ci * spi_nor_otp_erase_secr() - erase a security register 12562306a36Sopenharmony_ci * @nor: pointer to 'struct spi_nor' 12662306a36Sopenharmony_ci * @addr: offset of the security register to be erased 12762306a36Sopenharmony_ci * 12862306a36Sopenharmony_ci * Erase a security register by using the SPINOR_OP_ESECR command. 12962306a36Sopenharmony_ci * 13062306a36Sopenharmony_ci * For more information on the term "security register", see the documentation 13162306a36Sopenharmony_ci * of spi_nor_otp_read_secr(). 13262306a36Sopenharmony_ci * 13362306a36Sopenharmony_ci * This method is used on GigaDevice and Winbond flashes. 13462306a36Sopenharmony_ci * 13562306a36Sopenharmony_ci * Return: 0 on success, -errno otherwise 13662306a36Sopenharmony_ci */ 13762306a36Sopenharmony_ciint spi_nor_otp_erase_secr(struct spi_nor *nor, loff_t addr) 13862306a36Sopenharmony_ci{ 13962306a36Sopenharmony_ci u8 erase_opcode = nor->erase_opcode; 14062306a36Sopenharmony_ci int ret; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci ret = spi_nor_write_enable(nor); 14362306a36Sopenharmony_ci if (ret) 14462306a36Sopenharmony_ci return ret; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci nor->erase_opcode = SPINOR_OP_ESECR; 14762306a36Sopenharmony_ci ret = spi_nor_erase_sector(nor, addr); 14862306a36Sopenharmony_ci nor->erase_opcode = erase_opcode; 14962306a36Sopenharmony_ci if (ret) 15062306a36Sopenharmony_ci return ret; 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci return spi_nor_wait_till_ready(nor); 15362306a36Sopenharmony_ci} 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_cistatic int spi_nor_otp_lock_bit_cr(unsigned int region) 15662306a36Sopenharmony_ci{ 15762306a36Sopenharmony_ci static const int lock_bits[] = { SR2_LB1, SR2_LB2, SR2_LB3 }; 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (region >= ARRAY_SIZE(lock_bits)) 16062306a36Sopenharmony_ci return -EINVAL; 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci return lock_bits[region]; 16362306a36Sopenharmony_ci} 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci/** 16662306a36Sopenharmony_ci * spi_nor_otp_lock_sr2() - lock the OTP region 16762306a36Sopenharmony_ci * @nor: pointer to 'struct spi_nor' 16862306a36Sopenharmony_ci * @region: OTP region 16962306a36Sopenharmony_ci * 17062306a36Sopenharmony_ci * Lock the OTP region by writing the status register-2. This method is used on 17162306a36Sopenharmony_ci * GigaDevice and Winbond flashes. 17262306a36Sopenharmony_ci * 17362306a36Sopenharmony_ci * Return: 0 on success, -errno otherwise. 17462306a36Sopenharmony_ci */ 17562306a36Sopenharmony_ciint spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci u8 *cr = nor->bouncebuf; 17862306a36Sopenharmony_ci int ret, lock_bit; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci lock_bit = spi_nor_otp_lock_bit_cr(region); 18162306a36Sopenharmony_ci if (lock_bit < 0) 18262306a36Sopenharmony_ci return lock_bit; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci ret = spi_nor_read_cr(nor, cr); 18562306a36Sopenharmony_ci if (ret) 18662306a36Sopenharmony_ci return ret; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci /* no need to write the register if region is already locked */ 18962306a36Sopenharmony_ci if (cr[0] & lock_bit) 19062306a36Sopenharmony_ci return 0; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci cr[0] |= lock_bit; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci return spi_nor_write_16bit_cr_and_check(nor, cr[0]); 19562306a36Sopenharmony_ci} 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci/** 19862306a36Sopenharmony_ci * spi_nor_otp_is_locked_sr2() - get the OTP region lock status 19962306a36Sopenharmony_ci * @nor: pointer to 'struct spi_nor' 20062306a36Sopenharmony_ci * @region: OTP region 20162306a36Sopenharmony_ci * 20262306a36Sopenharmony_ci * Retrieve the OTP region lock bit by reading the status register-2. This 20362306a36Sopenharmony_ci * method is used on GigaDevice and Winbond flashes. 20462306a36Sopenharmony_ci * 20562306a36Sopenharmony_ci * Return: 0 on success, -errno otherwise. 20662306a36Sopenharmony_ci */ 20762306a36Sopenharmony_ciint spi_nor_otp_is_locked_sr2(struct spi_nor *nor, unsigned int region) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci u8 *cr = nor->bouncebuf; 21062306a36Sopenharmony_ci int ret, lock_bit; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci lock_bit = spi_nor_otp_lock_bit_cr(region); 21362306a36Sopenharmony_ci if (lock_bit < 0) 21462306a36Sopenharmony_ci return lock_bit; 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci ret = spi_nor_read_cr(nor, cr); 21762306a36Sopenharmony_ci if (ret) 21862306a36Sopenharmony_ci return ret; 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci return cr[0] & lock_bit; 22162306a36Sopenharmony_ci} 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_cistatic loff_t spi_nor_otp_region_start(const struct spi_nor *nor, unsigned int region) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci const struct spi_nor_otp_organization *org = nor->params->otp.org; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci return org->base + region * org->offset; 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_cistatic size_t spi_nor_otp_size(struct spi_nor *nor) 23162306a36Sopenharmony_ci{ 23262306a36Sopenharmony_ci return spi_nor_otp_n_regions(nor) * spi_nor_otp_region_len(nor); 23362306a36Sopenharmony_ci} 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci/* Translate the file offsets from and to OTP regions. */ 23662306a36Sopenharmony_cistatic loff_t spi_nor_otp_region_to_offset(struct spi_nor *nor, unsigned int region) 23762306a36Sopenharmony_ci{ 23862306a36Sopenharmony_ci return region * spi_nor_otp_region_len(nor); 23962306a36Sopenharmony_ci} 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_cistatic unsigned int spi_nor_otp_offset_to_region(struct spi_nor *nor, loff_t ofs) 24262306a36Sopenharmony_ci{ 24362306a36Sopenharmony_ci return div64_u64(ofs, spi_nor_otp_region_len(nor)); 24462306a36Sopenharmony_ci} 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_cistatic int spi_nor_mtd_otp_info(struct mtd_info *mtd, size_t len, 24762306a36Sopenharmony_ci size_t *retlen, struct otp_info *buf) 24862306a36Sopenharmony_ci{ 24962306a36Sopenharmony_ci struct spi_nor *nor = mtd_to_spi_nor(mtd); 25062306a36Sopenharmony_ci const struct spi_nor_otp_ops *ops = nor->params->otp.ops; 25162306a36Sopenharmony_ci unsigned int n_regions = spi_nor_otp_n_regions(nor); 25262306a36Sopenharmony_ci unsigned int i; 25362306a36Sopenharmony_ci int ret, locked; 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci if (len < n_regions * sizeof(*buf)) 25662306a36Sopenharmony_ci return -ENOSPC; 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci ret = spi_nor_prep_and_lock(nor); 25962306a36Sopenharmony_ci if (ret) 26062306a36Sopenharmony_ci return ret; 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci for (i = 0; i < n_regions; i++) { 26362306a36Sopenharmony_ci buf->start = spi_nor_otp_region_to_offset(nor, i); 26462306a36Sopenharmony_ci buf->length = spi_nor_otp_region_len(nor); 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci locked = ops->is_locked(nor, i); 26762306a36Sopenharmony_ci if (locked < 0) { 26862306a36Sopenharmony_ci ret = locked; 26962306a36Sopenharmony_ci goto out; 27062306a36Sopenharmony_ci } 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_ci buf->locked = !!locked; 27362306a36Sopenharmony_ci buf++; 27462306a36Sopenharmony_ci } 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_ci *retlen = n_regions * sizeof(*buf); 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ciout: 27962306a36Sopenharmony_ci spi_nor_unlock_and_unprep(nor); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci return ret; 28262306a36Sopenharmony_ci} 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_cistatic int spi_nor_mtd_otp_range_is_locked(struct spi_nor *nor, loff_t ofs, 28562306a36Sopenharmony_ci size_t len) 28662306a36Sopenharmony_ci{ 28762306a36Sopenharmony_ci const struct spi_nor_otp_ops *ops = nor->params->otp.ops; 28862306a36Sopenharmony_ci unsigned int region; 28962306a36Sopenharmony_ci int locked; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci /* 29262306a36Sopenharmony_ci * If any of the affected OTP regions are locked the entire range is 29362306a36Sopenharmony_ci * considered locked. 29462306a36Sopenharmony_ci */ 29562306a36Sopenharmony_ci for (region = spi_nor_otp_offset_to_region(nor, ofs); 29662306a36Sopenharmony_ci region <= spi_nor_otp_offset_to_region(nor, ofs + len - 1); 29762306a36Sopenharmony_ci region++) { 29862306a36Sopenharmony_ci locked = ops->is_locked(nor, region); 29962306a36Sopenharmony_ci /* take the branch it is locked or in case of an error */ 30062306a36Sopenharmony_ci if (locked) 30162306a36Sopenharmony_ci return locked; 30262306a36Sopenharmony_ci } 30362306a36Sopenharmony_ci 30462306a36Sopenharmony_ci return 0; 30562306a36Sopenharmony_ci} 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_cistatic int spi_nor_mtd_otp_read_write(struct mtd_info *mtd, loff_t ofs, 30862306a36Sopenharmony_ci size_t total_len, size_t *retlen, 30962306a36Sopenharmony_ci const u8 *buf, bool is_write) 31062306a36Sopenharmony_ci{ 31162306a36Sopenharmony_ci struct spi_nor *nor = mtd_to_spi_nor(mtd); 31262306a36Sopenharmony_ci const struct spi_nor_otp_ops *ops = nor->params->otp.ops; 31362306a36Sopenharmony_ci const size_t rlen = spi_nor_otp_region_len(nor); 31462306a36Sopenharmony_ci loff_t rstart, rofs; 31562306a36Sopenharmony_ci unsigned int region; 31662306a36Sopenharmony_ci size_t len; 31762306a36Sopenharmony_ci int ret; 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci if (ofs < 0 || ofs >= spi_nor_otp_size(nor)) 32062306a36Sopenharmony_ci return 0; 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci /* don't access beyond the end */ 32362306a36Sopenharmony_ci total_len = min_t(size_t, total_len, spi_nor_otp_size(nor) - ofs); 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci if (!total_len) 32662306a36Sopenharmony_ci return 0; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci ret = spi_nor_prep_and_lock(nor); 32962306a36Sopenharmony_ci if (ret) 33062306a36Sopenharmony_ci return ret; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci if (is_write) { 33362306a36Sopenharmony_ci ret = spi_nor_mtd_otp_range_is_locked(nor, ofs, total_len); 33462306a36Sopenharmony_ci if (ret < 0) { 33562306a36Sopenharmony_ci goto out; 33662306a36Sopenharmony_ci } else if (ret) { 33762306a36Sopenharmony_ci ret = -EROFS; 33862306a36Sopenharmony_ci goto out; 33962306a36Sopenharmony_ci } 34062306a36Sopenharmony_ci } 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci while (total_len) { 34362306a36Sopenharmony_ci /* 34462306a36Sopenharmony_ci * The OTP regions are mapped into a contiguous area starting 34562306a36Sopenharmony_ci * at 0 as expected by the MTD layer. This will map the MTD 34662306a36Sopenharmony_ci * file offsets to the address of an OTP region as used in the 34762306a36Sopenharmony_ci * actual SPI commands. 34862306a36Sopenharmony_ci */ 34962306a36Sopenharmony_ci region = spi_nor_otp_offset_to_region(nor, ofs); 35062306a36Sopenharmony_ci rstart = spi_nor_otp_region_start(nor, region); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci /* 35362306a36Sopenharmony_ci * The size of a OTP region is expected to be a power of two, 35462306a36Sopenharmony_ci * thus we can just mask the lower bits and get the offset into 35562306a36Sopenharmony_ci * a region. 35662306a36Sopenharmony_ci */ 35762306a36Sopenharmony_ci rofs = ofs & (rlen - 1); 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_ci /* don't access beyond one OTP region */ 36062306a36Sopenharmony_ci len = min_t(size_t, total_len, rlen - rofs); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci if (is_write) 36362306a36Sopenharmony_ci ret = ops->write(nor, rstart + rofs, len, buf); 36462306a36Sopenharmony_ci else 36562306a36Sopenharmony_ci ret = ops->read(nor, rstart + rofs, len, (u8 *)buf); 36662306a36Sopenharmony_ci if (ret == 0) 36762306a36Sopenharmony_ci ret = -EIO; 36862306a36Sopenharmony_ci if (ret < 0) 36962306a36Sopenharmony_ci goto out; 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci *retlen += ret; 37262306a36Sopenharmony_ci ofs += ret; 37362306a36Sopenharmony_ci buf += ret; 37462306a36Sopenharmony_ci total_len -= ret; 37562306a36Sopenharmony_ci } 37662306a36Sopenharmony_ci ret = 0; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ciout: 37962306a36Sopenharmony_ci spi_nor_unlock_and_unprep(nor); 38062306a36Sopenharmony_ci return ret; 38162306a36Sopenharmony_ci} 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_cistatic int spi_nor_mtd_otp_read(struct mtd_info *mtd, loff_t from, size_t len, 38462306a36Sopenharmony_ci size_t *retlen, u8 *buf) 38562306a36Sopenharmony_ci{ 38662306a36Sopenharmony_ci return spi_nor_mtd_otp_read_write(mtd, from, len, retlen, buf, false); 38762306a36Sopenharmony_ci} 38862306a36Sopenharmony_ci 38962306a36Sopenharmony_cistatic int spi_nor_mtd_otp_write(struct mtd_info *mtd, loff_t to, size_t len, 39062306a36Sopenharmony_ci size_t *retlen, const u8 *buf) 39162306a36Sopenharmony_ci{ 39262306a36Sopenharmony_ci return spi_nor_mtd_otp_read_write(mtd, to, len, retlen, buf, true); 39362306a36Sopenharmony_ci} 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_cistatic int spi_nor_mtd_otp_erase(struct mtd_info *mtd, loff_t from, size_t len) 39662306a36Sopenharmony_ci{ 39762306a36Sopenharmony_ci struct spi_nor *nor = mtd_to_spi_nor(mtd); 39862306a36Sopenharmony_ci const struct spi_nor_otp_ops *ops = nor->params->otp.ops; 39962306a36Sopenharmony_ci const size_t rlen = spi_nor_otp_region_len(nor); 40062306a36Sopenharmony_ci unsigned int region; 40162306a36Sopenharmony_ci loff_t rstart; 40262306a36Sopenharmony_ci int ret; 40362306a36Sopenharmony_ci 40462306a36Sopenharmony_ci /* OTP erase is optional */ 40562306a36Sopenharmony_ci if (!ops->erase) 40662306a36Sopenharmony_ci return -EOPNOTSUPP; 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci if (!len) 40962306a36Sopenharmony_ci return 0; 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci if (from < 0 || (from + len) > spi_nor_otp_size(nor)) 41262306a36Sopenharmony_ci return -EINVAL; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci /* the user has to explicitly ask for whole regions */ 41562306a36Sopenharmony_ci if (!IS_ALIGNED(len, rlen) || !IS_ALIGNED(from, rlen)) 41662306a36Sopenharmony_ci return -EINVAL; 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci ret = spi_nor_prep_and_lock(nor); 41962306a36Sopenharmony_ci if (ret) 42062306a36Sopenharmony_ci return ret; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci ret = spi_nor_mtd_otp_range_is_locked(nor, from, len); 42362306a36Sopenharmony_ci if (ret < 0) { 42462306a36Sopenharmony_ci goto out; 42562306a36Sopenharmony_ci } else if (ret) { 42662306a36Sopenharmony_ci ret = -EROFS; 42762306a36Sopenharmony_ci goto out; 42862306a36Sopenharmony_ci } 42962306a36Sopenharmony_ci 43062306a36Sopenharmony_ci while (len) { 43162306a36Sopenharmony_ci region = spi_nor_otp_offset_to_region(nor, from); 43262306a36Sopenharmony_ci rstart = spi_nor_otp_region_start(nor, region); 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci ret = ops->erase(nor, rstart); 43562306a36Sopenharmony_ci if (ret) 43662306a36Sopenharmony_ci goto out; 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci len -= rlen; 43962306a36Sopenharmony_ci from += rlen; 44062306a36Sopenharmony_ci } 44162306a36Sopenharmony_ci 44262306a36Sopenharmony_ciout: 44362306a36Sopenharmony_ci spi_nor_unlock_and_unprep(nor); 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci return ret; 44662306a36Sopenharmony_ci} 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_cistatic int spi_nor_mtd_otp_lock(struct mtd_info *mtd, loff_t from, size_t len) 44962306a36Sopenharmony_ci{ 45062306a36Sopenharmony_ci struct spi_nor *nor = mtd_to_spi_nor(mtd); 45162306a36Sopenharmony_ci const struct spi_nor_otp_ops *ops = nor->params->otp.ops; 45262306a36Sopenharmony_ci const size_t rlen = spi_nor_otp_region_len(nor); 45362306a36Sopenharmony_ci unsigned int region; 45462306a36Sopenharmony_ci int ret; 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci if (from < 0 || (from + len) > spi_nor_otp_size(nor)) 45762306a36Sopenharmony_ci return -EINVAL; 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci /* the user has to explicitly ask for whole regions */ 46062306a36Sopenharmony_ci if (!IS_ALIGNED(len, rlen) || !IS_ALIGNED(from, rlen)) 46162306a36Sopenharmony_ci return -EINVAL; 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci ret = spi_nor_prep_and_lock(nor); 46462306a36Sopenharmony_ci if (ret) 46562306a36Sopenharmony_ci return ret; 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci while (len) { 46862306a36Sopenharmony_ci region = spi_nor_otp_offset_to_region(nor, from); 46962306a36Sopenharmony_ci ret = ops->lock(nor, region); 47062306a36Sopenharmony_ci if (ret) 47162306a36Sopenharmony_ci goto out; 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_ci len -= rlen; 47462306a36Sopenharmony_ci from += rlen; 47562306a36Sopenharmony_ci } 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ciout: 47862306a36Sopenharmony_ci spi_nor_unlock_and_unprep(nor); 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci return ret; 48162306a36Sopenharmony_ci} 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_civoid spi_nor_set_mtd_otp_ops(struct spi_nor *nor) 48462306a36Sopenharmony_ci{ 48562306a36Sopenharmony_ci struct mtd_info *mtd = &nor->mtd; 48662306a36Sopenharmony_ci 48762306a36Sopenharmony_ci if (!nor->params->otp.ops) 48862306a36Sopenharmony_ci return; 48962306a36Sopenharmony_ci 49062306a36Sopenharmony_ci if (WARN_ON(!is_power_of_2(spi_nor_otp_region_len(nor)))) 49162306a36Sopenharmony_ci return; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci /* 49462306a36Sopenharmony_ci * We only support user_prot callbacks (yet). 49562306a36Sopenharmony_ci * 49662306a36Sopenharmony_ci * Some SPI NOR flashes like Macronix ones can be ordered in two 49762306a36Sopenharmony_ci * different variants. One with a factory locked OTP area and one where 49862306a36Sopenharmony_ci * it is left to the user to write to it. The factory locked OTP is 49962306a36Sopenharmony_ci * usually preprogrammed with an "electrical serial number". We don't 50062306a36Sopenharmony_ci * support these for now. 50162306a36Sopenharmony_ci */ 50262306a36Sopenharmony_ci mtd->_get_user_prot_info = spi_nor_mtd_otp_info; 50362306a36Sopenharmony_ci mtd->_read_user_prot_reg = spi_nor_mtd_otp_read; 50462306a36Sopenharmony_ci mtd->_write_user_prot_reg = spi_nor_mtd_otp_write; 50562306a36Sopenharmony_ci mtd->_lock_user_prot_reg = spi_nor_mtd_otp_lock; 50662306a36Sopenharmony_ci mtd->_erase_user_prot_reg = spi_nor_mtd_otp_erase; 50762306a36Sopenharmony_ci} 508