SPI NOR core changes:
- Add OTP support - Fix module unload while an op in progress - Add various cleanup patches SPI NOR controller drivers changes: - intel-spi: Move platform data header to x86 subfolder -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEEHUIqys8OyG1eHf7fS1VPR6WNFOkFAmByqyYACgkQS1VPR6WN FOkkYwgAvjQoUPZ/ZzwKbAuEseVB4DWj0goyTNNjksw5oIDmxcfDDd4gFTyRvLmb eyYMGCqnBSq9asbmzWu6H5IYWllsWgRvHuRlfKlJPj6Iqc+kAv8zaezQcY5t9lYy EopogLmc3RJdHdV3FbiRYqebATCCfwkQzDJdp7QU3EdKzQp+6AmJdClssKsxkXsm mUHmrhRF7K4jloaKfsbDwvM7DHPyhJEP/AyuDRactZzoQLhrTB/5rJ3nwpQ3k0x6 EZ53Qx08Qk9lhzR7trYOaltoTtOVc9wiIz+QGuLXfleBEfi7hRBvscx2FZKL4GwI O2Fsg4of06KKTJ3LiOuiHykXFZ+GUA== =/6EZ -----END PGP SIGNATURE----- Merge tag 'spi-nor/for-5.13' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux into mtd/next SPI NOR core changes: - Add OTP support - Fix module unload while an op in progress - Add various cleanup patches SPI NOR controller drivers changes: - intel-spi: Move platform data header to x86 subfolder
This commit is contained in:
commit
256437ebda
@ -16862,6 +16862,8 @@ F: arch/arm/mach-spear/
|
|||||||
|
|
||||||
SPI NOR SUBSYSTEM
|
SPI NOR SUBSYSTEM
|
||||||
M: Tudor Ambarus <tudor.ambarus@microchip.com>
|
M: Tudor Ambarus <tudor.ambarus@microchip.com>
|
||||||
|
R: Michael Walle <michael@walle.cc>
|
||||||
|
R: Pratyush Yadav <p.yadav@ti.com>
|
||||||
L: linux-mtd@lists.infradead.org
|
L: linux-mtd@lists.infradead.org
|
||||||
S: Maintained
|
S: Maintained
|
||||||
W: http://www.linux-mtd.infradead.org/
|
W: http://www.linux-mtd.infradead.org/
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# SPDX-License-Identifier: GPL-2.0
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
spi-nor-objs := core.o sfdp.o
|
spi-nor-objs := core.o sfdp.o swp.o otp.o
|
||||||
spi-nor-objs += atmel.o
|
spi-nor-objs += atmel.o
|
||||||
spi-nor-objs += catalyst.o
|
spi-nor-objs += catalyst.o
|
||||||
spi-nor-objs += eon.o
|
spi-nor-objs += eon.o
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
#include <linux/mtd/mtd.h>
|
#include <linux/mtd/mtd.h>
|
||||||
#include <linux/mtd/partitions.h>
|
#include <linux/mtd/partitions.h>
|
||||||
#include <linux/mtd/spi-nor.h>
|
#include <linux/mtd/spi-nor.h>
|
||||||
#include <linux/platform_data/intel-spi.h>
|
|
||||||
|
|
||||||
#include "intel-spi.h"
|
#include "intel-spi.h"
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
#ifndef INTEL_SPI_H
|
#ifndef INTEL_SPI_H
|
||||||
#define INTEL_SPI_H
|
#define INTEL_SPI_H
|
||||||
|
|
||||||
#include <linux/platform_data/intel-spi.h>
|
#include <linux/platform_data/x86/intel-spi.h>
|
||||||
|
|
||||||
struct intel_spi;
|
struct intel_spi;
|
||||||
struct resource;
|
struct resource;
|
||||||
|
@ -1034,7 +1034,7 @@ static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1)
|
|||||||
*
|
*
|
||||||
* Return: 0 on success, -errno otherwise.
|
* Return: 0 on success, -errno otherwise.
|
||||||
*/
|
*/
|
||||||
static int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr)
|
int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
u8 *sr_cr = nor->bouncebuf;
|
u8 *sr_cr = nor->bouncebuf;
|
||||||
@ -1610,6 +1610,9 @@ static int spi_nor_erase_multi_sectors(struct spi_nor *nor, u64 addr, u32 len)
|
|||||||
list_for_each_entry_safe(cmd, next, &erase_list, list) {
|
list_for_each_entry_safe(cmd, next, &erase_list, list) {
|
||||||
nor->erase_opcode = cmd->opcode;
|
nor->erase_opcode = cmd->opcode;
|
||||||
while (cmd->count) {
|
while (cmd->count) {
|
||||||
|
dev_vdbg(nor->dev, "erase_cmd->size = 0x%08x, erase_cmd->opcode = 0x%02x, erase_cmd->count = %u\n",
|
||||||
|
cmd->size, cmd->opcode, cmd->count);
|
||||||
|
|
||||||
ret = spi_nor_write_enable(nor);
|
ret = spi_nor_write_enable(nor);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto destroy_erase_cmd_list;
|
goto destroy_erase_cmd_list;
|
||||||
@ -1618,12 +1621,12 @@ static int spi_nor_erase_multi_sectors(struct spi_nor *nor, u64 addr, u32 len)
|
|||||||
if (ret)
|
if (ret)
|
||||||
goto destroy_erase_cmd_list;
|
goto destroy_erase_cmd_list;
|
||||||
|
|
||||||
addr += cmd->size;
|
|
||||||
cmd->count--;
|
|
||||||
|
|
||||||
ret = spi_nor_wait_till_ready(nor);
|
ret = spi_nor_wait_till_ready(nor);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto destroy_erase_cmd_list;
|
goto destroy_erase_cmd_list;
|
||||||
|
|
||||||
|
addr += cmd->size;
|
||||||
|
cmd->count--;
|
||||||
}
|
}
|
||||||
list_del(&cmd->list);
|
list_del(&cmd->list);
|
||||||
kfree(cmd);
|
kfree(cmd);
|
||||||
@ -1704,12 +1707,12 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
|
|||||||
if (ret)
|
if (ret)
|
||||||
goto erase_err;
|
goto erase_err;
|
||||||
|
|
||||||
addr += mtd->erasesize;
|
|
||||||
len -= mtd->erasesize;
|
|
||||||
|
|
||||||
ret = spi_nor_wait_till_ready(nor);
|
ret = spi_nor_wait_till_ready(nor);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto erase_err;
|
goto erase_err;
|
||||||
|
|
||||||
|
addr += mtd->erasesize;
|
||||||
|
len -= mtd->erasesize;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* erase multiple sectors */
|
/* erase multiple sectors */
|
||||||
@ -1727,376 +1730,6 @@ erase_err:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static u8 spi_nor_get_sr_bp_mask(struct spi_nor *nor)
|
|
||||||
{
|
|
||||||
u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
|
|
||||||
|
|
||||||
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6)
|
|
||||||
return mask | SR_BP3_BIT6;
|
|
||||||
|
|
||||||
if (nor->flags & SNOR_F_HAS_4BIT_BP)
|
|
||||||
return mask | SR_BP3;
|
|
||||||
|
|
||||||
return mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor)
|
|
||||||
{
|
|
||||||
if (nor->flags & SNOR_F_HAS_SR_TB_BIT6)
|
|
||||||
return SR_TB_BIT6;
|
|
||||||
else
|
|
||||||
return SR_TB_BIT5;
|
|
||||||
}
|
|
||||||
|
|
||||||
static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
|
|
||||||
{
|
|
||||||
unsigned int bp_slots, bp_slots_needed;
|
|
||||||
u8 mask = spi_nor_get_sr_bp_mask(nor);
|
|
||||||
|
|
||||||
/* Reserved one for "protect none" and one for "protect all". */
|
|
||||||
bp_slots = (1 << hweight8(mask)) - 2;
|
|
||||||
bp_slots_needed = ilog2(nor->info->n_sectors);
|
|
||||||
|
|
||||||
if (bp_slots_needed > bp_slots)
|
|
||||||
return nor->info->sector_size <<
|
|
||||||
(bp_slots_needed - bp_slots);
|
|
||||||
else
|
|
||||||
return nor->info->sector_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs,
|
|
||||||
uint64_t *len)
|
|
||||||
{
|
|
||||||
struct mtd_info *mtd = &nor->mtd;
|
|
||||||
u64 min_prot_len;
|
|
||||||
u8 mask = spi_nor_get_sr_bp_mask(nor);
|
|
||||||
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
|
|
||||||
u8 bp, val = sr & mask;
|
|
||||||
|
|
||||||
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6)
|
|
||||||
val = (val & ~SR_BP3_BIT6) | SR_BP3;
|
|
||||||
|
|
||||||
bp = val >> SR_BP_SHIFT;
|
|
||||||
|
|
||||||
if (!bp) {
|
|
||||||
/* No protection */
|
|
||||||
*ofs = 0;
|
|
||||||
*len = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
min_prot_len = spi_nor_get_min_prot_length_sr(nor);
|
|
||||||
*len = min_prot_len << (bp - 1);
|
|
||||||
|
|
||||||
if (*len > mtd->size)
|
|
||||||
*len = mtd->size;
|
|
||||||
|
|
||||||
if (nor->flags & SNOR_F_HAS_SR_TB && sr & tb_mask)
|
|
||||||
*ofs = 0;
|
|
||||||
else
|
|
||||||
*ofs = mtd->size - *len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return 1 if the entire region is locked (if @locked is true) or unlocked (if
|
|
||||||
* @locked is false); 0 otherwise
|
|
||||||
*/
|
|
||||||
static int spi_nor_check_lock_status_sr(struct spi_nor *nor, loff_t ofs,
|
|
||||||
uint64_t len, u8 sr, bool locked)
|
|
||||||
{
|
|
||||||
loff_t lock_offs;
|
|
||||||
uint64_t lock_len;
|
|
||||||
|
|
||||||
if (!len)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
spi_nor_get_locked_range_sr(nor, sr, &lock_offs, &lock_len);
|
|
||||||
|
|
||||||
if (locked)
|
|
||||||
/* Requested range is a sub-range of locked range */
|
|
||||||
return (ofs + len <= lock_offs + lock_len) && (ofs >= lock_offs);
|
|
||||||
else
|
|
||||||
/* Requested range does not overlap with locked range */
|
|
||||||
return (ofs >= lock_offs + lock_len) || (ofs + len <= lock_offs);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len,
|
|
||||||
u8 sr)
|
|
||||||
{
|
|
||||||
return spi_nor_check_lock_status_sr(nor, ofs, len, sr, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len,
|
|
||||||
u8 sr)
|
|
||||||
{
|
|
||||||
return spi_nor_check_lock_status_sr(nor, ofs, len, sr, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Lock a region of the flash. Compatible with ST Micro and similar flash.
|
|
||||||
* Supports the block protection bits BP{0,1,2}/BP{0,1,2,3} in the status
|
|
||||||
* register
|
|
||||||
* (SR). Does not support these features found in newer SR bitfields:
|
|
||||||
* - SEC: sector/block protect - only handle SEC=0 (block protect)
|
|
||||||
* - CMP: complement protect - only support CMP=0 (range is not complemented)
|
|
||||||
*
|
|
||||||
* Support for the following is provided conditionally for some flash:
|
|
||||||
* - TB: top/bottom protect
|
|
||||||
*
|
|
||||||
* Sample table portion for 8MB flash (Winbond w25q64fw):
|
|
||||||
*
|
|
||||||
* SEC | TB | BP2 | BP1 | BP0 | Prot Length | Protected Portion
|
|
||||||
* --------------------------------------------------------------------------
|
|
||||||
* X | X | 0 | 0 | 0 | NONE | NONE
|
|
||||||
* 0 | 0 | 0 | 0 | 1 | 128 KB | Upper 1/64
|
|
||||||
* 0 | 0 | 0 | 1 | 0 | 256 KB | Upper 1/32
|
|
||||||
* 0 | 0 | 0 | 1 | 1 | 512 KB | Upper 1/16
|
|
||||||
* 0 | 0 | 1 | 0 | 0 | 1 MB | Upper 1/8
|
|
||||||
* 0 | 0 | 1 | 0 | 1 | 2 MB | Upper 1/4
|
|
||||||
* 0 | 0 | 1 | 1 | 0 | 4 MB | Upper 1/2
|
|
||||||
* X | X | 1 | 1 | 1 | 8 MB | ALL
|
|
||||||
* ------|-------|-------|-------|-------|---------------|-------------------
|
|
||||||
* 0 | 1 | 0 | 0 | 1 | 128 KB | Lower 1/64
|
|
||||||
* 0 | 1 | 0 | 1 | 0 | 256 KB | Lower 1/32
|
|
||||||
* 0 | 1 | 0 | 1 | 1 | 512 KB | Lower 1/16
|
|
||||||
* 0 | 1 | 1 | 0 | 0 | 1 MB | Lower 1/8
|
|
||||||
* 0 | 1 | 1 | 0 | 1 | 2 MB | Lower 1/4
|
|
||||||
* 0 | 1 | 1 | 1 | 0 | 4 MB | Lower 1/2
|
|
||||||
*
|
|
||||||
* Returns negative on errors, 0 on success.
|
|
||||||
*/
|
|
||||||
static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
|
||||||
{
|
|
||||||
struct mtd_info *mtd = &nor->mtd;
|
|
||||||
u64 min_prot_len;
|
|
||||||
int ret, status_old, status_new;
|
|
||||||
u8 mask = spi_nor_get_sr_bp_mask(nor);
|
|
||||||
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
|
|
||||||
u8 pow, val;
|
|
||||||
loff_t lock_len;
|
|
||||||
bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
|
|
||||||
bool use_top;
|
|
||||||
|
|
||||||
ret = spi_nor_read_sr(nor, nor->bouncebuf);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
status_old = nor->bouncebuf[0];
|
|
||||||
|
|
||||||
/* If nothing in our range is unlocked, we don't need to do anything */
|
|
||||||
if (spi_nor_is_locked_sr(nor, ofs, len, status_old))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* If anything below us is unlocked, we can't use 'bottom' protection */
|
|
||||||
if (!spi_nor_is_locked_sr(nor, 0, ofs, status_old))
|
|
||||||
can_be_bottom = false;
|
|
||||||
|
|
||||||
/* If anything above us is unlocked, we can't use 'top' protection */
|
|
||||||
if (!spi_nor_is_locked_sr(nor, ofs + len, mtd->size - (ofs + len),
|
|
||||||
status_old))
|
|
||||||
can_be_top = false;
|
|
||||||
|
|
||||||
if (!can_be_bottom && !can_be_top)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
/* Prefer top, if both are valid */
|
|
||||||
use_top = can_be_top;
|
|
||||||
|
|
||||||
/* lock_len: length of region that should end up locked */
|
|
||||||
if (use_top)
|
|
||||||
lock_len = mtd->size - ofs;
|
|
||||||
else
|
|
||||||
lock_len = ofs + len;
|
|
||||||
|
|
||||||
if (lock_len == mtd->size) {
|
|
||||||
val = mask;
|
|
||||||
} else {
|
|
||||||
min_prot_len = spi_nor_get_min_prot_length_sr(nor);
|
|
||||||
pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
|
|
||||||
val = pow << SR_BP_SHIFT;
|
|
||||||
|
|
||||||
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3)
|
|
||||||
val = (val & ~SR_BP3) | SR_BP3_BIT6;
|
|
||||||
|
|
||||||
if (val & ~mask)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
/* Don't "lock" with no region! */
|
|
||||||
if (!(val & mask))
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
status_new = (status_old & ~mask & ~tb_mask) | val;
|
|
||||||
|
|
||||||
/* Disallow further writes if WP pin is asserted */
|
|
||||||
status_new |= SR_SRWD;
|
|
||||||
|
|
||||||
if (!use_top)
|
|
||||||
status_new |= tb_mask;
|
|
||||||
|
|
||||||
/* Don't bother if they're the same */
|
|
||||||
if (status_new == status_old)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* Only modify protection if it will not unlock other areas */
|
|
||||||
if ((status_new & mask) < (status_old & mask))
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
return spi_nor_write_sr_and_check(nor, status_new);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Unlock a region of the flash. See spi_nor_sr_lock() for more info
|
|
||||||
*
|
|
||||||
* Returns negative on errors, 0 on success.
|
|
||||||
*/
|
|
||||||
static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
|
||||||
{
|
|
||||||
struct mtd_info *mtd = &nor->mtd;
|
|
||||||
u64 min_prot_len;
|
|
||||||
int ret, status_old, status_new;
|
|
||||||
u8 mask = spi_nor_get_sr_bp_mask(nor);
|
|
||||||
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
|
|
||||||
u8 pow, val;
|
|
||||||
loff_t lock_len;
|
|
||||||
bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
|
|
||||||
bool use_top;
|
|
||||||
|
|
||||||
ret = spi_nor_read_sr(nor, nor->bouncebuf);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
status_old = nor->bouncebuf[0];
|
|
||||||
|
|
||||||
/* If nothing in our range is locked, we don't need to do anything */
|
|
||||||
if (spi_nor_is_unlocked_sr(nor, ofs, len, status_old))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* If anything below us is locked, we can't use 'top' protection */
|
|
||||||
if (!spi_nor_is_unlocked_sr(nor, 0, ofs, status_old))
|
|
||||||
can_be_top = false;
|
|
||||||
|
|
||||||
/* If anything above us is locked, we can't use 'bottom' protection */
|
|
||||||
if (!spi_nor_is_unlocked_sr(nor, ofs + len, mtd->size - (ofs + len),
|
|
||||||
status_old))
|
|
||||||
can_be_bottom = false;
|
|
||||||
|
|
||||||
if (!can_be_bottom && !can_be_top)
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
/* Prefer top, if both are valid */
|
|
||||||
use_top = can_be_top;
|
|
||||||
|
|
||||||
/* lock_len: length of region that should remain locked */
|
|
||||||
if (use_top)
|
|
||||||
lock_len = mtd->size - (ofs + len);
|
|
||||||
else
|
|
||||||
lock_len = ofs;
|
|
||||||
|
|
||||||
if (lock_len == 0) {
|
|
||||||
val = 0; /* fully unlocked */
|
|
||||||
} else {
|
|
||||||
min_prot_len = spi_nor_get_min_prot_length_sr(nor);
|
|
||||||
pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
|
|
||||||
val = pow << SR_BP_SHIFT;
|
|
||||||
|
|
||||||
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3)
|
|
||||||
val = (val & ~SR_BP3) | SR_BP3_BIT6;
|
|
||||||
|
|
||||||
/* Some power-of-two sizes are not supported */
|
|
||||||
if (val & ~mask)
|
|
||||||
return -EINVAL;
|
|
||||||
}
|
|
||||||
|
|
||||||
status_new = (status_old & ~mask & ~tb_mask) | val;
|
|
||||||
|
|
||||||
/* Don't protect status register if we're fully unlocked */
|
|
||||||
if (lock_len == 0)
|
|
||||||
status_new &= ~SR_SRWD;
|
|
||||||
|
|
||||||
if (!use_top)
|
|
||||||
status_new |= tb_mask;
|
|
||||||
|
|
||||||
/* Don't bother if they're the same */
|
|
||||||
if (status_new == status_old)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* Only modify protection if it will not lock other areas */
|
|
||||||
if ((status_new & mask) > (status_old & mask))
|
|
||||||
return -EINVAL;
|
|
||||||
|
|
||||||
return spi_nor_write_sr_and_check(nor, status_new);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check if a region of the flash is (completely) locked. See spi_nor_sr_lock()
|
|
||||||
* for more info.
|
|
||||||
*
|
|
||||||
* Returns 1 if entire region is locked, 0 if any portion is unlocked, and
|
|
||||||
* negative on errors.
|
|
||||||
*/
|
|
||||||
static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = spi_nor_read_sr(nor, nor->bouncebuf);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
return spi_nor_is_locked_sr(nor, ofs, len, nor->bouncebuf[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct spi_nor_locking_ops spi_nor_sr_locking_ops = {
|
|
||||||
.lock = spi_nor_sr_lock,
|
|
||||||
.unlock = spi_nor_sr_unlock,
|
|
||||||
.is_locked = spi_nor_sr_is_locked,
|
|
||||||
};
|
|
||||||
|
|
||||||
static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
|
||||||
{
|
|
||||||
struct spi_nor *nor = mtd_to_spi_nor(mtd);
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = spi_nor_lock_and_prep(nor);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
ret = nor->params->locking_ops->lock(nor, ofs, len);
|
|
||||||
|
|
||||||
spi_nor_unlock_and_unprep(nor);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int spi_nor_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
|
||||||
{
|
|
||||||
struct spi_nor *nor = mtd_to_spi_nor(mtd);
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = spi_nor_lock_and_prep(nor);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
ret = nor->params->locking_ops->unlock(nor, ofs, len);
|
|
||||||
|
|
||||||
spi_nor_unlock_and_unprep(nor);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int spi_nor_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
|
||||||
{
|
|
||||||
struct spi_nor *nor = mtd_to_spi_nor(mtd);
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = spi_nor_lock_and_prep(nor);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
ret = nor->params->locking_ops->is_locked(nor, ofs, len);
|
|
||||||
|
|
||||||
spi_nor_unlock_and_unprep(nor);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* spi_nor_sr1_bit6_quad_enable() - Set the Quad Enable BIT(6) in the Status
|
* spi_nor_sr1_bit6_quad_enable() - Set the Quad Enable BIT(6) in the Status
|
||||||
* Register 1.
|
* Register 1.
|
||||||
@ -2336,11 +1969,8 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
|
|||||||
* If page_size is a power of two, the offset can be quickly
|
* If page_size is a power of two, the offset can be quickly
|
||||||
* calculated with an AND operation. On the other cases we
|
* calculated with an AND operation. On the other cases we
|
||||||
* need to do a modulus operation (more expensive).
|
* need to do a modulus operation (more expensive).
|
||||||
* Power of two numbers have only one bit set and we can use
|
|
||||||
* the instruction hweight32 to detect if we need to do a
|
|
||||||
* modulus (do_div()) or not.
|
|
||||||
*/
|
*/
|
||||||
if (hweight32(nor->page_size) == 1) {
|
if (is_power_of_2(nor->page_size)) {
|
||||||
page_offset = addr & (nor->page_size - 1);
|
page_offset = addr & (nor->page_size - 1);
|
||||||
} else {
|
} else {
|
||||||
uint64_t aux = addr;
|
uint64_t aux = addr;
|
||||||
@ -2626,22 +2256,20 @@ void spi_nor_init_uniform_erase_map(struct spi_nor_erase_map *map,
|
|||||||
|
|
||||||
int spi_nor_post_bfpt_fixups(struct spi_nor *nor,
|
int spi_nor_post_bfpt_fixups(struct spi_nor *nor,
|
||||||
const struct sfdp_parameter_header *bfpt_header,
|
const struct sfdp_parameter_header *bfpt_header,
|
||||||
const struct sfdp_bfpt *bfpt,
|
const struct sfdp_bfpt *bfpt)
|
||||||
struct spi_nor_flash_parameter *params)
|
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (nor->manufacturer && nor->manufacturer->fixups &&
|
if (nor->manufacturer && nor->manufacturer->fixups &&
|
||||||
nor->manufacturer->fixups->post_bfpt) {
|
nor->manufacturer->fixups->post_bfpt) {
|
||||||
ret = nor->manufacturer->fixups->post_bfpt(nor, bfpt_header,
|
ret = nor->manufacturer->fixups->post_bfpt(nor, bfpt_header,
|
||||||
bfpt, params);
|
bfpt);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nor->info->fixups && nor->info->fixups->post_bfpt)
|
if (nor->info->fixups && nor->info->fixups->post_bfpt)
|
||||||
return nor->info->fixups->post_bfpt(nor, bfpt_header, bfpt,
|
return nor->info->fixups->post_bfpt(nor, bfpt_header, bfpt);
|
||||||
params);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -2896,7 +2524,7 @@ static void spi_nor_sfdp_init_params(struct spi_nor *nor)
|
|||||||
|
|
||||||
memcpy(&sfdp_params, nor->params, sizeof(sfdp_params));
|
memcpy(&sfdp_params, nor->params, sizeof(sfdp_params));
|
||||||
|
|
||||||
if (spi_nor_parse_sfdp(nor, nor->params)) {
|
if (spi_nor_parse_sfdp(nor)) {
|
||||||
memcpy(nor->params, &sfdp_params, sizeof(*nor->params));
|
memcpy(nor->params, &sfdp_params, sizeof(*nor->params));
|
||||||
nor->addr_width = 0;
|
nor->addr_width = 0;
|
||||||
nor->flags &= ~SNOR_F_4B_OPCODES;
|
nor->flags &= ~SNOR_F_4B_OPCODES;
|
||||||
@ -2916,10 +2544,12 @@ static void spi_nor_info_init_params(struct spi_nor *nor)
|
|||||||
struct device_node *np = spi_nor_get_flash_node(nor);
|
struct device_node *np = spi_nor_get_flash_node(nor);
|
||||||
u8 i, erase_mask;
|
u8 i, erase_mask;
|
||||||
|
|
||||||
/* Initialize legacy flash parameters and settings. */
|
/* Initialize default flash parameters and settings. */
|
||||||
params->quad_enable = spi_nor_sr2_bit1_quad_enable;
|
params->quad_enable = spi_nor_sr2_bit1_quad_enable;
|
||||||
params->set_4byte_addr_mode = spansion_set_4byte_addr_mode;
|
params->set_4byte_addr_mode = spansion_set_4byte_addr_mode;
|
||||||
params->setup = spi_nor_default_setup;
|
params->setup = spi_nor_default_setup;
|
||||||
|
params->otp.org = &info->otp_org;
|
||||||
|
|
||||||
/* Default to 16-bit Write Status (01h) Command */
|
/* Default to 16-bit Write Status (01h) Command */
|
||||||
nor->flags |= SNOR_F_HAS_16BIT_SR;
|
nor->flags |= SNOR_F_HAS_16BIT_SR;
|
||||||
|
|
||||||
@ -3048,7 +2678,7 @@ static void spi_nor_late_init_params(struct spi_nor *nor)
|
|||||||
* the default ones.
|
* the default ones.
|
||||||
*/
|
*/
|
||||||
if (nor->flags & SNOR_F_HAS_LOCK && !nor->params->locking_ops)
|
if (nor->flags & SNOR_F_HAS_LOCK && !nor->params->locking_ops)
|
||||||
nor->params->locking_ops = &spi_nor_sr_locking_ops;
|
spi_nor_init_default_locking_ops(nor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3160,32 +2790,6 @@ static int spi_nor_quad_enable(struct spi_nor *nor)
|
|||||||
return nor->params->quad_enable(nor);
|
return nor->params->quad_enable(nor);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* spi_nor_try_unlock_all() - Tries to unlock the entire flash memory array.
|
|
||||||
* @nor: pointer to a 'struct spi_nor'.
|
|
||||||
*
|
|
||||||
* Some SPI NOR flashes are write protected by default after a power-on reset
|
|
||||||
* cycle, in order to avoid inadvertent writes during power-up. Backward
|
|
||||||
* compatibility imposes to unlock the entire flash memory array at power-up
|
|
||||||
* by default.
|
|
||||||
*
|
|
||||||
* Unprotecting the entire flash array will fail for boards which are hardware
|
|
||||||
* write-protected. Thus any errors are ignored.
|
|
||||||
*/
|
|
||||||
static void spi_nor_try_unlock_all(struct spi_nor *nor)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
if (!(nor->flags & SNOR_F_HAS_LOCK))
|
|
||||||
return;
|
|
||||||
|
|
||||||
dev_dbg(nor->dev, "Unprotecting entire flash array\n");
|
|
||||||
|
|
||||||
ret = spi_nor_unlock(&nor->mtd, 0, nor->params->size);
|
|
||||||
if (ret)
|
|
||||||
dev_dbg(nor->dev, "Failed to unlock the entire flash memory array\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static int spi_nor_init(struct spi_nor *nor)
|
static int spi_nor_init(struct spi_nor *nor)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
@ -3301,6 +2905,37 @@ static void spi_nor_resume(struct mtd_info *mtd)
|
|||||||
dev_err(dev, "resume() failed\n");
|
dev_err(dev, "resume() failed\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int spi_nor_get_device(struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
struct mtd_info *master = mtd_get_master(mtd);
|
||||||
|
struct spi_nor *nor = mtd_to_spi_nor(master);
|
||||||
|
struct device *dev;
|
||||||
|
|
||||||
|
if (nor->spimem)
|
||||||
|
dev = nor->spimem->spi->controller->dev.parent;
|
||||||
|
else
|
||||||
|
dev = nor->dev;
|
||||||
|
|
||||||
|
if (!try_module_get(dev->driver->owner))
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void spi_nor_put_device(struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
struct mtd_info *master = mtd_get_master(mtd);
|
||||||
|
struct spi_nor *nor = mtd_to_spi_nor(master);
|
||||||
|
struct device *dev;
|
||||||
|
|
||||||
|
if (nor->spimem)
|
||||||
|
dev = nor->spimem->spi->controller->dev.parent;
|
||||||
|
else
|
||||||
|
dev = nor->dev;
|
||||||
|
|
||||||
|
module_put(dev->driver->owner);
|
||||||
|
}
|
||||||
|
|
||||||
void spi_nor_restore(struct spi_nor *nor)
|
void spi_nor_restore(struct spi_nor *nor)
|
||||||
{
|
{
|
||||||
/* restore the addressing mode */
|
/* restore the addressing mode */
|
||||||
@ -3495,12 +3130,8 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
|
|||||||
mtd->_read = spi_nor_read;
|
mtd->_read = spi_nor_read;
|
||||||
mtd->_suspend = spi_nor_suspend;
|
mtd->_suspend = spi_nor_suspend;
|
||||||
mtd->_resume = spi_nor_resume;
|
mtd->_resume = spi_nor_resume;
|
||||||
|
mtd->_get_device = spi_nor_get_device;
|
||||||
if (nor->params->locking_ops) {
|
mtd->_put_device = spi_nor_put_device;
|
||||||
mtd->_lock = spi_nor_lock;
|
|
||||||
mtd->_unlock = spi_nor_unlock;
|
|
||||||
mtd->_is_locked = spi_nor_is_locked;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info->flags & USE_FSR)
|
if (info->flags & USE_FSR)
|
||||||
nor->flags |= SNOR_F_USE_FSR;
|
nor->flags |= SNOR_F_USE_FSR;
|
||||||
@ -3553,11 +3184,16 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
|
|||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
spi_nor_register_locking_ops(nor);
|
||||||
|
|
||||||
/* Send all the required SPI flash commands to initialize device */
|
/* Send all the required SPI flash commands to initialize device */
|
||||||
ret = spi_nor_init(nor);
|
ret = spi_nor_init(nor);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
|
/* Configure OTP parameters and ops */
|
||||||
|
spi_nor_otp_init(nor);
|
||||||
|
|
||||||
dev_info(dev, "%s (%lld Kbytes)\n", info->name,
|
dev_info(dev, "%s (%lld Kbytes)\n", info->name,
|
||||||
(long long)mtd->size >> 10);
|
(long long)mtd->size >> 10);
|
||||||
|
|
||||||
|
@ -187,6 +187,45 @@ struct spi_nor_locking_ops {
|
|||||||
int (*is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len);
|
int (*is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct spi_nor_otp_organization - Structure to describe the SPI NOR OTP regions
|
||||||
|
* @len: size of one OTP region in bytes.
|
||||||
|
* @base: start address of the OTP area.
|
||||||
|
* @offset: offset between consecutive OTP regions if there are more
|
||||||
|
* than one.
|
||||||
|
* @n_regions: number of individual OTP regions.
|
||||||
|
*/
|
||||||
|
struct spi_nor_otp_organization {
|
||||||
|
size_t len;
|
||||||
|
loff_t base;
|
||||||
|
loff_t offset;
|
||||||
|
unsigned int n_regions;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct spi_nor_otp_ops - SPI NOR OTP methods
|
||||||
|
* @read: read from the SPI NOR OTP area.
|
||||||
|
* @write: write to the SPI NOR OTP area.
|
||||||
|
* @lock: lock an OTP region.
|
||||||
|
* @is_locked: check if an OTP region of the SPI NOR is locked.
|
||||||
|
*/
|
||||||
|
struct spi_nor_otp_ops {
|
||||||
|
int (*read)(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf);
|
||||||
|
int (*write)(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf);
|
||||||
|
int (*lock)(struct spi_nor *nor, unsigned int region);
|
||||||
|
int (*is_locked)(struct spi_nor *nor, unsigned int region);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct spi_nor_otp - SPI NOR OTP grouping structure
|
||||||
|
* @org: OTP region organization
|
||||||
|
* @ops: OTP access ops
|
||||||
|
*/
|
||||||
|
struct spi_nor_otp {
|
||||||
|
const struct spi_nor_otp_organization *org;
|
||||||
|
const struct spi_nor_otp_ops *ops;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct spi_nor_flash_parameter - SPI NOR flash parameters and settings.
|
* struct spi_nor_flash_parameter - SPI NOR flash parameters and settings.
|
||||||
* Includes legacy flash parameters and settings that can be overwritten
|
* Includes legacy flash parameters and settings that can be overwritten
|
||||||
@ -208,6 +247,7 @@ struct spi_nor_locking_ops {
|
|||||||
* higher index in the array, the higher priority.
|
* higher index in the array, the higher priority.
|
||||||
* @erase_map: the erase map parsed from the SFDP Sector Map Parameter
|
* @erase_map: the erase map parsed from the SFDP Sector Map Parameter
|
||||||
* Table.
|
* Table.
|
||||||
|
* @otp_info: describes the OTP regions.
|
||||||
* @octal_dtr_enable: enables SPI NOR octal DTR mode.
|
* @octal_dtr_enable: enables SPI NOR octal DTR mode.
|
||||||
* @quad_enable: enables SPI NOR quad mode.
|
* @quad_enable: enables SPI NOR quad mode.
|
||||||
* @set_4byte_addr_mode: puts the SPI NOR in 4 byte addressing mode.
|
* @set_4byte_addr_mode: puts the SPI NOR in 4 byte addressing mode.
|
||||||
@ -219,6 +259,7 @@ struct spi_nor_locking_ops {
|
|||||||
* e.g. different opcodes, specific address calculation,
|
* e.g. different opcodes, specific address calculation,
|
||||||
* page size, etc.
|
* page size, etc.
|
||||||
* @locking_ops: SPI NOR locking methods.
|
* @locking_ops: SPI NOR locking methods.
|
||||||
|
* @otp: SPI NOR OTP methods.
|
||||||
*/
|
*/
|
||||||
struct spi_nor_flash_parameter {
|
struct spi_nor_flash_parameter {
|
||||||
u64 size;
|
u64 size;
|
||||||
@ -232,6 +273,7 @@ struct spi_nor_flash_parameter {
|
|||||||
struct spi_nor_pp_command page_programs[SNOR_CMD_PP_MAX];
|
struct spi_nor_pp_command page_programs[SNOR_CMD_PP_MAX];
|
||||||
|
|
||||||
struct spi_nor_erase_map erase_map;
|
struct spi_nor_erase_map erase_map;
|
||||||
|
struct spi_nor_otp otp;
|
||||||
|
|
||||||
int (*octal_dtr_enable)(struct spi_nor *nor, bool enable);
|
int (*octal_dtr_enable)(struct spi_nor *nor, bool enable);
|
||||||
int (*quad_enable)(struct spi_nor *nor);
|
int (*quad_enable)(struct spi_nor *nor);
|
||||||
@ -261,8 +303,7 @@ struct spi_nor_fixups {
|
|||||||
void (*default_init)(struct spi_nor *nor);
|
void (*default_init)(struct spi_nor *nor);
|
||||||
int (*post_bfpt)(struct spi_nor *nor,
|
int (*post_bfpt)(struct spi_nor *nor,
|
||||||
const struct sfdp_parameter_header *bfpt_header,
|
const struct sfdp_parameter_header *bfpt_header,
|
||||||
const struct sfdp_bfpt *bfpt,
|
const struct sfdp_bfpt *bfpt);
|
||||||
struct spi_nor_flash_parameter *params);
|
|
||||||
void (*post_sfdp)(struct spi_nor *nor);
|
void (*post_sfdp)(struct spi_nor *nor);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -339,6 +380,8 @@ struct flash_info {
|
|||||||
* power-up in a write-protected state.
|
* power-up in a write-protected state.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const struct spi_nor_otp_organization otp_org;
|
||||||
|
|
||||||
/* Part specific fixup hooks. */
|
/* Part specific fixup hooks. */
|
||||||
const struct spi_nor_fixups *fixups;
|
const struct spi_nor_fixups *fixups;
|
||||||
};
|
};
|
||||||
@ -393,6 +436,14 @@ struct flash_info {
|
|||||||
.addr_width = 3, \
|
.addr_width = 3, \
|
||||||
.flags = SPI_NOR_NO_FR | SPI_NOR_XSR_RDY,
|
.flags = SPI_NOR_NO_FR | SPI_NOR_XSR_RDY,
|
||||||
|
|
||||||
|
#define OTP_INFO(_len, _n_regions, _base, _offset) \
|
||||||
|
.otp_org = { \
|
||||||
|
.len = (_len), \
|
||||||
|
.base = (_base), \
|
||||||
|
.offset = (_offset), \
|
||||||
|
.n_regions = (_n_regions), \
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct spi_nor_manufacturer - SPI NOR manufacturer object
|
* struct spi_nor_manufacturer - SPI NOR manufacturer object
|
||||||
* @name: manufacturer name
|
* @name: manufacturer name
|
||||||
@ -444,6 +495,7 @@ int spi_nor_read_sr(struct spi_nor *nor, u8 *sr);
|
|||||||
int spi_nor_read_cr(struct spi_nor *nor, u8 *cr);
|
int spi_nor_read_cr(struct spi_nor *nor, u8 *cr);
|
||||||
int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len);
|
int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len);
|
||||||
int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1);
|
int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1);
|
||||||
|
int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr);
|
||||||
|
|
||||||
int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr);
|
int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr);
|
||||||
ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
|
ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
|
||||||
@ -451,6 +503,11 @@ ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
|
|||||||
ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len,
|
ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len,
|
||||||
const u8 *buf);
|
const u8 *buf);
|
||||||
|
|
||||||
|
int spi_nor_otp_read_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf);
|
||||||
|
int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf);
|
||||||
|
int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region);
|
||||||
|
int spi_nor_otp_is_locked_sr2(struct spi_nor *nor, unsigned int region);
|
||||||
|
|
||||||
int spi_nor_hwcaps_read2cmd(u32 hwcaps);
|
int spi_nor_hwcaps_read2cmd(u32 hwcaps);
|
||||||
u8 spi_nor_convert_3to4_read(u8 opcode);
|
u8 spi_nor_convert_3to4_read(u8 opcode);
|
||||||
void spi_nor_set_read_settings(struct spi_nor_read_command *read,
|
void spi_nor_set_read_settings(struct spi_nor_read_command *read,
|
||||||
@ -470,8 +527,12 @@ void spi_nor_init_uniform_erase_map(struct spi_nor_erase_map *map,
|
|||||||
|
|
||||||
int spi_nor_post_bfpt_fixups(struct spi_nor *nor,
|
int spi_nor_post_bfpt_fixups(struct spi_nor *nor,
|
||||||
const struct sfdp_parameter_header *bfpt_header,
|
const struct sfdp_parameter_header *bfpt_header,
|
||||||
const struct sfdp_bfpt *bfpt,
|
const struct sfdp_bfpt *bfpt);
|
||||||
struct spi_nor_flash_parameter *params);
|
|
||||||
|
void spi_nor_init_default_locking_ops(struct spi_nor *nor);
|
||||||
|
void spi_nor_try_unlock_all(struct spi_nor *nor);
|
||||||
|
void spi_nor_register_locking_ops(struct spi_nor *nor);
|
||||||
|
void spi_nor_otp_init(struct spi_nor *nor);
|
||||||
|
|
||||||
static struct spi_nor __maybe_unused *mtd_to_spi_nor(struct mtd_info *mtd)
|
static struct spi_nor __maybe_unused *mtd_to_spi_nor(struct mtd_info *mtd)
|
||||||
{
|
{
|
||||||
|
@ -11,8 +11,7 @@
|
|||||||
static int
|
static int
|
||||||
is25lp256_post_bfpt_fixups(struct spi_nor *nor,
|
is25lp256_post_bfpt_fixups(struct spi_nor *nor,
|
||||||
const struct sfdp_parameter_header *bfpt_header,
|
const struct sfdp_parameter_header *bfpt_header,
|
||||||
const struct sfdp_bfpt *bfpt,
|
const struct sfdp_bfpt *bfpt)
|
||||||
struct spi_nor_flash_parameter *params)
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* IS25LP256 supports 4B opcodes, but the BFPT advertises a
|
* IS25LP256 supports 4B opcodes, but the BFPT advertises a
|
||||||
|
@ -11,8 +11,7 @@
|
|||||||
static int
|
static int
|
||||||
mx25l25635_post_bfpt_fixups(struct spi_nor *nor,
|
mx25l25635_post_bfpt_fixups(struct spi_nor *nor,
|
||||||
const struct sfdp_parameter_header *bfpt_header,
|
const struct sfdp_parameter_header *bfpt_header,
|
||||||
const struct sfdp_bfpt *bfpt,
|
const struct sfdp_bfpt *bfpt)
|
||||||
struct spi_nor_flash_parameter *params)
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* MX25L25635F supports 4B opcodes but MX25L25635E does not.
|
* MX25L25635F supports 4B opcodes but MX25L25635E does not.
|
||||||
@ -73,9 +72,6 @@ static const struct flash_info macronix_parts[] = {
|
|||||||
SECT_4K | SPI_NOR_DUAL_READ |
|
SECT_4K | SPI_NOR_DUAL_READ |
|
||||||
SPI_NOR_QUAD_READ) },
|
SPI_NOR_QUAD_READ) },
|
||||||
{ "mx25l25655e", INFO(0xc22619, 0, 64 * 1024, 512, 0) },
|
{ "mx25l25655e", INFO(0xc22619, 0, 64 * 1024, 512, 0) },
|
||||||
{ "mx25l51245g", INFO(0xc2201a, 0, 64 * 1024, 1024,
|
|
||||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
|
||||||
SPI_NOR_4B_OPCODES) },
|
|
||||||
{ "mx66l51235l", INFO(0xc2201a, 0, 64 * 1024, 1024,
|
{ "mx66l51235l", INFO(0xc2201a, 0, 64 * 1024, 1024,
|
||||||
SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||||
SPI_NOR_4B_OPCODES) },
|
SPI_NOR_4B_OPCODES) },
|
||||||
|
376
drivers/mtd/spi-nor/otp.c
Normal file
376
drivers/mtd/spi-nor/otp.c
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
// 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/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 OTP data
|
||||||
|
* @nor: pointer to 'struct spi_nor'
|
||||||
|
* @from: offset to read from
|
||||||
|
* @len: number of bytes to read
|
||||||
|
* @buf: pointer to dst buffer
|
||||||
|
*
|
||||||
|
* Read OTP data from one region by using the SPINOR_OP_RSECR commands. This
|
||||||
|
* method is used on GigaDevice and Winbond flashes.
|
||||||
|
*
|
||||||
|
* 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_width, read_opcode, read_dummy;
|
||||||
|
struct spi_mem_dirmap_desc *rdesc;
|
||||||
|
enum spi_nor_protocol read_proto;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
read_opcode = nor->read_opcode;
|
||||||
|
addr_width = nor->addr_width;
|
||||||
|
read_dummy = nor->read_dummy;
|
||||||
|
read_proto = nor->read_proto;
|
||||||
|
rdesc = nor->dirmap.rdesc;
|
||||||
|
|
||||||
|
nor->read_opcode = SPINOR_OP_RSECR;
|
||||||
|
nor->addr_width = 3;
|
||||||
|
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_width = addr_width;
|
||||||
|
nor->read_dummy = read_dummy;
|
||||||
|
nor->read_proto = read_proto;
|
||||||
|
nor->dirmap.rdesc = rdesc;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* spi_nor_otp_write_secr() - write OTP data
|
||||||
|
* @nor: pointer to 'struct spi_nor'
|
||||||
|
* @to: offset to write to
|
||||||
|
* @len: number of bytes to write
|
||||||
|
* @buf: pointer to src buffer
|
||||||
|
*
|
||||||
|
* Write OTP data to one region by using the SPINOR_OP_PSECR commands. This
|
||||||
|
* method is used on GigaDevice and Winbond flashes.
|
||||||
|
*
|
||||||
|
* Please note, the write must not span multiple OTP regions.
|
||||||
|
*
|
||||||
|
* Return: number of bytes written successfully, -errno otherwise
|
||||||
|
*/
|
||||||
|
int spi_nor_otp_write_secr(struct spi_nor *nor, loff_t addr, size_t len, u8 *buf)
|
||||||
|
{
|
||||||
|
enum spi_nor_protocol write_proto;
|
||||||
|
struct spi_mem_dirmap_desc *wdesc;
|
||||||
|
u8 addr_width, program_opcode;
|
||||||
|
int ret, written;
|
||||||
|
|
||||||
|
program_opcode = nor->program_opcode;
|
||||||
|
addr_width = nor->addr_width;
|
||||||
|
write_proto = nor->write_proto;
|
||||||
|
wdesc = nor->dirmap.wdesc;
|
||||||
|
|
||||||
|
nor->program_opcode = SPINOR_OP_PSECR;
|
||||||
|
nor->addr_width = 3;
|
||||||
|
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 OTP region.
|
||||||
|
*/
|
||||||
|
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_width = addr_width;
|
||||||
|
nor->write_proto = write_proto;
|
||||||
|
nor->dirmap.wdesc = wdesc;
|
||||||
|
|
||||||
|
return ret ?: written;
|
||||||
|
}
|
||||||
|
|
||||||
|
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_lock_and_prep(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_read_write(struct mtd_info *mtd, loff_t ofs,
|
||||||
|
size_t total_len, size_t *retlen,
|
||||||
|
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;
|
||||||
|
|
||||||
|
ret = spi_nor_lock_and_prep(nor);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/* don't access beyond the end */
|
||||||
|
total_len = min_t(size_t, total_len, spi_nor_otp_size(nor) - ofs);
|
||||||
|
|
||||||
|
*retlen = 0;
|
||||||
|
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, 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, u8 *buf)
|
||||||
|
{
|
||||||
|
return spi_nor_mtd_otp_read_write(mtd, to, len, retlen, buf, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
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_lock_and_prep(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_otp_init(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;
|
||||||
|
}
|
@ -405,8 +405,6 @@ static void spi_nor_regions_sort_erase_types(struct spi_nor_erase_map *map)
|
|||||||
* @nor: pointer to a 'struct spi_nor'
|
* @nor: pointer to a 'struct spi_nor'
|
||||||
* @bfpt_header: pointer to the 'struct sfdp_parameter_header' describing
|
* @bfpt_header: pointer to the 'struct sfdp_parameter_header' describing
|
||||||
* the Basic Flash Parameter Table length and version
|
* the Basic Flash Parameter Table length and version
|
||||||
* @params: pointer to the 'struct spi_nor_flash_parameter' to be
|
|
||||||
* filled
|
|
||||||
*
|
*
|
||||||
* The Basic Flash Parameter Table is the main and only mandatory table as
|
* The Basic Flash Parameter Table is the main and only mandatory table as
|
||||||
* defined by the SFDP (JESD216) specification.
|
* defined by the SFDP (JESD216) specification.
|
||||||
@ -431,9 +429,9 @@ static void spi_nor_regions_sort_erase_types(struct spi_nor_erase_map *map)
|
|||||||
* Return: 0 on success, -errno otherwise.
|
* Return: 0 on success, -errno otherwise.
|
||||||
*/
|
*/
|
||||||
static int spi_nor_parse_bfpt(struct spi_nor *nor,
|
static int spi_nor_parse_bfpt(struct spi_nor *nor,
|
||||||
const struct sfdp_parameter_header *bfpt_header,
|
const struct sfdp_parameter_header *bfpt_header)
|
||||||
struct spi_nor_flash_parameter *params)
|
|
||||||
{
|
{
|
||||||
|
struct spi_nor_flash_parameter *params = nor->params;
|
||||||
struct spi_nor_erase_map *map = ¶ms->erase_map;
|
struct spi_nor_erase_map *map = ¶ms->erase_map;
|
||||||
struct spi_nor_erase_type *erase_type = map->erase_type;
|
struct spi_nor_erase_type *erase_type = map->erase_type;
|
||||||
struct sfdp_bfpt bfpt;
|
struct sfdp_bfpt bfpt;
|
||||||
@ -552,8 +550,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
|
|||||||
|
|
||||||
/* Stop here if not JESD216 rev A or later. */
|
/* Stop here if not JESD216 rev A or later. */
|
||||||
if (bfpt_header->length == BFPT_DWORD_MAX_JESD216)
|
if (bfpt_header->length == BFPT_DWORD_MAX_JESD216)
|
||||||
return spi_nor_post_bfpt_fixups(nor, bfpt_header, &bfpt,
|
return spi_nor_post_bfpt_fixups(nor, bfpt_header, &bfpt);
|
||||||
params);
|
|
||||||
|
|
||||||
/* Page size: this field specifies 'N' so the page size = 2^N bytes. */
|
/* Page size: this field specifies 'N' so the page size = 2^N bytes. */
|
||||||
val = bfpt.dwords[BFPT_DWORD(11)];
|
val = bfpt.dwords[BFPT_DWORD(11)];
|
||||||
@ -614,8 +611,8 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
|
|||||||
|
|
||||||
/* Stop here if not JESD216 rev C or later. */
|
/* Stop here if not JESD216 rev C or later. */
|
||||||
if (bfpt_header->length == BFPT_DWORD_MAX_JESD216B)
|
if (bfpt_header->length == BFPT_DWORD_MAX_JESD216B)
|
||||||
return spi_nor_post_bfpt_fixups(nor, bfpt_header, &bfpt,
|
return spi_nor_post_bfpt_fixups(nor, bfpt_header, &bfpt);
|
||||||
params);
|
|
||||||
/* 8D-8D-8D command extension. */
|
/* 8D-8D-8D command extension. */
|
||||||
switch (bfpt.dwords[BFPT_DWORD(18)] & BFPT_DWORD18_CMD_EXT_MASK) {
|
switch (bfpt.dwords[BFPT_DWORD(18)] & BFPT_DWORD18_CMD_EXT_MASK) {
|
||||||
case BFPT_DWORD18_CMD_EXT_REP:
|
case BFPT_DWORD18_CMD_EXT_REP:
|
||||||
@ -635,7 +632,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
|
|||||||
return -EOPNOTSUPP;
|
return -EOPNOTSUPP;
|
||||||
}
|
}
|
||||||
|
|
||||||
return spi_nor_post_bfpt_fixups(nor, bfpt_header, &bfpt, params);
|
return spi_nor_post_bfpt_fixups(nor, bfpt_header, &bfpt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -800,18 +797,14 @@ spi_nor_region_check_overlay(struct spi_nor_erase_region *region,
|
|||||||
/**
|
/**
|
||||||
* spi_nor_init_non_uniform_erase_map() - initialize the non-uniform erase map
|
* spi_nor_init_non_uniform_erase_map() - initialize the non-uniform erase map
|
||||||
* @nor: pointer to a 'struct spi_nor'
|
* @nor: pointer to a 'struct spi_nor'
|
||||||
* @params: pointer to a duplicate 'struct spi_nor_flash_parameter' that is
|
|
||||||
* used for storing SFDP parsed data
|
|
||||||
* @smpt: pointer to the sector map parameter table
|
* @smpt: pointer to the sector map parameter table
|
||||||
*
|
*
|
||||||
* Return: 0 on success, -errno otherwise.
|
* Return: 0 on success, -errno otherwise.
|
||||||
*/
|
*/
|
||||||
static int
|
static int spi_nor_init_non_uniform_erase_map(struct spi_nor *nor,
|
||||||
spi_nor_init_non_uniform_erase_map(struct spi_nor *nor,
|
const u32 *smpt)
|
||||||
struct spi_nor_flash_parameter *params,
|
|
||||||
const u32 *smpt)
|
|
||||||
{
|
{
|
||||||
struct spi_nor_erase_map *map = ¶ms->erase_map;
|
struct spi_nor_erase_map *map = &nor->params->erase_map;
|
||||||
struct spi_nor_erase_type *erase = map->erase_type;
|
struct spi_nor_erase_type *erase = map->erase_type;
|
||||||
struct spi_nor_erase_region *region;
|
struct spi_nor_erase_region *region;
|
||||||
u64 offset;
|
u64 offset;
|
||||||
@ -889,8 +882,6 @@ spi_nor_init_non_uniform_erase_map(struct spi_nor *nor,
|
|||||||
* spi_nor_parse_smpt() - parse Sector Map Parameter Table
|
* spi_nor_parse_smpt() - parse Sector Map Parameter Table
|
||||||
* @nor: pointer to a 'struct spi_nor'
|
* @nor: pointer to a 'struct spi_nor'
|
||||||
* @smpt_header: sector map parameter table header
|
* @smpt_header: sector map parameter table header
|
||||||
* @params: pointer to a duplicate 'struct spi_nor_flash_parameter'
|
|
||||||
* that is used for storing SFDP parsed data
|
|
||||||
*
|
*
|
||||||
* This table is optional, but when available, we parse it to identify the
|
* This table is optional, but when available, we parse it to identify the
|
||||||
* location and size of sectors within the main data array of the flash memory
|
* location and size of sectors within the main data array of the flash memory
|
||||||
@ -899,8 +890,7 @@ spi_nor_init_non_uniform_erase_map(struct spi_nor *nor,
|
|||||||
* Return: 0 on success, -errno otherwise.
|
* Return: 0 on success, -errno otherwise.
|
||||||
*/
|
*/
|
||||||
static int spi_nor_parse_smpt(struct spi_nor *nor,
|
static int spi_nor_parse_smpt(struct spi_nor *nor,
|
||||||
const struct sfdp_parameter_header *smpt_header,
|
const struct sfdp_parameter_header *smpt_header)
|
||||||
struct spi_nor_flash_parameter *params)
|
|
||||||
{
|
{
|
||||||
const u32 *sector_map;
|
const u32 *sector_map;
|
||||||
u32 *smpt;
|
u32 *smpt;
|
||||||
@ -928,11 +918,11 @@ static int spi_nor_parse_smpt(struct spi_nor *nor,
|
|||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = spi_nor_init_non_uniform_erase_map(nor, params, sector_map);
|
ret = spi_nor_init_non_uniform_erase_map(nor, sector_map);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
spi_nor_regions_sort_erase_types(¶ms->erase_map);
|
spi_nor_regions_sort_erase_types(&nor->params->erase_map);
|
||||||
/* fall through */
|
/* fall through */
|
||||||
out:
|
out:
|
||||||
kfree(smpt);
|
kfree(smpt);
|
||||||
@ -944,13 +934,11 @@ out:
|
|||||||
* @nor: pointer to a 'struct spi_nor'.
|
* @nor: pointer to a 'struct spi_nor'.
|
||||||
* @param_header: pointer to the 'struct sfdp_parameter_header' describing
|
* @param_header: pointer to the 'struct sfdp_parameter_header' describing
|
||||||
* the 4-Byte Address Instruction Table length and version.
|
* the 4-Byte Address Instruction Table length and version.
|
||||||
* @params: pointer to the 'struct spi_nor_flash_parameter' to be.
|
|
||||||
*
|
*
|
||||||
* Return: 0 on success, -errno otherwise.
|
* Return: 0 on success, -errno otherwise.
|
||||||
*/
|
*/
|
||||||
static int spi_nor_parse_4bait(struct spi_nor *nor,
|
static int spi_nor_parse_4bait(struct spi_nor *nor,
|
||||||
const struct sfdp_parameter_header *param_header,
|
const struct sfdp_parameter_header *param_header)
|
||||||
struct spi_nor_flash_parameter *params)
|
|
||||||
{
|
{
|
||||||
static const struct sfdp_4bait reads[] = {
|
static const struct sfdp_4bait reads[] = {
|
||||||
{ SNOR_HWCAPS_READ, BIT(0) },
|
{ SNOR_HWCAPS_READ, BIT(0) },
|
||||||
@ -974,6 +962,7 @@ static int spi_nor_parse_4bait(struct spi_nor *nor,
|
|||||||
{ 0u /* not used */, BIT(11) },
|
{ 0u /* not used */, BIT(11) },
|
||||||
{ 0u /* not used */, BIT(12) },
|
{ 0u /* not used */, BIT(12) },
|
||||||
};
|
};
|
||||||
|
struct spi_nor_flash_parameter *params = nor->params;
|
||||||
struct spi_nor_pp_command *params_pp = params->page_programs;
|
struct spi_nor_pp_command *params_pp = params->page_programs;
|
||||||
struct spi_nor_erase_map *map = ¶ms->erase_map;
|
struct spi_nor_erase_map *map = ¶ms->erase_map;
|
||||||
struct spi_nor_erase_type *erase_type = map->erase_type;
|
struct spi_nor_erase_type *erase_type = map->erase_type;
|
||||||
@ -1130,13 +1119,11 @@ out:
|
|||||||
* @nor: pointer to a 'struct spi_nor'
|
* @nor: pointer to a 'struct spi_nor'
|
||||||
* @profile1_header: pointer to the 'struct sfdp_parameter_header' describing
|
* @profile1_header: pointer to the 'struct sfdp_parameter_header' describing
|
||||||
* the Profile 1.0 Table length and version.
|
* the Profile 1.0 Table length and version.
|
||||||
* @params: pointer to the 'struct spi_nor_flash_parameter' to be.
|
|
||||||
*
|
*
|
||||||
* Return: 0 on success, -errno otherwise.
|
* Return: 0 on success, -errno otherwise.
|
||||||
*/
|
*/
|
||||||
static int spi_nor_parse_profile1(struct spi_nor *nor,
|
static int spi_nor_parse_profile1(struct spi_nor *nor,
|
||||||
const struct sfdp_parameter_header *profile1_header,
|
const struct sfdp_parameter_header *profile1_header)
|
||||||
struct spi_nor_flash_parameter *params)
|
|
||||||
{
|
{
|
||||||
u32 *dwords, addr;
|
u32 *dwords, addr;
|
||||||
size_t len;
|
size_t len;
|
||||||
@ -1160,14 +1147,14 @@ static int spi_nor_parse_profile1(struct spi_nor *nor,
|
|||||||
|
|
||||||
/* Set the Read Status Register dummy cycles and dummy address bytes. */
|
/* Set the Read Status Register dummy cycles and dummy address bytes. */
|
||||||
if (dwords[0] & PROFILE1_DWORD1_RDSR_DUMMY)
|
if (dwords[0] & PROFILE1_DWORD1_RDSR_DUMMY)
|
||||||
params->rdsr_dummy = 8;
|
nor->params->rdsr_dummy = 8;
|
||||||
else
|
else
|
||||||
params->rdsr_dummy = 4;
|
nor->params->rdsr_dummy = 4;
|
||||||
|
|
||||||
if (dwords[0] & PROFILE1_DWORD1_RDSR_ADDR_BYTES)
|
if (dwords[0] & PROFILE1_DWORD1_RDSR_ADDR_BYTES)
|
||||||
params->rdsr_addr_nbytes = 4;
|
nor->params->rdsr_addr_nbytes = 4;
|
||||||
else
|
else
|
||||||
params->rdsr_addr_nbytes = 0;
|
nor->params->rdsr_addr_nbytes = 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We don't know what speed the controller is running at. Find the
|
* We don't know what speed the controller is running at. Find the
|
||||||
@ -1193,7 +1180,7 @@ static int spi_nor_parse_profile1(struct spi_nor *nor,
|
|||||||
dummy = round_up(dummy, 2);
|
dummy = round_up(dummy, 2);
|
||||||
|
|
||||||
/* Update the fast read settings. */
|
/* Update the fast read settings. */
|
||||||
spi_nor_set_read_settings(¶ms->reads[SNOR_CMD_READ_8_8_8_DTR],
|
spi_nor_set_read_settings(&nor->params->reads[SNOR_CMD_READ_8_8_8_DTR],
|
||||||
0, dummy, opcode,
|
0, dummy, opcode,
|
||||||
SNOR_PROTO_8_8_8_DTR);
|
SNOR_PROTO_8_8_8_DTR);
|
||||||
|
|
||||||
@ -1210,13 +1197,11 @@ out:
|
|||||||
* @nor: pointer to a 'struct spi_nor'
|
* @nor: pointer to a 'struct spi_nor'
|
||||||
* @sccr_header: pointer to the 'struct sfdp_parameter_header' describing
|
* @sccr_header: pointer to the 'struct sfdp_parameter_header' describing
|
||||||
* the SCCR Map table length and version.
|
* the SCCR Map table length and version.
|
||||||
* @params: pointer to the 'struct spi_nor_flash_parameter' to be.
|
|
||||||
*
|
*
|
||||||
* Return: 0 on success, -errno otherwise.
|
* Return: 0 on success, -errno otherwise.
|
||||||
*/
|
*/
|
||||||
static int spi_nor_parse_sccr(struct spi_nor *nor,
|
static int spi_nor_parse_sccr(struct spi_nor *nor,
|
||||||
const struct sfdp_parameter_header *sccr_header,
|
const struct sfdp_parameter_header *sccr_header)
|
||||||
struct spi_nor_flash_parameter *params)
|
|
||||||
{
|
{
|
||||||
u32 *dwords, addr;
|
u32 *dwords, addr;
|
||||||
size_t len;
|
size_t len;
|
||||||
@ -1245,8 +1230,6 @@ out:
|
|||||||
/**
|
/**
|
||||||
* spi_nor_parse_sfdp() - parse the Serial Flash Discoverable Parameters.
|
* spi_nor_parse_sfdp() - parse the Serial Flash Discoverable Parameters.
|
||||||
* @nor: pointer to a 'struct spi_nor'
|
* @nor: pointer to a 'struct spi_nor'
|
||||||
* @params: pointer to the 'struct spi_nor_flash_parameter' to be
|
|
||||||
* filled
|
|
||||||
*
|
*
|
||||||
* The Serial Flash Discoverable Parameters are described by the JEDEC JESD216
|
* The Serial Flash Discoverable Parameters are described by the JEDEC JESD216
|
||||||
* specification. This is a standard which tends to supported by almost all
|
* specification. This is a standard which tends to supported by almost all
|
||||||
@ -1256,8 +1239,7 @@ out:
|
|||||||
*
|
*
|
||||||
* Return: 0 on success, -errno otherwise.
|
* Return: 0 on success, -errno otherwise.
|
||||||
*/
|
*/
|
||||||
int spi_nor_parse_sfdp(struct spi_nor *nor,
|
int spi_nor_parse_sfdp(struct spi_nor *nor)
|
||||||
struct spi_nor_flash_parameter *params)
|
|
||||||
{
|
{
|
||||||
const struct sfdp_parameter_header *param_header, *bfpt_header;
|
const struct sfdp_parameter_header *param_header, *bfpt_header;
|
||||||
struct sfdp_parameter_header *param_headers = NULL;
|
struct sfdp_parameter_header *param_headers = NULL;
|
||||||
@ -1326,7 +1308,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor,
|
|||||||
bfpt_header = param_header;
|
bfpt_header = param_header;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = spi_nor_parse_bfpt(nor, bfpt_header, params);
|
err = spi_nor_parse_bfpt(nor, bfpt_header);
|
||||||
if (err)
|
if (err)
|
||||||
goto exit;
|
goto exit;
|
||||||
|
|
||||||
@ -1336,19 +1318,19 @@ int spi_nor_parse_sfdp(struct spi_nor *nor,
|
|||||||
|
|
||||||
switch (SFDP_PARAM_HEADER_ID(param_header)) {
|
switch (SFDP_PARAM_HEADER_ID(param_header)) {
|
||||||
case SFDP_SECTOR_MAP_ID:
|
case SFDP_SECTOR_MAP_ID:
|
||||||
err = spi_nor_parse_smpt(nor, param_header, params);
|
err = spi_nor_parse_smpt(nor, param_header);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SFDP_4BAIT_ID:
|
case SFDP_4BAIT_ID:
|
||||||
err = spi_nor_parse_4bait(nor, param_header, params);
|
err = spi_nor_parse_4bait(nor, param_header);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SFDP_PROFILE1_ID:
|
case SFDP_PROFILE1_ID:
|
||||||
err = spi_nor_parse_profile1(nor, param_header, params);
|
err = spi_nor_parse_profile1(nor, param_header);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SFDP_SCCR_MAP_ID:
|
case SFDP_SCCR_MAP_ID:
|
||||||
err = spi_nor_parse_sccr(nor, param_header, params);
|
err = spi_nor_parse_sccr(nor, param_header);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -107,7 +107,6 @@ struct sfdp_parameter_header {
|
|||||||
u8 id_msb;
|
u8 id_msb;
|
||||||
};
|
};
|
||||||
|
|
||||||
int spi_nor_parse_sfdp(struct spi_nor *nor,
|
int spi_nor_parse_sfdp(struct spi_nor *nor);
|
||||||
struct spi_nor_flash_parameter *params);
|
|
||||||
|
|
||||||
#endif /* __LINUX_MTD_SFDP_H */
|
#endif /* __LINUX_MTD_SFDP_H */
|
||||||
|
@ -142,8 +142,7 @@ static void s28hs512t_post_sfdp_fixup(struct spi_nor *nor)
|
|||||||
|
|
||||||
static int s28hs512t_post_bfpt_fixup(struct spi_nor *nor,
|
static int s28hs512t_post_bfpt_fixup(struct spi_nor *nor,
|
||||||
const struct sfdp_parameter_header *bfpt_header,
|
const struct sfdp_parameter_header *bfpt_header,
|
||||||
const struct sfdp_bfpt *bfpt,
|
const struct sfdp_bfpt *bfpt)
|
||||||
struct spi_nor_flash_parameter *params)
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* The BFPT table advertises a 512B page size but the page size is
|
* The BFPT table advertises a 512B page size but the page size is
|
||||||
@ -162,9 +161,9 @@ static int s28hs512t_post_bfpt_fixup(struct spi_nor *nor,
|
|||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
if (nor->bouncebuf[0] & SPINOR_REG_CYPRESS_CFR3V_PGSZ)
|
if (nor->bouncebuf[0] & SPINOR_REG_CYPRESS_CFR3V_PGSZ)
|
||||||
params->page_size = 512;
|
nor->params->page_size = 512;
|
||||||
else
|
else
|
||||||
params->page_size = 256;
|
nor->params->page_size = 256;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -178,8 +177,7 @@ static struct spi_nor_fixups s28hs512t_fixups = {
|
|||||||
static int
|
static int
|
||||||
s25fs_s_post_bfpt_fixups(struct spi_nor *nor,
|
s25fs_s_post_bfpt_fixups(struct spi_nor *nor,
|
||||||
const struct sfdp_parameter_header *bfpt_header,
|
const struct sfdp_parameter_header *bfpt_header,
|
||||||
const struct sfdp_bfpt *bfpt,
|
const struct sfdp_bfpt *bfpt)
|
||||||
struct spi_nor_flash_parameter *params)
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* The S25FS-S chip family reports 512-byte pages in BFPT but
|
* The S25FS-S chip family reports 512-byte pages in BFPT but
|
||||||
@ -187,7 +185,7 @@ s25fs_s_post_bfpt_fixups(struct spi_nor *nor,
|
|||||||
* of 256 bytes. Overwrite the page size advertised by BFPT
|
* of 256 bytes. Overwrite the page size advertised by BFPT
|
||||||
* to get the writes working.
|
* to get the writes working.
|
||||||
*/
|
*/
|
||||||
params->page_size = 256;
|
nor->params->page_size = 256;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
427
drivers/mtd/spi-nor/swp.c
Normal file
427
drivers/mtd/spi-nor/swp.c
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* SPI NOR Software Write Protection logic.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2005, Intec Automation Inc.
|
||||||
|
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||||
|
*/
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/spi-nor.h>
|
||||||
|
|
||||||
|
#include "core.h"
|
||||||
|
|
||||||
|
static u8 spi_nor_get_sr_bp_mask(struct spi_nor *nor)
|
||||||
|
{
|
||||||
|
u8 mask = SR_BP2 | SR_BP1 | SR_BP0;
|
||||||
|
|
||||||
|
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6)
|
||||||
|
return mask | SR_BP3_BIT6;
|
||||||
|
|
||||||
|
if (nor->flags & SNOR_F_HAS_4BIT_BP)
|
||||||
|
return mask | SR_BP3;
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor)
|
||||||
|
{
|
||||||
|
if (nor->flags & SNOR_F_HAS_SR_TB_BIT6)
|
||||||
|
return SR_TB_BIT6;
|
||||||
|
else
|
||||||
|
return SR_TB_BIT5;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
|
||||||
|
{
|
||||||
|
unsigned int bp_slots, bp_slots_needed;
|
||||||
|
u8 mask = spi_nor_get_sr_bp_mask(nor);
|
||||||
|
|
||||||
|
/* Reserved one for "protect none" and one for "protect all". */
|
||||||
|
bp_slots = (1 << hweight8(mask)) - 2;
|
||||||
|
bp_slots_needed = ilog2(nor->info->n_sectors);
|
||||||
|
|
||||||
|
if (bp_slots_needed > bp_slots)
|
||||||
|
return nor->info->sector_size <<
|
||||||
|
(bp_slots_needed - bp_slots);
|
||||||
|
else
|
||||||
|
return nor->info->sector_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs,
|
||||||
|
uint64_t *len)
|
||||||
|
{
|
||||||
|
struct mtd_info *mtd = &nor->mtd;
|
||||||
|
u64 min_prot_len;
|
||||||
|
u8 mask = spi_nor_get_sr_bp_mask(nor);
|
||||||
|
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
|
||||||
|
u8 bp, val = sr & mask;
|
||||||
|
|
||||||
|
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6)
|
||||||
|
val = (val & ~SR_BP3_BIT6) | SR_BP3;
|
||||||
|
|
||||||
|
bp = val >> SR_BP_SHIFT;
|
||||||
|
|
||||||
|
if (!bp) {
|
||||||
|
/* No protection */
|
||||||
|
*ofs = 0;
|
||||||
|
*len = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
min_prot_len = spi_nor_get_min_prot_length_sr(nor);
|
||||||
|
*len = min_prot_len << (bp - 1);
|
||||||
|
|
||||||
|
if (*len > mtd->size)
|
||||||
|
*len = mtd->size;
|
||||||
|
|
||||||
|
if (nor->flags & SNOR_F_HAS_SR_TB && sr & tb_mask)
|
||||||
|
*ofs = 0;
|
||||||
|
else
|
||||||
|
*ofs = mtd->size - *len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return true if the entire region is locked (if @locked is true) or unlocked
|
||||||
|
* (if @locked is false); false otherwise.
|
||||||
|
*/
|
||||||
|
static bool spi_nor_check_lock_status_sr(struct spi_nor *nor, loff_t ofs,
|
||||||
|
uint64_t len, u8 sr, bool locked)
|
||||||
|
{
|
||||||
|
loff_t lock_offs, lock_offs_max, offs_max;
|
||||||
|
uint64_t lock_len;
|
||||||
|
|
||||||
|
if (!len)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
spi_nor_get_locked_range_sr(nor, sr, &lock_offs, &lock_len);
|
||||||
|
|
||||||
|
lock_offs_max = lock_offs + lock_len;
|
||||||
|
offs_max = ofs + len;
|
||||||
|
|
||||||
|
if (locked)
|
||||||
|
/* Requested range is a sub-range of locked range */
|
||||||
|
return (offs_max <= lock_offs_max) && (ofs >= lock_offs);
|
||||||
|
else
|
||||||
|
/* Requested range does not overlap with locked range */
|
||||||
|
return (ofs >= lock_offs_max) || (offs_max <= lock_offs);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len,
|
||||||
|
u8 sr)
|
||||||
|
{
|
||||||
|
return spi_nor_check_lock_status_sr(nor, ofs, len, sr, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs,
|
||||||
|
uint64_t len, u8 sr)
|
||||||
|
{
|
||||||
|
return spi_nor_check_lock_status_sr(nor, ofs, len, sr, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lock a region of the flash. Compatible with ST Micro and similar flash.
|
||||||
|
* Supports the block protection bits BP{0,1,2}/BP{0,1,2,3} in the status
|
||||||
|
* register
|
||||||
|
* (SR). Does not support these features found in newer SR bitfields:
|
||||||
|
* - SEC: sector/block protect - only handle SEC=0 (block protect)
|
||||||
|
* - CMP: complement protect - only support CMP=0 (range is not complemented)
|
||||||
|
*
|
||||||
|
* Support for the following is provided conditionally for some flash:
|
||||||
|
* - TB: top/bottom protect
|
||||||
|
*
|
||||||
|
* Sample table portion for 8MB flash (Winbond w25q64fw):
|
||||||
|
*
|
||||||
|
* SEC | TB | BP2 | BP1 | BP0 | Prot Length | Protected Portion
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
* X | X | 0 | 0 | 0 | NONE | NONE
|
||||||
|
* 0 | 0 | 0 | 0 | 1 | 128 KB | Upper 1/64
|
||||||
|
* 0 | 0 | 0 | 1 | 0 | 256 KB | Upper 1/32
|
||||||
|
* 0 | 0 | 0 | 1 | 1 | 512 KB | Upper 1/16
|
||||||
|
* 0 | 0 | 1 | 0 | 0 | 1 MB | Upper 1/8
|
||||||
|
* 0 | 0 | 1 | 0 | 1 | 2 MB | Upper 1/4
|
||||||
|
* 0 | 0 | 1 | 1 | 0 | 4 MB | Upper 1/2
|
||||||
|
* X | X | 1 | 1 | 1 | 8 MB | ALL
|
||||||
|
* ------|-------|-------|-------|-------|---------------|-------------------
|
||||||
|
* 0 | 1 | 0 | 0 | 1 | 128 KB | Lower 1/64
|
||||||
|
* 0 | 1 | 0 | 1 | 0 | 256 KB | Lower 1/32
|
||||||
|
* 0 | 1 | 0 | 1 | 1 | 512 KB | Lower 1/16
|
||||||
|
* 0 | 1 | 1 | 0 | 0 | 1 MB | Lower 1/8
|
||||||
|
* 0 | 1 | 1 | 0 | 1 | 2 MB | Lower 1/4
|
||||||
|
* 0 | 1 | 1 | 1 | 0 | 4 MB | Lower 1/2
|
||||||
|
*
|
||||||
|
* Returns negative on errors, 0 on success.
|
||||||
|
*/
|
||||||
|
static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||||
|
{
|
||||||
|
struct mtd_info *mtd = &nor->mtd;
|
||||||
|
u64 min_prot_len;
|
||||||
|
int ret, status_old, status_new;
|
||||||
|
u8 mask = spi_nor_get_sr_bp_mask(nor);
|
||||||
|
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
|
||||||
|
u8 pow, val;
|
||||||
|
loff_t lock_len;
|
||||||
|
bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
|
||||||
|
bool use_top;
|
||||||
|
|
||||||
|
ret = spi_nor_read_sr(nor, nor->bouncebuf);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
status_old = nor->bouncebuf[0];
|
||||||
|
|
||||||
|
/* If nothing in our range is unlocked, we don't need to do anything */
|
||||||
|
if (spi_nor_is_locked_sr(nor, ofs, len, status_old))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* If anything below us is unlocked, we can't use 'bottom' protection */
|
||||||
|
if (!spi_nor_is_locked_sr(nor, 0, ofs, status_old))
|
||||||
|
can_be_bottom = false;
|
||||||
|
|
||||||
|
/* If anything above us is unlocked, we can't use 'top' protection */
|
||||||
|
if (!spi_nor_is_locked_sr(nor, ofs + len, mtd->size - (ofs + len),
|
||||||
|
status_old))
|
||||||
|
can_be_top = false;
|
||||||
|
|
||||||
|
if (!can_be_bottom && !can_be_top)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Prefer top, if both are valid */
|
||||||
|
use_top = can_be_top;
|
||||||
|
|
||||||
|
/* lock_len: length of region that should end up locked */
|
||||||
|
if (use_top)
|
||||||
|
lock_len = mtd->size - ofs;
|
||||||
|
else
|
||||||
|
lock_len = ofs + len;
|
||||||
|
|
||||||
|
if (lock_len == mtd->size) {
|
||||||
|
val = mask;
|
||||||
|
} else {
|
||||||
|
min_prot_len = spi_nor_get_min_prot_length_sr(nor);
|
||||||
|
pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
|
||||||
|
val = pow << SR_BP_SHIFT;
|
||||||
|
|
||||||
|
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3)
|
||||||
|
val = (val & ~SR_BP3) | SR_BP3_BIT6;
|
||||||
|
|
||||||
|
if (val & ~mask)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Don't "lock" with no region! */
|
||||||
|
if (!(val & mask))
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_new = (status_old & ~mask & ~tb_mask) | val;
|
||||||
|
|
||||||
|
/* Disallow further writes if WP pin is asserted */
|
||||||
|
status_new |= SR_SRWD;
|
||||||
|
|
||||||
|
if (!use_top)
|
||||||
|
status_new |= tb_mask;
|
||||||
|
|
||||||
|
/* Don't bother if they're the same */
|
||||||
|
if (status_new == status_old)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Only modify protection if it will not unlock other areas */
|
||||||
|
if ((status_new & mask) < (status_old & mask))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
return spi_nor_write_sr_and_check(nor, status_new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Unlock a region of the flash. See spi_nor_sr_lock() for more info
|
||||||
|
*
|
||||||
|
* Returns negative on errors, 0 on success.
|
||||||
|
*/
|
||||||
|
static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||||
|
{
|
||||||
|
struct mtd_info *mtd = &nor->mtd;
|
||||||
|
u64 min_prot_len;
|
||||||
|
int ret, status_old, status_new;
|
||||||
|
u8 mask = spi_nor_get_sr_bp_mask(nor);
|
||||||
|
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
|
||||||
|
u8 pow, val;
|
||||||
|
loff_t lock_len;
|
||||||
|
bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
|
||||||
|
bool use_top;
|
||||||
|
|
||||||
|
ret = spi_nor_read_sr(nor, nor->bouncebuf);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
status_old = nor->bouncebuf[0];
|
||||||
|
|
||||||
|
/* If nothing in our range is locked, we don't need to do anything */
|
||||||
|
if (spi_nor_is_unlocked_sr(nor, ofs, len, status_old))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* If anything below us is locked, we can't use 'top' protection */
|
||||||
|
if (!spi_nor_is_unlocked_sr(nor, 0, ofs, status_old))
|
||||||
|
can_be_top = false;
|
||||||
|
|
||||||
|
/* If anything above us is locked, we can't use 'bottom' protection */
|
||||||
|
if (!spi_nor_is_unlocked_sr(nor, ofs + len, mtd->size - (ofs + len),
|
||||||
|
status_old))
|
||||||
|
can_be_bottom = false;
|
||||||
|
|
||||||
|
if (!can_be_bottom && !can_be_top)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* Prefer top, if both are valid */
|
||||||
|
use_top = can_be_top;
|
||||||
|
|
||||||
|
/* lock_len: length of region that should remain locked */
|
||||||
|
if (use_top)
|
||||||
|
lock_len = mtd->size - (ofs + len);
|
||||||
|
else
|
||||||
|
lock_len = ofs;
|
||||||
|
|
||||||
|
if (lock_len == 0) {
|
||||||
|
val = 0; /* fully unlocked */
|
||||||
|
} else {
|
||||||
|
min_prot_len = spi_nor_get_min_prot_length_sr(nor);
|
||||||
|
pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
|
||||||
|
val = pow << SR_BP_SHIFT;
|
||||||
|
|
||||||
|
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3)
|
||||||
|
val = (val & ~SR_BP3) | SR_BP3_BIT6;
|
||||||
|
|
||||||
|
/* Some power-of-two sizes are not supported */
|
||||||
|
if (val & ~mask)
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_new = (status_old & ~mask & ~tb_mask) | val;
|
||||||
|
|
||||||
|
/* Don't protect status register if we're fully unlocked */
|
||||||
|
if (lock_len == 0)
|
||||||
|
status_new &= ~SR_SRWD;
|
||||||
|
|
||||||
|
if (!use_top)
|
||||||
|
status_new |= tb_mask;
|
||||||
|
|
||||||
|
/* Don't bother if they're the same */
|
||||||
|
if (status_new == status_old)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Only modify protection if it will not lock other areas */
|
||||||
|
if ((status_new & mask) > (status_old & mask))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
return spi_nor_write_sr_and_check(nor, status_new);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if a region of the flash is (completely) locked. See spi_nor_sr_lock()
|
||||||
|
* for more info.
|
||||||
|
*
|
||||||
|
* Returns 1 if entire region is locked, 0 if any portion is unlocked, and
|
||||||
|
* negative on errors.
|
||||||
|
*/
|
||||||
|
static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = spi_nor_read_sr(nor, nor->bouncebuf);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return spi_nor_is_locked_sr(nor, ofs, len, nor->bouncebuf[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct spi_nor_locking_ops spi_nor_sr_locking_ops = {
|
||||||
|
.lock = spi_nor_sr_lock,
|
||||||
|
.unlock = spi_nor_sr_unlock,
|
||||||
|
.is_locked = spi_nor_sr_is_locked,
|
||||||
|
};
|
||||||
|
|
||||||
|
void spi_nor_init_default_locking_ops(struct spi_nor *nor)
|
||||||
|
{
|
||||||
|
nor->params->locking_ops = &spi_nor_sr_locking_ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||||
|
{
|
||||||
|
struct spi_nor *nor = mtd_to_spi_nor(mtd);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = spi_nor_lock_and_prep(nor);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = nor->params->locking_ops->lock(nor, ofs, len);
|
||||||
|
|
||||||
|
spi_nor_unlock_and_unprep(nor);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int spi_nor_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||||
|
{
|
||||||
|
struct spi_nor *nor = mtd_to_spi_nor(mtd);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = spi_nor_lock_and_prep(nor);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = nor->params->locking_ops->unlock(nor, ofs, len);
|
||||||
|
|
||||||
|
spi_nor_unlock_and_unprep(nor);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int spi_nor_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||||
|
{
|
||||||
|
struct spi_nor *nor = mtd_to_spi_nor(mtd);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = spi_nor_lock_and_prep(nor);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = nor->params->locking_ops->is_locked(nor, ofs, len);
|
||||||
|
|
||||||
|
spi_nor_unlock_and_unprep(nor);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* spi_nor_try_unlock_all() - Tries to unlock the entire flash memory array.
|
||||||
|
* @nor: pointer to a 'struct spi_nor'.
|
||||||
|
*
|
||||||
|
* Some SPI NOR flashes are write protected by default after a power-on reset
|
||||||
|
* cycle, in order to avoid inadvertent writes during power-up. Backward
|
||||||
|
* compatibility imposes to unlock the entire flash memory array at power-up
|
||||||
|
* by default.
|
||||||
|
*
|
||||||
|
* Unprotecting the entire flash array will fail for boards which are hardware
|
||||||
|
* write-protected. Thus any errors are ignored.
|
||||||
|
*/
|
||||||
|
void spi_nor_try_unlock_all(struct spi_nor *nor)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!(nor->flags & SNOR_F_HAS_LOCK))
|
||||||
|
return;
|
||||||
|
|
||||||
|
dev_dbg(nor->dev, "Unprotecting entire flash array\n");
|
||||||
|
|
||||||
|
ret = spi_nor_unlock(&nor->mtd, 0, nor->params->size);
|
||||||
|
if (ret)
|
||||||
|
dev_dbg(nor->dev, "Failed to unlock the entire flash memory array\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void spi_nor_register_locking_ops(struct spi_nor *nor)
|
||||||
|
{
|
||||||
|
struct mtd_info *mtd = &nor->mtd;
|
||||||
|
|
||||||
|
if (!nor->params->locking_ops)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mtd->_lock = spi_nor_lock;
|
||||||
|
mtd->_unlock = spi_nor_unlock;
|
||||||
|
mtd->_is_locked = spi_nor_is_locked;
|
||||||
|
}
|
@ -11,8 +11,7 @@
|
|||||||
static int
|
static int
|
||||||
w25q256_post_bfpt_fixups(struct spi_nor *nor,
|
w25q256_post_bfpt_fixups(struct spi_nor *nor,
|
||||||
const struct sfdp_parameter_header *bfpt_header,
|
const struct sfdp_parameter_header *bfpt_header,
|
||||||
const struct sfdp_bfpt *bfpt,
|
const struct sfdp_bfpt *bfpt)
|
||||||
struct spi_nor_flash_parameter *params)
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* W25Q256JV supports 4B opcodes but W25Q256FV does not.
|
* W25Q256JV supports 4B opcodes but W25Q256FV does not.
|
||||||
@ -55,14 +54,18 @@ static const struct flash_info winbond_parts[] = {
|
|||||||
{ "w25q32", INFO(0xef4016, 0, 64 * 1024, 64, SECT_4K) },
|
{ "w25q32", INFO(0xef4016, 0, 64 * 1024, 64, SECT_4K) },
|
||||||
{ "w25q32dw", INFO(0xef6016, 0, 64 * 1024, 64,
|
{ "w25q32dw", INFO(0xef6016, 0, 64 * 1024, 64,
|
||||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||||
|
OTP_INFO(256, 3, 0x1000, 0x1000)
|
||||||
|
},
|
||||||
|
|
||||||
{ "w25q32jv", INFO(0xef7016, 0, 64 * 1024, 64,
|
{ "w25q32jv", INFO(0xef7016, 0, 64 * 1024, 64,
|
||||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||||
},
|
},
|
||||||
{ "w25q32jwm", INFO(0xef8016, 0, 64 * 1024, 64,
|
{ "w25q32jwm", INFO(0xef8016, 0, 64 * 1024, 64,
|
||||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||||
|
OTP_INFO(256, 3, 0x1000, 0x1000) },
|
||||||
{ "w25q64jwm", INFO(0xef8017, 0, 64 * 1024, 128,
|
{ "w25q64jwm", INFO(0xef8017, 0, 64 * 1024, 128,
|
||||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
||||||
@ -97,6 +100,8 @@ static const struct flash_info winbond_parts[] = {
|
|||||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||||
{ "w25m512jv", INFO(0xef7119, 0, 64 * 1024, 1024,
|
{ "w25m512jv", INFO(0xef7119, 0, 64 * 1024, 1024,
|
||||||
SECT_4K | SPI_NOR_QUAD_READ | SPI_NOR_DUAL_READ) },
|
SECT_4K | SPI_NOR_QUAD_READ | SPI_NOR_DUAL_READ) },
|
||||||
|
{ "w25q512jvq", INFO(0xef4020, 0, 64 * 1024, 1024,
|
||||||
|
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -131,9 +136,18 @@ static int winbond_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
|
|||||||
return spi_nor_write_disable(nor);
|
return spi_nor_write_disable(nor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const struct spi_nor_otp_ops winbond_otp_ops = {
|
||||||
|
.read = spi_nor_otp_read_secr,
|
||||||
|
.write = spi_nor_otp_write_secr,
|
||||||
|
.lock = spi_nor_otp_lock_sr2,
|
||||||
|
.is_locked = spi_nor_otp_is_locked_sr2,
|
||||||
|
};
|
||||||
|
|
||||||
static void winbond_default_init(struct spi_nor *nor)
|
static void winbond_default_init(struct spi_nor *nor)
|
||||||
{
|
{
|
||||||
nor->params->set_4byte_addr_mode = winbond_set_4byte_addr_mode;
|
nor->params->set_4byte_addr_mode = winbond_set_4byte_addr_mode;
|
||||||
|
if (nor->params->otp.org->n_regions)
|
||||||
|
nor->params->otp.ops = &winbond_otp_ops;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct spi_nor_fixups winbond_fixups = {
|
static const struct spi_nor_fixups winbond_fixups = {
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
#ifndef LPC_ICH_H
|
#ifndef LPC_ICH_H
|
||||||
#define LPC_ICH_H
|
#define LPC_ICH_H
|
||||||
|
|
||||||
#include <linux/platform_data/intel-spi.h>
|
#include <linux/platform_data/x86/intel-spi.h>
|
||||||
|
|
||||||
/* GPIO resources */
|
/* GPIO resources */
|
||||||
#define ICH_RES_GPIO 0
|
#define ICH_RES_GPIO 0
|
||||||
|
@ -107,6 +107,11 @@
|
|||||||
#define SPINOR_OP_RD_EVCR 0x65 /* Read EVCR register */
|
#define SPINOR_OP_RD_EVCR 0x65 /* Read EVCR register */
|
||||||
#define SPINOR_OP_WD_EVCR 0x61 /* Write EVCR register */
|
#define SPINOR_OP_WD_EVCR 0x61 /* Write EVCR register */
|
||||||
|
|
||||||
|
/* Used for GigaDevices and Winbond flashes. */
|
||||||
|
#define SPINOR_OP_ESECR 0x44 /* Erase Security registers */
|
||||||
|
#define SPINOR_OP_PSECR 0x42 /* Program Security registers */
|
||||||
|
#define SPINOR_OP_RSECR 0x48 /* Read Security registers */
|
||||||
|
|
||||||
/* Status Register bits. */
|
/* Status Register bits. */
|
||||||
#define SR_WIP BIT(0) /* Write in progress */
|
#define SR_WIP BIT(0) /* Write in progress */
|
||||||
#define SR_WEL BIT(1) /* Write enable latch */
|
#define SR_WEL BIT(1) /* Write enable latch */
|
||||||
@ -138,6 +143,9 @@
|
|||||||
|
|
||||||
/* Status Register 2 bits. */
|
/* Status Register 2 bits. */
|
||||||
#define SR2_QUAD_EN_BIT1 BIT(1)
|
#define SR2_QUAD_EN_BIT1 BIT(1)
|
||||||
|
#define SR2_LB1 BIT(3) /* Security Register Lock Bit 1 */
|
||||||
|
#define SR2_LB2 BIT(4) /* Security Register Lock Bit 2 */
|
||||||
|
#define SR2_LB3 BIT(5) /* Security Register Lock Bit 3 */
|
||||||
#define SR2_QUAD_EN_BIT7 BIT(7)
|
#define SR2_QUAD_EN_BIT7 BIT(7)
|
||||||
|
|
||||||
/* Supported SPI protocols */
|
/* Supported SPI protocols */
|
||||||
|
Loading…
x
Reference in New Issue
Block a user