SPI NOR changes for 6.19

Notable changes:
 
 - Fix SMPT parsing for S25FS-S flash family. They report variable dummy
   cycles for reads. This results in the default of 0 being used. This
   works for other Infineon chips, but not for the S25FS-S family. They
   need 8 dummy cycles. Add fixup hooks to specify that. Also add fixup
   hooks to fix incorrect map ID data in SFDP.
 
 - Add support for a bunch of Winbond flashes. Their block protection
   information is not discoverable, so they need to have an entry in the
   flash tables to describe that.
 
 - Some cleanups for Micron flash support.
 
 - Add support for Micron mt35xu01gbba.
 
 - Some SPI controllers like the Intel one on the PCI bus do not support
   the read CR opcode (0x35). Do not use the opcode if the controller
   does not support it.
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQQTlUWNzXGEo3bFmyIR4drqP028CQUCaSjP+QAKCRAR4drqP028
 CfGsAQC5Vj+FaeQHyY+yywqM5wxE+xj6mMCDNixd2FVYlf5b7wEA2/9bpiHjy3qi
 4MZmFJNcE+XsxReWDTBTZ6VbrjDlqg0=
 =M+s4
 -----END PGP SIGNATURE-----

Merge tag 'spi-nor/for-6.19' of https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux into mtd/next

SPI NOR changes for 6.19

Notable changes:

- Fix SMPT parsing for S25FS-S flash family. They report variable dummy
  cycles for reads. This results in the default of 0 being used. This
  works for other Infineon chips, but not for the S25FS-S family. They
  need 8 dummy cycles. Add fixup hooks to specify that. Also add fixup
  hooks to fix incorrect map ID data in SFDP.

- Add support for a bunch of Winbond flashes. Their block protection
  information is not discoverable, so they need to have an entry in the
  flash tables to describe that.

- Some cleanups for Micron flash support.

- Add support for Micron mt35xu01gbba.

- Some SPI controllers like the Intel one on the PCI bus do not support
  the read CR opcode (0x35). Do not use the opcode if the controller
  does not support it.

# -----BEGIN PGP SIGNATURE-----
#
# iHUEABYKAB0WIQQTlUWNzXGEo3bFmyIR4drqP028CQUCaSjP+QAKCRAR4drqP028
# CfGsAQC5Vj+FaeQHyY+yywqM5wxE+xj6mMCDNixd2FVYlf5b7wEA2/9bpiHjy3qi
# 4MZmFJNcE+XsxReWDTBTZ6VbrjDlqg0=
# =M+s4
# -----END PGP SIGNATURE-----
# gpg: Signature made jeu. 27 nov. 2025 23:26:01 CET
# gpg:                using EDDSA key 1395458DCD7184A376C59B2211E1DAEA3F4DBC09
# gpg: Good signature from "Pratyush Yadav <p.yadav@ti.com>" [expired]
# gpg:                 aka "Pratyush Yadav <me@yadavpratyush.com>" [expired]
# gpg: p.yadav@ti.com: Verified 5 signatures in the past 3 years.  Encrypted 0 messages.
# gpg: me@yadavpratyush.com: Verified 5 signatures in the past 3 years.  Encrypted
#      0 messages.
# gpg: Note: This key has expired!
# Primary key fingerprint: 805C 3923 2FBE 108C 49E1  663C F650 3556 C11B 1CCD
#      Subkey fingerprint: 1395 458D CD71 84A3 76C5  9B22 11E1 DAEA 3F4D BC09
pull/1354/merge
Miquel Raynal 2025-11-29 14:06:31 +01:00
commit de95c58798
7 changed files with 164 additions and 46 deletions

View File

@ -632,6 +632,7 @@ Peter Oruba <peter.oruba@amd.com>
Peter Oruba <peter@oruba.de> Peter Oruba <peter@oruba.de>
Pierre-Louis Bossart <pierre-louis.bossart@linux.dev> <pierre-louis.bossart@linux.intel.com> Pierre-Louis Bossart <pierre-louis.bossart@linux.dev> <pierre-louis.bossart@linux.intel.com>
Pratyush Anand <pratyush.anand@gmail.com> <pratyush.anand@st.com> Pratyush Anand <pratyush.anand@gmail.com> <pratyush.anand@st.com>
Pratyush Yadav <pratyush@kernel.org> <ptyadav@amazon.de>
Praveen BP <praveenbp@ti.com> Praveen BP <praveenbp@ti.com>
Pradeep Kumar Chitrapu <quic_pradeepc@quicinc.com> <pradeepc@codeaurora.org> Pradeep Kumar Chitrapu <quic_pradeepc@quicinc.com> <pradeepc@codeaurora.org>
Prasad Sodagudi <quic_psodagud@quicinc.com> <psodagud@codeaurora.org> Prasad Sodagudi <quic_psodagud@quicinc.com> <psodagud@codeaurora.org>

View File

@ -2459,6 +2459,16 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps)
&params->page_programs[ppidx])) &params->page_programs[ppidx]))
*hwcaps &= ~BIT(cap); *hwcaps &= ~BIT(cap);
} }
/* Some SPI controllers might not support CR read opcode. */
if (!(nor->flags & SNOR_F_NO_READ_CR)) {
struct spi_mem_op op = SPI_NOR_RDCR_OP(nor->bouncebuf);
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
if (spi_nor_spimem_check_op(nor, &op))
nor->flags |= SNOR_F_NO_READ_CR;
}
} }
/** /**

View File

@ -409,6 +409,10 @@ struct spi_nor_flash_parameter {
* flash parameters when information provided by the flash_info * flash parameters when information provided by the flash_info
* table is incomplete or wrong. * table is incomplete or wrong.
* @post_bfpt: called after the BFPT table has been parsed * @post_bfpt: called after the BFPT table has been parsed
* @smpt_read_dummy: called during SMPT table is being parsed. Used to fix the
* number of dummy cycles in read register ops.
* @smpt_map_id: called after map ID in SMPT table has been determined for the
* case the map ID is wrong and needs to be fixed.
* @post_sfdp: called after SFDP has been parsed (is also called for SPI NORs * @post_sfdp: called after SFDP has been parsed (is also called for SPI NORs
* that do not support RDSFDP). Typically used to tweak various * that do not support RDSFDP). Typically used to tweak various
* parameters that could not be extracted by other means (i.e. * parameters that could not be extracted by other means (i.e.
@ -426,6 +430,8 @@ struct spi_nor_fixups {
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);
void (*smpt_read_dummy)(const struct spi_nor *nor, u8 *read_dummy);
void (*smpt_map_id)(const struct spi_nor *nor, u8 *map_id);
int (*post_sfdp)(struct spi_nor *nor); int (*post_sfdp)(struct spi_nor *nor);
int (*late_init)(struct spi_nor *nor); int (*late_init)(struct spi_nor *nor);
}; };

View File

@ -127,9 +127,36 @@ static int micron_st_nor_set_octal_dtr(struct spi_nor *nor, bool enable)
micron_st_nor_octal_dtr_dis(nor); micron_st_nor_octal_dtr_dis(nor);
} }
static void mt35xu512aba_default_init(struct spi_nor *nor) static int micron_st_nor_four_die_late_init(struct spi_nor *nor)
{ {
nor->params->set_octal_dtr = micron_st_nor_set_octal_dtr; struct spi_nor_flash_parameter *params = nor->params;
params->die_erase_opcode = SPINOR_OP_MT_DIE_ERASE;
params->n_dice = 4;
/*
* Unfortunately the die erase opcode does not have a 4-byte opcode
* correspondent for these flashes. The SFDP 4BAIT table fails to
* consider the die erase too. We're forced to enter in the 4 byte
* address mode in order to benefit of the die erase.
*/
return spi_nor_set_4byte_addr_mode(nor, true);
}
static int micron_st_nor_two_die_late_init(struct spi_nor *nor)
{
struct spi_nor_flash_parameter *params = nor->params;
params->die_erase_opcode = SPINOR_OP_MT_DIE_ERASE;
params->n_dice = 2;
/*
* Unfortunately the die erase opcode does not have a 4-byte opcode
* correspondent for these flashes. The SFDP 4BAIT table fails to
* consider the die erase too. We're forced to enter in the 4 byte
* address mode in order to benefit of the die erase.
*/
return spi_nor_set_4byte_addr_mode(nor, true);
} }
static int mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor) static int mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor)
@ -155,22 +182,38 @@ static int mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor)
} }
static const struct spi_nor_fixups mt35xu512aba_fixups = { static const struct spi_nor_fixups mt35xu512aba_fixups = {
.default_init = mt35xu512aba_default_init,
.post_sfdp = mt35xu512aba_post_sfdp_fixup, .post_sfdp = mt35xu512aba_post_sfdp_fixup,
}; };
static const struct spi_nor_fixups mt35xu01gbba_fixups = {
.post_sfdp = mt35xu512aba_post_sfdp_fixup,
.late_init = micron_st_nor_two_die_late_init,
};
static const struct flash_info micron_nor_parts[] = { static const struct flash_info micron_nor_parts[] = {
{ {
/* MT35XU512ABA */
.id = SNOR_ID(0x2c, 0x5b, 0x1a), .id = SNOR_ID(0x2c, 0x5b, 0x1a),
.name = "mt35xu512aba",
.sector_size = SZ_128K,
.size = SZ_64M,
.no_sfdp_flags = SECT_4K | SPI_NOR_OCTAL_READ |
SPI_NOR_OCTAL_DTR_READ | SPI_NOR_OCTAL_DTR_PP,
.mfr_flags = USE_FSR, .mfr_flags = USE_FSR,
.fixup_flags = SPI_NOR_4B_OPCODES | SPI_NOR_IO_MODE_EN_VOLATILE, .fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE,
.fixups = &mt35xu512aba_fixups, .fixups = &mt35xu512aba_fixups,
}, { }, {
/* MT35XU01GBBA */
.id = SNOR_ID(0x2c, 0x5b, 0x1b),
.mfr_flags = USE_FSR,
.fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE,
.fixups = &mt35xu01gbba_fixups,
}, {
/*
* The MT35XU02GCBA flash device does not support chip erase,
* according to its datasheet. It supports die erase, which
* means the current driver implementation will likely need to
* be converted to use die erase. Furthermore, similar to the
* MT35XU01GBBA, the SPI_NOR_IO_MODE_EN_VOLATILE flag probably
* needs to be enabled.
*
* TODO: Fix these and test on real hardware.
*/
.id = SNOR_ID(0x2c, 0x5b, 0x1c), .id = SNOR_ID(0x2c, 0x5b, 0x1c),
.name = "mt35xu02g", .name = "mt35xu02g",
.sector_size = SZ_128K, .sector_size = SZ_128K,
@ -193,48 +236,16 @@ static const struct spi_nor_fixups mt25qu512a_fixups = {
.post_bfpt = mt25qu512a_post_bfpt_fixup, .post_bfpt = mt25qu512a_post_bfpt_fixup,
}; };
static int st_nor_four_die_late_init(struct spi_nor *nor)
{
struct spi_nor_flash_parameter *params = nor->params;
params->die_erase_opcode = SPINOR_OP_MT_DIE_ERASE;
params->n_dice = 4;
/*
* Unfortunately the die erase opcode does not have a 4-byte opcode
* correspondent for these flashes. The SFDP 4BAIT table fails to
* consider the die erase too. We're forced to enter in the 4 byte
* address mode in order to benefit of the die erase.
*/
return spi_nor_set_4byte_addr_mode(nor, true);
}
static int st_nor_two_die_late_init(struct spi_nor *nor)
{
struct spi_nor_flash_parameter *params = nor->params;
params->die_erase_opcode = SPINOR_OP_MT_DIE_ERASE;
params->n_dice = 2;
/*
* Unfortunately the die erase opcode does not have a 4-byte opcode
* correspondent for these flashes. The SFDP 4BAIT table fails to
* consider the die erase too. We're forced to enter in the 4 byte
* address mode in order to benefit of the die erase.
*/
return spi_nor_set_4byte_addr_mode(nor, true);
}
static const struct spi_nor_fixups n25q00_fixups = { static const struct spi_nor_fixups n25q00_fixups = {
.late_init = st_nor_four_die_late_init, .late_init = micron_st_nor_four_die_late_init,
}; };
static const struct spi_nor_fixups mt25q01_fixups = { static const struct spi_nor_fixups mt25q01_fixups = {
.late_init = st_nor_two_die_late_init, .late_init = micron_st_nor_two_die_late_init,
}; };
static const struct spi_nor_fixups mt25q02_fixups = { static const struct spi_nor_fixups mt25q02_fixups = {
.late_init = st_nor_four_die_late_init, .late_init = micron_st_nor_four_die_late_init,
}; };
static const struct flash_info st_nor_parts[] = { static const struct flash_info st_nor_parts[] = {
@ -635,6 +646,8 @@ static int micron_st_nor_late_init(struct spi_nor *nor)
if (!params->set_4byte_addr_mode) if (!params->set_4byte_addr_mode)
params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode_wren_en4b_ex4b; params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode_wren_en4b_ex4b;
params->set_octal_dtr = micron_st_nor_set_octal_dtr;
return 0; return 0;
} }

View File

@ -699,6 +699,17 @@ static u8 spi_nor_smpt_addr_nbytes(const struct spi_nor *nor, const u32 settings
} }
} }
static void spi_nor_smpt_read_dummy_fixups(const struct spi_nor *nor,
u8 *read_dummy)
{
if (nor->manufacturer && nor->manufacturer->fixups &&
nor->manufacturer->fixups->smpt_read_dummy)
nor->manufacturer->fixups->smpt_read_dummy(nor, read_dummy);
if (nor->info->fixups && nor->info->fixups->smpt_read_dummy)
nor->info->fixups->smpt_read_dummy(nor, read_dummy);
}
/** /**
* spi_nor_smpt_read_dummy() - return the configuration detection command read * spi_nor_smpt_read_dummy() - return the configuration detection command read
* latency, in clock cycles. * latency, in clock cycles.
@ -711,11 +722,24 @@ static u8 spi_nor_smpt_read_dummy(const struct spi_nor *nor, const u32 settings)
{ {
u8 read_dummy = SMPT_CMD_READ_DUMMY(settings); u8 read_dummy = SMPT_CMD_READ_DUMMY(settings);
if (read_dummy == SMPT_CMD_READ_DUMMY_IS_VARIABLE) if (read_dummy == SMPT_CMD_READ_DUMMY_IS_VARIABLE) {
return nor->read_dummy; read_dummy = nor->read_dummy;
spi_nor_smpt_read_dummy_fixups(nor, &read_dummy);
}
return read_dummy; return read_dummy;
} }
static void spi_nor_smpt_map_id_fixups(const struct spi_nor *nor, u8 *map_id)
{
if (nor->manufacturer && nor->manufacturer->fixups &&
nor->manufacturer->fixups->smpt_map_id)
nor->manufacturer->fixups->smpt_map_id(nor, map_id);
if (nor->info->fixups && nor->info->fixups->smpt_map_id)
nor->info->fixups->smpt_map_id(nor, map_id);
}
/** /**
* spi_nor_get_map_in_use() - get the configuration map in use * spi_nor_get_map_in_use() - get the configuration map in use
* @nor: pointer to a 'struct spi_nor' * @nor: pointer to a 'struct spi_nor'
@ -769,6 +793,8 @@ static const u32 *spi_nor_get_map_in_use(struct spi_nor *nor, const u32 *smpt,
map_id = map_id << 1 | !!(*buf & read_data_mask); map_id = map_id << 1 | !!(*buf & read_data_mask);
} }
spi_nor_smpt_map_id_fixups(nor, &map_id);
/* /*
* If command descriptors are provided, they always precede map * If command descriptors are provided, they always precede map
* descriptors in the table. There is no need to start the iteration * descriptors in the table. There is no need to start the iteration

View File

@ -785,8 +785,46 @@ s25fs_s_nor_post_bfpt_fixups(struct spi_nor *nor,
return 0; return 0;
} }
static void s25fs_s_nor_smpt_read_dummy(const struct spi_nor *nor,
u8 *read_dummy)
{
/*
* The configuration detection dwords in S25FS-S SMPT has 65h as
* command instruction and 'variable' as configuration detection command
* latency. Set 8 dummy cycles as it is factory default for 65h (read
* any register) op.
*/
*read_dummy = 8;
}
static void s25fs_s_nor_smpt_map_id_dummy(const struct spi_nor *nor, u8 *map_id)
{
/*
* The S25FS512S chip supports:
* - Hybrid sector option which has physical set of eight 4-KB sectors
* and one 224-KB sector at the top or bottom of address space with
* all remaining sectors of 256-KB
* - Uniform sector option which has uniform 256-KB sectors
*
* On the other hand, the datasheet rev.O Table 71 on page 153 JEDEC
* Sector Map Parameter Dword-6 Config. Detect-3 does use CR3NV[1] to
* discern 64-KB(CR3NV[1]=0) and 256-KB(CR3NV[1]=1) uniform sectors
* device configuration. And in section 7.5.5.1 Configuration Register 3
* Non-volatile (CR3NV) page 61, the CR3NV[1] is RFU Reserved for Future
* Use and set to 0, which means 64-KB uniform. Since the device does
* not support 64-KB uniform sectors in any configuration, parsing SMPT
* table cannot find a valid sector map entry and fails. Fix this up by
* setting SMPT by overwriting the CR3NV[1] value to 1, as the table
* expects.
*/
if (nor->params->size == SZ_64M)
*map_id |= BIT(0);
}
static const struct spi_nor_fixups s25fs_s_nor_fixups = { static const struct spi_nor_fixups s25fs_s_nor_fixups = {
.post_bfpt = s25fs_s_nor_post_bfpt_fixups, .post_bfpt = s25fs_s_nor_post_bfpt_fixups,
.smpt_read_dummy = s25fs_s_nor_smpt_read_dummy,
.smpt_map_id = s25fs_s_nor_smpt_map_id_dummy,
}; };
static const struct flash_info spansion_nor_parts[] = { static const struct flash_info spansion_nor_parts[] = {

View File

@ -343,6 +343,30 @@ static const struct flash_info winbond_nor_parts[] = {
.id = SNOR_ID(0xef, 0x80, 0x20), .id = SNOR_ID(0xef, 0x80, 0x20),
.name = "w25q512nwm", .name = "w25q512nwm",
.otp = SNOR_OTP(256, 3, 0x1000, 0x1000), .otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
}, {
/* W25Q01NWxxIQ */
.id = SNOR_ID(0xef, 0x60, 0x21),
.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
}, {
/* W25Q01NWxxIM */
.id = SNOR_ID(0xef, 0x80, 0x21),
.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
}, {
/* W25Q02NWxxIM */
.id = SNOR_ID(0xef, 0x80, 0x22),
.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
}, {
/* W25H512NWxxAM */
.id = SNOR_ID(0xef, 0xa0, 0x20),
.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
}, {
/* W25H01NWxxAM */
.id = SNOR_ID(0xef, 0xa0, 0x21),
.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
}, {
/* W25H02NWxxAM */
.id = SNOR_ID(0xef, 0xa0, 0x22),
.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
}, },
}; };