From jgross at suse.com  Mon Aug  3 02:07:39 2026
From: jgross at suse.com (=?UTF-8?B?SsO8cmdlbiBHcm/Dnw==?=)
Date: Mon, 3 Aug 2026 11:07:39 +0200
Subject: [PATCH RFC 00/14] Remove PG_private by using page/folio->private
 checks instead
In-Reply-To: <20260731-remove-pg_private-v1-0-142c97ba3562@nvidia.com>
References: <20260731-remove-pg_private-v1-0-142c97ba3562@nvidia.com>
Message-ID: <5ca4d0e4-1747-45a7-a916-a7516bb1692b@suse.com>

On 01.08.26 04:13, Zi Yan wrote:
> Hi all,
> 
> This patchset removes PG_private to make space for upcoming PG_folio
> (reserved as __PG_folio) for identifying pages from a folio (more details
> in Note below). Instead of checking PG_private, all code is changed to
> check page/folio->private != NULL instead.

I'm a little bit worried that page/folio->private is in a union, so today
it could (in theory) be != NULL while PG_private isn't set.

Is it really not possible to enter a path where PG_private is tested while
page/folio->private != NULL due to the union being used otherwise (PG_private
not set)?


Juergen
-------------- next part --------------
A non-text attachment was scrubbed...
Name: OpenPGP_0xB0DE9DD628BF132F.asc
Type: application/pgp-keys
Size: 3683 bytes
Desc: OpenPGP public key
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260803/c412bb84/attachment.bin>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: OpenPGP_signature.asc
Type: application/pgp-signature
Size: 495 bytes
Desc: OpenPGP digital signature
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260803/c412bb84/attachment.sig>

From sales at gazizkz.kz  Mon Aug  3 06:31:42 2026
From: sales at gazizkz.kz (Makhsutbekov Gaziz Imanalyevich)
Date: Mon, 03 Aug 2026 06:31:42 -0700
Subject: PETROLEUM PRODUCTS AVAILABLE FOR SELL.
Message-ID: <fc62e690c7d6efa232fa0919ad5c01c4@gazizkz.kz>

Dear Valued Buyer/Buyer mandate,

We are pleased to introduce ?GAZIZKZ, LLP", a leading representative 
supplier of petroleum products, including Jet Fuel A1, Diesel D2, and 
EN590 and more, with our current availability at ports in Rotterdam, 
Houston, Jurong, and Fujairah.

Our team is dedicated to providing a wide range of petroleum and 
petrochemical products at competitive prices, with delivery options 
including FOB, CIF, and TTO. We believe this could be a valuable 
opportunity for business development.

We look forward to the possibility of working together and exploring 
mutually beneficial opportunities.

Best Regards,
Makhsutbekov Gaziz Imanalyevich
GENERAL DIRECTOR
GAZIZKZ, LLP
WhatsApp: +77785744293



From ziy at nvidia.com  Mon Aug  3 11:13:13 2026
From: ziy at nvidia.com (Zi Yan)
Date: Mon, 03 Aug 2026 14:13:13 -0400
Subject: [PATCH RFC 00/14] Remove PG_private by using
 page/folio->private checks instead
In-Reply-To: <5ca4d0e4-1747-45a7-a916-a7516bb1692b@suse.com>
References: <20260731-remove-pg_private-v1-0-142c97ba3562@nvidia.com>
 <5ca4d0e4-1747-45a7-a916-a7516bb1692b@suse.com>
Message-ID: <DKFIGJBRSDB1.O204NIB7HZMR@nvidia.com>

On Mon Aug 3, 2026 at 5:07 AM EDT, J?rgen Gro? wrote:
> On 01.08.26 04:13, Zi Yan wrote:
>> Hi all,
>> 
>> This patchset removes PG_private to make space for upcoming PG_folio
>> (reserved as __PG_folio) for identifying pages from a folio (more details
>> in Note below). Instead of checking PG_private, all code is changed to
>> check page/folio->private != NULL instead.
>
> I'm a little bit worried that page/folio->private is in a union, so today
> it could (in theory) be != NULL while PG_private isn't set.
>
> Is it really not possible to enter a path where PG_private is tested while
> page/folio->private != NULL due to the union being used otherwise (PG_private
> not set)?

Yes, it is possible. See: #5 in the exceptional users: erofs uses
->private for reverse linked list and in-flight counters without setting
PG_private. I get rid of the first one and converted the second one to
use folio_attach/detach/get_private() to follow the general ->private
use pattern..

For non file system folios, anon swapcache puts swap_entry_t in
->private and hugetlb puts its flags in ->private. I added
folio_test_fs_private() to exclude them, but this helper is planned to
be used by core MM, since filesystem code should not encounter these
two.


-- 
Best Regards,
Yan, Zi



From lkp at intel.com  Mon Aug  3 23:08:12 2026
From: lkp at intel.com (kernel test robot)
Date: Tue, 4 Aug 2026 14:08:12 +0800
Subject: [PATCH] Add JEDEC ID table entries for additional ISSI SPI-NOR
   devices. Additionally added several structs to support ISSI octal flash
  functionality. (Octal SPI 2026 revised)
In-Reply-To: <LV8PR19MB85976C665A59A0DEBAF4F285B6EF2@LV8PR19MB8597.namprd19.prod.outlook.com>
References: <LV8PR19MB85976C665A59A0DEBAF4F285B6EF2@LV8PR19MB8597.namprd19.prod.outlook.com>
Message-ID: <202608041352.DkGtf1aE-lkp@intel.com>

Hi Jeffrey,

kernel test robot noticed the following build errors:

[auto build test ERROR on mtd/spi-nor/next]
[also build test ERROR on linus/master v7.2-rc6 next-20260803]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Jeffrey-Yu/Add-JEDEC-ID-table-entries-for-additional-ISSI-SPI-NOR-devices-Additionally-added-several-structs-to-support-ISSI-octal-/20260804-122036
base:   https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git spi-nor/next
patch link:    https://lore.kernel.org/r/LV8PR19MB85976C665A59A0DEBAF4F285B6EF2%40LV8PR19MB8597.namprd19.prod.outlook.com
patch subject: [PATCH] Add JEDEC ID table entries for additional ISSI SPI-NOR   devices. Additionally added several structs to support ISSI octal flash  functionality. (Octal SPI 2026 revised)
config: powerpc-allmodconfig (https://download.01.org/0day-ci/archive/20260804/202608041352.DkGtf1aE-lkp at intel.com/config)
compiler: powerpc64-linux-gcc (GCC) 16.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260804/202608041352.DkGtf1aE-lkp at intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp at intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202608041352.DkGtf1aE-lkp at intel.com/

All errors (new ones prefixed by >>):

   drivers/mtd/spi-nor/issi.c: In function 'spi_nor_issi_phy_enable':
>> drivers/mtd/spi-nor/issi.c:22:26: error: 'SPINOR_IS_EXSPI' undeclared (first use in this function); did you mean 'SPINOR_OP_EX4B'?
      22 |                 buf[0] = SPINOR_IS_EXSPI;
         |                          ^~~~~~~~~~~~~~~
         |                          SPINOR_OP_EX4B
   drivers/mtd/spi-nor/issi.c:22:26: note: each undeclared identifier is reported only once for each function it appears in
   In file included from include/linux/mtd/spi-nor.h:11,
                    from drivers/mtd/spi-nor/issi.c:7:
>> drivers/mtd/spi-nor/issi.c:25:59: error: 'SPINOR_OP_IS_WR_ANY_REG' undeclared (first use in this function)
      25 |                                 SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
         |                                                           ^~~~~~~~~~~~~~~~~~~~~~~
   include/linux/spi/spi-mem.h:219:24: note: in definition of macro 'SPI_MEM_OP'
     219 |                 .cmd = __cmd,                                   \
         |                        ^~~~~
   drivers/mtd/spi-nor/issi.c:25:44: note: in expansion of macro 'SPI_MEM_OP_CMD'
      25 |                                 SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
         |                                            ^~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:26:52: error: 'SPINOR_REG_IS_CFR0V' undeclared (first use in this function)
      26 |                                 SPI_MEM_OP_ADDR(4, SPINOR_REG_IS_CFR0V, 1),
         |                                                    ^~~~~~~~~~~~~~~~~~~
   include/linux/spi/spi-mem.h:220:25: note: in definition of macro 'SPI_MEM_OP'
     220 |                 .addr = __addr,                                 \
         |                         ^~~~~~
   drivers/mtd/spi-nor/issi.c:26:33: note: in expansion of macro 'SPI_MEM_OP_ADDR'
      26 |                                 SPI_MEM_OP_ADDR(4, SPINOR_REG_IS_CFR0V, 1),
         |                                 ^~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:36:56: error: 'SPI_CONTROLLER_SDR_PHY' undeclared (first use in this function); did you mean 'SPI_CONTROLLER_NO_TX'?
      36 |                 nor->spimem->spi->controller->flags |= SPI_CONTROLLER_SDR_PHY;
         |                                                        ^~~~~~~~~~~~~~~~~~~~~~
         |                                                        SPI_CONTROLLER_NO_TX
   drivers/mtd/spi-nor/issi.c: In function 'spi_nor_issi_octal_dtr_enable':
   drivers/mtd/spi-nor/issi.c:73:59: error: 'SPINOR_OP_IS_WR_ANY_REG' undeclared (first use in this function)
      73 |                                 SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
         |                                                           ^~~~~~~~~~~~~~~~~~~~~~~
   include/linux/spi/spi-mem.h:219:24: note: in definition of macro 'SPI_MEM_OP'
     219 |                 .cmd = __cmd,                                   \
         |                        ^~~~~
   drivers/mtd/spi-nor/issi.c:73:44: note: in expansion of macro 'SPI_MEM_OP_CMD'
      73 |                                 SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
         |                                            ^~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:74:60: error: 'SPINOR_REG_IS_CFR1V' undeclared (first use in this function)
      74 |                                         SPI_MEM_OP_ADDR(3, SPINOR_REG_IS_CFR1V, 1),
         |                                                            ^~~~~~~~~~~~~~~~~~~
   include/linux/spi/spi-mem.h:220:25: note: in definition of macro 'SPI_MEM_OP'
     220 |                 .addr = __addr,                                 \
         |                         ^~~~~~
   drivers/mtd/spi-nor/issi.c:74:41: note: in expansion of macro 'SPI_MEM_OP_ADDR'
      74 |                                         SPI_MEM_OP_ADDR(3, SPINOR_REG_IS_CFR1V, 1),
         |                                         ^~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:92:32: error: 'SPINOR_IS_OCT_DTR' undeclared (first use in this function)
      92 |                         *buf = SPINOR_IS_OCT_DTR;
         |                                ^~~~~~~~~~~~~~~~~
   drivers/mtd/spi-nor/issi.c:94:32: error: 'SPINOR_IS_EXSPI' undeclared (first use in this function); did you mean 'SPINOR_OP_EX4B'?
      94 |                         *buf = SPINOR_IS_EXSPI;
         |                                ^~~~~~~~~~~~~~~
         |                                SPINOR_OP_EX4B
   drivers/mtd/spi-nor/issi.c:99:57: error: 'SPINOR_REG_IS_CFR0V' undeclared (first use in this function)
      99 |                                                         SPINOR_REG_IS_CFR0V, 1),
         |                                                         ^~~~~~~~~~~~~~~~~~~
   include/linux/spi/spi-mem.h:220:25: note: in definition of macro 'SPI_MEM_OP'
     220 |                 .addr = __addr,                                 \
         |                         ^~~~~~
   drivers/mtd/spi-nor/issi.c:98:41: note: in expansion of macro 'SPI_MEM_OP_ADDR'
      98 |                                         SPI_MEM_OP_ADDR(enable ? 3 : 4,
         |                                         ^~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:110:35: error: 'SNOR_F_HAS_STACKED' undeclared (first use in this function); did you mean 'SNOR_F_HAS_LOCK'?
     110 |                 if ((nor->flags & SNOR_F_HAS_STACKED) && nor->spimem->spi->cs_index_mask == 1)
         |                                   ^~~~~~~~~~~~~~~~~~
         |                                   SNOR_F_HAS_LOCK
   drivers/mtd/spi-nor/issi.c: In function 'is25wx256_default_init':
>> drivers/mtd/spi-nor/issi.c:151:58: error: implicit declaration of function 'spi_nor_get_params' [-Wimplicit-function-declaration]
     151 |                 struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
         |                                                          ^~~~~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:151:58: error: initialization of 'struct spi_nor_flash_parameter *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
>> drivers/mtd/spi-nor/issi.c:155:23: error: 'struct spi_nor_flash_parameter' has no member named 'phy_enable'
     155 |                 params->phy_enable = spi_nor_issi_phy_enable;
         |                       ^~
   drivers/mtd/spi-nor/issi.c: In function 'is25wx256_post_sfdp_fixup':
   drivers/mtd/spi-nor/issi.c:160:58: error: initialization of 'struct spi_nor_flash_parameter *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
     160 |                 struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
         |                                                          ^~~~~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:165:72: error: 'SPINOR_OP_IS_DTR_RD' undeclared (first use in this function); did you mean 'SPINOR_OP_ESECR'?
     165 |                                                                 0, 20, SPINOR_OP_IS_DTR_RD,
         |                                                                        ^~~~~~~~~~~~~~~~~~~
         |                                                                        SPINOR_OP_ESECR
   drivers/mtd/spi-nor/issi.c: At top level:
>> drivers/mtd/spi-nor/issi.c:314:30: error: 'USE_FSR' undeclared here (not in a function)
     314 |                 .mfr_flags = USE_FSR,
         |                              ^~~~~~~


vim +22 drivers/mtd/spi-nor/issi.c

    10	
    11	
    12	static int spi_nor_issi_phy_enable(struct spi_nor *nor)
    13	{
    14			struct spi_mem_op op;
    15			u8 *buf = nor->bouncebuf;
    16			int ret;
    17	
    18			ret = spi_nor_write_enable(nor);
    19			if (ret)
    20				goto ret;
    21	
  > 22			buf[0] = SPINOR_IS_EXSPI;
    23	
    24			op = (struct spi_mem_op)
  > 25					SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
  > 26					SPI_MEM_OP_ADDR(4, SPINOR_REG_IS_CFR0V, 1),
    27					SPI_MEM_OP_NO_DUMMY,
    28					SPI_MEM_OP_DATA_OUT(1, buf, 1));
    29	
    30			spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_1_1_1);
    31	
    32			ret = spi_mem_exec_op(nor->spimem, &op);
    33			if (ret)
    34				goto ret;
    35	
  > 36			nor->spimem->spi->controller->flags |= SPI_CONTROLLER_SDR_PHY;
    37			/* Read flash ID to make sure the switch was successful. */
    38			op = (struct spi_mem_op)
    39					SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1),
    40							SPI_MEM_OP_NO_ADDR,
    41							SPI_MEM_OP_DUMMY(0, 1),
    42							SPI_MEM_OP_DATA_IN(nor->info->id->len, buf, 1));
    43	
    44			spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_1_1_1);
    45	
    46			ret = spi_mem_exec_op(nor->spimem, &op);
    47			if (ret)
    48				goto ret;
    49	
    50			if (memcmp(buf, nor->info->id->bytes, nor->info->id->len))
    51				goto ret;
    52	
    53			return 0;
    54	ret:
    55			nor->spimem->spi->controller->flags &= ~SPI_CONTROLLER_SDR_PHY;
    56			return 0;
    57	}
    58	
    59	static int spi_nor_issi_octal_dtr_enable(struct spi_nor *nor, bool enable)
    60	{
    61			struct spi_mem_op op;
    62			u8 *buf = nor->bouncebuf;
    63			int ret;
    64	
    65			if (enable) {
    66				/* Use 20 dummy cycles for memory array reads. */
    67				ret = spi_nor_write_enable(nor);
    68				if (ret)
    69					return ret;
    70	
    71				*buf = 20;
    72				op = (struct spi_mem_op)
    73					SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
  > 74						SPI_MEM_OP_ADDR(3, SPINOR_REG_IS_CFR1V, 1),
    75						SPI_MEM_OP_NO_DUMMY,
    76						SPI_MEM_OP_DATA_OUT(1, buf, 1));
    77	
    78				ret = spi_mem_exec_op(nor->spimem, &op);
    79				if (ret)
    80					return ret;
    81	
    82				ret = spi_nor_wait_till_ready(nor);
    83				if (ret)
    84					return ret;
    85			}
    86	
    87			ret = spi_nor_write_enable(nor);
    88			if (ret)
    89				return ret;
    90	
    91			if (enable)
  > 92				*buf = SPINOR_IS_OCT_DTR;
    93			else
    94				*buf = SPINOR_IS_EXSPI;
    95	
    96			op = (struct spi_mem_op)
    97				SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
    98						SPI_MEM_OP_ADDR(enable ? 3 : 4,
    99								SPINOR_REG_IS_CFR0V, 1),
   100						SPI_MEM_OP_NO_DUMMY,
   101						SPI_MEM_OP_DATA_OUT(1, buf, 1));
   102	
   103			if (!enable)
   104				spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_8_8_8_DTR);
   105	
   106			ret = spi_mem_exec_op(nor->spimem, &op);
   107			if (ret)
   108				return ret;
   109	
 > 110			if ((nor->flags & SNOR_F_HAS_STACKED) && nor->spimem->spi->cs_index_mask == 1)
   111				return 0;
   112	
   113			/* Read flash ID to make sure the switch was successful. */
   114			op = (struct spi_mem_op)
   115				SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1),
   116						SPI_MEM_OP_NO_ADDR,
   117						SPI_MEM_OP_DUMMY(enable ? 8 : 0, 1),
   118						SPI_MEM_OP_DATA_IN(round_up(nor->info->id->len, 2),
   119										buf, 1));
   120	
   121			if (enable)
   122				spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_8_8_8_DTR);
   123	
   124			ret = spi_mem_exec_op(nor->spimem, &op);
   125			if (ret)
   126				return ret;
   127	
   128			if (memcmp(buf, nor->info->id->bytes, nor->info->id->len))
   129				return -EINVAL;
   130	
   131			return 0;
   132	}
   133	
   134	static int is25wx256_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
   135	{
   136			int ret;
   137	
   138			ret = spi_nor_write_enable(nor);
   139			if (ret)
   140				return ret;
   141	
   142			ret = spi_nor_set_4byte_addr_mode(nor, enable);
   143			if (ret)
   144				return ret;
   145	
   146			return spi_nor_write_disable(nor);
   147	}
   148	
   149	static void is25wx256_default_init(struct spi_nor *nor)
   150	{
 > 151			struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
   152	
   153			params->set_octal_dtr = spi_nor_issi_octal_dtr_enable;
   154			params->set_4byte_addr_mode = is25wx256_set_4byte_addr_mode;
 > 155			params->phy_enable = spi_nor_issi_phy_enable;
   156	}
   157	
   158	static int is25wx256_post_sfdp_fixup(struct spi_nor *nor)
   159	{
   160			struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
   161	
   162			/* Set the Fast Read settings. */
   163			params->hwcaps.mask |= SNOR_HWCAPS_READ_8_8_8_DTR;
   164			spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_8_8_8_DTR],
 > 165									0, 20, SPINOR_OP_IS_DTR_RD,
   166									SNOR_PROTO_8_8_8_DTR);
   167	
   168			nor->cmd_ext_type = SPI_NOR_EXT_REPEAT;
   169			params->rdsr_dummy = 8;
   170			params->rdsr_addr_nbytes = 0;
   171	
   172			/*
   173			 * The BFPT quad enable field is set to a reserved value so the quad
   174			 * enable function is ignored by spi_nor_parse_bfpt(). Make sure we
   175			 * disable it.
   176			 */
   177			params->quad_enable = NULL;
   178	
   179			return 0;
   180	}
   181	

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


From lkp at intel.com  Tue Aug  4 01:56:18 2026
From: lkp at intel.com (kernel test robot)
Date: Tue, 4 Aug 2026 16:56:18 +0800
Subject: [PATCH] Add JEDEC ID table entries for additional ISSI SPI-NOR
   devices. Additionally added several structs to support ISSI octal flash
  functionality. (Octal SPI 2026 revised)
In-Reply-To: <LV8PR19MB85976C665A59A0DEBAF4F285B6EF2@LV8PR19MB8597.namprd19.prod.outlook.com>
References: <LV8PR19MB85976C665A59A0DEBAF4F285B6EF2@LV8PR19MB8597.namprd19.prod.outlook.com>
Message-ID: <202608041622.usbEnS4x-lkp@intel.com>

Hi Jeffrey,

kernel test robot noticed the following build warnings:

[auto build test WARNING on mtd/spi-nor/next]
[also build test WARNING on linus/master v7.2-rc6 next-20260803]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Jeffrey-Yu/Add-JEDEC-ID-table-entries-for-additional-ISSI-SPI-NOR-devices-Additionally-added-several-structs-to-support-ISSI-octal-/20260804-122036
base:   https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git spi-nor/next
patch link:    https://lore.kernel.org/r/LV8PR19MB85976C665A59A0DEBAF4F285B6EF2%40LV8PR19MB8597.namprd19.prod.outlook.com
patch subject: [PATCH] Add JEDEC ID table entries for additional ISSI SPI-NOR   devices. Additionally added several structs to support ISSI octal flash  functionality. (Octal SPI 2026 revised)
config: nios2-allmodconfig (https://download.01.org/0day-ci/archive/20260804/202608041622.usbEnS4x-lkp at intel.com/config)
compiler: nios2-linux-gcc (GCC) 11.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260804/202608041622.usbEnS4x-lkp at intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp at intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202608041622.usbEnS4x-lkp at intel.com/

All warnings (new ones prefixed by >>):

   drivers/mtd/spi-nor/issi.c: In function 'spi_nor_issi_phy_enable':
   drivers/mtd/spi-nor/issi.c:22:26: error: 'SPINOR_IS_EXSPI' undeclared (first use in this function); did you mean 'SPINOR_OP_EX4B'?
      22 |                 buf[0] = SPINOR_IS_EXSPI;
         |                          ^~~~~~~~~~~~~~~
         |                          SPINOR_OP_EX4B
   drivers/mtd/spi-nor/issi.c:22:26: note: each undeclared identifier is reported only once for each function it appears in
   In file included from include/linux/mtd/spi-nor.h:11,
                    from drivers/mtd/spi-nor/issi.c:7:
   drivers/mtd/spi-nor/issi.c:25:59: error: 'SPINOR_OP_IS_WR_ANY_REG' undeclared (first use in this function)
      25 |                                 SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
         |                                                           ^~~~~~~~~~~~~~~~~~~~~~~
   include/linux/spi/spi-mem.h:219:24: note: in definition of macro 'SPI_MEM_OP'
     219 |                 .cmd = __cmd,                                   \
         |                        ^~~~~
   drivers/mtd/spi-nor/issi.c:25:44: note: in expansion of macro 'SPI_MEM_OP_CMD'
      25 |                                 SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
         |                                            ^~~~~~~~~~~~~~
   drivers/mtd/spi-nor/issi.c:26:52: error: 'SPINOR_REG_IS_CFR0V' undeclared (first use in this function)
      26 |                                 SPI_MEM_OP_ADDR(4, SPINOR_REG_IS_CFR0V, 1),
         |                                                    ^~~~~~~~~~~~~~~~~~~
   include/linux/spi/spi-mem.h:220:25: note: in definition of macro 'SPI_MEM_OP'
     220 |                 .addr = __addr,                                 \
         |                         ^~~~~~
   drivers/mtd/spi-nor/issi.c:26:33: note: in expansion of macro 'SPI_MEM_OP_ADDR'
      26 |                                 SPI_MEM_OP_ADDR(4, SPINOR_REG_IS_CFR0V, 1),
         |                                 ^~~~~~~~~~~~~~~
   drivers/mtd/spi-nor/issi.c:36:56: error: 'SPI_CONTROLLER_SDR_PHY' undeclared (first use in this function); did you mean 'SPI_CONTROLLER_NO_RX'?
      36 |                 nor->spimem->spi->controller->flags |= SPI_CONTROLLER_SDR_PHY;
         |                                                        ^~~~~~~~~~~~~~~~~~~~~~
         |                                                        SPI_CONTROLLER_NO_RX
   In file included from include/linux/mtd/spi-nor.h:11,
                    from drivers/mtd/spi-nor/issi.c:7:
   drivers/mtd/spi-nor/issi.c: In function 'spi_nor_issi_octal_dtr_enable':
   drivers/mtd/spi-nor/issi.c:73:59: error: 'SPINOR_OP_IS_WR_ANY_REG' undeclared (first use in this function)
      73 |                                 SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
         |                                                           ^~~~~~~~~~~~~~~~~~~~~~~
   include/linux/spi/spi-mem.h:219:24: note: in definition of macro 'SPI_MEM_OP'
     219 |                 .cmd = __cmd,                                   \
         |                        ^~~~~
   drivers/mtd/spi-nor/issi.c:73:44: note: in expansion of macro 'SPI_MEM_OP_CMD'
      73 |                                 SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
         |                                            ^~~~~~~~~~~~~~
   drivers/mtd/spi-nor/issi.c:74:60: error: 'SPINOR_REG_IS_CFR1V' undeclared (first use in this function)
      74 |                                         SPI_MEM_OP_ADDR(3, SPINOR_REG_IS_CFR1V, 1),
         |                                                            ^~~~~~~~~~~~~~~~~~~
   include/linux/spi/spi-mem.h:220:25: note: in definition of macro 'SPI_MEM_OP'
     220 |                 .addr = __addr,                                 \
         |                         ^~~~~~
   drivers/mtd/spi-nor/issi.c:74:41: note: in expansion of macro 'SPI_MEM_OP_ADDR'
      74 |                                         SPI_MEM_OP_ADDR(3, SPINOR_REG_IS_CFR1V, 1),
         |                                         ^~~~~~~~~~~~~~~
   drivers/mtd/spi-nor/issi.c:92:32: error: 'SPINOR_IS_OCT_DTR' undeclared (first use in this function)
      92 |                         *buf = SPINOR_IS_OCT_DTR;
         |                                ^~~~~~~~~~~~~~~~~
   drivers/mtd/spi-nor/issi.c:94:32: error: 'SPINOR_IS_EXSPI' undeclared (first use in this function); did you mean 'SPINOR_OP_EX4B'?
      94 |                         *buf = SPINOR_IS_EXSPI;
         |                                ^~~~~~~~~~~~~~~
         |                                SPINOR_OP_EX4B
   In file included from include/linux/mtd/spi-nor.h:11,
                    from drivers/mtd/spi-nor/issi.c:7:
   drivers/mtd/spi-nor/issi.c:99:57: error: 'SPINOR_REG_IS_CFR0V' undeclared (first use in this function)
      99 |                                                         SPINOR_REG_IS_CFR0V, 1),
         |                                                         ^~~~~~~~~~~~~~~~~~~
   include/linux/spi/spi-mem.h:220:25: note: in definition of macro 'SPI_MEM_OP'
     220 |                 .addr = __addr,                                 \
         |                         ^~~~~~
   drivers/mtd/spi-nor/issi.c:98:41: note: in expansion of macro 'SPI_MEM_OP_ADDR'
      98 |                                         SPI_MEM_OP_ADDR(enable ? 3 : 4,
         |                                         ^~~~~~~~~~~~~~~
   drivers/mtd/spi-nor/issi.c:110:35: error: 'SNOR_F_HAS_STACKED' undeclared (first use in this function); did you mean 'SNOR_F_HAS_LOCK'?
     110 |                 if ((nor->flags & SNOR_F_HAS_STACKED) && nor->spimem->spi->cs_index_mask == 1)
         |                                   ^~~~~~~~~~~~~~~~~~
         |                                   SNOR_F_HAS_LOCK
   drivers/mtd/spi-nor/issi.c: In function 'is25wx256_default_init':
   drivers/mtd/spi-nor/issi.c:151:58: error: implicit declaration of function 'spi_nor_get_params' [-Werror=implicit-function-declaration]
     151 |                 struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
         |                                                          ^~~~~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:151:58: warning: initialization of 'struct spi_nor_flash_parameter *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
   drivers/mtd/spi-nor/issi.c:155:23: error: 'struct spi_nor_flash_parameter' has no member named 'phy_enable'
     155 |                 params->phy_enable = spi_nor_issi_phy_enable;
         |                       ^~
   drivers/mtd/spi-nor/issi.c: In function 'is25wx256_post_sfdp_fixup':
   drivers/mtd/spi-nor/issi.c:160:58: warning: initialization of 'struct spi_nor_flash_parameter *' from 'int' makes pointer from integer without a cast [-Wint-conversion]
     160 |                 struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
         |                                                          ^~~~~~~~~~~~~~~~~~
   drivers/mtd/spi-nor/issi.c:165:72: error: 'SPINOR_OP_IS_DTR_RD' undeclared (first use in this function); did you mean 'SPINOR_OP_RDSR2'?
     165 |                                                                 0, 20, SPINOR_OP_IS_DTR_RD,
         |                                                                        ^~~~~~~~~~~~~~~~~~~
         |                                                                        SPINOR_OP_RDSR2
   drivers/mtd/spi-nor/issi.c: At top level:
   drivers/mtd/spi-nor/issi.c:314:30: error: 'USE_FSR' undeclared here (not in a function)
     314 |                 .mfr_flags = USE_FSR,
         |                              ^~~~~~~
   cc1: some warnings being treated as errors


vim +151 drivers/mtd/spi-nor/issi.c

   148	
   149	static void is25wx256_default_init(struct spi_nor *nor)
   150	{
 > 151			struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
   152	
   153			params->set_octal_dtr = spi_nor_issi_octal_dtr_enable;
   154			params->set_4byte_addr_mode = is25wx256_set_4byte_addr_mode;
   155			params->phy_enable = spi_nor_issi_phy_enable;
   156	}
   157	

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


From miquel.raynal at bootlin.com  Tue Aug  4 03:58:59 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:58:59 +0200
Subject: [PATCH v3 01/27] mtd: spi-nor: sfdp: Fix spacing in function call
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-1-542ce107bc92@bootlin.com>

Remove the double space.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/sfdp.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index 4600983cb579..6a571d8d822c 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -453,7 +453,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 		    bfpt_header->length * sizeof(u32));
 	addr = SFDP_PARAM_HEADER_PTP(bfpt_header);
 	memset(&bfpt, 0, sizeof(bfpt));
-	err = spi_nor_read_sfdp_dma_unsafe(nor,  addr, len, &bfpt);
+	err = spi_nor_read_sfdp_dma_unsafe(nor, addr, len, &bfpt);
 	if (err < 0)
 		return err;
 

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:58:58 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:58:58 +0200
Subject: [PATCH v3 00/27] mtd: spi-nor: Clean Winbond W25QxxJV family
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>

Thanks to Winbond collaboration, I was able to test almost the entire JV
SPI NOR family. A few chips could not be sourced, I decided to apply the
very same logic than the other chips, all of them behave identically as
shown in all the logs that I am sharing in each and every commit.

This series is pretty straightforward, it is here to cleanup the JV
entries by:
- Aligning the comments and "fixing" the chip names in them
- Reordering the chips by density and family
- Testing and showing all the SFDP content and passing all the SPI NOR
  tests to ensure a good level of support.
- Adding all the locking information when it is missing.

Since v3, these Winbond changes are prefixed with core changes, to make
the whole support work with legacy chips and SFDP compliant chips at the
same time.

This work is necessary because I am about to propose the addition of the
W25QxxRV family, whose IDs actually collide with the JV chips. The chips
are similar, they have been "improved" (especially the silicon/engraving
smoothness) and we have a way to differentiate them based on their SFDP
content, but that will be for a following series.

Two Winbond-wide fixups are applied, non SFDP compatible chips do not
have CMP nor QUAD_PP support, so let's make sure the additions we do in
the table do not affect the old flags. Keeping the legacy flash handling
in a separate function means that it will be easier to handle the day we
want to drop support for them. So I picked that solution instead of
enabling conditionally the fields at runtime (which makes the ID table
real content harder to consolidate).

NB: There is one collision with a Spansion chip as well (S25FL064K). On
my side I commented out the Spansion entry to make sure the core was
taking the Winbond entry (by alphabetical order) but it is due to an old
joint agreement and is a legitimate ID duplication. We haven't found a
good way to differentiate them so far, so I am keeping this aside for
the moment and just assume we will have a solution later. In all cases,
the colliding Winbond ID exist and is already fulfilled, so let's make
sure the definition is accurate.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
Sahiko hallucinations:
- I disagree with Sashiko's feedback about the multi-die SWP issue: it
  is not fundamentally broken since there is only one set of status
  registers for the whole chip and all the bytes are addressed with
  those 4 bits. This is a false positive.
- SPI_NOR_BP3_SR_BIT6 is not used in Winbond driver: slop (it is
  probably a vendor mixup since Micron does use it, whereas Winbond uses
  SPI_NOR_TB_SR_BIT6).
- 254Mb devices do have 4 BP bits, I don't know why Sashiko compains
  about this. It is clearly stated in their datasheets.

Changes in v3:
- Added several core patches to change the deprecated SFDP parsing step
  a bit: do not limit SFDP parsing only to chips advertizing
  dual/quad/octal capabiltities.
- Make sure the nor->sfdp pointer is not left dangling.
- Ensure proper garbage collection in the SFDP parsing logic to avoid
  cluttering RAM with useless allocations.
- Fixed typos in commit logs.
- Fixed one (unrelated) extra typo in the sfdp.c file.
- Fixed an out-of-band access on the ID buffer raised by Sashiko.
- Link to v2: https://lore.kernel.org/r/20260731-winbond-v7-1-spi-nor-jv-cleanup-v2-0-2549ef1b1af8 at bootlin.com

Changes in v2:
- Dropped the patches removing the no_sfdp_flags (they do not hurt)
  since this would drop support for !SFDP chips (~until 2016).
- Stopped removing the .names. They are part of the ABI. I will
  try to drop them at runtime when adding the RV chips. At this stage,
  all the existing entries remain.
- Reordered the patches a bit.
- Added a catch all entry in the ID table as advised.
- Added two fixups for non SFDP chips, one removing the CMP bit and the
  other removing the QUAD_PP flag.
- Link to v1: https://lore.kernel.org/r/20260529-winbond-v7-1-spi-nor-jv-cleanup-v1-0-87e5d3122244 at bootlin.com

---
Miquel Raynal (27):
      mtd: spi-nor: sfdp: Fix spacing in function call
      mtd: spi-nor: sfdp: Garbage collection during sfdp parsing
      mtd: spi-nor: Unconditionally parse SFDP in the deprecated path
      mtd: spi-nor: Init flags before manufacturer late_init() hooks
      mtd: spi-nor: winbond: Move W25Q01NW to its right place
      mtd: spi-nor: winbond: Normalize names
      mtd: spi-nor: winbond: Properly document the chip names
      mtd: spi-nor: winbond: Make the RDCR fixup Winbond wide
      mtd: spi-nor: winbond: W25Q32JV-Q/N: Add quad page program capability
      mtd: spi-nor: winbond: W25Q64JV-Q/N: Add quad page program capability
      mtd: spi-nor: winbond: W25Q128JV-Q/N: Add quad page program capability
      mtd: spi-nor: winbond: W25Q32JV-M: Add quad page program capability
      mtd: spi-nor: winbond: W25Q64JV-M: Add quad page program capability
      mtd: spi-nor: winbond: W25Q128JV-M: Add quad page program capability
      mtd: spi-nor: winbond: W25QxxJV-Q/N/M: Add quad page program capability
      mtd: spi-nor: winbond: W25Q32JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q64JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q128JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q512JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q01JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q32JV-M: Fill locking information
      mtd: spi-nor: winbond: W25Q64JV-M: Fill locking information
      mtd: spi-nor: winbond: W25Q128JV-M: Fill locking information
      mtd: spi-nor: winbond: W25Q02JV-M: Fill locking information
      mtd: spi-nor: winbond: W25QxxJV-Q/N/M: Fill locking information
      mtd: spi-nor: winbond: W25Q512JV-M: New chip
      mtd: spi-nor: winbond: W25Q01JV-M: New chip

 drivers/mtd/spi-nor/core.c    |  12 ++--
 drivers/mtd/spi-nor/sfdp.c    |  30 +++++----
 drivers/mtd/spi-nor/winbond.c | 138 +++++++++++++++++++++++++++++-------------
 3 files changed, 119 insertions(+), 61 deletions(-)
---
base-commit: 61e14aacac415b8c7ccbc8a5434b1e316af21aea
change-id: 20260529-winbond-v7-1-spi-nor-jv-cleanup-817889edd1f7

Best regards,
-- 
Miquel Raynal <miquel.raynal at bootlin.com>



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:00 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:00 +0200
Subject: [PATCH v3 02/27] mtd: spi-nor: sfdp: Garbage collection during
 sfdp parsing
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-2-542ce107bc92@bootlin.com>

SFDP works as follows:
1- The header is read then parsed
2- If correct, the sfdp structure and the sfdp content table are
   allocated then filled
3- The mandatory BFPT table is parsed
4- The optional tables are then parsed as well

Any failure in steps 1-3 leads to an error and an early return. The
failure is not always fatal though, since the deprecated will fallback
to the non SFDP definition (in the ID table, for legacy chips). In this
case, the devm_ memory allocations for the SFDP table structure and
table will remain in memory for the lifetime of the device but will
never ever be accessed anymore (since they are incorrect). In this case,
we shall free the memory manually since the device is not going out of
scope anytime soon.

The assignation of nor->sfdp is also moved just after BFPT table
parsing, to avoid any dangling pointer upon partial failure. This is
particularly important since the presence of valid SFDP support is
checked using the following condition: `if (!nor->sfdp)`.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---

This change is particularly useful for the next patches, I don't think
it is relevant to backport it.
---
 drivers/mtd/spi-nor/sfdp.c | 28 +++++++++++++++++-----------
 1 file changed, 17 insertions(+), 11 deletions(-)

diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index 6a571d8d822c..fd9f93da014d 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -1490,7 +1490,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 					psize, param_headers);
 		if (err < 0) {
 			dev_dbg(dev, "failed to read SFDP parameter headers\n");
-			goto exit;
+			goto free_param_headers;
 		}
 	}
 
@@ -1518,7 +1518,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 	sfdp = devm_kzalloc(dev, sizeof(*sfdp), GFP_KERNEL);
 	if (!sfdp) {
 		err = -ENOMEM;
-		goto exit;
+		goto free_param_headers;
 	}
 
 	/*
@@ -1532,20 +1532,15 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 				    sizeof(*sfdp->dwords), GFP_KERNEL);
 	if (!sfdp->dwords) {
 		err = -ENOMEM;
-		devm_kfree(dev, sfdp);
-		goto exit;
+		goto free_sfdp;
 	}
 
 	err = spi_nor_read_sfdp(nor, 0, sfdp_size, sfdp->dwords);
 	if (err < 0) {
 		dev_dbg(dev, "failed to read SFDP data\n");
-		devm_kfree(dev, sfdp->dwords);
-		devm_kfree(dev, sfdp);
-		goto exit;
+		goto free_dwords;
 	}
 
-	nor->sfdp = sfdp;
-
 	/*
 	 * Check other parameter headers to get the latest revision of
 	 * the basic flash parameter table.
@@ -1563,7 +1558,9 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 
 	err = spi_nor_parse_bfpt(nor, bfpt_header);
 	if (err)
-		goto exit;
+		goto free_dwords;
+
+	nor->sfdp = sfdp;
 
 	/* Parse optional parameter tables. */
 	for (i = 0; i < header.nph; i++) {
@@ -1608,7 +1605,16 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 	}
 
 	err = spi_nor_post_sfdp_fixups(nor);
-exit:
+
+	kfree(param_headers);
+
+	return 0;
+
+free_dwords:
+	devm_kfree(dev, sfdp->dwords);
+free_sfdp:
+	devm_kfree(dev, sfdp);
+free_param_headers:
 	kfree(param_headers);
 	return err;
 }

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:01 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:01 +0200
Subject: [PATCH v3 03/27] mtd: spi-nor: Unconditionally parse SFDP in the
 deprecated path
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-3-542ce107bc92@bootlin.com>

As of today, the deprecated initialization path only attempts to parse
the SFDP tables when the flash entry advertised a dual, quad or octal
read capability in ->no_sfdp_flags. This was initially a surprise to me
since the documentation and commit logs I read while searching the
history do not clearly explain why. It seems that this is some kind of
(double) legacy behavior since a change made it clear in 2021 that in
order to skip trying to parse SFDP in the deprecated path, chips should
be marked with SPI_NOR_SKIP_SFDP (in which case the deprecated path
mentioned in the title will not be attempted).

As a result of this situation, Winbond W25Q32JV-Q/N and W25Q64JV-M
chips, which feature good SFDP support, are using only the stale flags
in the ID table because of ID reuse. I tried to remove the stale entries
but this change got refused because it would break support for legacy
chips without SFDP support (until ~2016).

Parsing SFDP unconditionally seems safe:
spi_nor_sfdp_init_params_deprecated() already restores the legacy
parameters whenever parsing fails, so non-SFDP chips simply fall back to
their previous behavior. As stated above, anybody bisecting a SPI NOR
boot issue and getting to this commit should add (and contribute) the
SPI_NOR_SKIP_SFDP flag in their no_sfdp_flags field.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/core.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ccf4396cdcd0..0c8630a54985 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -3087,11 +3087,7 @@ static void spi_nor_init_params_deprecated(struct spi_nor *nor)
 
 	spi_nor_manufacturer_init_params(nor);
 
-	if (nor->info->no_sfdp_flags & (SPI_NOR_DUAL_READ |
-					SPI_NOR_QUAD_READ |
-					SPI_NOR_OCTAL_READ |
-					SPI_NOR_OCTAL_DTR_READ))
-		spi_nor_sfdp_init_params_deprecated(nor);
+	spi_nor_sfdp_init_params_deprecated(nor);
 }
 
 /**

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:02 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:02 +0200
Subject: [PATCH v3 04/27] mtd: spi-nor: Init flags before manufacturer
 late_init() hooks
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-4-542ce107bc92@bootlin.com>

Current init order is:
- The manufacturer-wide late_init() hook is called.
- The core initializes some chip flags
- The chip specific late_init() hook is called.

This does not seem very consistent since both late_init() may need to
touch flags, which is then not working as expected since the order is a
bit inconsistent. Moving spi_nor_init_flags() earlier, right before the
manufacturer late_init() call, ensures the hook sees the flags after
their initialization, and can therefore apply changes to them.

Since spi_nor_init_flags() only reads ->info->flags and DT properties,
it does not depend on anything a late_init() hook produces. On the other
hand, no manufacturer late_init() hook currently touches any output of
spi_nor_init_flags() either. Reordering seems safe?.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/core.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 0c8630a54985..53d9a484e434 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -3014,6 +3014,9 @@ static int spi_nor_late_init_params(struct spi_nor *nor)
 	struct spi_nor_flash_parameter *params = nor->params;
 	int ret;
 
+	/* Needed by some late_init hooks */
+	spi_nor_init_flags(nor);
+
 	if (nor->manufacturer && nor->manufacturer->fixups &&
 	    nor->manufacturer->fixups->late_init) {
 		ret = nor->manufacturer->fixups->late_init(nor);
@@ -3021,9 +3024,6 @@ static int spi_nor_late_init_params(struct spi_nor *nor)
 			return ret;
 	}
 
-	/* Needed by some flashes late_init hooks. */
-	spi_nor_init_flags(nor);
-
 	if (nor->info->fixups && nor->info->fixups->late_init) {
 		ret = nor->info->fixups->late_init(nor);
 		if (ret)

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:03 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:03 +0200
Subject: [PATCH v3 05/27] mtd: spi-nor: winbond: Move W25Q01NW to its right
 place
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-5-542ce107bc92@bootlin.com>

Chips are sorted by families, and inside the families, by density. The
second ID byte indicates the family, the third indicates the
density. Move W25Q01NW to the right place.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
Reviewed-by: Michael Walle <mwalle at kernel.org>
---
 drivers/mtd/spi-nor/winbond.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 8ebdbcec0b3f..662a022d36e7 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -300,6 +300,12 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x60, 0x20),
 		.name = "w25q512nwq",
 		.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 | SPI_NOR_HAS_CMP,
+		.fixups = &winbond_rdcr_fixup,
 	}, {
 		.id = SNOR_ID(0xef, 0x70, 0x15),
 		.name = "w25q16jv-im/jm",
@@ -365,12 +371,6 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x80, 0x20),
 		.name = "w25q512nwm",
 		.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 | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_rdcr_fixup,
 	}, {
 		/* W25Q01NWxxIM */
 		.id = SNOR_ID(0xef, 0x80, 0x21),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:04 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:04 +0200
Subject: [PATCH v3 06/27] mtd: spi-nor: winbond: Normalize names
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-6-542ce107bc92@bootlin.com>

Winbond chips cannot just be named eg. "W25Q02JV". There are always more
letters after that name, some of them matter.

- W is the Winbond prefix
- 25Q is the type of device
  -> 25Q indicates a quad capable SPI NOR
  -> 25H indicates an automotive grade chip
- 02J is the density
  -> in this case 2Gib
- V is the voltage
  -> V means 3.3V
  -> W means 1.8V
- The next letters (either 2 or 3 letters) are completely irrelevant for
  the software and purely indicate the hardware package.
- The penultimate letter is the temperature grade
  -> I for Industrial grade
  -> J means "above" industrial
- Q, N or M indicate factory states (like QE=1) and also the DTR
  capability.
  -> Q and N typically have the same IDs
  -> M chips typically have a different ID

As a result, W25Q01NWxxIQ is irrelevant and would better be named
W25Q01NW-Q, W25Q02JV is irrelevant and would better be named W25Q02JV-M,
etc.

Make that list uniform before adding more IDs/names.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
Reviewed-by: Michael Walle <mwalle at kernel.org>
---
 drivers/mtd/spi-nor/winbond.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 662a022d36e7..03e59ff3228f 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -301,7 +301,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q512nwq",
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
 	}, {
-		/* W25Q01NWxxIQ */
+		/* W25Q01NW-Q */
 		.id = SNOR_ID(0xef, 0x60, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
@@ -334,7 +334,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x19),
 		.name = "w25q256jvm",
 	}, {
-		/* W25Q02JV */
+		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),
 		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
@@ -372,28 +372,28 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q512nwm",
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
 	}, {
-		/* W25Q01NWxxIM */
+		/* W25Q01NW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q02NWxxIM */
+		/* W25Q02NW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &winbond_rdcr_fixup,
 	}, {
-		/* W25H512NWxxAM */
+		/* W25H512NW-M */
 		.id = SNOR_ID(0xef, 0xa0, 0x20),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25H01NWxxAM */
+		/* W25H01NW-M */
 		.id = SNOR_ID(0xef, 0xa0, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25H02NWxxAM */
+		/* W25H02NW-M */
 		.id = SNOR_ID(0xef, 0xa0, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:05 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:05 +0200
Subject: [PATCH v3 07/27] mtd: spi-nor: winbond: Properly document the chip
 names
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-7-542ce107bc92@bootlin.com>

While the .name entry is a legacy field, is highly imprecise, and even
wrong sometimes due to ID reuse, it is part of the sysfs ABI, we cannot
remove it for existing chips. However, we can double the entry with the
now usual comment with the newly instated naming scheme to indicate what
chips are covered by each entry.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
I've been focusing on several chip families, but not all of them. I do
not have enough information about the oldest chips at the moment to
cleanup *all* IDs, but I am nevertheless trying to make a major cleaning
step here.
---
 drivers/mtd/spi-nor/winbond.c | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 03e59ff3228f..3e8e89058b8c 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -218,16 +218,19 @@ static const struct flash_info winbond_nor_parts[] = {
 		.size = SZ_1M,
 		.no_sfdp_flags = SECT_4K,
 	}, {
+		/* W25Q32JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x16),
 		.name = "w25q32",
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K,
 	}, {
+		/* W25Q64JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x17),
 		.name = "w25q64",
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q128JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x18),
 		/* Flavors w/ and w/o SFDP. */
 		.name = "w25q128",
@@ -236,18 +239,20 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.fixups = &w25q128_fixups,
 	}, {
+		/* W25Q256JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x19),
 		.name = "w25q256",
 		.size = SZ_32M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.fixups = &w25q256_fixups,
 	}, {
+		/* W25Q512JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x20),
 		.name = "w25q512jvq",
 		.size = SZ_64M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
-		/* W25Q01JV */
+		/* W25Q01JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x21),
 		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
@@ -285,52 +290,60 @@ static const struct flash_info winbond_nor_parts[] = {
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q128FW-G/Q, W25Q128JW-Q/N */
 		.id = SNOR_ID(0xef, 0x60, 0x18),
 		.name = "w25q128fw",
 		.size = SZ_16M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q256JW-Q/N */
 		.id = SNOR_ID(0xef, 0x60, 0x19),
 		.name = "w25q256jw",
 		.size = SZ_32M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q512NW-Q/N */
 		.id = SNOR_ID(0xef, 0x60, 0x20),
 		.name = "w25q512nwq",
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
 	}, {
-		/* W25Q01NW-Q */
+		/* W25Q01NW-Q/N */
 		.id = SNOR_ID(0xef, 0x60, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &winbond_rdcr_fixup,
 	}, {
+		/* W25Q16JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x15),
 		.name = "w25q16jv-im/jm",
 		.size = SZ_2M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q32JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x16),
 		.name = "w25q32jv",
 		.size = SZ_4M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q64JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x17),
 		.name = "w25q64jvm",
 		.size = SZ_8M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K,
 	}, {
+		/* W25Q128JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x18),
 		.name = "w25q128jv",
 		.size = SZ_16M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),
 		.name = "w25q256jvm",
 	}, {
@@ -343,6 +356,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.size = SZ_64M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q32JW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x16),
 		.name = "w25q32jwm",
 		.size = SZ_4M,
@@ -350,24 +364,28 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
 	}, {
+		/* W25Q64JW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x17),
 		.name = "w25q64jwm",
 		.size = SZ_8M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q128JW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x18),
 		.name = "w25q128jwm",
 		.size = SZ_16M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q256JW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x19),
 		.name = "w25q256jwm",
 		.size = SZ_32M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q512NW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x20),
 		.name = "w25q512nwm",
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:07 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:07 +0200
Subject: [PATCH v3 09/27] mtd: spi-nor: winbond: W25Q32JV-Q/N: Add quad
 page program capability
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-9-542ce107bc92@bootlin.com>

The benefit is not massive @25MHz, yet it is a supported feature
of the chip which is already handled by a flag, so let's enable it and
earn that little 1% write throughput.

Before:
$ flash_speed /dev/mtd0 -d -c10
eraseblock write speed is 560 KiB/s
page write speed is 540 KiB/s
2 page write speed is 550 KiB/s

After:
eraseblock write speed is 578 KiB/s
page write speed is 557 KiB/s
2 page write speed is 567 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook is added to make sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x03
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3b
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbb
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6b
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x02
 1S-1S-4S
  opcode	0x32

$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
$ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
83e7d587bce3b2031c82939839cc1fbcf62b8939b35882d176f207b0722d0dba  spi_read
83e7d587bce3b2031c82939839cc1fbcf62b8939b35882d176f207b0722d0dba  spi_test
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
83e7d587bce3b2031c82939839cc1fbcf62b8939b35882d176f207b0722d0dba  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 04713ab04d01..2e53f16a9f93 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -203,6 +203,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q32",
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K,
+		.flags = SPI_NOR_QUAD_PP,
 	}, {
 		/* W25Q64JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x17),
@@ -504,6 +505,22 @@ static int winbond_nor_late_init(struct spi_nor *nor)
 	if (nor->id[1] > 0x30)
 		nor->flags &= ~SNOR_F_NO_READ_CR;
 
+	/*
+	 * Winbond has reused many IDs, up to four times at this
+	 * stage. In general, most of the chips with SFDP support are
+	 * correctly described by the ID table, but the non-SFDP chips,
+	 * however, are known to not feature as many capabilities. Make
+	 * sure we filter out those capabilities to keep backward
+	 * compatibility with these devices manufactured until ~2016.
+	 */
+	if (!nor->sfdp) {
+		struct spi_nor_flash_parameter *p = nor->params;
+
+		/* SPI_NOR_QUAD_PP was unsupported */
+		p->hwcaps.mask &= ~SNOR_HWCAPS_PP_1_1_4;
+		spi_nor_set_pp_settings(&p->page_programs[SNOR_CMD_PP_1_1_4], 0, 0);
+	}
+
 	return 0;
 }
 

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:06 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:06 +0200
Subject: [PATCH v3 08/27] mtd: spi-nor: winbond: Make the RDCR fixup
 Winbond wide
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-8-542ce107bc92@bootlin.com>

The top level paragraph of the QER field in the JESD216B mentions:

"In this standard, [...]  Status register 2 refers to the byte read
using instruction 35h. Status register 2 is the second byte transferred
in a Write Status (01h) command. [...]"

Value 100b, named in Linux BFPT_DWORD15_QER_SR2_BIT1_NO_RD, does not
mention anything about reads and only brings details about writes.

This has been interpreted in the spi-nor core by the absence of read
capability, but there is no explicit reason for that, except that there
were probably some very old chips which didn't support command 35h.

All quad capable Winbond chips carry a CMP SWP bit in SR2. SR2 is
readable with command 35h. In practice, all Winbond families but the
W25X family have support for this feature, so re-enable it Winbond-wide
in a late vendor fixup, except for the {EF, 30, xx} family.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 44 ++++++++++++++++++++-----------------------
 1 file changed, 20 insertions(+), 24 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 3e8e89058b8c..04713ab04d01 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -73,26 +73,6 @@ static const struct spi_nor_fixups w25q256_fixups = {
 	.post_bfpt = w25q256_post_bfpt_fixups,
 };
 
-static int
-winbond_rdcr_post_bfpt_fixup(struct spi_nor *nor,
-			     const struct sfdp_parameter_header *bfpt_header,
-			     const struct sfdp_bfpt *bfpt)
-{
-	/*
-	 * W25H02NW, unlike its W25H512NW nor W25H01NW cousins, improperly sets
-	 * the QE BFPT configuration bits, indicating a non readable CR. This is
-	 * both incorrect and impractical, as the chip features a CMP bit for its
-	 * locking scheme that lays in the Control Register, and needs to be read.
-	 */
-	nor->flags &= ~SNOR_F_NO_READ_CR;
-
-	return 0;
-}
-
-static const struct spi_nor_fixups winbond_rdcr_fixup = {
-	.post_bfpt = winbond_rdcr_post_bfpt_fixup,
-};
-
 /**
  * winbond_nor_select_die() - Set active die.
  * @nor:	pointer to 'struct spi_nor'.
@@ -313,7 +293,6 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x60, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_rdcr_fixup,
 	}, {
 		/* W25Q16JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x15),
@@ -399,7 +378,6 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x80, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_rdcr_fixup,
 	}, {
 		/* W25H512NW-M */
 		.id = SNOR_ID(0xef, 0xa0, 0x20),
@@ -415,8 +393,13 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0xa0, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_rdcr_fixup,
-	},
+	}, {
+		/*
+		 * Catch all entry to make sure all chips solely relying
+		 * on SFDP still go through the manufacturer hooks.
+		 */
+		.id = SNOR_ID(0xef),
+	}
 };
 
 /**
@@ -508,6 +491,19 @@ static int winbond_nor_late_init(struct spi_nor *nor)
 	 */
 	params->set_4byte_addr_mode = winbond_nor_set_4byte_addr_mode;
 
+	/*
+	 * All W25Q/W25H chips do set the BFPT_DWORD15_QER_SR2_BIT1_NO_RD bit in
+	 * their SFDP tables. The historical spi-nor assumption in this case has
+	 * been to declare CR reads as unsupported, whereas the Jedec
+	 * specification doesn't clearly state that. In practice, all these
+	 * chips do support reading back the CR, which is needed for SWP support,
+	 * so make sure that capability remains enabled (needed for SWP).
+	 * In practice, only exclude the old W25X family (JEDEC ID: EF 30 xx)
+	 * which actually does not support this feature.
+	 */
+	if (nor->id[1] > 0x30)
+		nor->flags &= ~SNOR_F_NO_READ_CR;
+
 	return 0;
 }
 

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:10 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:10 +0200
Subject: [PATCH v3 12/27] mtd: spi-nor: winbond: W25Q32JV-M: Add quad page
 program capability
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-12-542ce107bc92@bootlin.com>

This chip does not advertise it correctly but it supports 1-1-4 page
programs.

Before:

$ flash_speed /dev/mtd0 -d -c10
eraseblock write speed is 2612 KiB/s
page write speed is 2509 KiB/s
2 page write speed is 2560 KiB/s

After:

$ flash_speed /dev/mtd0 -c10 -d
eraseblock write speed is 3033 KiB/s
page write speed is 2895 KiB/s
2 page write speed is 2976 KiB/s

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index f7cdbf1a56a5..456050fdd251 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -307,8 +307,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x16),
 		.name = "w25q32jv",
 		.size = SZ_4M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 	}, {
 		/* W25Q64JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x17),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:11 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:11 +0200
Subject: [PATCH v3 13/27] mtd: spi-nor: winbond: W25Q64JV-M: Add quad page
 program capability
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-13-542ce107bc92@bootlin.com>

The benefit is massive @25MHz, but the chip does not implement the 4BAIT
table to advertize this capability, so enable it manually:.

Before:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 558 KiB/s
page write speed is 553 KiB/s
2 page write speed is 555 KiB/s

After:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 3047 KiB/s
page write speed is 2909 KiB/s
2 page write speed is 2976 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 17 00 00 00
size		8.00 MiB
write size	1
page size	256
address nbytes	3
flags		HAS_SR_TB | HAS_LOCK | HAS_16BIT_SR | SOFT_RESET | NO_WP

opcodes
 read		0xeb
  dummy cycles	6
 erase		0xd8
 program	0x32
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 20 (4.00 KiB) [1]
 52 (32.0 KiB) [2]
 d8 (64.0 KiB) [3]
 c7 (8.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-007fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
$ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x03
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3b
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbb
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6b
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x02
 1S-1S-4S
  opcode	0x32

$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
$ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
d79c3f7b40646d437eff15309c5250e8ee203f805b9d13d8ad8a7019f0dd69f8  spi_read
d79c3f7b40646d437eff15309c5250e8ee203f805b9d13d8ad8a7019f0dd69f8  spi_test
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
d79c3f7b40646d437eff15309c5250e8ee203f805b9d13d8ad8a7019f0dd69f8  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 456050fdd251..41c9b620f26b 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -314,8 +314,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x17),
 		.name = "w25q64jvm",
 		.size = SZ_8M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 	}, {
 		/* W25Q128JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x18),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:12 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:12 +0200
Subject: [PATCH v3 14/27] mtd: spi-nor: winbond: W25Q128JV-M: Add quad page
 program capability
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-14-542ce107bc92@bootlin.com>

This chip does not advertise it correctly but it supports 1-1-4 page
programs, add the flag manually:

Before:

$ flash_speed /dev/mtd0 -d -c10
eraseblock write speed is 553 KiB/s
page write speed is 548 KiB/s
2 page write speed is 550 KiB/s

After:

$ flash_speed /dev/mtd0 -c10 -d
eraseblock write speed is 1628 KiB/s
page write speed is 1600 KiB/s
2 page write speed is 1612 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 41c9b620f26b..2cdb4acd9ecf 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -321,8 +321,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x18),
 		.name = "w25q128jv",
 		.size = SZ_16M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 	}, {
 		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:13 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:13 +0200
Subject: [PATCH v3 15/27] mtd: spi-nor: winbond: W25QxxJV-Q/N/M: Add quad
 page program capability
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-15-542ce107bc92@bootlin.com>

I do not have any W25Q256JV-Q/N/M nor any W25Q16JV-M chips in hand, but
all the chips with a density <= 256Mib are older revisions of the JV
chips which do not yet implement the 4BAIT extension table, as such,
they cannot advertise their quad page program capability. We can
reasonably expect that these chips follow the exact same rules, so add
the missing flag.

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the bit remains disabled
for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 2cdb4acd9ecf..b250a43ad4e1 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -226,6 +226,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q256",
 		.size = SZ_32M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP,
 		.fixups = &w25q256_fixups,
 	}, {
 		/* W25Q512JV-Q/N */
@@ -300,8 +301,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x15),
 		.name = "w25q16jv-im/jm",
 		.size = SZ_2M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 	}, {
 		/* W25Q32JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x16),
@@ -327,6 +328,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),
 		.name = "w25q256jvm",
+		.flags = SPI_NOR_QUAD_PP,
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:14 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:14 +0200
Subject: [PATCH v3 16/27] mtd: spi-nor: winbond: W25Q32JV-Q/N: Fill locking
 information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-16-542ce107bc92@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
let's make sure the bit remains disabled for them, for backward
compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ alias show_sectors='grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0
/params'
$ flash_lock -u /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: unlocked
Return code: 0
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_read
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_test
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff | unlocked | 64
$ flash_lock -l /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: locked
Return code: 1
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_read
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_test
$ dd if=/dev/urandom of=./spi_test2 bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
$ sha256sum spi*
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_read
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_read2
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_test
5b69b16c88c3ae48213c359d2f9fd7f0583e513dabb224b3404bc93a5e173253  spi_test2
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff |   locked | 64
$ flash_lock -u /dev/mtd0
$ size=$(cat /sys/class/mtd/mtd0/size)
$ nblocks=$(grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params | sed -e 's/.*
unlocked | //')
$
$ bs=$(($size / $nblocks))
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2 * $bs))) 2 # last two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff | unlocked | 62
 003e0000-003fffff |   locked | 2
$ flash_lock -u /dev/mtd0 $(($size - (2 * $bs))) 1 # last one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff | unlocked | 63
 003f0000-003fffff |   locked | 1
$ flash_lock -u /dev/mtd0
$ all_but_one=$((($size / $bs) - 1))
$ flash_lock -l /dev/mtd0 $bs $all_but_one # all but the first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-003fffff |   locked | 63
$ flash_lock -u /dev/mtd0 $bs 1 # all but the two first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-003fffff |   locked | 62
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $all_but_one # same from the other side
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff |   locked | 63
 003f0000-003fffff | unlocked | 1
$ flash_lock -u /dev/mtd0 $(($size - (2 * $bs))) 1 # all but two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff |   locked | 62
 003e0000-003fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index b250a43ad4e1..64bf98ffc25b 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -203,7 +203,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q32",
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K,
-		.flags = SPI_NOR_QUAD_PP,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q64JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x17),
@@ -522,6 +522,9 @@ static int winbond_nor_late_init(struct spi_nor *nor)
 		/* SPI_NOR_QUAD_PP was unsupported */
 		p->hwcaps.mask &= ~SNOR_HWCAPS_PP_1_1_4;
 		spi_nor_set_pp_settings(&p->page_programs[SNOR_CMD_PP_1_1_4], 0, 0);
+
+		/* SPI_NOR_HAS_CMP was unsupported */
+		nor->flags &= ~SNOR_F_HAS_SR2_CMP_BIT6;
 	}
 
 	return 0;

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:15 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:15 +0200
Subject: [PATCH v3 17/27] mtd: spi-nor: winbond: W25Q64JV-Q/N: Fill locking
 information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-17-542ce107bc92@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_read
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_read
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_read
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_read2
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_test
b12f8258bf0374eb5c55e659fc430df7a6016d3c0dca2dc04ef1fe82f61d7f67  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=8388608
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=131072
+ bps=2
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 8126464 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff | unlocked | 62
 007c0000-007fffff |   locked | 2
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff | unlocked | 63
 007e0000-007fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 -8388608 256
flash_lock: invalid option -- '8'
Utility to lock, unlock, or check the lock status of the flash.
Default action: lock

Usage: flash_lock [options] [--] <mtd device> [offset [block count]]

Options:
 -h         --help              Display this help and exit
 -V         --version           Display version information and exit
 -i         --islocked          Check if flash region is locked
 -l         --lock              Lock a region of flash
 -u         --unlock            Unlock a region of flash

 <mtd device>  MTD device node or 'mtd:<name>'

If offset is not specified, it defaults to 0.
If block count is not specified, it defaults to all blocks.
A block count of -1 means all blocks.
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 2
 00040000-007fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 1
 00020000-007fffff | unlocked | 63
+ all_but_one=126
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 131072 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 1
 00020000-007fffff |   locked | 63
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 2
 00040000-007fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff |   locked | 63
 007e0000-007fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff |   locked | 62
 007c0000-007fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 64bf98ffc25b..8fab520627bf 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -210,7 +210,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q64",
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q128JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x18),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:08 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:08 +0200
Subject: [PATCH v3 10/27] mtd: spi-nor: winbond: W25Q64JV-Q/N: Add quad
 page program capability
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-10-542ce107bc92@bootlin.com>

The benefit is not massive @25MHz, yet it is a supported feature
of the chip which is already handled by a flag, so let's enable it and
earn that little 1% write throughput.

Before:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 539 KiB/s
page write speed is 535 KiB/s
2 page write speed is 536 KiB/s

After:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 556 KiB/s
page write speed is 549 KiB/s
2 page write speed is 552 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 17 00 00 00
size		8.00 MiB
write size	1
page size	256
address nbytes	3
flags		HAS_16BIT_SR | SOFT_RESET | NO_WP

opcodes
 read		0xeb
  dummy cycles	6
 erase		0xd8
 program	0x32
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 20 (4.00 KiB) [1]
 52 (32.0 KiB) [2]
 d8 (64.0 KiB) [3]
 c7 (8.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-007fffff |     [   3] | no
$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug erase /dev/mtd0 0 2097152
mtd_debug read /dev/mtd0 0 2097152 spi_readErased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
$ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
36667197a637b731cc2068022138e6963a9499ce9b0ddda1a1585f7f5ebe66ff  spi_read
36667197a637b731cc2068022138e6963a9499ce9b0ddda1a1585f7f5ebe66ff  spi_test
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
36667197a637b731cc2068022138e6963a9499ce9b0ddda1a1585f7f5ebe66ff  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 2e53f16a9f93..15df3cc26fbc 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -210,6 +210,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q64",
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP,
 	}, {
 		/* W25Q128JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x18),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:09 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:09 +0200
Subject: [PATCH v3 11/27] mtd: spi-nor: winbond: W25Q128JV-Q/N: Add quad
 page program capability
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-11-542ce107bc92@bootlin.com>

The benefit is not massive @25MHz, yet this feature is not advertized
because these chips do not implement the 4BAIT table. Flag the quad page
program capability manually and earn a few % of write throughput.

Before:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 536 KiB/s
page write speed is 532 KiB/s
2 page write speed is 532 KiB/s

After:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 552 KiB/s
page write speed is 547 KiB/s
2 page write speed is 549 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
c2712b5617a588f8bba4b8fc7af62f6ca5e2356577681b23544d1a93540ecd1d  spi_read
c2712b5617a588f8bba4b8fc7af62f6ca5e2356577681b23544d1a93540ecd1d  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
c2712b5617a588f8bba4b8fc7af62f6ca5e2356577681b23544d1a93540ecd1d  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 15df3cc26fbc..f7cdbf1a56a5 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -217,7 +217,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		/* Flavors w/ and w/o SFDP. */
 		.name = "w25q128",
 		.size = SZ_16M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.fixups = &w25q128_fixups,
 	}, {

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:16 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:16 +0200
Subject: [PATCH v3 18/27] mtd: spi-nor: winbond: W25Q128JV-Q/N: Fill
 locking information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-18-542ce107bc92@bootlin.com>

The SFDP table does not advertize the CMP locking capabilities of the
device, flag it.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $ss $all_but_one # all but the first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 1
 00040000-00ffffff |   locked | 63
$ flash_lock -u /dev/mtd0 $ss $(($ss / $bs)) # all but the two first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0007ffff | unlocked | 2
 00080000-00ffffff |   locked | 62
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $all_but_one # same from the other side
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00fbffff |   locked | 63
 00fc0000-00ffffff | unlocked | 1
$ flash_lock -u /dev/mtd0 $(($size - (2 * $ss))) $(($ss / $bs)) # all but two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00f7ffff |   locked | 62
 00f80000-00ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 8fab520627bf..5a80de959768 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -217,8 +217,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		/* Flavors w/ and w/o SFDP. */
 		.name = "w25q128",
 		.size = SZ_16M,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 		.fixups = &w25q128_fixups,
 	}, {
 		/* W25Q256JV-Q/N */

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:17 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:17 +0200
Subject: [PATCH v3 19/27] mtd: spi-nor: winbond: W25Q512JV-Q/N: Fill
 locking information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-19-542ce107bc92@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ sha256sum spi_test
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_test
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_read
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_test
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
$ flash_lock -l /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: locked
Return code: 1
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_read
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_test
$ dd if=/dev/urandom of=./spi_test2 bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
$ sha256sum spi*
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_read
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_read2
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_test
0236e30ff05676c4bcc80b55aa0ebf4747630e0ae3360e025c9e0c2509402a6b  spi_test2
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff |   locked | 1024
$ flash_lock -u /dev/mtd0
$
$ size=$(cat /sys/class/mtd/mtd0/size)
$ nblocks=$(grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params | sed -e 's/.*
unlocked | //')
$
$ bs=$(($size / $nblocks))
$
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2 * $bs))) 2 # last two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff | unlocked | 1022
 03fe0000-03ffffff |   locked | 2
$ flash_lock -u /dev/mtd0 $(($size - (2 * $bs))) 1 # last one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff | unlocked | 1023
 03ff0000-03ffffff |   locked | 1
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2**7 * $bs))) $((2**7))
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-037fffff | unlocked | 896
 03800000-03ffffff |   locked | 128
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 2 # first two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-03ffffff | unlocked | 1022
$ flash_lock -u /dev/mtd0 $bs 1 # first one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-03ffffff | unlocked | 1023
$ all_but_one=$((($size / $bs) - 1))
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $bs $all_but_one # all but the first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-03ffffff |   locked | 1023
$ flash_lock -u /dev/mtd0 $bs 1 # all but the two first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-03ffffff |   locked | 1022
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $all_but_one # same from the other side
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff |   locked | 1023
 03ff0000-03ffffff | unlocked | 1
$ flash_lock -u /dev/mtd0 $(($size - (2 * $bs))) 1 # all but two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff |   locked | 1022
 03fe0000-03ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 5a80de959768..1ac0177aeb44 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -234,6 +234,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q512jvq",
 		.size = SZ_64M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q01JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x21),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:18 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:18 +0200
Subject: [PATCH v3 20/27] mtd: spi-nor: winbond: W25Q01JV-Q/N: Fill locking
 information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-20-542ce107bc92@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
$ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef4021
$ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
53464450060101ff00060110800000ff84000102d00000ff03000102f000
00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff3f44eb086b083b42bbfeffffffffff
0000ffff40eb0c200f5210d800003602a60082ea14e2e96376337a757a75
f7a2d55c19f74dffe970f9a5ffffffffffffffffffffffffffffffffff0a
f0ff21ffdcff
$ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
c733cf105c5f05ed23ad3a32c2a72c1a09d4b895c628db52e645a8a457dfc7d6  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
$ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/
jedec_id      manufacturer  sfdp
$ sha256sum /sys/kernel/debug/spi-nor/spi0.0/capabilities
6204c70963ab07f9865d051b888679ce34ad3a9f167fbbd4a2a6e0c3d0ce4cdf  /sys/kernel/debug/spi-nor/spi0.0/capabilities
$ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34

$ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 21 00 00 00
size		128 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_16BIT_SR | NO_READ_CR | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (128 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-07ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048

$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ flash_lock -u /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: unlocked
Return code: 0
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_read
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_test
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048
$ flash_lock -l /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: locked
Return code: 1
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_read
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_test
$ dd if=/dev/urandom of=./spi_test2 bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
$ sha256sum spi*
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_read
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_read2
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_test
367809dbdff878d023d3a1a8526a6382759a3ec78aa6289d18577d15f013d44f  spi_test2
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff |   locked | 2048
$ flash_lock -u /dev/mtd0
$ nsectors=$(grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params | sed -e 's/.
*unlocked | //')
$ ss=$(($size / $nsectors))
$ bps=$(($ss / $bs))
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2 * $ss))) $((2 * $bps)) # last two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff | unlocked | 2046
 07fe0000-07ffffff |   locked | 2
$ flash_lock -u /dev/mtd0 $(($size - (2 * $ss))) $((1 * $bps)) # last one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff | unlocked | 2047
 07ff0000-07ffffff |   locked | 1
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2**7 * $ss))) $((2**7 * $bps))
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-077fffff | unlocked | 1920
 07800000-07ffffff |   locked | 128
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $((2 * $bps)) # first two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-07ffffff | unlocked | 2046
$ flash_lock -u /dev/mtd0 $ss $((1 * $bps)) # first one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-07ffffff | unlocked | 2047
$ flash_lock -l /dev/mtd0 $ss $all_but_one # all but the first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-07ffffff |   locked | 2047
$ flash_lock -u /dev/mtd0 $ss $(($ss / $bs)) # all but the two first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-07ffffff |   locked | 2046
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $all_but_one # same from the other side
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff |   locked | 2047
 07ff0000-07ffffff | unlocked | 1
$ flash_lock -u /dev/mtd0 $(($size - (2 * $ss))) $(($ss / $bs)) # all but two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff |   locked | 2046
 07fe0000-07ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 1ac0177aeb44..59d5be14a73c 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -239,6 +239,8 @@ static const struct flash_info winbond_nor_parts[] = {
 	}, {
 		/* W25Q01JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x21),
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
 		.id = SNOR_ID(0xef, 0x50, 0x12),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:19 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:19 +0200
Subject: [PATCH v3 21/27] mtd: spi-nor: winbond: W25Q32JV-M: Fill locking
 information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-21-542ce107bc92@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag the missing CMP capability.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   34.621251] random: crng init done
2+0 records in
2+0 records out
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_read
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_read
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_read
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_read2
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_test
bcf94096d25ce535e4b920f876d53420e6d8d917d0d14dd766b5f8c3d6f40475  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=4194304
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 4063232 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff | unlocked | 62
 003e0000-003fffff |   locked | 2
+ flash_lock -u /dev/mtd0 4063232 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff | unlocked | 63
 003f0000-003fffff |   locked | 1
/* Skipping BP3 test */
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-003fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-003fffff | unlocked | 63
+ all_but_one=63
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 63
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-003fffff |   locked | 63
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-003fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 63
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff |   locked | 63
 003f0000-003fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 4063232 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff |   locked | 62
 003e0000-003fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 59d5be14a73c..3f9f2257ac91 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -313,7 +313,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q32jv",
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q64JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x17),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:20 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:20 +0200
Subject: [PATCH v3 22/27] mtd: spi-nor: winbond: W25Q64JV-M: Fill locking
 information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-22-542ce107bc92@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_read
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_read
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_read
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_read2
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_test
6ba881af6f5901f7ca55322d45a7cee2ec69900cfdfc348141c3c5b1e51f676b  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=8388608
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=131072
+ bps=2
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 8126464 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff | unlocked | 62
 007c0000-007fffff |   locked | 2
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff | unlocked | 63
 007e0000-007fffff |   locked | 1
/* Skipping BP3 test */
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 2
 00040000-007fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 1
 00020000-007fffff | unlocked | 63
+ all_but_one=126
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 131072 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 1
 00020000-007fffff |   locked | 63
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 2
 00040000-007fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff |   locked | 63
 007e0000-007fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff |   locked | 62
 007c0000-007fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 3f9f2257ac91..41b8733c4c85 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -320,7 +320,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q64jvm",
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q128JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x18),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:21 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:21 +0200
Subject: [PATCH v3 23/27] mtd: spi-nor: winbond: W25Q128JV-M: Fill locking
 information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-23-542ce107bc92@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x1000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00ffffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x1000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read2
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
c9d2a381c868b43a44563b0e36e2556b3095acf94f6dad8f259c85eab536363e  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00ffffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=16777216
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=262144
+ bps=4
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 16252928 8
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00f7ffff | unlocked | 62
 00f80000-00ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 16252928 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00fbffff | unlocked | 63
 00fc0000-00ffffff |   locked | 1
/* Skipping BP3 */
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 8
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0007ffff |   locked | 2
 00080000-00ffffff | unlocked | 62
+ flash_lock -u /dev/mtd0 262144 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 1
 00040000-00ffffff | unlocked | 63
+ all_but_one=252
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 262144 252
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 1
 00040000-00ffffff |   locked | 63
+ flash_lock -u /dev/mtd0 262144 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0007ffff | unlocked | 2
 00080000-00ffffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 252
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00fbffff |   locked | 63
 00fc0000-00ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 16252928 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00f7ffff |   locked | 62
 00f80000-00ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 41b8733c4c85..4f26191e1d6d 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -327,7 +327,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q128jv",
 		.size = SZ_16M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:22 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:22 +0200
Subject: [PATCH v3 24/27] mtd: spi-nor: winbond: W25Q02JV-M: Fill locking
 information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-24-542ce107bc92@bootlin.com>

The SFDP table is pretty complete except for the locking capabilities
which must be filled manually.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x10000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_read
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff | unlocked | 4096
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x10000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_read
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_read
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_read2
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_test
e61267bbd0864dfe7e67a8bfc0875a23308d98c7297fcf5a813830532f48c06f  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff |   locked | 4096
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=268435456
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=4096
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 268304384 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffdffff | unlocked | 4094
 0ffe0000-0fffffff |   locked | 2
+ flash_lockk uu /dev/mtd0 268304384 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffeffff | unlocked | 4095
 0fff0000-0fffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 260046848 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0f7fffff | unlocked | 3968
 0f800000-0fffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-0fffffff | unlocked | 4094
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-0fffffff | unlocked | 4095
+ all_but_one=4095
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 4095
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-0fffffff |   locked | 4095
+ flassh__llock u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-0fffffff |   locked | 4094
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4095
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffeffff |   locked | 4095
 0fff0000-0fffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 268304384 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffdffff |   locked | 4094
 0ffe0000-0fffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 4f26191e1d6d..5ab7f4860295 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -336,6 +336,8 @@ static const struct flash_info winbond_nor_parts[] = {
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
 		.id = SNOR_ID(0xef, 0x71, 0x19),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:23 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:23 +0200
Subject: [PATCH v3 25/27] mtd: spi-nor: winbond: W25QxxJV-Q/N/M: Fill
 locking information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-25-542ce107bc92@bootlin.com>

I do not have any W25Q256JV-Q/N/M nor any W25Q16JV-M chips in hand, but
all the other chips from this family have the same locking pattern, so
we can reasonably expect that these chips follow the exact same rules.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 5ab7f4860295..6b21fed74f4e 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -226,7 +226,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q256",
 		.size = SZ_32M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
+			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &w25q256_fixups,
 	}, {
 		/* W25Q512JV-Q/N */
@@ -306,7 +307,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q16jv-im/jm",
 		.size = SZ_2M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q32JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x16),
@@ -332,7 +333,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),
 		.name = "w25q256jvm",
-		.flags = SPI_NOR_QUAD_PP,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
+			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:25 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:25 +0200
Subject: [PATCH v3 27/27] mtd: spi-nor: winbond: W25Q01JV-M: New chip
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-27-542ce107bc92@bootlin.com>

Add support for W25Q01JV-M. It is a 3.3V chip with usual dual/quad
capabilities.

The SFDP table is pretty complete except for the locking capabilities
which must be filled manually.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7021
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
53464450060101ff00060110800000ff84000102d00000ff03000102f000
00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff3f44eb086b083b42bbfeffffffffff
0000ffff40eb0c200f5210d800003602a60082ea14e2e96376337a757a75
f7a2d55c19f74dffe970f9a5ffffffffffffffffffffffffffffffffff0a
f0ff21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
c733cf105c5f05ed23ad3a32c2a72c1a09d4b895c628db52e645a8a457dfc7d6  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 21 00 00 00
size		128 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_16BIT_SR | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (128 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-07ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 134217728 (128M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read2
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
43870da5d2b9fcb8fe0a6a96350b87da1e9423fe9f9e2f039bf363e68916ae0d  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff |   locked | 2048
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=134217728
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=2048
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 134086656 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff | unlocked | 2046
 07fe0000-07ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 134086656 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff | unlocked | 2047
 07ff0000-07ffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 125829120 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-077fffff | unlocked | 1920
 07800000-07ffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-07ffffff | unlocked | 2046
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-07ffffff | unlocked | 2047
+ all_but_one=2047
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 2047
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-07ffffff |   locked | 2047
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-07ffffff |   locked | 2046
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2047
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff |   locked | 2047
 07ff0000-07ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 134086656 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff |   locked | 2046
 07fe0000-07ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 980c437f1951..593429bd6cdf 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -340,6 +340,12 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x20),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+	}, {
+		/* W25Q01JV-M */
+		.id = SNOR_ID(0xef, 0x70, 0x21),
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 03:59:24 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 12:59:24 +0200
Subject: [PATCH v3 26/27] mtd: spi-nor: winbond: W25Q512JV-M: New chip
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-26-542ce107bc92@bootlin.com>

Add support for W25Q512JV-M. It is a 3.3V chip with usual dual/quad
capabilities.

The SFDP table is pretty complete except for the locking capabilities
which must be filled manually.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/partname
cat: can't open '/sys/bus/spi/devices/spi0.0/spi-nor/partname': No such file or directory
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7020
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
53464450060101ff00060110800000ff84000102d00000ff03000102f000
00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff1f44eb086b083b42bbfeffffffffff
0000ffff40eb0c200f5210d800003602a60082ea14e2e96376337a757a75
f7a2d55c19f74dffe970f9a5ffffffffffffffffffffffffffffffffff0a
f0ff21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
f41674a6c3742a42d31d3007d4d4b725c166ddb4e4566d1c347d29ed29855ef0  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 20 00 00 00
size		64.0 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_16BIT_SR | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (64.0 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-03ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   53.198369] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 67108864 (64M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read2
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
44db7586d798437868d29ea31ac1fa1e64d1b81337721d0d811776dad47110f3  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff |   locked | 1024
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=67108864
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=1024
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 66977792 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff | unlocked | 1022
 03fe0000-03ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff | unlocked | 1023
 03ff0000-03ffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 58720256 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-037fffff | unlocked | 896
 03800000-03ffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-03ffffff | unlocked | 1022
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-03ffffff | unlocked | 1023
+ all_but_one=1023
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-03ffffff |   locked | 1023
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-03ffffff |   locked | 1022
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff |   locked | 1023
 03ff0000-03ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff |   locked | 1022
 03fe0000-03ffffff | unlocked | 2
+ flash_speed /dev/mtd0 -dc10
not NAND flash, assume page size is 512 bytes.
scanning for bad eraseblocks
scanned 10 eraseblocks, 0 are bad
testing eraseblock write speed
eraseblock write speed is 437 KiB/s
testing eraseblock read speed
eraseblock read speed is 1560 KiB/s
testing page write speed
page write speed is 434 KiB/s
testing page read speed
page read speed is 1457 KiB/s
testing 2 page write speed
2 page write speed is 435 KiB/s
testing 2 page read speed
2 page read speed is 1498 KiB/s
Testing erase speed
erase speed is 257 KiB/s
Testing 2x multi-block erase speed
2x multi-block erase speed is 257 KiB/s
Testing 4x multi-block erase speed
4x multi-block erase speed is 257 KiB/s
Testing 8x multi-block erase speed
8x multi-block erase speed is 254 KiB/s
Testing 16x multi-block erase speed
16x multi-block erase speed is 255 KiB/s
Testing 32x multi-block erase speed
32x multi-block erase speed is 255 KiB/s
Testing 64x multi-block erase speed
64x multi-block erase speed is 253 KiB/s
finished
---
 drivers/mtd/spi-nor/winbond.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 6b21fed74f4e..980c437f1951 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -335,6 +335,11 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q256jvm",
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
 			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+	}, {
+		/* W25Q512JV-M */
+		.id = SNOR_ID(0xef, 0x70, 0x20),
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),

-- 
2.54.0



From lkp at intel.com  Tue Aug  4 05:06:57 2026
From: lkp at intel.com (kernel test robot)
Date: Tue, 4 Aug 2026 20:06:57 +0800
Subject: [PATCH] Add JEDEC ID table entries for additional ISSI SPI-NOR
   devices. Additionally added several structs to support ISSI octal flash
  functionality. (Octal SPI 2026 revised)
In-Reply-To: <LV8PR19MB85976C665A59A0DEBAF4F285B6EF2@LV8PR19MB8597.namprd19.prod.outlook.com>
References: <LV8PR19MB85976C665A59A0DEBAF4F285B6EF2@LV8PR19MB8597.namprd19.prod.outlook.com>
Message-ID: <202608041903.oQQ0fMx3-lkp@intel.com>

Hi Jeffrey,

kernel test robot noticed the following build errors:

[auto build test ERROR on mtd/spi-nor/next]
[also build test ERROR on linus/master v7.2-rc6 next-20260803]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Jeffrey-Yu/Add-JEDEC-ID-table-entries-for-additional-ISSI-SPI-NOR-devices-Additionally-added-several-structs-to-support-ISSI-octal-/20260804-122036
base:   https://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git spi-nor/next
patch link:    https://lore.kernel.org/r/LV8PR19MB85976C665A59A0DEBAF4F285B6EF2%40LV8PR19MB8597.namprd19.prod.outlook.com
patch subject: [PATCH] Add JEDEC ID table entries for additional ISSI SPI-NOR   devices. Additionally added several structs to support ISSI octal flash  functionality. (Octal SPI 2026 revised)
config: hexagon-randconfig-002-20260804 (https://download.01.org/0day-ci/archive/20260804/202608041903.oQQ0fMx3-lkp at intel.com/config)
compiler: clang version 24.0.0git (https://github.com/llvm/llvm-project bacfe2950f8218268fcc0a8765644ea0c15f0360)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260804/202608041903.oQQ0fMx3-lkp at intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp at intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202608041903.oQQ0fMx3-lkp at intel.com/

All errors (new ones prefixed by >>):

>> drivers/mtd/spi-nor/issi.c:22:12: error: use of undeclared identifier 'SPINOR_IS_EXSPI'
      22 |                 buf[0] = SPINOR_IS_EXSPI;
         |                          ^~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:25:31: error: use of undeclared identifier 'SPINOR_OP_IS_WR_ANY_REG'
      25 |                                 SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
         |                                                           ^~~~~~~~~~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:26:24: error: use of undeclared identifier 'SPINOR_REG_IS_CFR0V'
      26 |                                 SPI_MEM_OP_ADDR(4, SPINOR_REG_IS_CFR0V, 1),
         |                                                    ^~~~~~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:36:42: error: use of undeclared identifier 'SPI_CONTROLLER_SDR_PHY'
      36 |                 nor->spimem->spi->controller->flags |= SPI_CONTROLLER_SDR_PHY;
         |                                                        ^~~~~~~~~~~~~~~~~~~~~~
   drivers/mtd/spi-nor/issi.c:55:43: error: use of undeclared identifier 'SPI_CONTROLLER_SDR_PHY'
      55 |                 nor->spimem->spi->controller->flags &= ~SPI_CONTROLLER_SDR_PHY;
         |                                                         ^~~~~~~~~~~~~~~~~~~~~~
   drivers/mtd/spi-nor/issi.c:73:31: error: use of undeclared identifier 'SPINOR_OP_IS_WR_ANY_REG'
      73 |                                 SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
         |                                                           ^~~~~~~~~~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:74:25: error: use of undeclared identifier 'SPINOR_REG_IS_CFR1V'
      74 |                                         SPI_MEM_OP_ADDR(3, SPINOR_REG_IS_CFR1V, 1),
         |                                                            ^~~~~~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:92:11: error: use of undeclared identifier 'SPINOR_IS_OCT_DTR'
      92 |                         *buf = SPINOR_IS_OCT_DTR;
         |                                ^~~~~~~~~~~~~~~~~
   drivers/mtd/spi-nor/issi.c:94:11: error: use of undeclared identifier 'SPINOR_IS_EXSPI'
      94 |                         *buf = SPINOR_IS_EXSPI;
         |                                ^~~~~~~~~~~~~~~
   drivers/mtd/spi-nor/issi.c:97:30: error: use of undeclared identifier 'SPINOR_OP_IS_WR_ANY_REG'
      97 |                         SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
         |                                                   ^~~~~~~~~~~~~~~~~~~~~~~
   drivers/mtd/spi-nor/issi.c:99:8: error: use of undeclared identifier 'SPINOR_REG_IS_CFR0V'
      99 |                                                         SPINOR_REG_IS_CFR0V, 1),
         |                                                         ^~~~~~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:110:21: error: use of undeclared identifier 'SNOR_F_HAS_STACKED'; did you mean 'SNOR_F_HAS_LOCK'?
     110 |                 if ((nor->flags & SNOR_F_HAS_STACKED) && nor->spimem->spi->cs_index_mask == 1)
         |                                   ^~~~~~~~~~~~~~~~~~
         |                                   SNOR_F_HAS_LOCK
   drivers/mtd/spi-nor/core.h:131:2: note: 'SNOR_F_HAS_LOCK' declared here
     131 |         SNOR_F_HAS_LOCK         = BIT(5),
         |         ^
>> drivers/mtd/spi-nor/issi.c:151:44: error: call to undeclared function 'spi_nor_get_params'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     151 |                 struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
         |                                                          ^
>> drivers/mtd/spi-nor/issi.c:151:35: error: incompatible integer to pointer conversion initializing 'struct spi_nor_flash_parameter *' with an expression of type 'int' [-Wint-conversion]
     151 |                 struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
         |                                                 ^        ~~~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:155:11: error: no member named 'phy_enable' in 'struct spi_nor_flash_parameter'
     155 |                 params->phy_enable = spi_nor_issi_phy_enable;
         |                 ~~~~~~  ^
   drivers/mtd/spi-nor/issi.c:160:44: error: call to undeclared function 'spi_nor_get_params'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
     160 |                 struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
         |                                                          ^
   drivers/mtd/spi-nor/issi.c:160:35: error: incompatible integer to pointer conversion initializing 'struct spi_nor_flash_parameter *' with an expression of type 'int' [-Wint-conversion]
     160 |                 struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
         |                                                 ^        ~~~~~~~~~~~~~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:165:16: error: use of undeclared identifier 'SPINOR_OP_IS_DTR_RD'
     165 |                                                                 0, 20, SPINOR_OP_IS_DTR_RD,
         |                                                                        ^~~~~~~~~~~~~~~~~~~
>> drivers/mtd/spi-nor/issi.c:314:16: error: use of undeclared identifier 'USE_FSR'
     314 |                 .mfr_flags = USE_FSR,
         |                              ^~~~~~~
   fatal error: too many errors emitted, stopping now [-ferror-limit=]
   20 errors generated.


vim +/SPINOR_IS_EXSPI +22 drivers/mtd/spi-nor/issi.c

    10	
    11	
    12	static int spi_nor_issi_phy_enable(struct spi_nor *nor)
    13	{
    14			struct spi_mem_op op;
    15			u8 *buf = nor->bouncebuf;
    16			int ret;
    17	
    18			ret = spi_nor_write_enable(nor);
    19			if (ret)
    20				goto ret;
    21	
  > 22			buf[0] = SPINOR_IS_EXSPI;
    23	
    24			op = (struct spi_mem_op)
  > 25					SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
  > 26					SPI_MEM_OP_ADDR(4, SPINOR_REG_IS_CFR0V, 1),
    27					SPI_MEM_OP_NO_DUMMY,
    28					SPI_MEM_OP_DATA_OUT(1, buf, 1));
    29	
    30			spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_1_1_1);
    31	
    32			ret = spi_mem_exec_op(nor->spimem, &op);
    33			if (ret)
    34				goto ret;
    35	
  > 36			nor->spimem->spi->controller->flags |= SPI_CONTROLLER_SDR_PHY;
    37			/* Read flash ID to make sure the switch was successful. */
    38			op = (struct spi_mem_op)
    39					SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1),
    40							SPI_MEM_OP_NO_ADDR,
    41							SPI_MEM_OP_DUMMY(0, 1),
    42							SPI_MEM_OP_DATA_IN(nor->info->id->len, buf, 1));
    43	
    44			spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_1_1_1);
    45	
    46			ret = spi_mem_exec_op(nor->spimem, &op);
    47			if (ret)
    48				goto ret;
    49	
    50			if (memcmp(buf, nor->info->id->bytes, nor->info->id->len))
    51				goto ret;
    52	
    53			return 0;
    54	ret:
    55			nor->spimem->spi->controller->flags &= ~SPI_CONTROLLER_SDR_PHY;
    56			return 0;
    57	}
    58	
    59	static int spi_nor_issi_octal_dtr_enable(struct spi_nor *nor, bool enable)
    60	{
    61			struct spi_mem_op op;
    62			u8 *buf = nor->bouncebuf;
    63			int ret;
    64	
    65			if (enable) {
    66				/* Use 20 dummy cycles for memory array reads. */
    67				ret = spi_nor_write_enable(nor);
    68				if (ret)
    69					return ret;
    70	
    71				*buf = 20;
    72				op = (struct spi_mem_op)
    73					SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
  > 74						SPI_MEM_OP_ADDR(3, SPINOR_REG_IS_CFR1V, 1),
    75						SPI_MEM_OP_NO_DUMMY,
    76						SPI_MEM_OP_DATA_OUT(1, buf, 1));
    77	
    78				ret = spi_mem_exec_op(nor->spimem, &op);
    79				if (ret)
    80					return ret;
    81	
    82				ret = spi_nor_wait_till_ready(nor);
    83				if (ret)
    84					return ret;
    85			}
    86	
    87			ret = spi_nor_write_enable(nor);
    88			if (ret)
    89				return ret;
    90	
    91			if (enable)
  > 92				*buf = SPINOR_IS_OCT_DTR;
    93			else
    94				*buf = SPINOR_IS_EXSPI;
    95	
    96			op = (struct spi_mem_op)
    97				SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_IS_WR_ANY_REG, 1),
    98						SPI_MEM_OP_ADDR(enable ? 3 : 4,
    99								SPINOR_REG_IS_CFR0V, 1),
   100						SPI_MEM_OP_NO_DUMMY,
   101						SPI_MEM_OP_DATA_OUT(1, buf, 1));
   102	
   103			if (!enable)
   104				spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_8_8_8_DTR);
   105	
   106			ret = spi_mem_exec_op(nor->spimem, &op);
   107			if (ret)
   108				return ret;
   109	
 > 110			if ((nor->flags & SNOR_F_HAS_STACKED) && nor->spimem->spi->cs_index_mask == 1)
   111				return 0;
   112	
   113			/* Read flash ID to make sure the switch was successful. */
   114			op = (struct spi_mem_op)
   115				SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDID, 1),
   116						SPI_MEM_OP_NO_ADDR,
   117						SPI_MEM_OP_DUMMY(enable ? 8 : 0, 1),
   118						SPI_MEM_OP_DATA_IN(round_up(nor->info->id->len, 2),
   119										buf, 1));
   120	
   121			if (enable)
   122				spi_nor_spimem_setup_op(nor, &op, SNOR_PROTO_8_8_8_DTR);
   123	
   124			ret = spi_mem_exec_op(nor->spimem, &op);
   125			if (ret)
   126				return ret;
   127	
   128			if (memcmp(buf, nor->info->id->bytes, nor->info->id->len))
   129				return -EINVAL;
   130	
   131			return 0;
   132	}
   133	
   134	static int is25wx256_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
   135	{
   136			int ret;
   137	
   138			ret = spi_nor_write_enable(nor);
   139			if (ret)
   140				return ret;
   141	
   142			ret = spi_nor_set_4byte_addr_mode(nor, enable);
   143			if (ret)
   144				return ret;
   145	
   146			return spi_nor_write_disable(nor);
   147	}
   148	
   149	static void is25wx256_default_init(struct spi_nor *nor)
   150	{
 > 151			struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
   152	
   153			params->set_octal_dtr = spi_nor_issi_octal_dtr_enable;
   154			params->set_4byte_addr_mode = is25wx256_set_4byte_addr_mode;
 > 155			params->phy_enable = spi_nor_issi_phy_enable;
   156	}
   157	
   158	static int is25wx256_post_sfdp_fixup(struct spi_nor *nor)
   159	{
   160			struct spi_nor_flash_parameter *params = spi_nor_get_params(nor, 0);
   161	
   162			/* Set the Fast Read settings. */
   163			params->hwcaps.mask |= SNOR_HWCAPS_READ_8_8_8_DTR;
   164			spi_nor_set_read_settings(&params->reads[SNOR_CMD_READ_8_8_8_DTR],
 > 165									0, 20, SPINOR_OP_IS_DTR_RD,
   166									SNOR_PROTO_8_8_8_DTR);
   167	
   168			nor->cmd_ext_type = SPI_NOR_EXT_REPEAT;
   169			params->rdsr_dummy = 8;
   170			params->rdsr_addr_nbytes = 0;
   171	
   172			/*
   173			 * The BFPT quad enable field is set to a reserved value so the quad
   174			 * enable function is ignored by spi_nor_parse_bfpt(). Make sure we
   175			 * disable it.
   176			 */
   177			params->quad_enable = NULL;
   178	
   179			return 0;
   180	}
   181	

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


From miquel.raynal at bootlin.com  Tue Aug  4 06:53:28 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 15:53:28 +0200
Subject: [PATCH v3 00/27] mtd: spi-nor: Clean Winbond W25QxxJV family
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
	(Miquel Raynal's message of "Tue, 04 Aug 2026 12:58:58 +0200")
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92@bootlin.com>
Message-ID: <87tspaf0vb.fsf@bootlin.com>

Hi,

> Since v3, these Winbond changes are prefixed with core changes, to make
> the whole support work with legacy chips and SFDP compliant chips at the
> same time.

Sashiko complained about several aspects. There are 2 relevant comments
which I took into account, I am about to send a v4 with them, please
disregard v3.

Thanks,
Miqu?l


From miquel.raynal at bootlin.com  Tue Aug  4 07:04:23 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:23 +0200
Subject: [PATCH v4 01/28] mtd: spi-nor: Fix spacing in function call
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-1-ee3445066e4e@bootlin.com>

Remove the double space.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/sfdp.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index 4600983cb579..6a571d8d822c 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -453,7 +453,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 		    bfpt_header->length * sizeof(u32));
 	addr = SFDP_PARAM_HEADER_PTP(bfpt_header);
 	memset(&bfpt, 0, sizeof(bfpt));
-	err = spi_nor_read_sfdp_dma_unsafe(nor,  addr, len, &bfpt);
+	err = spi_nor_read_sfdp_dma_unsafe(nor, addr, len, &bfpt);
 	if (err < 0)
 		return err;
 

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:22 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:22 +0200
Subject: [PATCH v4 00/28] mtd: spi-nor: Clean Winbond W25QxxJV family
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>

Thanks to Winbond collaboration, I was able to test almost the entire JV
SPI NOR family. A few chips could not be sourced, I decided to apply the
very same logic than the other chips, all of them behave identically as
shown in all the logs that I am sharing in each and every commit.

This series is pretty straightforward, it is here to cleanup the JV
entries by:
- Aligning the comments and "fixing" the chip names in them
- Reordering the chips by density and family
- Testing and showing all the SFDP content and passing all the SPI NOR
  tests to ensure a good level of support.
- Adding all the locking information when it is missing.

Since v3, these Winbond changes are prefixed with core changes, to make
the whole support work with legacy chips and SFDP compliant chips at the
same time.

This work is necessary because I am about to propose the addition of the
W25QxxRV family, whose IDs actually collide with the JV chips. The chips
are similar, they have been "improved" (especially the silicon/engraving
smoothness) and we have a way to differentiate them based on their SFDP
content, but that will be for a following series.

Two Winbond-wide fixups are applied, non SFDP compatible chips do not
have CMP nor QUAD_PP support, so let's make sure the additions we do in
the table do not affect the old flags. Keeping the legacy flash handling
in a separate function means that it will be easier to handle the day we
want to drop support for them. So I picked that solution instead of
enabling conditionally the fields at runtime (which makes the ID table
real content harder to consolidate).

NB: There is one collision with a Spansion chip as well (S25FL064K). On
my side I commented out the Spansion entry to make sure the core was
taking the Winbond entry (by alphabetical order) but it is due to an old
joint agreement and is a legitimate ID duplication. We haven't found a
good way to differentiate them so far, so I am keeping this aside for
the moment and just assume we will have a solution later. In all cases,
the colliding Winbond ID exist and is already fulfilled, so let's make
sure the definition is accurate.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
Sahiko hallucinations:
- Unconditional SFDP probing is intentional, it is explained, but it
  will very likely not break backward compatibility for legacy non-SFDP
  chips. First, there is a flag to skip SFDP parsing if it exist and is
  wrong. If the SFDP command does not exist, this is handled early and I
  believe it is fine paying the cost of this extra check on legacy
  chips. Finally the deprecated path already parses SFDP, and actually
  claims it does. The only change here is the fact that it is now done
  even on chips not advertizing dual/quad/octal support, which is an
  justified condition. It would be much more dangerous to flag the
  dual/quad capabilities of these chips (which could have been done
  otherwise) given that we cannot test it.
- I disagree with Sashiko's feedback about the multi-die SWP issue: it
  is not fundamentally broken since there is only one set of Status
  Registers for the whole chip and all the bytes are addressed with
  those 4 bits. This is a false positive. So no, SR-based locking on the
  multi-die flash does not break partial locking nor incorrectly reports
  hardware lock state.
- SPI_NOR_BP3_SR_BIT6 is not used in Winbond driver: slop (it is
  probably a vendor mixup since Micron does use it, whereas Winbond uses
  SPI_NOR_TB_SR_BIT6).
- 254Mb devices do have 4 BP bits, I don't know why Sashiko compains
  about this. It is clearly stated in their datasheets.

Changes in v4:
- Fixed the handling of one error in the garbage collection patch (at
  the end of SFDP parsing).
- Fixed an issue in the commit log (s/CMP/QUAD_PP).
- Improved the SFDP parsing failure rollback mechanism.
- Link to v3: https://lore.kernel.org/r/20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92 at bootlin.com

Changes in v3:
- Added several core patches to change the deprecated SFDP parsing step
  a bit: do not limit SFDP parsing only to chips advertizing
  dual/quad/octal capabiltities.
- Make sure the nor->sfdp pointer is not left dangling.
- Ensure proper garbage collection in the SFDP parsing logic to avoid
  cluttering RAM with useless allocations.
- Fixed typos in commit logs.
- Fixed one (unrelated) extra typo in the sfdp.c file.
- Fixed an out-of-band access on the ID buffer raised by Sashiko.
- Link to v2: https://lore.kernel.org/r/20260731-winbond-v7-1-spi-nor-jv-cleanup-v2-0-2549ef1b1af8 at bootlin.com

Changes in v2:
- Dropped the patches removing the no_sfdp_flags (they do not hurt)
  since this would drop support for !SFDP chips (~until 2016).
- Stopped removing the .names. They are part of the ABI. I will
  try to drop them at runtime when adding the RV chips. At this stage,
  all the existing entries remain.
- Reordered the patches a bit.
- Added a catch all entry in the ID table as advised.
- Added two fixups for non SFDP chips, one removing the CMP bit and the
  other removing the QUAD_PP flag.
- Link to v1: https://lore.kernel.org/r/20260529-winbond-v7-1-spi-nor-jv-cleanup-v1-0-87e5d3122244 at bootlin.com

---
Miquel Raynal (28):
      mtd: spi-nor: Fix spacing in function call
      mtd: spi-nor: Complete error rollback on sfdp parsing failure
      mtd: spi-nor: Garbage collection during sfdp parsing
      mtd: spi-nor: Unconditionally parse SFDP in the deprecated path
      mtd: spi-nor: Init flags before manufacturer late_init() hooks
      mtd: spi-nor: winbond: Move W25Q01NW to its right place
      mtd: spi-nor: winbond: Normalize names
      mtd: spi-nor: winbond: Properly document the chip names
      mtd: spi-nor: winbond: Make the RDCR fixup Winbond wide
      mtd: spi-nor: winbond: W25Q32JV-Q/N: Add quad page program capability
      mtd: spi-nor: winbond: W25Q64JV-Q/N: Add quad page program capability
      mtd: spi-nor: winbond: W25Q128JV-Q/N: Add quad page program capability
      mtd: spi-nor: winbond: W25Q32JV-M: Add quad page program capability
      mtd: spi-nor: winbond: W25Q64JV-M: Add quad page program capability
      mtd: spi-nor: winbond: W25Q128JV-M: Add quad page program capability
      mtd: spi-nor: winbond: W25QxxJV-Q/N/M: Add quad page program capability
      mtd: spi-nor: winbond: W25Q32JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q64JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q128JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q512JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q01JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q32JV-M: Fill locking information
      mtd: spi-nor: winbond: W25Q64JV-M: Fill locking information
      mtd: spi-nor: winbond: W25Q128JV-M: Fill locking information
      mtd: spi-nor: winbond: W25Q02JV-M: Fill locking information
      mtd: spi-nor: winbond: W25QxxJV-Q/N/M: Fill locking information
      mtd: spi-nor: winbond: W25Q512JV-M: New chip
      mtd: spi-nor: winbond: W25Q01JV-M: New chip

 drivers/mtd/spi-nor/core.c    |  19 +++---
 drivers/mtd/spi-nor/sfdp.c    |  30 ++++++---
 drivers/mtd/spi-nor/winbond.c | 138 +++++++++++++++++++++++++++++-------------
 3 files changed, 127 insertions(+), 60 deletions(-)
---
base-commit: 61e14aacac415b8c7ccbc8a5434b1e316af21aea
change-id: 20260529-winbond-v7-1-spi-nor-jv-cleanup-817889edd1f7

Best regards,
-- 
Miquel Raynal <miquel.raynal at bootlin.com>



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:24 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:24 +0200
Subject: [PATCH v4 02/28] mtd: spi-nor: Complete error rollback on sfdp
 parsing failure
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-2-ee3445066e4e@bootlin.com>

Make sure to rollback all changes induced by SFDP parsing in case of
failure, which means also resetting nor->flags and nor->cmd_ext_type.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/core.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ccf4396cdcd0..c39c6833c9a4 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -3063,12 +3063,17 @@ static int spi_nor_late_init_params(struct spi_nor *nor)
 static void spi_nor_sfdp_init_params_deprecated(struct spi_nor *nor)
 {
 	struct spi_nor_flash_parameter sfdp_params;
+	enum spi_nor_cmd_ext cmd_ext_type;
+	u32 flags;
 
 	memcpy(&sfdp_params, nor->params, sizeof(sfdp_params));
+	cmd_ext_type = nor->cmd_ext_type;
+	flags = nor->flags;
 
 	if (spi_nor_parse_sfdp(nor)) {
 		memcpy(nor->params, &sfdp_params, sizeof(*nor->params));
-		nor->flags &= ~SNOR_F_4B_OPCODES;
+		nor->cmd_ext_type = cmd_ext_type;
+		nor->flags = flags;
 	}
 }
 

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:25 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:25 +0200
Subject: [PATCH v4 03/28] mtd: spi-nor: Garbage collection during sfdp
 parsing
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-3-ee3445066e4e@bootlin.com>

SFDP parsing works as follows:
1- The header is read then parsed
2- If correct, the sfdp structure and the sfdp content table are
   allocated then filled
3- The mandatory BFPT table is parsed
4- The optional tables are then parsed as well

Any failure in steps 1-3 leads to an error and an early return. The
failure is not always fatal though, since the deprecated will fallback
to the non SFDP definition (in the ID table, for legacy chips). In this
case, the devm_ memory allocations for the SFDP table structure and
table will remain in memory for the lifetime of the device but will
never ever be accessed anymore (since they are incorrect). In this case,
we shall free the memory manually since the device is not going out of
scope anytime soon.

The assignation of nor->sfdp is also cleared in case of failure, to
avoid carrying a dangling pointer upon partial failure. This is
particularly important since the presence of valid SFDP support is
checked using the following condition: `if (!nor->sfdp)`.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---

This change is particularly useful for the next patches which generalize
the fact that SFDP parsing will be attempted on legacy chips not
manually advertizing dual/quad/octal capabilities. I don't think it is
relevant to backport it.
---
 drivers/mtd/spi-nor/sfdp.c | 28 +++++++++++++++++++---------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index 6a571d8d822c..754c9379f89f 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -1490,7 +1490,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 					psize, param_headers);
 		if (err < 0) {
 			dev_dbg(dev, "failed to read SFDP parameter headers\n");
-			goto exit;
+			goto free_param_headers;
 		}
 	}
 
@@ -1518,7 +1518,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 	sfdp = devm_kzalloc(dev, sizeof(*sfdp), GFP_KERNEL);
 	if (!sfdp) {
 		err = -ENOMEM;
-		goto exit;
+		goto free_param_headers;
 	}
 
 	/*
@@ -1532,16 +1532,13 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 				    sizeof(*sfdp->dwords), GFP_KERNEL);
 	if (!sfdp->dwords) {
 		err = -ENOMEM;
-		devm_kfree(dev, sfdp);
-		goto exit;
+		goto free_sfdp;
 	}
 
 	err = spi_nor_read_sfdp(nor, 0, sfdp_size, sfdp->dwords);
 	if (err < 0) {
 		dev_dbg(dev, "failed to read SFDP data\n");
-		devm_kfree(dev, sfdp->dwords);
-		devm_kfree(dev, sfdp);
-		goto exit;
+		goto free_dwords;
 	}
 
 	nor->sfdp = sfdp;
@@ -1563,7 +1560,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 
 	err = spi_nor_parse_bfpt(nor, bfpt_header);
 	if (err)
-		goto exit;
+		goto clear_sfdp_ptr;
 
 	/* Parse optional parameter tables. */
 	for (i = 0; i < header.nph; i++) {
@@ -1608,7 +1605,20 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 	}
 
 	err = spi_nor_post_sfdp_fixups(nor);
-exit:
+	if (err)
+		goto clear_sfdp_ptr;
+
+	kfree(param_headers);
+
+	return 0;
+
+clear_sfdp_ptr:
+	sfdp = NULL;
+free_dwords:
+	devm_kfree(dev, sfdp->dwords);
+free_sfdp:
+	devm_kfree(dev, sfdp);
+free_param_headers:
 	kfree(param_headers);
 	return err;
 }

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:26 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:26 +0200
Subject: [PATCH v4 04/28] mtd: spi-nor: Unconditionally parse SFDP in the
 deprecated path
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-4-ee3445066e4e@bootlin.com>

As of today, the deprecated initialization path only attempts to parse
the SFDP tables when the flash entry advertised a dual, quad or octal
read capability in ->no_sfdp_flags. This was initially a surprise to me
since the documentation and commit logs I read while searching the
history do not clearly explain why. It seems that this is some kind of
(double) legacy behavior since a change made it clear in 2021 that in
order to skip trying to parse SFDP in the deprecated path, chips should
be marked with SPI_NOR_SKIP_SFDP (in which case the deprecated path
mentioned in the title will not be attempted).

As a result of this situation, Winbond W25Q32JV-Q/N and W25Q64JV-M
chips, which feature good SFDP support, are using only the stale flags
in the ID table because of ID reuse. I tried to remove the stale entries
but this change got refused because it would break support for legacy
chips without SFDP support (until ~2016).

Parsing SFDP unconditionally seems safe:
spi_nor_sfdp_init_params_deprecated() already restores the legacy
parameters whenever parsing fails, so non-SFDP chips simply fall back to
their previous behavior. As stated above, anybody bisecting a SPI NOR
boot issue and getting to this commit should add (and contribute) the
SPI_NOR_SKIP_SFDP flag in their no_sfdp_flags field.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/core.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index c39c6833c9a4..b4d09f14fff7 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -3092,11 +3092,7 @@ static void spi_nor_init_params_deprecated(struct spi_nor *nor)
 
 	spi_nor_manufacturer_init_params(nor);
 
-	if (nor->info->no_sfdp_flags & (SPI_NOR_DUAL_READ |
-					SPI_NOR_QUAD_READ |
-					SPI_NOR_OCTAL_READ |
-					SPI_NOR_OCTAL_DTR_READ))
-		spi_nor_sfdp_init_params_deprecated(nor);
+	spi_nor_sfdp_init_params_deprecated(nor);
 }
 
 /**

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:27 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:27 +0200
Subject: [PATCH v4 05/28] mtd: spi-nor: Init flags before manufacturer
 late_init() hooks
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-5-ee3445066e4e@bootlin.com>

Current init order is:
- The manufacturer-wide late_init() hook is called.
- The core initializes some chip flags
- The chip specific late_init() hook is called.

This does not seem very consistent since both late_init() may need to
touch flags, which is then not working as expected since the order is a
bit inconsistent. Moving spi_nor_init_flags() earlier, right before the
manufacturer late_init() call, ensures the hook sees the flags after
their initialization, and can therefore apply changes to them.

Since spi_nor_init_flags() only reads ->info->flags and DT properties,
it does not depend on anything a late_init() hook produces. On the other
hand, no manufacturer late_init() hook currently touches any output of
spi_nor_init_flags() either. Reordering seems safe?.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/core.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index b4d09f14fff7..fef7f426a3a2 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -3014,6 +3014,9 @@ static int spi_nor_late_init_params(struct spi_nor *nor)
 	struct spi_nor_flash_parameter *params = nor->params;
 	int ret;
 
+	/* Needed by some late_init hooks */
+	spi_nor_init_flags(nor);
+
 	if (nor->manufacturer && nor->manufacturer->fixups &&
 	    nor->manufacturer->fixups->late_init) {
 		ret = nor->manufacturer->fixups->late_init(nor);
@@ -3021,9 +3024,6 @@ static int spi_nor_late_init_params(struct spi_nor *nor)
 			return ret;
 	}
 
-	/* Needed by some flashes late_init hooks. */
-	spi_nor_init_flags(nor);
-
 	if (nor->info->fixups && nor->info->fixups->late_init) {
 		ret = nor->info->fixups->late_init(nor);
 		if (ret)

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:28 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:28 +0200
Subject: [PATCH v4 06/28] mtd: spi-nor: winbond: Move W25Q01NW to its right
 place
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-6-ee3445066e4e@bootlin.com>

Chips are sorted by families, and inside the families, by density. The
second ID byte indicates the family, the third indicates the
density. Move W25Q01NW to the right place.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
Reviewed-by: Michael Walle <mwalle at kernel.org>
---
 drivers/mtd/spi-nor/winbond.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 8ebdbcec0b3f..662a022d36e7 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -300,6 +300,12 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x60, 0x20),
 		.name = "w25q512nwq",
 		.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 | SPI_NOR_HAS_CMP,
+		.fixups = &winbond_rdcr_fixup,
 	}, {
 		.id = SNOR_ID(0xef, 0x70, 0x15),
 		.name = "w25q16jv-im/jm",
@@ -365,12 +371,6 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x80, 0x20),
 		.name = "w25q512nwm",
 		.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 | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_rdcr_fixup,
 	}, {
 		/* W25Q01NWxxIM */
 		.id = SNOR_ID(0xef, 0x80, 0x21),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:29 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:29 +0200
Subject: [PATCH v4 07/28] mtd: spi-nor: winbond: Normalize names
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-7-ee3445066e4e@bootlin.com>

Winbond chips cannot just be named eg. "W25Q02JV". There are always more
letters after that name, some of them matter.

- W is the Winbond prefix
- 25Q is the type of device
  -> 25Q indicates a quad capable SPI NOR
  -> 25H indicates an automotive grade chip
- 02J is the density
  -> in this case 2Gib
- V is the voltage
  -> V means 3.3V
  -> W means 1.8V
- The next letters (either 2 or 3 letters) are completely irrelevant for
  the software and purely indicate the hardware package.
- The penultimate letter is the temperature grade
  -> I for Industrial grade
  -> J means "above" industrial
- Q, N or M indicate factory states (like QE=1) and also the DTR
  capability.
  -> Q and N typically have the same IDs
  -> M chips typically have a different ID

As a result, W25Q01NWxxIQ is irrelevant and would better be named
W25Q01NW-Q, W25Q02JV is irrelevant and would better be named W25Q02JV-M,
etc.

Make that list uniform before adding more IDs/names.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
Reviewed-by: Michael Walle <mwalle at kernel.org>
---
 drivers/mtd/spi-nor/winbond.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 662a022d36e7..03e59ff3228f 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -301,7 +301,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q512nwq",
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
 	}, {
-		/* W25Q01NWxxIQ */
+		/* W25Q01NW-Q */
 		.id = SNOR_ID(0xef, 0x60, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
@@ -334,7 +334,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x19),
 		.name = "w25q256jvm",
 	}, {
-		/* W25Q02JV */
+		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),
 		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
@@ -372,28 +372,28 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q512nwm",
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
 	}, {
-		/* W25Q01NWxxIM */
+		/* W25Q01NW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q02NWxxIM */
+		/* W25Q02NW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &winbond_rdcr_fixup,
 	}, {
-		/* W25H512NWxxAM */
+		/* W25H512NW-M */
 		.id = SNOR_ID(0xef, 0xa0, 0x20),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25H01NWxxAM */
+		/* W25H01NW-M */
 		.id = SNOR_ID(0xef, 0xa0, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25H02NWxxAM */
+		/* W25H02NW-M */
 		.id = SNOR_ID(0xef, 0xa0, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:30 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:30 +0200
Subject: [PATCH v4 08/28] mtd: spi-nor: winbond: Properly document the chip
 names
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-8-ee3445066e4e@bootlin.com>

While the .name entry is a legacy field, is highly imprecise, and even
wrong sometimes due to ID reuse, it is part of the sysfs ABI, we cannot
remove it for existing chips. However, we can double the entry with the
now usual comment with the newly instated naming scheme to indicate what
chips are covered by each entry.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
I've been focusing on several chip families, but not all of them. I do
not have enough information about the oldest chips at the moment to
cleanup *all* IDs, but I am nevertheless trying to make a major cleaning
step here.
---
 drivers/mtd/spi-nor/winbond.c | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 03e59ff3228f..3e8e89058b8c 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -218,16 +218,19 @@ static const struct flash_info winbond_nor_parts[] = {
 		.size = SZ_1M,
 		.no_sfdp_flags = SECT_4K,
 	}, {
+		/* W25Q32JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x16),
 		.name = "w25q32",
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K,
 	}, {
+		/* W25Q64JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x17),
 		.name = "w25q64",
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q128JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x18),
 		/* Flavors w/ and w/o SFDP. */
 		.name = "w25q128",
@@ -236,18 +239,20 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.fixups = &w25q128_fixups,
 	}, {
+		/* W25Q256JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x19),
 		.name = "w25q256",
 		.size = SZ_32M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.fixups = &w25q256_fixups,
 	}, {
+		/* W25Q512JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x20),
 		.name = "w25q512jvq",
 		.size = SZ_64M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
-		/* W25Q01JV */
+		/* W25Q01JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x21),
 		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
@@ -285,52 +290,60 @@ static const struct flash_info winbond_nor_parts[] = {
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q128FW-G/Q, W25Q128JW-Q/N */
 		.id = SNOR_ID(0xef, 0x60, 0x18),
 		.name = "w25q128fw",
 		.size = SZ_16M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q256JW-Q/N */
 		.id = SNOR_ID(0xef, 0x60, 0x19),
 		.name = "w25q256jw",
 		.size = SZ_32M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q512NW-Q/N */
 		.id = SNOR_ID(0xef, 0x60, 0x20),
 		.name = "w25q512nwq",
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
 	}, {
-		/* W25Q01NW-Q */
+		/* W25Q01NW-Q/N */
 		.id = SNOR_ID(0xef, 0x60, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &winbond_rdcr_fixup,
 	}, {
+		/* W25Q16JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x15),
 		.name = "w25q16jv-im/jm",
 		.size = SZ_2M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q32JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x16),
 		.name = "w25q32jv",
 		.size = SZ_4M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q64JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x17),
 		.name = "w25q64jvm",
 		.size = SZ_8M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K,
 	}, {
+		/* W25Q128JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x18),
 		.name = "w25q128jv",
 		.size = SZ_16M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),
 		.name = "w25q256jvm",
 	}, {
@@ -343,6 +356,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.size = SZ_64M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q32JW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x16),
 		.name = "w25q32jwm",
 		.size = SZ_4M,
@@ -350,24 +364,28 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
 	}, {
+		/* W25Q64JW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x17),
 		.name = "w25q64jwm",
 		.size = SZ_8M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q128JW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x18),
 		.name = "w25q128jwm",
 		.size = SZ_16M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q256JW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x19),
 		.name = "w25q256jwm",
 		.size = SZ_32M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q512NW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x20),
 		.name = "w25q512nwm",
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:31 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:31 +0200
Subject: [PATCH v4 09/28] mtd: spi-nor: winbond: Make the RDCR fixup
 Winbond wide
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-9-ee3445066e4e@bootlin.com>

The top level paragraph of the QER field in the JESD216B mentions:

"In this standard, [...]  Status register 2 refers to the byte read
using instruction 35h. Status register 2 is the second byte transferred
in a Write Status (01h) command. [...]"

Value 100b, named in Linux BFPT_DWORD15_QER_SR2_BIT1_NO_RD, does not
mention anything about reads and only brings details about writes.

This has been interpreted in the spi-nor core by the absence of read
capability, but there is no explicit reason for that, except that there
were probably some very old chips which didn't support command 35h.

All quad capable Winbond chips carry a CMP SWP bit in SR2. SR2 is
readable with command 35h. In practice, all Winbond families but the
W25X family have support for this feature, so re-enable it Winbond-wide
in a late vendor fixup, except for the {EF, 30, xx} family.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 44 ++++++++++++++++++++-----------------------
 1 file changed, 20 insertions(+), 24 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 3e8e89058b8c..04713ab04d01 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -73,26 +73,6 @@ static const struct spi_nor_fixups w25q256_fixups = {
 	.post_bfpt = w25q256_post_bfpt_fixups,
 };
 
-static int
-winbond_rdcr_post_bfpt_fixup(struct spi_nor *nor,
-			     const struct sfdp_parameter_header *bfpt_header,
-			     const struct sfdp_bfpt *bfpt)
-{
-	/*
-	 * W25H02NW, unlike its W25H512NW nor W25H01NW cousins, improperly sets
-	 * the QE BFPT configuration bits, indicating a non readable CR. This is
-	 * both incorrect and impractical, as the chip features a CMP bit for its
-	 * locking scheme that lays in the Control Register, and needs to be read.
-	 */
-	nor->flags &= ~SNOR_F_NO_READ_CR;
-
-	return 0;
-}
-
-static const struct spi_nor_fixups winbond_rdcr_fixup = {
-	.post_bfpt = winbond_rdcr_post_bfpt_fixup,
-};
-
 /**
  * winbond_nor_select_die() - Set active die.
  * @nor:	pointer to 'struct spi_nor'.
@@ -313,7 +293,6 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x60, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_rdcr_fixup,
 	}, {
 		/* W25Q16JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x15),
@@ -399,7 +378,6 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x80, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_rdcr_fixup,
 	}, {
 		/* W25H512NW-M */
 		.id = SNOR_ID(0xef, 0xa0, 0x20),
@@ -415,8 +393,13 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0xa0, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_rdcr_fixup,
-	},
+	}, {
+		/*
+		 * Catch all entry to make sure all chips solely relying
+		 * on SFDP still go through the manufacturer hooks.
+		 */
+		.id = SNOR_ID(0xef),
+	}
 };
 
 /**
@@ -508,6 +491,19 @@ static int winbond_nor_late_init(struct spi_nor *nor)
 	 */
 	params->set_4byte_addr_mode = winbond_nor_set_4byte_addr_mode;
 
+	/*
+	 * All W25Q/W25H chips do set the BFPT_DWORD15_QER_SR2_BIT1_NO_RD bit in
+	 * their SFDP tables. The historical spi-nor assumption in this case has
+	 * been to declare CR reads as unsupported, whereas the Jedec
+	 * specification doesn't clearly state that. In practice, all these
+	 * chips do support reading back the CR, which is needed for SWP support,
+	 * so make sure that capability remains enabled (needed for SWP).
+	 * In practice, only exclude the old W25X family (JEDEC ID: EF 30 xx)
+	 * which actually does not support this feature.
+	 */
+	if (nor->id[1] > 0x30)
+		nor->flags &= ~SNOR_F_NO_READ_CR;
+
 	return 0;
 }
 

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:32 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:32 +0200
Subject: [PATCH v4 10/28] mtd: spi-nor: winbond: W25Q32JV-Q/N: Add quad
 page program capability
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-10-ee3445066e4e@bootlin.com>

The benefit is not massive @25MHz, yet it is a supported feature
of the chip which is already handled by a flag, so let's enable it and
earn that little 1% write throughput.

Before:
$ flash_speed /dev/mtd0 -d -c10
eraseblock write speed is 560 KiB/s
page write speed is 540 KiB/s
2 page write speed is 550 KiB/s

After:
eraseblock write speed is 578 KiB/s
page write speed is 557 KiB/s
2 page write speed is 567 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook is added to make sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x03
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3b
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbb
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6b
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x02
 1S-1S-4S
  opcode	0x32

$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
$ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
83e7d587bce3b2031c82939839cc1fbcf62b8939b35882d176f207b0722d0dba  spi_read
83e7d587bce3b2031c82939839cc1fbcf62b8939b35882d176f207b0722d0dba  spi_test
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
83e7d587bce3b2031c82939839cc1fbcf62b8939b35882d176f207b0722d0dba  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 04713ab04d01..2e53f16a9f93 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -203,6 +203,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q32",
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K,
+		.flags = SPI_NOR_QUAD_PP,
 	}, {
 		/* W25Q64JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x17),
@@ -504,6 +505,22 @@ static int winbond_nor_late_init(struct spi_nor *nor)
 	if (nor->id[1] > 0x30)
 		nor->flags &= ~SNOR_F_NO_READ_CR;
 
+	/*
+	 * Winbond has reused many IDs, up to four times at this
+	 * stage. In general, most of the chips with SFDP support are
+	 * correctly described by the ID table, but the non-SFDP chips,
+	 * however, are known to not feature as many capabilities. Make
+	 * sure we filter out those capabilities to keep backward
+	 * compatibility with these devices manufactured until ~2016.
+	 */
+	if (!nor->sfdp) {
+		struct spi_nor_flash_parameter *p = nor->params;
+
+		/* SPI_NOR_QUAD_PP was unsupported */
+		p->hwcaps.mask &= ~SNOR_HWCAPS_PP_1_1_4;
+		spi_nor_set_pp_settings(&p->page_programs[SNOR_CMD_PP_1_1_4], 0, 0);
+	}
+
 	return 0;
 }
 

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:33 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:33 +0200
Subject: [PATCH v4 11/28] mtd: spi-nor: winbond: W25Q64JV-Q/N: Add quad
 page program capability
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-11-ee3445066e4e@bootlin.com>

The benefit is not massive @25MHz, yet it is a supported feature
of the chip which is already handled by a flag, so let's enable it and
earn that little 1% write throughput.

Before:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 539 KiB/s
page write speed is 535 KiB/s
2 page write speed is 536 KiB/s

After:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 556 KiB/s
page write speed is 549 KiB/s
2 page write speed is 552 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 17 00 00 00
size		8.00 MiB
write size	1
page size	256
address nbytes	3
flags		HAS_16BIT_SR | SOFT_RESET | NO_WP

opcodes
 read		0xeb
  dummy cycles	6
 erase		0xd8
 program	0x32
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 20 (4.00 KiB) [1]
 52 (32.0 KiB) [2]
 d8 (64.0 KiB) [3]
 c7 (8.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-007fffff |     [   3] | no
$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug erase /dev/mtd0 0 2097152
mtd_debug read /dev/mtd0 0 2097152 spi_readErased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
$ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
36667197a637b731cc2068022138e6963a9499ce9b0ddda1a1585f7f5ebe66ff  spi_read
36667197a637b731cc2068022138e6963a9499ce9b0ddda1a1585f7f5ebe66ff  spi_test
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
36667197a637b731cc2068022138e6963a9499ce9b0ddda1a1585f7f5ebe66ff  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 2e53f16a9f93..15df3cc26fbc 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -210,6 +210,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q64",
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP,
 	}, {
 		/* W25Q128JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x18),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:34 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:34 +0200
Subject: [PATCH v4 12/28] mtd: spi-nor: winbond: W25Q128JV-Q/N: Add quad
 page program capability
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-12-ee3445066e4e@bootlin.com>

The benefit is not massive @25MHz, yet this feature is not advertized
because these chips do not implement the 4BAIT table. Flag the quad page
program capability manually and earn a few % of write throughput.

Before:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 536 KiB/s
page write speed is 532 KiB/s
2 page write speed is 532 KiB/s

After:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 552 KiB/s
page write speed is 547 KiB/s
2 page write speed is 549 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
c2712b5617a588f8bba4b8fc7af62f6ca5e2356577681b23544d1a93540ecd1d  spi_read
c2712b5617a588f8bba4b8fc7af62f6ca5e2356577681b23544d1a93540ecd1d  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
c2712b5617a588f8bba4b8fc7af62f6ca5e2356577681b23544d1a93540ecd1d  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 15df3cc26fbc..f7cdbf1a56a5 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -217,7 +217,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		/* Flavors w/ and w/o SFDP. */
 		.name = "w25q128",
 		.size = SZ_16M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.fixups = &w25q128_fixups,
 	}, {

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:35 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:35 +0200
Subject: [PATCH v4 13/28] mtd: spi-nor: winbond: W25Q32JV-M: Add quad page
 program capability
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-13-ee3445066e4e@bootlin.com>

This chip does not advertise it correctly but it supports 1-1-4 page
programs.

Before:

$ flash_speed /dev/mtd0 -d -c10
eraseblock write speed is 2612 KiB/s
page write speed is 2509 KiB/s
2 page write speed is 2560 KiB/s

After:

$ flash_speed /dev/mtd0 -c10 -d
eraseblock write speed is 3033 KiB/s
page write speed is 2895 KiB/s
2 page write speed is 2976 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index f7cdbf1a56a5..456050fdd251 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -307,8 +307,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x16),
 		.name = "w25q32jv",
 		.size = SZ_4M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 	}, {
 		/* W25Q64JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x17),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:36 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:36 +0200
Subject: [PATCH v4 14/28] mtd: spi-nor: winbond: W25Q64JV-M: Add quad page
 program capability
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-14-ee3445066e4e@bootlin.com>

The benefit is massive @25MHz, but the chip does not implement the 4BAIT
table to advertize this capability, so enable it manually:.

Before:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 558 KiB/s
page write speed is 553 KiB/s
2 page write speed is 555 KiB/s

After:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 3047 KiB/s
page write speed is 2909 KiB/s
2 page write speed is 2976 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 17 00 00 00
size		8.00 MiB
write size	1
page size	256
address nbytes	3
flags		HAS_SR_TB | HAS_LOCK | HAS_16BIT_SR | SOFT_RESET | NO_WP

opcodes
 read		0xeb
  dummy cycles	6
 erase		0xd8
 program	0x32
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 20 (4.00 KiB) [1]
 52 (32.0 KiB) [2]
 d8 (64.0 KiB) [3]
 c7 (8.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-007fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
$ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x03
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3b
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbb
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6b
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x02
 1S-1S-4S
  opcode	0x32

$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
$ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
d79c3f7b40646d437eff15309c5250e8ee203f805b9d13d8ad8a7019f0dd69f8  spi_read
d79c3f7b40646d437eff15309c5250e8ee203f805b9d13d8ad8a7019f0dd69f8  spi_test
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
d79c3f7b40646d437eff15309c5250e8ee203f805b9d13d8ad8a7019f0dd69f8  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 456050fdd251..41c9b620f26b 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -314,8 +314,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x17),
 		.name = "w25q64jvm",
 		.size = SZ_8M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 	}, {
 		/* W25Q128JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x18),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:37 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:37 +0200
Subject: [PATCH v4 15/28] mtd: spi-nor: winbond: W25Q128JV-M: Add quad page
 program capability
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-15-ee3445066e4e@bootlin.com>

This chip does not advertise it correctly but it supports 1-1-4 page
programs, add the flag manually:

Before:

$ flash_speed /dev/mtd0 -d -c10
eraseblock write speed is 553 KiB/s
page write speed is 548 KiB/s
2 page write speed is 550 KiB/s

After:

$ flash_speed /dev/mtd0 -c10 -d
eraseblock write speed is 1628 KiB/s
page write speed is 1600 KiB/s
2 page write speed is 1612 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 41c9b620f26b..2cdb4acd9ecf 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -321,8 +321,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x18),
 		.name = "w25q128jv",
 		.size = SZ_16M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 	}, {
 		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:38 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:38 +0200
Subject: [PATCH v4 16/28] mtd: spi-nor: winbond: W25QxxJV-Q/N/M: Add quad
 page program capability
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-16-ee3445066e4e@bootlin.com>

I do not have any W25Q256JV-Q/N/M nor any W25Q16JV-M chips in hand, but
all the chips with a density <= 256Mib are older revisions of the JV
chips which do not yet implement the 4BAIT extension table, as such,
they cannot advertise their quad page program capability. We can
reasonably expect that these chips follow the exact same rules, so add
the missing flag.

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the bit remains disabled
for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 2cdb4acd9ecf..b250a43ad4e1 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -226,6 +226,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q256",
 		.size = SZ_32M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP,
 		.fixups = &w25q256_fixups,
 	}, {
 		/* W25Q512JV-Q/N */
@@ -300,8 +301,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x15),
 		.name = "w25q16jv-im/jm",
 		.size = SZ_2M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 	}, {
 		/* W25Q32JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x16),
@@ -327,6 +328,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),
 		.name = "w25q256jvm",
+		.flags = SPI_NOR_QUAD_PP,
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:39 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:39 +0200
Subject: [PATCH v4 17/28] mtd: spi-nor: winbond: W25Q32JV-Q/N: Fill locking
 information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-17-ee3445066e4e@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
let's make sure the bit remains disabled for them, for backward
compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ alias show_sectors='grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0
/params'
$ flash_lock -u /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: unlocked
Return code: 0
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_read
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_test
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff | unlocked | 64
$ flash_lock -l /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: locked
Return code: 1
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_read
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_test
$ dd if=/dev/urandom of=./spi_test2 bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
$ sha256sum spi*
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_read
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_read2
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_test
5b69b16c88c3ae48213c359d2f9fd7f0583e513dabb224b3404bc93a5e173253  spi_test2
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff |   locked | 64
$ flash_lock -u /dev/mtd0
$ size=$(cat /sys/class/mtd/mtd0/size)
$ nblocks=$(grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params | sed -e 's/.*
unlocked | //')
$
$ bs=$(($size / $nblocks))
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2 * $bs))) 2 # last two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff | unlocked | 62
 003e0000-003fffff |   locked | 2
$ flash_lock -u /dev/mtd0 $(($size - (2 * $bs))) 1 # last one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff | unlocked | 63
 003f0000-003fffff |   locked | 1
$ flash_lock -u /dev/mtd0
$ all_but_one=$((($size / $bs) - 1))
$ flash_lock -l /dev/mtd0 $bs $all_but_one # all but the first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-003fffff |   locked | 63
$ flash_lock -u /dev/mtd0 $bs 1 # all but the two first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-003fffff |   locked | 62
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $all_but_one # same from the other side
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff |   locked | 63
 003f0000-003fffff | unlocked | 1
$ flash_lock -u /dev/mtd0 $(($size - (2 * $bs))) 1 # all but two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff |   locked | 62
 003e0000-003fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index b250a43ad4e1..64bf98ffc25b 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -203,7 +203,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q32",
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K,
-		.flags = SPI_NOR_QUAD_PP,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q64JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x17),
@@ -522,6 +522,9 @@ static int winbond_nor_late_init(struct spi_nor *nor)
 		/* SPI_NOR_QUAD_PP was unsupported */
 		p->hwcaps.mask &= ~SNOR_HWCAPS_PP_1_1_4;
 		spi_nor_set_pp_settings(&p->page_programs[SNOR_CMD_PP_1_1_4], 0, 0);
+
+		/* SPI_NOR_HAS_CMP was unsupported */
+		nor->flags &= ~SNOR_F_HAS_SR2_CMP_BIT6;
 	}
 
 	return 0;

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:40 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:40 +0200
Subject: [PATCH v4 18/28] mtd: spi-nor: winbond: W25Q64JV-Q/N: Fill locking
 information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-18-ee3445066e4e@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_read
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_read
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_read
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_read2
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_test
b12f8258bf0374eb5c55e659fc430df7a6016d3c0dca2dc04ef1fe82f61d7f67  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=8388608
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=131072
+ bps=2
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 8126464 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff | unlocked | 62
 007c0000-007fffff |   locked | 2
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff | unlocked | 63
 007e0000-007fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 -8388608 256
flash_lock: invalid option -- '8'
Utility to lock, unlock, or check the lock status of the flash.
Default action: lock

Usage: flash_lock [options] [--] <mtd device> [offset [block count]]

Options:
 -h         --help              Display this help and exit
 -V         --version           Display version information and exit
 -i         --islocked          Check if flash region is locked
 -l         --lock              Lock a region of flash
 -u         --unlock            Unlock a region of flash

 <mtd device>  MTD device node or 'mtd:<name>'

If offset is not specified, it defaults to 0.
If block count is not specified, it defaults to all blocks.
A block count of -1 means all blocks.
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 2
 00040000-007fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 1
 00020000-007fffff | unlocked | 63
+ all_but_one=126
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 131072 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 1
 00020000-007fffff |   locked | 63
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 2
 00040000-007fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff |   locked | 63
 007e0000-007fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff |   locked | 62
 007c0000-007fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 64bf98ffc25b..8fab520627bf 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -210,7 +210,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q64",
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q128JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x18),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:42 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:42 +0200
Subject: [PATCH v4 20/28] mtd: spi-nor: winbond: W25Q512JV-Q/N: Fill
 locking information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-20-ee3445066e4e@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ sha256sum spi_test
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_test
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_read
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_test
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
$ flash_lock -l /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: locked
Return code: 1
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_read
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_test
$ dd if=/dev/urandom of=./spi_test2 bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
$ sha256sum spi*
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_read
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_read2
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_test
0236e30ff05676c4bcc80b55aa0ebf4747630e0ae3360e025c9e0c2509402a6b  spi_test2
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff |   locked | 1024
$ flash_lock -u /dev/mtd0
$
$ size=$(cat /sys/class/mtd/mtd0/size)
$ nblocks=$(grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params | sed -e 's/.*
unlocked | //')
$
$ bs=$(($size / $nblocks))
$
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2 * $bs))) 2 # last two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff | unlocked | 1022
 03fe0000-03ffffff |   locked | 2
$ flash_lock -u /dev/mtd0 $(($size - (2 * $bs))) 1 # last one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff | unlocked | 1023
 03ff0000-03ffffff |   locked | 1
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2**7 * $bs))) $((2**7))
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-037fffff | unlocked | 896
 03800000-03ffffff |   locked | 128
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 2 # first two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-03ffffff | unlocked | 1022
$ flash_lock -u /dev/mtd0 $bs 1 # first one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-03ffffff | unlocked | 1023
$ all_but_one=$((($size / $bs) - 1))
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $bs $all_but_one # all but the first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-03ffffff |   locked | 1023
$ flash_lock -u /dev/mtd0 $bs 1 # all but the two first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-03ffffff |   locked | 1022
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $all_but_one # same from the other side
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff |   locked | 1023
 03ff0000-03ffffff | unlocked | 1
$ flash_lock -u /dev/mtd0 $(($size - (2 * $bs))) 1 # all but two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff |   locked | 1022
 03fe0000-03ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 5a80de959768..1ac0177aeb44 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -234,6 +234,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q512jvq",
 		.size = SZ_64M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q01JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x21),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:44 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:44 +0200
Subject: [PATCH v4 22/28] mtd: spi-nor: winbond: W25Q32JV-M: Fill locking
 information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-22-ee3445066e4e@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag the missing CMP capability.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   34.621251] random: crng init done
2+0 records in
2+0 records out
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_read
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_read
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_read
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_read2
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_test
bcf94096d25ce535e4b920f876d53420e6d8d917d0d14dd766b5f8c3d6f40475  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=4194304
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 4063232 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff | unlocked | 62
 003e0000-003fffff |   locked | 2
+ flash_lock -u /dev/mtd0 4063232 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff | unlocked | 63
 003f0000-003fffff |   locked | 1
/* Skipping BP3 test */
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-003fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-003fffff | unlocked | 63
+ all_but_one=63
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 63
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-003fffff |   locked | 63
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-003fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 63
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff |   locked | 63
 003f0000-003fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 4063232 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff |   locked | 62
 003e0000-003fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 59d5be14a73c..3f9f2257ac91 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -313,7 +313,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q32jv",
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q64JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x17),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:46 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:46 +0200
Subject: [PATCH v4 24/28] mtd: spi-nor: winbond: W25Q128JV-M: Fill locking
 information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-24-ee3445066e4e@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x1000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00ffffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x1000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read2
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
c9d2a381c868b43a44563b0e36e2556b3095acf94f6dad8f259c85eab536363e  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00ffffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=16777216
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=262144
+ bps=4
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 16252928 8
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00f7ffff | unlocked | 62
 00f80000-00ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 16252928 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00fbffff | unlocked | 63
 00fc0000-00ffffff |   locked | 1
/* Skipping BP3 */
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 8
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0007ffff |   locked | 2
 00080000-00ffffff | unlocked | 62
+ flash_lock -u /dev/mtd0 262144 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 1
 00040000-00ffffff | unlocked | 63
+ all_but_one=252
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 262144 252
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 1
 00040000-00ffffff |   locked | 63
+ flash_lock -u /dev/mtd0 262144 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0007ffff | unlocked | 2
 00080000-00ffffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 252
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00fbffff |   locked | 63
 00fc0000-00ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 16252928 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00f7ffff |   locked | 62
 00f80000-00ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 41b8733c4c85..4f26191e1d6d 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -327,7 +327,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q128jv",
 		.size = SZ_16M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:48 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:48 +0200
Subject: [PATCH v4 26/28] mtd: spi-nor: winbond: W25QxxJV-Q/N/M: Fill
 locking information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-26-ee3445066e4e@bootlin.com>

I do not have any W25Q256JV-Q/N/M nor any W25Q16JV-M chips in hand, but
all the other chips from this family have the same locking pattern, so
we can reasonably expect that these chips follow the exact same rules.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 5ab7f4860295..6b21fed74f4e 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -226,7 +226,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q256",
 		.size = SZ_32M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
+			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &w25q256_fixups,
 	}, {
 		/* W25Q512JV-Q/N */
@@ -306,7 +307,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q16jv-im/jm",
 		.size = SZ_2M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q32JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x16),
@@ -332,7 +333,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),
 		.name = "w25q256jvm",
-		.flags = SPI_NOR_QUAD_PP,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
+			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:49 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:49 +0200
Subject: [PATCH v4 27/28] mtd: spi-nor: winbond: W25Q512JV-M: New chip
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-27-ee3445066e4e@bootlin.com>

Add support for W25Q512JV-M. It is a 3.3V chip with usual dual/quad
capabilities.

The SFDP table is pretty complete except for the locking capabilities
which must be filled manually.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/partname
cat: can't open '/sys/bus/spi/devices/spi0.0/spi-nor/partname': No such file or directory
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7020
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
53464450060101ff00060110800000ff84000102d00000ff03000102f000
00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff1f44eb086b083b42bbfeffffffffff
0000ffff40eb0c200f5210d800003602a60082ea14e2e96376337a757a75
f7a2d55c19f74dffe970f9a5ffffffffffffffffffffffffffffffffff0a
f0ff21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
f41674a6c3742a42d31d3007d4d4b725c166ddb4e4566d1c347d29ed29855ef0  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 20 00 00 00
size		64.0 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_16BIT_SR | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (64.0 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-03ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   53.198369] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 67108864 (64M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read2
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
44db7586d798437868d29ea31ac1fa1e64d1b81337721d0d811776dad47110f3  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff |   locked | 1024
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=67108864
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=1024
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 66977792 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff | unlocked | 1022
 03fe0000-03ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff | unlocked | 1023
 03ff0000-03ffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 58720256 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-037fffff | unlocked | 896
 03800000-03ffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-03ffffff | unlocked | 1022
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-03ffffff | unlocked | 1023
+ all_but_one=1023
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-03ffffff |   locked | 1023
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-03ffffff |   locked | 1022
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff |   locked | 1023
 03ff0000-03ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff |   locked | 1022
 03fe0000-03ffffff | unlocked | 2
+ flash_speed /dev/mtd0 -dc10
not NAND flash, assume page size is 512 bytes.
scanning for bad eraseblocks
scanned 10 eraseblocks, 0 are bad
testing eraseblock write speed
eraseblock write speed is 437 KiB/s
testing eraseblock read speed
eraseblock read speed is 1560 KiB/s
testing page write speed
page write speed is 434 KiB/s
testing page read speed
page read speed is 1457 KiB/s
testing 2 page write speed
2 page write speed is 435 KiB/s
testing 2 page read speed
2 page read speed is 1498 KiB/s
Testing erase speed
erase speed is 257 KiB/s
Testing 2x multi-block erase speed
2x multi-block erase speed is 257 KiB/s
Testing 4x multi-block erase speed
4x multi-block erase speed is 257 KiB/s
Testing 8x multi-block erase speed
8x multi-block erase speed is 254 KiB/s
Testing 16x multi-block erase speed
16x multi-block erase speed is 255 KiB/s
Testing 32x multi-block erase speed
32x multi-block erase speed is 255 KiB/s
Testing 64x multi-block erase speed
64x multi-block erase speed is 253 KiB/s
finished
---
 drivers/mtd/spi-nor/winbond.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 6b21fed74f4e..980c437f1951 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -335,6 +335,11 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q256jvm",
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
 			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+	}, {
+		/* W25Q512JV-M */
+		.id = SNOR_ID(0xef, 0x70, 0x20),
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:50 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:50 +0200
Subject: [PATCH v4 28/28] mtd: spi-nor: winbond: W25Q01JV-M: New chip
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-28-ee3445066e4e@bootlin.com>

Add support for W25Q01JV-M. It is a 3.3V chip with usual dual/quad
capabilities.

The SFDP table is pretty complete except for the locking capabilities
which must be filled manually.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7021
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
53464450060101ff00060110800000ff84000102d00000ff03000102f000
00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff3f44eb086b083b42bbfeffffffffff
0000ffff40eb0c200f5210d800003602a60082ea14e2e96376337a757a75
f7a2d55c19f74dffe970f9a5ffffffffffffffffffffffffffffffffff0a
f0ff21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
c733cf105c5f05ed23ad3a32c2a72c1a09d4b895c628db52e645a8a457dfc7d6  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 21 00 00 00
size		128 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_16BIT_SR | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (128 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-07ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 134217728 (128M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read2
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
43870da5d2b9fcb8fe0a6a96350b87da1e9423fe9f9e2f039bf363e68916ae0d  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff |   locked | 2048
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=134217728
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=2048
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 134086656 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff | unlocked | 2046
 07fe0000-07ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 134086656 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff | unlocked | 2047
 07ff0000-07ffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 125829120 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-077fffff | unlocked | 1920
 07800000-07ffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-07ffffff | unlocked | 2046
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-07ffffff | unlocked | 2047
+ all_but_one=2047
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 2047
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-07ffffff |   locked | 2047
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-07ffffff |   locked | 2046
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2047
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff |   locked | 2047
 07ff0000-07ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 134086656 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff |   locked | 2046
 07fe0000-07ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 980c437f1951..593429bd6cdf 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -340,6 +340,12 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x20),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+	}, {
+		/* W25Q01JV-M */
+		.id = SNOR_ID(0xef, 0x70, 0x21),
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:45 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:45 +0200
Subject: [PATCH v4 23/28] mtd: spi-nor: winbond: W25Q64JV-M: Fill locking
 information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-23-ee3445066e4e@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_read
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_read
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_read
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_read2
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_test
6ba881af6f5901f7ca55322d45a7cee2ec69900cfdfc348141c3c5b1e51f676b  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=8388608
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=131072
+ bps=2
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 8126464 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff | unlocked | 62
 007c0000-007fffff |   locked | 2
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff | unlocked | 63
 007e0000-007fffff |   locked | 1
/* Skipping BP3 test */
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 2
 00040000-007fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 1
 00020000-007fffff | unlocked | 63
+ all_but_one=126
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 131072 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 1
 00020000-007fffff |   locked | 63
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 2
 00040000-007fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff |   locked | 63
 007e0000-007fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff |   locked | 62
 007c0000-007fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 3f9f2257ac91..41b8733c4c85 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -320,7 +320,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q64jvm",
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q128JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x18),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:41 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:41 +0200
Subject: [PATCH v4 19/28] mtd: spi-nor: winbond: W25Q128JV-Q/N: Fill
 locking information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-19-ee3445066e4e@bootlin.com>

The SFDP table does not advertize the CMP locking capabilities of the
device, flag it.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $ss $all_but_one # all but the first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 1
 00040000-00ffffff |   locked | 63
$ flash_lock -u /dev/mtd0 $ss $(($ss / $bs)) # all but the two first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0007ffff | unlocked | 2
 00080000-00ffffff |   locked | 62
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $all_but_one # same from the other side
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00fbffff |   locked | 63
 00fc0000-00ffffff | unlocked | 1
$ flash_lock -u /dev/mtd0 $(($size - (2 * $ss))) $(($ss / $bs)) # all but two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00f7ffff |   locked | 62
 00f80000-00ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 8fab520627bf..5a80de959768 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -217,8 +217,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		/* Flavors w/ and w/o SFDP. */
 		.name = "w25q128",
 		.size = SZ_16M,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 		.fixups = &w25q128_fixups,
 	}, {
 		/* W25Q256JV-Q/N */

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:47 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:47 +0200
Subject: [PATCH v4 25/28] mtd: spi-nor: winbond: W25Q02JV-M: Fill locking
 information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-25-ee3445066e4e@bootlin.com>

The SFDP table is pretty complete except for the locking capabilities
which must be filled manually.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x10000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_read
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff | unlocked | 4096
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x10000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_read
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_read
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_read2
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_test
e61267bbd0864dfe7e67a8bfc0875a23308d98c7297fcf5a813830532f48c06f  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff |   locked | 4096
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=268435456
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=4096
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 268304384 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffdffff | unlocked | 4094
 0ffe0000-0fffffff |   locked | 2
+ flash_lockk uu /dev/mtd0 268304384 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffeffff | unlocked | 4095
 0fff0000-0fffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 260046848 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0f7fffff | unlocked | 3968
 0f800000-0fffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-0fffffff | unlocked | 4094
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-0fffffff | unlocked | 4095
+ all_but_one=4095
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 4095
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-0fffffff |   locked | 4095
+ flassh__llock u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-0fffffff |   locked | 4094
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4095
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffeffff |   locked | 4095
 0fff0000-0fffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 268304384 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffdffff |   locked | 4094
 0ffe0000-0fffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 4f26191e1d6d..5ab7f4860295 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -336,6 +336,8 @@ static const struct flash_info winbond_nor_parts[] = {
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
 		.id = SNOR_ID(0xef, 0x71, 0x19),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:04:43 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:04:43 +0200
Subject: [PATCH v4 21/28] mtd: spi-nor: winbond: W25Q01JV-Q/N: Fill locking
 information
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-21-ee3445066e4e@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
$ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef4021
$ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
53464450060101ff00060110800000ff84000102d00000ff03000102f000
00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff3f44eb086b083b42bbfeffffffffff
0000ffff40eb0c200f5210d800003602a60082ea14e2e96376337a757a75
f7a2d55c19f74dffe970f9a5ffffffffffffffffffffffffffffffffff0a
f0ff21ffdcff
$ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
c733cf105c5f05ed23ad3a32c2a72c1a09d4b895c628db52e645a8a457dfc7d6  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
$ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/
jedec_id      manufacturer  sfdp
$ sha256sum /sys/kernel/debug/spi-nor/spi0.0/capabilities
6204c70963ab07f9865d051b888679ce34ad3a9f167fbbd4a2a6e0c3d0ce4cdf  /sys/kernel/debug/spi-nor/spi0.0/capabilities
$ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34

$ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 21 00 00 00
size		128 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_16BIT_SR | NO_READ_CR | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (128 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-07ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048

$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ flash_lock -u /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: unlocked
Return code: 0
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_read
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_test
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048
$ flash_lock -l /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: locked
Return code: 1
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_read
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_test
$ dd if=/dev/urandom of=./spi_test2 bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
$ sha256sum spi*
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_read
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_read2
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_test
367809dbdff878d023d3a1a8526a6382759a3ec78aa6289d18577d15f013d44f  spi_test2
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff |   locked | 2048
$ flash_lock -u /dev/mtd0
$ nsectors=$(grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params | sed -e 's/.
*unlocked | //')
$ ss=$(($size / $nsectors))
$ bps=$(($ss / $bs))
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2 * $ss))) $((2 * $bps)) # last two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff | unlocked | 2046
 07fe0000-07ffffff |   locked | 2
$ flash_lock -u /dev/mtd0 $(($size - (2 * $ss))) $((1 * $bps)) # last one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff | unlocked | 2047
 07ff0000-07ffffff |   locked | 1
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2**7 * $ss))) $((2**7 * $bps))
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-077fffff | unlocked | 1920
 07800000-07ffffff |   locked | 128
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $((2 * $bps)) # first two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-07ffffff | unlocked | 2046
$ flash_lock -u /dev/mtd0 $ss $((1 * $bps)) # first one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-07ffffff | unlocked | 2047
$ flash_lock -l /dev/mtd0 $ss $all_but_one # all but the first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-07ffffff |   locked | 2047
$ flash_lock -u /dev/mtd0 $ss $(($ss / $bs)) # all but the two first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-07ffffff |   locked | 2046
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $all_but_one # same from the other side
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff |   locked | 2047
 07ff0000-07ffffff | unlocked | 1
$ flash_lock -u /dev/mtd0 $(($size - (2 * $ss))) $(($ss / $bs)) # all but two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff |   locked | 2046
 07fe0000-07ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 1ac0177aeb44..59d5be14a73c 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -239,6 +239,8 @@ static const struct flash_info winbond_nor_parts[] = {
 	}, {
 		/* W25Q01JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x21),
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
 		.id = SNOR_ID(0xef, 0x50, 0x12),

-- 
2.54.0



From miquel.raynal at bootlin.com  Tue Aug  4 07:57:26 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 04 Aug 2026 16:57:26 +0200
Subject: [PATCH 1/5] mtd: spi-nor: Refactor Read Status/Write Status
 support
In-Reply-To: <DJS8PQ5HZZG5.2YT956NCDX8MG@kernel.org> (Michael Walle's message
	of "Tue, 07 Jul 2026 11:43:44 +0200")
References: <20260529-winbond-v7-1-spi-nor-rv-addition-v1-0-f3ae18502d5a@bootlin.com>
	<20260529-winbond-v7-1-spi-nor-rv-addition-v1-1-f3ae18502d5a@bootlin.com>
	<DJS8PQ5HZZG5.2YT956NCDX8MG@kernel.org>
Message-ID: <87mrv2exwp.fsf@bootlin.com>

Hello Michael,

On 07/07/2026 at 11:43:44 +02, "Michael Walle" <mwalle at kernel.org> wrote:

> Hi Miquel,
>
> thanks for doing this (somewhat thankless) task. I briefly skimmed
> over this patch.. [Ok coming back here, it's quite a bigger
> feedback. And I'm sorry if it's sometime confusing, I was jumping
> back and forth].

Thanks a lot for doing that review, which I know is painful.

> On Fri May 29, 2026 at 6:05 PM CEST, Miquel Raynal wrote:
>> SPI NOR has a lot of history. Additions over additions, the subtleties
>> of the JESD216 specification, their implementations by the manufacturers
>> and the hardware mistakes have generated a gigantic maze, let's try to
>> understand what is really needed.
>>
>> The specification explains the QE (Quad Enable) bitfield as describing
>> where the bit to enable the Quad capability is, but also how to set
>> it.
>
> I think the standard just wrote down what all the different vendors
> were doing. So it didn't really start as "one" standard but just
> collected all the different variants from all the vendors. Also, it
> seems that the standard is only interested in how to enable the
> quad IO mode. Everything else is guesswork. But iff a given vendor
> stick to its implementation, chances are that the QER will
> correspond to how to read and write status registers. So for our
> guesswork, we can use a QER-to-"sr access" mapping as a baseline.
> We should map the QER enumeration to vendors to get a better
> understanding now and in the future.

It seems more complex than that, because indeed it all started with a
status of what were (more or less) doing the vendors. Unfortunately they
did not stick to that method, so it is not possible to identify "vendor
X => QER Y". And it continues to evolve (as in patch 2). And TBH I don't
have the history, but if someone wants to sparkle the SFDP switch with
surgical comments, I agree it's interesting to keep a trace.

> Also, I haven't found a vendor which is using the 3Eh/3Fh opcodes?!

Good question, I have no idea either, but if I remove it I fear even
more pushback :)

>> Unfortunately, the specification is not precise about what opcodes
>> are supported exactly in all the cases. There is an introduction that
>> basically states:
>> - Opcode 0x05 reads SR1, and then SR2 if another byte is read
>
> Where is that? It only states that 05 reads sr1, no?

You are right, my bad. What a sniper :)

I will correct the commit log and the code.

>> - Opcode 0x35 allows to read SR2
>> - Opcode 0x01 writes SR1, and then SR2 if another byte is written
>
> Ack.
>
> But reading the individual QER methods below, I'm not sure, if that
> is really the baseline here.

This is what the introduction of the QER field states. Then, every field
slightly modifies that baseline, except 101 which IIRC is more or less
the nominal behaviour without subtlety.

> I mean 101b corresponds to exactly
> that, no?

In the spec, yes. In the implementation, no, since (I believe for wrong
reasons) SR1 cannot be written alone. So we force 2 bytes writes in this
case, I am not changing this.

>> Then the bitfield, among indicating the location of the QE bit, may
>> indicate:
>> - Reading SR1 and SR2 in one operation is not possible (loops over the
>>   content of SR1)
>> - Reading SR2 directly is possible
>> - Writing SR1 smashes SR2
>
> You mean writing just one byte with 01h, will set sr2 to zero. IOW,
> you'll have to always write two bytes if using 01h.

Yes. I will make it more clear in the next iteration.

> What we also don't know is how many status registers there actually
> are.

Very true, and solve with this approached btw. For now I don't need
it, but the RV chips feature a third status registers.

>> One problem with the current implementation, is that it only focuses on
>> the QE bit. A quad_enable function was created for each case, even
>> though in practice, the logic was always the same: read, modify, write,
>> read back and verify. One problem comes when other features need to play
>> with the status registers, like software block protection or OTP: you
>> never know how to properly handle the QE bit, nor where it is, nor how
>> to read/write the Status registers. This lead to
>> approximations/guessing (in swp.c, otp.c and obviously in legacy
>> controller drivers like atmel.c) but also to the implementation of a
>> gazillon of helpers for reading/writing/checking the status registers.
>
> Mh? why do we have to know the QE bit in other parts? We shall just
> read-modify-write the status registers, no?

I am sorry I'm not sure I get the question here let me attempt a full
answer.

No, rmw is not always the way to go, one must be more careful, since you
cannot read SR2 sometimes and cannot just write only SR1 without risking
to loose SR2 some other times.

However, I don't want that complexity to be handled by all the callers,
hence I am proposing high-level helpers for that.

>> In addition, I believe some design decisions had a negative impact over
>> the years.
>>
>> - All possible situations had to be flagged by the core. This is likely
>>   wrong, because we no longer know why we need specific quirks. It was
>>   ineherent to the state of the SPI NOR core before the great cleanup
>>   that had happened the past few years. I believe this creates confusion
>>   in the core today, and we should push this to vendor fixups
>>   instead. As an example, in 2023 Hsin-Yi was facing an issue because
>>   his chip was setting the wrong QER value, leading to RDCR being
>>   prevented, thus falling into a condition blindly setting a random QE
>>   bit (I strongy believe it is done like that for wrong reasons). His
>>   chip actually had RDCR support! The correct fix should have been to
>>   mark the capability in a device fixup instead of handling this in the
>>   core.
>>
>>   Link: https://lore.kernel.org/lkml/CAJMQK-hR0eaO0b4Vd0U8_KAndLyZapqdHjVLAoe42rWi9rdLkA at mail.gmail.com/
>
> You mean the "always set the QER bit"?
>
> I agree, this should be pushed into the vendor fixups. But what I
> really want to avoid is that we have to have a never ending list of
> IDs for these fixups. So there shall be a generic vendor fixup.

I guess we can have this kind of fixups, there is one in macronix.c and
soon another one in winbond.c. But at the moment I just cannot invent
them. I can only translate the existing ones and cry for the others.

> As a side node, I really don't like how the fixups are handled at
> the moment.  I've proposed that way back but it would be awesome to
> be able to just register a fixup with
>
> register_spi_nor_fixup(SNOR_ID(0xef, 0x40, 0x16), winbond_w25q32_fixup);
> register_spi_nor_fixup(SNOR_ID(0xef), winbond_default_properties);

Aaaaaah, that would be great. It is indeed a problem currently, since we
cannot "assign" a fixup to several chips easily, if these chips already
have other fixups. I found a neat way in the Winbond driver (the
is_w25qxxrv() helper) but if someone comes with such a series, I'd be
all for it.

If I get bothered too much with that I might also do it myself, but,
heh, one step at a time :-)

> Maybe you have some thoughts on that. I'm not sure we really need
> the distinction between manufacturer fixups and per device fixups.
>
> Now obviously, with our id reuse problem, that above isn't enough.
> I've proposed to have a match function back that - by default - only
> matches the id - but can be overwritten to match anything else. I
> have also thought about just doing a checksum over the SFDP and
> match that.

A checksum would be super painful to maintain, however I would
definitely go for a possible hook that says whether or not the fixup
applies (based on SFDP content, like the SFDP version or anything else
that could be checked). This would be a way to deduplicate the entries
when there is an ID reuse.

> Any thoughts on that?
>
> But I digress..

:')

>> - SFDP parsing is over cautious. I believe
>>   BFPT_DWORD15_QER_SR2_BIT1_NO_RD is abusive (nothing states that RDCR is
>>   not supported), and BFPT_DWORD15_QER_SR2_BIT1 is also out of
>>   specification when forcing 16-bit Status writes.
>
> Also, I don't think BFPT_DWORD15_QER_SR2_BIT1_BUGGY is a good name
> :)

Yes, maybe I can rename it now.

>> - SNOR_F_HAS_16BIT_SR is only imposing 16bit Status writes, whereas
>>   reads can still be 8-bit wides and even sometimes can only be 8-bit
>>   wide.
>>
>> - The usage of helpers verifying the writes was also spread for IMHO no
>>   really good reason. Why shouldn't we trust spi operations when it
>>   comes to Status Registers? We do not read back our page reads, so why
>>   status registers should be treated with so much care, if it's not
>>   because we are unsure of what is being done? My proposal includes a
>>   check when it comes to the QE bit (done once) but we don't need these
>>   checks otherwise. If the QE bit was written properly, there are high
>>   chances that the other register accesses will just be fine, no?
>
> One thing that comes to mind is hardware write protection. If
> there's nothing before that code which checks it, the verify might
> fail if the hardware write protection is enabled. So we should
> somehow check for that and drop the verify here.

But how do you think we should handle it? Will the QE bit writing
verification fail if HW WP is enabled?

>
>> Asde from my main quest, I also observed no good reason to ask the
>> read/write status register callers to use nor->bouncebuf while the
>> low-level helpers could do it themselves (we are talking about one or
>> two bytes being copied).
>
> Totally agree!
>
>> So after these observations, my proposal is the following:
>> - Create private low level helpers that just read or write a status
>>   register. They are flexible, we can give the opcode (which varies
>>   based on the SFDP QER field) and the length (1 or 2).
>> - Create public generic accessors which will be used to read/write sr1
>>   and/or sr2. This is where all the cleverness shall be. The helpers use
>>   the available opcodes for a given chip in order to fullfill the
>>   request.
>> - Provide a single generic ->quad_enable() hook which generically does
>>   all the steps mentioned above (read, modify, write, read back and
>>   verify).
>> - Create a list of opcodes for all 6 possible situations:
>>   {read, write} {sr1, sr2, sr1 and sr2}. These opcodes are
>>   filled/cleared based on the QER field. An opcode set to 0 indicates
>>   the absence of support (there is no 0x00 opcode in SPI NOR).
>
> How would we fill these fields for flashes without SFDP? I guess
> by vendor in the vendor modules.

Non SFPD chips are already using the "default" that the core implies, I
am not changing that (just translated it to the new formalism).

>> I tried my best to analyze the current behavior and to mimic it as much
>> as possible, but this is a risky cleanup. However, if we go for this, it
>> will be *much* easier in the future to handle all kind of chip
>> variations. We won't be limited to a couple of flags anymore, but rather
>> we'll be able to just disable a read or write capability using a single
>> line.
>
> I'd op for taking this early in the cycle for at least two cycles.
> I.e. just let it sit in next and don't include it in the next PR,
> but one after that.

I very much agree with this. Ideally at -rc1.

[...]

>> +/**
>> + * spi_nor_write_srs_ll() - Low-level Status Registers write.
>> + * @nor:	pointer to 'struct spi_nor'.
>> + * @opcode:	opcode for the status register write operation.
>> + * @srs:	pointer to status registers buffer to write.
>> + * @len:	number of status registers to write.
>> + *
>> + * Return: 0 on success, -errno otherwise.
>> + */
>> +static int spi_nor_write_srs_ll(struct spi_nor *nor, u8 opcode,	const u8 *srs,
>> +				unsigned int len)
>> +{
>> +	int ret, i;
>> +
>> +	ret = spi_nor_write_enable(nor);
>> +	if (ret)
>> +		return ret;
>> +
>> +	for (i = 0; i < len; i++)
>> +		nor->bouncebuf[i] = srs[i];
>> +
>> +	if (nor->spimem) {
>> +		struct spi_mem_op op = SPI_NOR_WRSR_OP(opcode,
>> +						       nor->bouncebuf, len);
>> +
>> +		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
>> +
>> +		ret = spi_mem_exec_op(nor->spimem, &op);
>> +	} else {
>> +		ret = spi_nor_controller_ops_write_reg(nor, opcode,
>> +						       nor->bouncebuf, len);
>> +	}
>> +
>> +	if (ret) {
>> +		dev_dbg(nor->dev, "Error %d writing Status Registers\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	return spi_nor_wait_till_ready(nor);
>> +}
>> +
>> +int spi_nor_read_srs(struct spi_nor *nor, u8 *sr1, u8 *sr2)
>
> nitpick, i think this is a weird name. But maybe it's just me.
>
> What if we'd just make the one status register as u32? I mean in the
> winbond datasheet it's called S7-S0 and S15-S8. That way we also
> don't need a two byte qe_mask. To optimize the standard usecase to
> poll the WIP bit, we could add a byte mask.
>
>> +{
>> +	struct spi_nor_flash_parameter *params = nor->params;
>> +	int ret;
>> +
>> +	if (params->read_srs_opcode &&
>> +	    ((sr1 && !params->read_sr1_opcode) || (sr2 && !params->read_sr2_opcode))) {
>> +		u8 srs[2] = {};
>> +
>> +		ret = spi_nor_read_srs_ll(nor, params->read_srs_opcode, srs, 2);
>> +		if (ret)
>> +			return ret;
>> +
>> +		if (sr1)
>> +			*sr1 = srs[0];
>> +
>> +		if (sr2)
>> +			*sr2 = srs[1];
>> +
>> +		return 0;
>> +	}
>> +

[...]

>> @@ -3106,11 +2804,14 @@ static void spi_nor_init_default_params(struct spi_nor *nor)
>>  	const struct flash_info *info = nor->info;
>>  	struct device_node *np = spi_nor_get_flash_node(nor);
>>  
>> -	params->quad_enable = spi_nor_sr2_bit1_quad_enable;
>> -	params->otp.org = info->otp;
>> -
>> -	/* Default to 16-bit Write Status (01h) Command */
>> +	/* Default to 16-bit Read/Write Status commands */
>>  	nor->flags |= SNOR_F_HAS_16BIT_SR;
>> +	params->read_srs_opcode = SPINOR_OP_RDSR;
>> +	params->write_srs_opcode = SPINOR_OP_WRSR;
>> +	params->quad_enable = spi_nor_generic_quad_enable;
>> +	params->qe_mask[1] = BIT(1);
>
> Not sure this is a safe bet. I'd need to consult all datasheets of
> the different vendors. Not now..

This is not a bet, just a translation. Unless if I'm missing
something?

>> +
>> +	params->otp.org = info->otp;
>>  
>>  	/* Set SPI NOR sizes. */
>>  	params->writesize = 1;
>> @@ -3258,8 +2959,11 @@ static int spi_nor_quad_enable(struct spi_nor *nor)
>>  		return 0;
>>  
>>  	if (!(spi_nor_get_protocol_width(nor->read_proto) == 4 ||
>> -	      spi_nor_get_protocol_width(nor->write_proto) == 4))
>> +	      spi_nor_get_protocol_width(nor->write_proto) == 4)) {
>> +		nor->params->qe_mask[0] = 0;
>> +		nor->params->qe_mask[1] = 0;
>
> Why setting the mask to 0? The mask is still valid, but we just
> cannot use it. But it could still be a useful information, i.e. in
> debugfs.

I see, let me find another way.

>>  		return 0;
>> +	}
>>  
>>  	return nor->params->quad_enable(nor);
>>  }

[...]

>> @@ -130,7 +112,6 @@ enum spi_nor_option_flags {
>>  	SNOR_F_HAS_4BAIT	= BIT(4),
>>  	SNOR_F_HAS_LOCK		= BIT(5),
>>  	SNOR_F_HAS_16BIT_SR	= BIT(6),
>> -	SNOR_F_NO_READ_CR	= BIT(7),
>>  	SNOR_F_HAS_SR_TB_BIT6	= BIT(8),
>>  	SNOR_F_HAS_4BIT_BP      = BIT(9),
>>  	SNOR_F_HAS_SR_BP3_BIT6  = BIT(10),
>> @@ -375,6 +356,13 @@ struct spi_nor_otp {
>>   * @otp:		SPI NOR OTP info.
>>   * @set_octal_dtr:	enables or disables SPI NOR octal DTR mode.
>>   * @quad_enable:	enables SPI NOR quad mode.
>> + * @qe_mask:		two bytes mask used to set/clear the QE bit
>
> Two bytes? I assume that's one for each status register. That's not
> really clear. But also see above, maybe make it u32.

Yes, why not. So we are safe until they add a fifth SR :)

>> + * @read_srs_opcode:	opcode used to read SR1 and SR2 in one operation
>> + * @read_sr1_opcode:	opcode used to read SR1 alone
>> + * @read_sr2_opcode:	opcode used to read SR2 alone
>> + * @write_srs_opcode:	opcode used to write SR1 and SR2 in one operation
>> + * @write_sr1_opcode:	opcode used to write SR1 alone
>> + * @write_sr2_opcode:	opcode used to write SR2 alone
>>   * @set_4byte_addr_mode: puts the SPI NOR in 4 byte addressing mode.
>>   * @ready:		(optional) flashes might use a different mechanism
>>   *			than reading the status register to indicate they
>> @@ -405,6 +393,13 @@ struct spi_nor_flash_parameter {
>>  
>>  	int (*set_octal_dtr)(struct spi_nor *nor, bool enable);
>>  	int (*quad_enable)(struct spi_nor *nor);
>> +	u8				qe_mask[2];
>> +	u8				read_srs_opcode;
>> +	u8				read_sr1_opcode;
>> +	u8				read_sr2_opcode;
>> +	u8				write_srs_opcode;
>> +	u8				write_sr1_opcode;
>> +	u8				write_sr2_opcode;
>
> Can we move all the opcodes into one place? There is already a
> die_erase_opcode. Though it might just be a good idea now to group
> them into a subnode.
>
> spi_nor_flash_opcodes {
> 	u8 die_erase;
> 	u8 rdsr;
> 	u8 rdsr1;
> 	u8 rdsr2;
> 	u8 wrsr;
> 	u8 wrsr1;
> 	u8 wrsr2;
> };

Definitely a good idea.

>
> Also I'd make it a callback, so a it can be overwritten if there's
> some weird flash.

Why a callback? We just need to amend that table. That's exactly what I
propose in winbond.c, it just works and is much more readable.

> int (*read_sr)(struct spi_nor *nor, u32 *sr, u8 bm)
> int (*write_sr)(struct spi_nor *nor, u32 sr, u8 bm)
>
> int spi_nor_generic_read_sr(struct spi_nor *nor, u32 *sr,
>                             u8 byte_mask)
> {
> 	spi_nor_flash_parameter *params = nor->params;
>
> 	if (params->flags & SNOR_F_SUPPORTS_SR_16BIT_READ)
> 		...
> }
>
> int spi_nor_generic_write_sr(struct spi_nor *nor, u32 *sr,
>                              u8 byte_mask)
> {
> 	if (params->flags & SNOR_F_HAS_16BIT_SR)
> 		...
> }

[...]

>> --- a/drivers/mtd/spi-nor/sfdp.c
>> +++ b/drivers/mtd/spi-nor/sfdp.c
>> @@ -561,10 +561,21 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
>>  	val >>= BFPT_DWORD11_PAGE_SIZE_SHIFT;
>>  	params->page_size = 1U << val;
>>  
>> +	/*
>> +	 * The standard declares various read and write status methods, some of
>> +	 * them will be overloaded based on the QER field.
>> +	 */
>> +	params->read_srs_opcode = SPINOR_OP_RDSR;
>
> Not sure, that's a sane default. I think it was always 05/35 for
> reading.

That is right, I will amend and follow your suggestion which seems safer.

> Some newer flashes might have mapped the sr2 to the second
> byte for the 05 opcode.
>
>> +	params->read_sr1_opcode = SPINOR_OP_RDSR;
>> +	params->read_sr2_opcode = SPINOR_OP_RDCR;
>> +	params->write_srs_opcode = SPINOR_OP_WRSR;
>> +	params->write_sr1_opcode = SPINOR_OP_WRSR;
>> +

Thanks! I will come up with an update soon.

Miqu?l


From miquel.raynal at bootlin.com  Wed Aug  5 00:59:15 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 09:59:15 +0200
Subject: [PATCH v4 00/28] mtd: spi-nor: Clean Winbond W25QxxJV family
In-Reply-To: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
	(Miquel Raynal's message of "Tue, 04 Aug 2026 16:04:22 +0200")
References: <20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e@bootlin.com>
Message-ID: <87a4r1f164.fsf@bootlin.com>

Hello,

On 04/08/2026 at 16:04:22 +02, Miquel Raynal <miquel.raynal at bootlin.com> wrote:

> Thanks to Winbond collaboration, I was able to test almost the entire JV
> SPI NOR family. A few chips could not be sourced, I decided to apply the
> very same logic than the other chips, all of them behave identically as
> shown in all the logs that I am sharing in each and every commit.
>
> This series is pretty straightforward, it is here to cleanup the JV
> entries by:
> - Aligning the comments and "fixing" the chip names in them
> - Reordering the chips by density and family
> - Testing and showing all the SFDP content and passing all the SPI NOR
>   tests to ensure a good level of support.
> - Adding all the locking information when it is missing.
>
> Since v3, these Winbond changes are prefixed with core changes, to make
> the whole support work with legacy chips and SFDP compliant chips at the
> same time.

I cleared the wrong version of the sfdp table pointer in my v4, Sashiko
complains ad nauseam about that. Otherwise, the other comments are
bullshit.

I'll send a v5 with that pointer assignment fixed.

Thanks,
Miqu?l


From miquel.raynal at bootlin.com  Wed Aug  5 01:02:25 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:25 +0200
Subject: [PATCH v5 00/28] mtd: spi-nor: Clean Winbond W25QxxJV family
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>

Thanks to Winbond collaboration, I was able to test almost the entire JV
SPI NOR family. A few chips could not be sourced, I decided to apply the
very same logic than the other chips, all of them behave identically as
shown in all the logs that I am sharing in each and every commit.

This series is pretty straightforward, it is here to cleanup the JV
entries by:
- Aligning the comments and "fixing" the chip names in them
- Reordering the chips by density and family
- Testing and showing all the SFDP content and passing all the SPI NOR
  tests to ensure a good level of support.
- Adding all the locking information when it is missing.

Since v3, these Winbond changes are prefixed with core changes, to make
the whole support work with legacy chips and SFDP compliant chips at the
same time.

This work is necessary because I am about to propose the addition of the
W25QxxRV family, whose IDs actually collide with the JV chips. The chips
are similar, they have been "improved" (especially the silicon/engraving
smoothness) and we have a way to differentiate them based on their SFDP
content, but that will be for a following series.

Two Winbond-wide fixups are applied, non SFDP compatible chips do not
have CMP nor QUAD_PP support, so let's make sure the additions we do in
the table do not affect the old flags. Keeping the legacy flash handling
in a separate function means that it will be easier to handle the day we
want to drop support for them. So I picked that solution instead of
enabling conditionally the fields at runtime (which makes the ID table
real content harder to consolidate).

NB: There is one collision with a Spansion chip as well (S25FL064K). On
my side I commented out the Spansion entry to make sure the core was
taking the Winbond entry (by alphabetical order) but it is due to an old
joint agreement and is a legitimate ID duplication. We haven't found a
good way to differentiate them so far, so I am keeping this aside for
the moment and just assume we will have a solution later. In all cases,
the colliding Winbond ID exist and is already fulfilled, so let's make
sure the definition is accurate.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
New Sahiko hallucinations:
- Sashiko complains about breaking legacy chips, its description of the
  problem is incomplete and already very well documented in the commit
  log, so a bit annoying to get that same answer repeated over and over
  again.
- I don't know why it continuously complains about swp.c being
  *fundamentally broken* all the time. Winbond datasheets clearly state
  "The write to registers (without input address) instructions will write
  simultaneously to all the die registers as long as the die are not
  busy". Now please shut up about that, Sashiko.

Changes in v5:
- Fixed a wrong clearing operation: s/sfdp = NULL/nor->sfdp = NULL/,
  repeated ad nauseam in the review.
- Link to v4: https://lore.kernel.org/r/20260804-winbond-v7-1-spi-nor-jv-cleanup-v4-0-ee3445066e4e at bootlin.com

Sahiko hallucinations:
- Unconditional SFDP probing is intentional, it is explained, but it
  will very likely not break backward compatibility for legacy non-SFDP
  chips. First, there is a flag to skip SFDP parsing if it exist and is
  wrong. If the SFDP command does not exist, this is handled early and I
  believe it is fine paying the cost of this extra check on legacy
  chips. Finally the deprecated path already parses SFDP, and actually
  claims it does. The only change here is the fact that it is now done
  even on chips not advertizing dual/quad/octal support, which is an
  justified condition. It would be much more dangerous to flag the
  dual/quad capabilities of these chips (which could have been done
  otherwise) given that we cannot test it.
- I disagree with Sashiko's feedback about the multi-die SWP issue: it
  is not fundamentally broken since there is only one set of Status
  Registers for the whole chip and all the bytes are addressed with
  those 4 bits. This is a false positive. So no, SR-based locking on the
  multi-die flash does not break partial locking nor incorrectly reports
  hardware lock state.
- SPI_NOR_BP3_SR_BIT6 is not used in Winbond driver: slop (it is
  probably a vendor mixup since Micron does use it, whereas Winbond uses
  SPI_NOR_TB_SR_BIT6).
- 254Mb devices do have 4 BP bits, I don't know why Sashiko compains
  about this. It is clearly stated in their datasheets.

Changes in v4:
- Fixed the handling of one error in the garbage collection patch (at
  the end of SFDP parsing).
- Fixed an issue in the commit log (s/CMP/QUAD_PP).
- Improved the SFDP parsing failure rollback mechanism.
- Link to v3: https://lore.kernel.org/r/20260804-winbond-v7-1-spi-nor-jv-cleanup-v3-0-542ce107bc92 at bootlin.com

Changes in v3:
- Added several core patches to change the deprecated SFDP parsing step
  a bit: do not limit SFDP parsing only to chips advertizing
  dual/quad/octal capabiltities.
- Make sure the nor->sfdp pointer is not left dangling.
- Ensure proper garbage collection in the SFDP parsing logic to avoid
  cluttering RAM with useless allocations.
- Fixed typos in commit logs.
- Fixed one (unrelated) extra typo in the sfdp.c file.
- Fixed an out-of-band access on the ID buffer raised by Sashiko.
- Link to v2: https://lore.kernel.org/r/20260731-winbond-v7-1-spi-nor-jv-cleanup-v2-0-2549ef1b1af8 at bootlin.com

Changes in v2:
- Dropped the patches removing the no_sfdp_flags (they do not hurt)
  since this would drop support for !SFDP chips (~until 2016).
- Stopped removing the .names. They are part of the ABI. I will
  try to drop them at runtime when adding the RV chips. At this stage,
  all the existing entries remain.
- Reordered the patches a bit.
- Added a catch all entry in the ID table as advised.
- Added two fixups for non SFDP chips, one removing the CMP bit and the
  other removing the QUAD_PP flag.
- Link to v1: https://lore.kernel.org/r/20260529-winbond-v7-1-spi-nor-jv-cleanup-v1-0-87e5d3122244 at bootlin.com

---
Miquel Raynal (28):
      mtd: spi-nor: Fix spacing in function call
      mtd: spi-nor: Complete error rollback on sfdp parsing failure
      mtd: spi-nor: Garbage collection during sfdp parsing
      mtd: spi-nor: Unconditionally parse SFDP in the deprecated path
      mtd: spi-nor: Init flags before manufacturer late_init() hooks
      mtd: spi-nor: winbond: Move W25Q01NW to its right place
      mtd: spi-nor: winbond: Normalize names
      mtd: spi-nor: winbond: Properly document the chip names
      mtd: spi-nor: winbond: Make the RDCR fixup Winbond wide
      mtd: spi-nor: winbond: W25Q32JV-Q/N: Add quad page program capability
      mtd: spi-nor: winbond: W25Q64JV-Q/N: Add quad page program capability
      mtd: spi-nor: winbond: W25Q128JV-Q/N: Add quad page program capability
      mtd: spi-nor: winbond: W25Q32JV-M: Add quad page program capability
      mtd: spi-nor: winbond: W25Q64JV-M: Add quad page program capability
      mtd: spi-nor: winbond: W25Q128JV-M: Add quad page program capability
      mtd: spi-nor: winbond: W25QxxJV-Q/N/M: Add quad page program capability
      mtd: spi-nor: winbond: W25Q32JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q64JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q128JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q512JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q01JV-Q/N: Fill locking information
      mtd: spi-nor: winbond: W25Q32JV-M: Fill locking information
      mtd: spi-nor: winbond: W25Q64JV-M: Fill locking information
      mtd: spi-nor: winbond: W25Q128JV-M: Fill locking information
      mtd: spi-nor: winbond: W25Q02JV-M: Fill locking information
      mtd: spi-nor: winbond: W25QxxJV-Q/N/M: Fill locking information
      mtd: spi-nor: winbond: W25Q512JV-M: New chip
      mtd: spi-nor: winbond: W25Q01JV-M: New chip

 drivers/mtd/spi-nor/core.c    |  19 +++---
 drivers/mtd/spi-nor/sfdp.c    |  30 ++++++---
 drivers/mtd/spi-nor/winbond.c | 138 +++++++++++++++++++++++++++++-------------
 3 files changed, 127 insertions(+), 60 deletions(-)
---
base-commit: 61e14aacac415b8c7ccbc8a5434b1e316af21aea
change-id: 20260529-winbond-v7-1-spi-nor-jv-cleanup-817889edd1f7

Best regards,
-- 
Miquel Raynal <miquel.raynal at bootlin.com>



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:26 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:26 +0200
Subject: [PATCH v5 01/28] mtd: spi-nor: Fix spacing in function call
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-1-87238e577794@bootlin.com>

Remove the double space.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/sfdp.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index 4600983cb579..6a571d8d822c 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -453,7 +453,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 		    bfpt_header->length * sizeof(u32));
 	addr = SFDP_PARAM_HEADER_PTP(bfpt_header);
 	memset(&bfpt, 0, sizeof(bfpt));
-	err = spi_nor_read_sfdp_dma_unsafe(nor,  addr, len, &bfpt);
+	err = spi_nor_read_sfdp_dma_unsafe(nor, addr, len, &bfpt);
 	if (err < 0)
 		return err;
 

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:27 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:27 +0200
Subject: [PATCH v5 02/28] mtd: spi-nor: Complete error rollback on sfdp
 parsing failure
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-2-87238e577794@bootlin.com>

Make sure to rollback all changes induced by SFDP parsing in case of
failure, which means also resetting nor->flags and nor->cmd_ext_type.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/core.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ccf4396cdcd0..c39c6833c9a4 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -3063,12 +3063,17 @@ static int spi_nor_late_init_params(struct spi_nor *nor)
 static void spi_nor_sfdp_init_params_deprecated(struct spi_nor *nor)
 {
 	struct spi_nor_flash_parameter sfdp_params;
+	enum spi_nor_cmd_ext cmd_ext_type;
+	u32 flags;
 
 	memcpy(&sfdp_params, nor->params, sizeof(sfdp_params));
+	cmd_ext_type = nor->cmd_ext_type;
+	flags = nor->flags;
 
 	if (spi_nor_parse_sfdp(nor)) {
 		memcpy(nor->params, &sfdp_params, sizeof(*nor->params));
-		nor->flags &= ~SNOR_F_4B_OPCODES;
+		nor->cmd_ext_type = cmd_ext_type;
+		nor->flags = flags;
 	}
 }
 

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:28 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:28 +0200
Subject: [PATCH v5 03/28] mtd: spi-nor: Garbage collection during sfdp
 parsing
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-3-87238e577794@bootlin.com>

SFDP parsing works as follows:
1- The header is read then parsed
2- If correct, the sfdp structure and the sfdp content table are
   allocated then filled
3- The mandatory BFPT table is parsed
4- The optional tables are then parsed as well

Any failure in steps 1-3 leads to an error and an early return. The
failure is not always fatal though, since the deprecated will fallback
to the non SFDP definition (in the ID table, for legacy chips). In this
case, the devm_ memory allocations for the SFDP table structure and
table will remain in memory for the lifetime of the device but will
never ever be accessed anymore (since they are incorrect). In this case,
we shall free the memory manually since the device is not going out of
scope anytime soon.

The assignation of nor->sfdp is also cleared in case of failure, to
avoid carrying a dangling pointer upon partial failure. This is
particularly important since the presence of valid SFDP support is
checked using the following condition: `if (!nor->sfdp)`.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---

This change is particularly useful for the next patches which generalize
the fact that SFDP parsing will be attempted on legacy chips not
manually advertizing dual/quad/octal capabilities. I don't think it is
relevant to backport it.
---
 drivers/mtd/spi-nor/sfdp.c | 28 +++++++++++++++++++---------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index 6a571d8d822c..cf0e2dc7c120 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -1490,7 +1490,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 					psize, param_headers);
 		if (err < 0) {
 			dev_dbg(dev, "failed to read SFDP parameter headers\n");
-			goto exit;
+			goto free_param_headers;
 		}
 	}
 
@@ -1518,7 +1518,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 	sfdp = devm_kzalloc(dev, sizeof(*sfdp), GFP_KERNEL);
 	if (!sfdp) {
 		err = -ENOMEM;
-		goto exit;
+		goto free_param_headers;
 	}
 
 	/*
@@ -1532,16 +1532,13 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 				    sizeof(*sfdp->dwords), GFP_KERNEL);
 	if (!sfdp->dwords) {
 		err = -ENOMEM;
-		devm_kfree(dev, sfdp);
-		goto exit;
+		goto free_sfdp;
 	}
 
 	err = spi_nor_read_sfdp(nor, 0, sfdp_size, sfdp->dwords);
 	if (err < 0) {
 		dev_dbg(dev, "failed to read SFDP data\n");
-		devm_kfree(dev, sfdp->dwords);
-		devm_kfree(dev, sfdp);
-		goto exit;
+		goto free_dwords;
 	}
 
 	nor->sfdp = sfdp;
@@ -1563,7 +1560,7 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 
 	err = spi_nor_parse_bfpt(nor, bfpt_header);
 	if (err)
-		goto exit;
+		goto clear_sfdp_ptr;
 
 	/* Parse optional parameter tables. */
 	for (i = 0; i < header.nph; i++) {
@@ -1608,7 +1605,20 @@ int spi_nor_parse_sfdp(struct spi_nor *nor)
 	}
 
 	err = spi_nor_post_sfdp_fixups(nor);
-exit:
+	if (err)
+		goto clear_sfdp_ptr;
+
+	kfree(param_headers);
+
+	return 0;
+
+clear_sfdp_ptr:
+	nor->sfdp = NULL;
+free_dwords:
+	devm_kfree(dev, sfdp->dwords);
+free_sfdp:
+	devm_kfree(dev, sfdp);
+free_param_headers:
 	kfree(param_headers);
 	return err;
 }

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:29 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:29 +0200
Subject: [PATCH v5 04/28] mtd: spi-nor: Unconditionally parse SFDP in the
 deprecated path
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-4-87238e577794@bootlin.com>

As of today, the deprecated initialization path only attempts to parse
the SFDP tables when the flash entry advertised a dual, quad or octal
read capability in ->no_sfdp_flags. This was initially a surprise to me
since the documentation and commit logs I read while searching the
history do not clearly explain why. It seems that this is some kind of
(double) legacy behavior since a change made it clear in 2021 that in
order to skip trying to parse SFDP in the deprecated path, chips should
be marked with SPI_NOR_SKIP_SFDP (in which case the deprecated path
mentioned in the title will not be attempted).

As a result of this situation, Winbond W25Q32JV-Q/N and W25Q64JV-M
chips, which feature good SFDP support, are using only the stale flags
in the ID table because of ID reuse. I tried to remove the stale entries
but this change got refused because it would break support for legacy
chips without SFDP support (until ~2016).

Parsing SFDP unconditionally seems safe:
spi_nor_sfdp_init_params_deprecated() already restores the legacy
parameters whenever parsing fails, so non-SFDP chips simply fall back to
their previous behavior. As stated above, anybody bisecting a SPI NOR
boot issue and getting to this commit should add (and contribute) the
SPI_NOR_SKIP_SFDP flag in their no_sfdp_flags field.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/core.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index c39c6833c9a4..b4d09f14fff7 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -3092,11 +3092,7 @@ static void spi_nor_init_params_deprecated(struct spi_nor *nor)
 
 	spi_nor_manufacturer_init_params(nor);
 
-	if (nor->info->no_sfdp_flags & (SPI_NOR_DUAL_READ |
-					SPI_NOR_QUAD_READ |
-					SPI_NOR_OCTAL_READ |
-					SPI_NOR_OCTAL_DTR_READ))
-		spi_nor_sfdp_init_params_deprecated(nor);
+	spi_nor_sfdp_init_params_deprecated(nor);
 }
 
 /**

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:30 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:30 +0200
Subject: [PATCH v5 05/28] mtd: spi-nor: Init flags before manufacturer
 late_init() hooks
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-5-87238e577794@bootlin.com>

Current init order is:
- The manufacturer-wide late_init() hook is called.
- The core initializes some chip flags
- The chip specific late_init() hook is called.

This does not seem very consistent since both late_init() may need to
touch flags, which is then not working as expected since the order is a
bit inconsistent. Moving spi_nor_init_flags() earlier, right before the
manufacturer late_init() call, ensures the hook sees the flags after
their initialization, and can therefore apply changes to them.

Since spi_nor_init_flags() only reads ->info->flags and DT properties,
it does not depend on anything a late_init() hook produces. On the other
hand, no manufacturer late_init() hook currently touches any output of
spi_nor_init_flags() either. Reordering seems safe?.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/core.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index b4d09f14fff7..fef7f426a3a2 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -3014,6 +3014,9 @@ static int spi_nor_late_init_params(struct spi_nor *nor)
 	struct spi_nor_flash_parameter *params = nor->params;
 	int ret;
 
+	/* Needed by some late_init hooks */
+	spi_nor_init_flags(nor);
+
 	if (nor->manufacturer && nor->manufacturer->fixups &&
 	    nor->manufacturer->fixups->late_init) {
 		ret = nor->manufacturer->fixups->late_init(nor);
@@ -3021,9 +3024,6 @@ static int spi_nor_late_init_params(struct spi_nor *nor)
 			return ret;
 	}
 
-	/* Needed by some flashes late_init hooks. */
-	spi_nor_init_flags(nor);
-
 	if (nor->info->fixups && nor->info->fixups->late_init) {
 		ret = nor->info->fixups->late_init(nor);
 		if (ret)

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:31 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:31 +0200
Subject: [PATCH v5 06/28] mtd: spi-nor: winbond: Move W25Q01NW to its right
 place
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-6-87238e577794@bootlin.com>

Chips are sorted by families, and inside the families, by density. The
second ID byte indicates the family, the third indicates the
density. Move W25Q01NW to the right place.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
Reviewed-by: Michael Walle <mwalle at kernel.org>
---
 drivers/mtd/spi-nor/winbond.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 8ebdbcec0b3f..662a022d36e7 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -300,6 +300,12 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x60, 0x20),
 		.name = "w25q512nwq",
 		.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 | SPI_NOR_HAS_CMP,
+		.fixups = &winbond_rdcr_fixup,
 	}, {
 		.id = SNOR_ID(0xef, 0x70, 0x15),
 		.name = "w25q16jv-im/jm",
@@ -365,12 +371,6 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x80, 0x20),
 		.name = "w25q512nwm",
 		.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 | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_rdcr_fixup,
 	}, {
 		/* W25Q01NWxxIM */
 		.id = SNOR_ID(0xef, 0x80, 0x21),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:32 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:32 +0200
Subject: [PATCH v5 07/28] mtd: spi-nor: winbond: Normalize names
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-7-87238e577794@bootlin.com>

Winbond chips cannot just be named eg. "W25Q02JV". There are always more
letters after that name, some of them matter.

- W is the Winbond prefix
- 25Q is the type of device
  -> 25Q indicates a quad capable SPI NOR
  -> 25H indicates an automotive grade chip
- 02J is the density
  -> in this case 2Gib
- V is the voltage
  -> V means 3.3V
  -> W means 1.8V
- The next letters (either 2 or 3 letters) are completely irrelevant for
  the software and purely indicate the hardware package.
- The penultimate letter is the temperature grade
  -> I for Industrial grade
  -> J means "above" industrial
- Q, N or M indicate factory states (like QE=1) and also the DTR
  capability.
  -> Q and N typically have the same IDs
  -> M chips typically have a different ID

As a result, W25Q01NWxxIQ is irrelevant and would better be named
W25Q01NW-Q, W25Q02JV is irrelevant and would better be named W25Q02JV-M,
etc.

Make that list uniform before adding more IDs/names.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
Reviewed-by: Michael Walle <mwalle at kernel.org>
---
 drivers/mtd/spi-nor/winbond.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 662a022d36e7..03e59ff3228f 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -301,7 +301,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q512nwq",
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
 	}, {
-		/* W25Q01NWxxIQ */
+		/* W25Q01NW-Q */
 		.id = SNOR_ID(0xef, 0x60, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
@@ -334,7 +334,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x19),
 		.name = "w25q256jvm",
 	}, {
-		/* W25Q02JV */
+		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),
 		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
@@ -372,28 +372,28 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q512nwm",
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
 	}, {
-		/* W25Q01NWxxIM */
+		/* W25Q01NW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q02NWxxIM */
+		/* W25Q02NW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &winbond_rdcr_fixup,
 	}, {
-		/* W25H512NWxxAM */
+		/* W25H512NW-M */
 		.id = SNOR_ID(0xef, 0xa0, 0x20),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25H01NWxxAM */
+		/* W25H01NW-M */
 		.id = SNOR_ID(0xef, 0xa0, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25H02NWxxAM */
+		/* W25H02NW-M */
 		.id = SNOR_ID(0xef, 0xa0, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:33 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:33 +0200
Subject: [PATCH v5 08/28] mtd: spi-nor: winbond: Properly document the chip
 names
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-8-87238e577794@bootlin.com>

While the .name entry is a legacy field, is highly imprecise, and even
wrong sometimes due to ID reuse, it is part of the sysfs ABI, we cannot
remove it for existing chips. However, we can double the entry with the
now usual comment with the newly instated naming scheme to indicate what
chips are covered by each entry.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
I've been focusing on several chip families, but not all of them. I do
not have enough information about the oldest chips at the moment to
cleanup *all* IDs, but I am nevertheless trying to make a major cleaning
step here.
---
 drivers/mtd/spi-nor/winbond.c | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 03e59ff3228f..3e8e89058b8c 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -218,16 +218,19 @@ static const struct flash_info winbond_nor_parts[] = {
 		.size = SZ_1M,
 		.no_sfdp_flags = SECT_4K,
 	}, {
+		/* W25Q32JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x16),
 		.name = "w25q32",
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K,
 	}, {
+		/* W25Q64JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x17),
 		.name = "w25q64",
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q128JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x18),
 		/* Flavors w/ and w/o SFDP. */
 		.name = "w25q128",
@@ -236,18 +239,20 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.fixups = &w25q128_fixups,
 	}, {
+		/* W25Q256JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x19),
 		.name = "w25q256",
 		.size = SZ_32M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.fixups = &w25q256_fixups,
 	}, {
+		/* W25Q512JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x20),
 		.name = "w25q512jvq",
 		.size = SZ_64M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
-		/* W25Q01JV */
+		/* W25Q01JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x21),
 		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
@@ -285,52 +290,60 @@ static const struct flash_info winbond_nor_parts[] = {
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q128FW-G/Q, W25Q128JW-Q/N */
 		.id = SNOR_ID(0xef, 0x60, 0x18),
 		.name = "w25q128fw",
 		.size = SZ_16M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q256JW-Q/N */
 		.id = SNOR_ID(0xef, 0x60, 0x19),
 		.name = "w25q256jw",
 		.size = SZ_32M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q512NW-Q/N */
 		.id = SNOR_ID(0xef, 0x60, 0x20),
 		.name = "w25q512nwq",
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
 	}, {
-		/* W25Q01NW-Q */
+		/* W25Q01NW-Q/N */
 		.id = SNOR_ID(0xef, 0x60, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &winbond_rdcr_fixup,
 	}, {
+		/* W25Q16JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x15),
 		.name = "w25q16jv-im/jm",
 		.size = SZ_2M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q32JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x16),
 		.name = "w25q32jv",
 		.size = SZ_4M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q64JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x17),
 		.name = "w25q64jvm",
 		.size = SZ_8M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K,
 	}, {
+		/* W25Q128JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x18),
 		.name = "w25q128jv",
 		.size = SZ_16M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),
 		.name = "w25q256jvm",
 	}, {
@@ -343,6 +356,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.size = SZ_64M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q32JW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x16),
 		.name = "w25q32jwm",
 		.size = SZ_4M,
@@ -350,24 +364,28 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
 	}, {
+		/* W25Q64JW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x17),
 		.name = "w25q64jwm",
 		.size = SZ_8M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q128JW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x18),
 		.name = "w25q128jwm",
 		.size = SZ_16M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q256JW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x19),
 		.name = "w25q256jwm",
 		.size = SZ_32M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 	}, {
+		/* W25Q512NW-M */
 		.id = SNOR_ID(0xef, 0x80, 0x20),
 		.name = "w25q512nwm",
 		.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:34 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:34 +0200
Subject: [PATCH v5 09/28] mtd: spi-nor: winbond: Make the RDCR fixup
 Winbond wide
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-9-87238e577794@bootlin.com>

The top level paragraph of the QER field in the JESD216B mentions:

"In this standard, [...]  Status register 2 refers to the byte read
using instruction 35h. Status register 2 is the second byte transferred
in a Write Status (01h) command. [...]"

Value 100b, named in Linux BFPT_DWORD15_QER_SR2_BIT1_NO_RD, does not
mention anything about reads and only brings details about writes.

This has been interpreted in the spi-nor core by the absence of read
capability, but there is no explicit reason for that, except that there
were probably some very old chips which didn't support command 35h.

All quad capable Winbond chips carry a CMP SWP bit in SR2. SR2 is
readable with command 35h. In practice, all Winbond families but the
W25X family have support for this feature, so re-enable it Winbond-wide
in a late vendor fixup, except for the {EF, 30, xx} family.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 44 ++++++++++++++++++++-----------------------
 1 file changed, 20 insertions(+), 24 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 3e8e89058b8c..04713ab04d01 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -73,26 +73,6 @@ static const struct spi_nor_fixups w25q256_fixups = {
 	.post_bfpt = w25q256_post_bfpt_fixups,
 };
 
-static int
-winbond_rdcr_post_bfpt_fixup(struct spi_nor *nor,
-			     const struct sfdp_parameter_header *bfpt_header,
-			     const struct sfdp_bfpt *bfpt)
-{
-	/*
-	 * W25H02NW, unlike its W25H512NW nor W25H01NW cousins, improperly sets
-	 * the QE BFPT configuration bits, indicating a non readable CR. This is
-	 * both incorrect and impractical, as the chip features a CMP bit for its
-	 * locking scheme that lays in the Control Register, and needs to be read.
-	 */
-	nor->flags &= ~SNOR_F_NO_READ_CR;
-
-	return 0;
-}
-
-static const struct spi_nor_fixups winbond_rdcr_fixup = {
-	.post_bfpt = winbond_rdcr_post_bfpt_fixup,
-};
-
 /**
  * winbond_nor_select_die() - Set active die.
  * @nor:	pointer to 'struct spi_nor'.
@@ -313,7 +293,6 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x60, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_rdcr_fixup,
 	}, {
 		/* W25Q16JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x15),
@@ -399,7 +378,6 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x80, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_rdcr_fixup,
 	}, {
 		/* W25H512NW-M */
 		.id = SNOR_ID(0xef, 0xa0, 0x20),
@@ -415,8 +393,13 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0xa0, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_rdcr_fixup,
-	},
+	}, {
+		/*
+		 * Catch all entry to make sure all chips solely relying
+		 * on SFDP still go through the manufacturer hooks.
+		 */
+		.id = SNOR_ID(0xef),
+	}
 };
 
 /**
@@ -508,6 +491,19 @@ static int winbond_nor_late_init(struct spi_nor *nor)
 	 */
 	params->set_4byte_addr_mode = winbond_nor_set_4byte_addr_mode;
 
+	/*
+	 * All W25Q/W25H chips do set the BFPT_DWORD15_QER_SR2_BIT1_NO_RD bit in
+	 * their SFDP tables. The historical spi-nor assumption in this case has
+	 * been to declare CR reads as unsupported, whereas the Jedec
+	 * specification doesn't clearly state that. In practice, all these
+	 * chips do support reading back the CR, which is needed for SWP support,
+	 * so make sure that capability remains enabled (needed for SWP).
+	 * In practice, only exclude the old W25X family (JEDEC ID: EF 30 xx)
+	 * which actually does not support this feature.
+	 */
+	if (nor->id[1] > 0x30)
+		nor->flags &= ~SNOR_F_NO_READ_CR;
+
 	return 0;
 }
 

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:35 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:35 +0200
Subject: [PATCH v5 10/28] mtd: spi-nor: winbond: W25Q32JV-Q/N: Add quad
 page program capability
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-10-87238e577794@bootlin.com>

The benefit is not massive @25MHz, yet it is a supported feature
of the chip which is already handled by a flag, so let's enable it and
earn that little 1% write throughput.

Before:
$ flash_speed /dev/mtd0 -d -c10
eraseblock write speed is 560 KiB/s
page write speed is 540 KiB/s
2 page write speed is 550 KiB/s

After:
eraseblock write speed is 578 KiB/s
page write speed is 557 KiB/s
2 page write speed is 567 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook is added to make sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x03
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3b
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbb
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6b
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x02
 1S-1S-4S
  opcode	0x32

$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
$ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
83e7d587bce3b2031c82939839cc1fbcf62b8939b35882d176f207b0722d0dba  spi_read
83e7d587bce3b2031c82939839cc1fbcf62b8939b35882d176f207b0722d0dba  spi_test
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
83e7d587bce3b2031c82939839cc1fbcf62b8939b35882d176f207b0722d0dba  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 04713ab04d01..2e53f16a9f93 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -203,6 +203,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q32",
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K,
+		.flags = SPI_NOR_QUAD_PP,
 	}, {
 		/* W25Q64JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x17),
@@ -504,6 +505,22 @@ static int winbond_nor_late_init(struct spi_nor *nor)
 	if (nor->id[1] > 0x30)
 		nor->flags &= ~SNOR_F_NO_READ_CR;
 
+	/*
+	 * Winbond has reused many IDs, up to four times at this
+	 * stage. In general, most of the chips with SFDP support are
+	 * correctly described by the ID table, but the non-SFDP chips,
+	 * however, are known to not feature as many capabilities. Make
+	 * sure we filter out those capabilities to keep backward
+	 * compatibility with these devices manufactured until ~2016.
+	 */
+	if (!nor->sfdp) {
+		struct spi_nor_flash_parameter *p = nor->params;
+
+		/* SPI_NOR_QUAD_PP was unsupported */
+		p->hwcaps.mask &= ~SNOR_HWCAPS_PP_1_1_4;
+		spi_nor_set_pp_settings(&p->page_programs[SNOR_CMD_PP_1_1_4], 0, 0);
+	}
+
 	return 0;
 }
 

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:36 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:36 +0200
Subject: [PATCH v5 11/28] mtd: spi-nor: winbond: W25Q64JV-Q/N: Add quad
 page program capability
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-11-87238e577794@bootlin.com>

The benefit is not massive @25MHz, yet it is a supported feature
of the chip which is already handled by a flag, so let's enable it and
earn that little 1% write throughput.

Before:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 539 KiB/s
page write speed is 535 KiB/s
2 page write speed is 536 KiB/s

After:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 556 KiB/s
page write speed is 549 KiB/s
2 page write speed is 552 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 17 00 00 00
size		8.00 MiB
write size	1
page size	256
address nbytes	3
flags		HAS_16BIT_SR | SOFT_RESET | NO_WP

opcodes
 read		0xeb
  dummy cycles	6
 erase		0xd8
 program	0x32
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 20 (4.00 KiB) [1]
 52 (32.0 KiB) [2]
 d8 (64.0 KiB) [3]
 c7 (8.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-007fffff |     [   3] | no
$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug erase /dev/mtd0 0 2097152
mtd_debug read /dev/mtd0 0 2097152 spi_readErased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
$ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
36667197a637b731cc2068022138e6963a9499ce9b0ddda1a1585f7f5ebe66ff  spi_read
36667197a637b731cc2068022138e6963a9499ce9b0ddda1a1585f7f5ebe66ff  spi_test
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
36667197a637b731cc2068022138e6963a9499ce9b0ddda1a1585f7f5ebe66ff  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 2e53f16a9f93..15df3cc26fbc 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -210,6 +210,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q64",
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP,
 	}, {
 		/* W25Q128JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x18),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:37 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:37 +0200
Subject: [PATCH v5 12/28] mtd: spi-nor: winbond: W25Q128JV-Q/N: Add quad
 page program capability
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-12-87238e577794@bootlin.com>

The benefit is not massive @25MHz, yet this feature is not advertized
because these chips do not implement the 4BAIT table. Flag the quad page
program capability manually and earn a few % of write throughput.

Before:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 536 KiB/s
page write speed is 532 KiB/s
2 page write speed is 532 KiB/s

After:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 552 KiB/s
page write speed is 547 KiB/s
2 page write speed is 549 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
c2712b5617a588f8bba4b8fc7af62f6ca5e2356577681b23544d1a93540ecd1d  spi_read
c2712b5617a588f8bba4b8fc7af62f6ca5e2356577681b23544d1a93540ecd1d  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
c2712b5617a588f8bba4b8fc7af62f6ca5e2356577681b23544d1a93540ecd1d  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 15df3cc26fbc..f7cdbf1a56a5 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -217,7 +217,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		/* Flavors w/ and w/o SFDP. */
 		.name = "w25q128",
 		.size = SZ_16M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.fixups = &w25q128_fixups,
 	}, {

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:38 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:38 +0200
Subject: [PATCH v5 13/28] mtd: spi-nor: winbond: W25Q32JV-M: Add quad page
 program capability
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-13-87238e577794@bootlin.com>

This chip does not advertise it correctly but it supports 1-1-4 page
programs.

Before:

$ flash_speed /dev/mtd0 -d -c10
eraseblock write speed is 2612 KiB/s
page write speed is 2509 KiB/s
2 page write speed is 2560 KiB/s

After:

$ flash_speed /dev/mtd0 -c10 -d
eraseblock write speed is 3033 KiB/s
page write speed is 2895 KiB/s
2 page write speed is 2976 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index f7cdbf1a56a5..456050fdd251 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -307,8 +307,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x16),
 		.name = "w25q32jv",
 		.size = SZ_4M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 	}, {
 		/* W25Q64JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x17),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:39 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:39 +0200
Subject: [PATCH v5 14/28] mtd: spi-nor: winbond: W25Q64JV-M: Add quad page
 program capability
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-14-87238e577794@bootlin.com>

The benefit is massive @25MHz, but the chip does not implement the 4BAIT
table to advertize this capability, so enable it manually:.

Before:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 558 KiB/s
page write speed is 553 KiB/s
2 page write speed is 555 KiB/s

After:
$ flash_speed /dev/mtd0 -dc10
eraseblock write speed is 3047 KiB/s
page write speed is 2909 KiB/s
2 page write speed is 2976 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 17 00 00 00
size		8.00 MiB
write size	1
page size	256
address nbytes	3
flags		HAS_SR_TB | HAS_LOCK | HAS_16BIT_SR | SOFT_RESET | NO_WP

opcodes
 read		0xeb
  dummy cycles	6
 erase		0xd8
 program	0x32
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 20 (4.00 KiB) [1]
 52 (32.0 KiB) [2]
 d8 (64.0 KiB) [3]
 c7 (8.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-007fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
$ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x03
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3b
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbb
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6b
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x02
 1S-1S-4S
  opcode	0x32

$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
$ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
d79c3f7b40646d437eff15309c5250e8ee203f805b9d13d8ad8a7019f0dd69f8  spi_read
d79c3f7b40646d437eff15309c5250e8ee203f805b9d13d8ad8a7019f0dd69f8  spi_test
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
d79c3f7b40646d437eff15309c5250e8ee203f805b9d13d8ad8a7019f0dd69f8  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 456050fdd251..41c9b620f26b 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -314,8 +314,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x17),
 		.name = "w25q64jvm",
 		.size = SZ_8M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 	}, {
 		/* W25Q128JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x18),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:40 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:40 +0200
Subject: [PATCH v5 15/28] mtd: spi-nor: winbond: W25Q128JV-M: Add quad page
 program capability
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-15-87238e577794@bootlin.com>

This chip does not advertise it correctly but it supports 1-1-4 page
programs, add the flag manually:

Before:

$ flash_speed /dev/mtd0 -d -c10
eraseblock write speed is 553 KiB/s
page write speed is 548 KiB/s
2 page write speed is 550 KiB/s

After:

$ flash_speed /dev/mtd0 -c10 -d
eraseblock write speed is 1628 KiB/s
page write speed is 1600 KiB/s
2 page write speed is 1612 KiB/s

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the capability remains
disabled for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 41c9b620f26b..2cdb4acd9ecf 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -321,8 +321,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x18),
 		.name = "w25q128jv",
 		.size = SZ_16M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 	}, {
 		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:41 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:41 +0200
Subject: [PATCH v5 16/28] mtd: spi-nor: winbond: W25QxxJV-Q/N/M: Add quad
 page program capability
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-16-87238e577794@bootlin.com>

I do not have any W25Q256JV-Q/N/M nor any W25Q16JV-M chips in hand, but
all the chips with a density <= 256Mib are older revisions of the JV
chips which do not yet implement the 4BAIT extension table, as such,
they cannot advertise their quad page program capability. We can
reasonably expect that these chips follow the exact same rules, so add
the missing flag.

The QUAD_PP capability was historically not supported by non-SFDP chips,
so a Winbond wide late hook already makes sure the bit remains disabled
for them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 2cdb4acd9ecf..b250a43ad4e1 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -226,6 +226,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q256",
 		.size = SZ_32M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP,
 		.fixups = &w25q256_fixups,
 	}, {
 		/* W25Q512JV-Q/N */
@@ -300,8 +301,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x15),
 		.name = "w25q16jv-im/jm",
 		.size = SZ_2M,
-		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 	}, {
 		/* W25Q32JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x16),
@@ -327,6 +328,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),
 		.name = "w25q256jvm",
+		.flags = SPI_NOR_QUAD_PP,
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:42 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:42 +0200
Subject: [PATCH v5 17/28] mtd: spi-nor: winbond: W25Q32JV-Q/N: Fill locking
 information
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-17-87238e577794@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
let's make sure the bit remains disabled for them, for backward
compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ alias show_sectors='grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0
/params'
$ flash_lock -u /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: unlocked
Return code: 0
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_read
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_test
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff | unlocked | 64
$ flash_lock -l /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: locked
Return code: 1
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_read
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_test
$ dd if=/dev/urandom of=./spi_test2 bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
$ sha256sum spi*
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_read
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_read2
6d7c114421890dfa12e1824aa351d4490815a9c0ceb9c2e7981073106a11fc71  spi_test
5b69b16c88c3ae48213c359d2f9fd7f0583e513dabb224b3404bc93a5e173253  spi_test2
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff |   locked | 64
$ flash_lock -u /dev/mtd0
$ size=$(cat /sys/class/mtd/mtd0/size)
$ nblocks=$(grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params | sed -e 's/.*
unlocked | //')
$
$ bs=$(($size / $nblocks))
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2 * $bs))) 2 # last two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff | unlocked | 62
 003e0000-003fffff |   locked | 2
$ flash_lock -u /dev/mtd0 $(($size - (2 * $bs))) 1 # last one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff | unlocked | 63
 003f0000-003fffff |   locked | 1
$ flash_lock -u /dev/mtd0
$ all_but_one=$((($size / $bs) - 1))
$ flash_lock -l /dev/mtd0 $bs $all_but_one # all but the first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-003fffff |   locked | 63
$ flash_lock -u /dev/mtd0 $bs 1 # all but the two first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-003fffff |   locked | 62
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $all_but_one # same from the other side
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff |   locked | 63
 003f0000-003fffff | unlocked | 1
$ flash_lock -u /dev/mtd0 $(($size - (2 * $bs))) 1 # all but two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff |   locked | 62
 003e0000-003fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index b250a43ad4e1..64bf98ffc25b 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -203,7 +203,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q32",
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K,
-		.flags = SPI_NOR_QUAD_PP,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q64JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x17),
@@ -522,6 +522,9 @@ static int winbond_nor_late_init(struct spi_nor *nor)
 		/* SPI_NOR_QUAD_PP was unsupported */
 		p->hwcaps.mask &= ~SNOR_HWCAPS_PP_1_1_4;
 		spi_nor_set_pp_settings(&p->page_programs[SNOR_CMD_PP_1_1_4], 0, 0);
+
+		/* SPI_NOR_HAS_CMP was unsupported */
+		nor->flags &= ~SNOR_F_HAS_SR2_CMP_BIT6;
 	}
 
 	return 0;

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:43 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:43 +0200
Subject: [PATCH v5 18/28] mtd: spi-nor: winbond: W25Q64JV-Q/N: Fill locking
 information
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-18-87238e577794@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_read
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_read
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_read
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_read2
a4ad2c0352f71d61f64ed9cabb705edc84ac1f1be079619550cd18c70db707ce  spi_test
b12f8258bf0374eb5c55e659fc430df7a6016d3c0dca2dc04ef1fe82f61d7f67  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=8388608
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=131072
+ bps=2
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 8126464 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff | unlocked | 62
 007c0000-007fffff |   locked | 2
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff | unlocked | 63
 007e0000-007fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 -8388608 256
flash_lock: invalid option -- '8'
Utility to lock, unlock, or check the lock status of the flash.
Default action: lock

Usage: flash_lock [options] [--] <mtd device> [offset [block count]]

Options:
 -h         --help              Display this help and exit
 -V         --version           Display version information and exit
 -i         --islocked          Check if flash region is locked
 -l         --lock              Lock a region of flash
 -u         --unlock            Unlock a region of flash

 <mtd device>  MTD device node or 'mtd:<name>'

If offset is not specified, it defaults to 0.
If block count is not specified, it defaults to all blocks.
A block count of -1 means all blocks.
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 2
 00040000-007fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 1
 00020000-007fffff | unlocked | 63
+ all_but_one=126
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 131072 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 1
 00020000-007fffff |   locked | 63
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 2
 00040000-007fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff |   locked | 63
 007e0000-007fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff |   locked | 62
 007c0000-007fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 64bf98ffc25b..8fab520627bf 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -210,7 +210,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q64",
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q128JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x18),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:44 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:44 +0200
Subject: [PATCH v5 19/28] mtd: spi-nor: winbond: W25Q128JV-Q/N: Fill
 locking information
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-19-87238e577794@bootlin.com>

The SFDP table does not advertize the CMP locking capabilities of the
device, flag it.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $ss $all_but_one # all but the first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 1
 00040000-00ffffff |   locked | 63
$ flash_lock -u /dev/mtd0 $ss $(($ss / $bs)) # all but the two first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0007ffff | unlocked | 2
 00080000-00ffffff |   locked | 62
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $all_but_one # same from the other side
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00fbffff |   locked | 63
 00fc0000-00ffffff | unlocked | 1
$ flash_lock -u /dev/mtd0 $(($size - (2 * $ss))) $(($ss / $bs)) # all but two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00f7ffff |   locked | 62
 00f80000-00ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 8fab520627bf..5a80de959768 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -217,8 +217,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		/* Flavors w/ and w/o SFDP. */
 		.name = "w25q128",
 		.size = SZ_16M,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 		.fixups = &w25q128_fixups,
 	}, {
 		/* W25Q256JV-Q/N */

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:48 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:48 +0200
Subject: [PATCH v5 23/28] mtd: spi-nor: winbond: W25Q64JV-M: Fill locking
 information
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-23-87238e577794@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_read
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_read
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_read
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_read2
f358338956920defcf811679166ade6f25eeee00abe29dda49932536591fb4d1  spi_test
6ba881af6f5901f7ca55322d45a7cee2ec69900cfdfc348141c3c5b1e51f676b  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=8388608
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=131072
+ bps=2
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 8126464 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff | unlocked | 62
 007c0000-007fffff |   locked | 2
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff | unlocked | 63
 007e0000-007fffff |   locked | 1
/* Skipping BP3 test */
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 2
 00040000-007fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 1
 00020000-007fffff | unlocked | 63
+ all_but_one=126
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 131072 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 1
 00020000-007fffff |   locked | 63
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 2
 00040000-007fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff |   locked | 63
 007e0000-007fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff |   locked | 62
 007c0000-007fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 3f9f2257ac91..41b8733c4c85 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -320,7 +320,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q64jvm",
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q128JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x18),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:46 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:46 +0200
Subject: [PATCH v5 21/28] mtd: spi-nor: winbond: W25Q01JV-Q/N: Fill locking
 information
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-21-87238e577794@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
$ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef4021
$ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
53464450060101ff00060110800000ff84000102d00000ff03000102f000
00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff3f44eb086b083b42bbfeffffffffff
0000ffff40eb0c200f5210d800003602a60082ea14e2e96376337a757a75
f7a2d55c19f74dffe970f9a5ffffffffffffffffffffffffffffffffff0a
f0ff21ffdcff
$ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
c733cf105c5f05ed23ad3a32c2a72c1a09d4b895c628db52e645a8a457dfc7d6  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
$ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/
jedec_id      manufacturer  sfdp
$ sha256sum /sys/kernel/debug/spi-nor/spi0.0/capabilities
6204c70963ab07f9865d051b888679ce34ad3a9f167fbbd4a2a6e0c3d0ce4cdf  /sys/kernel/debug/spi-nor/spi0.0/capabilities
$ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34

$ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 21 00 00 00
size		128 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_16BIT_SR | NO_READ_CR | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (128 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-07ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048

$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ flash_lock -u /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: unlocked
Return code: 0
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_read
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_test
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048
$ flash_lock -l /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: locked
Return code: 1
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_read
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_test
$ dd if=/dev/urandom of=./spi_test2 bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
$ sha256sum spi*
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_read
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_read2
3fa6e836be5a072bd2d4ae538559dce20b14c5827ccc0cf6df783c0c35a4a83f  spi_test
367809dbdff878d023d3a1a8526a6382759a3ec78aa6289d18577d15f013d44f  spi_test2
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff |   locked | 2048
$ flash_lock -u /dev/mtd0
$ nsectors=$(grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params | sed -e 's/.
*unlocked | //')
$ ss=$(($size / $nsectors))
$ bps=$(($ss / $bs))
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2 * $ss))) $((2 * $bps)) # last two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff | unlocked | 2046
 07fe0000-07ffffff |   locked | 2
$ flash_lock -u /dev/mtd0 $(($size - (2 * $ss))) $((1 * $bps)) # last one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff | unlocked | 2047
 07ff0000-07ffffff |   locked | 1
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2**7 * $ss))) $((2**7 * $bps))
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-077fffff | unlocked | 1920
 07800000-07ffffff |   locked | 128
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $((2 * $bps)) # first two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-07ffffff | unlocked | 2046
$ flash_lock -u /dev/mtd0 $ss $((1 * $bps)) # first one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-07ffffff | unlocked | 2047
$ flash_lock -l /dev/mtd0 $ss $all_but_one # all but the first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-07ffffff |   locked | 2047
$ flash_lock -u /dev/mtd0 $ss $(($ss / $bs)) # all but the two first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-07ffffff |   locked | 2046
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $all_but_one # same from the other side
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff |   locked | 2047
 07ff0000-07ffffff | unlocked | 1
$ flash_lock -u /dev/mtd0 $(($size - (2 * $ss))) $(($ss / $bs)) # all but two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff |   locked | 2046
 07fe0000-07ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 1ac0177aeb44..59d5be14a73c 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -239,6 +239,8 @@ static const struct flash_info winbond_nor_parts[] = {
 	}, {
 		/* W25Q01JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x21),
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
 		.id = SNOR_ID(0xef, 0x50, 0x12),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:50 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:50 +0200
Subject: [PATCH v5 25/28] mtd: spi-nor: winbond: W25Q02JV-M: Fill locking
 information
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-25-87238e577794@bootlin.com>

The SFDP table is pretty complete except for the locking capabilities
which must be filled manually.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x10000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_read
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff | unlocked | 4096
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x10000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_read
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_read
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_read2
8370dfccafceea1f6961d138d3412ce61dca4c7826d4775d3562acef2b10e3c6  spi_test
e61267bbd0864dfe7e67a8bfc0875a23308d98c7297fcf5a813830532f48c06f  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff |   locked | 4096
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=268435456
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=4096
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 268304384 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffdffff | unlocked | 4094
 0ffe0000-0fffffff |   locked | 2
+ flash_lockk uu /dev/mtd0 268304384 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffeffff | unlocked | 4095
 0fff0000-0fffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 260046848 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0f7fffff | unlocked | 3968
 0f800000-0fffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-0fffffff | unlocked | 4094
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-0fffffff | unlocked | 4095
+ all_but_one=4095
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 4095
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-0fffffff |   locked | 4095
+ flassh__llock u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-0fffffff |   locked | 4094
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4095
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffeffff |   locked | 4095
 0fff0000-0fffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 268304384 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffdffff |   locked | 4094
 0ffe0000-0fffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 4f26191e1d6d..5ab7f4860295 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -336,6 +336,8 @@ static const struct flash_info winbond_nor_parts[] = {
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
 		.id = SNOR_ID(0xef, 0x71, 0x19),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:51 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:51 +0200
Subject: [PATCH v5 26/28] mtd: spi-nor: winbond: W25QxxJV-Q/N/M: Fill
 locking information
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-26-87238e577794@bootlin.com>

I do not have any W25Q256JV-Q/N/M nor any W25Q16JV-M chips in hand, but
all the other chips from this family have the same locking pattern, so
we can reasonably expect that these chips follow the exact same rules.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 5ab7f4860295..6b21fed74f4e 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -226,7 +226,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q256",
 		.size = SZ_32M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
+			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &w25q256_fixups,
 	}, {
 		/* W25Q512JV-Q/N */
@@ -306,7 +307,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q16jv-im/jm",
 		.size = SZ_2M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q32JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x16),
@@ -332,7 +333,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),
 		.name = "w25q256jvm",
-		.flags = SPI_NOR_QUAD_PP,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
+			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:47 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:47 +0200
Subject: [PATCH v5 22/28] mtd: spi-nor: winbond: W25Q32JV-M: Fill locking
 information
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-22-87238e577794@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag the missing CMP capability.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   34.621251] random: crng init done
2+0 records in
2+0 records out
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_read
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_read
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_read
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_read2
a3d9042b2e8085f39ebdefbabee5d3bc8baed5217f2a51510bd523db1b147395  spi_test
bcf94096d25ce535e4b920f876d53420e6d8d917d0d14dd766b5f8c3d6f40475  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=4194304
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 4063232 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff | unlocked | 62
 003e0000-003fffff |   locked | 2
+ flash_lock -u /dev/mtd0 4063232 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff | unlocked | 63
 003f0000-003fffff |   locked | 1
/* Skipping BP3 test */
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-003fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-003fffff | unlocked | 63
+ all_but_one=63
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 63
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-003fffff |   locked | 63
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-003fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 63
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff |   locked | 63
 003f0000-003fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 4063232 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff |   locked | 62
 003e0000-003fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 59d5be14a73c..3f9f2257ac91 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -313,7 +313,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q32jv",
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q64JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x17),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:52 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:52 +0200
Subject: [PATCH v5 27/28] mtd: spi-nor: winbond: W25Q512JV-M: New chip
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-27-87238e577794@bootlin.com>

Add support for W25Q512JV-M. It is a 3.3V chip with usual dual/quad
capabilities.

The SFDP table is pretty complete except for the locking capabilities
which must be filled manually.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/partname
cat: can't open '/sys/bus/spi/devices/spi0.0/spi-nor/partname': No such file or directory
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7020
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
53464450060101ff00060110800000ff84000102d00000ff03000102f000
00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff1f44eb086b083b42bbfeffffffffff
0000ffff40eb0c200f5210d800003602a60082ea14e2e96376337a757a75
f7a2d55c19f74dffe970f9a5ffffffffffffffffffffffffffffffffff0a
f0ff21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
f41674a6c3742a42d31d3007d4d4b725c166ddb4e4566d1c347d29ed29855ef0  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 20 00 00 00
size		64.0 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_16BIT_SR | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (64.0 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-03ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   53.198369] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 67108864 (64M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_read2
f27c6b54ce92a46104f2fd43fc2d6d1353099a6a125591b47a615d3d61171a86  spi_test
44db7586d798437868d29ea31ac1fa1e64d1b81337721d0d811776dad47110f3  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff |   locked | 1024
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=67108864
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=1024
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 66977792 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff | unlocked | 1022
 03fe0000-03ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff | unlocked | 1023
 03ff0000-03ffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 58720256 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-037fffff | unlocked | 896
 03800000-03ffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-03ffffff | unlocked | 1022
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-03ffffff | unlocked | 1023
+ all_but_one=1023
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-03ffffff |   locked | 1023
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-03ffffff |   locked | 1022
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff |   locked | 1023
 03ff0000-03ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff |   locked | 1022
 03fe0000-03ffffff | unlocked | 2
+ flash_speed /dev/mtd0 -dc10
not NAND flash, assume page size is 512 bytes.
scanning for bad eraseblocks
scanned 10 eraseblocks, 0 are bad
testing eraseblock write speed
eraseblock write speed is 437 KiB/s
testing eraseblock read speed
eraseblock read speed is 1560 KiB/s
testing page write speed
page write speed is 434 KiB/s
testing page read speed
page read speed is 1457 KiB/s
testing 2 page write speed
2 page write speed is 435 KiB/s
testing 2 page read speed
2 page read speed is 1498 KiB/s
Testing erase speed
erase speed is 257 KiB/s
Testing 2x multi-block erase speed
2x multi-block erase speed is 257 KiB/s
Testing 4x multi-block erase speed
4x multi-block erase speed is 257 KiB/s
Testing 8x multi-block erase speed
8x multi-block erase speed is 254 KiB/s
Testing 16x multi-block erase speed
16x multi-block erase speed is 255 KiB/s
Testing 32x multi-block erase speed
32x multi-block erase speed is 255 KiB/s
Testing 64x multi-block erase speed
64x multi-block erase speed is 253 KiB/s
finished
---
 drivers/mtd/spi-nor/winbond.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 6b21fed74f4e..980c437f1951 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -335,6 +335,11 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q256jvm",
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
 			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+	}, {
+		/* W25Q512JV-M */
+		.id = SNOR_ID(0xef, 0x70, 0x20),
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:53 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:53 +0200
Subject: [PATCH v5 28/28] mtd: spi-nor: winbond: W25Q01JV-M: New chip
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-28-87238e577794@bootlin.com>

Add support for W25Q01JV-M. It is a 3.3V chip with usual dual/quad
capabilities.

The SFDP table is pretty complete except for the locking capabilities
which must be filled manually.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7021
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
53464450060101ff00060110800000ff84000102d00000ff03000102f000
00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff3f44eb086b083b42bbfeffffffffff
0000ffff40eb0c200f5210d800003602a60082ea14e2e96376337a757a75
f7a2d55c19f74dffe970f9a5ffffffffffffffffffffffffffffffffff0a
f0ff21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
c733cf105c5f05ed23ad3a32c2a72c1a09d4b895c628db52e645a8a457dfc7d6  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	0

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 21 00 00 00
size		128 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_16BIT_SR | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	none

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (128 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-07ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 134217728 (128M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_read2
7ccac32f903edeaed4e7c6355a91c2e6dfc95dd007069c9f22ea2b9d45f5ce35  spi_test
43870da5d2b9fcb8fe0a6a96350b87da1e9423fe9f9e2f039bf363e68916ae0d  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff |   locked | 2048
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=134217728
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=2048
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 134086656 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff | unlocked | 2046
 07fe0000-07ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 134086656 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff | unlocked | 2047
 07ff0000-07ffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 125829120 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-077fffff | unlocked | 1920
 07800000-07ffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-07ffffff | unlocked | 2046
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-07ffffff | unlocked | 2047
+ all_but_one=2047
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 2047
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-07ffffff |   locked | 2047
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-07ffffff |   locked | 2046
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2047
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff |   locked | 2047
 07ff0000-07ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 134086656 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff |   locked | 2046
 07fe0000-07ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 980c437f1951..593429bd6cdf 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -340,6 +340,12 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x20),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+	}, {
+		/* W25Q01JV-M */
+		.id = SNOR_ID(0xef, 0x70, 0x21),
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:49 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:49 +0200
Subject: [PATCH v5 24/28] mtd: spi-nor: winbond: W25Q128JV-M: Fill locking
 information
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-24-87238e577794@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x1000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00ffffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x1000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_read2
e30cacd4f9d230b2fc4ae12851797332d9baf8456520872161f79379c80ea2b0  spi_test
c9d2a381c868b43a44563b0e36e2556b3095acf94f6dad8f259c85eab536363e  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00ffffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=16777216
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=262144
+ bps=4
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 16252928 8
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00f7ffff | unlocked | 62
 00f80000-00ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 16252928 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00fbffff | unlocked | 63
 00fc0000-00ffffff |   locked | 1
/* Skipping BP3 */
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 8
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0007ffff |   locked | 2
 00080000-00ffffff | unlocked | 62
+ flash_lock -u /dev/mtd0 262144 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 1
 00040000-00ffffff | unlocked | 63
+ all_but_one=252
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 262144 252
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 1
 00040000-00ffffff |   locked | 63
+ flash_lock -u /dev/mtd0 262144 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0007ffff | unlocked | 2
 00080000-00ffffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 252
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00fbffff |   locked | 63
 00fc0000-00ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 16252928 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-00f7ffff |   locked | 62
 00f80000-00ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 41b8733c4c85..4f26191e1d6d 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -327,7 +327,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q128jv",
 		.size = SZ_16M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:02:45 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:02:45 +0200
Subject: [PATCH v5 20/28] mtd: spi-nor: winbond: W25Q512JV-Q/N: Fill
 locking information
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-20-87238e577794@bootlin.com>

The SFDP table does not advertize the locking capabilities of the
device, flag these capabilities.

The CMP capability was historically not supported by non-SFDP chips, so
a Winbond wide late hook already makes sure the bit remains disabled for
them, for backward compatibility reasons.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
$ dd if=/dev/urandom of=./spi_test bs=1M count=2
2+0 records in
2+0 records out
$ sha256sum spi_test
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_test
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_read
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_test
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
$ flash_lock -l /dev/mtd0
$ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: locked
Return code: 1
$ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
$ sha256sum spi*
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_read
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_test
$ dd if=/dev/urandom of=./spi_test2 bs=1M count=2
2+0 records in
2+0 records out
$ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
$ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
$ sha256sum spi*
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_read
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_read2
090febbfd471d60250b2c6166939335012ad84207ab29b6657fcf62e8fc21fa4  spi_test
0236e30ff05676c4bcc80b55aa0ebf4747630e0ae3360e025c9e0c2509402a6b  spi_test2
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff |   locked | 1024
$ flash_lock -u /dev/mtd0
$
$ size=$(cat /sys/class/mtd/mtd0/size)
$ nblocks=$(grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params | sed -e 's/.*
unlocked | //')
$
$ bs=$(($size / $nblocks))
$
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2 * $bs))) 2 # last two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff | unlocked | 1022
 03fe0000-03ffffff |   locked | 2
$ flash_lock -u /dev/mtd0 $(($size - (2 * $bs))) 1 # last one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff | unlocked | 1023
 03ff0000-03ffffff |   locked | 1
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $(($size - (2**7 * $bs))) $((2**7))
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-037fffff | unlocked | 896
 03800000-03ffffff |   locked | 128
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 2 # first two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-03ffffff | unlocked | 1022
$ flash_lock -u /dev/mtd0 $bs 1 # first one
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-03ffffff | unlocked | 1023
$ all_but_one=$((($size / $bs) - 1))
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 $bs $all_but_one # all but the first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-03ffffff |   locked | 1023
$ flash_lock -u /dev/mtd0 $bs 1 # all but the two first
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-03ffffff |   locked | 1022
$ flash_lock -u /dev/mtd0
$ flash_lock -l /dev/mtd0 0 $all_but_one # same from the other side
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff |   locked | 1023
 03ff0000-03ffffff | unlocked | 1
$ flash_lock -u /dev/mtd0 $(($size - (2 * $bs))) 1 # all but two
$ show_sectors
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff |   locked | 1022
 03fe0000-03ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 5a80de959768..1ac0177aeb44 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -234,6 +234,8 @@ static const struct flash_info winbond_nor_parts[] = {
 		.name = "w25q512jvq",
 		.size = SZ_64M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q01JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x21),

-- 
2.54.0



From miquel.raynal at bootlin.com  Wed Aug  5 01:39:58 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 10:39:58 +0200
Subject: [PATCH v5 00/28] mtd: spi-nor: Clean Winbond W25QxxJV family
In-Reply-To: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
	(Miquel Raynal's message of "Wed, 05 Aug 2026 10:02:25 +0200")
References: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794@bootlin.com>
Message-ID: <874ih9eza9.fsf@bootlin.com>

Hi,

> This series is pretty straightforward, it is here to cleanup the JV
> entries by:
> - Aligning the comments and "fixing" the chip names in them
> - Reordering the chips by density and family
> - Testing and showing all the SFDP content and passing all the SPI NOR
>   tests to ensure a good level of support.
> - Adding all the locking information when it is missing.

v5 is ready for human review. I already answered most of Sashiko's
comments (see changelog in the cover letter) and the new ones are just
unrelated or totally wrong.

Thanks,
Miqu?l


From miquel.raynal at bootlin.com  Wed Aug  5 07:04:52 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 05 Aug 2026 16:04:52 +0200
Subject: [PATCH 1/5] mtd: spi-nor: Refactor Read Status/Write Status
 support
In-Reply-To: <87mrv2exwp.fsf@bootlin.com> (Miquel Raynal's message of "Tue, 04
	Aug 2026 16:57:26 +0200")
References: <20260529-winbond-v7-1-spi-nor-rv-addition-v1-0-f3ae18502d5a@bootlin.com>
	<20260529-winbond-v7-1-spi-nor-rv-addition-v1-1-f3ae18502d5a@bootlin.com>
	<DJS8PQ5HZZG5.2YT956NCDX8MG@kernel.org> <87mrv2exwp.fsf@bootlin.com>
Message-ID: <87y0ekek8r.fsf@bootlin.com>

Hello Michael,

>>> +int spi_nor_read_srs(struct spi_nor *nor, u8 *sr1, u8 *sr2)
>>
>> nitpick, i think this is a weird name. But maybe it's just me.
>>
>> What if we'd just make the one status register as u32? I mean in the
>> winbond datasheet it's called S7-S0 and S15-S8. That way we also
>> don't need a two byte qe_mask. To optimize the standard usecase to
>> poll the WIP bit, we could add a byte mask.

I experimented a bit the u32 status register, I don't think it is a good
idea.

My target of improving the readability is completely defeated by the
fact that:
- all the callers need to add extra maths to explain what SR
  they want
- endianness shall be handled, people always get it wrong (including
  me), so why bother if an array just makes the whole thing simpler?
- 16-bit accesses get more convoluted, we need extra
  get_/put_unaligned_le32() calls and pack/unpack bytes in the hot path
- 8-bit accesses imply an extensive use of FIELD_GET(GENMASK(),) macros,
  which make the whole fonction totally non obvious anymore.

Whereas, a simple:

        if (sr2)
                *sr2 = foo;

is self explanatory. I also do not really get the wish for a QE mask
instead of a two bytes array.

So I will rename the "srs" naming that you dislike, I will group the
opcodes in a big structure, change the baseline default to match your
suggestion and follow-up with the other minor comments, but I believe
I'm going to avoid the u32 switch.

Thanks,
Miqu?l


From ptpt52 at gmail.com  Wed Aug  5 10:12:14 2026
From: ptpt52 at gmail.com (Chen Minqiang)
Date: Thu,  6 Aug 2026 01:12:14 +0800
Subject: [PATCH] mtd: spi-nor: allow force unlocking via DT property
Message-ID: <20260805171214.2934-1-ptpt52@gmail.com>

Some SPI NOR flash chips (such as generic or unlisted chips used in vendor
devices like Tenda AX12L Pro) have Block Protection (BP) bits set in the
Status Register by bootloaders or factory settings, locking flash blocks.

Because vendors frequently switch between various generic SPI NOR flash
chips ("Flash Lottery"), it is impractical to upstream explicit chip ID
flags (SNOR_F_HAS_LOCK) for every possible generic chip variant.

This patch introduces support for the "linux,force-sr-unlock" Device Tree
property:
1. In spi_nor_init(), trigger spi_nor_try_unlock_all() if "linux,force-sr-unlock"
   is present in the flash DT node, even when CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE
   is active and the chip is non-volatile.
2. In spi_nor_try_unlock_all(), bypass the SNOR_F_HAS_LOCK flag check when
   "linux,force-sr-unlock" is specified, ensure locking_ops are initialized,
   and invoke Linux kernel's native spi_nor_unlock() mechanism.

Signed-off-by: Chen Minqiang <ptpt52 at gmail.com>
---
 drivers/mtd/spi-nor/core.c | 3 ++-
 drivers/mtd/spi-nor/swp.c  | 7 ++++++-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ccf4396cdcd0..ef0bdc1254bb 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -3332,7 +3332,8 @@ static int spi_nor_init(struct spi_nor *nor)
 	spi_nor_cache_sr_lock_bits(nor, NULL);
 	if (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE) ||
 	    (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE) &&
-	     nor->flags & SNOR_F_SWP_IS_VOLATILE)) {
+	     nor->flags & SNOR_F_SWP_IS_VOLATILE) ||
+	    of_property_read_bool(spi_nor_get_flash_node(nor), "linux,force-sr-unlock")) {
 		spi_nor_try_unlock_all(nor);
 	}
 
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index 235070b215d1..a190d10c1630 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -628,11 +628,16 @@ static int spi_nor_is_locked(struct mtd_info *mtd, loff_t ofs, u64 len)
  */
 void spi_nor_try_unlock_all(struct spi_nor *nor)
 {
+	struct device_node *np = spi_nor_get_flash_node(nor);
+	bool force_unlock = of_property_read_bool(np, "linux,force-sr-unlock");
 	int ret;
 
-	if (!(nor->flags & SNOR_F_HAS_LOCK))
+	if (!(nor->flags & SNOR_F_HAS_LOCK) && !force_unlock)
 		return;
 
+	if (!nor->params->locking_ops)
+		spi_nor_init_default_locking_ops(nor);
+
 	dev_dbg(nor->dev, "Unprotecting entire flash array\n");
 
 	ret = spi_nor_unlock(&nor->mtd, 0, nor->params->size);
-- 
2.17.1



From Takahiro.Kuwano at infineon.com  Wed Aug  5 22:41:18 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 6 Aug 2026 05:41:18 +0000
Subject: [PATCH] mtd: spi-nor: allow force unlocking via DT property
In-Reply-To: <20260805171214.2934-1-ptpt52@gmail.com>
References: <20260805171214.2934-1-ptpt52@gmail.com>
Message-ID: <c0205980208a489598f4cdbdb5c8d271@infineon.com>

Hi,
 
> Some SPI NOR flash chips (such as generic or unlisted chips used in vendor
> devices like Tenda AX12L Pro) have Block Protection (BP) bits set in the
> Status Register by bootloaders or factory settings, locking flash blocks.
> 
> Because vendors frequently switch between various generic SPI NOR flash
> chips ("Flash Lottery"), it is impractical to upstream explicit chip ID
> flags (SNOR_F_HAS_LOCK) for every possible generic chip variant.
> 
> This patch introduces support for the "linux,force-sr-unlock" Device Tree
> property:
> 1. In spi_nor_init(), trigger spi_nor_try_unlock_all() if "linux,force-sr-unlock"
>    is present in the flash DT node, even when CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE
>    is active and the chip is non-volatile.
> 2. In spi_nor_try_unlock_all(), bypass the SNOR_F_HAS_LOCK flag check when
>    "linux,force-sr-unlock" is specified, ensure locking_ops are initialized,
>    and invoke Linux kernel's native spi_nor_unlock() mechanism.

Does this work for generic(unlisted) SPI NOR flash chips with 4-bit BP
and/or CMP bit? I think we need to rely on ID database to know what block
protection bits are available in the chip.

> 
> Signed-off-by: Chen Minqiang <ptpt52 at gmail.com>
> ---
>  drivers/mtd/spi-nor/core.c | 3 ++-
>  drivers/mtd/spi-nor/swp.c  | 7 ++++++-
>  2 files changed, 8 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index ccf4396cdcd0..ef0bdc1254bb 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
> @@ -3332,7 +3332,8 @@ static int spi_nor_init(struct spi_nor *nor)
>         spi_nor_cache_sr_lock_bits(nor, NULL);
>         if (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE) ||
>             (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE) &&
> -            nor->flags & SNOR_F_SWP_IS_VOLATILE)) {
> +            nor->flags & SNOR_F_SWP_IS_VOLATILE) ||
> +           of_property_read_bool(spi_nor_get_flash_node(nor), "linux,force-sr-unlock")) {
>                 spi_nor_try_unlock_all(nor);
>         }
> 
> diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
> index 235070b215d1..a190d10c1630 100644
> --- a/drivers/mtd/spi-nor/swp.c
> +++ b/drivers/mtd/spi-nor/swp.c
> @@ -628,11 +628,16 @@ static int spi_nor_is_locked(struct mtd_info *mtd, loff_t ofs, u64 len)
>   */
>  void spi_nor_try_unlock_all(struct spi_nor *nor)
>  {
> +       struct device_node *np = spi_nor_get_flash_node(nor);
> +       bool force_unlock = of_property_read_bool(np, "linux,force-sr-unlock");
>         int ret;
> 
> -       if (!(nor->flags & SNOR_F_HAS_LOCK))
> +       if (!(nor->flags & SNOR_F_HAS_LOCK) && !force_unlock)
>                 return;
> 
> +       if (!nor->params->locking_ops)
> +               spi_nor_init_default_locking_ops(nor);
> +
>         dev_dbg(nor->dev, "Unprotecting entire flash array\n");
> 
>         ret = spi_nor_unlock(&nor->mtd, 0, nor->params->size);
> --
> 2.17.1

Thanks,
Takahiro



From miquel.raynal at bootlin.com  Thu Aug  6 00:38:50 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 06 Aug 2026 09:38:50 +0200
Subject: [PATCH 1/5] mtd: spi-nor: Refactor Read Status/Write Status
 support
In-Reply-To: <87mrv2exwp.fsf@bootlin.com> (Miquel Raynal's message of "Tue, 04
	Aug 2026 16:57:26 +0200")
References: <20260529-winbond-v7-1-spi-nor-rv-addition-v1-0-f3ae18502d5a@bootlin.com>
	<20260529-winbond-v7-1-spi-nor-rv-addition-v1-1-f3ae18502d5a@bootlin.com>
	<DJS8PQ5HZZG5.2YT956NCDX8MG@kernel.org> <87mrv2exwp.fsf@bootlin.com>
Message-ID: <87se4rem0l.fsf@bootlin.com>

Hello Michael,

>> One thing that comes to mind is hardware write protection. If
>> there's nothing before that code which checks it, the verify might
>> fail if the hardware write protection is enabled. So we should
>> somehow check for that and drop the verify here.
>
> But how do you think we should handle it? Will the QE bit writing
> verification fail if HW WP is enabled?

Looking into this further: we shall return an error if the QE bit is not
set. It just tells the caller that quad mode cannot be used. Then up to
the caller to either hard fail or just degrade into single mode (maybe
because of a strapped WP). What we should do is to propose a DT property
to flag when WP is strapped in hardware, this would make the content of
the status registers immutable and we would just skip the entire write
operation in the first place, instead of deliberately trying and get a
100% failure rate. Nevertheless, the changes introduced here are kind of
orthogonal and do not alter the current behaviour; we shall however
listen if people start complaining about this and perhaps implement the
solution proposed above.

Thanks!
Miqu?l


From miquel.raynal at bootlin.com  Thu Aug  6 02:47:46 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 06 Aug 2026 11:47:46 +0200
Subject: [PATCH 03/30] mtd: spi-nor: winbond: Stop filling the .name entry
In-Reply-To: <DJRJJB4NEUEP.168MF88DBSFDF@kernel.org> (Michael Walle's message
	of "Mon, 06 Jul 2026 15:59:53 +0200")
References: <20260529-winbond-v7-1-spi-nor-jv-cleanup-v1-0-87e5d3122244@bootlin.com>
	<20260529-winbond-v7-1-spi-nor-jv-cleanup-v1-3-87e5d3122244@bootlin.com>
	<DJRJJB4NEUEP.168MF88DBSFDF@kernel.org>
Message-ID: <87mruzeg1p.fsf@bootlin.com>

Hello,

>> Replace the names with a comment above the entry with the newly instated
>> naming scheme to indicate what chips are covered by each entry.
>
> This is exported via sysfs, so this could be a regression.

Well, actually names are not exported through sysfs. Only the mtd name
is available through sysfs, and it is set to `spiX.Y`. That name is only
available through debugfs, which is not part of the stable API.

I should have checked before dropping all my patches...

Thanks,
Miqu?l


From ptpt52 at gmail.com  Thu Aug  6 04:24:18 2026
From: ptpt52 at gmail.com (Chen Minqiang)
Date: Thu,  6 Aug 2026 19:24:18 +0800
Subject: [PATCH] mtd: spi-nor: allow force unlocking via DT property
In-Reply-To: <2026-08-05171214.2934-1-ptpt52@gmail.com>
References: <2026-08-05171214.2934-1-ptpt52@gmail.com>
Message-ID: <20260806112418.14695-1-ptpt52@gmail.com>

Hi,

Thank you for your valuable insight!

You are completely right that for unlisted/generic chips that feature a 4-bit
BP layout (BP3 at bit 5 or bit 6) or a CMP (Complement Protect) bit in SR2,
spi_nor_unlock() won't clear those extra bits without the corresponding flags
(SNOR_F_HAS_4BIT_BP / SNOR_F_HAS_SR2_CMP_BIT6) set by the ID database or SFDP.

However, in practice:
1. The vast majority of 3.3V/1.8V generic SPI NOR flashes (e.g. 4MB-16MB chips
   commonly found in vendor devices like Tenda AX12L Pro) use the standard
   3-bit BP (BP0-BP2, SR1 bits 2..4).
2. The current main issue is that even for these standard 3-bit BP chips, the
   kernel currently skips spi_nor_try_unlock_all() completely at boot time if
   CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE is set (for non-volatile chips)
   or if SNOR_F_HAS_LOCK is not set in chip flags. As a result, status registers
   locked by factory bootloaders are never cleared.

`linux,force-sr-unlock` serves as a pragmatic DT override to force the unlock
attempt at probe time.

To address your point regarding 4-bit BP and CMP bits for unlisted chips, we
have two potential options:

Option A (Current Best-Effort):
Keep the patch as-is, treating `linux,force-sr-unlock` as a best-effort DT
trigger to invoke standard spi_nor_unlock(). It successfully unlocks the vast
majority of standard 3-bit BP generic chips. For rare unlisted chips with 4-bit
BP or CMP bits, explicit entries can still be added to the ID database when
discovered.

Option B (Aggressive Force-Clear):
When `linux,force-sr-unlock` is present in DT, enhance spi_nor_try_unlock_all()
to perform a broader clear operation on SR1 (masking bits 2..6 to clear BP0-BP3/TB)
and SR2 (clearing CMP bit if SR2 is readable).

Which approach would you prefer? I'd be happy to revise the patch based on your
guidance.

Best regards,
Chen Minqiang


From ptpt52 at gmail.com  Thu Aug  6 04:37:33 2026
From: ptpt52 at gmail.com (Chen Minqiang)
Date: Thu,  6 Aug 2026 19:37:33 +0800
Subject: [PATCH v2] mtd: spi-nor: allow force unlocking via DT property
In-Reply-To: <2026-08-05171214.2934-1-ptpt52@gmail.com>
References: <2026-08-05171214.2934-1-ptpt52@gmail.com>
Message-ID: <20260806113733.31042-1-ptpt52@gmail.com>

Some SPI NOR flash chips (such as generic or unlisted chips used in vendor
devices like Tenda AX12L Pro) have Block Protection (BP) bits set in the
Status Register by bootloaders or factory settings, locking flash blocks.

Because vendors frequently switch between various generic SPI NOR flash
chips ("Flash Lottery"), it is impractical to upstream explicit chip ID
flags (SNOR_F_HAS_LOCK) for every possible generic chip variant.

This patch introduces support for the "linux,force-sr-unlock" Device Tree
property:
1. In spi_nor_init_flags(), set the SNOR_F_HAS_LOCK flag on the flash
   instance if "linux,force-sr-unlock" is present in the flash DT node. This
   allows spi_nor_late_init_params() to automatically populate default
   locking_ops without altering swp.c.
2. In spi_nor_init(), trigger spi_nor_try_unlock_all() if "linux,force-sr-unlock"
   is set, invoking Linux kernel's native spi_nor_unlock() mechanism.

Signed-off-by: Chen Minqiang <ptpt52 at gmail.com>
---
v1 -> v2:
- Set SNOR_F_HAS_LOCK in spi_nor_init_flags() when "linux,force-sr-unlock"
  is present in DT, allowing spi_nor_late_init_params() to set default
  locking_ops automatically without modifying swp.c.
---
 drivers/mtd/spi-nor/core.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ccf4396cdcd0..9aa2aaa10c38 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -2956,6 +2956,9 @@ static void spi_nor_init_flags(struct spi_nor *nor)
 	if (of_property_read_bool(np, "no-wp"))
 		nor->flags |= SNOR_F_NO_WP;
 
+	if (of_property_read_bool(np, "linux,force-sr-unlock"))
+		nor->flags |= SNOR_F_HAS_LOCK;
+
 	if (flags & SPI_NOR_SWP_IS_VOLATILE)
 		nor->flags |= SNOR_F_SWP_IS_VOLATILE;
 
@@ -3332,7 +3335,8 @@ static int spi_nor_init(struct spi_nor *nor)
 	spi_nor_cache_sr_lock_bits(nor, NULL);
 	if (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE) ||
 	    (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE) &&
-	     nor->flags & SNOR_F_SWP_IS_VOLATILE)) {
+	     nor->flags & SNOR_F_SWP_IS_VOLATILE) ||
+	    of_property_read_bool(spi_nor_get_flash_node(nor), "linux,force-sr-unlock")) {
 		spi_nor_try_unlock_all(nor);
 	}
 
-- 
2.17.1



From andriy.shevchenko at linux.intel.com  Thu Aug  6 13:59:01 2026
From: andriy.shevchenko at linux.intel.com (Andy Shevchenko)
Date: Thu, 6 Aug 2026 23:59:01 +0300
Subject: [PATCH v2] mtd: maps: remove dead select of MTD_CFI_BE_BYTE_SWAP
In-Reply-To: <20260722220618.198164-1-julianbraha@gmail.com>
References: <20260722220618.198164-1-julianbraha@gmail.com>
Message-ID: <anT1lU7zm95HPs6J@ashevche-desk.local>

On Wed, Jul 22, 2026 at 11:06:18PM +0100, Julian Braha wrote:
> 'select' does not work on config options in a 'choice', so currently it is
> possible to enable MTD_PHYSMAP_IXP4XX without MTD_CFI_BE_BYTE_SWAP.
> 
> From a previous conversation, it was decided to remove the select entirely:
> https://lore.kernel.org/all/c6268184-4904-49d9-b14f-0e11dce8bdad at app.fastmail.com/

You can make this a proper Link tag.

"From a previous conversation [1], it was decided to remove the select entirely.
 ...
 Link: $URL [1]"


> This dead select was found by kconfirm, a static analysis tool for Kconfig.

-- 
With Best Regards,
Andy Shevchenko




From syzbot+743079a09058321247d8 at syzkaller.appspotmail.com  Thu Aug  6 17:00:36 2026
From: syzbot+743079a09058321247d8 at syzkaller.appspotmail.com (syzbot)
Date: Thu, 06 Aug 2026 17:00:36 -0700
Subject: [syzbot] [jffs2?] possible deadlock in jffs2_do_clear_inode (2)
Message-ID: <6a752024.b50370da.49fe0.0017.GAE@google.com>

Hello,

syzbot found the following issue on:

HEAD commit:    848acc8ffe1b Merge tag 'fsverity-for-linus' of git://git.k..
git tree:       upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=13a07e32580000
kernel config:  https://syzkaller.appspot.com/x/.config?x=4e38b15c29e6a1d9
dashboard link: https://syzkaller.appspot.com/bug?extid=743079a09058321247d8
compiler:       Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8

Unfortunately, I don't have any reproducer for this issue yet.

Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/9bf3ffd710de/disk-848acc8f.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/770c0a63a80c/vmlinux-848acc8f.xz
kernel image: https://storage.googleapis.com/syzbot-assets/c54f9393cb03/bzImage-848acc8f.xz

IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+743079a09058321247d8 at syzkaller.appspotmail.com

======================================================
WARNING: possible circular locking dependency detected
syzkaller #0 Tainted: G                 N 
------------------------------------------------------
syz.3.73/6195 is trying to acquire lock:
ffff88805432b1e0 (&f->sem){+.+.}-{4:4}, at: jffs2_do_clear_inode+0x62/0x390 fs/jffs2/readinode.c:1419

but task is already holding lock:
ffffffff8ec848e0 (fs_reclaim){+.+.}-{0:0}, at: __perform_reclaim mm/page_alloc.c:4402 [inline]
ffffffff8ec848e0 (fs_reclaim){+.+.}-{0:0}, at: __alloc_pages_direct_reclaim+0xbd/0x310 mm/page_alloc.c:4427

which lock already depends on the new lock.


the existing dependency chain (in reverse order) is:

-> #1 (fs_reclaim){+.+.}-{0:0}:
       __fs_reclaim_acquire mm/page_alloc.c:4329 [inline]
       fs_reclaim_acquire+0x71/0x100 mm/page_alloc.c:4343
       might_alloc include/linux/sched/mm.h:317 [inline]
       slab_pre_alloc_hook mm/slub.c:4537 [inline]
       slab_alloc_node mm/slub.c:4897 [inline]
       kmem_cache_alloc_noprof+0x64/0x5f0 mm/slub.c:4931
       jffs2_do_read_inode+0x33f/0x6a0 fs/jffs2/readinode.c:1372
       jffs2_iget+0x228/0xcc0 fs/jffs2/fs.c:277
       jffs2_do_fill_super+0x59d/0xc50 fs/jffs2/fs.c:578
       mtd_get_sb+0x18a/0x410 drivers/mtd/mtdsuper.c:57
       mtd_get_sb_by_nr drivers/mtd/mtdsuper.c:88 [inline]
       get_tree_mtd+0x4bd/0x7d0 drivers/mtd/mtdsuper.c:158
       vfs_get_tree+0x92/0x2a0 fs/super.c:1700
       fc_mount fs/namespace.c:1198 [inline]
       do_new_mount_fc fs/namespace.c:3765 [inline]
       do_new_mount+0x319/0xdc0 fs/namespace.c:3841
       do_mount fs/namespace.c:4174 [inline]
       __do_sys_mount fs/namespace.c:4390 [inline]
       __se_sys_mount+0x31d/0x420 fs/namespace.c:4367
       do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
       do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
       entry_SYSCALL_64_after_hwframe+0x77/0x7f

-> #0 (&f->sem){+.+.}-{4:4}:
       check_prev_add kernel/locking/lockdep.c:3165 [inline]
       check_prevs_add kernel/locking/lockdep.c:3284 [inline]
       validate_chain kernel/locking/lockdep.c:3908 [inline]
       __lock_acquire+0x1520/0x2cf0 kernel/locking/lockdep.c:5237
       lock_acquire+0x106/0x350 kernel/locking/lockdep.c:5868
       __mutex_lock_common kernel/locking/mutex.c:646 [inline]
       __mutex_lock+0x19d/0x1550 kernel/locking/mutex.c:821
       jffs2_do_clear_inode+0x62/0x390 fs/jffs2/readinode.c:1419
       evict+0x624/0xb50 fs/inode.c:825
       dispose_list fs/inode.c:867 [inline]
       prune_icache_sb+0x223/0x2d0 fs/inode.c:1014
       super_cache_scan+0x38d/0x4a0 fs/super.c:225
       do_shrink_slab+0x6d0/0x10e0 mm/shrinker.c:443
       shrink_slab_memcg mm/shrinker.c:560 [inline]
       shrink_slab+0x838/0x1220 mm/shrinker.c:638
       shrink_one+0x2a2/0x730 mm/vmscan.c:5026
       shrink_many mm/vmscan.c:5087 [inline]
       lru_gen_shrink_node mm/vmscan.c:5165 [inline]
       shrink_node+0x3303/0x3b60 mm/vmscan.c:6154
       shrink_zones mm/vmscan.c:6410 [inline]
       do_try_to_free_pages+0x6a3/0x1990 mm/vmscan.c:6472
       try_to_free_pages+0x90a/0xe50 mm/vmscan.c:6711
       __perform_reclaim mm/page_alloc.c:4405 [inline]
       __alloc_pages_direct_reclaim+0x148/0x310 mm/page_alloc.c:4427
       __alloc_pages_slowpath+0x59d/0xcc0 mm/page_alloc.c:4876
       __alloc_frozen_pages_noprof+0x31f/0x380 mm/page_alloc.c:5317
       alloc_pages_mpol+0x212/0x380 mm/mempolicy.c:2490
       alloc_frozen_pages_noprof mm/mempolicy.c:2561 [inline]
       alloc_pages_noprof+0xac/0x2a0 mm/mempolicy.c:2581
       alloc_image_page+0x23/0x130 kernel/power/snapshot.c:231
       preallocate_image_pages kernel/power/snapshot.c:1654 [inline]
       preallocate_image_memory+0x76/0x140 kernel/power/snapshot.c:1681
       hibernate_preallocate_memory+0x43c/0x750 kernel/power/snapshot.c:1960
       hibernation_snapshot+0x126/0x3d0 kernel/power/hibernate.c:430
       hibernate+0x28f/0x680 kernel/power/hibernate.c:811
       state_store+0x234/0x290 kernel/power/main.c:821
       kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345
       new_sync_write fs/read_write.c:595 [inline]
       vfs_write+0x612/0xba0 fs/read_write.c:687
       ksys_write+0x150/0x270 fs/read_write.c:739
       do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
       do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
       entry_SYSCALL_64_after_hwframe+0x77/0x7f

other info that might help us debug this:

 Possible unsafe locking scenario:

       CPU0                    CPU1
       ----                    ----
  lock(fs_reclaim);
                               lock(&f->sem);
                               lock(fs_reclaim);
  lock(&f->sem);

 *** DEADLOCK ***

9 locks held by syz.3.73/6195:
 #0: ffff88807a0a70b0 (&f->f_pos_lock){+.+.}-{4:4}, at: fdget_pos+0x246/0x320 fs/file.c:1259
 #1: ffff888034304450 (sb_writers#7){.+.+}-{0:0}, at: file_start_write include/linux/fs.h:2733 [inline]
 #1: ffff888034304450 (sb_writers#7){.+.+}-{0:0}, at: vfs_write+0x22b/0xba0 fs/read_write.c:683
 #2: ffff88805386dc80 (&of->mutex){+.+.}-{4:4}, at: kernfs_fop_write_iter+0x1d8/0x540 fs/kernfs/file.c:336
 #3: ffff88801f28d968 (kn->active#59){.+.+}-{0:0}, at: kernfs_get_active_of fs/kernfs/file.c:73 [inline]
 #3: ffff88801f28d968 (kn->active#59){.+.+}-{0:0}, at: kernfs_fop_write_iter+0x22b/0x540 fs/kernfs/file.c:337
 #4: ffffffff8ea02160 (system_transition_mutex){+.+.}-{4:4}, at: lock_system_sleep+0x49/0x70 kernel/power/main.c:71
 #5: ffffffff8f5e45e0 (device_hotplug_lock){+.+.}-{4:4}, at: hibernate+0x25b/0x680 kernel/power/hibernate.c:805
 #6: ffffffff8f3d5240 (acpi_scan_lock){+.+.}-{4:4}, at: acpi_pm_start drivers/acpi/sleep.c:533 [inline]
 #6: ffffffff8f3d5240 (acpi_scan_lock){+.+.}-{4:4}, at: acpi_hibernation_begin+0xc8/0x110 drivers/acpi/sleep.c:945
 #7: ffffffff8ec848e0 (fs_reclaim){+.+.}-{0:0}, at: __perform_reclaim mm/page_alloc.c:4402 [inline]
 #7: ffffffff8ec848e0 (fs_reclaim){+.+.}-{0:0}, at: __alloc_pages_direct_reclaim+0xbd/0x310 mm/page_alloc.c:4427
 #8: ffff8880358a40d8 (&type->s_umount_key#83){.+.+}-{4:4}, at: super_trylock_shared fs/super.c:566 [inline]
 #8: ffff8880358a40d8 (&type->s_umount_key#83){.+.+}-{4:4}, at: super_cache_scan+0x91/0x4a0 fs/super.c:198

stack backtrace:
CPU: 0 UID: 0 PID: 6195 Comm: syz.3.73 Tainted: G                 N  syzkaller #0 PREEMPT(full) 
Tainted: [N]=TEST
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/16/2026
Call Trace:
 <TASK>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 print_circular_bug+0x2e1/0x300 kernel/locking/lockdep.c:2043
 check_noncircular+0x12e/0x150 kernel/locking/lockdep.c:2175
 check_prev_add kernel/locking/lockdep.c:3165 [inline]
 check_prevs_add kernel/locking/lockdep.c:3284 [inline]
 validate_chain kernel/locking/lockdep.c:3908 [inline]
 __lock_acquire+0x1520/0x2cf0 kernel/locking/lockdep.c:5237
 lock_acquire+0x106/0x350 kernel/locking/lockdep.c:5868
 __mutex_lock_common kernel/locking/mutex.c:646 [inline]
 __mutex_lock+0x19d/0x1550 kernel/locking/mutex.c:821
 jffs2_do_clear_inode+0x62/0x390 fs/jffs2/readinode.c:1419
 evict+0x624/0xb50 fs/inode.c:825
 dispose_list fs/inode.c:867 [inline]
 prune_icache_sb+0x223/0x2d0 fs/inode.c:1014
 super_cache_scan+0x38d/0x4a0 fs/super.c:225
 do_shrink_slab+0x6d0/0x10e0 mm/shrinker.c:443
 shrink_slab_memcg mm/shrinker.c:560 [inline]
 shrink_slab+0x838/0x1220 mm/shrinker.c:638
 shrink_one+0x2a2/0x730 mm/vmscan.c:5026
 shrink_many mm/vmscan.c:5087 [inline]
 lru_gen_shrink_node mm/vmscan.c:5165 [inline]
 shrink_node+0x3303/0x3b60 mm/vmscan.c:6154
 shrink_zones mm/vmscan.c:6410 [inline]
 do_try_to_free_pages+0x6a3/0x1990 mm/vmscan.c:6472
 try_to_free_pages+0x90a/0xe50 mm/vmscan.c:6711
 __perform_reclaim mm/page_alloc.c:4405 [inline]
 __alloc_pages_direct_reclaim+0x148/0x310 mm/page_alloc.c:4427
 __alloc_pages_slowpath+0x59d/0xcc0 mm/page_alloc.c:4876
 __alloc_frozen_pages_noprof+0x31f/0x380 mm/page_alloc.c:5317
 alloc_pages_mpol+0x212/0x380 mm/mempolicy.c:2490
 alloc_frozen_pages_noprof mm/mempolicy.c:2561 [inline]
 alloc_pages_noprof+0xac/0x2a0 mm/mempolicy.c:2581
 alloc_image_page+0x23/0x130 kernel/power/snapshot.c:231
 preallocate_image_pages kernel/power/snapshot.c:1654 [inline]
 preallocate_image_memory+0x76/0x140 kernel/power/snapshot.c:1681
 hibernate_preallocate_memory+0x43c/0x750 kernel/power/snapshot.c:1960
 hibernation_snapshot+0x126/0x3d0 kernel/power/hibernate.c:430
 hibernate+0x28f/0x680 kernel/power/hibernate.c:811
 state_store+0x234/0x290 kernel/power/main.c:821
 kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345
 new_sync_write fs/read_write.c:595 [inline]
 vfs_write+0x612/0xba0 fs/read_write.c:687
 ksys_write+0x150/0x270 fs/read_write.c:739
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fb3bdd9e019
Code: Unable to access opcode bytes at 0x7fb3bdd9dfef.
RSP: 002b:00007fb3beb8f028 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007fb3be025fa0 RCX: 00007fb3bdd9e019
RDX: 0000000000000004 RSI: 0000200000000100 RDI: 0000000000000003
RBP: 00007fb3bde3500c R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fb3be026038 R14: 00007fb3be025fa0 R15: 00007ffecece6378
 </TASK>
PM: hibernation: Allocated 724545 pages for snapshot
PM: hibernation: Allocated 2898180 kbytes in 41.10 seconds (70.51 MB/s)
printk: Suspending console(s) (use no_console_suspend to debug)


---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller at googlegroups.com.

syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.

If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title

If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)

If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report

If you want to undo deduplication, reply with:
#syz undup


From devnull+mateusz.litwin.nokia.com at kernel.org  Thu Aug  6 23:06:45 2026
From: devnull+mateusz.litwin.nokia.com at kernel.org (Mateusz Litwin via B4 Relay)
Date: Fri, 07 Aug 2026 08:06:45 +0200
Subject: [PATCH v2] mtd: spi-nor: allow multiple erase sizes on uniform
 flash
Message-ID: <20260807-spi_nor_multisize_erase-v2-1-5a0a61471d3b@nokia.com>

From: Mateusz Litwin <mateusz.litwin at nokia.com>

Allow an SPI NOR MTD device to use multiple erase sizes on uniform flashes.
The driver keeps all supported erase types in the uniform region mask
instead of collapsing to a single size.

When the largest erase size is exposed through mtd->erasesize, this
allows minor-aligned partitions (for example u-boot-env, RouterBoot
soft_config, or a boot header partition) while still exposing large
erase size to upper layers. Erase requests are validated against the
smallest supported uniform erase size, so callers may issue erases
smaller than mtd->erasesize.

When CONFIG_MTD_SPI_NOR_USE_4K_SECTORS is enabled, mtd->erasesize is
still reported as 4096 B to tools and upper layers, but bulk erase
requests now use larger erase commands internally instead of always
erasing 4 KiB sectors to improve bulk region erase performance.

Refactor the single-sector erase sequence into spi_nor_erase_one(),
shared with spi_nor_erase_multi_sectors().  Validate multi-size uniform
requests through spi_nor_is_uniform_erasable() before locking the device:
address and length must be aligned to the smallest supported uniform erase
size.  This allows erase requests smaller than mtd->erasesize (e.g. a
32 KiB boot header region) while still using larger erase commands for
aligned bulk portions.

Also convert all erase-type opcodes to their 4-byte-address variants on
uniform flashes, so multi-size erases work correctly on devices above
16 MiB.

Assisted-by: Cursor:Auto

Signed-off-by: Mateusz Litwin <mateusz.litwin at nokia.com>
---
Most SPI NOR flashes advertise several erase sizes (e.g. 4 KiB, 32 KiB and
64 KiB) even when the erase map is uniform across the whole device.  The
spi-nor driver currently collapses such flashes to a single erase size and
uses only that size for every erase request.

This is a problem on platforms that need a small erasesize for partition
alignment (for example u-boot-env, RouterBoot soft_config, or boot header
partitions) while still wanting fast bulk erases.  Today the usual
workaround is to enable MTD_SPI_NOR_USE_4K_SECTORS, which forces 4 KiB
erases for the entire device and hurts erase performance on large regions.

This patch allows an SPI NOR MTD device to use multiple erase sizes on
uniform flashes. The driver keeps the full erase map and picks the largest
suitable erase size for each step of an erase operation, similar to what
non-uniform flashes already do.  Userspace still sees a single
mtd->erasesize (4 KiB when MTD_SPI_NOR_USE_4K_SECTORS is enabled), but
large aligned portions of an erase request can use 32 KiB or 64 KiB erase
commands internally.

The patch also converts all erase-type opcodes to their 4-byte-address
variants on uniform flashes, so multi-size erases work correctly on devices
larger than 16 MiB.

The previous uniform erase path only required the length to be a multiple
of mtd->erasesize.  With this patch, spi_nor_is_uniform_erasable() instead
checks the address and length against the smallest supported uniform erase
size. Requests aligned to mtd->erasesize still pass (it is a multiple of
that size), while callers may now issue erases smaller than mtd->erasesize. 

This patch also rejects MEMERASE requests with misaligned addresses.
On uniform flashes, MEMERASE with a misaligned address but aligned length
previously returned success yet erased a different block (in W25Q128JV case
the controller implicitly aligned the address). Reject such requests with
-EINVAL instead of relying on that undefined behavior; please report any
regressions.

Tested: backported to a 6.6-based tree on Micron MT25QU02G with
MTD_SPI_NOR_USE_4K_SECTORS disabled (mtd->erasesize reports 64 KiB).
Test uses a lightly modified Remote System Update (RSU) stack for Agilex:
https://github.com/altera-fpga/intel-rsu. librsu erases the flash with
the MEMERASE ioctl on the MTD character device, issuing erase requests
smaller than mtd->erasesize (e.g. a 32 KiB boot header region).
Verified writes and erases on the 32 KiB region align with 64 KiB erase
size.

Tested also with latest kernel version 7.2 tree on Winbond W25Q128JV.
Erase of 0xfc0000 bytes with offset 0x0
Command: time sudo mtd_debug erase /dev/mtd0 0x0 0xfc0000

Without this patch:
MTD_SPI_NOR_USE_4K_SECTORS=n	|y
real    0m 59.640s		|3m 14.724s
user    0m 0.009s		|0m 0.005s
sys     0m 0.000s		|0m 0.004s
can write minor-aligned parts? N|Y

With this patch:
MTD_SPI_NOR_USE_4K_SECTORS=n	|y
real    0m 59.347s		|0m 59.621s
user    0m 0.004s		|0m 0.004s
sys     0m 0.008s		|0m 0.005s
can write minor-aligned parts? Y|Y

A few implementation details are worth discussing:

1. spansion_nor_late_init() overrides nor->erase_opcode (and mtd->erasesize)
   for flashes larger than 16 MiB.  With this patch,
   spi_nor_erase_uniform() sets nor->erase_opcode before each erase, so the
   late_init value is not used on the erase path.  If the SFDP table masks
   unsupported erase types in 4-byte-address mode, this is not an issue;
   otherwise late_init() or erase-type masking may need changes for
   Cypress/Spansion parts.

2. Some flashes do not support every erase type with 4-byte-address opcodes.
   Unsupported types are masked by clearing erase_type[].size, but the
   corresponding erase_mask bit is not cleared.  Erases work correctly, but
   debugfs can show a set bit in the sector-map erase mask for a type that is
   not listed under "erase commands".  Fixing that is probably best done in
   a separate patch.
---
Changes in v2:
- Drop MTD_SPI_NOR_MULTI_ERASE_SIZE kernel config adding.
- Drop skipping erase-type search in uniform bulk erase as it complicates
  the flow and gives little performance gain.
- Extract spi_nor_find_smallest_erase_type() helper for uniform erase
  validation.
- Clean up commit message and Kconfig help text.
- Added testing on the latest kernel. 
- Link to v1: https://lore.kernel.org/r/20260720-spi_nor_multisize_erase-v1-0-38d719bfe77b at nokia.com

To: Pratyush Yadav <pratyush at kernel.org>
To: Michael Walle <mwalle at kernel.org>
To: Takahiro Kuwano <takahiro.kuwano at infineon.com>
To: Miquel Raynal <miquel.raynal at bootlin.com>
To: Richard Weinberger <richard at nod.at>
To: Vignesh Raghavendra <vigneshr at ti.com>
Cc: linux-mtd at lists.infradead.org
Cc: linux-kernel at vger.kernel.org
---
 drivers/mtd/spi-nor/Kconfig |  13 ++-
 drivers/mtd/spi-nor/core.c  | 207 ++++++++++++++++++++++++++++----------------
 2 files changed, 144 insertions(+), 76 deletions(-)

diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index fd05a24d64a9..4f83f9037d05 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -17,8 +17,17 @@ config MTD_SPI_NOR_USE_4K_SECTORS
 	  on the usage this feature may provide performance gain in comparison
 	  to erasing whole blocks (32/64 KiB).
 	  Changing a small part of the flash's contents is usually faster with
-	  small sectors. On the other hand erasing should be faster when using
-	  64 KiB block instead of 16 ? 4 KiB sectors.
+	  small sectors.
+
+	  Say Y here to report 4096 B (if flash supports it) as mtd->erasesize
+	  to tools and upper layers.
+
+	  This is also useful when partition alignment or filesystem
+	  requirements call for a small erase size.
+
+	  Internally the driver still uses multiple erase sizes when performing
+	  erase operations, selecting the largest suitable size for each step
+	  to maintain erase performance on large regions.
 
 	  Please note that some tools/drivers/filesystems may not work with
 	  4096 B erase size (e.g. UBIFS requires 15 KiB as a minimum).
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index b25d1a870a22..ef88de0a5438 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -1227,20 +1227,17 @@ static bool spi_nor_has_uniform_erase(const struct spi_nor *nor)
 
 static void spi_nor_set_4byte_opcodes(struct spi_nor *nor)
 {
+	struct spi_nor_erase_map *map = &nor->params->erase_map;
+	struct spi_nor_erase_type *erase;
+	int i;
+
 	nor->read_opcode = spi_nor_convert_3to4_read(nor->read_opcode);
 	nor->program_opcode = spi_nor_convert_3to4_program(nor->program_opcode);
 	nor->erase_opcode = spi_nor_convert_3to4_erase(nor->erase_opcode);
 
-	if (!spi_nor_has_uniform_erase(nor)) {
-		struct spi_nor_erase_map *map = &nor->params->erase_map;
-		struct spi_nor_erase_type *erase;
-		int i;
-
-		for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) {
-			erase = &map->erase_type[i];
-			erase->opcode =
-				spi_nor_convert_3to4_erase(erase->opcode);
-		}
+	for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) {
+		erase = &map->erase_type[i];
+		erase->opcode = spi_nor_convert_3to4_erase(erase->opcode);
 	}
 }
 
@@ -1537,6 +1534,35 @@ int spi_nor_erase_sector(struct spi_nor *nor, u32 addr)
 						nor->bouncebuf, nor->addr_nbytes);
 }
 
+/**
+ * spi_nor_erase_one() - erase a single sector at the given address
+ * @nor:	pointer to a 'struct spi_nor'
+ * @addr:	offset in the serial flash memory
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_erase_one(struct spi_nor *nor, u64 addr)
+{
+	int ret;
+
+	ret = spi_nor_lock_device(nor);
+	if (ret)
+		return ret;
+
+	ret = spi_nor_write_enable(nor);
+	if (ret) {
+		spi_nor_unlock_device(nor);
+		return ret;
+	}
+
+	ret = spi_nor_erase_sector(nor, addr);
+	spi_nor_unlock_device(nor);
+	if (ret)
+		return ret;
+
+	return spi_nor_wait_till_ready(nor);
+}
+
 /**
  * spi_nor_div_by_erase_size() - calculate remainder and update new dividend
  * @erase:	pointer to a structure that describes a SPI NOR erase type
@@ -1553,6 +1579,62 @@ static u64 spi_nor_div_by_erase_size(const struct spi_nor_erase_type *erase,
 	return dividend >> erase->size_shift;
 }
 
+/**
+ * spi_nor_find_smallest_erase_type() - find the smallest erase type in a mask
+ * @map:	the erase map of the SPI NOR
+ * @erase_mask:	bitmask of erase types to consider
+ *
+ * Erase types are ordered by size, with the smallest erase type at index 0.
+ *
+ * Return: pointer to the smallest active erase type, NULL otherwise.
+ */
+static const struct spi_nor_erase_type *
+spi_nor_find_smallest_erase_type(const struct spi_nor_erase_map *map,
+				 u8 erase_mask)
+{
+	int i;
+
+	for (i = 0; i < SNOR_ERASE_TYPE_MAX; i++) {
+		if (erase_mask & BIT(i) && map->erase_type[i].size)
+			return &map->erase_type[i];
+	}
+
+	return NULL;
+}
+
+/**
+ * spi_nor_is_uniform_erasable() - check if a uniform erase request is valid
+ * @nor:	pointer to a 'struct spi_nor'
+ * @instr:	pointer to 'struct erase_info'
+ *
+ * Verify that the requested address and length are aligned to the smallest
+ * supported erase size in the uniform region.
+ *
+ * Return: true if the range can be erased, false otherwise.
+ */
+static bool spi_nor_is_uniform_erasable(const struct spi_nor *nor,
+					struct erase_info *instr)
+{
+	const struct spi_nor_erase_map *map = &nor->params->erase_map;
+	const struct spi_nor_erase_type *erase;
+	u32 rem;
+
+	erase = spi_nor_find_smallest_erase_type(map,
+						 map->uniform_region.erase_mask);
+	if (unlikely(!erase))
+		return false;
+
+	spi_nor_div_by_erase_size(erase, instr->addr, &rem);
+	if (rem)
+		return false;
+
+	spi_nor_div_by_erase_size(erase, instr->len, &rem);
+	if (rem)
+		return false;
+
+	return true;
+}
+
 /**
  * spi_nor_find_best_erase_type() - find the best erase type for the given
  *				    offset in the serial flash memory and the
@@ -1604,6 +1686,39 @@ spi_nor_find_best_erase_type(const struct spi_nor_erase_map *map,
 	return NULL;
 }
 
+/**
+ * spi_nor_erase_uniform() - erase a range on a uniform flash
+ * @nor:	pointer to a 'struct spi_nor'
+ * @addr:	offset in the serial flash memory
+ * @len:	number of bytes to erase
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_erase_uniform(struct spi_nor *nor, u64 addr, u32 len)
+{
+	const struct spi_nor_erase_map *map = &nor->params->erase_map;
+	const struct spi_nor_erase_region *region = &map->uniform_region;
+	const struct spi_nor_erase_type *erase;
+	int ret;
+
+	while (len) {
+		erase = spi_nor_find_best_erase_type(map, region, addr, len);
+		if (unlikely(!erase))
+			return -EINVAL;
+
+		nor->erase_opcode = erase->opcode;
+
+		ret = spi_nor_erase_one(nor, addr);
+		if (ret)
+			return ret;
+
+		addr += erase->size;
+		len -= erase->size;
+	}
+
+	return 0;
+}
+
 /**
  * spi_nor_init_erase_cmd() - initialize an erase command
  * @region:	pointer to a structure that describes a SPI NOR erase region
@@ -1736,22 +1851,7 @@ static int spi_nor_erase_multi_sectors(struct spi_nor *nor, u64 addr, u32 len)
 			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_lock_device(nor);
-			if (ret)
-				goto destroy_erase_cmd_list;
-
-			ret = spi_nor_write_enable(nor);
-			if (ret) {
-				spi_nor_unlock_device(nor);
-				goto destroy_erase_cmd_list;
-			}
-
-			ret = spi_nor_erase_sector(nor, addr);
-			spi_nor_unlock_device(nor);
-			if (ret)
-				goto destroy_erase_cmd_list;
-
-			ret = spi_nor_wait_till_ready(nor);
+			ret = spi_nor_erase_one(nor, addr);
 			if (ret)
 				goto destroy_erase_cmd_list;
 
@@ -1823,7 +1923,7 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
 	struct spi_nor *nor = mtd_to_spi_nor(mtd);
 	u8 n_dice = nor->params->n_dice;
 	bool multi_die_erase = false;
-	u32 addr, len, rem;
+	u32 addr, len;
 	size_t die_size;
 	int ret;
 
@@ -1831,8 +1931,7 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
 			(long long)instr->len);
 
 	if (spi_nor_has_uniform_erase(nor)) {
-		div_u64_rem(instr->len, mtd->erasesize, &rem);
-		if (rem)
+		if (!spi_nor_is_uniform_erasable(nor, instr))
 			return -EINVAL;
 	}
 
@@ -1858,38 +1957,12 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
 		if (ret)
 			goto erase_err;
 
-	/* REVISIT in some cases we could speed up erasing large regions
-	 * by using SPINOR_OP_SE instead of SPINOR_OP_BE_4K.  We may have set up
-	 * to use "small sector erase", but that's not always optimal.
-	 */
-
-	/* "sector"-at-a-time erase */
 	} else if (spi_nor_has_uniform_erase(nor)) {
-		while (len) {
-			ret = spi_nor_lock_device(nor);
-			if (ret)
-				goto erase_err;
-
-			ret = spi_nor_write_enable(nor);
-			if (ret) {
-				spi_nor_unlock_device(nor);
-				goto erase_err;
-			}
-
-			ret = spi_nor_erase_sector(nor, addr);
-			spi_nor_unlock_device(nor);
-			if (ret)
-				goto erase_err;
-
-			ret = spi_nor_wait_till_ready(nor);
-			if (ret)
-				goto erase_err;
-
-			addr += mtd->erasesize;
-			len -= mtd->erasesize;
-		}
+		ret = spi_nor_erase_uniform(nor, addr, len);
+		if (ret)
+			goto erase_err;
 
-	/* erase multiple sectors */
+	/* erase multiple sectors on non-uniform flashes */
 	} else {
 		ret = spi_nor_erase_multi_sectors(nor, addr, len);
 		if (ret)
@@ -2652,8 +2725,7 @@ static int spi_nor_select_pp(struct spi_nor *nor,
  * spi_nor_select_uniform_erase() - select optimum uniform erase type
  * @map:		the erase map of the SPI NOR
  *
- * Once the optimum uniform sector erase command is found, disable all the
- * other.
+ * Select the optimum uniform sector erase type.
  *
  * Return: pointer to erase type on success, NULL otherwise.
  */
@@ -2697,11 +2769,6 @@ spi_nor_select_uniform_erase(struct spi_nor_erase_map *map)
 			/* keep iterating to find the wanted_size */
 	}
 
-	if (!erase)
-		return NULL;
-
-	/* Disable all other Sector Erase commands. */
-	map->uniform_region.erase_mask = BIT(erase - map->erase_type);
 	return erase;
 }
 
@@ -2712,14 +2779,6 @@ static int spi_nor_select_erase(struct spi_nor *nor)
 	struct mtd_info *mtd = &nor->mtd;
 	int i;
 
-	/*
-	 * The previous implementation handling Sector Erase commands assumed
-	 * that the SPI flash memory has an uniform layout then used only one
-	 * of the supported erase sizes for all Sector Erase commands.
-	 * So to be backward compatible, the new implementation also tries to
-	 * manage the SPI flash memory as uniform with a single erase sector
-	 * size, when possible.
-	 */
 	if (spi_nor_has_uniform_erase(nor)) {
 		erase = spi_nor_select_uniform_erase(map);
 		if (!erase)

---
base-commit: df415c5e1de0f1aeefacb4e6252ff98d38c04437
change-id: 20260720-spi_nor_multisize_erase-d644cfd4a5fb

Best regards,
--  
Mateusz Litwin <mateusz.litwin at nokia.com>




From md.alam at oss.qualcomm.com  Thu Aug  6 23:43:37 2026
From: md.alam at oss.qualcomm.com (Md Sadre Alam)
Date: Fri,  7 Aug 2026 12:13:37 +0530
Subject: [PATCH 0/2] mtd: spinand: winbond: add support for additional SPI-NAND devices
Message-ID: <20260807064339.1632249-1-md.alam@oss.qualcomm.com>


This series adds support for three Winbond SPI-NAND devices that
are currently not recognized by the winbond SPI-NAND driver.

The W25N01KW and W25N02LW share page geometry and ECC handling
with existing supported devices and therefore reuse the existing
OOB layout definitions and ECC status callbacks.

The W25N04LW uses a different page and spare-area organization
from the existing W25N04KV/W25N04KW devices and requires a
dedicated OOB layout description.

Md Sadre Alam (2):
  mtd: spinand: winbond: add support for W25N04LW
  mtd: spinand: winbond: Add support for W25N01KW and W25N02LW

 drivers/mtd/nand/spi/winbond.c | 50 ++++++++++++++++++++++++++++++++++
 1 file changed, 50 insertions(+)

-- 
2.34.1



From md.alam at oss.qualcomm.com  Thu Aug  6 23:43:38 2026
From: md.alam at oss.qualcomm.com (Md Sadre Alam)
Date: Fri,  7 Aug 2026 12:13:38 +0530
Subject: [PATCH 1/2] mtd: spinand: winbond: add support for W25N04LW
In-Reply-To: <20260807064339.1632249-1-md.alam@oss.qualcomm.com>
References: <20260807064339.1632249-1-md.alam@oss.qualcomm.com>
Message-ID: <20260807064339.1632249-2-md.alam@oss.qualcomm.com>

Add support for Winbond W25N04LW 4Gbit SPI-NAND.
It has 8-bit on-die ECC and a 4096+256 byte page
(4096 byte main area, 128 byte usable OOB once
on-chip ECC hides the 128 byte parity area),
organized as 2048 blocks of 64 pages each, unlike
the existing 2048 byte page W25N04KV/W25N04KW parts.

Signed-off-by: Md Sadre Alam <md.alam at oss.qualcomm.com>
---
 drivers/mtd/nand/spi/winbond.c | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
index 9b78c1e6cbc9..b0147b7cb76f 100644
--- a/drivers/mtd/nand/spi/winbond.c
+++ b/drivers/mtd/nand/spi/winbond.c
@@ -345,6 +345,29 @@ static const struct mtd_ooblayout_ops w25n02kv_ooblayout = {
 	.free = w25n02kv_ooblayout_free,
 };
 
+static int w25n04lw_ooblayout_ecc(struct mtd_info *mtd, int section,
+				  struct mtd_oob_region *region)
+{
+	return -ERANGE;
+}
+
+static int w25n04lw_ooblayout_free(struct mtd_info *mtd, int section,
+				   struct mtd_oob_region *region)
+{
+	if (section)
+		return -ERANGE;
+
+	region->offset = 2;
+	region->length = mtd->oobsize - 2;
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops w25n04lw_ooblayout = {
+	.ecc = w25n04lw_ooblayout_ecc,
+	.free = w25n04lw_ooblayout_free,
+};
+
 static int w25n01jw_ooblayout_ecc(struct mtd_info *mtd, int section,
 				  struct mtd_oob_region *region)
 {
@@ -768,6 +791,15 @@ static const struct spinand_info winbond_spinand_table[] = {
 					      &update_cache_variants),
 		     0,
 		     SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
+	SPINAND_INFO("W25N04LW", /* 1.8V */
+		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb2, 0x23),
+		     NAND_MEMORG(1, 4096, 128, 64, 2048, 40, 1, 1, 1),
+		     NAND_ECCREQ(8, 512),
+		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+					      &write_cache_variants,
+					      &update_cache_variants),
+		     0,
+		     SPINAND_ECCINFO(&w25n04lw_ooblayout, w25n02kv_ecc_get_status)),
 	SPINAND_INFO("W35N04JW", /* 1.8V */
 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x23),
 		     NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 4, 1),
-- 
2.34.1



From md.alam at oss.qualcomm.com  Thu Aug  6 23:43:39 2026
From: md.alam at oss.qualcomm.com (Md Sadre Alam)
Date: Fri,  7 Aug 2026 12:13:39 +0530
Subject: [PATCH 2/2] mtd: spinand: winbond: add support for W25N01KW and W25N02LW
In-Reply-To: <20260807064339.1632249-1-md.alam@oss.qualcomm.com>
References: <20260807064339.1632249-1-md.alam@oss.qualcomm.com>
Message-ID: <20260807064339.1632249-3-md.alam@oss.qualcomm.com>

Add support for the Winbond W25N01KW and W25N02LW SPI-NAND
devices.

The W25N01KW shares the same geometry, OOB layout, and ECC
status handling as the existing W25N01KV, while the W25N02LW
matches the existing W25N02KV/W25N02KW devices and reuses
their OOB layout and ECC status callback.

Signed-off-by: Md Sadre Alam <md.alam at oss.qualcomm.com>
---
 drivers/mtd/nand/spi/winbond.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
index b0147b7cb76f..3bffa13bfa68 100644
--- a/drivers/mtd/nand/spi/winbond.c
+++ b/drivers/mtd/nand/spi/winbond.c
@@ -704,6 +704,15 @@ static const struct spinand_info winbond_spinand_table[] = {
 					      &update_cache_variants),
 		     0,
 		     SPINAND_ECCINFO(&w25n01kv_ooblayout, w25n02kv_ecc_get_status)),
+	SPINAND_INFO("W25N01KW", /* 1.8V */
+		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xbe, 0x21),
+		     NAND_MEMORG(1, 2048, 96, 64, 1024, 20, 1, 1, 1),
+		     NAND_ECCREQ(4, 512),
+		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+					      &write_cache_variants,
+					      &update_cache_variants),
+		     0,
+		     SPINAND_ECCINFO(&w25n01kv_ooblayout, w25n02kv_ecc_get_status)),
 	SPINAND_INFO("W35N01JW", /* 1.8V */
 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdc, 0x21),
 		     NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 1, 1),
@@ -759,6 +768,15 @@ static const struct spinand_info winbond_spinand_table[] = {
 					      &update_cache_variants),
 		     0,
 		     SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
+	SPINAND_INFO("W25N02LW", /* 1.8V */
+		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x12, 0x22),
+		     NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
+		     NAND_ECCREQ(8, 512),
+		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+					      &write_cache_variants,
+					      &update_cache_variants),
+		     0,
+		     SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
 	SPINAND_INFO("W35N02JW", /* 1.8V */
 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x22),
 		     NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 2, 1),
-- 
2.34.1



From miquel.raynal at bootlin.com  Fri Aug  7 03:13:52 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:13:52 +0200
Subject: [PATCH v2 00/18] mtd: spi-nor: Massive QE handling cleanup and
 Winbond RV chips addition
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>

Hello,

Following my "software protection" enhancement series, there were
several comments about the need to simplify and reorganize the handling
of the QE bits, which is strongly impacted by the various read/write
status register capabilities.

The first patch is all about this. It is a massive cleanup, everything
is explained inside, the how and the why. This is a commit that must be
tested, so if you have a SPI NOR chip with one of the not yet tested
SFDP QER configuration, please consider applying that patch (just patch
1 is enough) and boot. If your chip is recognized, it's probably all
good.

Then I add support for JESD216 revision F and its new QER field.

Finally, I propose two Winbond chip additions (from the new RV family)
which makes use of these new capabilities. 

There will be more chips to come, but that is all I can test at the
moment.

Thanks,
Miqu?l

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
Changes in v2:
- Added support for more chips that I received and could test. The main
  logic in the first patches is completely untouched.
- Fixed the name of a parameter (s/sr2/srs/) in the low level read status
  registers helper.
- Decided that there was no specific handling of the WP pin being
  strapped wrt the read check over the SR content. This would be new,
  and I don't think it is wide to handle it in the verify helper, but
  instead this should be let to the caller to decide whether it needs
  to hard fail or just degrade into single mode (typically if the QE
  bit has not been written). But this would require an extra DT
  property indicating the WP strap state which cannot be read back by
  software.
- Created a structure to store the flash specific opcodes.
- Proposed a rename of BFPT_DWORD15_QER_SR2_BIT1_BUGGY in favour of
  the _NO_1B_WR suffix.
- After checking, decided that the .name field is legacy and not part
  of any stable API, thus it can be removed. So added back a commit
  for that, removing the names for the whole JV family.
- Exposed all opcodes through debugfs, including the QE bit position.
- OTP and SWP writes are now read back like before.
- 2 known deviations are now explicitely stated in the commit log.
- I changed a little bit the QER field parsing in the sfdp.c driver, to
  limit deviations, as much as my understanding would allow me.
- Link to v1: https://lore.kernel.org/r/20260529-winbond-v7-1-spi-nor-rv-addition-v1-0-f3ae18502d5a at bootlin.com

---
Miquel Raynal (18):
      mtd: spi-nor: Rename BFPT_DWORD15_QER_SR2_BIT1_BUGGY
      mtd: spi-nor: Create a structure containing the flash opcodes
      mtd: spi-nor: Refactor Read Status/Write Status support
      mtd: spi-nor: Add support for the new JESD216 rev F QER field
      mtd: spi-nor: Move the SFDP header structure to a C header
      mtd: spi-nor: winbond: Drop the .name entry of JV chips
      mtd: spi-nor: winbond: Add support for W25Q32RV-Q/N
      mtd: spi-nor: winbond: Add support for W25Q32RV-M
      mtd: spi-nor: winbond: Add support for W25Q64RV-Q/N
      mtd: spi-nor: winbond: Add support for W25Q64RV-M
      mtd: spi-nor: winbond: Add support for W25Q12RV-Q/N
      mtd: spi-nor: winbond: Add support for W25Q12RV-M
      mtd: spi-nor: winbond: Add support for W25Q51RV-Q/N
      mtd: spi-nor: winbond: Add support for W25Q01RV-Q/N
      mtd: spi-nor: winbond: Add support for W25Q01RV-M
      mtd: spi-nor: winbond: Add support for W25Q02RV-M
      mtd: spi-nor: winbond: W25QxxRV-Q/N/M: Add missing chips
      mtd: spi-nor: debugfs: Expose SR opcodes and QE mask

 Documentation/driver-api/mtd/spi-nor.rst |   2 +-
 drivers/mtd/spi-nor/atmel.c              |  52 +-
 drivers/mtd/spi-nor/core.c               | 790 +++++++++++--------------------
 drivers/mtd/spi-nor/core.h               |  71 ++-
 drivers/mtd/spi-nor/debugfs.c            |  19 +-
 drivers/mtd/spi-nor/gigadevice.c         |   6 +-
 drivers/mtd/spi-nor/issi.c               |   3 +-
 drivers/mtd/spi-nor/macronix.c           |  12 +-
 drivers/mtd/spi-nor/micron-st.c          |   8 +-
 drivers/mtd/spi-nor/otp.c                |  16 +-
 drivers/mtd/spi-nor/sfdp.c               |  58 ++-
 drivers/mtd/spi-nor/sfdp.h               |  21 +-
 drivers/mtd/spi-nor/spansion.c           |  20 +-
 drivers/mtd/spi-nor/sst.c                |   5 +-
 drivers/mtd/spi-nor/swp.c                |  58 +--
 drivers/mtd/spi-nor/winbond.c            |  96 ++--
 include/linux/mtd/spi-nor.h              |   5 +-
 17 files changed, 541 insertions(+), 701 deletions(-)
---
base-commit: 728e68a889bcf257b1e67298b12c360e5c3a13e0
change-id: 20260529-winbond-v7-1-spi-nor-rv-addition-cea2f1f158be
prerequisite-message-id: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794 at bootlin.com>
prerequisite-patch-id: b4c64eddd0cbb8a67a3ceb3d5a8fb7118ee8bbd3
prerequisite-patch-id: ea4fe4b14c3613b81a14c7af7ef5b7783c8cc633
prerequisite-patch-id: 4386d6ade3a3cd161669fec71550a9ccd96313f1
prerequisite-patch-id: c8a25a85853dc662591c1099350a37ff842d072b
prerequisite-patch-id: 74656376c96b96fc1b3cdc7074de6b3cb87d9ca7
prerequisite-patch-id: 90271b7e055db3079d3897546c3480a285b740c8
prerequisite-patch-id: 600c72d0a18dc003917f774209625f24c37d11d5
prerequisite-patch-id: e4543f8bf8f0fc69ce6dffc1b18858cf9ba6c04c
prerequisite-patch-id: 260a5ff56d2e5fc5583b875e0e0eca14786f23c6
prerequisite-patch-id: 2895240e7ee5b7d3f0792e7c964be474c583a4c6
prerequisite-patch-id: d429c7951d6bc134fb9f8990d72ff678b55e39d2
prerequisite-patch-id: 4733bf85a0015bf832f46909518c5c3b291824ff
prerequisite-patch-id: 876879d9ca584afcfda5ea388a39f71ba5e88847
prerequisite-patch-id: 849e3782c7a6263bce97a359f1e8521dc2b2ec77
prerequisite-patch-id: a4d9ceb8d99e527d9b9b184cbb1923c99e15fe1a
prerequisite-patch-id: 8c9246e1485d06429b1caaae7a1efb7af5122723
prerequisite-patch-id: 66880ad35a1681b724b51cb40e591270a65429ce
prerequisite-patch-id: a750eb3c350e1a88aef8749506332663fb44e007
prerequisite-patch-id: 6e9d4b20e02afb09defda703c74d56ea222d10c1
prerequisite-patch-id: 808ce773f9ecb493ba9362e088453638ee93b222
prerequisite-patch-id: 462a6751e3e9074102e8d31b23bf06140f6ee6e6
prerequisite-patch-id: 309ee450a2ac8eb24516c6839b944bc5f36a6db6
prerequisite-patch-id: 07059d14bb516ba19fc0a678a96b3224f91caf24
prerequisite-patch-id: 1e221a9bbfb1fc53982beaf75bccf48baaba491f
prerequisite-patch-id: 20ce5105adc4c7f8ebc6c779b107fd54def4f1f8
prerequisite-patch-id: cd8fddc6d97316419b52997207a198037b41b32d
prerequisite-patch-id: 759f747940c26df6ec85fecb4779fd54e0ea3735
prerequisite-patch-id: eaeebc9df5221bab68a6cabc64a97e9c2608899b

Best regards,
-- 
Miquel Raynal <miquel.raynal at bootlin.com>



From miquel.raynal at bootlin.com  Fri Aug  7 03:13:53 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:13:53 +0200
Subject: [PATCH v2 01/18] mtd: spi-nor: Rename
 BFPT_DWORD15_QER_SR2_BIT1_BUGGY
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-1-7797642dfc40@bootlin.com>

The _BUGGY suffix is not super accurate. There is a behavior that indeed
seems strange, but it is somewhat documented. The fact is, with this QER
field the status register is considered 16-bit wide and if one writes
the first 8 bits (SR1) without the next 8 bits (SR2) then the content of
SR2 will be assumed to be 0x00 and will be cleared.

Rename it BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR to align with the other
namings (_NO_RD) and clarify the meaning of this field.

No functional change.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/sfdp.c | 2 +-
 drivers/mtd/spi-nor/sfdp.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index cf0e2dc7c120..526763c79f55 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -567,7 +567,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 		params->quad_enable = NULL;
 		break;
 
-	case BFPT_DWORD15_QER_SR2_BIT1_BUGGY:
+	case BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR:
 		/*
 		 * Writing only one byte to the Status Register has the
 		 * side-effect of clearing Status Register 2.
diff --git a/drivers/mtd/spi-nor/sfdp.h b/drivers/mtd/spi-nor/sfdp.h
index f74a0eb339ea..d976c6c9a4ad 100644
--- a/drivers/mtd/spi-nor/sfdp.h
+++ b/drivers/mtd/spi-nor/sfdp.h
@@ -84,7 +84,7 @@ struct sfdp_bfpt {
  */
 #define BFPT_DWORD15_QER_MASK			GENMASK(22, 20)
 #define BFPT_DWORD15_QER_NONE			(0x0UL << 20) /* Micron */
-#define BFPT_DWORD15_QER_SR2_BIT1_BUGGY		(0x1UL << 20)
+#define BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR	(0x1UL << 20)
 #define BFPT_DWORD15_QER_SR1_BIT6		(0x2UL << 20) /* Macronix */
 #define BFPT_DWORD15_QER_SR2_BIT7		(0x3UL << 20)
 #define BFPT_DWORD15_QER_SR2_BIT1_NO_RD		(0x4UL << 20)

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:13:54 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:13:54 +0200
Subject: [PATCH v2 02/18] mtd: spi-nor: Create a structure containing the
 flash opcodes
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-2-7797642dfc40@bootlin.com>

Create a structure named opcodes in the flash parameters structures to
store all the flash specific opcodes. They are typically discovered
dynamically through SFDP parsing. Move the die_erase opcode
inside. Later on we will add new fields there, like the supported
opcodes for accessing the status registers, which are almost vendor
specific.

No fonctional change.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/core.c      |  6 +++---
 drivers/mtd/spi-nor/core.h      | 14 +++++++++++---
 drivers/mtd/spi-nor/debugfs.c   |  2 +-
 drivers/mtd/spi-nor/micron-st.c |  4 ++--
 drivers/mtd/spi-nor/spansion.c  |  4 ++--
 5 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index fef7f426a3a2..29b8fe3d1c65 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -1140,7 +1140,7 @@ static int spi_nor_erase_die(struct spi_nor *nor, loff_t addr, size_t die_size)
 
 	if (nor->spimem) {
 		struct spi_mem_op op =
-			SPI_NOR_DIE_ERASE_OP(nor->params->die_erase_opcode,
+			SPI_NOR_DIE_ERASE_OP(nor->params->opcodes.die_erase,
 					     nor->addr_nbytes, addr, multi_die);
 
 		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
@@ -3030,8 +3030,8 @@ static int spi_nor_late_init_params(struct spi_nor *nor)
 			return ret;
 	}
 
-	if (!nor->params->die_erase_opcode)
-		nor->params->die_erase_opcode = SPINOR_OP_CHIP_ERASE;
+	if (!nor->params->opcodes.die_erase)
+		nor->params->opcodes.die_erase = SPINOR_OP_CHIP_ERASE;
 
 	/* Default method kept for backward compatibility. */
 	if (!params->set_4byte_addr_mode)
diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
index ba2d1a862c9d..2cc0c80da3fa 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -340,6 +340,16 @@ struct spi_nor_otp {
 	const struct spi_nor_otp_ops *ops;
 };
 
+/**
+ * struct spi_nor_opcodes - SPI NOR flash specific opcodes.
+ * List of variable opcodes used by the chip.
+ *
+ * @die_erase: opcode for erasing a die, defaults to SPINOR_OP_CHIP_ERASE
+ */
+struct spi_nor_opcodes {
+	u8 die_erase;
+};
+
 /**
  * struct spi_nor_flash_parameter - SPI NOR flash parameters and settings.
  * Includes legacy flash parameters and settings that can be overwritten
@@ -362,7 +372,6 @@ struct spi_nor_otp {
  *			command in octal DTR mode.
  * @n_banks:		number of banks.
  * @n_dice:		number of dice in the flash memory.
- * @die_erase_opcode:	die erase opcode. Defaults to SPINOR_OP_CHIP_ERASE.
  * @vreg_offset:	volatile register offset for each die.
  * @hwcaps:		describes the read and page program hardware
  *			capabilities.
@@ -393,13 +402,12 @@ struct spi_nor_flash_parameter {
 	u8				rdsr_addr_nbytes;
 	u8				n_banks;
 	u8				n_dice;
-	u8				die_erase_opcode;
 	u32				*vreg_offset;
 
 	struct spi_nor_hwcaps		hwcaps;
 	struct spi_nor_read_command	reads[SNOR_CMD_READ_MAX];
 	struct spi_nor_pp_command	page_programs[SNOR_CMD_PP_MAX];
-
+	struct spi_nor_opcodes		opcodes;
 	struct spi_nor_erase_map        erase_map;
 	struct spi_nor_otp		otp;
 
diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index 288e2866daed..ef6f0ffcd6bb 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -146,7 +146,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
 
 	if (!(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
 		string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf));
-		seq_printf(s, " %02x (%s)\n", params->die_erase_opcode, buf);
+		seq_printf(s, " %02x (%s)\n", params->opcodes.die_erase, buf);
 	}
 
 	seq_puts(s, "\nsector map\n");
diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c
index c75b0a1cd567..031cf887318c 100644
--- a/drivers/mtd/spi-nor/micron-st.c
+++ b/drivers/mtd/spi-nor/micron-st.c
@@ -131,7 +131,7 @@ static int micron_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->opcodes.die_erase = SPINOR_OP_MT_DIE_ERASE;
 	params->n_dice = 4;
 
 	/*
@@ -147,7 +147,7 @@ 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->opcodes.die_erase = SPINOR_OP_MT_DIE_ERASE;
 	params->n_dice = 2;
 
 	/*
diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c
index 15dcec09332a..2a8238c707e1 100644
--- a/drivers/mtd/spi-nor/spansion.c
+++ b/drivers/mtd/spi-nor/spansion.c
@@ -675,7 +675,7 @@ static int s25hx_t_late_init(struct spi_nor *nor)
 	cypress_nor_ecc_init(nor);
 
 	if (params->n_dice > 1)
-		params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE;
+		params->opcodes.die_erase = SPINOR_OP_CYPRESS_DIE_ERASE;
 
 	return 0;
 }
@@ -763,7 +763,7 @@ static int s28hx_t_late_init(struct spi_nor *nor)
 	cypress_nor_ecc_init(nor);
 
 	if (params->n_dice > 1)
-		params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE;
+		params->opcodes.die_erase = SPINOR_OP_CYPRESS_DIE_ERASE;
 
 	return 0;
 }

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:13:55 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:13:55 +0200
Subject: [PATCH v2 03/18] mtd: spi-nor: Refactor Read Status/Write Status
 support
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-3-7797642dfc40@bootlin.com>

SPI NOR has a lot of history. Additions over additions, the subtleties
of the JESD216 specification, their implementations by the manufacturers
and the hardware mistakes have generated a gigantic maze, let's try to
understand what is really needed.

The specification explains the QE (Quad Enable) bitfield as describing
where the bit to enable the Quad capability is, but also how to set
it. Unfortunately, the specification is not precise about what opcodes
are supported exactly in all the cases. There is an introduction that
basically states:
- Opcode 0x05 reads SR1
- Opcode 0x35 reads SR2
- Opcode 0x01 writes SR1, and then SR2 if another byte is written

Then the bitfield, among indicating the location of the QE bit, may
indicate:
- Reading SR1 and SR2 in one operation is not possible (loops over the
  content of SR1)
- Reading SR2 directly is possible
- Only writing SR1 zeroes SR2.

One problem with the current implementation, is that it only focuses on
the QE bit. A quad_enable function was created for each case, even
though in practice, the logic was always the same: read, modify, write,
read back and verify. One problem comes when other features need to play
with the status registers, like software block protection or OTP: you
never know how to properly handle the QE bit, nor where it is, nor how
to read/write the Status registers. This lead to
approximations/guessing (in swp.c, otp.c and obviously in legacy
controller drivers like atmel.c) but also to the implementation of a
gazillon of helpers for reading/writing/checking the status registers.

In addition, I believe some design decisions had a negative impact over
the years.

- All possible situations had to be flagged by the core. This is likely
  wrong, because we no longer know why we need specific quirks. It was
  ineherent to the state of the SPI NOR core before the great cleanup
  that had happened the past few years. I believe this creates confusion
  in the core today, and we should push this to vendor fixups
  instead. As an example, in 2023 Hsin-Yi was facing an issue because
  his chip was setting the wrong QER value, leading to RDCR being
  prevented, thus falling into a condition blindly setting a random QE
  bit (I strongy believe it is done like that for wrong reasons). His
  chip actually had RDCR support! The correct fix should have been to
  mark the capability in a device fixup instead of handling this in the
  core.

  Link: https://lore.kernel.org/lkml/CAJMQK-hR0eaO0b4Vd0U8_KAndLyZapqdHjVLAoe42rWi9rdLkA at mail.gmail.com/

- SFDP parsing is over cautious. I believe
  BFPT_DWORD15_QER_SR2_BIT1_NO_RD is abusive (nothing states that RDCR is
  not supported), and BFPT_DWORD15_QER_SR2_BIT1 is also out of
  specification when forcing 16-bit Status writes.

- SNOR_F_HAS_16BIT_SR is only imposing 16bit Status writes, whereas
  reads can still be 8-bit wide.

- The usage of helpers verifying the writes was also spread for IMHO no
  really good reason. Why shouldn't we trust spi operations when it
  comes to Status Registers? We do not read back our page reads, so why
  status registers should be treated with so much care, if it's not
  because we are unsure of what is being done? My proposal includes a
  check when it comes to the QE bit (done once) but we don't need these
  checks otherwise. If the QE bit was written properly, there are high
  chances that the other register accesses will just be fine, no?

Asde from my main quest, I also observed no good reason to ask the
read/write status register callers to use nor->bouncebuf while the
low-level helpers could do it themselves (we are talking about one or
two bytes being copied).

So after these observations, my proposal is the following:
- Create private low level helpers that just read or write a status
  register. They are flexible, we can give the opcode (which varies
  based on the SFDP QER field) and the length (1 or 2).
- Create public generic accessors which will be used to read/write sr1
  and/or sr2. This is where all the cleverness shall be. The helpers use
  the available opcodes for a given chip in order to fullfill the
  request.
- Provide a single generic ->quad_enable() hook which generically does
  all the steps mentioned above (read, modify, write, read back and
  verify).
- Create a list of opcodes for all 6 possible situations:
  {read, write} {sr1, sr2, sr1 and sr2}. These opcodes are
  filled/cleared based on the QER field. An opcode set to 0 indicates
  the absence of support (there is no 0x00 opcode in SPI NOR).

I tried my best to analyze the current behavior and to mimic it as much
as possible, but this is a risky cleanup. However, if we go for this, it
will be *much* easier in the future to handle all kind of chip
variations. We won't be limited to a couple of flags anymore, but rather
we'll be able to just disable a read or write capability using a single
line.

Known deviations:
- The Atmel manufacturer driver drives non SFDP chips which by default
  were receiving the HAS_16B_WR flag, forcing SR2 writes all the times,
  but it was deliberately making single bytes writes to SR1 for its
  locking operations. The flag is likely wrong for them, so I removed
  it.
- I am proposing on purpose a single quad_enable helper. It should match
  all the cases, with 1 identified difference which I believe is
  harmless: in the SR2_BIT7 case, the helper would make an extra SR1
  read/write which was not done before. This entry explicitly supports
  reading and writing the two registers. Note: no manufacturer has been
  identified to actually use that entry yet.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
This is a really hard to review patch, it is big, full of changes
everywhere, but I cannot really split it further. The commit log is
really important, it tries to explain the steps I went through when
trying to refactor the whole QE/SR handling for which I received
feedback from Michael, Tudor and Sashiko.

Ideally we could expect test coverage of each case:
- BFPT_DWORD15_QER_NONE: Probably not needed
- BFPT_DWORD15_QER_SR2_BIT1_BUGGY: Expected from Hsin-Yi (flag is being
  renamed _NO_1B_WR)
- BFPT_DWORD15_QER_SR2_BIT1_NO_RD: Tested with a chip re-enabling RDCR
  in a fixup
- BFPT_DWORD15_QER_SR1_BIT6: TBD
- BFPT_DWORD15_QER_SR2_BIT7: TBD
- BFPT_DWORD15_QER_SR2_BIT1: TBD
- BFPT_DWORD15_QER_SR2_BIT1_1B: Done.
There will probably be breakages on older chips. These cannot be
guessed because they are not properly listed in manufacturer
fixups (yet). If we want a cleanup/simplification, we will have to cope
with this risk.

I hope the diff stats will motivate people to have a look and report
their testing.

I will also eagerly monitor Sashiko's output which will probably be
very useful to catch niche weird cases where these changes might break.
---
 Documentation/driver-api/mtd/spi-nor.rst |   2 +-
 drivers/mtd/spi-nor/atmel.c              |  52 +-
 drivers/mtd/spi-nor/core.c               | 784 +++++++++++--------------------
 drivers/mtd/spi-nor/core.h               |  57 +--
 drivers/mtd/spi-nor/debugfs.c            |   2 -
 drivers/mtd/spi-nor/gigadevice.c         |   6 +-
 drivers/mtd/spi-nor/issi.c               |   3 +-
 drivers/mtd/spi-nor/macronix.c           |  12 +-
 drivers/mtd/spi-nor/micron-st.c          |   4 +-
 drivers/mtd/spi-nor/otp.c                |  16 +-
 drivers/mtd/spi-nor/sfdp.c               |  41 +-
 drivers/mtd/spi-nor/spansion.c           |  16 +-
 drivers/mtd/spi-nor/sst.c                |   5 +-
 drivers/mtd/spi-nor/swp.c                |  58 +--
 drivers/mtd/spi-nor/winbond.c            |   4 +-
 include/linux/mtd/spi-nor.h              |   4 -
 16 files changed, 416 insertions(+), 650 deletions(-)

diff --git a/Documentation/driver-api/mtd/spi-nor.rst b/Documentation/driver-api/mtd/spi-nor.rst
index 747a326fb6c0..5441fa7d4f04 100644
--- a/Documentation/driver-api/mtd/spi-nor.rst
+++ b/Documentation/driver-api/mtd/spi-nor.rst
@@ -118,7 +118,7 @@ section, after the ``---`` marker.
     write size		1
     page size		256
     address nbytes	3
-    flags		HAS_LOCK | HAS_16BIT_SR | SOFT_RESET | SWP_IS_VOLATILE
+    flags		HAS_LOCK | SOFT_RESET | SWP_IS_VOLATILE
 
     opcodes
      read		0xeb
diff --git a/drivers/mtd/spi-nor/atmel.c b/drivers/mtd/spi-nor/atmel.c
index 82c592f0a1e1..8741c0bda497 100644
--- a/drivers/mtd/spi-nor/atmel.c
+++ b/drivers/mtd/spi-nor/atmel.c
@@ -23,18 +23,28 @@ static int at25fs_nor_lock(struct spi_nor *nor, loff_t ofs, u64 len)
 
 static int at25fs_nor_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
 {
+	/* Write 0x00 to the status register to disable write protection */
+	u8 sr = 0;
 	int ret;
 
 	/* We only support unlocking the whole flash array */
 	if (ofs || len != nor->params->size)
 		return -EINVAL;
 
-	/* Write 0x00 to the status register to disable write protection */
-	ret = spi_nor_write_sr_and_check(nor, 0);
+	ret = spi_nor_write_sr1(nor, &sr);
 	if (ret)
-		dev_dbg(nor->dev, "unable to clear BP bits, WP# asserted?\n");
+		return ret;
 
-	return ret;
+	ret = spi_nor_read_sr1(nor, &sr);
+	if (ret)
+		return ret;
+
+	if (sr) {
+		dev_dbg(nor->dev, "unable to clear BP bits, WP# asserted?\n");
+		return -EIO;
+	}
+
+	return 0;
 }
 
 static int at25fs_nor_is_locked(struct spi_nor *nor, loff_t ofs, u64 len)
@@ -50,6 +60,7 @@ static const struct spi_nor_locking_ops at25fs_nor_locking_ops = {
 
 static int at25fs_nor_late_init(struct spi_nor *nor)
 {
+	nor->params->opcodes.write_sr1 = SPINOR_OP_WRSR;
 	nor->params->locking_ops = &at25fs_nor_locking_ops;
 
 	return 0;
@@ -69,6 +80,7 @@ static const struct spi_nor_fixups at25fs_nor_fixups = {
  * Return: 0 on success, -error otherwise.
  */
 static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs,
+
 					   u64 len, bool is_protect)
 {
 	int ret;
@@ -78,19 +90,24 @@ static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs,
 	if (ofs || len != nor->params->size)
 		return -EINVAL;
 
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr1(nor, &sr);
 	if (ret)
 		return ret;
 
-	sr = nor->bouncebuf[0];
-
 	/* SRWD bit needs to be cleared, otherwise the protection doesn't change */
 	if (sr & SR_SRWD) {
 		sr &= ~SR_SRWD;
-		ret = spi_nor_write_sr_and_check(nor, sr);
-		if (ret) {
-			dev_dbg(nor->dev, "unable to clear SRWD bit, WP# asserted?\n");
+		ret = spi_nor_write_sr1(nor, &sr);
+		if (ret)
 			return ret;
+
+		ret = spi_nor_read_sr1(nor, &sr);
+		if (ret)
+			return ret;
+
+		if (sr & SR_SRWD) {
+			dev_dbg(nor->dev, "unable to clear SRWD bit, WP# asserted?\n");
+			return -EIO;
 		}
 	}
 
@@ -108,14 +125,7 @@ static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs,
 		sr &= ~ATMEL_SR_GLOBAL_PROTECT_MASK;
 	}
 
-	nor->bouncebuf[0] = sr;
-
-	/*
-	 * We cannot use the spi_nor_write_sr_and_check() because this command
-	 * isn't really setting any bits, instead it is an pseudo command for
-	 * "Global Unprotect" or "Global Protect"
-	 */
-	return spi_nor_write_sr(nor, nor->bouncebuf, 1);
+	return spi_nor_write_sr1(nor, &sr);
 }
 
 static int atmel_nor_global_protect(struct spi_nor *nor, loff_t ofs, u64 len)
@@ -131,16 +141,17 @@ static int atmel_nor_global_unprotect(struct spi_nor *nor, loff_t ofs, u64 len)
 static int atmel_nor_is_global_protected(struct spi_nor *nor, loff_t ofs,
 					 u64 len)
 {
+	u8 sr;
 	int ret;
 
 	if (ofs >= nor->params->size || (ofs + len) > nor->params->size)
 		return -EINVAL;
 
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr1(nor, &sr);
 	if (ret)
 		return ret;
 
-	return ((nor->bouncebuf[0] & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK);
+	return ((sr & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK);
 }
 
 static const struct spi_nor_locking_ops atmel_nor_global_protection_ops = {
@@ -151,6 +162,7 @@ static const struct spi_nor_locking_ops atmel_nor_global_protection_ops = {
 
 static int atmel_nor_global_protection_late_init(struct spi_nor *nor)
 {
+	nor->params->opcodes.write_sr1 = SPINOR_OP_WRSR;
 	nor->params->locking_ops = &atmel_nor_global_protection_ops;
 
 	return 0;
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 29b8fe3d1c65..ac7812562047 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -443,75 +443,6 @@ int spi_nor_read_id(struct spi_nor *nor, u8 naddr, u8 ndummy, u8 *id,
 	return ret;
 }
 
-/**
- * spi_nor_read_sr() - Read the Status Register.
- * @nor:	pointer to 'struct spi_nor'.
- * @sr:		pointer to a DMA-able buffer where the value of the
- *              Status Register will be written. Should be at least 2 bytes.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_read_sr(struct spi_nor *nor, u8 *sr)
-{
-	int ret;
-
-	if (nor->spimem) {
-		struct spi_mem_op op = SPI_NOR_RDSR_OP(sr);
-
-		if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) {
-			op.addr.nbytes = nor->params->rdsr_addr_nbytes;
-			op.dummy.nbytes = nor->params->rdsr_dummy;
-			/*
-			 * We don't want to read only one byte in DTR mode. So,
-			 * read 2 and then discard the second byte.
-			 */
-			op.data.nbytes = 2;
-		}
-
-		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
-
-		ret = spi_mem_exec_op(nor->spimem, &op);
-	} else {
-		ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDSR, sr,
-						      1);
-	}
-
-	if (ret)
-		dev_dbg(nor->dev, "error %d reading SR\n", ret);
-
-	return ret;
-}
-
-/**
- * spi_nor_read_cr() - Read the Configuration Register using the
- * SPINOR_OP_RDCR (35h) command.
- * @nor:	pointer to 'struct spi_nor'
- * @cr:		pointer to a DMA-able buffer where the value of the
- *              Configuration Register will be written.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_read_cr(struct spi_nor *nor, u8 *cr)
-{
-	int ret;
-
-	if (nor->spimem) {
-		struct spi_mem_op op = SPI_NOR_RDCR_OP(cr);
-
-		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
-
-		ret = spi_mem_exec_op(nor->spimem, &op);
-	} else {
-		ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDCR, cr,
-						      1);
-	}
-
-	if (ret)
-		dev_dbg(nor->dev, "error %d reading CR\n", ret);
-
-	return ret;
-}
-
 /**
  * spi_nor_set_4byte_addr_mode_en4b_ex4b() - Enter/Exit 4-byte address mode
  *			using SPINOR_OP_EN4B/SPINOR_OP_EX4B. Typically used by
@@ -617,12 +548,13 @@ int spi_nor_set_4byte_addr_mode_brwr(struct spi_nor *nor, bool enable)
 int spi_nor_sr_ready(struct spi_nor *nor)
 {
 	int ret;
+	u8 sr;
 
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr1(nor, &sr);
 	if (ret)
 		return ret;
 
-	return !(nor->bouncebuf[0] & SR_WIP);
+	return !(sr & SR_WIP);
 }
 
 /**
@@ -784,345 +716,297 @@ int spi_nor_global_block_unlock(struct spi_nor *nor)
 }
 
 /**
- * spi_nor_write_sr() - Write the Status Register.
+ * spi_nor_read_sr_ll() - Low-level Status Registers read.
  * @nor:	pointer to 'struct spi_nor'.
- * @sr:		pointer to DMA-able buffer to write to the Status Register.
- * @len:	number of bytes to write to the Status Register.
+ * @opcode:	opcode for the status register read operation.
+ * @sr:		pointer to buffer for storing the content of the status registers.
+ * @len:	number of status registers to read (1 or 2).
  *
  * Return: 0 on success, -errno otherwise.
  */
-int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len)
+static int spi_nor_read_sr_ll(struct spi_nor *nor, u8 opcode, u8 *sr,
+			      unsigned int len)
 {
-	int ret;
+	int ret, i;
 
-	ret = spi_nor_write_enable(nor);
-	if (ret)
-		return ret;
+	if (len > 2)
+		return -EINVAL;
+
+	for (i = 0; i < len; i++)
+		nor->bouncebuf[i] = 0;
 
 	if (nor->spimem) {
-		struct spi_mem_op op = SPI_NOR_WRSR_OP(sr, len);
+		struct spi_mem_op op = SPI_NOR_RDSR_OP(opcode, nor->bouncebuf, len);
+
+		if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) {
+			op.addr.nbytes = nor->params->rdsr_addr_nbytes;
+			op.dummy.nbytes = nor->params->rdsr_dummy;
+			/*
+			 * We don't want to read only one byte in DTR mode. So,
+			 * read 2 and then discard the second byte.
+			 */
+			op.data.nbytes = 2;
+		}
 
 		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
 
 		ret = spi_mem_exec_op(nor->spimem, &op);
 	} else {
-		ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WRSR, sr,
-						       len);
+		ret = spi_nor_controller_ops_read_reg(nor, opcode, nor->bouncebuf, len);
 	}
 
-	if (ret) {
-		dev_dbg(nor->dev, "error %d writing SR\n", ret);
-		return ret;
-	}
-
-	return spi_nor_wait_till_ready(nor);
-}
-
-/**
- * spi_nor_write_sr1_and_check() - Write one byte to the Status Register 1 and
- * ensure that the byte written match the received value.
- * @nor:	pointer to a 'struct spi_nor'.
- * @sr1:	byte value to be written to the Status Register.
- *
- * Return: 0 on success, -errno otherwise.
- */
-static int spi_nor_write_sr1_and_check(struct spi_nor *nor, u8 sr1)
-{
-	int ret;
-
-	nor->bouncebuf[0] = sr1;
-
-	ret = spi_nor_write_sr(nor, nor->bouncebuf, 1);
-	if (ret)
-		return ret;
-
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
-	if (ret)
-		return ret;
-
-	if (nor->bouncebuf[0] != sr1) {
-		dev_dbg(nor->dev, "SR1: read back test failed\n");
-		return -EIO;
-	}
-
-	return 0;
-}
-
-/**
- * spi_nor_write_16bit_sr_and_check() - Write the Status Register 1 and the
- * Status Register 2 in one shot. Ensure that the byte written in the Status
- * Register 1 match the received value, and that the 16-bit Write did not
- * affect what was already in the Status Register 2.
- * @nor:	pointer to a 'struct spi_nor'.
- * @sr1:	byte value to be written to the Status Register 1.
- *
- * Return: 0 on success, -errno otherwise.
- */
-static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1)
-{
-	int ret;
-	u8 *sr_cr = nor->bouncebuf;
-	u8 cr_written;
-
-	/* Make sure we don't overwrite the contents of Status Register 2. */
-	if (!(nor->flags & SNOR_F_NO_READ_CR)) {
-		ret = spi_nor_read_cr(nor, &sr_cr[1]);
-		if (ret)
-			return ret;
-	} else if ((spi_nor_get_protocol_width(nor->read_proto) == 4 ||
-		    spi_nor_get_protocol_width(nor->write_proto) == 4) &&
-		   nor->params->quad_enable) {
-		/*
-		 * If the Status Register 2 Read command (35h) is not
-		 * supported, we should at least be sure we don't
-		 * change the value of the SR2 Quad Enable bit.
-		 *
-		 * When the Quad Enable method is set and the buswidth is 4, we
-		 * can safely assume that the value of the QE bit is one, as a
-		 * consequence of the nor->params->quad_enable() call.
-		 *
-		 * According to the JESD216 revB standard, BFPT DWORDS[15],
-		 * bits 22:20, the 16-bit Write Status (01h) command is
-		 * available just for the cases in which the QE bit is
-		 * described in SR2 at BIT(1).
-		 */
-		sr_cr[1] = SR2_QUAD_EN_BIT1;
-	} else {
-		sr_cr[1] = 0;
-	}
-
-	sr_cr[0] = sr1;
-
-	ret = spi_nor_write_sr(nor, sr_cr, 2);
-	if (ret)
-		return ret;
-
-	ret = spi_nor_read_sr(nor, sr_cr);
-	if (ret)
-		return ret;
-
-	if (sr1 != sr_cr[0]) {
-		dev_dbg(nor->dev, "SR: Read back test failed\n");
-		return -EIO;
-	}
-
-	if (nor->flags & SNOR_F_NO_READ_CR)
-		return 0;
-
-	cr_written = sr_cr[1];
-
-	ret = spi_nor_read_cr(nor, &sr_cr[1]);
-	if (ret)
-		return ret;
-
-	if (cr_written != sr_cr[1]) {
-		dev_dbg(nor->dev, "CR: read back test failed\n");
-		return -EIO;
-	}
-
-	return 0;
-}
-
-/**
- * spi_nor_write_16bit_cr_and_check() - Write the Status Register 1 and the
- * Configuration Register in one shot. Ensure that the byte written in the
- * Configuration Register match the received value, and that the 16-bit Write
- * did not affect what was already in the Status Register 1.
- * @nor:	pointer to a 'struct spi_nor'.
- * @cr:		byte value to be written to the Configuration Register.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr)
-{
-	int ret;
-	u8 *sr_cr = nor->bouncebuf;
-	u8 sr_written;
-
-	/* Keep the current value of the Status Register 1. */
-	ret = spi_nor_read_sr(nor, sr_cr);
-	if (ret)
-		return ret;
-
-	sr_cr[1] = cr;
-
-	ret = spi_nor_write_sr(nor, sr_cr, 2);
-	if (ret)
-		return ret;
-
-	sr_written = sr_cr[0];
-
-	ret = spi_nor_read_sr(nor, sr_cr);
-	if (ret)
-		return ret;
-
-	if (sr_written != sr_cr[0]) {
-		dev_dbg(nor->dev, "SR: Read back test failed\n");
-		return -EIO;
-	}
-
-	if (nor->flags & SNOR_F_NO_READ_CR)
-		return 0;
-
-	ret = spi_nor_read_cr(nor, &sr_cr[1]);
-	if (ret)
-		return ret;
-
-	if (cr != sr_cr[1]) {
-		dev_dbg(nor->dev, "CR: read back test failed\n");
-		return -EIO;
-	}
-
-	return 0;
-}
-
-/**
- * spi_nor_write_16bit_sr_cr_and_check() - Write the Status Register 1 and the
- * Configuration Register in one shot. Ensure that the bytes written in both
- * registers match the received value.
- * @nor:	pointer to a 'struct spi_nor'.
- * @regs:	two-byte array with values to be written to the status and
- *		configuration registers.
- *
- * Return: 0 on success, -errno otherwise.
- */
-static int spi_nor_write_16bit_sr_cr_and_check(struct spi_nor *nor, const u8 *regs)
-{
-	u8 written_regs[2];
-	int ret;
-
-	written_regs[0] = regs[0];
-	written_regs[1] = regs[1];
-	nor->bouncebuf[0] = regs[0];
-	nor->bouncebuf[1] = regs[1];
-
-	ret = spi_nor_write_sr(nor, nor->bouncebuf, 2);
-	if (ret)
-		return ret;
-
-	ret = spi_nor_read_sr(nor, &nor->bouncebuf[0]);
-	if (ret)
-		return ret;
-
-	if (written_regs[0] != nor->bouncebuf[0]) {
-		dev_dbg(nor->dev, "SR: Read back test failed\n");
-		return -EIO;
-	}
-
-	if (nor->flags & SNOR_F_NO_READ_CR)
-		return 0;
-
-	ret = spi_nor_read_cr(nor, &nor->bouncebuf[1]);
-	if (ret)
-		return ret;
-
-	if (written_regs[1] != nor->bouncebuf[1]) {
-		dev_dbg(nor->dev, "CR: read back test failed\n");
-		return -EIO;
-	}
-
-	return 0;
-}
-
-/**
- * spi_nor_write_sr_and_check() - Write the Status Register 1 and ensure that
- * the byte written match the received value without affecting other bits in the
- * Status Register 1 and 2.
- * @nor:	pointer to a 'struct spi_nor'.
- * @sr1:	byte value to be written to the Status Register.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1)
-{
-	if (nor->flags & SNOR_F_HAS_16BIT_SR)
-		return spi_nor_write_16bit_sr_and_check(nor, sr1);
-
-	return spi_nor_write_sr1_and_check(nor, sr1);
-}
-
-/**
- * spi_nor_write_sr_cr_and_check() - Write the Status Register 1 and ensure that
- * the byte written match the received value. Same for the Control Register if
- * available.
- * @nor:	pointer to a 'struct spi_nor'.
- * @regs:	byte array to be written to the registers.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs)
-{
-	if (nor->flags & SNOR_F_HAS_16BIT_SR)
-		return spi_nor_write_16bit_sr_cr_and_check(nor, regs);
-
-	return spi_nor_write_sr1_and_check(nor, regs[0]);
-}
-
-/**
- * spi_nor_write_sr2() - Write the Status Register 2 using the
- * SPINOR_OP_WRSR2 (3eh) command.
- * @nor:	pointer to 'struct spi_nor'.
- * @sr2:	pointer to DMA-able buffer to write to the Status Register 2.
- *
- * Return: 0 on success, -errno otherwise.
- */
-static int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2)
-{
-	int ret;
-
-	ret = spi_nor_write_enable(nor);
-	if (ret)
-		return ret;
-
-	if (nor->spimem) {
-		struct spi_mem_op op = SPI_NOR_WRSR2_OP(sr2);
-
-		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
-
-		ret = spi_mem_exec_op(nor->spimem, &op);
-	} else {
-		ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WRSR2,
-						       sr2, 1);
-	}
-
-	if (ret) {
-		dev_dbg(nor->dev, "error %d writing SR2\n", ret);
-		return ret;
-	}
-
-	return spi_nor_wait_till_ready(nor);
-}
-
-/**
- * spi_nor_read_sr2() - Read the Status Register 2 using the
- * SPINOR_OP_RDSR2 (3fh) command.
- * @nor:	pointer to 'struct spi_nor'.
- * @sr2:	pointer to DMA-able buffer where the value of the
- *		Status Register 2 will be written.
- *
- * Return: 0 on success, -errno otherwise.
- */
-static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2)
-{
-	int ret;
-
-	if (nor->spimem) {
-		struct spi_mem_op op = SPI_NOR_RDSR2_OP(sr2);
-
-		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
-
-		ret = spi_mem_exec_op(nor->spimem, &op);
-	} else {
-		ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDSR2, sr2,
-						      1);
-	}
+	memcpy(sr, nor->bouncebuf, len);
 
 	if (ret)
-		dev_dbg(nor->dev, "error %d reading SR2\n", ret);
+		dev_dbg(nor->dev, "Error %d reading Status Registers\n", ret);
 
 	return ret;
 }
 
+/**
+ * spi_nor_write_sr_ll() - Low-level Status Registers write.
+ * @nor:	pointer to 'struct spi_nor'.
+ * @opcode:	opcode for the status register write operation.
+ * @sr:		pointer to status registers buffer to write.
+ * @len:	number of status registers to write.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_write_sr_ll(struct spi_nor *nor, u8 opcode,	const u8 *sr,
+			       unsigned int len)
+{
+	int ret, i;
+
+	if (len > 2)
+		return -EINVAL;
+
+	ret = spi_nor_write_enable(nor);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < len; i++)
+		nor->bouncebuf[i] = sr[i];
+
+	if (nor->spimem) {
+		struct spi_mem_op op = SPI_NOR_WRSR_OP(opcode,
+						       nor->bouncebuf, len);
+
+		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
+
+		ret = spi_mem_exec_op(nor->spimem, &op);
+	} else {
+		ret = spi_nor_controller_ops_write_reg(nor, opcode,
+						       nor->bouncebuf, len);
+	}
+
+	if (ret) {
+		dev_dbg(nor->dev, "Error %d writing Status Registers\n", ret);
+		return ret;
+	}
+
+	return spi_nor_wait_till_ready(nor);
+}
+
+/**
+ * spi_nor_read_sr1() - Read SR1 only
+ * Useful for:
+ *   - The core to offer a generic "read SR1 and SR2" capability
+ *   - Manufacturer drivers (since they know the chip SR layout)
+ *   - Polling the BUSY bit in the core
+ *
+ * @nor: the spi_nor structure
+ * @sr1: pointer to a valid SR1 buffer
+ *
+ * Return 0 or errno.
+ */
+int spi_nor_read_sr1(struct spi_nor *nor, u8 *sr1)
+{
+	return spi_nor_read_sr_ll(nor, nor->params->opcodes.read_sr1, sr1, 1);
+}
+
+/**
+ * spi_nor_read_sr2() - Read SR2 only
+ * Useful for:
+ *   - The core to offer a generic "read SR1 and SR2" capability
+ *   - Manufacturer drivers (since they know the chip SR layout)
+ *   - SR2 based OTP configuration
+ *
+ * @nor: the spi_nor structure
+ * @sr2: pointer to a valid SR2 buffer
+ *
+ * Return 0 or errno.
+ */
+int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2)
+{
+	struct spi_nor_flash_parameter *params = nor->params;
+	int ret;
+
+	if (params->opcodes.read_sr2) {
+		ret = spi_nor_read_sr_ll(nor, params->opcodes.read_sr2, sr2, 1);
+		if (ret)
+			return ret;
+	} else if (spi_nor_get_protocol_width(nor->read_proto) == 4 ||
+		   spi_nor_get_protocol_width(nor->write_proto) == 4) {
+		/* Make sure the QE bit is persistently kept */
+		*sr2 = params->qe_mask[1];
+	} else {
+		*sr2 = 0;
+	}
+
+	return 0;
+}
+
+/**
+ * spi_nor_read_sr1_and_sr2() - Read SR1 then SR2
+ * General purpose helper, always safe to call.
+ *
+ * @nor: the spi_nor structure
+ * @sr: pointer to a valid 2-byte array
+ *
+ * Return 0 or errno.
+ */
+int spi_nor_read_sr1_and_sr2(struct spi_nor *nor, u8 *sr)
+{
+	int ret;
+
+	ret = spi_nor_read_sr1(nor, &sr[0]);
+	if (ret)
+		return ret;
+
+	return spi_nor_read_sr2(nor, &sr[1]);
+}
+
+/**
+ * spi_nor_write_sr1() - Write SR1 only
+ * Useful for:
+ *   - Manufacturer drivers (since they know the chip SR layout)
+ *
+ * @nor: the spi_nor structure
+ * @sr1: pointer to a valid SR1 buffer
+ *
+ * Return 0 or errno.
+ */
+int spi_nor_write_sr1(struct spi_nor *nor, const u8 *sr1)
+{
+	if (WARN_ONCE(!nor->params->opcodes.write_sr1,
+		      "Restricted helper use, write SR1 not supported"))
+		return -EIO;
+
+	return spi_nor_write_sr_ll(nor, nor->params->opcodes.write_sr1, sr1, 1);
+}
+
+/**
+ * spi_nor_write_sr2() - Write SR2 only
+ * Useful for:
+ *   - Manufacturer drivers (since they know the chip SR layout)
+ *   - SR2 based OTP configuration
+ *
+ * @nor: the spi_nor structure
+ * @sr2: pointer to a valid SR2 buffer
+ *
+ * Return 0 or errno.
+ */
+int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2)
+{
+	if (WARN_ONCE(!nor->params->opcodes.write_sr2,
+		      "Restricted helper use, write SR2 not supported"))
+		return -EIO;
+
+	return spi_nor_write_sr_ll(nor, nor->params->opcodes.write_sr2, sr2, 1);
+}
+
+/**
+ * spi_nor_write_sr1_and_sr2() - Write SR1 then SR2
+ * General purpose helper, always safe to call. Will expectedly ignore
+ * SR2 on certain chips.
+ *
+ * @nor: the spi_nor structure
+ * @sr: pointer to a valid 2-byte array
+ *
+ * Return 0 or errno.
+ */
+static int spi_nor_write_sr1_and_sr2(struct spi_nor *nor, const u8 *sr)
+{
+	struct spi_nor_flash_parameter *params = nor->params;
+	int ret;
+
+	if (params->opcodes.write_sr1_and_sr2)
+		return spi_nor_write_sr_ll(nor,
+					   params->opcodes.write_sr1_and_sr2,
+					   sr, 2);
+
+	ret = spi_nor_write_sr1(nor, &sr[0]);
+	if (ret)
+		return ret;
+
+	if (params->opcodes.write_sr2)
+		return spi_nor_write_sr2(nor, &sr[1]);
+
+	return 0;
+}
+
+/**
+ * spi_nor_write_sr1_and_sr2_and_check() - Write SR1 then SR2, then read
+ *					   them back and verifies
+ * General purpose helper, always safe to call. Will expectedly ignore
+ * SR2 on certain chips.
+ *
+ * @nor: the spi_nor structure
+ * @sr: pointer to a valid 2-byte array
+ *
+ * Return 0 or errno.
+ */
+int spi_nor_write_sr1_and_sr2_and_check(struct spi_nor *nor, const u8 *sr)
+{
+	u8 tmp[2];
+	int ret;
+
+	ret = spi_nor_write_sr1_and_sr2(nor, sr);
+	if (ret)
+		return ret;
+
+	ret = spi_nor_read_sr1_and_sr2(nor, tmp);
+	if (ret)
+		return ret;
+
+	if (sr[0] != tmp[0] || sr[1] != tmp[1])
+		return -EIO;
+
+	return 0;
+}
+
+/**
+ * spi_nor_generic_quad_enable() - Read the status registers,
+ *				   apply the QE bit mask,
+ *				   write the status registers,
+ *				   read them back and check the content.
+ *
+ * @nor:	pointer to 'struct spi_nor'
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_generic_quad_enable(struct spi_nor *nor)
+{
+	u8 *qe_mask = nor->params->qe_mask;
+	u8 sr[2] = {};
+	int ret;
+
+	if (!qe_mask[0] && !qe_mask[1])
+		return 0;
+
+	ret = spi_nor_read_sr1_and_sr2(nor, sr);
+	if (ret)
+		return ret;
+
+	if (sr[0] & qe_mask[0] || sr[1] & qe_mask[1])
+		return 0;
+
+	sr[0] |= qe_mask[0];
+	sr[1] |= qe_mask[1];
+
+	return spi_nor_write_sr1_and_sr2_and_check(nor, sr);
+}
+
 /**
  * spi_nor_erase_die() - Erase the entire die.
  * @nor:	pointer to 'struct spi_nor'.
@@ -1904,106 +1788,6 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
 	return ret;
 }
 
-/**
- * spi_nor_sr1_bit6_quad_enable() - Set the Quad Enable BIT(6) in the Status
- * Register 1.
- * @nor:	pointer to a 'struct spi_nor'
- *
- * Bit 6 of the Status Register 1 is the QE bit for Macronix like QSPI memories.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor)
-{
-	int ret;
-
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
-	if (ret)
-		return ret;
-
-	if (nor->bouncebuf[0] & SR1_QUAD_EN_BIT6)
-		return 0;
-
-	nor->bouncebuf[0] |= SR1_QUAD_EN_BIT6;
-
-	return spi_nor_write_sr1_and_check(nor, nor->bouncebuf[0]);
-}
-
-/**
- * spi_nor_sr2_bit1_quad_enable() - set the Quad Enable BIT(1) in the Status
- * Register 2.
- * @nor:       pointer to a 'struct spi_nor'.
- *
- * Bit 1 of the Status Register 2 is the QE bit for Spansion like QSPI memories.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor)
-{
-	int ret;
-
-	if (nor->flags & SNOR_F_NO_READ_CR)
-		return spi_nor_write_16bit_cr_and_check(nor, SR2_QUAD_EN_BIT1);
-
-	ret = spi_nor_read_cr(nor, nor->bouncebuf);
-	if (ret)
-		return ret;
-
-	if (nor->bouncebuf[0] & SR2_QUAD_EN_BIT1)
-		return 0;
-
-	nor->bouncebuf[0] |= SR2_QUAD_EN_BIT1;
-
-	return spi_nor_write_16bit_cr_and_check(nor, nor->bouncebuf[0]);
-}
-
-/**
- * spi_nor_sr2_bit7_quad_enable() - set QE bit in Status Register 2.
- * @nor:	pointer to a 'struct spi_nor'
- *
- * Set the Quad Enable (QE) bit in the Status Register 2.
- *
- * This is one of the procedures to set the QE bit described in the SFDP
- * (JESD216 rev B) specification but no manufacturer using this procedure has
- * been identified yet, hence the name of the function.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_sr2_bit7_quad_enable(struct spi_nor *nor)
-{
-	u8 *sr2 = nor->bouncebuf;
-	int ret;
-	u8 sr2_written;
-
-	/* Check current Quad Enable bit value. */
-	ret = spi_nor_read_sr2(nor, sr2);
-	if (ret)
-		return ret;
-	if (*sr2 & SR2_QUAD_EN_BIT7)
-		return 0;
-
-	/* Update the Quad Enable bit. */
-	*sr2 |= SR2_QUAD_EN_BIT7;
-
-	ret = spi_nor_write_sr2(nor, sr2);
-	if (ret)
-		return ret;
-
-	sr2_written = *sr2;
-
-	/* Read back and check it. */
-	ret = spi_nor_read_sr2(nor, sr2);
-	if (ret)
-		return ret;
-
-	if (*sr2 != sr2_written) {
-		dev_dbg(nor->dev, "SR2: Read back test failed\n");
-		return -EIO;
-	}
-
-	return 0;
-}
-
 static const struct spi_nor_manufacturer *manufacturers[] = {
 	&spi_nor_atmel,
 	&spi_nor_eon,
@@ -2494,6 +2278,7 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps)
 {
 	struct spi_nor_flash_parameter *params = nor->params;
 	unsigned int cap;
+	u8 opcode;
 
 	/* X-X-X modes are not supported yet, mask them all. */
 	*hwcaps &= ~SNOR_HWCAPS_X_X_X;
@@ -2525,14 +2310,14 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps)
 			*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);
-
+	/* Some SPI controllers might not support reading SR2 */
+	opcode = nor->params->opcodes.read_sr2;
+	if (opcode) {
+		struct spi_mem_op op = SPI_NOR_RDSR_OP(opcode, nor->bouncebuf, 1);
 		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
 
 		if (!spi_mem_supports_op(nor->spimem, &op))
-			nor->flags |= SNOR_F_NO_READ_CR;
+			nor->params->opcodes.read_sr2 = 0;
 	}
 }
 
@@ -3107,11 +2892,14 @@ static void spi_nor_init_default_params(struct spi_nor *nor)
 	const struct flash_info *info = nor->info;
 	struct device_node *np = spi_nor_get_flash_node(nor);
 
-	params->quad_enable = spi_nor_sr2_bit1_quad_enable;
-	params->otp.org = info->otp;
+	/* Default to 16-bit Read/Write Status commands */
+	params->opcodes.read_sr1 = SPINOR_OP_RDSR;
+	params->opcodes.read_sr2 = SPINOR_OP_RDCR;
+	params->opcodes.write_sr1_and_sr2 = SPINOR_OP_WRSR;
+	params->quad_enable = spi_nor_generic_quad_enable;
+	params->qe_mask[1] = BIT(1);
 
-	/* Default to 16-bit Write Status (01h) Command */
-	nor->flags |= SNOR_F_HAS_16BIT_SR;
+	params->otp.org = info->otp;
 
 	/* Set SPI NOR sizes. */
 	params->writesize = 1;
diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
index 2cc0c80da3fa..7d43d3ba47ee 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -37,36 +37,18 @@
 		   SPI_MEM_OP_NO_DUMMY,					\
 		   SPI_MEM_OP_NO_DATA)
 
-#define SPI_NOR_RDSR_OP(buf)						\
-	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 0),			\
+#define SPI_NOR_RDSR_OP(opcode, buf, len)				\
+	SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0),				\
 		   SPI_MEM_OP_NO_ADDR,					\
 		   SPI_MEM_OP_NO_DUMMY,					\
-		   SPI_MEM_OP_DATA_IN(1, buf, 0))
+		   SPI_MEM_OP_DATA_IN(len, buf, 0))
 
-#define SPI_NOR_WRSR_OP(buf, len)					\
-	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 0),			\
+#define SPI_NOR_WRSR_OP(opcode, buf, len)				\
+	SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0),				\
 		   SPI_MEM_OP_NO_ADDR,					\
 		   SPI_MEM_OP_NO_DUMMY,					\
 		   SPI_MEM_OP_DATA_OUT(len, buf, 0))
 
-#define SPI_NOR_RDSR2_OP(buf)						\
-	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 0),			\
-		   SPI_MEM_OP_NO_ADDR,					\
-		   SPI_MEM_OP_NO_DUMMY,					\
-		   SPI_MEM_OP_DATA_OUT(1, buf, 0))
-
-#define SPI_NOR_WRSR2_OP(buf)						\
-	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 0),			\
-		   SPI_MEM_OP_NO_ADDR,					\
-		   SPI_MEM_OP_NO_DUMMY,					\
-		   SPI_MEM_OP_DATA_OUT(1, buf, 0))
-
-#define SPI_NOR_RDCR_OP(buf)						\
-	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 0),			\
-		   SPI_MEM_OP_NO_ADDR,					\
-		   SPI_MEM_OP_NO_DUMMY,					\
-		   SPI_MEM_OP_DATA_IN(1, buf, 0))
-
 #define SPI_NOR_EN4B_EX4B_OP(enable)					\
 	SPI_MEM_OP(SPI_MEM_OP_CMD(enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B, 0),	\
 		   SPI_MEM_OP_NO_ADDR,					\
@@ -129,8 +111,6 @@ enum spi_nor_option_flags {
 	SNOR_F_4B_OPCODES	= BIT(3),
 	SNOR_F_HAS_4BAIT	= BIT(4),
 	SNOR_F_HAS_LOCK		= BIT(5),
-	SNOR_F_HAS_16BIT_SR	= BIT(6),
-	SNOR_F_NO_READ_CR	= BIT(7),
 	SNOR_F_HAS_SR_TB_BIT6	= BIT(8),
 	SNOR_F_HAS_4BIT_BP      = BIT(9),
 	SNOR_F_HAS_SR_BP3_BIT6  = BIT(10),
@@ -345,9 +325,19 @@ struct spi_nor_otp {
  * List of variable opcodes used by the chip.
  *
  * @die_erase: opcode for erasing a die, defaults to SPINOR_OP_CHIP_ERASE
+ * @read_sr1: opcode for reading SR1 alone
+ * @read_sr2: opcode for reading SR2 alone
+ * @write_sr1: opcode for writing SR1 alone
+ * @write_sr2: opcode for writing SR2 alone
+ * @write_sr1_and_sr2: opcode for writing SR1 then SR2 in one operation
  */
 struct spi_nor_opcodes {
 	u8 die_erase;
+	u8 read_sr1;
+	u8 read_sr2;
+	u8 write_sr1;
+	u8 write_sr2;
+	u8 write_sr1_and_sr2;
 };
 
 /**
@@ -384,6 +374,7 @@ struct spi_nor_opcodes {
  * @otp:		SPI NOR OTP info.
  * @set_octal_dtr:	enables or disables SPI NOR octal DTR mode.
  * @quad_enable:	enables SPI NOR quad mode.
+ * @qe_mask:		two bytes mask used to set/clear the QE bit
  * @set_4byte_addr_mode: puts the SPI NOR in 4 byte addressing mode.
  * @ready:		(optional) flashes might use a different mechanism
  *			than reading the status register to indicate they
@@ -413,6 +404,7 @@ struct spi_nor_flash_parameter {
 
 	int (*set_octal_dtr)(struct spi_nor *nor, bool enable);
 	int (*quad_enable)(struct spi_nor *nor);
+	u8				qe_mask[2];
 	int (*set_4byte_addr_mode)(struct spi_nor *nor, bool enable);
 	int (*ready)(struct spi_nor *nor);
 
@@ -641,18 +633,15 @@ int spi_nor_wait_till_ready(struct spi_nor *nor);
 int spi_nor_global_block_unlock(struct spi_nor *nor);
 int spi_nor_prep_and_lock(struct spi_nor *nor);
 void spi_nor_unlock_and_unprep(struct spi_nor *nor);
-int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor);
-int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor);
-int spi_nor_sr2_bit7_quad_enable(struct spi_nor *nor);
 int spi_nor_read_id(struct spi_nor *nor, u8 naddr, u8 ndummy, u8 *id,
 		    enum spi_nor_protocol reg_proto);
-int spi_nor_read_sr(struct spi_nor *nor, u8 *sr);
 int spi_nor_sr_ready(struct spi_nor *nor);
-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_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_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs);
+int spi_nor_read_sr1(struct spi_nor *nor, u8 *sr1);
+int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2);
+int spi_nor_read_sr1_and_sr2(struct spi_nor *nor, u8 *sr);
+int spi_nor_write_sr1(struct spi_nor *nor, const u8 *sr1);
+int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2);
+int spi_nor_write_sr1_and_sr2_and_check(struct spi_nor *nor, const u8 *sr);
 
 ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
 			  u8 *buf);
diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index ef6f0ffcd6bb..512825c3fc85 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -19,8 +19,6 @@ static const char *const snor_f_names[] = {
 	SNOR_F_NAME(4B_OPCODES),
 	SNOR_F_NAME(HAS_4BAIT),
 	SNOR_F_NAME(HAS_LOCK),
-	SNOR_F_NAME(HAS_16BIT_SR),
-	SNOR_F_NAME(NO_READ_CR),
 	SNOR_F_NAME(HAS_SR_TB_BIT6),
 	SNOR_F_NAME(HAS_4BIT_BP),
 	SNOR_F_NAME(HAS_SR_BP3_BIT6),
diff --git a/drivers/mtd/spi-nor/gigadevice.c b/drivers/mtd/spi-nor/gigadevice.c
index ef1edd0add70..4070a692e968 100644
--- a/drivers/mtd/spi-nor/gigadevice.c
+++ b/drivers/mtd/spi-nor/gigadevice.c
@@ -23,8 +23,10 @@ gd25q256_post_bfpt(struct spi_nor *nor,
 	 *      GD25Q256E      | SFDP_JESD216_MAJOR | SFDP_JESD216B_MINOR
 	 */
 	if (bfpt_header->major == SFDP_JESD216_MAJOR &&
-	    bfpt_header->minor == SFDP_JESD216_MINOR)
-		nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
+	    bfpt_header->minor == SFDP_JESD216_MINOR) {
+		nor->params->qe_mask[0] = BIT(6);
+		nor->params->qe_mask[1] = 0;
+	}
 
 	return 0;
 }
diff --git a/drivers/mtd/spi-nor/issi.c b/drivers/mtd/spi-nor/issi.c
index 18d9a00aa22e..04db42b5141e 100644
--- a/drivers/mtd/spi-nor/issi.c
+++ b/drivers/mtd/spi-nor/issi.c
@@ -131,7 +131,8 @@ static const struct flash_info issi_nor_parts[] = {
 
 static void issi_nor_default_init(struct spi_nor *nor)
 {
-	nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
+	nor->params->qe_mask[0] = BIT(6);
+	nor->params->qe_mask[1] = 0;
 }
 
 static const struct spi_nor_fixups issi_fixups = {
diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c
index e97f5cbd9aad..55612237c1ef 100644
--- a/drivers/mtd/spi-nor/macronix.c
+++ b/drivers/mtd/spi-nor/macronix.c
@@ -65,11 +65,12 @@ mx25l3255e_late_init_fixups(struct spi_nor *nor)
 
 	/*
 	 * SFDP of MX25L3255E is JESD216, which does not include the Quad
-	 * Enable bit Requirement in BFPT. As a result, during BFPT parsing,
-	 * the quad_enable method is not set to spi_nor_sr1_bit6_quad_enable.
-	 * Therefore, it is necessary to correct this setting by late_init.
+	 * Enable bit Requirement in BFPT. As a result, during BFPT parsing
+	 * the quad_enable mask is reset. Therefore, it is necessary to
+	 * correct this setting by late_init.
 	 */
-	params->quad_enable = spi_nor_sr1_bit6_quad_enable;
+	params->qe_mask[0] = BIT(6);
+	params->qe_mask[1] = 0;
 
 	/*
 	 * In addition, MX25L3255E also supports 1-4-4 page program in 3-byte
@@ -314,7 +315,8 @@ static int macronix_nor_set_octal_dtr(struct spi_nor *nor, bool enable)
 
 static void macronix_nor_default_init(struct spi_nor *nor)
 {
-	nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
+	nor->params->qe_mask[0] = BIT(6);
+	nor->params->qe_mask[1] = 0;
 }
 
 static int macronix_nor_late_init(struct spi_nor *nor)
diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c
index 031cf887318c..344539d2a36a 100644
--- a/drivers/mtd/spi-nor/micron-st.c
+++ b/drivers/mtd/spi-nor/micron-st.c
@@ -229,7 +229,7 @@ static int mt25qu512a_post_bfpt_fixup(struct spi_nor *nor,
 				      const struct sfdp_parameter_header *bfpt_header,
 				      const struct sfdp_bfpt *bfpt)
 {
-	nor->flags &= ~SNOR_F_HAS_16BIT_SR;
+	nor->params->opcodes.write_sr1 = 0;
 	return 0;
 }
 
@@ -633,7 +633,7 @@ static int micron_st_nor_ready(struct spi_nor *nor)
 static void micron_st_nor_default_init(struct spi_nor *nor)
 {
 	nor->flags |= SNOR_F_HAS_LOCK;
-	nor->flags &= ~SNOR_F_HAS_16BIT_SR;
+	nor->params->opcodes.write_sr1 = 0;
 	nor->params->quad_enable = NULL;
 }
 
diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c
index 7d0b145d78d8..1eeb33a20091 100644
--- a/drivers/mtd/spi-nor/otp.c
+++ b/drivers/mtd/spi-nor/otp.c
@@ -175,24 +175,24 @@ static int spi_nor_otp_lock_bit_cr(unsigned int region)
  */
 int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region)
 {
-	u8 *cr = nor->bouncebuf;
 	int ret, lock_bit;
+	u8 sr[2];
 
 	lock_bit = spi_nor_otp_lock_bit_cr(region);
 	if (lock_bit < 0)
 		return lock_bit;
 
-	ret = spi_nor_read_cr(nor, cr);
+	ret = spi_nor_read_sr1_and_sr2(nor, sr);
 	if (ret)
 		return ret;
 
 	/* no need to write the register if region is already locked */
-	if (cr[0] & lock_bit)
+	if (sr[1] & lock_bit)
 		return 0;
 
-	cr[0] |= lock_bit;
+	sr[1] |= lock_bit;
 
-	return spi_nor_write_16bit_cr_and_check(nor, cr[0]);
+	return spi_nor_write_sr1_and_sr2_and_check(nor, sr);
 }
 
 /**
@@ -207,18 +207,18 @@ 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)
 {
-	u8 *cr = nor->bouncebuf;
 	int ret, lock_bit;
+	u8 sr2;
 
 	lock_bit = spi_nor_otp_lock_bit_cr(region);
 	if (lock_bit < 0)
 		return lock_bit;
 
-	ret = spi_nor_read_cr(nor, cr);
+	ret = spi_nor_read_sr2(nor, &sr2);
 	if (ret)
 		return ret;
 
-	return cr[0] & lock_bit;
+	return sr2 & lock_bit;
 }
 
 static loff_t spi_nor_otp_region_start(const struct spi_nor *nor, unsigned int region)
diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index 526763c79f55..1d799f02b8e5 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -561,10 +561,21 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 	val >>= BFPT_DWORD11_PAGE_SIZE_SHIFT;
 	params->page_size = 1U << val;
 
+	/*
+	 * The standard declares various read and write status methods, some of
+	 * them will be overloaded based on the QER field.
+	 */
+	params->opcodes.read_sr1 = SPINOR_OP_RDSR;
+	params->opcodes.read_sr2 = SPINOR_OP_RDCR;
+	params->opcodes.write_sr1 = SPINOR_OP_WRSR;
+	params->opcodes.write_sr1_and_sr2 = SPINOR_OP_WRSR;
+
+	params->qe_mask[0] = 0;
+	params->qe_mask[1] = 0;
+
 	/* Quad Enable Requirements. */
 	switch (bfpt.dwords[SFDP_DWORD(15)] & BFPT_DWORD15_QER_MASK) {
 	case BFPT_DWORD15_QER_NONE:
-		params->quad_enable = NULL;
 		break;
 
 	case BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR:
@@ -572,23 +583,28 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 		 * Writing only one byte to the Status Register has the
 		 * side-effect of clearing Status Register 2.
 		 */
+		fallthrough;
 	case BFPT_DWORD15_QER_SR2_BIT1_NO_RD:
 		/*
 		 * Read Configuration Register (35h) instruction is not
-		 * supported.
+		 * supported. 16-bit writes expected.
 		 */
-		nor->flags |= SNOR_F_HAS_16BIT_SR | SNOR_F_NO_READ_CR;
-		params->quad_enable = spi_nor_sr2_bit1_quad_enable;
+		params->opcodes.read_sr2 = 0;
+		params->opcodes.write_sr1 = 0;
+		params->qe_mask[1] = BIT(1);
 		break;
 
 	case BFPT_DWORD15_QER_SR1_BIT6:
-		nor->flags &= ~SNOR_F_HAS_16BIT_SR;
-		params->quad_enable = spi_nor_sr1_bit6_quad_enable;
+		params->opcodes.read_sr2 = 0;
+		params->opcodes.write_sr1_and_sr2 = 0;
+		params->qe_mask[0] = BIT(6);
 		break;
 
 	case BFPT_DWORD15_QER_SR2_BIT7:
-		nor->flags &= ~SNOR_F_HAS_16BIT_SR;
-		params->quad_enable = spi_nor_sr2_bit7_quad_enable;
+		params->opcodes.read_sr2 = SPINOR_OP_RDSR2;
+		params->opcodes.write_sr1_and_sr2 = 0;
+		params->opcodes.write_sr2 = SPINOR_OP_WRSR2;
+		params->qe_mask[1] = BIT(7);
 		break;
 
 	case BFPT_DWORD15_QER_SR2_BIT1:
@@ -598,9 +614,8 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 		 * Register 2, so let's be cautious and keep the default
 		 * assumption of a 16-bit Write Status (01h) command.
 		 */
-		nor->flags |= SNOR_F_HAS_16BIT_SR;
-
-		params->quad_enable = spi_nor_sr2_bit1_quad_enable;
+		params->opcodes.write_sr1 = 0;
+		params->qe_mask[1] = BIT(1);
 		break;
 
 	default:
@@ -608,6 +623,10 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 		break;
 	}
 
+	/* opcodes sanity check */
+	WARN_ON(!params->opcodes.read_sr1 ||
+		(!params->opcodes.write_sr1 && !params->opcodes.write_sr1_and_sr2));
+
 	dword = bfpt.dwords[SFDP_DWORD(16)] & BFPT_DWORD16_4B_ADDR_MODE_MASK;
 	if (SFDP_MASK_CHECK(dword, BFPT_DWORD16_4B_ADDR_MODE_BRWR))
 		params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode_brwr;
diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c
index 2a8238c707e1..ca2703a5a338 100644
--- a/drivers/mtd/spi-nor/spansion.c
+++ b/drivers/mtd/spi-nor/spansion.c
@@ -399,8 +399,9 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor,
 					  nor->bouncebuf);
 	bool is3byte, is4byte;
 	int ret;
+	u8 sr;
 
-	ret = spi_nor_read_sr(nor, &nor->bouncebuf[1]);
+	ret = spi_nor_read_sr1(nor, &sr);
 	if (ret)
 		return ret;
 
@@ -408,7 +409,7 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor,
 	if (ret)
 		return ret;
 
-	is3byte = (nor->bouncebuf[0] == nor->bouncebuf[1]);
+	is3byte = (nor->bouncebuf[0] == sr);
 
 	op = (struct spi_mem_op)
 		CYPRESS_NOR_RD_ANY_REG_OP(4, SPINOR_REG_CYPRESS_STR1V, 0,
@@ -417,7 +418,7 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor,
 	if (ret)
 		return ret;
 
-	is4byte = (nor->bouncebuf[0] == nor->bouncebuf[1]);
+	is4byte = (nor->bouncebuf[0] == sr);
 
 	if (is3byte == is4byte)
 		return -EIO;
@@ -1103,13 +1104,14 @@ static const struct flash_info spansion_nor_parts[] = {
 static int spansion_nor_sr_ready_and_clear(struct spi_nor *nor)
 {
 	int ret;
+	u8 sr;
 
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr1(nor, &sr);
 	if (ret)
 		return ret;
 
-	if (nor->bouncebuf[0] & (SR_E_ERR | SR_P_ERR)) {
-		if (nor->bouncebuf[0] & SR_E_ERR)
+	if (sr & (SR_E_ERR | SR_P_ERR)) {
+		if (sr & SR_E_ERR)
 			dev_err(nor->dev, "Erase Error occurred\n");
 		else
 			dev_err(nor->dev, "Programming Error occurred\n");
@@ -1129,7 +1131,7 @@ static int spansion_nor_sr_ready_and_clear(struct spi_nor *nor)
 		return -EIO;
 	}
 
-	return !(nor->bouncebuf[0] & SR_WIP);
+	return !(sr & SR_WIP);
 }
 
 static int spansion_nor_late_init(struct spi_nor *nor)
diff --git a/drivers/mtd/spi-nor/sst.c b/drivers/mtd/spi-nor/sst.c
index db02c14ba16f..77a0bc41d4e5 100644
--- a/drivers/mtd/spi-nor/sst.c
+++ b/drivers/mtd/spi-nor/sst.c
@@ -21,16 +21,17 @@ static int sst26vf_nor_lock(struct spi_nor *nor, loff_t ofs, u64 len)
 static int sst26vf_nor_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
 {
 	int ret;
+	u8 cr;
 
 	/* We only support unlocking the entire flash array. */
 	if (ofs != 0 || len != nor->params->size)
 		return -EINVAL;
 
-	ret = spi_nor_read_cr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr2(nor, &cr);
 	if (ret)
 		return ret;
 
-	if (!(nor->bouncebuf[0] & SST26VF_CR_BPNV)) {
+	if (!(cr & SST26VF_CR_BPNV)) {
 		dev_dbg(nor->dev, "Any block has been permanently locked\n");
 		return -EINVAL;
 	}
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index 235070b215d1..b7b291e15917 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -36,7 +36,7 @@ static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor)
 
 static u8 spi_nor_get_sr_cmp_mask(struct spi_nor *nor)
 {
-	if (!(nor->flags & SNOR_F_NO_READ_CR) &&
+	if (nor->params->opcodes.read_sr2 &&
 	    nor->flags & SNOR_F_HAS_SR2_CMP_BIT6)
 		return SR2_CMP_BIT6;
 	else
@@ -207,17 +207,9 @@ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr)
 
 
 	if (!sr) {
-		if (spi_nor_read_sr(nor, nor->bouncebuf))
+		if (spi_nor_read_sr1_and_sr2(nor, sr_cr))
 			return;
 
-		sr_cr[0] = nor->bouncebuf[0];
-
-		if (!(nor->flags & SNOR_F_NO_READ_CR)) {
-			if (spi_nor_read_cr(nor, nor->bouncebuf))
-				return;
-		}
-
-		sr_cr[1] = nor->bouncebuf[0];
 		sr = sr_cr;
 	}
 
@@ -273,20 +265,10 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
 	int ret;
 	u8 pow;
 
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr1_and_sr2(nor, status_old);
 	if (ret)
 		return ret;
 
-	status_old[0] = nor->bouncebuf[0];
-
-	if (!(nor->flags & SNOR_F_NO_READ_CR)) {
-		ret = spi_nor_read_cr(nor, nor->bouncebuf);
-		if (ret)
-			return ret;
-
-		status_old[1] = 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;
@@ -378,10 +360,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
 	    (ofs_old < ofs_new || (ofs_new + len_new) < (ofs_old + len_old)))
 		return -EINVAL;
 
-	if (nor->flags & SNOR_F_NO_READ_CR)
-		ret = spi_nor_write_sr_and_check(nor, best_status_new[0]);
-	else
-		ret = spi_nor_write_sr_cr_and_check(nor, best_status_new);
+	ret = spi_nor_write_sr1_and_sr2_and_check(nor, best_status_new);
 	if (ret)
 		return ret;
 
@@ -409,20 +388,10 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
 	int ret;
 	u8 pow;
 
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr1_and_sr2(nor, status_old);
 	if (ret)
 		return ret;
 
-	status_old[0] = nor->bouncebuf[0];
-
-	if (!(nor->flags & SNOR_F_NO_READ_CR)) {
-		ret = spi_nor_read_cr(nor, nor->bouncebuf);
-		if (ret)
-			return ret;
-
-		status_old[1] = 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;
@@ -512,10 +481,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
 	    (ofs_new < ofs_old || (ofs_old + len_old) < (ofs_new + len_new)))
 		return -EINVAL;
 
-	if (nor->flags & SNOR_F_NO_READ_CR)
-		ret = spi_nor_write_sr_and_check(nor, best_status_new[0]);
-	else
-		ret = spi_nor_write_sr_cr_and_check(nor, best_status_new);
+	ret = spi_nor_write_sr1_and_sr2_and_check(nor, best_status_new);
 	if (ret)
 		return ret;
 
@@ -536,20 +502,10 @@ static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, u64 len)
 	u8 sr_cr[2] = {};
 	int ret;
 
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr1_and_sr2(nor, sr_cr);
 	if (ret)
 		return ret;
 
-	sr_cr[0] = nor->bouncebuf[0];
-
-	if (!(nor->flags & SNOR_F_NO_READ_CR)) {
-		ret = spi_nor_read_cr(nor, nor->bouncebuf);
-		if (ret)
-			return ret;
-
-		sr_cr[1] = nor->bouncebuf[0];
-	}
-
 	return spi_nor_is_locked_sr(nor, ofs, len, sr_cr);
 }
 
diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 593429bd6cdf..01a693033448 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -520,12 +520,12 @@ static int winbond_nor_late_init(struct spi_nor *nor)
 	 * been to declare CR reads as unsupported, whereas the Jedec
 	 * specification doesn't clearly state that. In practice, all these
 	 * chips do support reading back the CR, which is needed for SWP support,
-	 * so make sure that capability remains enabled (needed for SWP).
+	 * so make sure that capability remains enabled.
 	 * In practice, only exclude the old W25X family (JEDEC ID: EF 30 xx)
 	 * which actually does not support this feature.
 	 */
 	if (nor->id[1] > 0x30)
-		nor->flags &= ~SNOR_F_NO_READ_CR;
+		params->opcodes.read_sr2 = SPINOR_OP_RDCR;
 
 	/*
 	 * Winbond has reused many IDs, up to four times at this
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 4b92494827b1..f0b5a1989881 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -113,20 +113,16 @@
 #define SR_E_ERR		BIT(5)
 #define SR_P_ERR		BIT(6)
 
-#define SR1_QUAD_EN_BIT6	BIT(6)
-
 #define SR_BP_SHIFT		2
 
 /* Enhanced Volatile Configuration Register bits */
 #define EVCR_QUAD_EN_MICRON	BIT(7)	/* Micron Quad I/O */
 
 /* Status Register 2 bits. */
-#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_CMP_BIT6		BIT(6)
-#define SR2_QUAD_EN_BIT7	BIT(7)
 
 /* Supported SPI protocols */
 #define SNOR_PROTO_INST_MASK	GENMASK(23, 16)

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:13:56 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:13:56 +0200
Subject: [PATCH v2 04/18] mtd: spi-nor: Add support for the new JESD216 rev
 F QER field
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-4-7797642dfc40@bootlin.com>

Revision F of the JEDEC JESD216 standard includes yet another
supported setting in the QER 3-bit bitfield: 110b.

This setting implies that the QE bit is bit 1 of SR2 (which is an
already supported case) but this time it makes it very clear that both
reading and writing to SR2 is supported, but not with the usual
opcodes. The write operation uses an alternate WRSR2 opcode:
0x31 (instead of 0x3E) and both the reads and writes manipulate a single
register at a time.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/sfdp.c  | 6 ++++++
 drivers/mtd/spi-nor/sfdp.h  | 8 ++++++++
 include/linux/mtd/spi-nor.h | 1 +
 3 files changed, 15 insertions(+)

diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index 1d799f02b8e5..285ac781fba8 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -618,6 +618,12 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 		params->qe_mask[1] = BIT(1);
 		break;
 
+	case BFPT_DWORD15_QER_SR2_BIT1_1B:
+		params->qe_mask[1] = BIT(1);
+		params->opcodes.write_sr1_and_sr2 = 0;
+		params->opcodes.write_sr2 = SPINOR_OP_WRSR2_ALT;
+		break;
+
 	default:
 		dev_dbg(nor->dev, "BFPT QER reserved value used\n");
 		break;
diff --git a/drivers/mtd/spi-nor/sfdp.h b/drivers/mtd/spi-nor/sfdp.h
index d976c6c9a4ad..e8ed814c6530 100644
--- a/drivers/mtd/spi-nor/sfdp.h
+++ b/drivers/mtd/spi-nor/sfdp.h
@@ -12,6 +12,7 @@
 #define SFDP_JESD216_MINOR	0
 #define SFDP_JESD216A_MINOR	5
 #define SFDP_JESD216B_MINOR	6
+#define SFDP_JESD216F_MINOR	10
 
 /* SFDP DWORDS are indexed from 1 but C arrays are indexed from 0. */
 #define SFDP_DWORD(i)		((i) - 1)
@@ -81,6 +82,12 @@ struct sfdp_bfpt {
  *         instruction 35h. QE is set via Write Status instruction 01h with
  *         two data bytes where bit 1 of the second byte is one.
  *         [...]
+ * (from JESD216 rev F)
+ * - 110b: QE is bit 1 of status register 2. Status register 1 is read using
+ *         Read Status instruction 05h. Status register2 is read using
+ *         instruction 35h, and status register 3 is read using instruction 15h.
+ *         QE is set via Write Status Register instruction 31h with one data
+ *         byte.
  */
 #define BFPT_DWORD15_QER_MASK			GENMASK(22, 20)
 #define BFPT_DWORD15_QER_NONE			(0x0UL << 20) /* Micron */
@@ -89,6 +96,7 @@ struct sfdp_bfpt {
 #define BFPT_DWORD15_QER_SR2_BIT7		(0x3UL << 20)
 #define BFPT_DWORD15_QER_SR2_BIT1_NO_RD		(0x4UL << 20)
 #define BFPT_DWORD15_QER_SR2_BIT1		(0x5UL << 20) /* Spansion */
+#define BFPT_DWORD15_QER_SR2_BIT1_1B		(0x6UL << 20) /* Winbond */
 
 #define BFPT_DWORD16_EN4B_MASK			GENMASK(31, 24)
 #define BFPT_DWORD16_EN4B_ALWAYS_4B		BIT(30)
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index f0b5a1989881..3a55d76ea77d 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -25,6 +25,7 @@
 #define SPINOR_OP_WRSR		0x01	/* Write status register 1 */
 #define SPINOR_OP_RDSR2		0x3f	/* Read status register 2 */
 #define SPINOR_OP_WRSR2		0x3e	/* Write status register 2 */
+#define SPINOR_OP_WRSR2_ALT	0x31	/* Write status register 2 (alternative) */
 #define SPINOR_OP_READ		0x03	/* Read data bytes (low frequency) */
 #define SPINOR_OP_READ_FAST	0x0b	/* Read data bytes (high frequency) */
 #define SPINOR_OP_READ_1_1_2	0x3b	/* Read data bytes (Dual Output SPI) */

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:13:57 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:13:57 +0200
Subject: [PATCH v2 05/18] mtd: spi-nor: Move the SFDP header structure to a
 C header
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-5-7797642dfc40@bootlin.com>

The sfdp_header structure is currently defined in sfdp.c, whereas the
sfdp_parameter_header structure is defined in sfdp.h. In order to ease
the reuse of this structure outside of the sfdp.c file, move this
structure to the sfdp C header (.h).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/sfdp.c | 11 -----------
 drivers/mtd/spi-nor/sfdp.h | 11 +++++++++++
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index 285ac781fba8..e18bfb7d5dc3 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -34,17 +34,6 @@
 
 #define SFDP_SIGNATURE		0x50444653U
 
-struct sfdp_header {
-	u32		signature; /* Ox50444653U <=> "SFDP" */
-	u8		minor;
-	u8		major;
-	u8		nph; /* 0-base number of parameter headers */
-	u8		unused;
-
-	/* Basic Flash Parameter Table. */
-	struct sfdp_parameter_header	bfpt_header;
-};
-
 /* Fast Read settings. */
 struct sfdp_bfpt_read {
 	/* The Fast Read x-y-z hardware capability in params->hwcaps.mask. */
diff --git a/drivers/mtd/spi-nor/sfdp.h b/drivers/mtd/spi-nor/sfdp.h
index e8ed814c6530..2cbe22246c5e 100644
--- a/drivers/mtd/spi-nor/sfdp.h
+++ b/drivers/mtd/spi-nor/sfdp.h
@@ -149,4 +149,15 @@ struct sfdp_parameter_header {
 	u8		id_msb;
 };
 
+struct sfdp_header {
+	u32		signature; /* Ox50444653U <=> "SFDP" */
+	u8		minor;
+	u8		major;
+	u8		nph; /* 0-base number of parameter headers */
+	u8		unused;
+
+	/* Basic Flash Parameter Table. */
+	struct sfdp_parameter_header	bfpt_header;
+};
+
 #endif /* __LINUX_MTD_SFDP_H */

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:13:59 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:13:59 +0200
Subject: [PATCH v2 07/18] mtd: spi-nor: winbond: Add support for
 W25Q32RV-Q/N
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-7-7797642dfc40@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef4016
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0100ff00080117800000ffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f9ffffffff0144eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d800001432010183e314c2e96376337a757a75
f7a2d55c39f66dffe930f8800000000000002c0000000000f6f4ffff0f00
0000060d44bd27ed080d
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
2c0c54d6e360dae30b72615eac5f0faff59463aeb2237d46d08d6cfd77d6c9f6  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x03
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3b
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbb
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6b
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x02
 1S-1S-4S
  opcode	0x32
+ cat /sys/kernel/debug/spi-nor/spi0.0/p[   84.112272] random: crng init done
arams
name		(null)
id		ef 40 16 00 00 00
size		4.00 MiB
write size	1
page size	256
address nbytes	3
flags		HAS_SR_TB | HAS_LOCK | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xeb
  dummy cycles	6
 erase		0xd8
 program	0x32
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 20 (4.00 KiB) [1]
 52 (32.0 KiB) [2]
 d8 (64.0 KiB) [3]
 c7 (4.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-003fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff | unlocked | 64
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_read
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 4194304 (4M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_read
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_read
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_read
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_read2
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_test
831e9e5cc6efa7d3afe7b0584100549d93b9953d64ad237fd1df15f790f23ae6  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=4194304
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 4063232 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff | unlocked | 62
 003e0000-003fffff |   locked | 2
+ flash_lock -u /dev/mtd0 4063232 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff | unlocked | 63
 003f0000-003fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-003fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-003fffff | unlocked | 63
+ all_but_one=63
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 63
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-003fffff |   locked | 63
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-003fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 63
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff |   locked | 63
 003f0000-003fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 4063232 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff |   locked | 62
 003e0000-003fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 88fdcfeb6eff..913d1b112c9a 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -198,7 +198,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.size = SZ_1M,
 		.no_sfdp_flags = SECT_4K,
 	}, {
-		/* W25Q32JV-Q/N */
+		/* W25Q32JV-Q/N, W25Q32RV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x16),
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K,

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:14:00 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:14:00 +0200
Subject: [PATCH v2 08/18] mtd: spi-nor: winbond: Add support for W25Q32RV-M
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-8-7797642dfc40@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7016
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0100ff00080117800000ffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f9ffffffff0144eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d800001432010183e314c2e96376337a757a75
f7a2d55c39f66dffe930f8800000000000002c0000000000f6f4ffff0f00
0000060d44bd27ed080d
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
2c0c54d6e360dae30b72615eac5f0faff59463aeb2237d46d08d6cfd77d6c9f6  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x03
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3b
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbb
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6b
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x02
 1S-1S-4S
  opcode	0x32
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 16 00 00 00
size		4.00 MiB
write size	1
page size	256
address nbytes	3
flags		HAS_SR_TB | HAS_LOCK | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xeb
  dummy cycles	6
 erase		0xd8
 program	0x32
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 20 (4.00 KiB) [1]
 52 (32.0 KiB) [2]
 d8 (64.0 KiB) [3]
 c7 (4.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-003fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff | unlocked | 64
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   15.820716] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_read
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 4194304 (4M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_read
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_read
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_read
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_read2
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_test
d26d11f7de825c1f2397e52307997c9befb07b93e6cd520d15a87d06abef2529  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=4194304
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 4063232 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff | unlocked | 62
 003e0000-003fffff |   locked | 2
+ flash_lock -u /dev/mtd0 4063232 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff | unlocked | 63
 003f0000-003fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-003fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-003fffff | unlocked | 63
+ all_but_one=63
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 63
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-003fffff |   locked | 63
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-003fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 63
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff |   locked | 63
 003f0000-003fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 4063232 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff |   locked | 62
 003e0000-003fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 913d1b112c9a..c97506625d37 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -304,7 +304,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q32JV-M */
+		/* W25Q32JV-M, W25Q32RV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x16),
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:14:01 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:14:01 +0200
Subject: [PATCH v2 09/18] mtd: spi-nor: winbond: Add support for
 W25Q64RV-Q/N
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-9-7797642dfc40@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef4017
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f3ffffffff0344eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d80000d639a50081d614c3e96376337a757a75
f7a4d55c39de6dffe970f9a50000000000002c000000000066ffffff0000
00000000000000000000ffffffffff0a00fe21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
c7fa99bdcdef9a930e609215704dd349dbed161487dee0ece8ea1b25d2d4dd25  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 17 00 00 00
size		8.00 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (8.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-007fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[    7.803395] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_read
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 8388608 (8M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_read
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_read
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_read
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_read2
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_test
64f4a5c5a05eae4a6dffb9e76171d97328257a96110214f52222870681b7b28c  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=8388608
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=131072
+ bps=2
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 8126464 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff | unlocked | 62
 007c0000-007fffff |   locked | 2
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff | unlocked | 63
 007e0000-007fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 2
 00040000-007fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 1
 00020000-007fffff | unlocked | 63
+ all_but_one=126
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 131072 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 1
 00020000-007fffff |   locked | 63
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 2
 00040000-007fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff |   locked | 63
 007e0000-007fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff |   locked | 62
 007c0000-007fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index c97506625d37..5b65076039b6 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -204,7 +204,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q64JV-Q/N */
+		/* W25Q64JV-Q/N, W25Q64RV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x17),
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:14:02 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:14:02 +0200
Subject: [PATCH v2 10/18] mtd: spi-nor: winbond: Add support for W25Q64RV-M
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-10-7797642dfc40@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7017
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff0344eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d80000d639a50081d614c3e96376337a757a75
f7a4d55c39de6dffe970f9a50000000000002c00000000006655ffff0f00
0000060d44bd27ed080dffffffffff8a00fe21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
ef6c5eb61fa86c6daac85436446b97ca078a46e4cfd4c0fed4c9f9400d79fa86  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 17 00 00 00
size		8.00 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (8.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-007fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   15.931750] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_read
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 8388608 (8M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_read
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_read
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_read
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_read2
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_test
154432cc7e89d18c1e4ddb1604dfd2a0038c75aa5521a74808944ce19a320a42  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=8388608
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=131072
+ bps=2
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 8126464 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff | unlocked | 62
 007c0000-007fffff |   locked | 2
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff | unlocked | 63
 007e0000-007fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 2
 00040000-007fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 1
 00020000-007fffff | unlocked | 63
+ all_but_one=126
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 131072 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 1
 00020000-007fffff |   locked | 63
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 2
 00040000-007fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff |   locked | 63
 007e0000-007fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff |   locked | 62
 007c0000-007fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 5b65076039b6..32122420b804 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -310,7 +310,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q64JV-M */
+		/* W25Q64JV-M, W25Q64RV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x17),
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K,

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:13:58 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:13:58 +0200
Subject: [PATCH v2 06/18] mtd: spi-nor: winbond: Drop the .name entry of JV
 chips
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-6-7797642dfc40@bootlin.com>

This is a legacy field. It is often incorrectly filled and will soon
become very inconsistent due to IDs being reused. As an example, chips
ending with JV-Q or JV-N and soon RV-Q and RV-N all share the same ID.

There are comments above the fields which already list the correct names
being supported by a single entry. The names not being part of any
stable ABI (they are not displayed through sysfs, which only shows the
mtd name being spi-norX.Y), they are only available through debugfs, so
let's just drop that field when it becomes a problem.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 01a693033448..88fdcfeb6eff 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -200,14 +200,12 @@ static const struct flash_info winbond_nor_parts[] = {
 	}, {
 		/* W25Q32JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x16),
-		.name = "w25q32",
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q64JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x17),
-		.name = "w25q64",
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
@@ -223,7 +221,6 @@ static const struct flash_info winbond_nor_parts[] = {
 	}, {
 		/* W25Q256JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x19),
-		.name = "w25q256",
 		.size = SZ_32M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
@@ -232,7 +229,6 @@ static const struct flash_info winbond_nor_parts[] = {
 	}, {
 		/* W25Q512JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x20),
-		.name = "w25q512jvq",
 		.size = SZ_64M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
@@ -304,35 +300,30 @@ static const struct flash_info winbond_nor_parts[] = {
 	}, {
 		/* W25Q16JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x15),
-		.name = "w25q16jv-im/jm",
 		.size = SZ_2M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q32JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x16),
-		.name = "w25q32jv",
 		.size = SZ_4M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q64JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x17),
-		.name = "w25q64jvm",
 		.size = SZ_8M,
 		.no_sfdp_flags = SECT_4K,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q128JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x18),
-		.name = "w25q128jv",
 		.size = SZ_16M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
 		/* W25Q256JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),
-		.name = "w25q256jvm",
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
 			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:14:03 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:14:03 +0200
Subject: [PATCH v2 11/18] mtd: spi-nor: winbond: Add support for
 W25Q12RV-Q/N
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-11-7797642dfc40@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef4018
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0100ff00080117800000ffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f9ffffffff0344eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d80000142a010186e114c1e96376337a757a75
f7a2d55c39f66dffe930f880000000000000ac0000000000f7f5ffff0f00
0000060d44bd27ed080d
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
94b52d6e903a2046e144eab0760384b6932c6fdbfec17eb7336f1778c3898daf  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x03
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3b
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbb
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6b
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x02
 1S-1S-4S
  opcode	0x32
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 18 00 00 00
size		8.00 MiB
write size	1
page size	256
address nbytes	3
flags		HAS_SR_TB | HAS_LOCK | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xeb
  dummy cycles	6
 erase		0xd8
 program	0x32
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 20 (4.00 KiB) [1]
 52 (32.0 KiB) [2]
 d8 (64.0 KiB) [3]
 c7 (8.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-007fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   15.791564] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_read
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 8388608 (8M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_read
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_read
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_read
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_read2
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_test
884cedd0494868db17c609edc62e2471ef344025e30cd511fa28b41b9b50e50d  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=8388608
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=131072
+ bps=2
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 8126464 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff | unlocked | 62
 007c0000-007fffff |   locked | 2
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff | unlocked | 63
 007e0000-007fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 2
 00040000-007fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 1
 00020000-007fffff | unlocked | 63
+ all_but_one=126
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 131072 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 1
 00020000-007fffff |   locked | 63
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 2
 00040000-007fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff |   locked | 63
 007e0000-007fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff |   locked | 62
 007c0000-007fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 32122420b804..0ae16f5b0ec0 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -210,7 +210,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q128JV-Q/N */
+		/* W25Q128JV-Q/N, W25Q12RV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x18),
 		/* Flavors w/ and w/o SFDP. */
 		.name = "w25q128",

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:14:04 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:14:04 +0200
Subject: [PATCH v2 12/18] mtd: spi-nor: winbond: Add support for W25Q12RV-M
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-12-7797642dfc40@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7018
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0100ff00080117800000ffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f9ffffffff0344eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d80000142a010186e114c1e96376337a757a75
f7a2d55c39f66dffe930f880000000000000ac0000000000f7f5ffff0f00
0000060d44bd27ed080d
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
94b52d6e903a2046e144eab0760384b6932c6fdbfec17eb7336f1778c3898daf  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x03
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3b
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbb
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6b
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x02
 1S-1S-4S
  opcode	0x32
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 18 00 00 00
size		8.00 MiB
write size	1
page size	256
address nbytes	3
flags		HAS_SR_TB | HAS_LOCK | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xeb
  dummy cycles	6
 erase		0xd8
 program	0x32
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 20 (4.00 KiB) [1]
 52 (32.0 KiB) [2]
 d8 (64.0 KiB) [3]
 c7 (8.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-007fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   28.249430] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_read
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 8388608 (8M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_read
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_read
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_read
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_read2
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_test
df6c52ecc59473c5ed9bd10a0479b52ee248adbf7d032aa7cde35e86acd461ca  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=8388608
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=131072
+ bps=2
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 8126464 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff | unlocked | 62
 007c0000-007fffff |   locked | 2
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff | unlocked | 63
 007e0000-007fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 2
 00040000-007fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 1
 00020000-007fffff | unlocked | 63
+ all_but_one=126
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 131072 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 1
 00020000-007fffff |   locked | 63
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 2
 00040000-007fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff |   locked | 63
 007e0000-007fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff |   locked | 62
 007c0000-007fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 0ae16f5b0ec0..0746e157713b 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -316,7 +316,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q128JV-M */
+		/* W25Q128JV-M, W25Q12RV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x18),
 		.size = SZ_16M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:14:05 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:14:05 +0200
Subject: [PATCH v2 13/18] mtd: spi-nor: winbond: Add support for
 W25Q51RV-Q/N
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-13-7797642dfc40@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef4020
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f3ffffffff1f44eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d800001602a60081e214dde96376337a757a75
f7a4d55c39f66dffe970f9a50000000000002c0c0000000066ffffff0000
00000000000000000000ffffffffff0a00fe21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
74d21fecc7faa297b3f77f1136b1dc53f78b2a432629e226675b6384153fd9f9  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 20 00 00 00
size		64.0 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (64.0 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-03ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   25.427347] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_read
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 67108864 (64M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_read
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_read
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_read
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_read2
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_test
f506020804f91fb750e8250e091fb2f732319221a645ed575e1b6ed3553d584c  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff |   locked | 1024
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=67108864
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=1024
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 66977792 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff | unlocked | 1022
 03fe0000-03ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff | unlocked | 1023
 03ff0000-03ffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 58720256 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-037fffff | unlocked | 896
 03800000-03ffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-03ffffff | unlocked | 1022
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-03ffffff | unlocked | 1023
+ all_but_one=1023
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-03ffffff |   locked | 1023
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-03ffffff |   locked | 1022
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff |   locked | 1023
 03ff0000-03ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff |   locked | 1022
 03fe0000-03ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 0746e157713b..cc5f3fe77fd8 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -227,7 +227,7 @@ static const struct flash_info winbond_nor_parts[] = {
 			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &w25q256_fixups,
 	}, {
-		/* W25Q512JV-Q/N */
+		/* W25Q512JV-Q/N, W25Q51RV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x20),
 		.size = SZ_64M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:14:06 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:14:06 +0200
Subject: [PATCH v2 14/18] mtd: spi-nor: winbond: Add support for
 W25Q01RV-Q/N
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-14-7797642dfc40@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef4020
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f3ffffffff1f44eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d800001602a60081e214dde96376337a757a75
f7a4d55c39f66dffe970f9a50000000000002c0c0000000066ffffff0000
00000000000000000000ffffffffff0a00fe21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
74d21fecc7faa297b3f77f1136b1dc53f78b2a432629e226675b6384153fd9f9  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 20 00 00 00
size		64.0 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (64.0 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-03ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 67108864 (64M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read2
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
643412dbe0f46c2780ec8d4f609d369923829945f4bc3d7842138b2fbbc86046  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff |   locked | 1024
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=67108864
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=1024
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 66977792 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff | unlocked | 1022
 03fe0000-03ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff | unlocked | 1023
 03ff0000-03ffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 58720256 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-037fffff | unlocked | 896
 03800000-03ffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-03ffffff | unlocked | 1022
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-03ffffff | unlocked | 1023
+ all_but_one=1023
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-03ffffff |   locked | 1023
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-03ffffff |   locked | 1022
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff |   locked | 1023
 03ff0000-03ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff |   locked | 1022
 03fe0000-03ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 53 +++++++++++++++++++++++++++++++++++++------
 1 file changed, 46 insertions(+), 7 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index cc5f3fe77fd8..e8098f5e59f1 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -128,7 +128,9 @@ static int winbond_nor_multi_die_ready(struct spi_nor *nor)
 }
 
 static int
-winbond_nor_multi_die_post_sfdp_fixups(struct spi_nor *nor)
+winbond_nor_multi_die_post_sfdp_fixups(struct spi_nor *nor,
+				       const struct sfdp_parameter_header *bfpt_header,
+				       const struct sfdp_bfpt *bfpt)
 {
 	/*
 	 * SFDP supports dice numbers, but this information is only available in
@@ -142,8 +144,45 @@ winbond_nor_multi_die_post_sfdp_fixups(struct spi_nor *nor)
 	return 0;
 }
 
-static const struct spi_nor_fixups winbond_nor_multi_die_fixups = {
-	.post_sfdp = winbond_nor_multi_die_post_sfdp_fixups,
+static bool is_w25qxxrv(struct spi_nor *nor)
+{
+	struct sfdp_header *sfdp_h = (struct sfdp_header *)nor->sfdp->dwords;
+
+	/*
+	 * W25QxxRV chips re-use the same ID as the W25QxxJV family.
+	 *
+	 * Chips are very similar, W25QxxRV brings mostly performance and power
+	 * consumption improvements. The RV family does not require the multi
+	 * die fixup.
+	 *
+	 * They can be distinguished based on their SFDP minor revision:
+	 * W25QxxJV:        JESD216A, minor revision == 05h
+	 * W25Q512/01/02JV: JESD216B, minor revision == 06h
+	 * W25QxxRV:        JESD216F, minor revision >= 0Ah
+	 */
+	return sfdp_h->minor >= SFDP_JESD216F_MINOR;
+}
+
+static int w25qxxjv_rv_post_bfpt_fixups(struct spi_nor *nor,
+					const struct sfdp_parameter_header *bfpt_header,
+					const struct sfdp_bfpt *bfpt)
+{
+	int ret;
+
+	if (!is_w25qxxrv(nor)) {
+		/* Only 1Gb and 2Gb variants are affected */
+		if (nor->params->size == SZ_128M || nor->params->size == SZ_256M) {
+			ret = winbond_nor_multi_die_post_sfdp_fixups(nor, bfpt_header, bfpt);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static const struct spi_nor_fixups w25qxxjv_rv_fixups = {
+	.post_bfpt = w25qxxjv_rv_post_bfpt_fixups,
 };
 
 static const struct flash_info winbond_nor_parts[] = {
@@ -234,11 +273,11 @@ static const struct flash_info winbond_nor_parts[] = {
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q01JV-Q/N */
+		/* W25Q01JV-Q/N, W25Q01RV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_nor_multi_die_fixups,
+		.fixups = &w25qxxjv_rv_fixups,
 	}, {
 		.id = SNOR_ID(0xef, 0x50, 0x12),
 		.name = "w25q20bw",
@@ -336,13 +375,13 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_nor_multi_die_fixups,
+		.fixups = &w25qxxjv_rv_fixups,
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_nor_multi_die_fixups,
+		.fixups = &w25qxxjv_rv_fixups,
 	}, {
 		.id = SNOR_ID(0xef, 0x71, 0x19),
 		.name = "w25m512jv",

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:14:07 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:14:07 +0200
Subject: [PATCH v2 15/18] mtd: spi-nor: winbond: Add support for W25Q01RV-M
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-15-7797642dfc40@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7021
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff3f44eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d800001602a60081e214dde9e378477a757a75
f7a4d55c39f66dffe970f9a50000000000002c0c000000006655ffff0f00
0000060d44bd27ed080dffffffffff8a00fe21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
f0ba26954c84f126916616ac4311057151ea46cae2eda1752709c3a5adfe4b55  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 21 00 00 00
size		128 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (128 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-07ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   19.799786] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_read
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 134217728 (128M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_read
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_read
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_read
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_read2
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_test
792b00a5bf62350ee8f4868980820577046401c3e6f79075ea636034cff7cef2  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff |   locked | 2048
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=134217728
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=2048
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 134086656 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff | unlocked | 2046
 07fe0000-07ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 134086656 1
+ grep -A4 'locked sectors' /sys/kernel/debbg//spi-nr/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff | unlocked | 2047
 07ff0000-07ffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 125829120 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-077fffff | unlocked | 1920
 07800000-07ffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-07ffffff | unlocked | 2046
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-07ffffff | unlocked | 2047
+ all_but_one=2047
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 2047
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-07ffffff |   locked | 2047
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-07ffffff |   locked | 2046
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2047
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff |   locked | 2047
 07ff0000-07ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 134086656 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff |   locked | 2046
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index e8098f5e59f1..a6325bba96d0 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -371,7 +371,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q01JV-M */
+		/* W25Q01JV-M, W25Q01RV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:14:10 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:14:10 +0200
Subject: [PATCH v2 18/18] mtd: spi-nor: debugfs: Expose SR opcodes and QE
 mask
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-18-7797642dfc40@bootlin.com>

Expose Status Register opcodes based on the flash capabilities
discovered while parsing the SFDP QER field.

Value '0' indicates an absence of opcode (non supported), we do not
print anything in that case.

The QE bit is also displayed.

Example on a W25Q02JV-M:

$ grep opcodes -A10 /sys/kernel/debug/spi-nor/spi0.0/params
opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 SR1 read	0x05
 SR2 read	0x35
 SR1+SR2 write	0x01
 QE		SR2 bit 1
 8D extension	none

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/debugfs.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index 512825c3fc85..ec5b87861539 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -106,6 +106,21 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
 	seq_printf(s, "  dummy cycles\t%u\n", nor->read_dummy);
 	seq_printf(s, " erase\t\t0x%02x\n", nor->erase_opcode);
 	seq_printf(s, " program\t0x%02x\n", nor->program_opcode);
+	seq_printf(s, " SR1 read\t0x%02x\n", params->opcodes.read_sr1);
+	if (params->opcodes.read_sr2)
+		seq_printf(s, " SR2 read\t0x%02x\n", params->opcodes.read_sr2);
+	if (params->opcodes.write_sr1)
+		seq_printf(s, " SR1 write\t0x%02x\n", params->opcodes.write_sr1);
+	if (params->opcodes.write_sr2)
+		seq_printf(s, " SR2 write\t0x%02x\n", params->opcodes.write_sr2);
+	if (params->opcodes.write_sr1_and_sr2)
+		seq_printf(s, " SR1+SR2 write\t0x%02x\n",
+			   params->opcodes.write_sr1_and_sr2);
+
+	if (params->qe_mask[0] || params->qe_mask[1])
+		seq_printf(s, " QE\t\tSR%d bit %d\n",
+			   params->qe_mask[0] ? 1 : 2,
+			   ffs(params->qe_mask[0] | params->qe_mask[1]) - 1);
 
 	switch (nor->cmd_ext_type) {
 	case SPI_NOR_EXT_NONE:

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:14:09 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:14:09 +0200
Subject: [PATCH v2 17/18] mtd: spi-nor: winbond: W25QxxRV-Q/N/M: Add
 missing chips
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-17-7797642dfc40@bootlin.com>

I currently do not have access to the following chips:

- W25Q25RV-Q/N
- W25Q25RV-M
- W25Q51RV-M
- W25Q02RV-Q/N

Yet, I successfully tested the entire JV family and all other chips from
the RV family. They all behaved strictly identically, so I propose to
nevertheless add the missing comments for the sake of clarity and
completeness, as well as the missing 02JV/RV entry.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 5e3924345667..17d889709d53 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -258,7 +258,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 		.fixups = &w25q128_fixups,
 	}, {
-		/* W25Q256JV-Q/N */
+		/* W25Q256JV-Q/N, W25Q25RV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x19),
 		.size = SZ_32M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
@@ -278,6 +278,12 @@ static const struct flash_info winbond_nor_parts[] = {
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &w25qxxjv_rv_fixups,
+	}, {
+		/* W25Q02RV-Q/N */
+		.id = SNOR_ID(0xef, 0x40, 0x22),
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+		.fixups = &w25qxxjv_rv_fixups,
 	}, {
 		.id = SNOR_ID(0xef, 0x50, 0x12),
 		.name = "w25q20bw",
@@ -361,12 +367,12 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q256JV-M */
+		/* W25Q256JV-M, W25Q25RV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
 			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q512JV-M */
+		/* W25Q512JV-M, W25Q51RV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x20),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 03:14:08 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 12:14:08 +0200
Subject: [PATCH v2 16/18] mtd: spi-nor: winbond: Add support for W25Q02RV-M
In-Reply-To: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
References: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40@bootlin.com>
Message-ID: <20260807-winbond-v7-1-spi-nor-rv-addition-v2-16-7797642dfc40@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7022
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff7f44eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d800001602a60081e214dde9e378477a757a75
f7a4d55c39f66dffe970f9a50000000000002c0c000000006655ffff0f00
0000060d44bd27ed080dffffffffff8a00fe21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
fe929abb1ba38dd6a8668200051298a8140bfeb56bd6d58f56b86b074a150f12  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 22 00 00 00
size		256 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (256 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-0fffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff | unlocked | 4096
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   11.527038] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_read
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 268435456 (256M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x10000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_read
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff | unlocked | 4096
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x10000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_read
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_read
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_read2
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_test
4b1329df073cd497567d8cb6878aa20af6af71df98cc7c445d31a69eaf0d69f1  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff |   locked | 4096
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=268435456
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=4096
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 268304384 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffdffff | unlocked | 4094
 0ffe0000-0fffffff |   locked | 2
+ flash_lock -u /dev/mtd0 268304384 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffeffff | unlocked | 4095
 0fff0000-0fffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 260046848 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0f7fffff | unlocked | 3968
 0f800000-0fffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-0fffffff | unlocked | 4094
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-0fffffff | unlocked | 4095
+ all_but_one=4095
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 4095
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-0fffffff |   locked | 4095
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-0fffffff |   locked | 4094
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4095
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffeffff |   locked | 4095
 0fff0000-0fffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 268304384 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffdffff |   locked | 4094
 0ffe0000-0fffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index a6325bba96d0..5e3924345667 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -377,7 +377,7 @@ static const struct flash_info winbond_nor_parts[] = {
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 		.fixups = &w25qxxjv_rv_fixups,
 	}, {
-		/* W25Q02JV-M */
+		/* W25Q02JV-M, W25Q02RV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 06:50:42 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 15:50:42 +0200
Subject: [PATCH 2/2] mtd: spinand: winbond: add support for W25N01KW and
 W25N02LW
In-Reply-To: <20260807064339.1632249-3-md.alam@oss.qualcomm.com> (Md Sadre
	Alam's message of "Fri, 7 Aug 2026 12:13:39 +0530")
References: <20260807064339.1632249-1-md.alam@oss.qualcomm.com>
	<20260807064339.1632249-3-md.alam@oss.qualcomm.com>
Message-ID: <875x1mca4t.fsf@bootlin.com>

Hi Sadre,

On 07/08/2026 at 12:13:39 +0530, Md Sadre Alam <md.alam at oss.qualcomm.com> wrote:

> Add support for the Winbond W25N01KW and W25N02LW SPI-NAND
> devices.
>
> The W25N01KW shares the same geometry, OOB layout, and ECC
> status handling as the existing W25N01KV, while the W25N02LW
> matches the existing W25N02KV/W25N02KW devices and reuses
> their OOB layout and ECC status callback.
>
> Signed-off-by: Md Sadre Alam <md.alam at oss.qualcomm.com>
> ---

Sashiko raises a point:

- [Medium] The device ID `0x12, 0x22` for W25N02LW breaks the
established pattern for Winbond 1.8V SPI NAND devices and is highly
likely a typo for `0xb2, 0x22`.

Can you please check?

Thanks,
Miqu?l


From miquel.raynal at bootlin.com  Fri Aug  7 06:52:12 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 15:52:12 +0200
Subject: [PATCH 1/2] mtd: spinand: winbond: add support for W25N04LW
In-Reply-To: <20260807064339.1632249-2-md.alam@oss.qualcomm.com> (Md Sadre
	Alam's message of "Fri, 7 Aug 2026 12:13:38 +0530")
References: <20260807064339.1632249-1-md.alam@oss.qualcomm.com>
	<20260807064339.1632249-2-md.alam@oss.qualcomm.com>
Message-ID: <87zeyyavhv.fsf@bootlin.com>

On 07/08/2026 at 12:13:38 +0530, Md Sadre Alam <md.alam at oss.qualcomm.com> wrote:

> Add support for Winbond W25N04LW 4Gbit SPI-NAND.
> It has 8-bit on-die ECC and a 4096+256 byte page
> (4096 byte main area, 128 byte usable OOB once
> on-chip ECC hides the 128 byte parity area),

That is not what you did below. Below you declare 254 free bytes.

Thanks,
Miqu?l


From miquel.raynal at bootlin.com  Fri Aug  7 06:55:41 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 15:55:41 +0200
Subject: [PATCH v2] mtd: rawnand: sunxi: add H616 MBUS DMA support
In-Reply-To: <20260717164202.3099982-1-james.hilliard1@gmail.com> (James
	Hilliard's message of "Fri, 17 Jul 2026 10:41:58 -0600")
References: <20260717164202.3099982-1-james.hilliard1@gmail.com>
Message-ID: <87tsp6avc2.fsf@bootlin.com>

Hi James,

On 17/07/2026 at 10:41:58 -06, James Hilliard <james.hilliard1 at gmail.com> wrote:

> The H616 NAND controller uses a descriptor-based internal MBUS DMA
> engine instead of the direct address and count registers used by the
> A23/A33 controller. Since the driver does not support these descriptors,
> it currently attempts to request an external rxtx DMA channel and falls
> back to PIO when none is provided.
>
> Add a single-descriptor backend to the existing ECC page DMA paths.
> Allocate the descriptor coherently, constrain data mappings to the
> controller's 32-bit address range, program the H6-style data block mask,
> and request an interrupt for both command and DMA completion. Keep the
> existing external DMA and legacy MBUS DMA paths unchanged, and fall back
> to PIO if the descriptor cannot be allocated.
>
> With identical kernels except for this patch, running
> flash_speed -d -b 1906 -c 100 /dev/mtd6 on an H616 board with 2 KiB-page
> SLC NAND reported:
>
>                          PIO          descriptor DMA
> eraseblock write        3365 KiB/s       4192 KiB/s
> eraseblock read         6454 KiB/s      16040 KiB/s
> page write              3254 KiB/s       4021 KiB/s
> page read               6419 KiB/s      15686 KiB/s
>
> Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>

This patch does not apply (linux-mtd repository, nand/next branch). You
need to rebase and send a new iteration.

Thansk,
Miqu?l


From miquel.raynal at bootlin.com  Fri Aug  7 06:56:47 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 15:56:47 +0200
Subject: [PATCH v2] mtd: rawnand: sunxi: add H616 MBUS DMA support
In-Reply-To: <87tsp6avc2.fsf@bootlin.com> (Miquel Raynal's message of "Fri, 07
	Aug 2026 15:55:41 +0200")
References: <20260717164202.3099982-1-james.hilliard1@gmail.com>
	<87tsp6avc2.fsf@bootlin.com>
Message-ID: <87o6feava8.fsf@bootlin.com>


> This patch does not apply (linux-mtd repository, nand/next branch). You
> need to rebase and send a new iteration.

Nevermind, I got it applied twice and the error was of course due to the
second try, my bad, no need to resend.

Miqu?l


From miquel.raynal at bootlin.com  Fri Aug  7 06:58:46 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 15:58:46 +0200
Subject: [PATCH v2 0/3] mtd: rawnand: sunxi: fix H6/H616 controller timings
In-Reply-To: <20260717-submit-sunxi-nand-h6-h616-timings-v2-0-225515107640@gmail.com>
	(James Hilliard's message of "Fri, 17 Jul 2026 11:33:56 -0600")
References: <20260717-submit-sunxi-nand-h6-h616-timings-v2-0-225515107640@gmail.com>
Message-ID: <87ik5mav6x.fsf@bootlin.com>

Hi James,

On 17/07/2026 at 11:33:56 -06, James Hilliard <james.hilliard1 at gmail.com> wrote:

> The sunxi NAND timing solver currently applies the original A10 NDFC
> timing characteristics to every supported controller. The H6/H616 NDFC
> uses two internal cycles for several setup and hold intervals and has
> different delay-field encodings, so the resulting clock and timing
> register values do not match the hardware.
>
> Prepare for per-controller timing data in two behaviorally focused
> patches, then add and select the H616 characteristics in the final fix.
> Legacy controllers retain their existing setup-cycle behavior and delay
> encodings.
>
> Tested on an H616 board with Toshiba TC58NVG1S3H NAND.
>
> Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>

There are a number of checkpatch issue. Can you please fix them? (except
the camelcase one which is fine with timing names coming from the ONFI
spec).

Thanks,
Miqu?l


From miquel.raynal at bootlin.com  Fri Aug  7 06:59:57 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 15:59:57 +0200
Subject: [PATCH v2] mtd: spinand: fmsh: fix FM25G01B/FM25G02B Quad I/O
 read dummy cycles
In-Reply-To: <20260717155051.50-1-sanderrrs@gmail.com>
References: <20260717155051.50-1-sanderrrs@gmail.com>
Message-ID: <178611119790.758547.8725356694959417850.b4-ty@bootlin.com>

On Fri, 17 Jul 2026 18:50:50 +0300, Aleksandr Mineev wrote:
> The FM25G01B/FM25G02B datasheets specify a single dummy byte for the
> 0xEB Quad I/O read-from-cache operation, but the generic
> read_cache_variants set uses two dummy bytes for the 1S-4S-4S variant.
> The extra dummy byte shifts the data phase and returns corrupted data
> with no ECC error, breaking boot on boards using these chips.
> 
> Use a dedicated read-from-cache variant set with ndummy=1 for the
> 1S-4S-4S (0xEB) operation.
> 
> [...]

Applied to nand/next, thanks!

[1/1] mtd: spinand: fmsh: fix FM25G01B/FM25G02B Quad I/O read dummy cycles
      commit: 8211f2d74b356a02fe38aeea5b74030ac156461f

Patche(s) should be available on mtd/linux.git and will be
part of the next PR (provided that no robot complains by then).

Kind regards,
Miqu?l



From miquel.raynal at bootlin.com  Fri Aug  7 07:00:03 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 16:00:03 +0200
Subject: [PATCH v2] mtd: rawnand: sunxi: add H616 MBUS DMA support
In-Reply-To: <20260717164202.3099982-1-james.hilliard1@gmail.com>
References: <20260717164202.3099982-1-james.hilliard1@gmail.com>
Message-ID: <178611120394.758547.12981488112151990618.b4-ty@bootlin.com>

On Fri, 17 Jul 2026 10:41:58 -0600, James Hilliard wrote:
> The H616 NAND controller uses a descriptor-based internal MBUS DMA
> engine instead of the direct address and count registers used by the
> A23/A33 controller. Since the driver does not support these descriptors,
> it currently attempts to request an external rxtx DMA channel and falls
> back to PIO when none is provided.
> 
> Add a single-descriptor backend to the existing ECC page DMA paths.
> Allocate the descriptor coherently, constrain data mappings to the
> controller's 32-bit address range, program the H6-style data block mask,
> and request an interrupt for both command and DMA completion. Keep the
> existing external DMA and legacy MBUS DMA paths unchanged, and fall back
> to PIO if the descriptor cannot be allocated.
> 
> [...]

Applied to nand/next, thanks!

[1/1] mtd: rawnand: sunxi: add H616 MBUS DMA support
      commit: 5ee37db34637ca6e33ce01d7062cb31275484404

Patche(s) should be available on mtd/linux.git and will be
part of the next PR (provided that no robot complains by then).

Kind regards,
Miqu?l



From miquel.raynal at bootlin.com  Fri Aug  7 07:00:05 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 16:00:05 +0200
Subject: [PATCH v2] mtd: rawnand: validate ONFI extended parameter page
 sections
In-Reply-To: <20260720115725.10096-1-pengpeng@iscas.ac.cn>
References: <20260720115725.10096-1-pengpeng@iscas.ac.cn>
Message-ID: <178611120597.758547.13517822399489917781.b4-ty@bootlin.com>

On Mon, 20 Jul 2026 19:57:25 +0800, Pengpeng Hou wrote:
> nand_flash_detect_ext_param_page() allocates the length declared by the
> ONFI parameter page, then treats the data as a fixed header followed by
> variable-length sections. It reads that header and advances over sections
> without first proving that the fixed page and each current section fit in
> the allocation.
> 
> Reject pages shorter than the fixed header, track the remaining variable
> area while walking sections, and require the ECC section to contain every
> field read from struct onfi_ext_ecc_info. Use device-scoped diagnostics
> that identify the malformed ONFI section.
> 
> [...]

Applied to nand/next, thanks!

[1/1] mtd: rawnand: validate ONFI extended parameter page sections
      commit: e5e415262330bd70f983e091d8919d9dcd99e475

Patche(s) should be available on mtd/linux.git and will be
part of the next PR (provided that no robot complains by then).

Kind regards,
Miqu?l



From miquel.raynal at bootlin.com  Fri Aug  7 07:00:15 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 16:00:15 +0200
Subject: [PATCH v2] mtd: rawnand: gpmi: add debugfs entry for BCH
 geometry
In-Reply-To: <20260729-nand_debugfs-v2-1-2631e92adcb2@nxp.com>
References: <20260729-nand_debugfs-v2-1-2631e92adcb2@nxp.com>
Message-ID: <178611121596.758547.3889962762044128142.b4-ty@bootlin.com>

On Wed, 29 Jul 2026 14:22:20 -0500, han.xu at oss.nxp.com wrote:
> Export the BCH geometry parameters via debugfs to aid debugging and
> provide the necessary information for legacy kobs-ng tool. The debugfs
> directory "gpmi-nand" is created under the root debugfs tree, exposing:
> 
> bch_geometry  - blob containing the struct bch_geometry fields
> raw_mode      - flag indicating raw mode status
> 
> [...]

Applied to nand/next, thanks!

[1/1] mtd: rawnand: gpmi: add debugfs entry for BCH geometry
      commit: 113f62225e98febabba0fee00afd29d4eaf65abb

Patche(s) should be available on mtd/linux.git and will be
part of the next PR (provided that no robot complains by then).

Kind regards,
Miqu?l



From miquel.raynal at bootlin.com  Fri Aug  7 07:01:17 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 16:01:17 +0200
Subject: [PATCH 0/4] rawnand: pl35x: Implement mixed ECC
In-Reply-To: <20260723-mix-ecc-v1-0-7361c3baeb07@bootlin.com> (Bastien
	Curutchet's message of "Thu, 23 Jul 2026 15:41:00 +0200")
References: <20260723-mix-ecc-v1-0-7361c3baeb07@bootlin.com>
Message-ID: <87cxvuav2q.fsf@bootlin.com>

Hello Bastien,

On 23/07/2026 at 15:41:00 +02, Bastien Curutchet <bastien.curutchet at bootlin.com> wrote:

> Hi all,
>
> This series addresses the PL353 errata "SLC ECC misses single error to
> bit0 byte0 and fails to detect some double error cases" (see 721059 in
> [1]).
>
> The ECC HW engine is known to be broken on this chip, it fails to detect
> some bitflips on reads.
>
> This series implements a mixed-ECC where the HW engine is used on writes
> and reads are done by software. To make it work, the software ECC has to
> use the same layout for the ECC bits than the HW engine. Patches 1 & 2
> add the PL35x ordering to the available orderings for soft ECC. Patches
> 3 & 4 make use of this ordering to implement the mixed-ECC for the PL353
> controller.
>
> Note that this series is based on top of nand/next.
>
> [1]: https://developer.arm.com/documentation/rlnc000227/a
>
> Signed-off-by: Bastien Curutchet <bastien.curutchet at bootlin.com>
> ---

There are several interesting Sashiko findings for this series, can you
please have a look?

Thanks!
Miqu?l


From miquel.raynal at bootlin.com  Fri Aug  7 07:02:08 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 16:02:08 +0200
Subject: [PATCH v2] mtd: maps: remove dead select of
 MTD_CFI_BE_BYTE_SWAP
In-Reply-To: <20260722220618.198164-1-julianbraha@gmail.com>
References: <20260722220618.198164-1-julianbraha@gmail.com>
Message-ID: <178611132874.763513.10124405560170395066.b4-ty@bootlin.com>

On Wed, 22 Jul 2026 23:06:18 +0100, Julian Braha wrote:
> 'select' does not work on config options in a 'choice', so currently it is
> possible to enable MTD_PHYSMAP_IXP4XX without MTD_CFI_BE_BYTE_SWAP.
> 
> >From a previous conversation, it was decided to remove the select entirely:
> https://lore.kernel.org/all/c6268184-4904-49d9-b14f-0e11dce8bdad at app.fastmail.com/
> 
> This dead select was found by kconfirm, a static analysis tool for Kconfig.
> 
> [...]

Applied to mtd/next, thanks!

[1/1] mtd: maps: remove dead select of MTD_CFI_BE_BYTE_SWAP
      commit: 94d32f1ace8ee3ef3537ac6c8e71b76a418a4762

Patche(s) should be available on mtd/linux.git and will be
part of the next PR (provided that no robot complains by then).

Kind regards,
Miqu?l



From miquel.raynal at bootlin.com  Fri Aug  7 07:46:47 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 16:46:47 +0200
Subject: [PATCH v2 1/3] spi: spi-mem: Flag DQS capability
In-Reply-To: <20260807-winbond-nand-next-phy-tuning-v2-0-73595e89d6e3@bootlin.com>
References: <20260807-winbond-nand-next-phy-tuning-v2-0-73595e89d6e3@bootlin.com>
Message-ID: <20260807-winbond-nand-next-phy-tuning-v2-1-73595e89d6e3@bootlin.com>

DQS is a typical SPI memory signal used to help with reading the data on
the bus at high speeds (especially in DTR mode) by avoiding clock
skews. The chip generates a clock signal synchronized with its data
output fronts, also called data strobe.

SPI NOR and SPI NAND cores must set this flag in order to indicate to
other layers that DQS is available.

Create a getter and a setter to reach this capability.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
Acked-by: Mark Brown <broonie at kernel.org>
---
 drivers/spi/spi-mem.c       | 32 ++++++++++++++++++++++++++++++++
 include/linux/spi/spi-mem.h |  4 ++++
 2 files changed, 36 insertions(+)

diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index 1816d9d2712a..cb5ec486f494 100644
--- a/drivers/spi/spi-mem.c
+++ b/drivers/spi/spi-mem.c
@@ -541,6 +541,38 @@ const char *spi_mem_get_name(struct spi_mem *mem)
 }
 EXPORT_SYMBOL_GPL(spi_mem_get_name);
 
+/**
+ * spi_mem_set_dqs() - Mark DQS as being available
+ * @mem: the SPI memory
+ *
+ * When reading at high frequencies (> 100MHz), especially when DTR is enabled,
+ * transfer speed is limited due to clock skews. In particular, the controller
+ * does not know the board propagation delay nor the memory chip internal delay
+ * (clock in to data out) and thus cannot optimize its sampling points.
+ * Mitigating this limitation is possible with the addition of a data strobe
+ * signal, commonly named DQS.
+ *
+ * Set the DQS boolean if the feature is available and configured at the chip
+ * level. Controllers may query this value.
+ */
+void spi_mem_set_dqs(struct spi_mem *mem)
+{
+	mem->dqs = true;
+}
+EXPORT_SYMBOL_GPL(spi_mem_set_dqs);
+
+/**
+ * spi_mem_has_dqs() - Query whether the DQS is available or not
+ * @mem: the SPI memory
+ *
+ * Return: a boolean indicating whether the DQS signal is available or not.
+ */
+bool spi_mem_has_dqs(struct spi_mem *mem)
+{
+	return mem->dqs;
+}
+EXPORT_SYMBOL_GPL(spi_mem_has_dqs);
+
 /**
  * spi_mem_adjust_op_size() - Adjust the data size of a SPI mem operation to
  *			      match controller limitations
diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h
index c4e7ebafdf0e..89d3f0218cc9 100644
--- a/include/linux/spi/spi-mem.h
+++ b/include/linux/spi/spi-mem.h
@@ -275,6 +275,7 @@ struct spi_mem_dirmap_desc {
  * @spi: the underlying SPI device
  * @drvpriv: spi_mem_driver private data
  * @name: name of the SPI memory device
+ * @dqs: extra data trobe pin available for high frequency read operations
  *
  * Extra information that describe the SPI memory device and may be needed by
  * the controller to properly handle this device should be placed here.
@@ -286,6 +287,7 @@ struct spi_mem {
 	struct spi_device *spi;
 	void *drvpriv;
 	const char *name;
+	bool dqs;
 };
 
 /**
@@ -463,6 +465,8 @@ bool spi_mem_default_supports_op(struct spi_mem *mem,
 }
 #endif /* CONFIG_SPI_MEM */
 
+void spi_mem_set_dqs(struct spi_mem *mem);
+bool spi_mem_has_dqs(struct spi_mem *mem);
 int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op);
 void spi_mem_adjust_op_freq(struct spi_mem *mem, struct spi_mem_op *op);
 u64 spi_mem_calc_op_duration(struct spi_mem *mem, struct spi_mem_op *op);

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 07:46:46 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 16:46:46 +0200
Subject: [PATCH v2 0/3] mtd/spi-mem: Enable DQS support
Message-ID: <20260807-winbond-nand-next-phy-tuning-v2-0-73595e89d6e3@bootlin.com>

For his PHY tuning series on the Cadence QSPI controller embedded in TI
SoCs, Santhosh initially needed to access the availability of the DQS
(data strobe) signal. This is a chip dependent capability, which may
sometimes be enabled.

Create a SPI memory flag for it, let the SPI NAND core set this flag
when it knows about the capability, and expect manufacturer drivers to
enable it for octal needs when the chips are compatible.

This is an alternative at needing a DT property. Please note that there
are a few blind spots:
- the line may not be wired (this would be surprising, but can be
  flagged this time by a DT property)
- manufacturer drivers must enable the feature if it is
  available (especially for high speed DTR modes)
- this implementation is proposed for SPI NANDs only, if this proposal
  is accepted the same approach may be taken in SPI NOR.

Here is the original thread which lead to this series:
https://lore.kernel.org/linux-spi/87v7gbdwdh.fsf at bootlin.com/T/#ma79fc364d7b882a48dbdf47203dde75df4bb0ec4

This series was compile tested only at this stage. As DDR tuning does
not yet work on my board, I cannot make sure this change has a real
impact.

Mark, as agreed, you can take the spi-mem patch through spi for this
cycle, and I will pick up the remaining spi-nand patches after the merge
window.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
Changes in v2:
- Dropped the RFC prefix.
- Rebased on top of v7.2-rc1.
- Dropped the spi patch for now, just allowing to use DQS on a few
  Winbond SPI NANDs (can be improved later).
- Link to v1: https://lore.kernel.org/r/20260205-winbond-nand-next-phy-tuning-v1-0-5e7d3976f0f1 at bootlin.com

---
Miquel Raynal (3):
      spi: spi-mem: Flag DQS capability
      mtd: spi-nand: Set the DQS spi-mem capability if available
      mtd: spi-nand: winbond: Enable the DQS pin on W35N**JW series

 drivers/mtd/nand/spi/core.c    |  4 ++++
 drivers/mtd/nand/spi/winbond.c |  8 ++++----
 drivers/spi/spi-mem.c          | 32 ++++++++++++++++++++++++++++++++
 include/linux/mtd/spinand.h    |  1 +
 include/linux/spi/spi-mem.h    |  4 ++++
 5 files changed, 45 insertions(+), 4 deletions(-)
---
base-commit: 6f787fd9f19e4e1bc80d3eee1a394eb65defdff7
change-id: 20260205-winbond-nand-next-phy-tuning-aabefc018032

Best regards,
-- 
Miquel Raynal <miquel.raynal at bootlin.com>



From miquel.raynal at bootlin.com  Fri Aug  7 07:46:48 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 16:46:48 +0200
Subject: [PATCH v2 2/3] mtd: spi-nand: Set the DQS spi-mem capability if
 available
In-Reply-To: <20260807-winbond-nand-next-phy-tuning-v2-0-73595e89d6e3@bootlin.com>
References: <20260807-winbond-nand-next-phy-tuning-v2-0-73595e89d6e3@bootlin.com>
Message-ID: <20260807-winbond-nand-next-phy-tuning-v2-2-73595e89d6e3@bootlin.com>

Check whether the NAND is capable of generating a DQS signal and set the
flag accordingly.

Not wiring the DQS signal on a DQS capable chip that will be used at
frequency requiring this signal may be considered a hardware bug, so
let's assume this line will be routed "in most cases". If/when we get
issues with this assumption, a DT property describing the lacking line
in the routing can be created.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/nand/spi/core.c | 4 ++++
 include/linux/mtd/spinand.h | 1 +
 2 files changed, 5 insertions(+)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 72a630b266f8..6ffd0f1b9c26 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -2057,6 +2057,10 @@ static int spinand_probe(struct spi_mem *mem)
 	read_op = *spinand->op_templates->read_cache;
 	write_op = *spinand->op_templates->write_cache;
 
+	/* Assume manufacturer drivers will enable the DQS pin if it is available */
+	if (spinand->flags & SPINAND_HAS_DQS)
+		spi_mem_set_dqs(mem);
+
 	ret = spi_mem_execute_tuning(mem, &read_op, &write_op);
 	if (ret && ret != -EOPNOTSUPP) {
 		dev_warn(&mem->spi->dev, "Failed to execute PHY tuning: %d\n",
diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
index ec6efcfeef83..fe05b477afef 100644
--- a/include/linux/mtd/spinand.h
+++ b/include/linux/mtd/spinand.h
@@ -490,6 +490,7 @@ struct spinand_ecc_info {
 #define SPINAND_HAS_READ_PLANE_SELECT_BIT		BIT(3)
 #define SPINAND_NO_RAW_ACCESS				BIT(4)
 #define SPINAND_ODTR_PACKED_PAGE_READ			BIT(5)
+#define SPINAND_HAS_DQS					BIT(6)
 
 /**
  * struct spinand_ondie_ecc_conf - private SPI-NAND on-die ECC engine structure

-- 
2.54.0



From miquel.raynal at bootlin.com  Fri Aug  7 07:46:49 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Fri, 07 Aug 2026 16:46:49 +0200
Subject: [PATCH v2 3/3] mtd: spi-nand: winbond: Enable the DQS pin on
 W35N**JW series
In-Reply-To: <20260807-winbond-nand-next-phy-tuning-v2-0-73595e89d6e3@bootlin.com>
References: <20260807-winbond-nand-next-phy-tuning-v2-0-73595e89d6e3@bootlin.com>
Message-ID: <20260807-winbond-nand-next-phy-tuning-v2-3-73595e89d6e3@bootlin.com>

These chips have a DQS pin, enable it by default in DTR mode because
there is apparently no issue in setting it for lower frequencies, and
the extra power consumption seems very low compared to the current drawn
by the NAND array itself while operating.

This setting will be required for high speed I/O transfers (with PHY
tuning).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/nand/spi/winbond.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
index 9b78c1e6cbc9..8209b214e594 100644
--- a/drivers/mtd/nand/spi/winbond.c
+++ b/drivers/mtd/nand/spi/winbond.c
@@ -607,7 +607,7 @@ static int w35n0xjw_vcr_cfg(struct spinand_device *spinand,
 	else if (!single && !dtr)
 		io_mode = W35N01JW_VCR_IO_MODE_OCTAL_SDR;
 	else if (!single && dtr)
-		io_mode = W35N01JW_VCR_IO_MODE_OCTAL_DDR;
+		io_mode = W35N01JW_VCR_IO_MODE_OCTAL_DDR_DS;
 	else
 		return -EINVAL;
 
@@ -689,7 +689,7 @@ static const struct spinand_info winbond_spinand_table[] = {
 							&write_cache_octal_variants,
 							&update_cache_octal_variants,
 							&cont_read_cache_octal_variants),
-		     0,
+		     SPINAND_HAS_DQS,
 		     SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops),
 		     SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status),
 		     SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg),
@@ -744,7 +744,7 @@ static const struct spinand_info winbond_spinand_table[] = {
 							&write_cache_octal_variants,
 							&update_cache_octal_variants,
 							&cont_read_cache_octal_variants),
-		     SPINAND_ODTR_PACKED_PAGE_READ,
+		     SPINAND_ODTR_PACKED_PAGE_READ | SPINAND_HAS_DQS,
 		     SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops),
 		     SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status),
 		     SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg),
@@ -776,7 +776,7 @@ static const struct spinand_info winbond_spinand_table[] = {
 							&write_cache_octal_variants,
 							&update_cache_octal_variants,
 							&cont_read_cache_octal_variants),
-		     SPINAND_ODTR_PACKED_PAGE_READ,
+		     SPINAND_ODTR_PACKED_PAGE_READ | SPINAND_HAS_DQS,
 		     SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops),
 		     SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status),
 		     SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg),

-- 
2.54.0



From james.hilliard1 at gmail.com  Fri Aug  7 08:38:58 2026
From: james.hilliard1 at gmail.com (James Hilliard)
Date: Fri, 07 Aug 2026 09:38:58 -0600
Subject: [PATCH v3 0/3] mtd: rawnand: sunxi: fix H6/H616 controller timings
Message-ID: <20260807-submit-sunxi-nand-h6-h616-timings-v3-0-91287b543f41@gmail.com>

The sunxi NAND timing solver currently applies the original A10 NDFC
timing characteristics to every supported controller. The H6/H616 NDFC
uses two internal cycles for several setup and hold intervals and has
different delay-field encodings, so the resulting clock and timing
register values do not match the hardware.

Prepare for per-controller timing data in two behaviorally focused
patches, then add and select the H616 characteristics in the final fix.
Legacy controllers retain their existing setup-cycle behavior and delay
encodings.

Tested on an H616 board with Toshiba TC58NVG1S3H NAND.

Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
Changes v1 -> v2:
  - Split legacy timing setup from the H616 fix  (suggested by Miquel)
  - Move tADL/tWHR encodings to a separate patch  (suggested by Miquel)
  - Keep the timing lookup helper unchanged  (suggested by Miquel)
  - Drop the minimum-period helper  (suggested by Miquel)
  - Align H616 timing-data and capability names  (suggested by Miquel)
  - Add Cc: stable to all three patches  (suggested by Miquel)

Changes v2 -> v3:
  - Rebase onto current nand/next
  - Fix strict checkpatch diagnostics  (suggested by Miquel)
  - Add Fixes tags to the two stable preparatory patches
- Link to v2: https://patch.msgid.link/20260717-submit-sunxi-nand-h6-h616-timings-v2-0-225515107640 at gmail.com

To: Miquel Raynal <miquel.raynal at bootlin.com>
To: Richard Weinberger <richard at nod.at>
To: Vignesh Raghavendra <vigneshr at ti.com>
To: Chen-Yu Tsai <wens at kernel.org>
To: Jernej Skrabec <jernej.skrabec at gmail.com>
To: Samuel Holland <samuel at sholland.org>
To: Richard Genoud <richard.genoud at bootlin.com>
Cc: linux-mtd at lists.infradead.org
Cc: linux-arm-kernel at lists.infradead.org
Cc: linux-sunxi at lists.linux.dev
Cc: linux-kernel at vger.kernel.org

---
James Hilliard (3):
      mtd: rawnand: sunxi: group controller delay tables
      mtd: rawnand: sunxi: describe tADL and tWHR delays
      mtd: rawnand: sunxi: fix H6/H616 controller timings

 drivers/mtd/nand/raw/sunxi_nand.c | 119 +++++++++++++++++++++++++++-----------
 1 file changed, 86 insertions(+), 33 deletions(-)
---
base-commit: f97bdc8ec1dc7b33781a702eeba55326c206be56
change-id: 20260717-submit-sunxi-nand-h6-h616-timings-794a2994267d

Best regards,
--  
James Hilliard <james.hilliard1 at gmail.com>



From james.hilliard1 at gmail.com  Fri Aug  7 08:38:59 2026
From: james.hilliard1 at gmail.com (James Hilliard)
Date: Fri, 07 Aug 2026 09:38:59 -0600
Subject: [PATCH v3 1/3] mtd: rawnand: sunxi: group controller delay tables
In-Reply-To: <20260807-submit-sunxi-nand-h6-h616-timings-v3-0-91287b543f41@gmail.com>
References: <20260807-submit-sunxi-nand-h6-h616-timings-v3-0-91287b543f41@gmail.com>
Message-ID: <20260807-submit-sunxi-nand-h6-h616-timings-v3-1-91287b543f41@gmail.com>

The tWB and tRHW timing field encodings are controller properties, but
they currently live in standalone lookup tables.

Group them in a timing descriptor selected through the controller
capability data. Point every existing controller at the legacy values so
this is a pure preparation change.

Fixes: 88fd4e4deae8 ("mtd: rawnand: sunxi: Add support for H616 nand controller")
Cc: stable at vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
 drivers/mtd/nand/raw/sunxi_nand.c | 36 ++++++++++++++++++++++++++++--------
 1 file changed, 28 insertions(+), 8 deletions(-)

diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 02647565c8ba..9885248c9b85 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -237,6 +237,14 @@ struct sunxi_nand_hw_ecc {
 	u32 ecc_ctl;
 };
 
+#define SUNXI_NFC_TIMING_STEPS	4
+
+/* Delay arrays contain internal NDFC clock cycles for field values 0 to 3. */
+struct sunxi_nfc_timings {
+	s32 tWB[SUNXI_NFC_TIMING_STEPS];
+	s32 tRHW[SUNXI_NFC_TIMING_STEPS];
+};
+
 /**
  * struct sunxi_nand_chip - stores NAND chip device related information
  *
@@ -301,6 +309,7 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand)
  *			bytes to write
  * @nuser_data_tab:	Size of @user_data_len_tab
  * @sram_size:		Size of the NAND controller SRAM
+ * @timings:		Controller timing characteristics
  */
 struct sunxi_nfc_caps {
 	bool has_mdma;
@@ -327,6 +336,7 @@ struct sunxi_nfc_caps {
 	unsigned int nuser_data_tab;
 	unsigned int max_ecc_steps;
 	int sram_size;
+	const struct sunxi_nfc_timings *timings;
 };
 
 /**
@@ -1667,8 +1677,10 @@ static int sunxi_nfc_hw_ecc_write_oob(struct nand_chip *nand, int page)
 	return nand_prog_page_end_op(nand);
 }
 
-static const s32 tWB_lut[] = {6, 12, 16, 20};
-static const s32 tRHW_lut[] = {4, 8, 12, 20};
+static const struct sunxi_nfc_timings sun4i_a10_nfc_timings = {
+	.tWB = { 6, 12, 16, 20 },
+	.tRHW = { 4, 8, 12, 20 },
+};
 
 static int _sunxi_nand_lookup_timing(const s32 *lut, int lut_size, u32 duration,
 		u32 clk_period)
@@ -1693,6 +1705,7 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
 {
 	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
 	struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller);
+	const struct sunxi_nfc_timings *nfc_timings = nfc->caps->timings;
 	const struct nand_sdr_timings *timings;
 	u32 min_clk_period = 0;
 	s32 tWB, tADL, tWHR, tRHW, tCAD;
@@ -1763,8 +1776,10 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
 		min_clk_period = DIV_ROUND_UP(timings->tWC_min, 2);
 
 	/* T16 - T19 + tCAD */
-	if (timings->tWB_max > (min_clk_period * 20))
-		min_clk_period = DIV_ROUND_UP(timings->tWB_max, 20);
+	if (timings->tWB_max >
+	    (min_clk_period * nfc_timings->tWB[SUNXI_NFC_TIMING_STEPS - 1]))
+		min_clk_period = DIV_ROUND_UP(timings->tWB_max,
+					      nfc_timings->tWB[SUNXI_NFC_TIMING_STEPS - 1]);
 
 	if (timings->tADL_min > (min_clk_period * 32))
 		min_clk_period = DIV_ROUND_UP(timings->tADL_min, 32);
@@ -1772,8 +1787,10 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
 	if (timings->tWHR_min > (min_clk_period * 32))
 		min_clk_period = DIV_ROUND_UP(timings->tWHR_min, 32);
 
-	if (timings->tRHW_min > (min_clk_period * 20))
-		min_clk_period = DIV_ROUND_UP(timings->tRHW_min, 20);
+	if (timings->tRHW_min >
+	    (min_clk_period * nfc_timings->tRHW[SUNXI_NFC_TIMING_STEPS - 1]))
+		min_clk_period = DIV_ROUND_UP(timings->tRHW_min,
+					      nfc_timings->tRHW[SUNXI_NFC_TIMING_STEPS - 1]);
 
 	/*
 	 * In non-EDO, tREA should be less than tRP to guarantee that the
@@ -1789,7 +1806,7 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
 	if (timings->tREA_max > min_clk_period && !timings->tRLOH_min)
 		min_clk_period = timings->tREA_max;
 
-	tWB  = sunxi_nand_lookup_timing(tWB_lut, timings->tWB_max,
+	tWB  = sunxi_nand_lookup_timing(nfc_timings->tWB, timings->tWB_max,
 					min_clk_period);
 	if (tWB < 0) {
 		dev_err(nfc->dev, "unsupported tWB\n");
@@ -1808,7 +1825,7 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
 		return -EINVAL;
 	}
 
-	tRHW = sunxi_nand_lookup_timing(tRHW_lut, timings->tRHW_min,
+	tRHW = sunxi_nand_lookup_timing(nfc_timings->tRHW, timings->tRHW_min,
 					min_clk_period);
 	if (tRHW < 0) {
 		dev_err(nfc->dev, "unsupported tRHW\n");
@@ -2595,6 +2612,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = {
 	.nstrengths = ARRAY_SIZE(sunxi_ecc_strengths_a10),
 	.max_ecc_steps = 16,
 	.sram_size = 1024,
+	.timings = &sun4i_a10_nfc_timings,
 };
 
 static const struct sunxi_nfc_caps sunxi_nfc_a23_caps = {
@@ -2617,6 +2635,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_a23_caps = {
 	.nstrengths = ARRAY_SIZE(sunxi_ecc_strengths_a10),
 	.max_ecc_steps = 16,
 	.sram_size = 1024,
+	.timings = &sun4i_a10_nfc_timings,
 };
 
 static const struct sunxi_nfc_caps sunxi_nfc_h616_caps = {
@@ -2641,6 +2660,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_h616_caps = {
 	.nuser_data_tab = ARRAY_SIZE(sunxi_user_data_len_h6),
 	.max_ecc_steps = 32,
 	.sram_size = 8192,
+	.timings = &sun4i_a10_nfc_timings,
 };
 
 static const struct of_device_id sunxi_nfc_ids[] = {

-- 
2.53.0



From james.hilliard1 at gmail.com  Fri Aug  7 08:39:00 2026
From: james.hilliard1 at gmail.com (James Hilliard)
Date: Fri, 07 Aug 2026 09:39:00 -0600
Subject: [PATCH v3 2/3] mtd: rawnand: sunxi: describe tADL and tWHR delays
In-Reply-To: <20260807-submit-sunxi-nand-h6-h616-timings-v3-0-91287b543f41@gmail.com>
References: <20260807-submit-sunxi-nand-h6-h616-timings-v3-0-91287b543f41@gmail.com>
Message-ID: <20260807-submit-sunxi-nand-h6-h616-timings-v3-2-91287b543f41@gmail.com>

The tADL and tWHR timing fields use four encoded delays, but the driver
currently derives their values with a shift. This hides the actual
controller timing characteristics and lets the clock solver select a
32-cycle delay that the fields cannot encode.

Describe the legacy 7, 15, 23 and 31 cycle thresholds explicitly and use
the tables for both clock selection and field lookup. This prepares the
driver for controllers with different encodings.

Fixes: 88fd4e4deae8 ("mtd: rawnand: sunxi: Add support for H616 nand controller")
Cc: stable at vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
 drivers/mtd/nand/raw/sunxi_nand.c | 30 ++++++++++++++++++++----------
 1 file changed, 20 insertions(+), 10 deletions(-)

diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 9885248c9b85..df52501c1285 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -242,6 +242,8 @@ struct sunxi_nand_hw_ecc {
 /* Delay arrays contain internal NDFC clock cycles for field values 0 to 3. */
 struct sunxi_nfc_timings {
 	s32 tWB[SUNXI_NFC_TIMING_STEPS];
+	s32 tADL[SUNXI_NFC_TIMING_STEPS];
+	s32 tWHR[SUNXI_NFC_TIMING_STEPS];
 	s32 tRHW[SUNXI_NFC_TIMING_STEPS];
 };
 
@@ -1679,6 +1681,8 @@ static int sunxi_nfc_hw_ecc_write_oob(struct nand_chip *nand, int page)
 
 static const struct sunxi_nfc_timings sun4i_a10_nfc_timings = {
 	.tWB = { 6, 12, 16, 20 },
+	.tADL = { 7, 15, 23, 31 },
+	.tWHR = { 7, 15, 23, 31 },
 	.tRHW = { 4, 8, 12, 20 },
 };
 
@@ -1781,11 +1785,15 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
 		min_clk_period = DIV_ROUND_UP(timings->tWB_max,
 					      nfc_timings->tWB[SUNXI_NFC_TIMING_STEPS - 1]);
 
-	if (timings->tADL_min > (min_clk_period * 32))
-		min_clk_period = DIV_ROUND_UP(timings->tADL_min, 32);
+	if (timings->tADL_min >
+	    (min_clk_period * nfc_timings->tADL[SUNXI_NFC_TIMING_STEPS - 1]))
+		min_clk_period = DIV_ROUND_UP(timings->tADL_min,
+					      nfc_timings->tADL[SUNXI_NFC_TIMING_STEPS - 1]);
 
-	if (timings->tWHR_min > (min_clk_period * 32))
-		min_clk_period = DIV_ROUND_UP(timings->tWHR_min, 32);
+	if (timings->tWHR_min >
+	    (min_clk_period * nfc_timings->tWHR[SUNXI_NFC_TIMING_STEPS - 1]))
+		min_clk_period = DIV_ROUND_UP(timings->tWHR_min,
+					      nfc_timings->tWHR[SUNXI_NFC_TIMING_STEPS - 1]);
 
 	if (timings->tRHW_min >
 	    (min_clk_period * nfc_timings->tRHW[SUNXI_NFC_TIMING_STEPS - 1]))
@@ -1813,16 +1821,18 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
 		return tWB;
 	}
 
-	tADL = DIV_ROUND_UP(timings->tADL_min, min_clk_period) >> 3;
-	if (tADL > 3) {
+	tADL = sunxi_nand_lookup_timing(nfc_timings->tADL,
+					timings->tADL_min, min_clk_period);
+	if (tADL < 0) {
 		dev_err(nfc->dev, "unsupported tADL\n");
-		return -EINVAL;
+		return tADL;
 	}
 
-	tWHR = DIV_ROUND_UP(timings->tWHR_min, min_clk_period) >> 3;
-	if (tWHR > 3) {
+	tWHR = sunxi_nand_lookup_timing(nfc_timings->tWHR,
+					timings->tWHR_min, min_clk_period);
+	if (tWHR < 0) {
 		dev_err(nfc->dev, "unsupported tWHR\n");
-		return -EINVAL;
+		return tWHR;
 	}
 
 	tRHW = sunxi_nand_lookup_timing(nfc_timings->tRHW, timings->tRHW_min,

-- 
2.53.0



From james.hilliard1 at gmail.com  Fri Aug  7 08:39:01 2026
From: james.hilliard1 at gmail.com (James Hilliard)
Date: Fri, 07 Aug 2026 09:39:01 -0600
Subject: [PATCH v3 3/3] mtd: rawnand: sunxi: fix H6/H616 controller timings
In-Reply-To: <20260807-submit-sunxi-nand-h6-h616-timings-v3-0-91287b543f41@gmail.com>
References: <20260807-submit-sunxi-nand-h6-h616-timings-v3-0-91287b543f41@gmail.com>
Message-ID: <20260807-submit-sunxi-nand-h6-h616-timings-v3-3-91287b543f41@gmail.com>

The NAND timing calculation assumes that command and address setup and
hold intervals T1-T4, T7 and T11 each take one controller clock. It also
uses the original A10 delay encodings for tWB, tADL, tWHR and tRHW.

The H6/H616 NDFC defines the setup and hold intervals as two internal
clock cycles and uses different delay encodings. Add the H616 timing
characteristics and select them through the controller capability data so
the clock solver and timing fields match the hardware.

Fixes: 88fd4e4deae8 ("mtd: rawnand: sunxi: Add support for H616 nand controller")
Cc: stable at vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
 drivers/mtd/nand/raw/sunxi_nand.c | 49 ++++++++++++++++++++++++++++-----------
 1 file changed, 36 insertions(+), 13 deletions(-)

diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index df52501c1285..ca74c9c333e7 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -241,6 +241,8 @@ struct sunxi_nand_hw_ecc {
 
 /* Delay arrays contain internal NDFC clock cycles for field values 0 to 3. */
 struct sunxi_nfc_timings {
+	/* Internal clock cycles used by T1-T4, T7 and T11. */
+	u8 setup_cycles;
 	s32 tWB[SUNXI_NFC_TIMING_STEPS];
 	s32 tADL[SUNXI_NFC_TIMING_STEPS];
 	s32 tWHR[SUNXI_NFC_TIMING_STEPS];
@@ -1680,12 +1682,21 @@ static int sunxi_nfc_hw_ecc_write_oob(struct nand_chip *nand, int page)
 }
 
 static const struct sunxi_nfc_timings sun4i_a10_nfc_timings = {
+	.setup_cycles = 1,
 	.tWB = { 6, 12, 16, 20 },
 	.tADL = { 7, 15, 23, 31 },
 	.tWHR = { 7, 15, 23, 31 },
 	.tRHW = { 4, 8, 12, 20 },
 };
 
+static const struct sunxi_nfc_timings sun50i_h616_nfc_timings = {
+	.setup_cycles = 2,
+	.tWB = { 28, 44, 60, 76 },
+	.tADL = { 0, 12, 28, 44 },
+	.tWHR = { 0, 12, 28, 44 },
+	.tRHW = { 8, 24, 40, 56 },
+};
+
 static int _sunxi_nand_lookup_timing(const s32 *lut, int lut_size, u32 duration,
 		u32 clk_period)
 {
@@ -1720,20 +1731,28 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
 		return -ENOTSUPP;
 
 	/* T1 <=> tCLS */
-	if (timings->tCLS_min > min_clk_period)
-		min_clk_period = timings->tCLS_min;
+	if (timings->tCLS_min >
+	    min_clk_period * nfc_timings->setup_cycles)
+		min_clk_period = DIV_ROUND_UP(timings->tCLS_min,
+					      nfc_timings->setup_cycles);
 
 	/* T2 <=> tCLH */
-	if (timings->tCLH_min > min_clk_period)
-		min_clk_period = timings->tCLH_min;
+	if (timings->tCLH_min >
+	    min_clk_period * nfc_timings->setup_cycles)
+		min_clk_period = DIV_ROUND_UP(timings->tCLH_min,
+					      nfc_timings->setup_cycles);
 
 	/* T3 <=> tCS */
-	if (timings->tCS_min > min_clk_period)
-		min_clk_period = timings->tCS_min;
+	if (timings->tCS_min >
+	    min_clk_period * nfc_timings->setup_cycles)
+		min_clk_period = DIV_ROUND_UP(timings->tCS_min,
+					      nfc_timings->setup_cycles);
 
 	/* T4 <=> tCH */
-	if (timings->tCH_min > min_clk_period)
-		min_clk_period = timings->tCH_min;
+	if (timings->tCH_min >
+	    min_clk_period * nfc_timings->setup_cycles)
+		min_clk_period = DIV_ROUND_UP(timings->tCH_min,
+					      nfc_timings->setup_cycles);
 
 	/* T5 <=> tWP */
 	if (timings->tWP_min > min_clk_period)
@@ -1744,8 +1763,10 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
 		min_clk_period = timings->tWH_min;
 
 	/* T7 <=> tALS */
-	if (timings->tALS_min > min_clk_period)
-		min_clk_period = timings->tALS_min;
+	if (timings->tALS_min >
+	    min_clk_period * nfc_timings->setup_cycles)
+		min_clk_period = DIV_ROUND_UP(timings->tALS_min,
+					      nfc_timings->setup_cycles);
 
 	/* T8 <=> tDS */
 	if (timings->tDS_min > min_clk_period)
@@ -1760,8 +1781,10 @@ static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline,
 		min_clk_period = DIV_ROUND_UP(timings->tRR_min, 3);
 
 	/* T11 <=> tALH */
-	if (timings->tALH_min > min_clk_period)
-		min_clk_period = timings->tALH_min;
+	if (timings->tALH_min >
+	    min_clk_period * nfc_timings->setup_cycles)
+		min_clk_period = DIV_ROUND_UP(timings->tALH_min,
+					      nfc_timings->setup_cycles);
 
 	/* T12 <=> tRP */
 	if (timings->tRP_min > min_clk_period)
@@ -2670,7 +2693,7 @@ static const struct sunxi_nfc_caps sunxi_nfc_h616_caps = {
 	.nuser_data_tab = ARRAY_SIZE(sunxi_user_data_len_h6),
 	.max_ecc_steps = 32,
 	.sram_size = 8192,
-	.timings = &sun4i_a10_nfc_timings,
+	.timings = &sun50i_h616_nfc_timings,
 };
 
 static const struct of_device_id sunxi_nfc_ids[] = {

-- 
2.53.0



From broonie at kernel.org  Fri Aug  7 09:14:37 2026
From: broonie at kernel.org (Mark Brown)
Date: Fri, 7 Aug 2026 17:14:37 +0100
Subject: [PATCH v2 0/3] mtd/spi-mem: Enable DQS support
In-Reply-To: <20260807-winbond-nand-next-phy-tuning-v2-0-73595e89d6e3@bootlin.com>
References: <20260807-winbond-nand-next-phy-tuning-v2-0-73595e89d6e3@bootlin.com>
Message-ID: <8a729209-3bf9-4f2e-8870-1cbb64a95184@sirena.org.uk>

On Fri, Aug 07, 2026 at 04:46:46PM +0200, Miquel Raynal wrote:

> Mark, as agreed, you can take the spi-mem patch through spi for this
> cycle, and I will pick up the remaining spi-nand patches after the merge
> window.

As I said on the previous version can you please send something that can
be applied?  The MTD patches are failing to apply.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 488 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260807/93c4d591/attachment.sig>

From james.hilliard1 at gmail.com  Fri Aug  7 09:57:19 2026
From: james.hilliard1 at gmail.com (James Hilliard)
Date: Fri, 7 Aug 2026 10:57:19 -0600
Subject: [PATCH v2 0/3] mtd: rawnand: sunxi: fix H6/H616 controller timings
In-Reply-To: <87ik5mav6x.fsf@bootlin.com>
References: <20260717-submit-sunxi-nand-h6-h616-timings-v2-0-225515107640@gmail.com>
 <87ik5mav6x.fsf@bootlin.com>
Message-ID: <CADvTj4pHOpKfNCoVcfDmtCuHh2F0H8e+_AhCwcSKG8xosLpbzA@mail.gmail.com>

On Fri, Aug 7, 2026 at 7:58?AM Miquel Raynal <miquel.raynal at bootlin.com> wrote:
>
> Hi James,
>
> On 17/07/2026 at 11:33:56 -06, James Hilliard <james.hilliard1 at gmail.com> wrote:
>
> > The sunxi NAND timing solver currently applies the original A10 NDFC
> > timing characteristics to every supported controller. The H6/H616 NDFC
> > uses two internal cycles for several setup and hold intervals and has
> > different delay-field encodings, so the resulting clock and timing
> > register values do not match the hardware.
> >
> > Prepare for per-controller timing data in two behaviorally focused
> > patches, then add and select the H616 characteristics in the final fix.
> > Legacy controllers retain their existing setup-cycle behavior and delay
> > encodings.
> >
> > Tested on an H616 board with Toshiba TC58NVG1S3H NAND.
> >
> > Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
>
> There are a number of checkpatch issue. Can you please fix them? (except
> the camelcase one which is fine with timing names coming from the ONFI
> spec).

Should be fixed in my v3:
https://lore.kernel.org/all/20260807-submit-sunxi-nand-h6-h616-timings-v3-0-91287b543f41 at gmail.com/

>
> Thanks,
> Miqu?l


From dbgh9129 at gmail.com  Fri Aug  7 13:45:50 2026
From: dbgh9129 at gmail.com (Yuho Choi)
Date: Fri,  7 Aug 2026 16:45:50 -0400
Subject: [PATCH v1] mtd: ubi: Release device reference on busy detach
Message-ID: <20260807204550.1016755-1-dbgh9129@gmail.com>

ubi_detach_mtd_dev() obtains a device reference through ubi_get_device()
before checking whether the UBI device is busy. The busy return path drops
ubi->ref_count but leaves the device reference held, so the device object
cannot be released after a later detach.

Drop the device reference before returning -EBUSY.

Fixes: 7e84c961b2eb ("mtd: ubi: introduce pre-removal notification for UBI volumes")
Signed-off-by: Yuho Choi <dbgh9129 at gmail.com>
---
 drivers/mtd/ubi/build.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c
index 674ad87809df..ee6ce4471b25 100644
--- a/drivers/mtd/ubi/build.c
+++ b/drivers/mtd/ubi/build.c
@@ -1105,6 +1105,7 @@ int ubi_detach_mtd_dev(int ubi_num, int anyway)
 	ubi->ref_count -= 1;
 	if (ubi->ref_count) {
 		if (!anyway) {
+			put_device(&ubi->dev);
 			spin_unlock(&ubi_devices_lock);
 			return -EBUSY;
 		}
-- 
2.43.0



From han.xu at oss.nxp.com  Fri Aug  7 16:11:27 2026
From: han.xu at oss.nxp.com (han.xu at oss.nxp.com)
Date: Fri, 07 Aug 2026 18:11:27 -0500
Subject: [PATCH] mtd: spinand: Enable QE on all dies
Message-ID: <20260807-spinand_qe-v1-1-c102c79834a3@nxp.com>

From: Han Xu <han.xu at nxp.com>

The QUAD ENABLE (QE) bit is stored in a per-die configuration
register on some SPI-NAND devices. When a device contains multiple
dies, updating the QE bit only on the currently selected die can
leave the remaining dies operating in non-quad mode.
?
Iterate over all targets and update the QE setting on each die
during initialization to ensure consistent quad I/O operation
across the entire device.

Tested on ISSI IS38SMW04G8B.

Fixes: 7529df465248 ("mtd: nand: Add core infrastructure to support SPI NANDs")
Signed-off-by: Han Xu <han.xu at nxp.com>
---
 drivers/mtd/nand/spi/core.c | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 35365b67dd8e..744662533f37 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -281,8 +281,26 @@ static int spinand_init_cfg_cache(struct spinand_device *spinand)
 static int spinand_init_quad_enable(struct spinand_device *spinand,
 				    bool enable)
 {
-	return spinand_upd_cfg(spinand, CFG_QUAD_ENABLE,
-			       enable ? CFG_QUAD_ENABLE : 0);
+	struct nand_device *nand = spinand_to_nand(spinand);
+	unsigned int target;
+	int ret;
+
+	/*
+	 * QE is a per-die setting on some devices. Program each target
+	 * individually when enabling or disabling quad I/O mode.
+	 */
+	for (target = 0; target < nand->memorg.ntargets; target++) {
+		ret = spinand_select_target(spinand, target);
+		if (ret)
+			return ret;
+
+		ret = spinand_upd_cfg(spinand, CFG_QUAD_ENABLE,
+				      enable ? CFG_QUAD_ENABLE : 0);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
 
 static int spinand_ecc_enable(struct spinand_device *spinand,

---
base-commit: 113f62225e98febabba0fee00afd29d4eaf65abb
change-id: 20260807-spinand_qe-dd6148042f63

Best regards,
--  
Han Xu <han.xu at nxp.com>



From chengzhihao1 at huawei.com  Fri Aug  7 18:50:18 2026
From: chengzhihao1 at huawei.com (Zhihao Cheng)
Date: Sat, 8 Aug 2026 09:50:18 +0800
Subject: [PATCH v1] mtd: ubi: Release device reference on busy detach
In-Reply-To: <20260807204550.1016755-1-dbgh9129@gmail.com>
References: <20260807204550.1016755-1-dbgh9129@gmail.com>
Message-ID: <27eaa6d4-cae1-2e44-6b46-a753c2475c50@huawei.com>

? 2026/8/8 4:45, Yuho Choi ??:
> ubi_detach_mtd_dev() obtains a device reference through ubi_get_device()
> before checking whether the UBI device is busy. The busy return path drops
> ubi->ref_count but leaves the device reference held, so the device object
> cannot be released after a later detach.
> 
> Drop the device reference before returning -EBUSY.
> 
> Fixes: 7e84c961b2eb ("mtd: ubi: introduce pre-removal notification for UBI volumes")
> Signed-off-by: Yuho Choi <dbgh9129 at gmail.com>
> ---
>   drivers/mtd/ubi/build.c | 1 +
>   1 file changed, 1 insertion(+)

I think you have sent this patch before?
https://lore.kernel.org/linux-mtd/26c9c959-a3b6-e1de-1215-887990a46870 at huawei.com/
> 
> diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c
> index 674ad87809df..ee6ce4471b25 100644
> --- a/drivers/mtd/ubi/build.c
> +++ b/drivers/mtd/ubi/build.c
> @@ -1105,6 +1105,7 @@ int ubi_detach_mtd_dev(int ubi_num, int anyway)
>   	ubi->ref_count -= 1;
>   	if (ubi->ref_count) {
>   		if (!anyway) {
> +			put_device(&ubi->dev);
>   			spin_unlock(&ubi_devices_lock);
>   			return -EBUSY;
>   		}
> 



From runyu.xiao at seu.edu.cn  Sun Aug  9 01:42:23 2026
From: runyu.xiao at seu.edu.cn (Runyu Xiao)
Date: Sun,  9 Aug 2026 16:42:23 +0800
Subject: [PATCH] mtd: spi-nor: scope the exclusive RWW lock
Message-ID: <20260809084223.3596259-1-runyu.xiao@seu.edu.cn>

spi_nor_rww_start_exclusive() is used as a wait_event_killable()
condition. The raw mutex_lock() leaves nor->lock held when the busy
condition returns false, so the waiter can block the active operation that
must clear the RWW state.

Use the same scoped mutex guard as the other RWW start helpers so the
mutex is released on both the busy and successful condition paths. The
state flags remain the handoff to the caller, while
spi_nor_rww_end_exclusive() continues
to acquire the mutex when clearing them.

The change was checked by comparing the original and patched source.

A source-level check of the original wait condition found that it takes
`nor->lock` and returns false while an RWW operation is still active. The
patched source was checked for a scoped mutex guard that releases
`nor->lock` before the wait condition returns. A user-space pthread model
held `nor->lock` on the false-condition path and showed that the
operation-ending path then blocks when it needs the same mutex. No live
SPI-NOR test was run.

Fixes: 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup helpers")
Cc: stable at vger.kernel.org
Signed-off-by: Runyu Xiao <runyu.xiao at seu.edu.cn>
---
 drivers/mtd/spi-nor/core.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ccf4396cdcd0..8bc117b46e02 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -1310,7 +1310,7 @@ static bool spi_nor_rww_start_exclusive(struct spi_nor *nor)
 {
 	struct spi_nor_rww *rww = &nor->rww;
 
-	mutex_lock(&nor->lock);
+	guard(mutex)(&nor->lock);
 
 	if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe)
 		return false;
-- 
2.34.1


From syzbot+9bbc6788187abbabb2ee at syzkaller.appspotmail.com  Sun Aug  9 17:25:31 2026
From: syzbot+9bbc6788187abbabb2ee at syzkaller.appspotmail.com (syzbot)
Date: Sun, 09 Aug 2026 17:25:31 -0700
Subject: [syzbot] [jffs2?] kernel BUG in jffs2_garbage_collect_pass
Message-ID: <6a791a7b.01d0871a.3a0d52.0098.GAE@google.com>

Hello,

syzbot found the following issue on:

HEAD commit:    562bfb501c54 Merge tag 'integrity-v7.2-rc7' of git://git.k..
git tree:       upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=13a35bb9580000
kernel config:  https://syzkaller.appspot.com/x/.config?x=cb17729fb04b1e3a
dashboard link: https://syzkaller.appspot.com/bug?extid=9bbc6788187abbabb2ee
compiler:       Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8

Unfortunately, I don't have any reproducer for this issue yet.

Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/dc0b8603d3a0/disk-562bfb50.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/08c750d8b185/vmlinux-562bfb50.xz
kernel image: https://storage.googleapis.com/syzbot-assets/6142deb5b95c/bzImage-562bfb50.xz

IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+9bbc6788187abbabb2ee at syzkaller.appspotmail.com

------------[ cut here ]------------
kernel BUG at fs/jffs2/gc.c:226!
Oops: invalid opcode: 0000 [#1] SMP KASAN PTI
CPU: 0 UID: 0 PID: 7620 Comm: jffs2_gcd_mtd0 Not tainted syzkaller #0 PREEMPT_{RT,(full)} 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/24/2026
RIP: 0010:jffs2_garbage_collect_pass+0x20e7/0x2170 fs/jffs2/gc.c:226
Code: 6b f7 fd 48 8b 7c 24 20 e8 06 6b 16 08 90 0f 0b e8 de de 95 fe bf 02 00 00 00 e8 34 44 64 fe e9 9d e7 ff ff e8 ca de 95 fe 90 <0f> 0b e8 c2 de 95 fe bf 02 00 00 00 e8 18 44 64 fe e9 7a f8 ff ff
RSP: 0018:ffffc900041b7aa0 EFLAGS: 00010293
RAX: ffffffff832ffa76 RBX: 0000000000000006 RCX: ffff888029738000
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
RBP: ffffc900041b7c70 R08: ffff888029738000 R09: 0000000000000006
R10: 0000000000000100 R11: 0000000000000005 R12: ffff8880357c6410
R13: ffff88805f5ef700 R14: 1ffff1100bebdee2 R15: ffff88805f5ef712
FS:  0000000000000000(0000) GS:ffff888125c3e000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f06a384fffc CR3: 0000000056094000 CR4: 00000000003526f0
Call Trace:
 <TASK>
 jffs2_garbage_collect_thread+0x634/0x6c0 fs/jffs2/background.c:155
 kthread+0x388/0x470 kernel/kthread.c:436
 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:jffs2_garbage_collect_pass+0x20e7/0x2170 fs/jffs2/gc.c:226
Code: 6b f7 fd 48 8b 7c 24 20 e8 06 6b 16 08 90 0f 0b e8 de de 95 fe bf 02 00 00 00 e8 34 44 64 fe e9 9d e7 ff ff e8 ca de 95 fe 90 <0f> 0b e8 c2 de 95 fe bf 02 00 00 00 e8 18 44 64 fe e9 7a f8 ff ff
RSP: 0018:ffffc900041b7aa0 EFLAGS: 00010293
RAX: ffffffff832ffa76 RBX: 0000000000000006 RCX: ffff888029738000
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
RBP: ffffc900041b7c70 R08: ffff888029738000 R09: 0000000000000006
R10: 0000000000000100 R11: 0000000000000005 R12: ffff8880357c6410
R13: ffff88805f5ef700 R14: 1ffff1100bebdee2 R15: ffff88805f5ef712
FS:  0000000000000000(0000) GS:ffff888125c3e000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f06a384fffc CR3: 0000000056094000 CR4: 00000000003526f0


---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller at googlegroups.com.

syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.

If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title

If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)

If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report

If you want to undo deduplication, reply with:
#syz undup


From syzbot+b320a4d5f65a61dbbf89 at syzkaller.appspotmail.com  Sun Aug  9 17:34:28 2026
From: syzbot+b320a4d5f65a61dbbf89 at syzkaller.appspotmail.com (syzbot)
Date: Sun, 09 Aug 2026 17:34:28 -0700
Subject: [syzbot] [mtd?] divide error in block2mtd_setup2
Message-ID: <6a791c94.01d0871a.3a0d52.009b.GAE@google.com>

Hello,

syzbot found the following issue on:

HEAD commit:    848acc8ffe1b Merge tag 'fsverity-for-linus' of git://git.k..
git tree:       upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=149f2cc6580000
kernel config:  https://syzkaller.appspot.com/x/.config?x=c05be6c9b0d36cb9
dashboard link: https://syzkaller.appspot.com/bug?extid=b320a4d5f65a61dbbf89
compiler:       Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8

Unfortunately, I don't have any reproducer for this issue yet.

Downloadable assets:
disk image (non-bootable): https://storage.googleapis.com/syzbot-assets/d900f083ada3/non_bootable_disk-848acc8f.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/2425c07b5d46/vmlinux-848acc8f.xz
kernel image: https://storage.googleapis.com/syzbot-assets/cf278dd14da3/bzImage-848acc8f.xz

IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+b320a4d5f65a61dbbf89 at syzkaller.appspotmail.com

Oops: divide error: 0000 [#1] SMP KASAN NOPTI
CPU: 0 UID: 0 PID: 5325 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
RIP: 0010:add_device drivers/mtd/devices/block2mtd.c:296 [inline]
RIP: 0010:block2mtd_setup2+0x592/0xda0 drivers/mtd/devices/block2mtd.c:459
Code: 48 8b 74 24 30 48 63 ce 48 89 d8 48 09 c8 48 c1 e8 20 4c 8b 64 24 10 74 0d 48 89 d8 48 99 48 f7 f9 49 89 d5 eb 09 89 d8 31 d2 <f7> f6 41 89 d5 31 ff 4c 89 ee e8 1f da 39 fb 4d 85 ed 0f 85 90 04
RSP: 0018:ffffc9000f26f9a0 EFLAGS: 00010246
RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000000
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000001f00000
RBP: ffffc9000f26fb98 R08: ffff8880353962ef R09: 1ffff11006a72c5d
R10: dffffc0000000000 R11: ffffed1006a72c5e R12: 0000000000000000
R13: ffff88801d02d0b4 R14: ffff8880122be000 R15: ffff8880122be000
FS:  00007fa7d8c5d6c0(0000) GS:ffff88808c540000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fa7d801bb30 CR3: 000000003fb3c000 CR4: 0000000000352ef0
Call Trace:
 <TASK>
 block2mtd_setup+0x27/0xe0 drivers/mtd/devices/block2mtd.c:476
 param_attr_store+0x214/0x310 kernel/params.c:589
 module_attr_store+0x65/0x90 kernel/params.c:904
 kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345
 new_sync_write fs/read_write.c:595 [inline]
 vfs_write+0x612/0xba0 fs/read_write.c:687
 ksys_write+0x150/0x270 fs/read_write.c:739
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fa7d7d9e019
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fa7d8c5cfe8 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007fa7d8025fa0 RCX: 00007fa7d7d9e019
RDX: 000000000000000c RSI: 00002000000000c0 RDI: 0000000000000003
RBP: 00007fa7d7e3500c R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fa7d8026038 R14: 00007fa7d8025fa0 R15: 00007ffe5587a628
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:add_device drivers/mtd/devices/block2mtd.c:296 [inline]
RIP: 0010:block2mtd_setup2+0x592/0xda0 drivers/mtd/devices/block2mtd.c:459
Code: 48 8b 74 24 30 48 63 ce 48 89 d8 48 09 c8 48 c1 e8 20 4c 8b 64 24 10 74 0d 48 89 d8 48 99 48 f7 f9 49 89 d5 eb 09 89 d8 31 d2 <f7> f6 41 89 d5 31 ff 4c 89 ee e8 1f da 39 fb 4d 85 ed 0f 85 90 04
RSP: 0018:ffffc9000f26f9a0 EFLAGS: 00010246
RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000000
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000001f00000
RBP: ffffc9000f26fb98 R08: ffff8880353962ef R09: 1ffff11006a72c5d
R10: dffffc0000000000 R11: ffffed1006a72c5e R12: 0000000000000000
R13: ffff88801d02d0b4 R14: ffff8880122be000 R15: ffff8880122be000
FS:  00007fa7d8c5d6c0(0000) GS:ffff88808c540000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007ffee237bf52 CR3: 000000003fb3c000 CR4: 0000000000352ef0
----------------
Code disassembly (best guess):
   0:	48 8b 74 24 30       	mov    0x30(%rsp),%rsi
   5:	48 63 ce             	movslq %esi,%rcx
   8:	48 89 d8             	mov    %rbx,%rax
   b:	48 09 c8             	or     %rcx,%rax
   e:	48 c1 e8 20          	shr    $0x20,%rax
  12:	4c 8b 64 24 10       	mov    0x10(%rsp),%r12
  17:	74 0d                	je     0x26
  19:	48 89 d8             	mov    %rbx,%rax
  1c:	48 99                	cqto
  1e:	48 f7 f9             	idiv   %rcx
  21:	49 89 d5             	mov    %rdx,%r13
  24:	eb 09                	jmp    0x2f
  26:	89 d8                	mov    %ebx,%eax
  28:	31 d2                	xor    %edx,%edx
* 2a:	f7 f6                	div    %esi <-- trapping instruction
  2c:	41 89 d5             	mov    %edx,%r13d
  2f:	31 ff                	xor    %edi,%edi
  31:	4c 89 ee             	mov    %r13,%rsi
  34:	e8 1f da 39 fb       	call   0xfb39da58
  39:	4d 85 ed             	test   %r13,%r13
  3c:	0f                   	.byte 0xf
  3d:	85                   	.byte 0x85
  3e:	90                   	nop
  3f:	04                   	.byte 0x4


---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller at googlegroups.com.

syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.

If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title

If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)

If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report

If you want to undo deduplication, reply with:
#syz undup


From joern at barelysecure.org  Sun Aug  9 19:03:22 2026
From: joern at barelysecure.org (=?iso-8859-1?Q?J=F6rn?= Engel)
Date: Sun, 9 Aug 2026 19:03:22 -0700
Subject: [syzbot] [mtd?] divide error in block2mtd_setup2
In-Reply-To: <6a791c94.01d0871a.3a0d52.009b.GAE@google.com>
References: <6a791c94.01d0871a.3a0d52.009b.GAE@google.com>
Message-ID: <ankxaiwtTltL0xMj@cashel.logfs.org>

On Sun, Aug 09, 2026 at 05:34:28PM -0700, syzbot wrote:
> 
> * 2a:	f7 f6                	div    %esi <-- trapping instruction

Best guess would be this:

	if ((long)size % erase_size) {

Someone passing an erase_size of 0 would indeed trigger an exception.
Fix would be something like this:

-	if ((long)size % erase_size) {
+	if (!erase_size || (long)size % erase_size) {

I haven't touched the code is nearly two decades.  If somebody else
feels like sending a patch and gathering all the glory, please be my
guest!

J?rn

--
Those who come seeking peace without a treaty are plotting.
-- Sun Tzu


From mwalle at kernel.org  Sun Aug  9 23:30:11 2026
From: mwalle at kernel.org (Michael Walle)
Date: Mon, 10 Aug 2026 08:30:11 +0200
Subject: [PATCH] mtd: spi-nor: allow force unlocking via DT property
In-Reply-To: <20260806112418.14695-1-ptpt52@gmail.com>
References: <2026-08-05171214.2934-1-ptpt52@gmail.com>
 <20260806112418.14695-1-ptpt52@gmail.com>
Message-ID: <DKL1W2D3OJ5J.2VW3B1VPH5LHF@kernel.org>

On Thu Aug 6, 2026 at 1:24 PM CEST, Chen Minqiang wrote:
> Hi,
>
> Thank you for your valuable insight!
>
> You are completely right that for unlisted/generic chips that feature a 4-bit
> BP layout (BP3 at bit 5 or bit 6) or a CMP (Complement Protect) bit in SR2,
> spi_nor_unlock() won't clear those extra bits without the corresponding flags
> (SNOR_F_HAS_4BIT_BP / SNOR_F_HAS_SR2_CMP_BIT6) set by the ID database or SFDP.


Is this AI assisted?

> However, in practice:
> 1. The vast majority of 3.3V/1.8V generic SPI NOR flashes (e.g. 4MB-16MB chips
>    commonly found in vendor devices like Tenda AX12L Pro) use the standard
>    3-bit BP (BP0-BP2, SR1 bits 2..4).
> 2. The current main issue is that even for these standard 3-bit BP chips, the
>    kernel currently skips spi_nor_try_unlock_all() completely at boot time if
>    CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE is set (for non-volatile chips)
>    or if SNOR_F_HAS_LOCK is not set in chip flags. As a result, status registers
>    locked by factory bootloaders are never cleared.
>
> `linux,force-sr-unlock` serves as a pragmatic DT override to force the unlock
> attempt at probe time.
>
> To address your point regarding 4-bit BP and CMP bits for unlisted chips, we
> have two potential options:
>
> Option A (Current Best-Effort):
> Keep the patch as-is, treating `linux,force-sr-unlock` as a best-effort DT
> trigger to invoke standard spi_nor_unlock(). It successfully unlocks the vast
> majority of standard 3-bit BP generic chips. For rare unlisted chips with 4-bit
> BP or CMP bits, explicit entries can still be added to the ID database when
> discovered.
>
> Option B (Aggressive Force-Clear):
> When `linux,force-sr-unlock` is present in DT, enhance spi_nor_try_unlock_all()
> to perform a broader clear operation on SR1 (masking bits 2..6 to clear BP0-BP3/TB)
> and SR2 (clearing CMP bit if SR2 is readable).
>
> Which approach would you prefer? I'd be happy to revise the patch based on your
> guidance.

This patch won't fly as is. You're missing the dt-bindings patch
(which I doubt will be accepted) and I really don't like this
approach. First, why would you unlock a flash automatically? I've
worked hard, to get rid of that anti-feature. Second, what if you
still loose the flash "lottery"?

-michael
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260810/64a7ce84/attachment-0001.sig>

From mwalle at kernel.org  Sun Aug  9 23:50:13 2026
From: mwalle at kernel.org (Michael Walle)
Date: Mon, 10 Aug 2026 08:50:13 +0200
Subject: [PATCH 1/5] mtd: spi-nor: Refactor Read Status/Write Status
 support
In-Reply-To: <87y0ekek8r.fsf@bootlin.com>
References: <20260529-winbond-v7-1-spi-nor-rv-addition-v1-0-f3ae18502d5a@bootlin.com> <20260529-winbond-v7-1-spi-nor-rv-addition-v1-1-f3ae18502d5a@bootlin.com> <DJS8PQ5HZZG5.2YT956NCDX8MG@kernel.org> <87mrv2exwp.fsf@bootlin.com> <87y0ekek8r.fsf@bootlin.com>
Message-ID: <DKL2BE8MZUQJ.7GKQF4IIQEJN@kernel.org>

Hi,

On Wed Aug 5, 2026 at 4:04 PM CEST, Miquel Raynal wrote:
> Hello Michael,
>
>>>> +int spi_nor_read_srs(struct spi_nor *nor, u8 *sr1, u8 *sr2)
>>>
>>> nitpick, i think this is a weird name. But maybe it's just me.
>>>
>>> What if we'd just make the one status register as u32? I mean in the
>>> winbond datasheet it's called S7-S0 and S15-S8. That way we also
>>> don't need a two byte qe_mask. To optimize the standard usecase to
>>> poll the WIP bit, we could add a byte mask.
>
> I experimented a bit the u32 status register, I don't think it is a good
> idea.
>
> My target of improving the readability is completely defeated by the
> fact that:
> - all the callers need to add extra maths to explain what SR
>   they want

Actually, what I had i mind is that there is no more SR2 and so on.
But just one SR (and the BIT macros will change of course). Some
datasheets are already naming the bits B0 to B15.

> - endianness shall be handled, people always get it wrong (including
>   me), so why bother if an array just makes the whole thing simpler?

Where would you need endianess? If at all, just in the core, which
will split the u32 into two u8.

 sr1 = sr & 0xff;
 sr2 = (sr >> 8) & 0xff;

> - 16-bit accesses get more convoluted, we need extra
>   get_/put_unaligned_le32() calls and pack/unpack bytes in the hot path

There is not really a hot path, is it. Not in a sense that it
matters for performance though.

> - 8-bit accesses imply an extensive use of FIELD_GET(GENMASK(),) macros,
>   which make the whole fonction totally non obvious anymore.
>
> Whereas, a simple:
>
>         if (sr2)
>                 *sr2 = foo;
>
> is self explanatory. I also do not really get the wish for a QE mask
> instead of a two bytes array.

Because IMHO an array of two u8 (which your qe mask is) is never
self explanatory. While with

#define SR1_QUAD_EN_BIT6 BIT(6)
#define SR2_QUAD_EN_BIT1 BIT(9)
#define SR2_QUAD_EN_BIT7 BIT(15)

you can just use the macros as before.

-michael

> So I will rename the "srs" naming that you dislike, I will group the
> opcodes in a big structure, change the baseline default to match your
> suggestion and follow-up with the other minor comments, but I believe
> I'm going to avoid the u32 switch.
>
> Thanks,
> Miqu?l

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260810/249bdc8a/attachment.sig>

From mwalle at kernel.org  Mon Aug 10 00:18:07 2026
From: mwalle at kernel.org (Michael Walle)
Date: Mon, 10 Aug 2026 09:18:07 +0200
Subject: [PATCH 1/5] mtd: spi-nor: Refactor Read Status/Write Status
 support
In-Reply-To: <87se4rem0l.fsf@bootlin.com>
References: <20260529-winbond-v7-1-spi-nor-rv-addition-v1-0-f3ae18502d5a@bootlin.com> <20260529-winbond-v7-1-spi-nor-rv-addition-v1-1-f3ae18502d5a@bootlin.com> <DJS8PQ5HZZG5.2YT956NCDX8MG@kernel.org> <87mrv2exwp.fsf@bootlin.com> <87se4rem0l.fsf@bootlin.com>
Message-ID: <DKL2WROHNLXJ.3239EBUTVXRQL@kernel.org>

On Thu Aug 6, 2026 at 9:38 AM CEST, Miquel Raynal wrote:
> Hello Michael,
>
>>> One thing that comes to mind is hardware write protection. If
>>> there's nothing before that code which checks it, the verify might
>>> fail if the hardware write protection is enabled. So we should
>>> somehow check for that and drop the verify here.
>>
>> But how do you think we should handle it? Will the QE bit writing
>> verification fail if HW WP is enabled?

It should, because you cannot alter the SR.

> Looking into this further: we shall return an error if the QE bit is not
> set. It just tells the caller that quad mode cannot be used. Then up to
> the caller to either hard fail or just degrade into single mode (maybe
> because of a strapped WP). What we should do is to propose a DT property
> to flag when WP is strapped in hardware, this would make the content of
> the status registers immutable and we would just skip the entire write
> operation in the first place, instead of deliberately trying and get a
> 100% failure rate. Nevertheless, the changes introduced here are kind of
> orthogonal and do not alter the current behaviour; we shall however
> listen if people start complaining about this and perhaps implement the
> solution proposed above.

There is already a property in the DT, where you almost certainly
know that quad mode is impossible. Iff there are four rx/tx lines
(spi-{rx,tx}-bus-width). I haven't seen a flash (even in larger
packages) where there is a dedicated WP# pin.

What do you mean by a dt property? If (1) the flash is using the WP#
pin? Or (2) if it is using the WP# pin and it's tied to low?

For (1) you have the QE bit, if it's set, WP is turned off and vice
versa.

For (2), that doesn't make sense, because that pin could be tied
to some kind of logic which can change during runtime.

FWIW, I don't care too much, you've asked why there is a
verification step, I've just answered :)

-michael
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260810/338d9d5f/attachment.sig>

From mwalle at kernel.org  Mon Aug 10 00:22:45 2026
From: mwalle at kernel.org (Michael Walle)
Date: Mon, 10 Aug 2026 09:22:45 +0200
Subject: [PATCH 03/30] mtd: spi-nor: winbond: Stop filling the .name
 entry
In-Reply-To: <87mruzeg1p.fsf@bootlin.com>
References: <20260529-winbond-v7-1-spi-nor-jv-cleanup-v1-0-87e5d3122244@bootlin.com> <20260529-winbond-v7-1-spi-nor-jv-cleanup-v1-3-87e5d3122244@bootlin.com> <DJRJJB4NEUEP.168MF88DBSFDF@kernel.org> <87mruzeg1p.fsf@bootlin.com>
Message-ID: <DKL30B77Z7QJ.IXPI630PUWTV@kernel.org>

On Thu Aug 6, 2026 at 11:47 AM CEST, Miquel Raynal wrote:
> Hello,
>
>>> Replace the names with a comment above the entry with the newly instated
>>> naming scheme to indicate what chips are covered by each entry.
>>
>> This is exported via sysfs, so this could be a regression.
>
> Well, actually names are not exported through sysfs. Only the mtd name
> is available through sysfs, and it is set to `spiX.Y`. That name is only
> available through debugfs, which is not part of the stable API.
>
> I should have checked before dropping all my patches...

That's not correct, see

https://elixir.bootlin.com/linux/v7.1.7/source/drivers/mtd/spi-nor/sysfs.c#L21

-michael
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260810/4ec91095/attachment.sig>

From miquel.raynal at bootlin.com  Mon Aug 10 00:58:20 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Mon, 10 Aug 2026 09:58:20 +0200
Subject: [PATCH] mtd: spinand: Enable QE on all dies
In-Reply-To: <20260807-spinand_qe-v1-1-c102c79834a3@nxp.com> (han xu's message
	of "Fri, 07 Aug 2026 18:11:27 -0500")
References: <20260807-spinand_qe-v1-1-c102c79834a3@nxp.com>
Message-ID: <87qzk6s8yr.fsf@bootlin.com>

Hello Han,

On 07/08/2026 at 18:11:27 -05, han.xu at oss.nxp.com wrote:

> From: Han Xu <han.xu at nxp.com>
>
> The QUAD ENABLE (QE) bit is stored in a per-die configuration
> register on some SPI-NAND devices. When a device contains multiple
> dies, updating the QE bit only on the currently selected die can
> leave the remaining dies operating in non-quad mode.
> ?
> Iterate over all targets and update the QE setting on each die
> during initialization to ensure consistent quad I/O operation
> across the entire device.
>
> Tested on ISSI IS38SMW04G8B.
>
> Fixes: 7529df465248 ("mtd: nand: Add core infrastructure to support
> SPI NANDs")

There was no ISSI device back then, but I guess this can be useful for
other devices. However this shall be backported, so Cc: stable.

> Signed-off-by: Han Xu <han.xu at nxp.com>
> ---
>  drivers/mtd/nand/spi/core.c | 22 ++++++++++++++++++++--
>  1 file changed, 20 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
> index 35365b67dd8e..744662533f37 100644
> --- a/drivers/mtd/nand/spi/core.c
> +++ b/drivers/mtd/nand/spi/core.c
> @@ -281,8 +281,26 @@ static int spinand_init_cfg_cache(struct spinand_device *spinand)
>  static int spinand_init_quad_enable(struct spinand_device *spinand,
>  				    bool enable)
>  {
> -	return spinand_upd_cfg(spinand, CFG_QUAD_ENABLE,
> -			       enable ? CFG_QUAD_ENABLE : 0);
> +	struct nand_device *nand = spinand_to_nand(spinand);
> +	unsigned int target;
> +	int ret;
> +
> +	/*
> +	 * QE is a per-die setting on some devices. Program each target
> +	 * individually when enabling or disabling quad I/O mode.
> +	 */
> +	for (target = 0; target < nand->memorg.ntargets; target++) {
> +		ret = spinand_select_target(spinand, target);
> +		if (ret)
> +			return ret;
> +
> +		ret = spinand_upd_cfg(spinand, CFG_QUAD_ENABLE,
> +				      enable ? CFG_QUAD_ENABLE : 0);
> +		if (ret)
> +			return ret;

If there is an issue in the middle of the loop, the chip will become
unusable. Perhaps we should reset the dies to the !QE state and return
an error to continue in degraded (single) mode if that happens?

Thanks,
Miqu?l


From mwalle at kernel.org  Mon Aug 10 01:25:28 2026
From: mwalle at kernel.org (Michael Walle)
Date: Mon, 10 Aug 2026 10:25:28 +0200
Subject: [PATCH] mtd: spi-nor: scope the exclusive RWW lock
In-Reply-To: <20260809084223.3596259-1-runyu.xiao@seu.edu.cn>
References: <20260809084223.3596259-1-runyu.xiao@seu.edu.cn>
Message-ID: <DKL4CBQNKCO2.3NAZ6S25Q60XN@kernel.org>

On Sun Aug 9, 2026 at 10:42 AM CEST, Runyu Xiao wrote:
> spi_nor_rww_start_exclusive() is used as a wait_event_killable()
> condition. The raw mutex_lock() leaves nor->lock held when the busy
> condition returns false, so the waiter can block the active operation that
> must clear the RWW state.
>
> Use the same scoped mutex guard as the other RWW start helpers so the
> mutex is released on both the busy and successful condition paths. The
> state flags remain the handoff to the caller, while
> spi_nor_rww_end_exclusive() continues
> to acquire the mutex when clearing them.
>
> The change was checked by comparing the original and patched source.

What do you mean? Was this AI assisted?

> A source-level check of the original wait condition found that it takes
> `nor->lock` and returns false while an RWW operation is still active. The
> patched source was checked for a scoped mutex guard that releases
> `nor->lock` before the wait condition returns. A user-space pthread model
> held `nor->lock` on the false-condition path and showed that the
> operation-ending path then blocks when it needs the same mutex. No live
> SPI-NOR test was run.

What do you mean by "user-space pthread model"? Ported this into
user-space and tried it there? Please keep the commit message
precise and don't use any AI gibberish. Please, explain what's going
on with your own sentences. That way, it shows at least some
understanding what you are trying to change here.

That being said, there is something odd about that commit. Tudor, do
you know why you've omitted the guard() in spi_nor_rww_start_exclusive()?

-michael

> Fixes: 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup helpers")
> Cc: stable at vger.kernel.org
> Signed-off-by: Runyu Xiao <runyu.xiao at seu.edu.cn>
> ---
>  drivers/mtd/spi-nor/core.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index ccf4396cdcd0..8bc117b46e02 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
> @@ -1310,7 +1310,7 @@ static bool spi_nor_rww_start_exclusive(struct spi_nor *nor)
>  {
>  	struct spi_nor_rww *rww = &nor->rww;
>  
> -	mutex_lock(&nor->lock);
> +	guard(mutex)(&nor->lock);
>  
>  	if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe)
>  		return false;

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260810/07501103/attachment.sig>

From dominique.martinet at atmark-techno.com  Mon Aug 10 01:28:32 2026
From: dominique.martinet at atmark-techno.com (Dominique MARTINET)
Date: Mon, 10 Aug 2026 17:28:32 +0900
Subject: [PATCH 1/2] mtd: spinand: winbond: add support for W25N04LW
In-Reply-To: <20260807064339.1632249-2-md.alam@oss.qualcomm.com>
References: <20260807064339.1632249-1-md.alam@oss.qualcomm.com>
 <20260807064339.1632249-2-md.alam@oss.qualcomm.com>
Message-ID: <anmLsPcXRk0VAQSW@atmark-techno.com>

Md Sadre Alam wrote on Fri, Aug 07, 2026 at 12:13:38PM +0530:
> Add support for Winbond W25N04LW 4Gbit SPI-NAND.
> It has 8-bit on-die ECC and a 4096+256 byte page
> (4096 byte main area, 128 byte usable OOB once
> on-chip ECC hides the 128 byte parity area),
> organized as 2048 blocks of 64 pages each, unlike
> the existing 2048 byte page W25N04KV/W25N04KW parts.

This is pretty good timing, I was just looking at sending a patch for
W25N04LW myself!

I agree with Miqu?l that your patch looks odd, you wrote
> +		     NAND_MEMORG(1, 4096, 128, 64, 2048, 40, 1, 1, 1),
but the erase blocks are 256K long so it should be
+		     NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),

did you actually test this device?

If it helps, the datasheet for W25N04LWZExx / W25N04LWTBxx is freely
available from digikey here[1]
[1] https://mm.digikey.com/Volume0/opasdata/d220001/medias/docus/7182/W25N04LWZExx_W25N04LWTBxx_RevD_2025-08-05.pdf

(And it also supports continuous read, so I'm curious to see if it'd
work with the recent continous read support from Miqu?l[2] merged in
7.2... I'm not familiar with the subsystem so will need a bit of time to
plug all the hooks in properly)
[2] https://lore.kernel.org/linux-mtd/20260429-winbond-v6-18-rc1-cont-read-v3-0-0f38b3c229ad at bootlin.com/


FWIW, I've confirmed it works with the following diff (at least up to
the point of creating an ubi volume and booting from it); happy to send
this as a patch if this has indeed not been tested
------------
diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
index 9b78c1e6cbc9..43a4886dc4a5 100644
--- a/drivers/mtd/nand/spi/winbond.c
+++ b/drivers/mtd/nand/spi/winbond.c
@@ -335,6 +335,30 @@ static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section,
 	return 0;
 }
 
+static int w25n04lw_ooblayout_ecc(struct mtd_info *mtd, int section,
+				  struct mtd_oob_region *region)
+{
+	if (section > 7)
+		return -ERANGE;
+
+	region->offset = 128 + (16 * section);
+	region->length = 13;
+
+	return 0;
+}
+
+static int w25n04lw_ooblayout_free(struct mtd_info *mtd, int section,
+				   struct mtd_oob_region *region)
+{
+	if (section > 7)
+		return -ERANGE;
+
+	region->offset = (16 * section) + 2;
+	region->length = 14;
+
+	return 0;
+}
+
 static const struct mtd_ooblayout_ops w25n01kv_ooblayout = {
 	.ecc = w25n01kv_ooblayout_ecc,
 	.free = w25n02kv_ooblayout_free,
@@ -345,6 +369,11 @@ static const struct mtd_ooblayout_ops w25n02kv_ooblayout = {
 	.free = w25n02kv_ooblayout_free,
 };
 
+static const struct mtd_ooblayout_ops w25n04lw_ooblayout = {
+	.ecc = w25n04lw_ooblayout_ecc,
+	.free = w25n04lw_ooblayout_free,
+};
+
 static int w25n01jw_ooblayout_ecc(struct mtd_info *mtd, int section,
 				  struct mtd_oob_region *region)
 {
@@ -768,6 +797,15 @@ static const struct spinand_info winbond_spinand_table[] = {
 					      &update_cache_variants),
 		     0,
 		     SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
+	SPINAND_INFO("W25N04LW", /* 1.8V */
+		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb2, 0x23),
+		     NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
+		     NAND_ECCREQ(8, 512),
+		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+					      &write_cache_variants,
+					      &update_cache_variants),
+		     0,
+		     SPINAND_ECCINFO(&w25n04lw_ooblayout, w25n02kv_ecc_get_status)),
 	SPINAND_INFO("W35N04JW", /* 1.8V */
 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x23),
 		     NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 4, 1),
-----------------

Thanks,
-- 
Dominique Martinet | Asmadeus


From xiaopei01 at kylinos.cn  Mon Aug 10 02:12:29 2026
From: xiaopei01 at kylinos.cn (Pei Xiao)
Date: Mon, 10 Aug 2026 17:12:29 +0800
Subject: [PATCH] mtd: block2mtd: Fix divide error when erase_size is zero
In-Reply-To: <6a791c94.01d0871a.3a0d52.009b.GAE@google.com>
References: <6a791c94.01d0871a.3a0d52.009b.GAE@google.com>
Message-ID: <c0148dd9482275f3bab75b7263408fca89ef311b.1786353116.git.xiaopei01@kylinos.cn>

The erase size is parsed from the "block2mtd" module parameter and can
be set to zero. add_device() then evaluates

	if ((long)size % erase_size)

with a zero divisor, which triggers a divide error:

	divide error: 0000 [#1] PREEMPT SMP PTI
	RIP: 0010:add_device drivers/mtd/devices/block2mtd.c:296 [inline]
	RIP: 0010:block2mtd_setup2+0x592/0xda0 drivers/mtd/devices/block2mtd.c:459
	Call Trace:
	 block2mtd_setup+0x27/0xe0 drivers/mtd/devices/block2mtd.c:476
	 param_attr_store+0x214/0x310 kernel/params.c:589
	 module_attr_store+0x65/0x90 kernel/params.c:904
	 kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345
	 ...

Reject a zero erase size before performing the modulo operation so the
existing "erasesize must be a divisor of device size" error path
reports the invalid argument and frees the device.

Fixes: ea6d833a3fdd ("mtd: block2mtd: check device size")
Reported-by: syzbot+b320a4d5f65a61dbbf89 at syzkaller.appspotmail.com
Closes: https://lore.kernel.org/all/6a791c94.01d0871a.3a0d52.009b.GAE at google.com/T/#m5d4961a95b273da06457d603ddcc4170bf82a9fd
Cc: stable at vger.kernel.org
Signed-off-by: Pei Xiao <xiaopei01 at kylinos.cn>
---
 drivers/mtd/devices/block2mtd.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c
index 03e80b2c4f5a..349fa07be314 100644
--- a/drivers/mtd/devices/block2mtd.c
+++ b/drivers/mtd/devices/block2mtd.c
@@ -293,7 +293,7 @@ static struct block2mtd_dev *add_device(char *devname, int erase_size,
 	}
 
 	size = bdev_nr_bytes(bdev);
-	if ((long)size % erase_size) {
+	if (!erase_size || (long)size % erase_size) {
 		pr_err("erasesize must be a divisor of device size\n");
 		goto err_free_block2mtd;
 	}
-- 
2.25.1



From xiaopei01 at kylinos.cn  Mon Aug 10 02:17:50 2026
From: xiaopei01 at kylinos.cn (Pei Xiao)
Date: Mon, 10 Aug 2026 17:17:50 +0800
Subject: [syzbot] [mtd?] divide error in block2mtd_setup2
In-Reply-To: <ankxaiwtTltL0xMj@cashel.logfs.org>
References: <6a791c94.01d0871a.3a0d52.009b.GAE@google.com>
 <ankxaiwtTltL0xMj@cashel.logfs.org>
Message-ID: <29397db9-7db4-4969-bb0e-5bb71f0c0303@kylinos.cn>



? 2026/8/10 10:03, J?rn Engel ??:
> On Sun, Aug 09, 2026 at 05:34:28PM -0700, syzbot wrote:
>>
>> * 2a:	f7 f6                	div    %esi <-- trapping instruction
> 
> Best guess would be this:
> 
> 	if ((long)size % erase_size) {
> 
> Someone passing an erase_size of 0 would indeed trigger an exception.
> Fix would be something like this:
> 
> -	if ((long)size % erase_size) {
> +	if (!erase_size || (long)size % erase_size) {
> 
> I haven't touched the code is nearly two decades.  If somebody else
> feels like sending a patch and gathering all the glory, please be my
> guest!
Thank you for your contribution. I have sent this patch, but I forgot to
add the Suggested-by: tag

Thanks?
Pei.
> 
> J?rn
> 
> --
> Those who come seeking peace without a treaty are plotting.
> -- Sun Tzu
> 
> ______________________________________________________
> Linux MTD discussion mailing list
> http://lists.infradead.org/mailman/listinfo/linux-mtd/
> 
> From mboxrd at z Thu Jan  1 00:00:00 1970
> Received: from mx01.bremer-it.com (mx01.bremer-it.com [85.215.132.167])
> 	(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))
> 	(No client certificate requested)
> 	by smtp.subspace.kernel.org (Postfix) with ESMTPS id 73877212550
> 	for <linux-kernel at vger.kernel.org>; Mon, 10 Aug 2026 02:11:08 +0000 (UTC)
> Authentication-Results: smtp.subspace.kernel.org; arc=ne smtp.client-ip?.215.132.167
> ARC-Seal:i= a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116;
> 	t86327871; cv=none; b=pmhQ+YkskPdHVocSl0Q3ONibHDLR7s7fI85cff5H13l/yM9Z8mDYTBscKANq7mGFD2ASRqvBgqTpqe5XwuxKTwZFODwuxMgU6JESnW9mL+RH7dPhx/4yRbt05Zn2YwFK2lesfp+iHyordavupm+PNHamOQyJJwTUw9tjZZ0ntHAARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org;
> 	s=c-20240116; t86327871; c=relaxed/simple;
> 	bh=oRdL2tBTFal/JDe029vqeCkmNFJ6Y4/KQ2bpPcX7k=;
> 	h?te:From:To:Cc:Subject:Message-ID:References:MIME-Version:
> 	 Content-Type:Content-Disposition:In-Reply-To; b=XeZFkoGl/EX953bQRJEM5Tfe5d/GrpVVJUzcDSc66bRWr8I0onQDDy/7EWgIvCiEGhJzItU2sfKSjm4pNpXCnZj6NtzWG6zriQzO7WsKIZ2npUEbzkr9fxYKPgNq7NiBQehPlmVw8mZbzVsaVdvKHi5zwE5zILA9+gU9cPWxcARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from?relysecure.org; spf=pass smtp.mailfrom?relysecure.org; dkim=pass (2048-bit key) header.d?relysecure.org header.i=@barelysecure.org header.b=HAaxkQ/6; arc=none smtp.client-ip?.215.132.167
> Authentication-Results: smtp.subspace.kernel.org; dmarc=ss (p=none dis=none) header.from?relysecure.org
> Authentication-Results: smtp.subspace.kernel.org; spf=ss smtp.mailfrom?relysecure.org
> Authentication-Results: smtp.subspace.kernel.org;
> 	dkim=ss (2048-bit key) header.d?relysecure.org header.i=@barelysecure.org header.b="HAaxkQ/6"
> Received: from cashel.logfs.org (c-98-33-96-243.hsd1.ca.comcast.net [98.33.96.243])
> 	(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
> 	 key-exchange ECDHE (prime256v1) server-signature ECDSA (secp384r1) server-digest SHA384)
> 	(No client certificate requested)
> 	by mx01.bremer-it.com (Postfix) with ESMTPSA id 061CA20B86;
> 	Mon, 10 Aug 2026 04:03:25 +0200 (CEST)
> DKIM-Signature: v= a=rsa-sha256; c=relaxed/relaxed; d?relysecure.org;
> 	s=im68; t86327408;
> 	h=om:from:reply-to:subject:subject:date:date:message-id:message-id:
> 	 to:to:cc:cc:mime-version:mime-version:content-type:content-type:
> 	 content-transfer-encoding:content-transfer-encoding:
> 	 in-reply-to:in-reply-to:references:references;
> 	bh?IDTuliDDGgOAKpI245cMmyPRVVKxIWKH3GuXafqZA=;
> 	b=HAaxkQ/6AawUqio6ZtZxsW0LqcdaN10B/vRSFk0TcWRBU45jxR+oipsFDUbLTtUrltNgoq
> 	SUJTT9FHYYItqxPYvDw94jAGkuvJipWw3nalbM+Kl5az2K2VReh3w5eQsKr6sdoXp2TYzJ
> 	R/1QaEGzKjPtOIK5qoT0bvc6jV/0DL6pI9DWuc6O6DxxGrPYGtwZo2mUmQAuO9Qnm4Snbc
> 	xSJC10kazHhQLi5gnGRmMR3oz+JF66HhtQ+ApzYgkv3zty8o4P/Vk4Bj7YXV9UTiPwdcJq
> 	HVIfjaeb2e+Z+Q1K56IpiPILzHBRAYt0tcFSf+P7vSEEBvoUD2BFobn0ZIvA2Q=Authentication-Results: ORIGINATING;
> 	auth=ss smtp.auth=joern at lazybastard.org smtp.mailfrom=joern at barelysecure.org
> Date: Sun, 9 Aug 2026 19:03:22 -0700
> From: =so-8859-1?Q?J?rn?= Engel <joern at barelysecure.org>
> To: syzbot <syzbot+b320a4d5f65a61dbbf89 at syzkaller.appspotmail.com>
> Cc: joern at lazybastard.org, linux-kernel at vger.kernel.org,
> 	linux-mtd at lists.infradead.org, miquel.raynal at bootlin.com,
> 	richard at nod.at, syzkaller-bugs at googlegroups.com, vigneshr at ti.com
> Subject: Re: [syzbot] [mtd?] divide error in block2mtd_setup2
> Message-ID: <ankxaiwtTltL0xMj at cashel.logfs.org>
> References: <6a791c94.01d0871a.3a0d52.009b.GAE at google.com>
> Precedence: bulk
> X-Mailing-List: linux-kernel at vger.kernel.org
> List-Id: <linux-kernel.vger.kernel.org>
> List-Subscribe: <mailto:linux-kernel+subscribe at vger.kernel.org>
> List-Unsubscribe: <mailto:linux-kernel+unsubscribe at vger.kernel.org>
> MIME-Version: 1.0
> Content-Type: text/plain; charset=o-8859-1
> Content-Disposition: inline
> Content-Transfer-Encoding: 8bit
> In-Reply-To: <6a791c94.01d0871a.3a0d52.009b.GAE at google.com>
> X-Spam-Level: *
> X-Rspamd-Action: no action
> X-Spamd-Result: default: False [1.40 / 14.00];
> 	SUSPICIOUS_RECIPS(1.50)[];
> 	MIME_GOOD(-0.10)[text/plain];
> 	ARC_NA(0.00)[];
> 	TAGGED_RCPT(0.00)[b320a4d5f65a61dbbf89];
> 	RCVD_COUNT_ZERO(0.00)[0];
> 	ASN(0.00)[asn:7922, ipnet:98.32.0.0/11, country:US];
> 	MIME_TRACE(0.00)[0:+];
> 	MISSING_XM_UA(0.00)[];
> 	RCPT_COUNT_SEVEN(0.00)[8];
> 	FROM_HAS_DN(0.00)[];
> 	ALIAS_RESOLVED(0.00)[];
> 	LOCAL_OUTBOUND(0.00)[];
> 	FROM_EQ_ENVFROM(0.00)[];
> 	TO_DN_SOME(0.00)[];
> 	TO_MATCH_ENVRCPT_SOME(0.00)[];
> 	DKIM_SIGNED(0.00)[barelysecure.org:s=im68];
> 	URIBL_BLOCKED(0.00)[cashel.logfs.org:mid,cashel.logfs.org:helo];
> 	SUBJECT_HAS_QUESTION(0.00)[]
> X-Rspamd-Server: server01
> X-Rspamd-Queue-Id: 061CA20B86
> X-Spamd-Bar: +
> 
> On Sun, Aug 09, 2026 at 05:34:28PM -0700, syzbot wrote:
>>
>> * 2a:	f7 f6                	div    %esi <-- trapping instruction
> 
> Best guess would be this:
> 
> 	if ((long)size % erase_size) {
> 
> Someone passing an erase_size of 0 would indeed trigger an exception.
> Fix would be something like this:
> 
> -	if ((long)size % erase_size) {
> +	if (!erase_size || (long)size % erase_size) {
> 
> I haven't touched the code is nearly two decades.  If somebody else
> feels like sending a patch and gathering all the glory, please be my
> guest!
> 
> J?rn
> 
> --
> Those who come seeking peace without a treaty are plotting.
> -- Sun Tzu
> 



From miquel.raynal at bootlin.com  Mon Aug 10 05:40:28 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Mon, 10 Aug 2026 14:40:28 +0200
Subject: [syzbot] [mtd?] divide error in block2mtd_setup2
In-Reply-To: <29397db9-7db4-4969-bb0e-5bb71f0c0303@kylinos.cn> (Pei Xiao's
	message of "Mon, 10 Aug 2026 17:17:50 +0800")
References: <6a791c94.01d0871a.3a0d52.009b.GAE@google.com>
	<ankxaiwtTltL0xMj@cashel.logfs.org>
	<29397db9-7db4-4969-bb0e-5bb71f0c0303@kylinos.cn>
Message-ID: <87se4mqhc3.fsf@bootlin.com>

Hi Pei,

>> Best guess would be this:
>> 
>> 	if ((long)size % erase_size) {
>> 
>> Someone passing an erase_size of 0 would indeed trigger an exception.
>> Fix would be something like this:
>> 
>> -	if ((long)size % erase_size) {
>> +	if (!erase_size || (long)size % erase_size) {
>> 
>> I haven't touched the code is nearly two decades.  If somebody else
>> feels like sending a patch and gathering all the glory, please be my
>> guest!
> Thank you for your contribution. I have sent this patch, but I forgot to
> add the Suggested-by: tag

You are invited to send a v2 with the said tag :)

Thanks,
Miqu?l


From miquel.raynal at bootlin.com  Mon Aug 10 05:43:52 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Mon, 10 Aug 2026 14:43:52 +0200
Subject: [PATCH 1/2] mtd: spinand: winbond: add support for W25N04LW
In-Reply-To: <anmLsPcXRk0VAQSW@atmark-techno.com> (Dominique MARTINET's
	message of "Mon, 10 Aug 2026 17:28:32 +0900")
References: <20260807064339.1632249-1-md.alam@oss.qualcomm.com>
	<20260807064339.1632249-2-md.alam@oss.qualcomm.com>
	<anmLsPcXRk0VAQSW@atmark-techno.com>
Message-ID: <87mruuqh6f.fsf@bootlin.com>

Hi Dominique,

On 10/08/2026 at 17:28:32 +09, Dominique MARTINET <dominique.martinet at atmark-techno.com> wrote:

> Md Sadre Alam wrote on Fri, Aug 07, 2026 at 12:13:38PM +0530:
>> Add support for Winbond W25N04LW 4Gbit SPI-NAND.
>> It has 8-bit on-die ECC and a 4096+256 byte page
>> (4096 byte main area, 128 byte usable OOB once
>> on-chip ECC hides the 128 byte parity area),
>> organized as 2048 blocks of 64 pages each, unlike
>> the existing 2048 byte page W25N04KV/W25N04KW parts.
>
> This is pretty good timing, I was just looking at sending a patch for
> W25N04LW myself!
>
> I agree with Miqu?l that your patch looks odd, you wrote
>> +		     NAND_MEMORG(1, 4096, 128, 64, 2048, 40, 1, 1, 1),
> but the erase blocks are 256K long so it should be
> +		     NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
>
> did you actually test this device?
>
> If it helps, the datasheet for W25N04LWZExx / W25N04LWTBxx is freely
> available from digikey here[1]
> [1] https://mm.digikey.com/Volume0/opasdata/d220001/medias/docus/7182/W25N04LWZExx_W25N04LWTBxx_RevD_2025-08-05.pdf

Thanks for all the feedback, Sadre, can you please propose an udpate
with these details and changes?

> (And it also supports continuous read, so I'm curious to see if it'd
> work with the recent continous read support from Miqu?l[2] merged in
> 7.2... I'm not familiar with the subsystem so will need a bit of time to
> plug all the hooks in properly)
> [2]
> https://lore.kernel.org/linux-mtd/20260429-winbond-v6-18-rc1-cont-read-v3-0-0f38b3c229ad at bootlin.com/

If it has continuous read, it requires the flag and a hook to
enable/disable the feature. The existing Winbond helper may already be
enough for it. You can test it with a recent version of mtd-utils, I
added options to nanddump, nandbiterrs and flash_speed for that.

If Sadre dooesn't do it, you can propose a follow-up patch.

Thanks,
Miqu?l


From miquel.raynal at bootlin.com  Mon Aug 10 05:47:50 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Mon, 10 Aug 2026 14:47:50 +0200
Subject: [PATCH] mtd: spi-nor: scope the exclusive RWW lock
In-Reply-To: <DKL4CBQNKCO2.3NAZ6S25Q60XN@kernel.org> (Michael Walle's message
	of "Mon, 10 Aug 2026 10:25:28 +0200")
References: <20260809084223.3596259-1-runyu.xiao@seu.edu.cn>
	<DKL4CBQNKCO2.3NAZ6S25Q60XN@kernel.org>
Message-ID: <87h5l2qgzt.fsf@bootlin.com>

Hello,

On 10/08/2026 at 10:25:28 +02, "Michael Walle" <mwalle at kernel.org> wrote:

> On Sun Aug 9, 2026 at 10:42 AM CEST, Runyu Xiao wrote:
>> spi_nor_rww_start_exclusive() is used as a wait_event_killable()
>> condition. The raw mutex_lock() leaves nor->lock held when the busy
>> condition returns false, so the waiter can block the active operation that
>> must clear the RWW state.
>>
>> Use the same scoped mutex guard as the other RWW start helpers so the
>> mutex is released on both the busy and successful condition paths. The
>> state flags remain the handoff to the caller, while
>> spi_nor_rww_end_exclusive() continues
>> to acquire the mutex when clearing them.
>>
>> The change was checked by comparing the original and patched source.
>
> What do you mean? Was this AI assisted?
>
>> A source-level check of the original wait condition found that it takes
>> `nor->lock` and returns false while an RWW operation is still active. The
>> patched source was checked for a scoped mutex guard that releases
>> `nor->lock` before the wait condition returns. A user-space pthread model
>> held `nor->lock` on the false-condition path and showed that the
>> operation-ending path then blocks when it needs the same mutex. No live
>> SPI-NOR test was run.
>
> What do you mean by "user-space pthread model"? Ported this into
> user-space and tried it there? Please keep the commit message
> precise and don't use any AI gibberish. Please, explain what's going
> on with your own sentences. That way, it shows at least some
> understanding what you are trying to change here.
>
> That being said, there is something odd about that commit. Tudor, do
> you know why you've omitted the guard() in
> spi_nor_rww_start_exclusive()?

I agree with Michael, the fix looks legitimate, however the commit log
is unreadable and should be a 3-line straightforward paragraph.

Thanks,
Miqu?l


From miquel.raynal at bootlin.com  Mon Aug 10 06:17:09 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Mon, 10 Aug 2026 15:17:09 +0200
Subject: [PATCH 03/30] mtd: spi-nor: winbond: Stop filling the .name entry
In-Reply-To: <DKL30B77Z7QJ.IXPI630PUWTV@kernel.org> (Michael Walle's message
	of "Mon, 10 Aug 2026 09:22:45 +0200")
References: <20260529-winbond-v7-1-spi-nor-jv-cleanup-v1-0-87e5d3122244@bootlin.com>
	<20260529-winbond-v7-1-spi-nor-jv-cleanup-v1-3-87e5d3122244@bootlin.com>
	<DJRJJB4NEUEP.168MF88DBSFDF@kernel.org> <87mruzeg1p.fsf@bootlin.com>
	<DKL30B77Z7QJ.IXPI630PUWTV@kernel.org>
Message-ID: <87bjbaqfmy.fsf@bootlin.com>

On 10/08/2026 at 09:22:45 +02, "Michael Walle" <mwalle at kernel.org> wrote:

> On Thu Aug 6, 2026 at 11:47 AM CEST, Miquel Raynal wrote:
>> Hello,
>>
>>>> Replace the names with a comment above the entry with the newly instated
>>>> naming scheme to indicate what chips are covered by each entry.
>>>
>>> This is exported via sysfs, so this could be a regression.
>>
>> Well, actually names are not exported through sysfs. Only the mtd name
>> is available through sysfs, and it is set to `spiX.Y`. That name is only
>> available through debugfs, which is not part of the stable API.
>>
>> I should have checked before dropping all my patches...
>
> That's not correct, see
>
> https://elixir.bootlin.com/linux/v7.1.7/source/drivers/mtd/spi-nor/sysfs.c#L21

True, it is further down the sysfs maze:

# cat /sys/class/mtd/mtd0/name 
spi0.0
# cat /sys/class/mtd/mtd0/device/spi-nor/partname 
FOO

So that means I have to find a way to drop that name for the RV
addition.

Thanks!
Miqu?l


From miquel.raynal at bootlin.com  Mon Aug 10 07:05:59 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Mon, 10 Aug 2026 16:05:59 +0200
Subject: [PATCH 1/5] mtd: spi-nor: Refactor Read Status/Write Status
 support
In-Reply-To: <DKL2WROHNLXJ.3239EBUTVXRQL@kernel.org> (Michael Walle's message
	of "Mon, 10 Aug 2026 09:18:07 +0200")
References: <20260529-winbond-v7-1-spi-nor-rv-addition-v1-0-f3ae18502d5a@bootlin.com>
	<20260529-winbond-v7-1-spi-nor-rv-addition-v1-1-f3ae18502d5a@bootlin.com>
	<DJS8PQ5HZZG5.2YT956NCDX8MG@kernel.org> <87mrv2exwp.fsf@bootlin.com>
	<87se4rem0l.fsf@bootlin.com> <DKL2WROHNLXJ.3239EBUTVXRQL@kernel.org>
Message-ID: <875x1iqddk.fsf@bootlin.com>

On 10/08/2026 at 09:18:07 +02, "Michael Walle" <mwalle at kernel.org> wrote:

> On Thu Aug 6, 2026 at 9:38 AM CEST, Miquel Raynal wrote:
>> Hello Michael,
>>
>>>> One thing that comes to mind is hardware write protection. If
>>>> there's nothing before that code which checks it, the verify might
>>>> fail if the hardware write protection is enabled. So we should
>>>> somehow check for that and drop the verify here.
>>>
>>> But how do you think we should handle it? Will the QE bit writing
>>> verification fail if HW WP is enabled?
>
> It should, because you cannot alter the SR.
>
>> Looking into this further: we shall return an error if the QE bit is not
>> set. It just tells the caller that quad mode cannot be used. Then up to
>> the caller to either hard fail or just degrade into single mode (maybe
>> because of a strapped WP). What we should do is to propose a DT property
>> to flag when WP is strapped in hardware, this would make the content of
>> the status registers immutable and we would just skip the entire write
>> operation in the first place, instead of deliberately trying and get a
>> 100% failure rate. Nevertheless, the changes introduced here are kind of
>> orthogonal and do not alter the current behaviour; we shall however
>> listen if people start complaining about this and perhaps implement the
>> solution proposed above.
>
> There is already a property in the DT, where you almost certainly
> know that quad mode is impossible. Iff there are four rx/tx lines
> (spi-{rx,tx}-bus-width). I haven't seen a flash (even in larger
> packages) where there is a dedicated WP# pin.
>
> What do you mean by a dt property? If (1) the flash is using the WP#
> pin? Or (2) if it is using the WP# pin and it's tied to low?
>
> For (1) you have the QE bit, if it's set, WP is turned off and vice
> versa.
>
> For (2), that doesn't make sense, because that pin could be tied
> to some kind of logic which can change during runtime.
>
> FWIW, I don't care too much, you've asked why there is a
> verification step, I've just answered :)

Thank you :) Anyway, I've reinstated these checks in my last version.

If you look at it, don't focus on the commit about the names, I need to
fix that up again by adding some kind of "hide" flag for the RV chips,
instead of dropping the names (the rest won't change much).

Thanks,
Miqu?l



From maciej.andrzejewski at m-works.net  Mon Aug 10 07:15:55 2026
From: maciej.andrzejewski at m-works.net (Maciej Andrzejewski ICEYE)
Date: Mon, 10 Aug 2026 16:15:55 +0200
Subject: [PATCH 0/2] gpiolib: of: Scope the legacy SPI CS quirk to SPI
Message-ID: <20260810141555.70575-1-maciej.andrzejewski@m-works.net>

of_gpio_flags_quirks() decides whether to apply SPI chip-select polarity
semantics purely from the property name "cs-gpios". Nothing about that
name is SPI specific. nand-controller.yaml documents it for NAND
controllers too, and rawnand_dt_parse_gpio_cs() asks for those lines
with gpiod_count(dev, "cs"), which gpiolib expands to "cs-gpios". The
result is that a NAND chip node whose first reg cell happens to equal a
GPIO index gets flipped to active low, and since the NAND core requests
the descriptors GPIOD_OUT_HIGH and drivers assert with a logical 0, the
die is never selected.

We hit this on an Arasan controller with a GPIO CS1 and worked around it
downstream by putting spi-cs-high on the NAND chip node, which is
plainly the wrong property to need. These two patches narrow the quirk
instead, so that it only fires on an SPI bus.

Device tree has no bus type marker, so the series uses two tests, one
per patch, and applies the quirk if either matches:

  1. the matched child has a property namespaced with "spi-"
  2. the controller nodename has the "spi" prefix that
     spi-controller.yaml requires

Testing
-------

Both tests were scored against every board device tree in the kernel.
Each .dts under arch/*/boot/dts was expanded with the same cpp and
scripts/dtc pipeline the build uses; all 3620 expand. 503 of them hold
at least one GPIO chip select that can reach the quirk, 765 in total.
761 match both tests, 1 needs only the property scan, 3 need only the
nodename, and none match neither, so no in-tree board changes behaviour.
Patch 2 names the four exceptions individually.

The collision with NAND is in-tree but latent: 36 board trees, all
Atmel/Microchip at91, give their nand at 3 controller a cs-gpios line and
escape only because the sole child of those nodes is a partitions
container with no reg for the quirk to match against.

drivers/gpio/gpiolib-of.o builds warning-free at W=1 with CONFIG_SPI=y
and with CONFIG_SPI=n.

Note on bisect
--------------

The two tests are complementary rather than layered, so patch 1 on its
own would stop applying the quirk to the three peripherals that carry no
"spi-" property (a panel, an mcp251xfd and one ecspi child, all named in
patch 2). Only the pair is behaviour-preserving. They are split because
the property scan and the nodename test are separate arguments and I
expect the second to attract more discussion. Happy to squash them into
one commit if you would rather not have that window.

Maciej Andrzejewski ICEYE (2):
  gpiolib: of: Only apply the SPI CS quirk to SPI peripherals
  gpiolib: of: Identify SPI controllers by nodename

 drivers/gpio/gpiolib-of.c | 40 +++++++++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)


base-commit: db2ddb87143519e20a95aa36c60b36107b736a58
-- 
2.50.1



From maciej.andrzejewski at m-works.net  Mon Aug 10 07:16:45 2026
From: maciej.andrzejewski at m-works.net (Maciej Andrzejewski ICEYE)
Date: Mon, 10 Aug 2026 16:16:45 +0200
Subject: [PATCH 0/2] gpiolib: of: Scope the legacy SPI CS quirk to SPI
Message-ID: <20260810141629.81650-1-maciej.andrzejewski@m-works.net>

of_gpio_flags_quirks() decides whether to apply SPI chip-select polarity
semantics purely from the property name "cs-gpios". Nothing about that
name is SPI specific. nand-controller.yaml documents it for NAND
controllers too, and rawnand_dt_parse_gpio_cs() asks for those lines
with gpiod_count(dev, "cs"), which gpiolib expands to "cs-gpios". The
result is that a NAND chip node whose first reg cell happens to equal a
GPIO index gets flipped to active low, and since the NAND core requests
the descriptors GPIOD_OUT_HIGH and drivers assert with a logical 0, the
die is never selected.

We hit this on an Arasan controller with a GPIO CS1 and worked around it
downstream by putting spi-cs-high on the NAND chip node, which is
plainly the wrong property to need. These two patches narrow the quirk
instead, so that it only fires on an SPI bus.

Device tree has no bus type marker, so the series uses two tests, one
per patch, and applies the quirk if either matches:

  1. the matched child has a property namespaced with "spi-"
  2. the controller nodename has the "spi" prefix that
     spi-controller.yaml requires

Testing
-------

Both tests were scored against every board device tree in the kernel.
Each .dts under arch/*/boot/dts was expanded with the same cpp and
scripts/dtc pipeline the build uses; all 3620 expand. 503 of them hold
at least one GPIO chip select that can reach the quirk, 765 in total.
761 match both tests, 1 needs only the property scan, 3 need only the
nodename, and none match neither, so no in-tree board changes behaviour.
Patch 2 names the four exceptions individually.

The collision with NAND is in-tree but latent: 36 board trees, all
Atmel/Microchip at91, give their nand at 3 controller a cs-gpios line and
escape only because the sole child of those nodes is a partitions
container with no reg for the quirk to match against.

drivers/gpio/gpiolib-of.o builds warning-free at W=1 with CONFIG_SPI=y
and with CONFIG_SPI=n.

Note on bisect
--------------

The two tests are complementary rather than layered, so patch 1 on its
own would stop applying the quirk to the three peripherals that carry no
"spi-" property (a panel, an mcp251xfd and one ecspi child, all named in
patch 2). Only the pair is behaviour-preserving. They are split because
the property scan and the nodename test are separate arguments and I
expect the second to attract more discussion. Happy to squash them into
one commit if you would rather not have that window.

Maciej Andrzejewski ICEYE (2):
  gpiolib: of: Only apply the SPI CS quirk to SPI peripherals
  gpiolib: of: Identify SPI controllers by nodename

 drivers/gpio/gpiolib-of.c | 40 +++++++++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)


base-commit: db2ddb87143519e20a95aa36c60b36107b736a58
-- 
2.50.1



From maciej.andrzejewski at m-works.net  Mon Aug 10 07:16:52 2026
From: maciej.andrzejewski at m-works.net (Maciej Andrzejewski ICEYE)
Date: Mon, 10 Aug 2026 16:16:52 +0200
Subject: [PATCH 1/2] gpiolib: of: Only apply the SPI CS quirk to SPI
 peripherals
In-Reply-To: <20260810141629.81650-1-maciej.andrzejewski@m-works.net>
References: <20260810141629.81650-1-maciej.andrzejewski@m-works.net>
Message-ID: <20260810141629.81650-2-maciej.andrzejewski@m-works.net>

The legacy SPI chip-select polarity quirk in of_gpio_flags_quirks() is
keyed on nothing but the property name "cs-gpios". That name is not
exclusive to SPI: nand-controller.yaml documents the very same property
for NAND controllers, and rawnand_dt_parse_gpio_cs() requests those
lines with gpiod_count(dev, "cs"), which gpiolib expands to "cs-gpios".
A NAND controller therefore has SPI chip-select semantics forced onto
its chip selects, and any chip node whose first reg cell matches a GPIO
index is silently flipped to active low. The NAND core requests the
descriptors GPIOD_OUT_HIGH and drivers assert with a logical 0, so the
inversion leaves the die permanently deselected.

The example in nand-controller.yaml is itself affected: it pairs a
native chip select with a GPIO one and gives the second chip a reg of 1,
which trips the quirk whenever CONFIG_SPI_MASTER is enabled. In-tree the
collision is real but latent. 36 board trees, all Atmel/Microchip at91,
give their nand at 3 controller a cs-gpios line, and they escape only
because the sole child of those nodes is a partitions container with no
reg for the quirk to match against.

Device tree carries no bus type marker, so identify the bus from what
the peripheral advertises about itself. Properties of an SPI peripheral
are namespaced with "spi-" (spi-max-frequency, spi-cpol, spi-cs-high and
the rest of spi-peripheral-props.yaml), whereas a NAND chip node carries
only reg, nand-* and its partition table. Skip the quirk when the
matched child has no "spi-" prefixed property.

This is a convention rather than a guarantee: compatible and reg are the
only properties spi-controller.yaml makes mandatory for a peripheral, so
a minimal one may carry no "spi-" property at all. The next patch covers
those by looking at the controller instead.

Signed-off-by: Maciej Andrzejewski ICEYE <maciej.andrzejewski at m-works.net>
---
 drivers/gpio/gpiolib-of.c | 28 ++++++++++++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)

diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c
index 940b566946ce..bd9a623f2ae1 100644
--- a/drivers/gpio/gpiolib-of.c
+++ b/drivers/gpio/gpiolib-of.c
@@ -340,6 +340,25 @@ static void of_gpio_set_polarity_by_property(const struct device_node *np,
 	}
 }
 
+/*
+ * The legacy SPI chip select binding below is keyed on a property name that
+ * other subsystems reuse for the same purpose, notably NAND controllers (see
+ * Documentation/devicetree/bindings/mtd/nand-controller.yaml), whose chip
+ * selects carry no SPI polarity semantics. Device tree has no bus type
+ * marker, so go by what the peripheral advertises about itself: properties
+ * of an SPI peripheral are namespaced with "spi-".
+ */
+static bool of_gpio_child_is_spi_peripheral(const struct device_node *child)
+{
+	struct property *pp;
+
+	for_each_property_of_node(child, pp)
+		if (str_has_prefix(pp->name, "spi-"))
+			return true;
+
+	return false;
+}
+
 static void of_gpio_flags_quirks(const struct device_node *np,
 				 const char *propname,
 				 enum of_gpio_flags *flags,
@@ -374,6 +393,11 @@ static void of_gpio_flags_quirks(const struct device_node *np,
 			if (ret)
 				continue;
 			if (cs == index) {
+				bool active_high;
+
+				if (!of_gpio_child_is_spi_peripheral(child))
+					break;
+
 				/*
 				 * SPI children have active low chip selects
 				 * by default. This can be specified negatively
@@ -386,8 +410,8 @@ static void of_gpio_flags_quirks(const struct device_node *np,
 				 * conflict and the "spi-cs-high" flag will
 				 * take precedence.
 				 */
-				bool active_high = of_property_read_bool(child,
-								"spi-cs-high");
+				active_high = of_property_read_bool(child,
+								    "spi-cs-high");
 				of_gpio_quirk_polarity(child, active_high,
 						       flags);
 				break;
-- 
2.50.1



From maciej.andrzejewski at m-works.net  Mon Aug 10 07:16:59 2026
From: maciej.andrzejewski at m-works.net (Maciej Andrzejewski ICEYE)
Date: Mon, 10 Aug 2026 16:16:59 +0200
Subject: [PATCH 2/2] gpiolib: of: Identify SPI controllers by nodename
In-Reply-To: <20260810141629.81650-1-maciej.andrzejewski@m-works.net>
References: <20260810141629.81650-1-maciej.andrzejewski@m-works.net>
Message-ID: <20260810141629.81650-3-maciej.andrzejewski@m-works.net>

The "spi-" property scan added by the previous patch misses SPI
peripherals that declare nothing beyond compatible and reg, which are
the only two properties spi-controller.yaml makes mandatory for a child
node. Let the controller settle those cases: spi-controller.yaml
constrains the controller nodename to ^spi(@.*|-[0-9]+)?$, so the name
is the one bus marker every conforming SPI controller has to carry, and
of_node_name_prefix() is the existing helper for testing it.

Both tests were scored over every board device tree in the kernel,
expanded with the same cpp and scripts/dtc pipeline the build uses, at
v7.2-rc7. All 3620 trees under arch/*/boot/dts expand; 503 of them hold
at least one GPIO chip select that can reach the quirk, 765 such chip
selects in total. 761 are matched by both tests, and none are matched by
neither, so no in-tree board changes behaviour.

One needs the property scan on its own: psc at 11400 on ac14xx, a
fsl,mpc5121-psc-spi named after the hardware block rather than the bus,
whose m25p128 at 0 child carries spi-max-frequency.

Three need the nodename on its own, all peripherals with no "spi-"
property of any kind:

  - panel at 0 under the spi-gpio controller on rk3566-anbernic-rg503
  - can at 0, an mcp251xfd, under ecspi3 on imx8mn-vhip4-evalboard-v1
  - spi at 1 under ecspi1 on imx53-ppd

of_gpio_spi_cs_get_count() in this file identifies SPI controllers with
of_device_is_compatible() instead, but it only has to name three legacy
controllers whose bindings are closed. An allow-list here would have to
name every SPI controller binding in the tree, 71 distinct compatible
strings among the candidates alone, and grow with every new one.

Signed-off-by: Maciej Andrzejewski ICEYE <maciej.andrzejewski at m-works.net>
---
 drivers/gpio/gpiolib-of.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c
index bd9a623f2ae1..959b32b3bea0 100644
--- a/drivers/gpio/gpiolib-of.c
+++ b/drivers/gpio/gpiolib-of.c
@@ -359,6 +359,17 @@ static bool of_gpio_child_is_spi_peripheral(const struct device_node *child)
 	return false;
 }
 
+/*
+ * A minimal SPI peripheral may carry no "spi-" property at all, so also let
+ * the controller identify the bus. spi-controller.yaml constrains the
+ * controller nodename to ^spi(@.*|-[0-9]+)?$, which makes the name the one
+ * bus marker a controller is required to have.
+ */
+static bool of_gpio_node_is_spi_controller(const struct device_node *np)
+{
+	return of_node_name_prefix(np, "spi");
+}
+
 static void of_gpio_flags_quirks(const struct device_node *np,
 				 const char *propname,
 				 enum of_gpio_flags *flags,
@@ -395,7 +406,8 @@ static void of_gpio_flags_quirks(const struct device_node *np,
 			if (cs == index) {
 				bool active_high;
 
-				if (!of_gpio_child_is_spi_peripheral(child))
+				if (!of_gpio_child_is_spi_peripheral(child) &&
+				    !of_gpio_node_is_spi_controller(np))
 					break;
 
 				/*
-- 
2.50.1



From miquel.raynal at bootlin.com  Mon Aug 10 07:25:51 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Mon, 10 Aug 2026 16:25:51 +0200
Subject: [PATCH v3 0/3] mtd: rawnand: sunxi: fix H6/H616 controller
 timings
In-Reply-To: <20260807-submit-sunxi-nand-h6-h616-timings-v3-0-91287b543f41@gmail.com>
References: <20260807-submit-sunxi-nand-h6-h616-timings-v3-0-91287b543f41@gmail.com>
Message-ID: <178637195198.1098769.15933415840493831349.b4-ty@bootlin.com>

On Fri, 07 Aug 2026 09:38:58 -0600, James Hilliard wrote:
> The sunxi NAND timing solver currently applies the original A10 NDFC
> timing characteristics to every supported controller. The H6/H616 NDFC
> uses two internal cycles for several setup and hold intervals and has
> different delay-field encodings, so the resulting clock and timing
> register values do not match the hardware.
> 
> Prepare for per-controller timing data in two behaviorally focused
> patches, then add and select the H616 characteristics in the final fix.
> Legacy controllers retain their existing setup-cycle behavior and delay
> encodings.
> 
> [...]

Applied to nand/next, thanks!

[1/3] mtd: rawnand: sunxi: group controller delay tables
      commit: 9f2e033754c6c023150f184b4e72bbcc5d6eea13
[2/3] mtd: rawnand: sunxi: describe tADL and tWHR delays
      commit: 147f2a5743f8864bfc265654b1856af11c8c0031
[3/3] mtd: rawnand: sunxi: fix H6/H616 controller timings
      commit: 15a3cbce32994141252bb4ecfe3ff3a5d22d0b4f

Patche(s) should be available on mtd/linux.git and will be
part of the next PR (provided that no robot complains by then).

Kind regards,
Miqu?l



From miquel.raynal at bootlin.com  Mon Aug 10 07:43:09 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Mon, 10 Aug 2026 16:43:09 +0200
Subject: [PATCH v2 0/3] mtd/spi-mem: Enable DQS support
In-Reply-To: <8a729209-3bf9-4f2e-8870-1cbb64a95184@sirena.org.uk> (Mark
	Brown's message of "Fri, 7 Aug 2026 17:14:37 +0100")
References: <20260807-winbond-nand-next-phy-tuning-v2-0-73595e89d6e3@bootlin.com>
	<8a729209-3bf9-4f2e-8870-1cbb64a95184@sirena.org.uk>
Message-ID: <87zeyuox36.fsf@bootlin.com>

Hi,

>> Mark, as agreed, you can take the spi-mem patch through spi for this
>> cycle, and I will pick up the remaining spi-nand patches after the merge
>> window.
>
> As I said on the previous version can you please send something that can
> be applied?  The MTD patches are failing to apply.

I though you had issues with the spi-mem patch. This version still
depends on Santhosh's work. I will send an update.

Thanks,
Miqu?l


From miquel.raynal at bootlin.com  Mon Aug 10 08:10:41 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Mon, 10 Aug 2026 17:10:41 +0200
Subject: [PATCH v3 0/3] mtd/spi-mem: Enable DQS support
Message-ID: <20260810-winbond-nand-next-phy-tuning-v3-0-a97c3a61e675@bootlin.com>

For his PHY tuning series on the Cadence QSPI controller embedded in TI
SoCs, Santhosh initially needed to access the availability of the DQS
(data strobe) signal. This is a chip dependent capability, which may
sometimes be enabled.

Create a SPI memory flag for it, let the SPI NAND core set this flag
when it knows about the capability, and expect manufacturer drivers to
enable it for octal needs when the chips are compatible.

This is an alternative at needing a DT property. Please note that there
are a few blind spots:
- the line may not be wired (this would be surprising, but can be
  flagged this time by a DT property)
- manufacturer drivers must enable the feature if it is
  available (especially for high speed DTR modes)
- this implementation is proposed for SPI NANDs only, if this proposal
  is accepted the same approach may be taken in SPI NOR.

Here is the original thread which lead to this series:
https://lore.kernel.org/linux-spi/87v7gbdwdh.fsf at bootlin.com/T/#ma79fc364d7b882a48dbdf47203dde75df4bb0ec4

This series was compile tested only at this stage. As DDR tuning does
not yet work on my board, I cannot make sure this change has a real
impact.

Mark, as agreed, you can take the spi-mem patch through spi for this
cycle, and I will pick up the remaining spi-nand patches after the merge
window.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
Changes in v3:
- Rebased on top of linux-spi/for-7.3 and dopped all existing
  dependencies on the MTD bits.
- Link to v2: https://lore.kernel.org/r/20260807-winbond-nand-next-phy-tuning-v2-0-73595e89d6e3 at bootlin.com

Changes in v2:
- Dropped the RFC prefix.
- Rebased on top of v7.2-rc1.
- Dropped the spi patch for now, just allowing to use DQS on a few
  Winbond SPI NANDs (can be improved later).
- Link to v1: https://lore.kernel.org/r/20260205-winbond-nand-next-phy-tuning-v1-0-5e7d3976f0f1 at bootlin.com

---
Miquel Raynal (3):
      spi: spi-mem: Flag DQS capability
      mtd: spi-nand: Set the DQS spi-mem capability if available
      mtd: spi-nand: winbond: Enable the DQS pin on W35N**JW series

 drivers/mtd/nand/spi/core.c    |  4 ++++
 drivers/mtd/nand/spi/winbond.c |  8 ++++----
 drivers/spi/spi-mem.c          | 32 ++++++++++++++++++++++++++++++++
 include/linux/mtd/spinand.h    |  1 +
 include/linux/spi/spi-mem.h    |  4 ++++
 5 files changed, 45 insertions(+), 4 deletions(-)
---
base-commit: 1ec281b42ee249cfe9bad52a54149de29ed6aa78
change-id: 20260205-winbond-nand-next-phy-tuning-aabefc018032

Best regards,
-- 
Miquel Raynal <miquel.raynal at bootlin.com>



From miquel.raynal at bootlin.com  Mon Aug 10 08:10:42 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Mon, 10 Aug 2026 17:10:42 +0200
Subject: [PATCH v3 1/3] spi: spi-mem: Flag DQS capability
In-Reply-To: <20260810-winbond-nand-next-phy-tuning-v3-0-a97c3a61e675@bootlin.com>
References: <20260810-winbond-nand-next-phy-tuning-v3-0-a97c3a61e675@bootlin.com>
Message-ID: <20260810-winbond-nand-next-phy-tuning-v3-1-a97c3a61e675@bootlin.com>

DQS is a typical SPI memory signal used to help with reading the data on
the bus at high speeds (especially in DTR mode) by avoiding clock
skews. The chip generates a clock signal synchronized with its data
output fronts, also called data strobe.

SPI NOR and SPI NAND cores must set this flag in order to indicate to
other layers that DQS is available.

Create a getter and a setter to reach this capability.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
Acked-by: Mark Brown <broonie at kernel.org>
---
 drivers/spi/spi-mem.c       | 32 ++++++++++++++++++++++++++++++++
 include/linux/spi/spi-mem.h |  4 ++++
 2 files changed, 36 insertions(+)

diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index a88b9f038356..5f973ebfb8b6 100644
--- a/drivers/spi/spi-mem.c
+++ b/drivers/spi/spi-mem.c
@@ -541,6 +541,38 @@ const char *spi_mem_get_name(struct spi_mem *mem)
 }
 EXPORT_SYMBOL_GPL(spi_mem_get_name);
 
+/**
+ * spi_mem_set_dqs() - Mark DQS as being available
+ * @mem: the SPI memory
+ *
+ * When reading at high frequencies (> 100MHz), especially when DTR is enabled,
+ * transfer speed is limited due to clock skews. In particular, the controller
+ * does not know the board propagation delay nor the memory chip internal delay
+ * (clock in to data out) and thus cannot optimize its sampling points.
+ * Mitigating this limitation is possible with the addition of a data strobe
+ * signal, commonly named DQS.
+ *
+ * Set the DQS boolean if the feature is available and configured at the chip
+ * level. Controllers may query this value.
+ */
+void spi_mem_set_dqs(struct spi_mem *mem)
+{
+	mem->dqs = true;
+}
+EXPORT_SYMBOL_GPL(spi_mem_set_dqs);
+
+/**
+ * spi_mem_has_dqs() - Query whether the DQS is available or not
+ * @mem: the SPI memory
+ *
+ * Return: a boolean indicating whether the DQS signal is available or not.
+ */
+bool spi_mem_has_dqs(struct spi_mem *mem)
+{
+	return mem->dqs;
+}
+EXPORT_SYMBOL_GPL(spi_mem_has_dqs);
+
 /**
  * spi_mem_adjust_op_size() - Adjust the data size of a SPI mem operation to
  *			      match controller limitations
diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h
index f660bb2e9f85..0cce6b57242a 100644
--- a/include/linux/spi/spi-mem.h
+++ b/include/linux/spi/spi-mem.h
@@ -275,6 +275,7 @@ struct spi_mem_dirmap_desc {
  * @spi: the underlying SPI device
  * @drvpriv: spi_mem_driver private data
  * @name: name of the SPI memory device
+ * @dqs: extra data trobe pin available for high frequency read operations
  *
  * Extra information that describe the SPI memory device and may be needed by
  * the controller to properly handle this device should be placed here.
@@ -286,6 +287,7 @@ struct spi_mem {
 	struct spi_device *spi;
 	void *drvpriv;
 	const char *name;
+	bool dqs;
 };
 
 /**
@@ -461,6 +463,8 @@ bool spi_mem_default_supports_op(struct spi_mem *mem,
 }
 #endif /* CONFIG_SPI_MEM */
 
+void spi_mem_set_dqs(struct spi_mem *mem);
+bool spi_mem_has_dqs(struct spi_mem *mem);
 int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op);
 void spi_mem_adjust_op_freq(struct spi_mem *mem, struct spi_mem_op *op);
 u64 spi_mem_calc_op_duration(struct spi_mem *mem, struct spi_mem_op *op);

-- 
2.54.0



From miquel.raynal at bootlin.com  Mon Aug 10 08:10:43 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Mon, 10 Aug 2026 17:10:43 +0200
Subject: [PATCH v3 2/3] mtd: spi-nand: Set the DQS spi-mem capability if
 available
In-Reply-To: <20260810-winbond-nand-next-phy-tuning-v3-0-a97c3a61e675@bootlin.com>
References: <20260810-winbond-nand-next-phy-tuning-v3-0-a97c3a61e675@bootlin.com>
Message-ID: <20260810-winbond-nand-next-phy-tuning-v3-2-a97c3a61e675@bootlin.com>

Check whether the NAND is capable of generating a DQS signal and set the
flag accordingly.

Not wiring the DQS signal on a DQS capable chip that will be used at
frequency requiring this signal may be considered a hardware bug, so
let's assume this line will be routed "in most cases". If/when we get
issues with this assumption, a DT property describing the lacking line
in the routing can be created.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/nand/spi/core.c | 4 ++++
 include/linux/mtd/spinand.h | 1 +
 2 files changed, 5 insertions(+)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 74bb5ee83b31..b54f7a1dae85 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -2050,6 +2050,10 @@ static int spinand_probe(struct spi_mem *mem)
 	if (ret)
 		return ret;
 
+	/* Assume manufacturer drivers will enable the DQS pin if it is available */
+	if (spinand->flags & SPINAND_HAS_DQS)
+		spi_mem_set_dqs(mem);
+
 	ret = mtd_device_register(mtd, NULL, 0);
 	if (ret)
 		goto err_spinand_cleanup;
diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
index ec6efcfeef83..fe05b477afef 100644
--- a/include/linux/mtd/spinand.h
+++ b/include/linux/mtd/spinand.h
@@ -490,6 +490,7 @@ struct spinand_ecc_info {
 #define SPINAND_HAS_READ_PLANE_SELECT_BIT		BIT(3)
 #define SPINAND_NO_RAW_ACCESS				BIT(4)
 #define SPINAND_ODTR_PACKED_PAGE_READ			BIT(5)
+#define SPINAND_HAS_DQS					BIT(6)
 
 /**
  * struct spinand_ondie_ecc_conf - private SPI-NAND on-die ECC engine structure

-- 
2.54.0



From miquel.raynal at bootlin.com  Mon Aug 10 08:10:44 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Mon, 10 Aug 2026 17:10:44 +0200
Subject: [PATCH v3 3/3] mtd: spi-nand: winbond: Enable the DQS pin on
 W35N**JW series
In-Reply-To: <20260810-winbond-nand-next-phy-tuning-v3-0-a97c3a61e675@bootlin.com>
References: <20260810-winbond-nand-next-phy-tuning-v3-0-a97c3a61e675@bootlin.com>
Message-ID: <20260810-winbond-nand-next-phy-tuning-v3-3-a97c3a61e675@bootlin.com>

These chips have a DQS pin, enable it by default in DTR mode because
there is apparently no issue in setting it for lower frequencies, and
the extra power consumption seems very low compared to the current drawn
by the NAND array itself while operating.

This setting will be required for high speed I/O transfers (with PHY
tuning).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/nand/spi/winbond.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
index 9b78c1e6cbc9..8209b214e594 100644
--- a/drivers/mtd/nand/spi/winbond.c
+++ b/drivers/mtd/nand/spi/winbond.c
@@ -607,7 +607,7 @@ static int w35n0xjw_vcr_cfg(struct spinand_device *spinand,
 	else if (!single && !dtr)
 		io_mode = W35N01JW_VCR_IO_MODE_OCTAL_SDR;
 	else if (!single && dtr)
-		io_mode = W35N01JW_VCR_IO_MODE_OCTAL_DDR;
+		io_mode = W35N01JW_VCR_IO_MODE_OCTAL_DDR_DS;
 	else
 		return -EINVAL;
 
@@ -689,7 +689,7 @@ static const struct spinand_info winbond_spinand_table[] = {
 							&write_cache_octal_variants,
 							&update_cache_octal_variants,
 							&cont_read_cache_octal_variants),
-		     0,
+		     SPINAND_HAS_DQS,
 		     SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops),
 		     SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status),
 		     SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg),
@@ -744,7 +744,7 @@ static const struct spinand_info winbond_spinand_table[] = {
 							&write_cache_octal_variants,
 							&update_cache_octal_variants,
 							&cont_read_cache_octal_variants),
-		     SPINAND_ODTR_PACKED_PAGE_READ,
+		     SPINAND_ODTR_PACKED_PAGE_READ | SPINAND_HAS_DQS,
 		     SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops),
 		     SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status),
 		     SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg),
@@ -776,7 +776,7 @@ static const struct spinand_info winbond_spinand_table[] = {
 							&write_cache_octal_variants,
 							&update_cache_octal_variants,
 							&cont_read_cache_octal_variants),
-		     SPINAND_ODTR_PACKED_PAGE_READ,
+		     SPINAND_ODTR_PACKED_PAGE_READ | SPINAND_HAS_DQS,
 		     SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops),
 		     SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status),
 		     SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg),

-- 
2.54.0



From joern at barelysecure.org  Mon Aug 10 09:27:03 2026
From: joern at barelysecure.org (=?iso-8859-1?Q?J=F6rn?= Engel)
Date: Mon, 10 Aug 2026 09:27:03 -0700
Subject: [syzbot] [mtd?] divide error in block2mtd_setup2
In-Reply-To: <ankxaiwtTltL0xMj@cashel.logfs.org>
References: <6a791c94.01d0871a.3a0d52.009b.GAE@google.com>
 <ankxaiwtTltL0xMj@cashel.logfs.org>
Message-ID: <ann719YIqopUBCv5@cashel.logfs.org>

On Sun, Aug 09, 2026 at 07:03:22PM -0700, J?rn Engel wrote:
> 
> Best guess would be this:
> 
> 	if ((long)size % erase_size) {

While at it, what purpose does the cast to (long) serve?

As far as I can see, it is useless and should be removed.  Fabian, do
you remember why it was necessary?

J?rn

--
The real voyage of discovery consists not in seeking new landscapes,
but in having new eyes.
-- Marcel Proust


From robh at kernel.org  Mon Aug 10 11:03:24 2026
From: robh at kernel.org (Rob Herring)
Date: Mon, 10 Aug 2026 13:03:24 -0500
Subject: [PATCH v6 1/7] dt-bindings: mtd: jedec,spi-nor: allow the SFDP
 to be exposed via NVMEM
In-Reply-To: <20260729043026.1811147-2-manikandan.m@microchip.com>
References: <20260729043026.1811147-1-manikandan.m@microchip.com>
 <20260729043026.1811147-2-manikandan.m@microchip.com>
Message-ID: <20260810180324.GA2352629-robh@kernel.org>

On Wed, Jul 29, 2026 at 10:00:20AM +0530, Manikandan Muralidharan wrote:
> Add an optional "sfdp" child node (compatible "jedec,sfdp") that
> describes the SFDP as a read-only NVMEM provider via nvmem.yaml, so its
> contents (e.g. a vendor EUI-48/EUI-64) can be read through NVMEM cells.
> 
> Reviewed-by: Linus Walleij <linusw at kernel.org>
> Signed-off-by: Manikandan Muralidharan <manikandan.m at microchip.com>
> ---
>  .../devicetree/bindings/mtd/jedec,spi-nor.yaml | 18 ++++++++++++++++++
>  1 file changed, 18 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml b/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml
> index 587af4968255..98fd954598ab 100644
> --- a/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml
> +++ b/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml
> @@ -103,6 +103,20 @@ properties:
>    spi-cpol: true
>    spi-cpha: true
>  
> +  sfdp:
> +    $ref: /schemas/nvmem/nvmem.yaml#
> +    unevaluatedProperties: false
> +    description:
> +      The Serial Flash Discoverable Parameters (SFDP) tables exposed as a
> +      read-only NVMEM device. This allows standard or vendor-specific SFDP
> +      data (for example a factory-programmed EUI-48/EUI-64 identifier) to be
> +      consumed through NVMEM cells.

blank line

> +    properties:
> +      compatible:
> +        const: jedec,sfdp

blank line

With that,

Reviewed-by: Rob Herring (Arm) <robh at kernel.org>


From robh at kernel.org  Mon Aug 10 11:35:32 2026
From: robh at kernel.org (Rob Herring (Arm))
Date: Mon, 10 Aug 2026 13:35:32 -0500
Subject: [PATCH v6 2/7] dt-bindings: nvmem: layouts: add Microchip/SST
 SFDP EUI layout
In-Reply-To: <20260729043026.1811147-3-manikandan.m@microchip.com>
References: <20260729043026.1811147-1-manikandan.m@microchip.com>
 <20260729043026.1811147-3-manikandan.m@microchip.com>
Message-ID: <178638693216.2623305.17648873474456731679.robh@kernel.org>


On Wed, 29 Jul 2026 10:00:21 +0530, Manikandan Muralidharan wrote:
> Add a binding for the NVMEM layout that exposes the factory-programmed
> EUI-48 identifier from the Microchip/SST vendor-specific SFDP parameter
> table (e.g. SST26VF064BEUI) as a "mac-address" NVMEM cell, and reference
> it from nvmem-layout.yaml.
> 
> Reviewed-by: Linus Walleij <linusw at kernel.org>
> Signed-off-by: Manikandan Muralidharan <manikandan.m at microchip.com>
> ---
>  .../layouts/microchip,sst26vf-sfdp-eui.yaml   | 60 +++++++++++++++++++
>  .../bindings/nvmem/layouts/nvmem-layout.yaml  |  1 +
>  2 files changed, 61 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/nvmem/layouts/microchip,sst26vf-sfdp-eui.yaml
> 

Reviewed-by: Rob Herring (Arm) <robh at kernel.org>



From abramowitzk at gmail.com  Mon Aug 10 14:09:00 2026
From: abramowitzk at gmail.com (Kyle Abramowitz)
Date: Mon, 10 Aug 2026 17:09:00 -0400
Subject: UBI Static Volume CRC Corruption
Message-ID: <CAAbNe083MrxKmSB4JN8WhO+Gjomgt46k4CLi1k0D8kQ0GpTVGQ@mail.gmail.com>

Hello,

I am working on a NAND driver for an MT7620A and a Winbond W29N01HV

Something unusual came up in testing. An erase block that is bad, but not
yet marked bad is causing `ubiupdatevol` to report CRC corruption on its
validation pass for static volumes. The write and erase functions do not return
any errors as they do not do any sort of readback validation. This
seems consistent
with other drivers I see in the tree (Kernel 6.12).

The issue we are running into is that we expected UBI to torture this block once
it was discovered to have unrecoverable ECC errors. But after tracing
through the
code, we found that the scrubbing step is skipped in this case and no
torture happens
so this block never gets marked as bad.

My questions are: what is the proper way to handle this? What should
happen on a freshly
discovered bad block that has not been marked yet? Is it intended that
scrubbing and torturing
is skipped on crc failures in `ubi_eba_read_leb()`?

My hope was that it would get marked bad eventually and remapped to a
new PEB so a future
write would succeed.

Thank you!

Kyle


From broonie at kernel.org  Mon Aug 10 10:01:17 2026
From: broonie at kernel.org (Mark Brown)
Date: Mon, 10 Aug 2026 18:01:17 +0100
Subject: (subset) [PATCH v3 0/3] mtd/spi-mem: Enable DQS support
In-Reply-To: <20260810-winbond-nand-next-phy-tuning-v3-0-a97c3a61e675@bootlin.com>
References: <20260810-winbond-nand-next-phy-tuning-v3-0-a97c3a61e675@bootlin.com>
Message-ID: <178638127767.155010.4353834320826597950.b4-ty@b4>

On Mon, 10 Aug 2026 17:10:41 +0200, Miquel Raynal wrote:
> mtd/spi-mem: Enable DQS support
> 
> For his PHY tuning series on the Cadence QSPI controller embedded in TI
> SoCs, Santhosh initially needed to access the availability of the DQS
> (data strobe) signal. This is a chip dependent capability, which may
> sometimes be enabled.
> 
> [...]

Applied to

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-7.3

Thanks!

[1/3] spi: spi-mem: Flag DQS capability
      https://git.kernel.org/broonie/spi/c/391144742324

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark



From james.hilliard1 at gmail.com  Mon Aug 10 16:04:48 2026
From: james.hilliard1 at gmail.com (James Hilliard)
Date: Mon, 10 Aug 2026 17:04:48 -0600
Subject: [PATCH 0/3] mtd: rawnand: sunxi: support the Allwinner randomized
 OOB format
Message-ID: <20260810-submit-sunxi-nand-vendor-oob-layout-v1-v1-0-463853a14ad9@gmail.com>

The sunxi NAND controller randomizes normal pages, including their OOB
data. The mainline driver compensates the bad block marker so that it
remains plain on flash. On H6 and H616 it also fills the OOB space left
after ECC with protected user data. These choices produce a different
physical OOB format from the one used by Allwinner NAND firmware.

Add allwinner,randomized-oob to select the Allwinner randomized OOB
format. The property enables page randomization unconditionally, keeps
the bad block marker in the randomizer data stream, and selects the
controller-specific protected user-data placement. H6 and H616 use four
protected bytes per 1 KiB ECC step, capped at 16 bytes, with the complete
protected region placed before the first ECC step. Older controllers
retain their existing fixed user-data placement.

When the property is absent, retain the existing mainline marker handling
and H6/H616 maximized OOB layout.

The series first documents the opt-in format, then implements randomized
marker handling for all supported controllers and finally selects the
packed H6/H616 protected user-data layout.

Tested on an H616 board with a Toshiba TC58NVG1S3H NAND using matching
Linux and U-Boot implementations. Linux initialized the NAND, SPL and
U-Boot cold-booted from it, U-Boot loaded the FIT from UBI, and Linux
attached the same UBI with 2004 good PEBs, two bad PEBs and no corrupted
PEBs. A target-timed 64 MiB raw-MTD read completed without ECC, timeout
or I/O errors.

Validation completed with strict checkpatch, dt_binding_check and an
arm64 W=1 object build.

Assisted-by: OpenAI Codex (gpt-5.6-sol, max)
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
James Hilliard (3):
      dt-bindings: mtd: sunxi: Add randomized OOB flag
      mtd: rawnand: sunxi: support randomized OOB formats
      mtd: rawnand: sunxi: select the packed H6/H616 OOB layout

 .../bindings/mtd/allwinner,sun4i-a10-nand.yaml     |  9 +++++
 drivers/mtd/nand/raw/sunxi_nand.c                  | 46 ++++++++++++++++++----
 2 files changed, 47 insertions(+), 8 deletions(-)
---
base-commit: 15a3cbce32994141252bb4ecfe3ff3a5d22d0b4f
change-id: 20260810-submit-sunxi-nand-vendor-oob-layout-v1-e3114d10cc9c

Best regards,
--  
James Hilliard <james.hilliard1 at gmail.com>



From james.hilliard1 at gmail.com  Mon Aug 10 16:04:50 2026
From: james.hilliard1 at gmail.com (James Hilliard)
Date: Mon, 10 Aug 2026 17:04:50 -0600
Subject: [PATCH 2/3] mtd: rawnand: sunxi: support randomized OOB formats
In-Reply-To: <20260810-submit-sunxi-nand-vendor-oob-layout-v1-v1-0-463853a14ad9@gmail.com>
References: <20260810-submit-sunxi-nand-vendor-oob-layout-v1-v1-0-463853a14ad9@gmail.com>
Message-ID: <20260810-submit-sunxi-nand-vendor-oob-layout-v1-v1-2-463853a14ad9@gmail.com>

The controller randomizer covers the bad block marker along with the
rest of the OOB data. The driver currently compensates the marker bytes
before writes and after reads so that they remain plain on flash.

Allwinner NAND firmware instead stores the marker through the
randomizer. Media using that format appears to contain bad blocks unless
the randomizer is enabled while reading the marker.

Honor the allwinner,randomized-oob property by unconditionally enabling
page scrambling and leaving the marker in the randomizer data stream.
Keep the existing plain marker behavior when the property is absent.
This changes marker handling on all supported controllers; the H6/H616
protected user-data placement is handled separately.

Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
 drivers/mtd/nand/raw/sunxi_nand.c | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 45ccbce91551..92b26371a6d1 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -264,6 +264,7 @@ struct sunxi_nfc_timings {
  * @clk_rate: clk_rate required for this NAND chip
  * @timing_cfg: TIMING_CFG register value for this NAND chip
  * @timing_ctl: TIMING_CTL register value for this NAND chip
+ * @randomized_oob: use the randomized normal-page OOB format
  * @nsels: number of CS lines required by the NAND chip
  * @sels: array of CS lines descriptions
  * @user_data_bytes: array of user data lengths for all ECC steps
@@ -276,6 +277,7 @@ struct sunxi_nand_chip {
 	u32 timing_cfg;
 	u32 timing_ctl;
 	u8 *user_data_bytes;
+	bool randomized_oob;
 	int nsels;
 	struct sunxi_nand_chip_sel sels[] __counted_by(nsels);
 };
@@ -945,7 +947,8 @@ static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
 	}
 
 	/* De-randomize the Bad Block Marker. */
-	if (bbm && (nand->options & NAND_NEED_SCRAMBLING))
+	if (bbm && (nand->options & NAND_NEED_SCRAMBLING) &&
+	    !sunxi_nand->randomized_oob)
 		sunxi_nfc_randomize_bbm(nand, page, oob);
 }
 
@@ -1006,7 +1009,8 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
 	u8 *user_data = NULL;
 
 	/* Randomize the Bad Block Marker. */
-	if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) {
+	if (bbm && (nand->options & NAND_NEED_SCRAMBLING) &&
+	    !sunxi_nand->randomized_oob) {
 		user_data = kmalloc(user_data_sz, GFP_KERNEL);
 		memcpy(user_data, oob, user_data_sz);
 		sunxi_nfc_randomize_bbm(nand, page, user_data);
@@ -2219,6 +2223,7 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
 
 static int sunxi_nand_attach_chip(struct nand_chip *nand)
 {
+	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
 	const struct nand_ecc_props *requirements =
 		nanddev_get_ecc_requirements(&nand->base);
 	struct nand_ecc_ctrl *ecc = &nand->ecc;
@@ -2228,6 +2233,9 @@ static int sunxi_nand_attach_chip(struct nand_chip *nand)
 	if (nand->bbt_options & NAND_BBT_USE_FLASH)
 		nand->bbt_options |= NAND_BBT_NO_OOB;
 
+	if (sunxi_nand->randomized_oob)
+		nand->options |= NAND_NEED_SCRAMBLING;
+
 	if (nand->options & NAND_NEED_SCRAMBLING)
 		nand->options |= NAND_NO_SUBPAGE_WRITE;
 
@@ -2457,6 +2465,9 @@ static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc,
 	if (!sunxi_nand)
 		return -ENOMEM;
 
+	sunxi_nand->randomized_oob =
+		of_property_read_bool(np, "allwinner,randomized-oob");
+
 	sunxi_nand->nsels = nsels;
 
 	for (i = 0; i < nsels; i++) {

-- 
2.53.0



From james.hilliard1 at gmail.com  Mon Aug 10 16:04:49 2026
From: james.hilliard1 at gmail.com (James Hilliard)
Date: Mon, 10 Aug 2026 17:04:49 -0600
Subject: [PATCH 1/3] dt-bindings: mtd: sunxi: Add randomized OOB flag
In-Reply-To: <20260810-submit-sunxi-nand-vendor-oob-layout-v1-v1-0-463853a14ad9@gmail.com>
References: <20260810-submit-sunxi-nand-vendor-oob-layout-v1-v1-0-463853a14ad9@gmail.com>
Message-ID: <20260810-submit-sunxi-nand-vendor-oob-layout-v1-v1-1-463853a14ad9@gmail.com>

Allwinner NAND firmware stores the bad block marker through the
controller randomizer. The mainline driver instead compensates the first
two randomized bytes so that the marker remains plain on flash.

On H6 and H616, the firmware also uses a fixed protected user-data
placement while mainline fills the OOB space left after ECC. Add an
opt-in property for NAND using the randomized normal-page OOB format.

Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
 .../devicetree/bindings/mtd/allwinner,sun4i-a10-nand.yaml        | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/Documentation/devicetree/bindings/mtd/allwinner,sun4i-a10-nand.yaml b/Documentation/devicetree/bindings/mtd/allwinner,sun4i-a10-nand.yaml
index 9d061e2216cb..7eda1275135d 100644
--- a/Documentation/devicetree/bindings/mtd/allwinner,sun4i-a10-nand.yaml
+++ b/Documentation/devicetree/bindings/mtd/allwinner,sun4i-a10-nand.yaml
@@ -79,6 +79,15 @@ patternProperties:
           minimum: 0
           maximum: 1
 
+      allwinner,randomized-oob:
+        type: boolean
+        description:
+          Use the Allwinner randomized normal-page OOB format. This enables
+          the controller page randomizer, stores the bad block marker through
+          it instead of compensating the marker so that it remains plain on
+          flash, and selects the controller-specific protected user-data
+          placement used by Allwinner NAND firmware.
+
     unevaluatedProperties: false
 
 required:

-- 
2.53.0



From james.hilliard1 at gmail.com  Mon Aug 10 16:04:51 2026
From: james.hilliard1 at gmail.com (James Hilliard)
Date: Mon, 10 Aug 2026 17:04:51 -0600
Subject: [PATCH 3/3] mtd: rawnand: sunxi: select the packed H6/H616 OOB
 layout
In-Reply-To: <20260810-submit-sunxi-nand-vendor-oob-layout-v1-v1-0-463853a14ad9@gmail.com>
References: <20260810-submit-sunxi-nand-vendor-oob-layout-v1-v1-0-463853a14ad9@gmail.com>
Message-ID: <20260810-submit-sunxi-nand-vendor-oob-layout-v1-v1-3-463853a14ad9@gmail.com>

The H6/H616 controller can configure a separate protected user-data
length for every ECC step. Mainline fills all space left after ECC with
user data. This changes the physical ECC offsets from those used by
Allwinner NAND firmware, so pages written by one layout cannot be
decoded with the other.

The allwinner,randomized-oob property selects the randomized normal-page
OOB format. When it is present on H6/H616, use four protected user-data
bytes per 1 KiB ECC step, cap the total at 16 bytes, and assign the
entire total to ECC step zero. Reserve the same total before maximizing
ECC strength so the selected strength leaves enough room.

Keep the existing maximized mainline user-data layout when the property
is absent. Older controllers have fixed four-byte user-data registers
and need no additional placement change.

Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
 drivers/mtd/nand/raw/sunxi_nand.c | 31 +++++++++++++++++++++++++------
 1 file changed, 25 insertions(+), 6 deletions(-)

diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 92b26371a6d1..2388cc361685 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -223,6 +223,9 @@
  */
 #define USER_DATA_SZ 4
 
+/* The randomized H6/H616 layout packs at most 16 bytes before ECC step 0. */
+#define SUNXI_NFC_H6_MAX_USER_DATA_SZ 16
+
 /**
  * struct sunxi_nand_chip_sel - stores information related to NAND Chip Select
  *
@@ -2036,8 +2039,14 @@ static void sunxi_nand_detach_chip(struct nand_chip *nand)
 	sunxi_nand->user_data_bytes = NULL;
 }
 
-static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize,
-					int ecc_bytes, int nsectors)
+static unsigned int sunxi_nfc_h6_user_data_sz(int nsectors)
+{
+	return min(nsectors * USER_DATA_SZ,
+		   SUNXI_NFC_H6_MAX_USER_DATA_SZ);
+}
+
+static int sunxi_nfc_init_user_data(struct nand_chip *nand, uint32_t oobsize,
+				    int ecc_bytes, int nsectors)
 {
 	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
 	struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
@@ -2050,6 +2059,12 @@ static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize
 	if (!sunxi_nand->user_data_bytes)
 		return -ENOMEM;
 
+	if (sunxi_nand->randomized_oob) {
+		sunxi_nand->user_data_bytes[0] =
+			sunxi_nfc_h6_user_data_sz(nsectors);
+		return 0;
+	}
+
 	for (step = 0; (step < nsectors) && (remaining_bytes > 0); step++) {
 		for (i = 0; i < c->nuser_data_tab; i++) {
 			if (c->user_data_len_tab[i] > remaining_bytes)
@@ -2104,6 +2119,10 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
 				bytes -= 2;
 
 			bytes -= total_user_data_sz;
+		} else if (sunxi_nand->randomized_oob) {
+			total_user_data_sz =
+				sunxi_nfc_h6_user_data_sz(nsectors);
+			bytes -= total_user_data_sz;
 		} else {
 			/*
 			 * remove at least the BBM size before computing the
@@ -2170,12 +2189,12 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
 	nsectors = mtd->writesize / ecc->size;
 
 	/*
-	 * The rationale for variable data length is to prioritize maximum ECC
-	 * strength, and then use the remaining space for user data.
+	 * The default variable-length layout prioritizes maximum ECC strength,
+	 * then uses the remaining space for user data.
 	 */
 	if (nfc->caps->reg_user_data_len)
-		sunxi_nfc_maximize_user_data(nand, mtd->oobsize, ecc->bytes,
-					     nsectors);
+		sunxi_nfc_init_user_data(nand, mtd->oobsize, ecc->bytes,
+					 nsectors);
 
 	if (total_user_data_sz == 0)
 		for (i = 0; i < nsectors; i++)

-- 
2.53.0



From xiaopei01 at kylinos.cn  Mon Aug 10 18:21:49 2026
From: xiaopei01 at kylinos.cn (Pei Xiao)
Date: Tue, 11 Aug 2026 09:21:49 +0800
Subject: [syzbot] [mtd?] divide error in block2mtd_setup2
In-Reply-To: <ann719YIqopUBCv5@cashel.logfs.org>
References: <6a791c94.01d0871a.3a0d52.009b.GAE@google.com>
 <ankxaiwtTltL0xMj@cashel.logfs.org> <ann719YIqopUBCv5@cashel.logfs.org>
Message-ID: <11afb109-ddcb-4481-b4b2-9d9dca7f31a9@kylinos.cn>



? 2026/8/11 00:27, J?rn Engel ??:
> On Sun, Aug 09, 2026 at 07:03:22PM -0700, J?rn Engel wrote:
>>
>> Best guess would be this:
>>
>> 	if ((long)size % erase_size) {
> 
> While at it, what purpose does the cast to (long) serve?
> 
> As far as I can see, it is useless and should be removed.  Fabian, do
> you remember why it was necessary?
The type of size is loff_t (i.e., long long, which is always 64-bit).
Casting it to (long)size on a 32-bit system would truncate it to a
32-bit long, so it seems this long cast needs to be removed.
It's been 12 years.. I wonder if the author still remembers.

Pei.
> 
> J?rn
> 
> --
> The real voyage of discovery consists not in seeking new landscapes,
> but in having new eyes.
> -- Marcel Proust
> 



From linchengming884 at gmail.com  Mon Aug 10 18:22:38 2026
From: linchengming884 at gmail.com (Cheng Ming Lin)
Date: Tue, 11 Aug 2026 09:22:38 +0800
Subject: [PATCH v3 0/2] mtd: spi-nor: macronix: Add support for MX25L12833F and MX25L12845G
In-Reply-To: <20260605084837.1875896-1-linchengming884@gmail.com>
References: <20260605084837.1875896-1-linchengming884@gmail.com>
Message-ID: <20260811012238.961754-1-linchengming884@gmail.com>

Hi Michael,

Gentle ping on this series, which has been on the list since June 5
without review feedback.

v3 addresses the comments from v1 and v2: the fixup now uses
->post_sfdp() with a comment explaining the ID collision, and the build
error in v2 is fixed. Patch 2/2 is an independent regression fix
(Fixes: 947c86e481a0, Cc: stable) for the legacy MX25L12805D, which
currently fails to probe. The series still applies cleanly on master.

Could you take a look when you get a chance?

Thanks,
Cheng Ming Lin


From xiaopei01 at kylinos.cn  Mon Aug 10 20:02:53 2026
From: xiaopei01 at kylinos.cn (Pei Xiao)
Date: Tue, 11 Aug 2026 11:02:53 +0800
Subject: [syzbot] [mtd?] divide error in block2mtd_setup2
In-Reply-To: <87se4mqhc3.fsf@bootlin.com>
References: <6a791c94.01d0871a.3a0d52.009b.GAE@google.com>
 <ankxaiwtTltL0xMj@cashel.logfs.org>
 <29397db9-7db4-4969-bb0e-5bb71f0c0303@kylinos.cn>
 <87se4mqhc3.fsf@bootlin.com>
Message-ID: <bbbff0ba-09c4-49dc-9aac-371c0c7d6902@kylinos.cn>



? 2026/8/10 20:40, Miquel Raynal ??:
> Hi Pei,
> 
>>> Best guess would be this:
>>>
>>> 	if ((long)size % erase_size) {
>>>
>>> Someone passing an erase_size of 0 would indeed trigger an exception.
>>> Fix would be something like this:
>>>
>>> -	if ((long)size % erase_size) {
>>> +	if (!erase_size || (long)size % erase_size) {
>>>
>>> I haven't touched the code is nearly two decades.  If somebody else
>>> feels like sending a patch and gathering all the glory, please be my
>>> guest!
>> Thank you for your contribution. I have sent this patch, but I forgot to
>> add the Suggested-by: tag
> 
> You are invited to send a v2 with the said tag :)
Thank you for your reply. I am waiting for a reply on whether the cast
on 'long' needs to be removed. Once that is confirmed, I would be happy
to do it. Thank you.

Pei.
> 
> Thanks,
> Miqu?l



From runyu.xiao at seu.edu.cn  Mon Aug 10 21:13:49 2026
From: runyu.xiao at seu.edu.cn (Runyu Xiao)
Date: Tue, 11 Aug 2026 12:13:49 +0800
Subject: [PATCH v2] mtd: spi-nor: scope the exclusive RWW lock
In-Reply-To: <DKL4CBQNKCO2.3NAZ6S25Q60XN@kernel.org>
References: <DKL4CBQNKCO2.3NAZ6S25Q60XN@kernel.org>
Message-ID: <20260811041349.333176-1-runyu.xiao@seu.edu.cn>

spi_nor_rww_start_exclusive() is used as the wait_event_killable()
condition for RWW access. When RWW is busy, it can return false while
still holding nor->lock, which blocks spi_nor_rww_end_exclusive() from
clearing the RWW state.

Use guard(mutex) here so nor->lock is released before the condition
returns, while the state flags still hand off ownership to the caller.
This keeps the mutex scope aligned with the other RWW start helpers.

Fixes: 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup helpers")
Cc: stable at vger.kernel.org
Signed-off-by: Runyu Xiao <runyu.xiao at seu.edu.cn>
---
Changes in v2:
- Replace the analysis-style explanation with a short maintainer-facing
  description of the busy-path lock hold.
- Keep the fix focused on using guard(mutex) so the mutex is released on
  both paths.

drivers/mtd/spi-nor/core.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ccf4396cdcd0..8bc117b46e02 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -1310,7 +1310,7 @@ static bool spi_nor_rww_start_exclusive(struct spi_nor *nor)
 {
 	struct spi_nor_rww *rww = &nor->rww;
 
-	mutex_lock(&nor->lock);
+	guard(mutex)(&nor->lock);
 
 	if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe)
 		return false;
-- 
2.34.1


From james.hilliard1 at gmail.com  Mon Aug 10 23:01:58 2026
From: james.hilliard1 at gmail.com (James Hilliard)
Date: Tue, 11 Aug 2026 00:01:58 -0600
Subject: [PATCH 0/3] mtd: rawnand: sunxi: harden protected OOB handling
Message-ID: <20260811-sunxi-nand-protected-oob-fixes-v1-0-412e50444673@gmail.com>

The H6/H616 variable protected-OOB implementation has three related
failure cases around its per-step user-data lengths.

First, propagate failures while allocating the per-step length array.
Then reserve an encodable four-byte first section for the bad block
marker and reject layouts which cannot provide it. Finally, replace the
per-write heap allocation used for BBM compensation with the
controller's bounded 32-byte stack buffer.

These fixes keep the calculated, advertised and programmed OOB layouts
consistent and prevent zero-sized sections from reaching the BBM path.

Assisted-by: OpenAI Codex (gpt-5.6-sol, max)
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
James Hilliard (3):
      mtd: rawnand: sunxi: propagate user-data allocation errors
      mtd: rawnand: sunxi: reserve a full user-data word for BBM
      mtd: rawnand: sunxi: use a stack buffer for BBM randomization

 drivers/mtd/nand/raw/sunxi_nand.c | 32 +++++++++++++++++++++-----------
 1 file changed, 21 insertions(+), 11 deletions(-)
---
base-commit: 15a3cbce32994141252bb4ecfe3ff3a5d22d0b4f
change-id: 20260810-sunxi-nand-protected-oob-fixes-15db4368a8ae

Best regards,
--  
James Hilliard <james.hilliard1 at gmail.com>



From james.hilliard1 at gmail.com  Mon Aug 10 23:01:59 2026
From: james.hilliard1 at gmail.com (James Hilliard)
Date: Tue, 11 Aug 2026 00:01:59 -0600
Subject: [PATCH 1/3] mtd: rawnand: sunxi: propagate user-data allocation
 errors
In-Reply-To: <20260811-sunxi-nand-protected-oob-fixes-v1-0-412e50444673@gmail.com>
References: <20260811-sunxi-nand-protected-oob-fixes-v1-0-412e50444673@gmail.com>
Message-ID: <20260811-sunxi-nand-protected-oob-fixes-v1-1-412e50444673@gmail.com>

sunxi_nfc_maximize_user_data() returns -ENOMEM when its per-step
length array cannot be allocated, but the caller ignores that error.
The NULL array then makes sunxi_nfc_user_data_sz() report the fixed
four-byte fallback, which is not the layout that the variable-length
setup calculated.

Return the setup error so an allocation failure cannot silently select
and validate a different OOB layout.

Fixes: 54dcd6aa69db ("mtd: rawnand: sunxi: introduce maximize variable user data length")
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
 drivers/mtd/nand/raw/sunxi_nand.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 45ccbce91551..108161fe10cb 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -2072,6 +2072,7 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
 	int total_user_data_sz = 0;
 	int nsectors;
 	int ecc_mode;
+	int ret;
 	int i;
 
 	if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) {
@@ -2169,9 +2170,12 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
 	 * The rationale for variable data length is to prioritize maximum ECC
 	 * strength, and then use the remaining space for user data.
 	 */
-	if (nfc->caps->reg_user_data_len)
-		sunxi_nfc_maximize_user_data(nand, mtd->oobsize, ecc->bytes,
-					     nsectors);
+	if (nfc->caps->reg_user_data_len) {
+		ret = sunxi_nfc_maximize_user_data(nand, mtd->oobsize,
+						   ecc->bytes, nsectors);
+		if (ret)
+			return ret;
+	}
 
 	if (total_user_data_sz == 0)
 		for (i = 0; i < nsectors; i++)

-- 
2.53.0



From james.hilliard1 at gmail.com  Mon Aug 10 23:02:00 2026
From: james.hilliard1 at gmail.com (James Hilliard)
Date: Tue, 11 Aug 2026 00:02:00 -0600
Subject: [PATCH 2/3] mtd: rawnand: sunxi: reserve a full user-data word for
 BBM
In-Reply-To: <20260811-sunxi-nand-protected-oob-fixes-v1-0-412e50444673@gmail.com>
References: <20260811-sunxi-nand-protected-oob-fixes-v1-0-412e50444673@gmail.com>
Message-ID: <20260811-sunxi-nand-protected-oob-fixes-v1-2-412e50444673@gmail.com>

H6/H616 protected user-data lengths are encoded in four-byte units,
but ECC maximization reserves only the two bad block marker bytes. A
strength leaving fewer than four bytes therefore selects a zero-byte
first user-data section.

The OOB layout then subtracts the two marker bytes from that unsigned
zero length, and the controller has no protected user-data word in which
to store the marker.

Reserve one complete user-data word while maximizing ECC, reject
configurations which produce no ECC sectors or a shorter first section,
and make the OOB iterator bounds-safe.

Fixes: 54dcd6aa69db ("mtd: rawnand: sunxi: introduce maximize variable user data length")
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
 drivers/mtd/nand/raw/sunxi_nand.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 108161fe10cb..01a0d0fa7b62 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -1991,7 +1991,7 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section,
 	struct nand_chip *nand = mtd_to_nand(mtd);
 	struct nand_ecc_ctrl *ecc = &nand->ecc;
 	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
-	unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, section);
+	unsigned int user_data_sz;
 
 	/*
 	 * The controller does not provide access to OOB bytes
@@ -2000,6 +2000,8 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section,
 	if (section >= ecc->steps)
 		return -ERANGE;
 
+	user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, section);
+
 	/*
 	 * The first 2 bytes are used for BB markers, hence we
 	 * only have user_data_sz - 2 bytes available in the first user data
@@ -2007,7 +2009,7 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section,
 	 */
 	if (section == 0) {
 		oobregion->offset = 2;
-		oobregion->length = user_data_sz - 2;
+		oobregion->length = user_data_sz > 2 ? user_data_sz - 2 : 0;
 
 		return 0;
 	}
@@ -2041,6 +2043,9 @@ static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize
 	int remaining_bytes = oobsize - (ecc_bytes * nsectors);
 	int i, step;
 
+	if (nsectors <= 0)
+		return -EINVAL;
+
 	sunxi_nand->user_data_bytes = devm_kzalloc(nfc->dev, nsectors,
 						   GFP_KERNEL);
 	if (!sunxi_nand->user_data_bytes)
@@ -2056,6 +2061,8 @@ static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize
 		if (sunxi_nand->user_data_bytes[step] == 0)
 			break;
 	}
+	if (sunxi_nand->user_data_bytes[0] < USER_DATA_SZ)
+		return -EINVAL;
 
 	return 0;
 }
@@ -2103,10 +2110,10 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
 			bytes -= total_user_data_sz;
 		} else {
 			/*
-			 * remove at least the BBM size before computing the
-			 * max ECC
+			 * User-data lengths are encoded in four-byte units. Reserve
+			 * the first word because it contains the two BBM bytes.
 			 */
-			bytes -= 2;
+			bytes -= USER_DATA_SZ;
 		}
 
 		/*

-- 
2.53.0



From james.hilliard1 at gmail.com  Mon Aug 10 23:02:01 2026
From: james.hilliard1 at gmail.com (James Hilliard)
Date: Tue, 11 Aug 2026 00:02:01 -0600
Subject: [PATCH 3/3] mtd: rawnand: sunxi: use a stack buffer for BBM
 randomization
In-Reply-To: <20260811-sunxi-nand-protected-oob-fixes-v1-0-412e50444673@gmail.com>
References: <20260811-sunxi-nand-protected-oob-fixes-v1-0-412e50444673@gmail.com>
Message-ID: <20260811-sunxi-nand-protected-oob-fixes-v1-3-412e50444673@gmail.com>

Variable protected user-data lengths replaced the original four-byte
stack buffer with a heap allocation before compensating the bad block
marker. The allocation is unchecked, and a zero length returns
ZERO_SIZE_PTR which sunxi_nfc_randomize_bbm() dereferences.

The controller supports at most 32 protected user-data bytes per ECC
step, so use a zero-initialized buffer of that bounded size. This
removes the allocation failure path and keeps zero-sized invalid layouts
from dereferencing ZERO_SIZE_PTR.

Fixes: 54dcd6aa69db ("mtd: rawnand: sunxi: introduce maximize variable user data length")
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
 drivers/mtd/nand/raw/sunxi_nand.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 01a0d0fa7b62..83666dd6cb2a 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -222,6 +222,7 @@
  * USER_DATA_LEN registers.
  */
 #define USER_DATA_SZ 4
+#define SUNXI_NFC_MAX_USER_DATA_SZ 32
 
 /**
  * struct sunxi_nand_chip_sel - stores information related to NAND Chip Select
@@ -1003,11 +1004,10 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
 	struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
 	struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
 	unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
-	u8 *user_data = NULL;
+	u8 user_data[SUNXI_NFC_MAX_USER_DATA_SZ] = {};
 
 	/* Randomize the Bad Block Marker. */
 	if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) {
-		user_data = kmalloc(user_data_sz, GFP_KERNEL);
 		memcpy(user_data, oob, user_data_sz);
 		sunxi_nfc_randomize_bbm(nand, page, user_data);
 		oob = user_data;
@@ -1040,7 +1040,6 @@ static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
 		}
 	}
 
-	kfree(user_data);
 }
 
 static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,

-- 
2.53.0



From james.hilliard1 at gmail.com  Mon Aug 10 23:06:48 2026
From: james.hilliard1 at gmail.com (James Hilliard)
Date: Tue, 11 Aug 2026 00:06:48 -0600
Subject: [PATCH] mtd: rawnand: sunxi: reject invalid ECC step sizes
Message-ID: <20260811060651.227431-1-james.hilliard1@gmail.com>

ECC maximization forces a 1024-byte ECC step and divides the
available OOB bytes by the number of steps. A NAND with a smaller
page therefore produces zero steps and a division by zero.

An explicitly configured ECC step which is larger than, or does not
divide, the page also produces an unusable step count before the NAND
core can diagnose the configuration.

Validate the step size at both points where the driver derives the
number of sectors and reject invalid geometries.

Fixes: 4796d8655915 ("mtd: nand: sunxi: Support ECC maximization")
Cc: stable at vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
 drivers/mtd/nand/raw/sunxi_nand.c | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index 45ccbce91551..ad314c0e30b2 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -2032,6 +2032,15 @@ static void sunxi_nand_detach_chip(struct nand_chip *nand)
 	sunxi_nand->user_data_bytes = NULL;
 }
 
+static int sunxi_nfc_ecc_steps(struct mtd_info *mtd, unsigned int step_size)
+{
+	if (!step_size || mtd->writesize < step_size ||
+	    mtd->writesize % step_size)
+		return -EINVAL;
+
+	return mtd->writesize / step_size;
+}
+
 static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize,
 					int ecc_bytes, int nsectors)
 {
@@ -2078,7 +2087,9 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
 		int bytes = mtd->oobsize;
 
 		ecc->size = 1024;
-		nsectors = mtd->writesize / ecc->size;
+		nsectors = sunxi_nfc_ecc_steps(mtd, ecc->size);
+		if (nsectors < 0)
+			return nsectors;
 
 		if (!nfc->caps->reg_user_data_len) {
 			/*
@@ -2163,7 +2174,9 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
 	/* HW ECC always work with even numbers of ECC bytes */
 	ecc->bytes = ALIGN(ecc->bytes, 2);
 
-	nsectors = mtd->writesize / ecc->size;
+	nsectors = sunxi_nfc_ecc_steps(mtd, ecc->size);
+	if (nsectors < 0)
+		return nsectors;
 
 	/*
 	 * The rationale for variable data length is to prioritize maximum ECC
-- 
2.53.0



From mwalle at kernel.org  Mon Aug 10 23:31:28 2026
From: mwalle at kernel.org (Michael Walle)
Date: Tue, 11 Aug 2026 08:31:28 +0200
Subject: [PATCH v3 2/2] mtd: spi-nor: macronix: Restore fallback
 parameters for MX25L12805D
In-Reply-To: <20260605084837.1875896-3-linchengming884@gmail.com>
References: <20260605084837.1875896-1-linchengming884@gmail.com>
 <20260605084837.1875896-3-linchengming884@gmail.com>
Message-ID: <DKLWJLDFA3QD.DW07KLCA2H96@kernel.org>

On Fri Jun 5, 2026 at 10:48 AM CEST, Cheng Ming Lin wrote:
> From: Cheng Ming Lin <chengminglin at mxic.com.tw>
>
> In a previous effort to drop flash_info fields and rely on SFDP, the
> static size and no_sfdp_flags were removed from the MX25L12805D entry
> (JEDEC ID 0xc22018).
>
> At that time, the legacy MX25L12805D was already EOL and unavailable
> for physical testing. Verification was inadvertently performed using
> the newer MX25L12833F, which shares the same JEDEC ID but supports
> SFDP. As a result, the probe succeeded during testing, leading to
> the mistaken removal of the fallback parameters.
>
> Since the actual MX25L12805D lacks SFDP support entirely, it strictly
> requires these static parameters.
>
> Restore .size = SZ_16M and .no_sfdp_flags = SECT_4K to this entry
> to fix the probe failure for the legacy part.
>
> Fixes: 947c86e481a0 ("mtd: spi-nor: macronix: Drop the redundant flash info fields")
> Cc: stable at vger.kernel.org
> Signed-off-by: Cheng Ming Lin <chengminglin at mxic.com.tw>

Reviewed-by: Michael Walle <mwalle at kernel.org>


From mwalle at kernel.org  Mon Aug 10 23:31:18 2026
From: mwalle at kernel.org (Michael Walle)
Date: Tue, 11 Aug 2026 08:31:18 +0200
Subject: [PATCH v3 1/2] mtd: spi-nor: Add support for MX25L12833F and
 MX25L12845G
In-Reply-To: <20260605084837.1875896-2-linchengming884@gmail.com>
References: <20260605084837.1875896-1-linchengming884@gmail.com>
 <20260605084837.1875896-2-linchengming884@gmail.com>
Message-ID: <DKLWJGG9XFFT.26IGJEZPPYFGX@kernel.org>

On Fri Jun 5, 2026 at 10:48 AM CEST, Cheng Ming Lin wrote:
> From: Cheng Ming Lin <chengminglin at mxic.com.tw>
>
> Add support for Macronix MX25L12833F and MX25L12845G SPI NOR flashes.
> These parts share the same JEDEC ID (0xc2, 0x20, 0x18) as the legacy
> MX25L12805D.
>
> The newer flashes support SFDP and 1-4-4 Page Program in 3-byte address
> mode, but this 4PP capability is not defined in their SFDP tables.
> Conversely, the legacy MX25L12805D lacks SFDP support entirely and does
> not support 4PP.
>
> To safely enable 4PP for the newer flashes without breaking the legacy
> part, introduce a post_sfdp fixup. Since the legacy MX25L12805D does
> not support SFDP, it falls back to static parameters and will never
> execute the post_sfdp hook. The newer flashes will successfully parse
> the SFDP, trigger the hook, and safely append the SNOR_HWCAPS_PP_1_4_4
> capability.
>
> Signed-off-by: Cheng Ming Lin <chengminglin at mxic.com.tw>

Nice!

Reviewed-by: Michael Walle <mwalle at kernel.org>


From mwalle at kernel.org  Mon Aug 10 23:31:48 2026
From: mwalle at kernel.org (Michael Walle)
Date: Tue, 11 Aug 2026 08:31:48 +0200
Subject: [PATCH v3 0/2] mtd: spi-nor: macronix: Add support for
 MX25L12833F and MX25L12845G
In-Reply-To: <20260811012238.961754-1-linchengming884@gmail.com>
References: <20260605084837.1875896-1-linchengming884@gmail.com>
 <20260811012238.961754-1-linchengming884@gmail.com>
Message-ID: <DKLWJUH5UFTD.2WLKWBOCB56HS@kernel.org>

Hi,

On Tue Aug 11, 2026 at 3:22 AM CEST, Cheng Ming Lin wrote:
> Gentle ping on this series, which has been on the list since June 5
> without review feedback.
>
> v3 addresses the comments from v1 and v2: the fixup now uses
> ->post_sfdp() with a comment explaining the ID collision, and the build
> error in v2 is fixed. Patch 2/2 is an independent regression fix
> (Fixes: 947c86e481a0, Cc: stable) for the legacy MX25L12805D, which
> currently fails to probe. The series still applies cleanly on master.
>
> Could you take a look when you get a chance?

Sorry that fell through the cracks.

-michael


From mwalle at kernel.org  Mon Aug 10 23:44:44 2026
From: mwalle at kernel.org (Michael Walle)
Date: Tue, 11 Aug 2026 08:44:44 +0200
Subject: [PATCH] mtd: spi-nor: xmc: add locking to XM25QH128A
Message-ID: <20260811064512.42520-1-mwalle@kernel.org>

Add support for locking to the XM25QH128A part.

Reported-by: Takino <stayer.opt1f at icloud.com>
Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221825
Signed-off-by: Michael Walle <mwalle at kernel.org>
---
Hi Takino,

please test it and reply with a "Tested-by" tag. With your full name if
you like. I didn't get that from the bug report.
---
 drivers/mtd/spi-nor/xmc.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/mtd/spi-nor/xmc.c b/drivers/mtd/spi-nor/xmc.c
index d5a06054b0dd..d3b52eb93e2c 100644
--- a/drivers/mtd/spi-nor/xmc.c
+++ b/drivers/mtd/spi-nor/xmc.c
@@ -19,6 +19,7 @@ static const struct flash_info xmc_nor_parts[] = {
 		.name = "XM25QH128A",
 		.size = SZ_16M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_4BIT_BP | SPI_NOR_HAS_TB,
 	},
 };
 
-- 
2.47.3



From miquel.raynal at bootlin.com  Tue Aug 11 00:51:52 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 11 Aug 2026 09:51:52 +0200
Subject: [PATCH v2] mtd: spi-nor: scope the exclusive RWW lock
In-Reply-To: <20260811041349.333176-1-runyu.xiao@seu.edu.cn> (Runyu Xiao's
	message of "Tue, 11 Aug 2026 12:13:49 +0800")
References: <DKL4CBQNKCO2.3NAZ6S25Q60XN@kernel.org>
	<20260811041349.333176-1-runyu.xiao@seu.edu.cn>
Message-ID: <87tsp1p013.fsf@bootlin.com>

On 11/08/2026 at 12:13:49 +08, Runyu Xiao <runyu.xiao at seu.edu.cn> wrote:

> spi_nor_rww_start_exclusive() is used as the wait_event_killable()
> condition for RWW access. When RWW is busy, it can return false while
> still holding nor->lock, which blocks spi_nor_rww_end_exclusive() from
> clearing the RWW state.
>
> Use guard(mutex) here so nor->lock is released before the condition
> returns, while the state flags still hand off ownership to the caller.
> This keeps the mutex scope aligned with the other RWW start helpers.

The diff is now correct, but the commit log is still missing the
point. There has been a translation from raw mutexes into scoped
helpers, this translation missed one spot. This should be the core of
your commit log.

Miqu?l


From runyu.xiao at seu.edu.cn  Tue Aug 11 01:31:11 2026
From: runyu.xiao at seu.edu.cn (Runyu Xiao)
Date: Tue, 11 Aug 2026 16:31:11 +0800
Subject: [PATCH v3] mtd: spi-nor: use guard() in spi_nor_rww_start_exclusive
In-Reply-To: <20260811041349.333176-1-runyu.xiao@seu.edu.cn>
References: <20260811041349.333176-1-runyu.xiao@seu.edu.cn>
Message-ID: <20260811083111.403453-1-runyu.xiao@seu.edu.cn>

Commit 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup
helpers") converted the RWW start helpers to scoped mutex cleanup, but
left spi_nor_rww_start_exclusive() using a plain mutex_lock().

spi_nor_rww_start_exclusive() is used as a wait_event_killable()
condition. When it returns false on the busy path, the raw mutex_lock()
leaves nor->lock held and blocks the operation that must clear the RWW
state in spi_nor_rww_end_exclusive().

Use guard(mutex) here as well so this helper matches the other RWW
start helpers and releases nor->lock on the false return path.

Fixes: 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup helpers")
Cc: stable at vger.kernel.org
Signed-off-by: Runyu Xiao <runyu.xiao at seu.edu.cn>
---
Changes in v3:
- Reframe the changelog around the scoped-helper conversion that missed
  spi_nor_rww_start_exclusive().

Changes in v2:
- Replace the analysis-style explanation with a short maintainer-facing
  description of the busy-path lock hold.
- Keep the fix focused on using guard(mutex) so the mutex is released on
  both paths.

drivers/mtd/spi-nor/core.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ccf4396cdcd0..8bc117b46e02 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -1310,7 +1310,7 @@ static bool spi_nor_rww_start_exclusive(struct spi_nor *nor)
 {
 	struct spi_nor_rww *rww = &nor->rww;
 
-	mutex_lock(&nor->lock);
+	guard(mutex)(&nor->lock);
 
 	if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe)
 		return false;
-- 
2.34.1


From miquel.raynal at bootlin.com  Tue Aug 11 01:41:15 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 11 Aug 2026 10:41:15 +0200
Subject: [PATCH v3] mtd: spi-nor: use guard() in
 spi_nor_rww_start_exclusive
In-Reply-To: <20260811083111.403453-1-runyu.xiao@seu.edu.cn> (Runyu Xiao's
	message of "Tue, 11 Aug 2026 16:31:11 +0800")
References: <20260811041349.333176-1-runyu.xiao@seu.edu.cn>
	<20260811083111.403453-1-runyu.xiao@seu.edu.cn>
Message-ID: <877blxoxqs.fsf@bootlin.com>

On 11/08/2026 at 16:31:11 +08, Runyu Xiao <runyu.xiao at seu.edu.cn> wrote:

> Commit 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup
> helpers") converted the RWW start helpers to scoped mutex cleanup, but
> left spi_nor_rww_start_exclusive() using a plain mutex_lock().
>
> spi_nor_rww_start_exclusive() is used as a wait_event_killable()
> condition. When it returns false on the busy path, the raw mutex_lock()
> leaves nor->lock held and blocks the operation that must clear the RWW
> state in spi_nor_rww_end_exclusive().
>
> Use guard(mutex) here as well so this helper matches the other RWW
> start helpers and releases nor->lock on the false return path.
>
> Fixes: 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup helpers")
> Cc: stable at vger.kernel.org
> Signed-off-by: Runyu Xiao <runyu.xiao at seu.edu.cn>

Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>


From mwalle at kernel.org  Tue Aug 11 01:50:03 2026
From: mwalle at kernel.org (Michael Walle)
Date: Tue, 11 Aug 2026 10:50:03 +0200
Subject: [PATCH v2] mtd: spi-nor: scope the exclusive RWW lock
In-Reply-To: <20260811041349.333176-1-runyu.xiao@seu.edu.cn>
References: <DKL4CBQNKCO2.3NAZ6S25Q60XN@kernel.org>
 <20260811041349.333176-1-runyu.xiao@seu.edu.cn>
Message-ID: <DKLZHP07AA40.X5MMIPHUZEVX@kernel.org>

Hi,

On Tue Aug 11, 2026 at 6:13 AM CEST, Runyu Xiao wrote:
> spi_nor_rww_start_exclusive() is used as the wait_event_killable()
> condition for RWW access. When RWW is busy, it can return false while
> still holding nor->lock, which blocks spi_nor_rww_end_exclusive() from
> clearing the RWW state.
>
> Use guard(mutex) here so nor->lock is released before the condition
> returns, while the state flags still hand off ownership to the caller.
> This keeps the mutex scope aligned with the other RWW start helpers.
>
> Fixes: 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup helpers")
> Cc: stable at vger.kernel.org
> Signed-off-by: Runyu Xiao <runyu.xiao at seu.edu.cn>

There were questions in v1. Please answer them. Don't just ignore
them and just throw a new version of your patch at us. We put time
and effort into maintaining. See also [1].

-michael

[1] https://www.kernel.org/doc/html/latest/process/submitting-patches.html


From tudor.ambarus at linaro.org  Tue Aug 11 03:05:52 2026
From: tudor.ambarus at linaro.org (Tudor Ambarus)
Date: Tue, 11 Aug 2026 13:05:52 +0300
Subject: [PATCH] mtd: spi-nor: scope the exclusive RWW lock
In-Reply-To: <DKL4CBQNKCO2.3NAZ6S25Q60XN@kernel.org>
References: <20260809084223.3596259-1-runyu.xiao@seu.edu.cn>
 <DKL4CBQNKCO2.3NAZ6S25Q60XN@kernel.org>
Message-ID: <6e208a8b-da11-4ba1-adfc-f1e80c891310@linaro.org>



On 8/10/26 11:25 AM, Michael Walle wrote:
> Tudor, do
> you know why you've omitted the guard() in spi_nor_rww_start_exclusive()?

because using guard changes the behavior of the code.


From tudor.ambarus at linaro.org  Tue Aug 11 03:11:41 2026
From: tudor.ambarus at linaro.org (Tudor Ambarus)
Date: Tue, 11 Aug 2026 13:11:41 +0300
Subject: [PATCH v3] mtd: spi-nor: use guard() in
 spi_nor_rww_start_exclusive
In-Reply-To: <20260811083111.403453-1-runyu.xiao@seu.edu.cn>
References: <20260811041349.333176-1-runyu.xiao@seu.edu.cn>
 <20260811083111.403453-1-runyu.xiao@seu.edu.cn>
Message-ID: <0ff8afdc-7a11-4a6c-9f62-6f8ba712c4ac@linaro.org>



On 8/11/26 11:31 AM, Runyu Xiao wrote:
> Commit 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup
> helpers") converted the RWW start helpers to scoped mutex cleanup, but
> left spi_nor_rww_start_exclusive() using a plain mutex_lock().
> 
> spi_nor_rww_start_exclusive() is used as a wait_event_killable()
> condition. When it returns false on the busy path, the raw mutex_lock()
> leaves nor->lock held and blocks the operation that must clear the RWW
> state in spi_nor_rww_end_exclusive().
> 
> Use guard(mutex) here as well so this helper matches the other RWW
> start helpers and releases nor->lock on the false return path.
> 
> Fixes: 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup helpers")

The fixes tag is wrong. The blamed commit did not change the functionality
of the code, while yours does.

You shall instead blame the commit that failed to release the lock in the
first place. But then if you want to have this backported to stable kernels
you may want to add 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex
cleanup helpers") as a prerequisite patch, as it includes <linux/cleanup.h>.

Cheers,
ta

> Cc: stable at vger.kernel.org
> Signed-off-by: Runyu Xiao <runyu.xiao at seu.edu.cn>
> ---
> Changes in v3:
> - Reframe the changelog around the scoped-helper conversion that missed
>   spi_nor_rww_start_exclusive().
> 
> Changes in v2:
> - Replace the analysis-style explanation with a short maintainer-facing
>   description of the busy-path lock hold.
> - Keep the fix focused on using guard(mutex) so the mutex is released on
>   both paths.
> 
> drivers/mtd/spi-nor/core.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index ccf4396cdcd0..8bc117b46e02 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
> @@ -1310,7 +1310,7 @@ static bool spi_nor_rww_start_exclusive(struct spi_nor *nor)
>  {
>  	struct spi_nor_rww *rww = &nor->rww;
>  
> -	mutex_lock(&nor->lock);
> +	guard(mutex)(&nor->lock);
>  
>  	if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe)
>  		return false;



From tudor.ambarus at linaro.org  Tue Aug 11 03:42:17 2026
From: tudor.ambarus at linaro.org (Tudor Ambarus)
Date: Tue, 11 Aug 2026 13:42:17 +0300
Subject: [PATCH v3] mtd: spi-nor: use guard() in
 spi_nor_rww_start_exclusive
In-Reply-To: <0ff8afdc-7a11-4a6c-9f62-6f8ba712c4ac@linaro.org>
References: <20260811041349.333176-1-runyu.xiao@seu.edu.cn>
 <20260811083111.403453-1-runyu.xiao@seu.edu.cn>
 <0ff8afdc-7a11-4a6c-9f62-6f8ba712c4ac@linaro.org>
Message-ID: <56991b48-cbe9-45bf-a24c-d7aa771c55d2@linaro.org>

Hi, Runyu, Miquel,

On 8/11/26 1:11 PM, Tudor Ambarus wrote:
> 
> 
> On 8/11/26 11:31 AM, Runyu Xiao wrote:
>> Commit 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup
>> helpers") converted the RWW start helpers to scoped mutex cleanup, but
>> left spi_nor_rww_start_exclusive() using a plain mutex_lock().
>>
>> spi_nor_rww_start_exclusive() is used as a wait_event_killable()
>> condition. When it returns false on the busy path, the raw mutex_lock()
>> leaves nor->lock held and blocks the operation that must clear the RWW
>> state in spi_nor_rww_end_exclusive().
>>
>> Use guard(mutex) here as well so this helper matches the other RWW
>> start helpers and releases nor->lock on the false return path.
>>
>> Fixes: 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup helpers")
> 
> The fixes tag is wrong. The blamed commit did not change the functionality
> of the code, while yours does.
> 
> You shall instead blame the commit that failed to release the lock in the
> first place. But then if you want to have this backported to stable kernels
> you may want to add 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex
> cleanup helpers") as a prerequisite patch, as it includes <linux/cleanup.h>.
> 

Runyu, please also update the commit subject, the point of the patch is
that it fixes a mutex leak, thus I suggest to reword it with something like
"mtd: spi-nor: core: Fix mutex leak in spi_nor_rww_start_exclusive()"

>> Cc: stable at vger.kernel.org
>> Signed-off-by: Runyu Xiao <runyu.xiao at seu.edu.cn>
>> ---
>> Changes in v3:
>> - Reframe the changelog around the scoped-helper conversion that missed
>>   spi_nor_rww_start_exclusive().
>>
>> Changes in v2:
>> - Replace the analysis-style explanation with a short maintainer-facing
>>   description of the busy-path lock hold.
>> - Keep the fix focused on using guard(mutex) so the mutex is released on
>>   both paths.
>>
>> drivers/mtd/spi-nor/core.c | 2 +-
>> 1 file changed, 1 insertion(+), 1 deletion(-)
>>
>> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
>> index ccf4396cdcd0..8bc117b46e02 100644
>> --- a/drivers/mtd/spi-nor/core.c
>> +++ b/drivers/mtd/spi-nor/core.c
>> @@ -1310,7 +1310,7 @@ static bool spi_nor_rww_start_exclusive(struct spi_nor *nor)
>>  {
>>  	struct spi_nor_rww *rww = &nor->rww;
>>  
>> -	mutex_lock(&nor->lock);
>> +	guard(mutex)(&nor->lock);
>>  

Miquel,

Have you seen sashiko's review at 
https://sashiko.dev/#/patchset/20260811083111.403453-1-runyu.xiao%40seu.edu.cn?

Both concerns look valid to me. Do you think you can allocate time to fix them?

Cheers,
ta

>>  	if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe)
>>  		return false;
> 



From md.alam at oss.qualcomm.com  Tue Aug 11 05:40:50 2026
From: md.alam at oss.qualcomm.com (Md Sadre Alam)
Date: Tue, 11 Aug 2026 18:10:50 +0530
Subject: [PATCH 1/2] mtd: spinand: winbond: add support for W25N04LW
In-Reply-To: <anmLsPcXRk0VAQSW@atmark-techno.com>
References: <20260807064339.1632249-1-md.alam@oss.qualcomm.com>
 <20260807064339.1632249-2-md.alam@oss.qualcomm.com>
 <anmLsPcXRk0VAQSW@atmark-techno.com>
Message-ID: <ansYUoNKJyDQ+6f2@hu-mdalam-blr.qualcomm.com>

On Mon, Aug 10, 2026 at 05:28:32PM +0900, Dominique MARTINET wrote:
> Md Sadre Alam wrote on Fri, Aug 07, 2026 at 12:13:38PM +0530:
> > Add support for Winbond W25N04LW 4Gbit SPI-NAND.
> > It has 8-bit on-die ECC and a 4096+256 byte page
> > (4096 byte main area, 128 byte usable OOB once
> > on-chip ECC hides the 128 byte parity area),
> > organized as 2048 blocks of 64 pages each, unlike
> > the existing 2048 byte page W25N04KV/W25N04KW parts.
> 
> This is pretty good timing, I was just looking at sending a patch for
> W25N04LW myself!
> 
> I agree with Miqu?l that your patch looks odd, you wrote
> > +		     NAND_MEMORG(1, 4096, 128, 64, 2048, 40, 1, 1, 1),
> but the erase blocks are 256K long so it should be
> +		     NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
> 
> did you actually test this device?
> 
> If it helps, the datasheet for W25N04LWZExx / W25N04LWTBxx is freely
> available from digikey here[1]
> [1] https://mm.digikey.com/Volume0/opasdata/d220001/medias/docus/7182/W25N04LWZExx_W25N04LWTBxx_RevD_2025-08-05.pdf
> 
> (And it also supports continuous read, so I'm curious to see if it'd
> work with the recent continous read support from Miqu?l[2] merged in
> 7.2... I'm not familiar with the subsystem so will need a bit of time to
> plug all the hooks in properly)
> [2] https://lore.kernel.org/linux-mtd/20260429-winbond-v6-18-rc1-cont-read-v3-0-0f38b3c229ad at bootlin.com/
> 
> 
> FWIW, I've confirmed it works with the following diff (at least up to
> the point of creating an ubi volume and booting from it); happy to send
> this as a patch if this has indeed not been tested
This device was supported in our downstream tree and was tested there. I
picked that patch and posted it upstream assuming the configuration was
already correct.
The `128` in:
NAND_MEMORG(1, 4096, 128, 64, 2048, 40, 1, 1, 1)
was my mistake. It should indeed be `256`, matching the 4096 + 256 page
layout in the W25N04LW datasheet.
Please go ahead and post your tested patch. I do not currently have this
device available to retest the upstream version.

Thanks,
Alam.



From md.alam at oss.qualcomm.com  Tue Aug 11 05:48:27 2026
From: md.alam at oss.qualcomm.com (Md Sadre Alam)
Date: Tue, 11 Aug 2026 18:18:27 +0530
Subject: [PATCH 1/2] mtd: spinand: winbond: add support for W25N04LW
In-Reply-To: <87mruuqh6f.fsf@bootlin.com>
References: <20260807064339.1632249-1-md.alam@oss.qualcomm.com>
 <20260807064339.1632249-2-md.alam@oss.qualcomm.com>
 <anmLsPcXRk0VAQSW@atmark-techno.com>
 <87mruuqh6f.fsf@bootlin.com>
Message-ID: <ansaG+Z6kMWBW+kK@hu-mdalam-blr.qualcomm.com>

On Mon, Aug 10, 2026 at 02:43:52PM +0200, Miquel Raynal wrote:
> Hi Dominique,
> 
> On 10/08/2026 at 17:28:32 +09, Dominique MARTINET <dominique.martinet at atmark-techno.com> wrote:
> 
> > Md Sadre Alam wrote on Fri, Aug 07, 2026 at 12:13:38PM +0530:
> >> Add support for Winbond W25N04LW 4Gbit SPI-NAND.
> >> It has 8-bit on-die ECC and a 4096+256 byte page
> >> (4096 byte main area, 128 byte usable OOB once
> >> on-chip ECC hides the 128 byte parity area),
> >> organized as 2048 blocks of 64 pages each, unlike
> >> the existing 2048 byte page W25N04KV/W25N04KW parts.
> >
> > This is pretty good timing, I was just looking at sending a patch for
> > W25N04LW myself!
> >
> > I agree with Miqu?l that your patch looks odd, you wrote
> >> +		     NAND_MEMORG(1, 4096, 128, 64, 2048, 40, 1, 1, 1),
> > but the erase blocks are 256K long so it should be
> > +		     NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
> >
> > did you actually test this device?
> >
> > If it helps, the datasheet for W25N04LWZExx / W25N04LWTBxx is freely
> > available from digikey here[1]
> > [1] https://mm.digikey.com/Volume0/opasdata/d220001/medias/docus/7182/W25N04LWZExx_W25N04LWTBxx_RevD_2025-08-05.pdf
> 
> Thanks for all the feedback, Sadre, can you please propose an udpate
> with these details and changes?
I do not currently have access to this device, so I cannot properly
validate an updated version on hardware.

Given that the current patch needs the OOB size fixed to 256, and likely
the OOB layout adjusted accordingly, I think it is better if Dominique sends
his tested version. I am fine with him taking over this patch.

> 
> > (And it also supports continuous read, so I'm curious to see if it'd
> > work with the recent continous read support from Miqu?l[2] merged in
> > 7.2... I'm not familiar with the subsystem so will need a bit of time to
> > plug all the hooks in properly)
> > [2]
> > https://lore.kernel.org/linux-mtd/20260429-winbond-v6-18-rc1-cont-read-v3-0-0f38b3c229ad at bootlin.com/
> 
> If it has continuous read, it requires the flag and a hook to
> enable/disable the feature. The existing Winbond helper may already be
> enough for it. You can test it with a recent version of mtd-utils, I
> added options to nanddump, nandbiterrs and flash_speed for that.
> 
> If Sadre dooesn't do it, you can propose a follow-up patch.

I currently do not have access to this device to test continuous read,
so please go ahead with the follow-up patch if you have hardware to
validate it.

Thanks,
Alam.


From md.alam at oss.qualcomm.com  Tue Aug 11 05:57:46 2026
From: md.alam at oss.qualcomm.com (Md Sadre Alam)
Date: Tue, 11 Aug 2026 18:27:46 +0530
Subject: [PATCH 2/2] mtd: spinand: winbond: add support for W25N01KW and
 W25N02LW
In-Reply-To: <875x1mca4t.fsf@bootlin.com>
References: <20260807064339.1632249-1-md.alam@oss.qualcomm.com>
 <20260807064339.1632249-3-md.alam@oss.qualcomm.com>
 <875x1mca4t.fsf@bootlin.com>
Message-ID: <anscSq0A9hPL8uXT@hu-mdalam-blr.qualcomm.com>

On Fri, Aug 07, 2026 at 03:50:42PM +0200, Miquel Raynal wrote:
> Hi Sadre,
> 
> On 07/08/2026 at 12:13:39 +0530, Md Sadre Alam <md.alam at oss.qualcomm.com> wrote:
> 
> > Add support for the Winbond W25N01KW and W25N02LW SPI-NAND
> > devices.
> >
> > The W25N01KW shares the same geometry, OOB layout, and ECC
> > status handling as the existing W25N01KV, while the W25N02LW
> > matches the existing W25N02KV/W25N02KW devices and reuses
> > their OOB layout and ECC status callback.
> >
> > Signed-off-by: Md Sadre Alam <md.alam at oss.qualcomm.com>
> > ---
> 
> Sashiko raises a point:
> 
> - [Medium] The device ID `0x12, 0x22` for W25N02LW breaks the
> established pattern for Winbond 1.8V SPI NAND devices and is highly
> likely a typo for `0xb2, 0x22`.
> 
> Can you please check?

I checked the W25N02LW datasheet, section 8.1.1 "Manufacturer and Device
Identification". It lists the W25N02LW Device ID as 1222h.

Thanks,
Alam.


From miquel.raynal at bootlin.com  Tue Aug 11 07:36:49 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 11 Aug 2026 16:36:49 +0200
Subject: [PATCH v3] mtd: spi-nor: use guard() in
 spi_nor_rww_start_exclusive
In-Reply-To: <56991b48-cbe9-45bf-a24c-d7aa771c55d2@linaro.org> (Tudor
	Ambarus's message of "Tue, 11 Aug 2026 13:42:17 +0300")
References: <20260811041349.333176-1-runyu.xiao@seu.edu.cn>
	<20260811083111.403453-1-runyu.xiao@seu.edu.cn>
	<0ff8afdc-7a11-4a6c-9f62-6f8ba712c4ac@linaro.org>
	<56991b48-cbe9-45bf-a24c-d7aa771c55d2@linaro.org>
Message-ID: <87jypwoha6.fsf@bootlin.com>


> Miquel,
>
> Have you seen sashiko's review at 
> https://sashiko.dev/#/patchset/20260811083111.403453-1-runyu.xiao%40seu.edu.cn?
>
> Both concerns look valid to me. Do you think you can allocate time to
> fix them?

Sashiko is becoming a nightmare. I'll add this to my todo list, but if
someone in lack of technical challenge wants to propose fixes, be my
guest.

Thanks,
Miqu?l


From miquel.raynal at bootlin.com  Tue Aug 11 08:38:19 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 11 Aug 2026 17:38:19 +0200
Subject: [PATCH v3] mtd: spi-nor: use guard() in
 spi_nor_rww_start_exclusive
In-Reply-To: <87jypwoha6.fsf@bootlin.com> (Miquel Raynal's message of "Tue, 11
	Aug 2026 16:36:49 +0200")
References: <20260811041349.333176-1-runyu.xiao@seu.edu.cn>
	<20260811083111.403453-1-runyu.xiao@seu.edu.cn>
	<0ff8afdc-7a11-4a6c-9f62-6f8ba712c4ac@linaro.org>
	<56991b48-cbe9-45bf-a24c-d7aa771c55d2@linaro.org>
	<87jypwoha6.fsf@bootlin.com>
Message-ID: <878q6coefo.fsf@bootlin.com>


>> Have you seen sashiko's review at 
>> https://sashiko.dev/#/patchset/20260811083111.403453-1-runyu.xiao%40seu.edu.cn?
>>
>> Both concerns look valid to me. Do you think you can allocate time to
>> fix them?
>
> Sashiko is becoming a nightmare. I'll add this to my todo list, but if
> someone in lack of technical challenge wants to propose fixes, be my
> guest.

Actually, the might_sleep() warning seems to be an hallucination. It
mixes different concepts in its explanation (might_sleep() is not a
problem here, we are in process context) and I believe it got mislead by
the current state of the code which lacks a mutex_unlock(). The second
issue is definitely not "high" and only touches legacy
spi-nor-controllers. This is worth fixing though.

Thanks,
Miqu?l


From miquel.raynal at bootlin.com  Tue Aug 11 08:56:23 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 11 Aug 2026 17:56:23 +0200
Subject: [PATCH] mtd: spi-nor: Unprepare legacy controllers upon RWW wait
 interruption
Message-ID: <20260811-perso-rww-fixes-v1-1-5866a70e5d1c@bootlin.com>

All the parallel locking helpers (spi_nor_prep_and_lock(),
spi_nor_prep_and_lock_pe() and spi_nor_prep_and_lock_rd()) call
spi_nor_prep() then wait with a killable sleep. If the sleep gets
killed, we leave the controller in its prepared state.

Call ->unprepare() in these cases.

Fixes: 74df43b3f626 ("mtd: spi-nor: Enhance locking to support reads while writes")
Cc: stable at vger.kernel.org
Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/core.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 8bc117b46e02..250e996284f9 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -1346,6 +1346,9 @@ int spi_nor_prep_and_lock(struct spi_nor *nor)
 		ret = wait_event_killable(nor->rww.wait,
 					  spi_nor_rww_start_exclusive(nor));
 
+	if (ret)
+		spi_nor_unprep(nor);
+
 	return ret;
 }
 
@@ -1417,6 +1420,9 @@ static int spi_nor_prep_and_lock_pe(struct spi_nor *nor, loff_t start, size_t le
 		ret = wait_event_killable(nor->rww.wait,
 					  spi_nor_rww_start_pe(nor, start, len));
 
+	if (ret)
+		spi_nor_unprep(nor);
+
 	return ret;
 }
 
@@ -1490,6 +1496,9 @@ static int spi_nor_prep_and_lock_rd(struct spi_nor *nor, loff_t start, size_t le
 		ret = wait_event_killable(nor->rww.wait,
 					  spi_nor_rww_start_rd(nor, start, len));
 
+	if (ret)
+		spi_nor_unprep(nor);
+
 	return ret;
 }
 

---
base-commit: db5f6418501f9795a96026b764d747179a231b2b
change-id: 20260811-perso-rww-fixes-016cdd229647

Best regards,
-- 
Miquel Raynal <miquel.raynal at bootlin.com>



From frieder at fris.de  Tue Aug 11 09:13:37 2026
From: frieder at fris.de (Frieder Schrempf)
Date: Tue, 11 Aug 2026 18:13:37 +0200
Subject: [PATCH 6.12.y 2/3] mtd: spinand: try a regular dirmap if creating a dirmap for continuous reading fails
In-Reply-To: <20260811161342.533280-1-frieder@fris.de>
References: <20260811161342.533280-1-frieder@fris.de>
Message-ID: <20260811161342.533280-3-frieder@fris.de>

From: Mikhail Kshevetskiy <mikhail.kshevetskiy at iopsys.eu>

[ Upstream commit 004f8ea0d9917398aabff7388b3bf62a84a4088b ]

Continuous reading may result in multiple flash pages reading in one
operation. Typically only one flash page has read/written (a little bit
more than 2-4 Kb), but continuous reading requires the spi controller
to read up to 512 Kb in one operation without toggling CS in beetween.

Roughly speaking spi controllers can be divided on 2 categories:
 * spi controllers without dirmap acceleration support
 * spi controllers with dirmap acceleration support

Firt of them will have issues with continuous reading if restriction on
the transfer length is implemented in the adjust_op_size() handler.
Second group often supports acceleration of single page only reading.
Thus enabling of continuous reading can break flash reading.

This patch tries to create dirmap for continuous reading first and
fallback to regular reading if spi controller refuses to create it.

Cc: stable at vger.kernel.org
Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy at iopsys.eu>
Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
Signed-off-by: Frieder Schrempf <frieder.schrempf at kontron.de>
---
 drivers/mtd/nand/spi/core.c | 43 ++++++++++++++++++++++++++++++-------
 1 file changed, 35 insertions(+), 8 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index a480243385e2e..10339209e7322 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -1032,6 +1032,39 @@ static int spinand_mtd_block_isreserved(struct mtd_info *mtd, loff_t offs)
 	return ret;
 }
 
+static struct spi_mem_dirmap_desc *spinand_create_rdesc(
+					struct spinand_device *spinand,
+					struct spi_mem_dirmap_info *info)
+{
+	struct nand_device *nand = spinand_to_nand(spinand);
+	struct spi_mem_dirmap_desc *desc = NULL;
+
+	if (spinand->cont_read_possible) {
+		/*
+		 * spi controller may return an error if info->length is
+		 * too large
+		 */
+		info->length = nanddev_eraseblock_size(nand);
+		desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+						  spinand->spimem, info);
+	}
+
+	if (IS_ERR_OR_NULL(desc)) {
+		/*
+		 * continuous reading is not supported by flash or
+		 * its spi controller, use regular reading
+		 */
+		spinand->cont_read_possible = false;
+
+		info->length = nanddev_page_size(nand) +
+			       nanddev_per_page_oobsize(nand);
+		desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+						  spinand->spimem, info);
+	}
+
+	return desc;
+}
+
 static int spinand_create_dirmap(struct spinand_device *spinand,
 				 unsigned int plane)
 {
@@ -1051,11 +1084,8 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
 
 	spinand->dirmaps[plane].wdesc = desc;
 
-	if (spinand->cont_read_possible)
-		info.length = nanddev_eraseblock_size(nand);
 	info.op_tmpl = *spinand->op_templates.read_cache;
-	desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
-					  spinand->spimem, &info);
+	desc = spinand_create_rdesc(spinand, &info);
 	if (IS_ERR(desc))
 		return PTR_ERR(desc);
 
@@ -1078,12 +1108,9 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
 
 	spinand->dirmaps[plane].wdesc_ecc = desc;
 
-	if (spinand->cont_read_possible)
-		info.length = nanddev_eraseblock_size(nand);
 	info.op_tmpl = *spinand->op_templates.read_cache;
 	info.op_tmpl.data.ecc = true;
-	desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
-					  spinand->spimem, &info);
+	desc = spinand_create_rdesc(spinand, &info);
 	if (IS_ERR(desc))
 		return PTR_ERR(desc);
 
-- 
2.55.0



From frieder at fris.de  Tue Aug 11 09:13:38 2026
From: frieder at fris.de (Frieder Schrempf)
Date: Tue, 11 Aug 2026 18:13:38 +0200
Subject: [PATCH 6.12.y 3/3] mtd: spinand: repeat reading in regular mode if continuous reading fails
In-Reply-To: <20260811161342.533280-1-frieder@fris.de>
References: <20260811161342.533280-1-frieder@fris.de>
Message-ID: <20260811161342.533280-4-frieder@fris.de>

From: Mikhail Kshevetskiy <mikhail.kshevetskiy at iopsys.eu>

[ Upstream commit 010dc7f2dd6a0078ade3f88f627ed5fbf45ceb94 ]

Continuous reading may result in multiple flash pages reading in one
operation. Unfortunately, not all spinand controllers support such
large reading. They will read less data. Unfortunately, the operation
can't be continued.

In this case:
 * disable continuous reading on this (not good enough) spi controller
 * repeat reading in regular mode.

Cc: stable at vger.kernel.org
Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy at iopsys.eu>
Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
Signed-off-by: Frieder Schrempf <frieder.schrempf at kontron.de>
---
 drivers/mtd/nand/spi/core.c | 25 +++++++++++++++++++++----
 1 file changed, 21 insertions(+), 4 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 10339209e7322..bf16901b77b64 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -427,8 +427,16 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand,
 		 * Dirmap accesses are allowed to toggle the CS.
 		 * Toggling the CS during a continuous read is forbidden.
 		 */
-		if (nbytes && req->continuous)
-			return -EIO;
+		if (nbytes && req->continuous) {
+			/*
+			 * Spi controller with broken support of continuous
+			 * reading was detected. Disable future use of
+			 * continuous reading and return -EAGAIN to retry
+			 * reading within regular mode.
+			 */
+			spinand->cont_read_possible = false;
+			return -EAGAIN;
+		}
 	}
 
 	if (req->datalen)
@@ -849,10 +857,19 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t from,
 
 	old_stats = mtd->ecc_stats;
 
-	if (spinand_use_cont_read(mtd, from, ops))
+	if (spinand_use_cont_read(mtd, from, ops)) {
 		ret = spinand_mtd_continuous_page_read(mtd, from, ops, &max_bitflips);
-	else
+		if (ret == -EAGAIN && !spinand->cont_read_possible) {
+			/*
+			 * Spi controller with broken support of continuous
+			 * reading was detected (see spinand_read_from_cache_op()),
+			 * repeat reading in regular mode.
+			 */
+			ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips);
+		}
+	} else {
 		ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips);
+	}
 
 	if (ops->stats) {
 		ops->stats->uncorrectable_errors +=
-- 
2.55.0



From frieder at fris.de  Tue Aug 11 09:13:35 2026
From: frieder at fris.de (Frieder Schrempf)
Date: Tue, 11 Aug 2026 18:13:35 +0200
Subject: [PATCH 6.12.y 0/3] Backport fixes for SPI NAND on i.MX6UL
Message-ID: <20260811161342.533280-1-frieder@fris.de>

From: Frieder Schrempf <frieder.schrempf at kontron.de>

There are failures with our Kontron i.MX6UL boards using Macronix SPI NAND
chips on i.MX6UL that originate in the introduction of the continuous read
feature in 6.12 (631cfdd0520d19b7f4fc13b834fd9c8b46c6dbac) and the absence
of later fixes introduced in 6.18 that were never backported.

This series contains these fixes picked from mainline, applying to stable 6.12
cleanly.

Cc: stable at vger.kernel.org
Cc: Mikhail Kshevetskiy <mikhail.kshevetskiy at iopsys.eu>
Cc: Miquel Raynal <miquel.raynal at bootlin.com>
Cc: linux-mtd at lists.infradead.org

Mikhail Kshevetskiy (3):
  mtd: spinand: fix direct mapping creation sizes
  mtd: spinand: try a regular dirmap if creating a dirmap for continuous
    reading fails
  mtd: spinand: repeat reading in regular mode if continuous reading
    fails

 drivers/mtd/nand/spi/core.c | 74 +++++++++++++++++++++++++++++--------
 1 file changed, 59 insertions(+), 15 deletions(-)

-- 
2.55.0


From frieder at fris.de  Tue Aug 11 09:13:36 2026
From: frieder at fris.de (Frieder Schrempf)
Date: Tue, 11 Aug 2026 18:13:36 +0200
Subject: [PATCH 6.12.y 1/3] mtd: spinand: fix direct mapping creation sizes
In-Reply-To: <20260811161342.533280-1-frieder@fris.de>
References: <20260811161342.533280-1-frieder@fris.de>
Message-ID: <20260811161342.533280-2-frieder@fris.de>

From: Mikhail Kshevetskiy <mikhail.kshevetskiy at iopsys.eu>

[ Upstream commit e4a0cf9f1d90e6888e5373da3314f761024f6c97 ]

Continuous mode is only supported for data reads, thus writing
requires only single flash page mapping.

Cc: stable at vger.kernel.org
Signed-off-by: Mikhail Kshevetskiy <mikhail.kshevetskiy at iopsys.eu>
Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
Signed-off-by: Frieder Schrempf <frieder.schrempf at kontron.de>
---
 drivers/mtd/nand/spi/core.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 48ac009cbaad2..a480243385e2e 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -1036,18 +1036,13 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
 				 unsigned int plane)
 {
 	struct nand_device *nand = spinand_to_nand(spinand);
-	struct spi_mem_dirmap_info info = {
-		.length = nanddev_page_size(nand) +
-			  nanddev_per_page_oobsize(nand),
-	};
+	struct spi_mem_dirmap_info info = { 0 };
 	struct spi_mem_dirmap_desc *desc;
 
-	if (spinand->cont_read_possible)
-		info.length = nanddev_eraseblock_size(nand);
-
 	/* The plane number is passed in MSB just above the column address */
 	info.offset = plane << fls(nand->memorg.pagesize);
 
+	info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
 	info.op_tmpl = *spinand->op_templates.update_cache;
 	desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
 					  spinand->spimem, &info);
@@ -1056,6 +1051,8 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
 
 	spinand->dirmaps[plane].wdesc = desc;
 
+	if (spinand->cont_read_possible)
+		info.length = nanddev_eraseblock_size(nand);
 	info.op_tmpl = *spinand->op_templates.read_cache;
 	desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
 					  spinand->spimem, &info);
@@ -1071,6 +1068,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
 		return 0;
 	}
 
+	info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
 	info.op_tmpl = *spinand->op_templates.update_cache;
 	info.op_tmpl.data.ecc = true;
 	desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
@@ -1080,6 +1078,8 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
 
 	spinand->dirmaps[plane].wdesc_ecc = desc;
 
+	if (spinand->cont_read_possible)
+		info.length = nanddev_eraseblock_size(nand);
 	info.op_tmpl = *spinand->op_templates.read_cache;
 	info.op_tmpl.data.ecc = true;
 	desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
-- 
2.55.0



From miquel.raynal at bootlin.com  Tue Aug 11 09:23:36 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Tue, 11 Aug 2026 18:23:36 +0200
Subject: [PATCH 6.12.y 0/3] Backport fixes for SPI NAND on i.MX6UL
In-Reply-To: <20260811161342.533280-1-frieder@fris.de> (Frieder Schrempf's
	message of "Tue, 11 Aug 2026 18:13:35 +0200")
References: <20260811161342.533280-1-frieder@fris.de>
Message-ID: <87zeysmxrr.fsf@bootlin.com>


> This series contains these fixes picked from mainline, applying to stable 6.12
> cleanly.
>
> Cc: stable at vger.kernel.org
> Cc: Mikhail Kshevetskiy <mikhail.kshevetskiy at iopsys.eu>
> Cc: Miquel Raynal <miquel.raynal at bootlin.com>
> Cc: linux-mtd at lists.infradead.org
>
> Mikhail Kshevetskiy (3):
>   mtd: spinand: fix direct mapping creation sizes
>   mtd: spinand: try a regular dirmap if creating a dirmap for continuous
>     reading fails
>   mtd: spinand: repeat reading in regular mode if continuous reading
>     fails
>
>  drivers/mtd/nand/spi/core.c | 74 +++++++++++++++++++++++++++++--------
>  1 file changed, 59 insertions(+), 15 deletions(-)

Acked-by: Miquel Raynal <miquel.raynal at bootlin.com>

Thanks for the backports,
Miqu?l


From syzbot+b320a4d5f65a61dbbf89 at syzkaller.appspotmail.com  Tue Aug 11 10:15:38 2026
From: syzbot+b320a4d5f65a61dbbf89 at syzkaller.appspotmail.com (syzbot)
Date: Tue, 11 Aug 2026 10:15:38 -0700
Subject: [syzbot] [mtd?] divide error in block2mtd_setup2
In-Reply-To: <6a791c94.01d0871a.3a0d52.009b.GAE@google.com>
Message-ID: <6a7b58ba.ac361c09.22ff0a.0045.GAE@google.com>

syzbot has found a reproducer for the following issue on:

HEAD commit:    f5bbbfec59b4 Merge tag 'probes-fixes-v7.2-rc7' of git://gi..
git tree:       upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=13a31079580000
kernel config:  https://syzkaller.appspot.com/x/.config?x=c44651ea7dd2f307
dashboard link: https://syzkaller.appspot.com/bug?extid=b320a4d5f65a61dbbf89
compiler:       gcc (Debian 14.2.0-19) 14.2.0, GNU ld (GNU Binutils for Debian) 2.44
C reproducer:   https://syzkaller.appspot.com/x/repro.c?x=16b76149580000

IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+b320a4d5f65a61dbbf89 at syzkaller.appspotmail.com

Oops: divide error: 0000 [#1] SMP KASAN NOPTI
CPU: 0 UID: 0 PID: 6029 Comm: syz-executor518 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
RIP: 0010:add_device drivers/mtd/devices/block2mtd.c:296 [inline]
RIP: 0010:block2mtd_setup2.isra.0+0x3c8/0xc70 drivers/mtd/devices/block2mtd.c:459
Code: 48 b8 00 00 00 00 00 fc ff df 48 89 fa 48 c1 ea 03 80 3c 02 00 0f 85 2b 08 00 00 48 8b 6d 08 31 ff 48 89 e8 48 c1 e0 09 48 99 <49> f7 ff 48 89 d6 48 89 54 24 10 e8 98 d5 4d fb 48 8b 54 24 10 48
RSP: 0018:ffffc90004ccf9f0 EFLAGS: 00010206
RAX: 0000000000100000 RBX: 1ffff92000999f41 RCX: ffffffff86bc9838
RDX: 0000000000000000 RSI: ffffffff86bc984a RDI: 0000000000000000
RBP: 0000000000000800 R08: 0000000000000005 R09: 000000000000001f
R10: 0000000000000007 R11: 0000000000000000 R12: ffffc90004ccfaa8
R13: 0000000000000000 R14: ffff8880254fe000 R15: 0000000000000000
FS:  000055558148e400(0000) GS:ffff8880d5dec000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000055a8208cda78 CR3: 000000003ad58000 CR4: 0000000000352ef0
Call Trace:
 <TASK>
 block2mtd_setup+0xbd/0xd0 drivers/mtd/devices/block2mtd.c:476
 param_attr_store+0x199/0x300 kernel/params.c:589
 module_attr_store+0x58/0x80 kernel/params.c:904
 sysfs_kf_write+0xf2/0x150 fs/sysfs/file.c:145
 kernfs_fop_write_iter+0x3e0/0x5f0 fs/kernfs/file.c:345
 new_sync_write fs/read_write.c:595 [inline]
 vfs_write+0x6ac/0x1050 fs/read_write.c:687
 ksys_write+0x12a/0x250 fs/read_write.c:739
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x115/0x870 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f0b39a14737
Code: 48 89 fa 4c 89 df e8 98 1d 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5b> c3 0f 1f 80 00 00 00 00 83 e2 39 83 fa 08 75 de e8 23 ff ff ff
RSP: 002b:00007ffdcc22a9a0 EFLAGS: 00000202 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 000055558148e400 RCX: 00007f0b39a14737
RDX: 000000000000000c RSI: 00007ffdcc22aa50 RDI: 0000000000000004
RBP: 0000000000000003 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000202 R12: 00007ffdcc22aa50
R13: 00007ffdcc22aa10 R14: 00007ffdcc22a9f0 R15: 0000000000000002
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:add_device drivers/mtd/devices/block2mtd.c:296 [inline]
RIP: 0010:block2mtd_setup2.isra.0+0x3c8/0xc70 drivers/mtd/devices/block2mtd.c:459
Code: 48 b8 00 00 00 00 00 fc ff df 48 89 fa 48 c1 ea 03 80 3c 02 00 0f 85 2b 08 00 00 48 8b 6d 08 31 ff 48 89 e8 48 c1 e0 09 48 99 <49> f7 ff 48 89 d6 48 89 54 24 10 e8 98 d5 4d fb 48 8b 54 24 10 48
RSP: 0018:ffffc90004ccf9f0 EFLAGS: 00010206
RAX: 0000000000100000 RBX: 1ffff92000999f41 RCX: ffffffff86bc9838
RDX: 0000000000000000 RSI: ffffffff86bc984a RDI: 0000000000000000
RBP: 0000000000000800 R08: 0000000000000005 R09: 000000000000001f
R10: 0000000000000007 R11: 0000000000000000 R12: ffffc90004ccfaa8
R13: 0000000000000000 R14: ffff8880254fe000 R15: 0000000000000000
FS:  000055558148e400(0000) GS:ffff8880d5dec000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 000055a8208cda78 CR3: 000000003ad58000 CR4: 0000000000352ef0
----------------
Code disassembly (best guess):
   0:	48 b8 00 00 00 00 00 	movabs $0xdffffc0000000000,%rax
   7:	fc ff df
   a:	48 89 fa             	mov    %rdi,%rdx
   d:	48 c1 ea 03          	shr    $0x3,%rdx
  11:	80 3c 02 00          	cmpb   $0x0,(%rdx,%rax,1)
  15:	0f 85 2b 08 00 00    	jne    0x846
  1b:	48 8b 6d 08          	mov    0x8(%rbp),%rbp
  1f:	31 ff                	xor    %edi,%edi
  21:	48 89 e8             	mov    %rbp,%rax
  24:	48 c1 e0 09          	shl    $0x9,%rax
  28:	48 99                	cqto
* 2a:	49 f7 ff             	idiv   %r15 <-- trapping instruction
  2d:	48 89 d6             	mov    %rdx,%rsi
  30:	48 89 54 24 10       	mov    %rdx,0x10(%rsp)
  35:	e8 98 d5 4d fb       	call   0xfb4dd5d2
  3a:	48 8b 54 24 10       	mov    0x10(%rsp),%rdx
  3f:	48                   	rex.W


---
If you want syzbot to run the reproducer, reply with:
#syz test: git://repo/address.git branch-or-commit-hash
If you attach or paste a git patch, syzbot will apply it before testing.


From joern at barelysecure.org  Tue Aug 11 11:19:54 2026
From: joern at barelysecure.org (=?iso-8859-1?Q?J=F6rn?= Engel)
Date: Tue, 11 Aug 2026 11:19:54 -0700
Subject: [syzbot] [mtd?] divide error in block2mtd_setup2
In-Reply-To: <bbbff0ba-09c4-49dc-9aac-371c0c7d6902@kylinos.cn>
References: <6a791c94.01d0871a.3a0d52.009b.GAE@google.com>
 <ankxaiwtTltL0xMj@cashel.logfs.org>
 <29397db9-7db4-4969-bb0e-5bb71f0c0303@kylinos.cn>
 <87se4mqhc3.fsf@bootlin.com>
 <bbbff0ba-09c4-49dc-9aac-371c0c7d6902@kylinos.cn>
Message-ID: <antnynHuZ2W8JmCc@cashel.logfs.org>

On Tue, Aug 11, 2026 at 11:02:53AM +0800, Pei Xiao wrote:
> > 
> Thank you for your reply. I am waiting for a reply on whether the cast
> on 'long' needs to be removed. Once that is confirmed, I would be happy
> to do it. Thank you.

It doesn't need to be removed, but it should be removed.  Any
unnecessary code should be removed in general and unnecessary casts in
particular should.

One of better parts of the C language is that casts are hardly ever
necessary.  Which means that any remaining necessary casts should
immediately draw attention of the reader - something tricky and
potentially dangerous is going on here.

But you stop paying attention when things become too common.  Which
means that unnecessary casts are not just noise, they actively do harm.


In this particular case, we can simply test whether the cast makes any
difference:

#include <assert.h>
#include <stdio.h>

int main(void)
{
	for (int i=0; i<=1<<16; i++) {
		for (int k=1; k<=1<<16; k++) {
			long long size = (long long)i << 32;
			int erase_size = k;

			long long a = size % erase_size;
			long long b = (long)size % erase_size;
			assert(a==b);
		}
	}
}

Compile with optimizations and the compiler decides to remove the loop.
Unless you suspect a compiler bug, I guess that settles the question. ;)

J?rn

--
Semper ubi sub ubi ubique.
-- latin pun


From s-k6 at ti.com  Tue Aug 11 11:32:55 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:02:55 +0530
Subject: [PATCH v7 00/18] spi: cadence-quadspi: add PHY tuning support
Message-ID: <20260811183313.1550425-1-s-k6@ti.com>

This series implements PHY tuning support for the Cadence QSPI controller
to enable reliable high-speed operations. Without PHY tuning, controllers
use conservative timing that limits performance. PHY tuning calibrates
RX/TX delay lines to find optimal data capture timing windows, enabling
operation up to the controller's maximum frequency.

Background:
High-speed SPI memory controllers require precise timing calibration for
reliable operation. At higher frequencies, board-to-board variations make
fixed timing parameters inadequate. The Cadence QSPI controller includes
a PHY interface with programmable delay lines (0-127 taps) for RX and TX
paths, but these require runtime calibration to find the valid timing
window.

Approach:
Add SDR/DDR PHY tuning algorithms for the Cadence controller:

SDR Mode Tuning (1D search):
 - Searches for two consecutive valid RX delay windows
 - Selects the larger window and uses its midpoint for maximum margin
 - TX delay fixed at maximum (127) as it's less critical in SDR

DDR Mode Tuning (2D search):
 - Finds RX boundaries (rxlow/rxhigh) using TX window sweeps
 - Finds TX boundaries (txlow/txhigh) at fixed RX positions
 - Defines valid region corners and detects gaps via binary search
 - Applies temperature compensation for optimal point selection
 - Handles single or dual passing regions with different strategies

Patch description:
Infrastructure (1-5):
 - Patch 1:   Add spi-max-post-config-frequency-hz to describe maximum
              frequency achievable post controller configuration
 - Patch 2:   Add spi-phy-pattern-partition phandle for
              NOR flash PHY tuning pattern location
 - Patch 3:   Parse spi-max-post-config-frequency-hz in spi.c; adds
              spi_device.post_config_max_speed_hz (0 when not set
              keeping all existing DT fully compatible)
 - Patch 4:   Extend spi_mem_adjust_op_freq() with a bypass: if
              op->max_freq equals post_config_max_speed_hz, return
              immediately leaving op->max_freq unchanged. All other
              ops are capped to max_speed_hz. Also require per_op_freq
              controller capability for post-config ops in
              spi_mem_default_supports_op()
 - Patch 5:   Add execute_tuning callback to spi_controller_mem_ops and
              spi_mem_execute_tuning() wrapper in SPI-MEM core;
              serialises access via spi_mem_access_start/end and
              rejects GPIO chip selects

Cadence QSPI Implementation (6-14):
 - Patch 6:   Move cqspi_readdata_capture() earlier (preparatory)
 - Patch 7:   Add DQS bit to cqspi_readdata_capture() (preparatory)
 - Patch 8:   Add complete PHY tuning support: DLL management, pattern
              verification (NOR via spi-phy-pattern-partition phandle,
              NAND via write-to-cache), SDR 1D and DDR 2D search
              algorithms with temperature compensation, AM654-specific
              execute_tuning entry point
 - Patch 9:   Reject 2-byte-address DDR operations via a new
              CQSPI_NO_PHY_TUNING_WITH_ODDR_2B_ADDR quirk flag to work
              around AM654 OSPI erratum i2383
 - Patch 10:  Refactor direct read path for PHY support (preparatory)
 - Patch 11:  Enable PHY for direct reads at the calibrated frequency
              when both transfer boundaries are 16-byte aligned,
              falling back to non-PHY DMA otherwise; also reprograms
              DLL on chip-select switch when two devices have
              different calibrated settings
 - Patch 12:  Enable PHY for indirect writes of at least
              CQSPI_PHY_MIN_INDIRECT_WRITE_LEN bytes
 - Patch 13:  Reprogram per-device CS timing on any chip-select switch
 - Patch 14:  Reprogram the PHY DLL (master config and RX/TX taps) on
              runtime resume, since the DLL loses its lock when the
              OSPI controller is power-gated

MTD core (15-18):
 - Patch 15:  Extract spinand_select_op_variant() into a shared helper
              spinand_op_find_best_variant() with a skip_mask
 - Patch 16:  Negotiate optimal controller operating point before dirmap
              creation; iterate all read variants in performance order
              across ODTR and SSDR interfaces
 - Patch 17:  Extract spi_nor_spimem_get_read_op() helper (preparatory)
 - Patch 18:  Execute controller optimization in spi_nor_probe() before
              creating dirmaps

Testing:
This series was tested on TI's
AM62Ax SK with OSPI NAND flash and
AM62Px SK with OSPI NOR flash:

Read throughput:
|----------------------------------------|
|                | non-PHY   | PHY       |
|----------------------------------------|
| OSPI NOR (8D)  | 37.5 MB/s | 218 MB/s  |
|----------------------------------------|
| OSPI NAND (8S) | 9.2 MB/s  | 35.6 MB/s |
|----------------------------------------|

Write throughput:
|----------------------------------------|
|                | non-PHY   | PHY       |
|----------------------------------------|
| OSPI NAND (8S) | 6 MB/s    | 9.2 MB/s  |
|----------------------------------------|

Test log: https://gist.github.com/santhosh21/79c89e104e6d35c4071845293901e3ad
Repo: https://github.com/santhosh21/linux/commits/phy_tuning_v7/

Changes in v7:
 - Serialise spi_mem_execute_tuning() with spi_mem_access_start/end and
   reject GPIO chip-select configurations
 - Fix cqspi_phy_apply_setting() to also write the calibrated
   read_delay to CQSPI_REG_READCAPTURE, previously software-only
 - Use local op copies for pattern write/verify instead of mutating
   the caller's op templates in place
 - Fix the READCAPTURE bypass bit in cqspi_tune_phy() to match the
   value validated during calibration instead of being hardcoded
 - Enable PHY before starting the indirect-write state machine instead
   of after; the old order was a guaranteed deadlock
 - Add a new patch to reprogram the PHY DLL on runtime resume
 - Fix continuous-read and ODTR/SSDR template handling on ranked
   fallback in spinand_try_ranked_variant(), preventing data
   corruption after a fallback
 - Fix other, smaller correctness issues raised by sashiko-bot
 - Rebase on v7.2-rc7
 - Link to v6: https://lore.kernel.org/linux-spi/20260723110219.3180747-1-s-k6 at ti.com/

Changes in v6:
 - Add maxItems to spi-max-post-config-frequency-hz property
 - Rebase on v7.2-rc4
 - Collect tag from Krzysztof
 - Link to v5: https://lore.kernel.org/linux-spi/20260717185116.2065505-1-s-k6 at ti.com/

Changes in v5:
 - Rename spi-max-post-config-frequency to spi-max-post-config-frequency-hz
   per DT schema unit suffix convention
 - Rename CQSPI_NO_2BYTE_ADDR_PHY_DDR quirk to
   CQSPI_NO_PHY_TUNING_WITH_ODDR_2B_ADDR for clarity
 - Rename SPI NAND identifiers to remove PHY-specific terminology:
     spinand_configure_phy()     -> spinand_optimize_controller()
     spinand_try_phy_ranked()    -> spinand_try_ranked_variant()
     spinand_reset_max_ops()     -> spinand_reset_max_freq_ops()
     spinand_op_find_best()      -> spinand_op_find_best_variant()
     phy_read_variants (field)   -> all_read_variants
 - Replace bool odtr parameter with enum spinand_bus_interface iface in
   spinand_op_find_best_variant() to preserve the existing enumeration
 - Replace cqspi_get_phy_pattern_offset() with per-flash probe-time
   phandle lookup in cqspi_of_get_flash_pdata(); fixes incorrect pattern
   offset selection when multiple flash devices share a controller
 - In cqspi_tune_phy(), reprogramme and resync the DLL when the active
   device's calibrated settings differ from hardware
 - Edit cqspi_phy_apply_setting() to update phy_setting.rx and
   phy_setting.tx alongside phy_setting.read_delay
 - Reprogram per-device CS timing (CQSPI_REG_DELAY and read-capture register)
   on any chip-select switch, not only on clock changes
 - Move post_config_max_speed_hz early-return to the top of
   spinand_optimize_controller(), before any state is modified
 - Remove dead NULL guards on SSDR op templates in Pass 2
 - Downgrade tuning-failure log messages from dev_warn to dev_dbg
 - Rebase on v7.2-rc3
 - Collect tags
 - Link to v4: https://lore.kernel.org/linux-spi/20260618073725.84733-1-s-k6 at ti.com/

Changes in v4:
 - Add spi-max-post-config-frequency instead of extending spi-max-frequency
   to accept an optional second value
 - Replace spi_mem_apply_base_freq_cap() with spi_mem_adjust_op_freq() extension
 - For SPI NOR/NAND, execute PHY tuning before the dirmap creation
 - For SPI NAND, execute PHY tuning across all operation variants available,
   perform duration comparison, and select the best resulting variant
   by taking controller-specific restrictions into account
 - Move i2383 check from cqspi_supports_mem_op() to cqspi_am654_ospi_execute_tuning()
 - Rename cdns,phy-pattern-partition to spi-phy-pattern-partition,
   cqspi_phy_enable to cqspi_tune_phy and f_pdata->use_phy to use_tuned_phy
 - Remove redundant spi-max-frequency parsing in driver cqspi_of_get_flash_pdata()
 - Extract DMA refactoring into a preparatory patch
 - Rebase on v7.1
 - Collect tags from Miquel
 - Link to v3: https://lore.kernel.org/linux-spi/20260527175527.2247679-1-s-k6 at ti.com/

Changes in v3:
 - Drop spi-has-dqs DT property; DQS is now enabled automatically when
   the selected read operation uses DDR signalling (dtr flags in the op)
 - Extend spi-max-frequency to accept an optional second value forming a
   [base-freq, max-freq] pair; the presence of two values signals PHY
   tuning intent and encodes both the conservative base speed and the
   calibration target in one property
 - Add base_speed_hz to struct spi_device (spi.c/spi.h) and parse the
   two-element array there; single-value DT is fully backward-compatible
 - Move frequency enforcement from the cadence driver to core: new
   spi_mem_apply_base_freq_cap() called from spi_mem_exec_op() replaces
   the per-driver cqspi_op_matches_tuned() and non_phy_clk_rate field
 - Propagate the tuned max_freq to dirmap op templates after
   execute_tuning() succeeds; store persistent op templates in
   spi_nor.max_read_op and spinand.{max_read,max_write}_op so the
   frequency writeback survives across the probe call
 - Replace NOR pattern partition lookup by name with a
   cdns,phy-pattern-partition DT phandle pointing directly to the
   partition node
 - Add CQSPI_NO_2BYTE_ADDR_PHY_DDR quirk and reject 2-byte-address DDR
   ops in cqspi_supports_mem_op() to work around AM654 erratum i2383
 - Remove RFC tag
 - Rebase on v7.1-rc5
 - Collect tags from Miquel
 - Link to v2: https://lore.kernel.org/linux-spi/20260113141617.1905039-1-s-k6 at ti.com/

Changes in v2:
 - Restructure the .execute_tuning() call from spi-mem clients instead
   of mtdcore with best read_op and write_op (optional) passed
 - Add compatible-specific .execute_tuning() call which can be called by
   spi_mem_execute_tuning() if exists
 - Handle tuning requirement check by controller instead of spi-mem
   clients
 - Add support to write the phy_pattern to cache if relevant write_op
   is passed or get the partition offset which contains the phy_pattern
 - Add tuning algorithm for DDR mode
 - Add support for DQS
 - Restrict PHY frequency to tuned operations
 - Link to v1: https://lore.kernel.org/linux-spi/20250811193219.731851-1-s-k6 at ti.com/

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>

Pratyush Yadav (1):
  mtd: spi-nor: extract read op template construction into helper

Santhosh Kumar K (17):
  spi: dt-bindings: add spi-max-post-config-frequency-hz property
  spi: dt-bindings: add spi-phy-pattern-partition property
  spi: parse spi-max-post-config-frequency-hz into
    post_config_max_speed_hz
  spi: spi-mem: teach spi_mem_adjust_op_freq() about post-config ops
  spi: spi-mem: add execute_tuning callback and spi_mem_execute_tuning()
  spi: cadence-quadspi: move cqspi_readdata_capture earlier
  spi: cadence-quadspi: add DQS support to read data capture
  spi: cadence-quadspi: add PHY tuning support
  spi: cadence-quadspi: skip DDR PHY tuning for 2-byte-address ops
    (i2383)
  spi: cadence-quadspi: refactor direct read path for PHY support
  spi: cadence-quadspi: enable PHY for direct reads
  spi: cadence-quadspi: enable PHY for indirect writes
  spi: cadence-quadspi: reprogram CS timing on every chip-select switch
  spi: cadence-quadspi: reprogram PHY DLL on runtime resume
  mtd: spinand: extract variant ranking logic into
    spinand_op_find_best_variant()
  mtd: spinand: negotiate optimal controller operating point before
    dirmap creation
  mtd: spi-nor: run controller optimization before dirmap creation

 .../bindings/spi/cdns,qspi-nor.yaml           |   17 +
 .../bindings/spi/spi-peripheral-props.yaml    |   13 +
 drivers/mtd/nand/spi/core.c                   |  253 +-
 drivers/mtd/spi-nor/core.c                    |   80 +-
 drivers/spi/spi-cadence-quadspi.c             | 2280 +++++++++++++++--
 drivers/spi/spi-mem.c                         |   55 +-
 drivers/spi/spi.c                             |    2 +
 include/linux/mtd/spi-nor.h                   |    3 +
 include/linux/mtd/spinand.h                   |   11 +
 include/linux/spi/spi-mem.h                   |   14 +
 include/linux/spi/spi.h                       |    3 +
 11 files changed, 2541 insertions(+), 190 deletions(-)

-- 
2.34.1


From s-k6 at ti.com  Tue Aug 11 11:32:57 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:02:57 +0530
Subject: [PATCH v7 02/18] spi: dt-bindings: add spi-phy-pattern-partition property
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-3-s-k6@ti.com>

Add spi-phy-pattern-partition, a per-device phandle property on the
flash sub-node that allows the DT author to directly reference the
partition holding the SPI PHY tuning pattern. Used to locate the
pattern data during PHY tuning when the device cannot load the
pattern dynamically.

PHY tuning works by reading a known data pattern from the device
repeatedly while sweeping controller delay parameters until the
capture window is stable. For SPI NAND, the driver loads the pattern
into the page cache once using write-to-cache opcodes, then reads it
during the sweep. SPI NOR devices have no equivalent opcode, so the
pattern must be pre-programmed in a dedicated flash partition. One
partition per device is required to keep the procedure unambiguous
when multiple devices share a bus.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski at oss.qualcomm.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 .../devicetree/bindings/spi/cdns,qspi-nor.yaml  | 17 +++++++++++++++++
 .../bindings/spi/spi-peripheral-props.yaml      |  7 +++++++
 2 files changed, 24 insertions(+)

diff --git a/Documentation/devicetree/bindings/spi/cdns,qspi-nor.yaml b/Documentation/devicetree/bindings/spi/cdns,qspi-nor.yaml
index 891f578b5ac4..277dedcd4246 100644
--- a/Documentation/devicetree/bindings/spi/cdns,qspi-nor.yaml
+++ b/Documentation/devicetree/bindings/spi/cdns,qspi-nor.yaml
@@ -209,5 +209,22 @@ examples:
             cdns,tsd2d-ns = <60>;
             cdns,tchsh-ns = <60>;
             cdns,tslch-ns = <60>;
+            spi-phy-pattern-partition = <&phy_pattern>;
+
+            partitions {
+                compatible = "fixed-partitions";
+                #address-cells = <1>;
+                #size-cells = <1>;
+
+                partition at 0 {
+                    label = "data";
+                    reg = <0x0 0x3fc0000>;
+                };
+
+                phy_pattern: partition at 3fc0000 {
+                    label = "phy-pattern";
+                    reg = <0x3fc0000 0x40000>;
+                };
+            };
         };
     };
diff --git a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
index 84976fd3ee1b..cc01b643fe9b 100644
--- a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
+++ b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
@@ -123,6 +123,13 @@ properties:
     description:
       Delay, in microseconds, after a write transfer.
 
+  spi-phy-pattern-partition:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Phandle to the flash partition holding the pre-programmed SPI PHY tuning
+      pattern. Used when the device cannot load the pattern dynamically during
+      PHY tuning.
+
   stacked-memories:
     description: Several SPI memories can be wired in stacked mode.
       This basically means that either a device features several chip
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:33:00 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:03:00 +0530
Subject: [PATCH v7 05/18] spi: spi-mem: add execute_tuning callback and spi_mem_execute_tuning()
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-6-s-k6@ti.com>

Add an execute_tuning callback to struct spi_controller_mem_ops. The
callback receives a mandatory read op template and an optional write op
template. On success the controller sets op->max_freq in each provided
template to the validated clock rate. A callback return of 0 with
max_freq left at zero signals that this specific op variant cannot be
PHY-tuned (e.g. a hardware erratum blocks it), allowing the caller to
try a different variant without treating it as a hard error.

Add the corresponding spi_mem_execute_tuning() wrapper. Like other
mem_ops paths it serialises access with spi_mem_access_start/end for
serialisation and runtime PM, and rejects GPIO CS configurations which
cannot support hardware-level tuning.

Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-mem.c       | 43 +++++++++++++++++++++++++++++++++++++
 include/linux/spi/spi-mem.h | 14 ++++++++++++
 2 files changed, 57 insertions(+)

diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index c74858e58073..32773a4372f2 100644
--- a/drivers/spi/spi-mem.c
+++ b/drivers/spi/spi-mem.c
@@ -661,6 +661,49 @@ u64 spi_mem_calc_op_duration(struct spi_mem *mem, struct spi_mem_op *op)
 }
 EXPORT_SYMBOL_GPL(spi_mem_calc_op_duration);
 
+/**
+ * spi_mem_execute_tuning() - Execute controller tuning procedure
+ * @mem: the SPI memory device
+ * @read_op: read operation template (mandatory)
+ * @write_op: write operation template (optional, may be NULL)
+ *
+ * Requests the controller to perform tuning for high-speed operation
+ * using the provided op templates. On success the controller callback
+ * sets @read_op->max_freq (and @write_op->max_freq when non-NULL) to
+ * the validated clock rate.
+ *
+ * Return: 0 on success, -EINVAL if @mem or @read_op is NULL,
+ *         -EOPNOTSUPP if the controller doesn't support tuning,
+ *         or a negative error code on failure.
+ */
+int spi_mem_execute_tuning(struct spi_mem *mem, struct spi_mem_op *read_op,
+			   struct spi_mem_op *write_op)
+{
+	struct spi_controller *ctlr;
+	int ret;
+
+	if (!mem || !read_op)
+		return -EINVAL;
+
+	ctlr = mem->spi->controller;
+	if (!ctlr->mem_ops || !ctlr->mem_ops->execute_tuning)
+		return -EOPNOTSUPP;
+
+	if (spi_get_csgpiod(mem->spi, 0))
+		return -EOPNOTSUPP;
+
+	ret = spi_mem_access_start(mem);
+	if (ret)
+		return ret;
+
+	ret = ctlr->mem_ops->execute_tuning(mem, read_op, write_op);
+
+	spi_mem_access_end(mem);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(spi_mem_execute_tuning);
+
 static ssize_t spi_mem_no_dirmap_read(struct spi_mem_dirmap_desc *desc,
 				      u64 offs, size_t len, void *buf)
 {
diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h
index f660bb2e9f85..ef5a6d60bae9 100644
--- a/include/linux/spi/spi-mem.h
+++ b/include/linux/spi/spi-mem.h
@@ -346,6 +346,15 @@ static inline void *spi_mem_get_drvdata(struct spi_mem *mem)
  * @poll_status: poll memory device status until (status & mask) == match or
  *               when the timeout has expired. It fills the data buffer with
  *               the last status value.
+ * @execute_tuning: run the controller tuning procedure using the provided
+ *		    read and optional write op templates. On success, set
+ *		    @read_op->max_freq (and @write_op->max_freq when non-NULL)
+ *		    to the validated clock rate. Return a negative errno on
+ *		    error. Return -EOPNOTSUPP if the controller has no tuning
+ *		    capability at all. Return 0 with @read_op->max_freq left at
+ *		    zero to signal that this specific op cannot be PHY-tuned
+ *		    (e.g. a hardware erratum blocks it) but another variant may
+ *		    succeed; the caller will iterate remaining op variants.
  *
  * This interface should be implemented by SPI controllers providing an
  * high-level interface to execute SPI memory operation, which is usually the
@@ -376,6 +385,8 @@ struct spi_controller_mem_ops {
 			   unsigned long initial_delay_us,
 			   unsigned long polling_rate_us,
 			   unsigned long timeout_ms);
+	int (*execute_tuning)(struct spi_mem *mem, struct spi_mem_op *read_op,
+			      struct spi_mem_op *write_op);
 };
 
 /**
@@ -465,6 +476,9 @@ int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op);
 void spi_mem_adjust_op_freq(struct spi_mem *mem, struct spi_mem_op *op);
 u64 spi_mem_calc_op_duration(struct spi_mem *mem, struct spi_mem_op *op);
 
+int spi_mem_execute_tuning(struct spi_mem *mem, struct spi_mem_op *read_op,
+			   struct spi_mem_op *write_op);
+
 bool spi_mem_supports_op(struct spi_mem *mem,
 			 const struct spi_mem_op *op);
 
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:32:56 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:02:56 +0530
Subject: [PATCH v7 01/18] spi: dt-bindings: add spi-max-post-config-frequency-hz property
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-2-s-k6@ti.com>

Add spi-max-post-config-frequency-hz, a generic property for SPI
peripherals that operate at two distinct clock rates: a conservative
rate always reachable without controller configuration, and a higher
rate achievable only after controller-side configuration.

When both properties are present, spi-max-frequency gives the
conservative pre-configuration rate and spi-max-post-config-frequency-hz
gives the higher post-configuration target.

Acked-by: Conor Dooley <conor.dooley at microchip.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 .../devicetree/bindings/spi/spi-peripheral-props.yaml       | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
index 880a9f624566..84976fd3ee1b 100644
--- a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
+++ b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
@@ -45,6 +45,12 @@ properties:
     description:
       Maximum SPI clocking speed of the device in Hz.
 
+  spi-max-post-config-frequency-hz:
+    maxItems: 1
+    description:
+      Maximum SPI clock frequency in Hz achievable after controller-side
+      configuration.
+
   spi-cs-setup-delay-ns:
     description:
       Delay in nanoseconds to be introduced by the controller after CS is
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:32:58 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:02:58 +0530
Subject: [PATCH v7 03/18] spi: parse spi-max-post-config-frequency-hz into post_config_max_speed_hz
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-4-s-k6@ti.com>

Add post_config_max_speed_hz to struct spi_device and parse it from
the spi-max-post-config-frequency-hz DT property in of_spi_parse_dt().

This supports SPI devices that operate at two distinct clock rates: a
conservative rate always reachable without controller configuration,
and a higher rate achievable only after controller-side configuration
such as PHY tuning. With both properties set, spi-max-frequency gives
the conservative pre-configuration rate and post_config_max_speed_hz
carries the post-configuration target for the SPI-MEM layer.

Zero when not set, preserving existing behaviour.

Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi.c       | 2 ++
 include/linux/spi/spi.h | 3 +++
 2 files changed, 5 insertions(+)

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 07c572e1df5d..2688e987f900 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -2789,6 +2789,8 @@ static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
 	/* Device speed */
 	if (!of_property_read_u32(nc, "spi-max-frequency", &value))
 		spi->max_speed_hz = value;
+	if (!of_property_read_u32(nc, "spi-max-post-config-frequency-hz", &value))
+		spi->post_config_max_speed_hz = value;
 
 	/* Device CS delays */
 	of_spi_parse_dt_cs_delay(nc, &spi->cs_setup, "spi-cs-setup-delay-ns");
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 88d17fce02dc..02fd602f29e8 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -141,6 +141,8 @@ extern void spi_transfer_cs_change_delay_exec(struct spi_message *msg,
  * @max_speed_hz: Maximum clock rate to be used with this chip
  *	(on this board); may be changed by the device's driver.
  *	The spi_transfer.speed_hz can override this for each transfer.
+ * @post_config_max_speed_hz: Maximum clock rate achievable after controller
+ *	configuration (e.g. PHY tuning); zero when not assigned.
  * @bits_per_word: Data transfers involve one or more words; word sizes
  *	like eight or 12 bits are common.  In-memory wordsizes are
  *	powers of two bytes (e.g. 20 bit samples use 32 bits).
@@ -195,6 +197,7 @@ struct spi_device {
 	struct device		dev;
 	struct spi_controller	*controller;
 	u32			max_speed_hz;
+	u32			post_config_max_speed_hz;
 	u8			bits_per_word;
 	bool			rt;
 #define SPI_NO_TX		BIT(31)		/* No transmit wire */
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:32:59 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:02:59 +0530
Subject: [PATCH v7 04/18] spi: spi-mem: teach spi_mem_adjust_op_freq() about post-config ops
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-5-s-k6@ti.com>

Extend spi_mem_adjust_op_freq() with a bypass for post-config ops:
when op->max_freq equals post_config_max_speed_hz (the value written by
execute_tuning on success), return immediately leaving op->max_freq
unchanged. All other ops are capped to max_speed_hz, the
always-reachable base rate. This places the policy at the single
existing frequency-adjustment point so exec_op(), supports_op(), and
calc_op_duration() all see consistent frequencies.

Also extend spi_mem_default_supports_op() to require per_op_freq
controller capability for post-config ops. A post-config op runs at a
different clock rate than the pre-config base; a controller that cannot
switch frequencies per-op cannot execute it correctly.

Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-mem.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index a88b9f038356..c74858e58073 100644
--- a/drivers/spi/spi-mem.c
+++ b/drivers/spi/spi-mem.c
@@ -206,7 +206,8 @@ bool spi_mem_default_supports_op(struct spi_mem *mem,
 		return false;
 
 	if (op->max_freq &&
-	    op->max_freq < mem->spi->max_speed_hz) {
+	    (op->max_freq < mem->spi->max_speed_hz ||
+	     op->max_freq == mem->spi->post_config_max_speed_hz)) {
 		if (!spi_mem_controller_is_capable(ctlr, per_op_freq))
 			return false;
 	}
@@ -591,9 +592,18 @@ EXPORT_SYMBOL_GPL(spi_mem_adjust_op_size);
  * Some chips have per-op frequency limitations and must adapt the maximum
  * speed. This function allows SPI mem drivers to set @op->max_freq to the
  * maximum supported value.
+ *
+ * When @mem->spi->post_config_max_speed_hz is set, ops with @op->max_freq
+ * equal to that value are treated as post-configuration ops (e.g. PHY-tuned)
+ * and are allowed to run at the full post-config rate. All other ops are
+ * capped to @mem->spi->max_speed_hz, the always-reachable base rate.
  */
 void spi_mem_adjust_op_freq(struct spi_mem *mem, struct spi_mem_op *op)
 {
+	if (mem->spi->post_config_max_speed_hz &&
+	    op->max_freq == mem->spi->post_config_max_speed_hz)
+		return;
+
 	if (!op->max_freq || op->max_freq > mem->spi->max_speed_hz)
 		op->max_freq = mem->spi->max_speed_hz;
 }
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:33:01 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:03:01 +0530
Subject: [PATCH v7 06/18] spi: cadence-quadspi: move cqspi_readdata_capture earlier
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-7-s-k6@ti.com>

Move cqspi_readdata_capture() function earlier in the file. This is
preparatory refactoring for upcoming PHY tuning support that needs to
call it without a forward declaration.

No functional changes.

Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 45 +++++++++++++++----------------
 1 file changed, 22 insertions(+), 23 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index ecb0be394696..f8e266372367 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -457,6 +457,28 @@ static int cqspi_wait_idle(struct cqspi_st *cqspi)
 	}
 }
 
+static void cqspi_readdata_capture(struct cqspi_st *cqspi, const bool bypass,
+				   const unsigned int delay)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+
+	reg = readl(reg_base + CQSPI_REG_READCAPTURE);
+
+	if (bypass)
+		reg |= BIT(CQSPI_REG_READCAPTURE_BYPASS_LSB);
+	else
+		reg &= ~BIT(CQSPI_REG_READCAPTURE_BYPASS_LSB);
+
+	reg &= ~(CQSPI_REG_READCAPTURE_DELAY_MASK
+		 << CQSPI_REG_READCAPTURE_DELAY_LSB);
+
+	reg |= (delay & CQSPI_REG_READCAPTURE_DELAY_MASK)
+	       << CQSPI_REG_READCAPTURE_DELAY_LSB;
+
+	writel(reg, reg_base + CQSPI_REG_READCAPTURE);
+}
+
 static int cqspi_exec_flash_cmd(struct cqspi_st *cqspi, unsigned int reg)
 {
 	void __iomem *reg_base = cqspi->iobase;
@@ -1274,29 +1296,6 @@ static void cqspi_config_baudrate_div(struct cqspi_st *cqspi)
 	writel(reg, reg_base + CQSPI_REG_CONFIG);
 }
 
-static void cqspi_readdata_capture(struct cqspi_st *cqspi,
-				   const bool bypass,
-				   const unsigned int delay)
-{
-	void __iomem *reg_base = cqspi->iobase;
-	unsigned int reg;
-
-	reg = readl(reg_base + CQSPI_REG_READCAPTURE);
-
-	if (bypass)
-		reg |= BIT(CQSPI_REG_READCAPTURE_BYPASS_LSB);
-	else
-		reg &= ~BIT(CQSPI_REG_READCAPTURE_BYPASS_LSB);
-
-	reg &= ~(CQSPI_REG_READCAPTURE_DELAY_MASK
-		 << CQSPI_REG_READCAPTURE_DELAY_LSB);
-
-	reg |= (delay & CQSPI_REG_READCAPTURE_DELAY_MASK)
-		<< CQSPI_REG_READCAPTURE_DELAY_LSB;
-
-	writel(reg, reg_base + CQSPI_REG_READCAPTURE);
-}
-
 static void cqspi_configure(struct cqspi_flash_pdata *f_pdata,
 			    unsigned long sclk)
 {
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:33:04 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:03:04 +0530
Subject: [PATCH v7 09/18] spi: cadence-quadspi: skip DDR PHY tuning for 2-byte-address ops (i2383)
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-10-s-k6@ti.com>

Erratum i2383 on AM654 locks the address phase in PHY DDR mode when a
2-byte column address is used. DDR PHY tuning must not be attempted for
such operations; non-PHY DDR usage is unaffected. [0]

Add CQSPI_NO_PHY_TUNING_WITH_ODDR_2B_ADDR quirk and check it in
cqspi_am654_ospi_execute_tuning(). When the erratum applies, return 0
with read_op->max_freq cleared ? the op-skip signal that tells the
caller to try another op variant.

[0] https://www.ti.com/lit/er/sprz544c/sprz544c.pdf

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 43 +++++++++++++++++++++----------
 1 file changed, 29 insertions(+), 14 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 5651320426b1..a1aaba7c1a7e 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -37,19 +37,20 @@
 static_assert(CQSPI_MAX_CHIPSELECT <= SPI_DEVICE_CS_CNT_MAX);
 
 /* Quirks */
-#define CQSPI_NEEDS_WR_DELAY		BIT(0)
-#define CQSPI_DISABLE_DAC_MODE		BIT(1)
-#define CQSPI_SUPPORT_EXTERNAL_DMA	BIT(2)
-#define CQSPI_NO_SUPPORT_WR_COMPLETION	BIT(3)
-#define CQSPI_SLOW_SRAM			BIT(4)
-#define CQSPI_NEEDS_APB_AHB_HAZARD_WAR	BIT(5)
-#define CQSPI_RD_NO_IRQ			BIT(6)
-#define CQSPI_DMA_SET_MASK		BIT(7)
-#define CQSPI_SUPPORT_DEVICE_RESET	BIT(8)
-#define CQSPI_DISABLE_STIG_MODE		BIT(9)
-#define CQSPI_DISABLE_RUNTIME_PM	BIT(10)
-#define CQSPI_NO_INDIRECT_MODE		BIT(11)
-#define CQSPI_HAS_WR_PROTECT		BIT(12)
+#define CQSPI_NEEDS_WR_DELAY			BIT(0)
+#define CQSPI_DISABLE_DAC_MODE			BIT(1)
+#define CQSPI_SUPPORT_EXTERNAL_DMA		BIT(2)
+#define CQSPI_NO_SUPPORT_WR_COMPLETION		BIT(3)
+#define CQSPI_SLOW_SRAM				BIT(4)
+#define CQSPI_NEEDS_APB_AHB_HAZARD_WAR		BIT(5)
+#define CQSPI_RD_NO_IRQ				BIT(6)
+#define CQSPI_DMA_SET_MASK			BIT(7)
+#define CQSPI_SUPPORT_DEVICE_RESET		BIT(8)
+#define CQSPI_DISABLE_STIG_MODE			BIT(9)
+#define CQSPI_DISABLE_RUNTIME_PM		BIT(10)
+#define CQSPI_NO_INDIRECT_MODE			BIT(11)
+#define CQSPI_HAS_WR_PROTECT			BIT(12)
+#define CQSPI_NO_PHY_TUNING_WITH_ODDR_2B_ADDR	BIT(13)
 
 /* Capabilities */
 #define CQSPI_SUPPORTS_OCTAL		BIT(0)
@@ -3211,6 +3212,20 @@ static int cqspi_am654_ospi_execute_tuning(struct spi_mem *mem,
 		return -EOPNOTSUPP;
 	}
 
+	/*
+	 * Erratum i2383: in PHY DDR mode, a 2-byte column address locks up
+	 * the address phase. Skip DDR PHY tuning for such operations.
+	 */
+	if ((cqspi->ddata->quirks & CQSPI_NO_PHY_TUNING_WITH_ODDR_2B_ADDR) &&
+	    read_op->addr.nbytes == 2 &&
+	    (read_op->cmd.dtr || read_op->addr.dtr || read_op->dummy.dtr ||
+	     read_op->data.dtr)) {
+		dev_dbg(dev,
+			"i2383: skipping DDR PHY tuning (2-byte address)\n");
+		read_op->max_freq = 0;
+		return 0;
+	}
+
 	if (write_op) {
 		/*
 		 * For NAND: write the calibration pattern to the page cache.
@@ -3849,7 +3864,7 @@ static const struct cqspi_driver_platdata k2g_qspi = {
 
 static const struct cqspi_driver_platdata am654_ospi = {
 	.hwcaps_mask = CQSPI_SUPPORTS_OCTAL | CQSPI_SUPPORTS_QUAD,
-	.quirks = CQSPI_NEEDS_WR_DELAY,
+	.quirks = CQSPI_NEEDS_WR_DELAY | CQSPI_NO_PHY_TUNING_WITH_ODDR_2B_ADDR,
 	.execute_tuning = cqspi_am654_ospi_execute_tuning,
 };
 
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:33:05 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:03:05 +0530
Subject: [PATCH v7 10/18] spi: cadence-quadspi: refactor direct read path for PHY support
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-11-s-k6@ti.com>

Extract the DMA transfer code from cqspi_direct_read_execute() into a
new cqspi_direct_read_dma() helper. Add cqspi_memcpy_fromio() to handle
non-DMA transfers, using 2-byte-aligned I/O reads for 8D-8D-8D mode to
satisfy the minimum 16-bit AHB transaction width required by the Cadence
OSPI controller in that mode.

Change cqspi_direct_read_execute() to take the full spi_mem_op and
post_config_max_speed_hz instead of separate buf/from/len parameters,
matching the interface needed by the PHY-aware version in the following
patch. Thread post_config_max_speed_hz from cqspi_mem_process() through
cqspi_read().

Transfers shorter than CQSPI_PHY_MIN_DIRECT_READ_LEN bytes always use
the memcpy path; longer transfers use DMA when available.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 75 ++++++++++++++++++++++++++-----
 1 file changed, 65 insertions(+), 10 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index a1aaba7c1a7e..99a0ec0e769c 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -30,6 +30,7 @@
 #include <linux/spi/spi.h>
 #include <linux/spi/spi-mem.h>
 #include <linux/timer.h>
+#include <linux/unaligned.h>
 
 #define CQSPI_NAME			"cadence-qspi"
 #define CQSPI_MAX_CHIPSELECT		4
@@ -154,6 +155,8 @@ struct cqspi_driver_platdata {
 #define CQSPI_READ_TIMEOUT_MS			10
 #define CQSPI_BUSYWAIT_TIMEOUT_US		500
 #define CQSPI_DLL_TIMEOUT_US			300
+/* Minimum transfer length to use DMA for direct reads */
+#define CQSPI_PHY_MIN_DIRECT_READ_LEN		17
 
 /* Runtime_pm autosuspend delay */
 #define CQSPI_AUTOSUSPEND_TIMEOUT		2000
@@ -1528,8 +1531,8 @@ static void cqspi_rx_dma_callback(void *param)
 	complete(&cqspi->rx_dma_complete);
 }
 
-static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
-				     u_char *buf, loff_t from, size_t len)
+static int cqspi_direct_read_dma(struct cqspi_flash_pdata *f_pdata, u_char *buf,
+				 loff_t from, size_t len)
 {
 	struct cqspi_st *cqspi = f_pdata->cqspi;
 	struct device *dev = &cqspi->pdev->dev;
@@ -1541,11 +1544,6 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
 	dma_addr_t dma_dst;
 	struct device *ddev;
 
-	if (!cqspi->rx_chan || !virt_addr_valid(buf)) {
-		memcpy_fromio(buf, cqspi->ahb_base + from, len);
-		return 0;
-	}
-
 	ddev = cqspi->rx_chan->device->dev;
 	dma_dst = dma_map_single(ddev, buf, len, DMA_FROM_DEVICE);
 	if (dma_mapping_error(ddev, dma_dst)) {
@@ -1587,8 +1585,63 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
 	return ret;
 }
 
+static void cqspi_memcpy_fromio(const struct spi_mem_op *op, void *to,
+				const void __iomem *from, size_t count)
+{
+	if (op->data.buswidth == 8 && op->data.dtr) {
+		unsigned long from_addr = (unsigned long)from;
+
+		/* Handle unaligned start with 2-byte read */
+		if (count && !IS_ALIGNED(from_addr, 4)) {
+			put_unaligned_le16(__raw_readw(from), to);
+			from += 2;
+			to += 2;
+			count -= 2;
+		}
+
+		/*
+		 * Use an explicit 32-bit read loop for the bulk; this
+		 * guarantees the minimum 16-bit AHB transaction width required
+		 * in 8D-8D-8D mode on all architectures.
+		 */
+		while (count >= 4) {
+			put_unaligned_le32(__raw_readl(from), to);
+			from += 4;
+			to += 4;
+			count -= 4;
+		}
+
+		/* Handle remaining 2 bytes */
+		if (count)
+			put_unaligned_le16(__raw_readw(from), to);
+
+		return;
+	}
+
+	memcpy_fromio(to, from, count);
+}
+
+static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
+				     const struct spi_mem_op *op,
+				     u32 post_config_max_speed_hz)
+{
+	struct cqspi_st *cqspi = f_pdata->cqspi;
+	loff_t from = op->addr.val;
+	size_t len = op->data.nbytes;
+	u_char *buf = op->data.buf.in;
+
+	if (!cqspi->rx_chan || !virt_addr_valid(buf) ||
+	    len < CQSPI_PHY_MIN_DIRECT_READ_LEN) {
+		cqspi_memcpy_fromio(op, buf, cqspi->ahb_base + from, len);
+		return 0;
+	}
+
+	return cqspi_direct_read_dma(f_pdata, buf, from, len);
+}
+
 static ssize_t cqspi_read(struct cqspi_flash_pdata *f_pdata,
-			  const struct spi_mem_op *op)
+			  const struct spi_mem_op *op,
+			  u32 post_config_max_speed_hz)
 {
 	struct cqspi_st *cqspi = f_pdata->cqspi;
 	const struct cqspi_driver_platdata *ddata = cqspi->ddata;
@@ -1604,7 +1657,8 @@ static ssize_t cqspi_read(struct cqspi_flash_pdata *f_pdata,
 
 	if ((cqspi->use_direct_mode && ((from + len) <= cqspi->ahb_size)) ||
 	    (cqspi->ddata && cqspi->ddata->quirks & CQSPI_NO_INDIRECT_MODE))
-		return cqspi_direct_read_execute(f_pdata, buf, from, len);
+		return cqspi_direct_read_execute(f_pdata, op,
+						 post_config_max_speed_hz);
 
 	if (cqspi->use_dma_read && ddata && ddata->indirect_read_dma &&
 	    virt_addr_valid(buf) && ((dma_align & CQSPI_DMA_UNALIGN) == 0))
@@ -1632,7 +1686,8 @@ static int cqspi_mem_process(struct spi_mem *mem, const struct spi_mem_op *op)
 		     !cqspi->disable_stig_mode))
 			return cqspi_command_read(f_pdata, op);
 
-		return cqspi_read(f_pdata, op);
+		return cqspi_read(f_pdata, op,
+				  mem->spi->post_config_max_speed_hz);
 	}
 
 	if (!op->addr.nbytes || !op->data.buf.out)
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:33:03 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:03:03 +0530
Subject: [PATCH v7 08/18] spi: cadence-quadspi: add PHY tuning support
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-9-s-k6@ti.com>

The Cadence QSPI controller supports a delay-line PHY for high-speed
operation. Without calibration the PHY is unused and read capture relies
on a fixed delay, limiting throughput at frequencies above the base
operating speed.

Add an execute_tuning callback that performs delay-line calibration using
a known data pattern written to a dedicated flash region. The pattern is
either read from a NOR partition identified by the DT property
spi-phy-pattern-partition, or written to the NAND page cache before
each calibration read.

For DDR protocols (8D-8D-8D) a 2D sweep of (rx_delay, tx_delay) pairs
is performed to find the widest passing region in the combined RX/TX
space. Binary search locates the gap boundary between passing regions
when two separate windows exist; the final operating point is placed at
the centre of the larger region with a small temperature-dependent
offset.

For SDR protocols a 1D sweep of the RX delay is sufficient. Two windows
at adjacent read_delay values are measured; the wider one's midpoint is
selected.

The tuning infrastructure is platform-specific: only am654-based OSPI
controllers populate the execute_tuning hook. All other platform data
entries return -EOPNOTSUPP and are unaffected.

The calibration target is sourced from spi->post_config_max_speed_hz,
populated by the SPI core from the spi-max-post-config-frequency-hz DT
property. Tuning is skipped when post_config_max_speed_hz is zero.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 1769 ++++++++++++++++++++++++++++-
 1 file changed, 1756 insertions(+), 13 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 046fa5d69b0f..5651320426b1 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -6,6 +6,7 @@
 // Copyright Intel Corporation (C) 2019-2020. All rights reserved.
 // Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com
 
+#include <linux/bitfield.h>
 #include <linux/clk.h>
 #include <linux/completion.h>
 #include <linux/delay.h>
@@ -65,15 +66,27 @@ enum {
 
 struct cqspi_st;
 
+struct phy_setting {
+	u8			rx;
+	u8			tx;
+	u8			read_delay;
+};
+
 struct cqspi_flash_pdata {
-	struct cqspi_st	*cqspi;
-	u32		clk_rate;
-	u32		read_delay;
-	u32		tshsl_ns;
-	u32		tsd2d_ns;
-	u32		tchsh_ns;
-	u32		tslch_ns;
-	u8		cs;
+	struct cqspi_st		*cqspi;
+	u32			read_delay;
+	u32			tshsl_ns;
+	u32			tsd2d_ns;
+	u32			tchsh_ns;
+	u32			tslch_ns;
+	bool			use_dqs;
+	bool			use_tuned_phy;
+	u8			cs;
+	struct phy_setting	phy_setting;
+	struct spi_mem_op	phy_read_op;
+	struct spi_mem_op	phy_write_op;
+	u32			phy_pattern_partition_offset;
+	bool			phy_pattern_partition_valid;
 };
 
 static const struct clk_bulk_data cqspi_clks[CLK_QSPI_NUM] = {
@@ -117,6 +130,8 @@ struct cqspi_st {
 	bool			is_jh7110; /* Flag for StarFive JH7110 SoC */
 	bool			is_rzn1; /* Flag for Renesas RZ/N1 SoC */
 	bool			disable_stig_mode;
+	bool			phy_tuning_active;
+	u8			phy_saved_dummy;
 	refcount_t		refcount;
 	refcount_t		inflight_ops;
 
@@ -129,12 +144,15 @@ struct cqspi_driver_platdata {
 	int (*indirect_read_dma)(struct cqspi_flash_pdata *f_pdata,
 				 u_char *rxbuf, loff_t from_addr, size_t n_rx);
 	u32 (*get_dma_status)(struct cqspi_st *cqspi);
+	int (*execute_tuning)(struct spi_mem *mem, struct spi_mem_op *read_op,
+			      struct spi_mem_op *write_op);
 };
 
 /* Operation timeout value */
 #define CQSPI_TIMEOUT_MS			500
 #define CQSPI_READ_TIMEOUT_MS			10
 #define CQSPI_BUSYWAIT_TIMEOUT_US		500
+#define CQSPI_DLL_TIMEOUT_US			300
 
 /* Runtime_pm autosuspend delay */
 #define CQSPI_AUTOSUSPEND_TIMEOUT		2000
@@ -148,12 +166,14 @@ struct cqspi_driver_platdata {
 /* Register map */
 #define CQSPI_REG_CONFIG			0x00
 #define CQSPI_REG_CONFIG_ENABLE_MASK		BIT(0)
+#define CQSPI_REG_CONFIG_PHY_EN			BIT(3)
 #define CQSPI_REG_CONFIG_ENB_DIR_ACC_CTRL	BIT(7)
 #define CQSPI_REG_CONFIG_DECODE_MASK		BIT(9)
 #define CQSPI_REG_CONFIG_CHIPSELECT_LSB		10
 #define CQSPI_REG_CONFIG_DMA_MASK		BIT(15)
 #define CQSPI_REG_CONFIG_BAUD_LSB		19
 #define CQSPI_REG_CONFIG_DTR_PROTO		BIT(24)
+#define CQSPI_REG_CONFIG_PHY_PIPELINE		BIT(25)
 #define CQSPI_REG_CONFIG_DUAL_OPCODE		BIT(30)
 #define CQSPI_REG_CONFIG_IDLE_LSB		31
 #define CQSPI_REG_CONFIG_CHIPSELECT_MASK	0xF
@@ -192,6 +212,7 @@ struct cqspi_driver_platdata {
 #define CQSPI_REG_READCAPTURE_BYPASS_LSB	0
 #define CQSPI_REG_READCAPTURE_DELAY_LSB		1
 #define CQSPI_REG_READCAPTURE_DELAY_MASK	0xF
+#define CQSPI_REG_READCAPTURE_EDGE_LSB		5
 #define CQSPI_REG_READCAPTURE_DQS_LSB		8
 
 #define CQSPI_REG_SIZE				0x14
@@ -273,6 +294,28 @@ struct cqspi_driver_platdata {
 #define CQSPI_REG_POLLING_STATUS		0xB0
 #define CQSPI_REG_POLLING_STATUS_DUMMY_LSB	16
 
+#define CQSPI_REG_PHY_CONFIG			0xB4
+#define CQSPI_REG_PHY_CONFIG_RX_DEL_LSB		0
+#define CQSPI_REG_PHY_CONFIG_RX_DEL_MASK	0x7F
+#define CQSPI_REG_PHY_CONFIG_TX_DEL_LSB		16
+#define CQSPI_REG_PHY_CONFIG_TX_DEL_MASK	0x7F
+#define CQSPI_REG_PHY_CONFIG_DLL_RESET		BIT(30)
+#define CQSPI_REG_PHY_CONFIG_RESYNC		BIT(31)
+
+#define CQSPI_REG_PHY_DLL_MASTER		0xB8
+#define CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_LSB	0
+#define CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_MASK	0x7f
+#define CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_VAL	16
+#define CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LEN	0x7
+#define CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LSB	20
+#define CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_3	0x2
+#define CQSPI_REG_PHY_DLL_MASTER_BYPASS		BIT(23)
+#define CQSPI_REG_PHY_DLL_MASTER_CYCLE		BIT(24)
+
+#define CQSPI_REG_DLL_OBS_LOW			0xBC
+#define CQSPI_REG_DLL_OBS_LOW_DLL_LOCK		BIT(0)
+#define CQSPI_REG_DLL_OBS_LOW_LOOPBACK_LOCK	BIT(15)
+
 #define CQSPI_REG_OP_EXT_LOWER			0xE0
 #define CQSPI_REG_OP_EXT_READ_LSB		24
 #define CQSPI_REG_OP_EXT_WRITE_LSB		16
@@ -321,6 +364,50 @@ struct cqspi_driver_platdata {
 
 #define CQSPI_REG_VERSAL_DMA_VAL		0x602
 
+#define CQSPI_PHY_INIT_RD			1
+#define CQSPI_PHY_MAX_RD			4
+#define CQSPI_PHY_MAX_DELAY			127
+#define CQSPI_PHY_DDR_SEARCH_STEP		4
+#define CQSPI_PHY_TX_LOOKUP_LOW_START		28
+#define CQSPI_PHY_TX_LOOKUP_LOW_END		48
+#define CQSPI_PHY_TX_LOOKUP_HIGH_START		60
+#define CQSPI_PHY_TX_LOOKUP_HIGH_END		96
+#define CQSPI_PHY_RX_LOW_SEARCH_START		0
+#define CQSPI_PHY_RX_LOW_SEARCH_END		40
+#define CQSPI_PHY_RX_HIGH_SEARCH_START		24
+#define CQSPI_PHY_RX_HIGH_SEARCH_END		127
+#define CQSPI_PHY_TX_LOW_SEARCH_START		0
+#define CQSPI_PHY_TX_LOW_SEARCH_END		64
+#define CQSPI_PHY_TX_HIGH_SEARCH_START		78
+#define CQSPI_PHY_TX_HIGH_SEARCH_END		127
+#define CQSPI_PHY_SEARCH_OFFSET			8
+
+#define CQSPI_PHY_DEFAULT_TEMP			45
+#define CQSPI_PHY_MIN_TEMP			-45
+#define CQSPI_PHY_MAX_TEMP			130
+#define CQSPI_PHY_MID_TEMP			(CQSPI_PHY_MIN_TEMP +	\
+						 ((CQSPI_PHY_MAX_TEMP -	\
+						   CQSPI_PHY_MIN_TEMP) / 2))
+
+/*
+ * PHY tuning pattern for calibrating read data capture delay. This 128-byte
+ * pattern provides sufficient bit transitions across all byte lanes to
+ * reliably detect timing windows at high frequencies.
+ */
+static const u8 phy_tuning_pattern[] __aligned(64) = {
+	0xFE, 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xFE, 0xFE, 0x01,
+	0x01, 0x01, 0x01, 0x00, 0x00, 0xFE, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
+	0xFF, 0x00, 0x00, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFE,
+	0xFE, 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFE, 0x00, 0xFE, 0xFE, 0x01,
+	0x01, 0x01, 0x01, 0xFE, 0x00, 0xFE, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
+	0xFF, 0xFE, 0x00, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFE,
+	0xFE, 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xFE, 0xFE, 0xFE, 0x01,
+	0x01, 0x01, 0x01, 0x00, 0xFE, 0xFE, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
+	0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFE, 0xFE,
+	0xFE, 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFE, 0xFE, 0xFE, 0xFE, 0x01,
+	0x01, 0x01, 0x01, 0xFE, 0xFE, 0xFE, 0xFE, 0x01,
+};
+
 static int cqspi_wait_for_bit(const struct cqspi_driver_platdata *ddata,
 			      void __iomem *reg, const u32 mask, bool clr,
 			      bool busywait)
@@ -913,6 +1000,76 @@ static void cqspi_controller_enable(struct cqspi_st *cqspi, bool enable)
 	writel(reg, reg_base + CQSPI_REG_CONFIG);
 }
 
+static void cqspi_set_dll(void __iomem *reg_base, u8 rx_dll, u8 tx_dll)
+{
+	unsigned int reg;
+
+	reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+	reg &= ~((CQSPI_REG_PHY_CONFIG_RX_DEL_MASK
+		  << CQSPI_REG_PHY_CONFIG_RX_DEL_LSB) |
+		 (CQSPI_REG_PHY_CONFIG_TX_DEL_MASK
+		  << CQSPI_REG_PHY_CONFIG_TX_DEL_LSB));
+	reg |= ((rx_dll & CQSPI_REG_PHY_CONFIG_RX_DEL_MASK)
+		<< CQSPI_REG_PHY_CONFIG_RX_DEL_LSB) |
+	       ((tx_dll & CQSPI_REG_PHY_CONFIG_TX_DEL_MASK)
+		<< CQSPI_REG_PHY_CONFIG_TX_DEL_LSB) |
+	       CQSPI_REG_PHY_CONFIG_RESYNC;
+	writel(reg, reg_base + CQSPI_REG_PHY_CONFIG);
+}
+
+static int cqspi_resync_dll(struct cqspi_st *cqspi)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+	int ret;
+
+	ret = cqspi_wait_idle(cqspi);
+	if (ret)
+		return ret;
+
+	reg = readl(reg_base + CQSPI_REG_CONFIG);
+	reg &= ~CQSPI_REG_CONFIG_ENABLE_MASK;
+	writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+	reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+	reg &= ~(CQSPI_REG_PHY_CONFIG_DLL_RESET | CQSPI_REG_PHY_CONFIG_RESYNC);
+	writel(reg, reg_base + CQSPI_REG_PHY_CONFIG);
+
+	reg = readl(reg_base + CQSPI_REG_PHY_DLL_MASTER);
+	reg &= ~(CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_MASK
+		 << CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_LSB);
+	reg |= (CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_VAL
+		<< CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_LSB);
+	writel(reg, reg_base + CQSPI_REG_PHY_DLL_MASTER);
+
+	reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+	reg |= CQSPI_REG_PHY_CONFIG_DLL_RESET;
+	writel(reg, reg_base + CQSPI_REG_PHY_CONFIG);
+
+	ret = readl_poll_timeout(reg_base + CQSPI_REG_DLL_OBS_LOW, reg,
+				 (reg & CQSPI_REG_DLL_OBS_LOW_DLL_LOCK), 0,
+				 CQSPI_DLL_TIMEOUT_US);
+	if (ret)
+		goto re_enable;
+
+	ret = readl_poll_timeout(reg_base + CQSPI_REG_DLL_OBS_LOW, reg,
+				 (reg & CQSPI_REG_DLL_OBS_LOW_LOOPBACK_LOCK), 0,
+				 CQSPI_DLL_TIMEOUT_US);
+	if (ret)
+		goto re_enable;
+
+	reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+	reg |= CQSPI_REG_PHY_CONFIG_RESYNC;
+	writel(reg, reg_base + CQSPI_REG_PHY_CONFIG);
+
+re_enable:
+	reg = readl(reg_base + CQSPI_REG_CONFIG);
+	reg |= CQSPI_REG_CONFIG_ENABLE_MASK;
+	writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+	return ret;
+}
+
 static int cqspi_versal_indirect_read_dma(struct cqspi_flash_pdata *f_pdata,
 					  u_char *rxbuf, loff_t from_addr,
 					  size_t n_rx)
@@ -1323,8 +1480,9 @@ static void cqspi_configure(struct cqspi_flash_pdata *f_pdata,
 		cqspi->sclk = sclk;
 		cqspi_config_baudrate_div(cqspi);
 		cqspi_delay(f_pdata);
-		cqspi_readdata_capture(cqspi, !cqspi->rclk_en, false,
-				       f_pdata->read_delay);
+		if (!cqspi->phy_tuning_active)
+			cqspi_readdata_capture(cqspi, !cqspi->rclk_en, false,
+					       f_pdata->read_delay);
 	}
 
 	if (switch_cs || switch_ck)
@@ -1559,10 +1717,1590 @@ static bool cqspi_supports_mem_op(struct spi_mem *mem,
 	return spi_mem_default_supports_op(mem, op);
 }
 
+static int cqspi_write_pattern_to_cache(struct cqspi_flash_pdata *f_pdata,
+					struct spi_mem *mem,
+					const struct spi_mem_op *write_op)
+{
+	struct spi_controller *ctlr = mem->spi->controller;
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	struct spi_mem_op op = *write_op;
+	int ret;
+
+	op.max_freq = mem->spi->max_speed_hz;
+	op.data.nbytes = sizeof(phy_tuning_pattern);
+	op.data.buf.out = phy_tuning_pattern;
+
+	ret = ctlr->mem_ops->exec_op(mem, &op);
+	if (ret) {
+		dev_err(dev, "Failed to write PHY pattern to cache: %d\n", ret);
+		return ret;
+	}
+	dev_dbg(dev, "PHY pattern (%zu bytes) written to cache\n",
+		sizeof(phy_tuning_pattern));
+
+	return 0;
+}
+
+static int cqspi_phy_check_pattern(struct cqspi_flash_pdata *f_pdata,
+				   struct spi_mem *mem)
+{
+	struct spi_controller *ctlr = mem->spi->controller;
+	struct spi_mem_op op;
+	u8 *read_data;
+	int ret;
+
+	read_data = kmalloc_array(ARRAY_SIZE(phy_tuning_pattern),
+				  sizeof(phy_tuning_pattern[0]), GFP_KERNEL);
+	if (!read_data)
+		return -ENOMEM;
+
+	op = f_pdata->phy_read_op;
+	op.data.buf.in = read_data;
+	op.data.nbytes = sizeof(phy_tuning_pattern);
+
+	ret = ctlr->mem_ops->exec_op(mem, &op);
+	if (ret)
+		goto out;
+
+	if (memcmp(read_data, phy_tuning_pattern, sizeof(phy_tuning_pattern)))
+		ret = -EAGAIN;
+
+out:
+	kfree(read_data);
+	return ret;
+}
+
+static void cqspi_phy_set_dll_master(struct cqspi_st *cqspi)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+
+	reg = readl(reg_base + CQSPI_REG_PHY_DLL_MASTER);
+	reg &= ~((CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LEN
+		  << CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LSB) |
+		 CQSPI_REG_PHY_DLL_MASTER_BYPASS |
+		 CQSPI_REG_PHY_DLL_MASTER_CYCLE);
+	reg |= ((CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_3
+		 << CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LSB) |
+		CQSPI_REG_PHY_DLL_MASTER_CYCLE);
+
+	writel(reg, reg_base + CQSPI_REG_PHY_DLL_MASTER);
+}
+
+static void cqspi_phy_pre_config(struct cqspi_st *cqspi,
+				 struct cqspi_flash_pdata *f_pdata,
+				 const bool bypass)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+	u8 dummy;
+
+	cqspi->phy_tuning_active = true;
+
+	cqspi_readdata_capture(cqspi, bypass, f_pdata->use_dqs,
+			       f_pdata->phy_setting.read_delay);
+
+	reg = readl(reg_base + CQSPI_REG_CONFIG);
+	reg &= ~(CQSPI_REG_CONFIG_PHY_EN | CQSPI_REG_CONFIG_PHY_PIPELINE);
+	reg |= CQSPI_REG_CONFIG_PHY_EN;
+	writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+	reg = readl(reg_base + CQSPI_REG_RD_INSTR);
+	dummy = FIELD_GET(CQSPI_REG_RD_INSTR_DUMMY_MASK
+				  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+			  reg);
+	cqspi->phy_saved_dummy = dummy;
+	if (dummy)
+		dummy--;
+	reg &= ~(CQSPI_REG_RD_INSTR_DUMMY_MASK << CQSPI_REG_RD_INSTR_DUMMY_LSB);
+	reg |= FIELD_PREP(CQSPI_REG_RD_INSTR_DUMMY_MASK
+				  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+			  dummy);
+	writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+
+	cqspi_phy_set_dll_master(cqspi);
+}
+
+static void cqspi_phy_post_config(struct cqspi_st *cqspi,
+				  const unsigned int delay)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+
+	cqspi->phy_tuning_active = false;
+
+	/*
+	 * Clear the EDGE bit set by cqspi_phy_apply_setting, then restore the
+	 * READCAPTURE register to its non-PHY state (bypass, DQS, and delay).
+	 */
+	reg = readl(reg_base + CQSPI_REG_READCAPTURE);
+	reg &= ~BIT(CQSPI_REG_READCAPTURE_EDGE_LSB);
+	writel(reg, reg_base + CQSPI_REG_READCAPTURE);
+
+	cqspi_readdata_capture(cqspi, !cqspi->rclk_en, false, delay);
+
+	reg = readl(reg_base + CQSPI_REG_CONFIG);
+	reg &= ~(CQSPI_REG_CONFIG_PHY_EN | CQSPI_REG_CONFIG_PHY_PIPELINE);
+	writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+	/* Restore the original dummy cycle count saved by cqspi_phy_pre_config. */
+	reg = readl(reg_base + CQSPI_REG_RD_INSTR);
+	reg &= ~(CQSPI_REG_RD_INSTR_DUMMY_MASK << CQSPI_REG_RD_INSTR_DUMMY_LSB);
+	reg |= FIELD_PREP(CQSPI_REG_RD_INSTR_DUMMY_MASK
+				  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+			  cqspi->phy_saved_dummy);
+	writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+}
+
+static int cqspi_phy_apply_setting(struct cqspi_flash_pdata *f_pdata,
+				   struct phy_setting *phy)
+{
+	struct cqspi_st *cqspi = f_pdata->cqspi;
+	unsigned int reg;
+	int ret;
+
+	reg = readl(cqspi->iobase + CQSPI_REG_READCAPTURE);
+	reg |= BIT(CQSPI_REG_READCAPTURE_EDGE_LSB);
+	writel(reg, cqspi->iobase + CQSPI_REG_READCAPTURE);
+
+	cqspi_set_dll(cqspi->iobase, phy->rx, phy->tx);
+
+	ret = cqspi_resync_dll(cqspi);
+	if (ret)
+		return ret;
+
+	f_pdata->phy_setting.rx = phy->rx;
+	f_pdata->phy_setting.tx = phy->tx;
+	f_pdata->phy_setting.read_delay = phy->read_delay;
+
+	cqspi_readdata_capture(cqspi, !f_pdata->use_dqs, f_pdata->use_dqs,
+			       phy->read_delay);
+
+	return 0;
+}
+
+static int cqspi_find_rx_low_ddr(struct cqspi_flash_pdata *f_pdata,
+				 struct spi_mem *mem, struct phy_setting *phy)
+{
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	int ret;
+
+	do {
+		phy->rx = CQSPI_PHY_RX_LOW_SEARCH_START;
+		do {
+			ret = cqspi_phy_apply_setting(f_pdata, phy);
+			if (!ret) {
+				ret = cqspi_phy_check_pattern(f_pdata, mem);
+				if (!ret)
+					return 0;
+			}
+
+			phy->rx += CQSPI_PHY_DDR_SEARCH_STEP;
+		} while (phy->rx <= CQSPI_PHY_RX_LOW_SEARCH_END);
+
+		phy->read_delay++;
+	} while (phy->read_delay <= CQSPI_PHY_MAX_RD);
+
+	dev_dbg(dev, "Unable to find RX low\n");
+	return -ENOENT;
+}
+
+static int cqspi_find_rx_low_sdr(struct cqspi_flash_pdata *f_pdata,
+				 struct spi_mem *mem, struct phy_setting *phy)
+{
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	int ret;
+
+	phy->rx = 0;
+	do {
+		ret = cqspi_phy_apply_setting(f_pdata, phy);
+		if (!ret) {
+			ret = cqspi_phy_check_pattern(f_pdata, mem);
+			if (!ret)
+				return 0;
+		}
+		phy->rx++;
+	} while (phy->rx <= CQSPI_PHY_MAX_DELAY);
+
+	dev_dbg(dev, "Unable to find RX low\n");
+	return -ENOENT;
+}
+
+static int cqspi_find_rx_high_ddr(struct cqspi_flash_pdata *f_pdata,
+				  struct spi_mem *mem, struct phy_setting *phy)
+{
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	int ret;
+
+	do {
+		phy->rx = CQSPI_PHY_RX_HIGH_SEARCH_END;
+		do {
+			ret = cqspi_phy_apply_setting(f_pdata, phy);
+			if (!ret) {
+				ret = cqspi_phy_check_pattern(f_pdata, mem);
+				if (!ret)
+					return 0;
+			}
+
+			phy->rx -= CQSPI_PHY_DDR_SEARCH_STEP;
+		} while (phy->rx >= CQSPI_PHY_RX_HIGH_SEARCH_START);
+
+		phy->read_delay--;
+	} while (phy->read_delay >= CQSPI_PHY_INIT_RD);
+
+	dev_dbg(dev, "Unable to find RX high\n");
+	return -ENOENT;
+}
+
+static int cqspi_find_rx_high_sdr(struct cqspi_flash_pdata *f_pdata,
+				  struct spi_mem *mem, struct phy_setting *phy,
+				  u8 lowerbound)
+{
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	int ret;
+
+	phy->rx = CQSPI_PHY_MAX_DELAY;
+	do {
+		ret = cqspi_phy_apply_setting(f_pdata, phy);
+		if (!ret) {
+			ret = cqspi_phy_check_pattern(f_pdata, mem);
+			if (!ret)
+				return 0;
+		}
+		phy->rx--;
+	} while (phy->rx > lowerbound);
+
+	dev_dbg(dev, "Unable to find RX high\n");
+	return -ENOENT;
+}
+
+static int cqspi_find_tx_low_ddr(struct cqspi_flash_pdata *f_pdata,
+				 struct spi_mem *mem, struct phy_setting *phy)
+{
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	int ret;
+
+	do {
+		phy->tx = CQSPI_PHY_TX_LOW_SEARCH_START;
+		do {
+			ret = cqspi_phy_apply_setting(f_pdata, phy);
+			if (!ret) {
+				ret = cqspi_phy_check_pattern(f_pdata, mem);
+				if (!ret)
+					return 0;
+			}
+
+			phy->tx += CQSPI_PHY_DDR_SEARCH_STEP;
+		} while (phy->tx <= CQSPI_PHY_TX_LOW_SEARCH_END);
+
+		phy->read_delay++;
+	} while (phy->read_delay <= CQSPI_PHY_MAX_RD);
+
+	dev_dbg(dev, "Unable to find TX low\n");
+	return -ENOENT;
+}
+
+static int cqspi_find_tx_high_ddr(struct cqspi_flash_pdata *f_pdata,
+				  struct spi_mem *mem, struct phy_setting *phy)
+{
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	int ret;
+
+	do {
+		phy->tx = CQSPI_PHY_TX_HIGH_SEARCH_END;
+		do {
+			ret = cqspi_phy_apply_setting(f_pdata, phy);
+			if (!ret) {
+				ret = cqspi_phy_check_pattern(f_pdata, mem);
+				if (!ret)
+					return 0;
+			}
+
+			phy->tx -= CQSPI_PHY_DDR_SEARCH_STEP;
+		} while (phy->tx >= CQSPI_PHY_TX_HIGH_SEARCH_START);
+
+		phy->read_delay--;
+	} while (phy->read_delay >= CQSPI_PHY_INIT_RD);
+
+	dev_dbg(dev, "Unable to find TX high\n");
+	return -ENOENT;
+}
+
+static void cqspi_phy_find_gaplow_ddr(struct cqspi_flash_pdata *f_pdata,
+				      struct spi_mem *mem,
+				      struct phy_setting *bottomleft,
+				      struct phy_setting *topright,
+				      struct phy_setting *gaplow)
+{
+	struct phy_setting left, right, mid;
+	int ret;
+
+	left = *bottomleft;
+	right = *topright;
+
+	mid.tx = left.tx + ((right.tx - left.tx) / 2);
+	mid.rx = left.rx + ((right.rx - left.rx) / 2);
+	mid.read_delay = left.read_delay;
+
+	do {
+		ret = cqspi_phy_apply_setting(f_pdata, &mid);
+		if (!ret)
+			ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+		if (ret) {
+			/* The pattern was not found. Go to the lower half. */
+			right.tx = mid.tx;
+			right.rx = mid.rx;
+
+			mid.tx = left.tx + ((mid.tx - left.tx) / 2);
+			mid.rx = left.rx + ((mid.rx - left.rx) / 2);
+		} else {
+			/* The pattern was found. Go to the upper half. */
+			left.tx = mid.tx;
+			left.rx = mid.rx;
+
+			mid.tx = mid.tx + ((right.tx - mid.tx) / 2);
+			mid.rx = mid.rx + ((right.rx - mid.rx) / 2);
+		}
+
+		/* Break the loop if the window has closed. */
+	} while ((right.tx - left.tx >= 2) && (right.rx - left.rx >= 2));
+
+	*gaplow = mid;
+}
+
+static void cqspi_phy_find_gaphigh_ddr(struct cqspi_flash_pdata *f_pdata,
+				       struct spi_mem *mem,
+				       struct phy_setting *bottomleft,
+				       struct phy_setting *topright,
+				       struct phy_setting *gaphigh)
+{
+	struct phy_setting left, right, mid;
+	int ret;
+
+	left = *bottomleft;
+	right = *topright;
+
+	mid.tx = left.tx + ((right.tx - left.tx) / 2);
+	mid.rx = left.rx + ((right.rx - left.rx) / 2);
+	mid.read_delay = right.read_delay;
+
+	do {
+		ret = cqspi_phy_apply_setting(f_pdata, &mid);
+		if (!ret)
+			ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+		if (ret) {
+			/* The pattern was not found. Go to the upper half. */
+			left.tx = mid.tx;
+			left.rx = mid.rx;
+
+			mid.tx = mid.tx + ((right.tx - mid.tx) / 2);
+			mid.rx = mid.rx + ((right.rx - mid.rx) / 2);
+		} else {
+			/* The pattern was found. Go to the lower half. */
+			right.tx = mid.tx;
+			right.rx = mid.rx;
+
+			mid.tx = left.tx + ((mid.tx - left.tx) / 2);
+			mid.rx = left.rx + ((mid.rx - left.rx) / 2);
+		}
+
+		/* Break the loop if the window has closed. */
+	} while ((right.tx - left.tx >= 2) && (right.rx - left.rx >= 2));
+
+	*gaphigh = mid;
+}
+
+static int cqspi_get_temp(int *temp)
+{
+	/* TODO: read SoC thermal sensor; caller falls back to room temperature */
+	return -EOPNOTSUPP;
+}
+
+static inline void cqspi_phy_reset_setting(struct phy_setting *phy)
+{
+	*phy = (struct phy_setting){ .rx = 0, .tx = 127, .read_delay = 0 };
+}
+
+static int cqspi_phy_tuning_ddr(struct cqspi_flash_pdata *f_pdata,
+				struct spi_mem *mem)
+{
+	struct cqspi_st *cqspi = f_pdata->cqspi;
+	struct device *dev = &cqspi->pdev->dev;
+	struct phy_setting rxlow, rxhigh, txlow, txhigh;
+	struct phy_setting srxlow, srxhigh;
+	struct phy_setting bottomleft, topright, searchpoint;
+	struct phy_setting gaplow, gaphigh;
+	struct phy_setting backuppoint, backupcornerpoint;
+	int ret, rx_window, temp;
+	bool primary = true, secondary = true;
+
+	/*
+	 * DDR tuning: 2D search across RX and TX delays for optimal timing.
+	 *
+	 * Algorithm: Find RX boundaries (rxlow/rxhigh) using TX window search,
+	 * find TX boundaries (txlow/txhigh) at fixed RX, define valid region,
+	 * locate gaps via binary search, select final point with temperature
+	 * compensation.
+	 *
+	 *              rx
+	 *          127 ^
+	 *              |                                 topright
+	 *              |                                    *
+	 *              |       xxxxx     ++++++++++++++++++++
+	 *              |       xxxxxx     +++++++++++++++++++
+	 *              |       xxxxxxx     ++++++++++++++++++
+	 *              |       xxxxxxxx     +++++++++++++++++
+	 *              |       xxxxxxxxx     ++++++++++++++++
+	 *              |       xxxxxxxxxx     +++++++++++++++
+	 *              |       xxxxxxxxxxx     ++++++++++++++
+	 *              |       xxxxxxxxxxxx     +++++++++++++
+	 *              |       xxxxxxxxxxxxx     ++++++++++++
+	 *              |       xxxxxxxxxxxxxx     +++++++++++
+	 *              |       xxxxxxxxxxxxxxx     ++++++++++
+	 *              |       xxxxxxxxxxxxxxxx     +++++++++
+	 *              |       xxxxxxxxxxxxxxxxx     ++++++++
+	 *              |       xxxxxxxxxxxxxxxxxx     +++++++
+	 *              |       *
+	 *              |  bottomleft
+	 *              -----------------------------------------> tx
+	 *              0                                      127
+	 */
+
+	f_pdata->use_tuned_phy = true;
+
+	/* Golden rxlow search: Find lower RX boundary using TX window sweep */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |       xxxxx     ++++++++++++++++++++
+	 *              |       xxxxxx     +++++++++++++++++++
+	 *              |       xxxxxxx     ++++++++++++++++++
+	 *              |       xxxxxxxx     +++++++++++++++++
+	 *              |       xxxxxxxxx     ++++++++++++++++
+	 *              |       xxxxxxxxxx     +++++++++++++++
+	 *              |       xxxxxxxxxxx     ++++++++++++++
+	 *              |       |xxxxx|xxxxx     +++++++++++++
+	 *              |       |xxxxx|xxxxxx     ++++++++++++
+	 *      search  |       |xxxxx|xxxxxxx     +++++++++++
+	 *      rxlow --------->|xxxxx|xxxxxxxx     ++++++++++
+	 *              |       |xxxxx|xxxxxxxxx     +++++++++
+	 *              |       |xxxxx|xxxxxxxxxx     ++++++++
+	 *              |       |xxxxx|xxxxxxxxxxx     +++++++
+	 *              |       |     |
+	 *              --------|-----|----------------------------> tx
+	 *              0       |     |                          127
+	 *                  txlow     txlow
+	 *                  start     end
+	 *
+	 *      |----------------------------------------------------------|
+	 *      | Primary | Secondary | Final                              |
+	 *      | Search  | Search    | Point                              |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Fail    | Fail      | Return Fail                        |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Fail    | Pass      | Return Fail                        |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Pass    | Fail      | Return Fail                        |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Pass    | Pass      | rx = min(primary.rx, secondary.rx) |
+	 *      |         |           | tx = primary.tx                    |
+	 *      |         |           | read_delay =                       |
+	 *      |         |           |         min(primary.read_delay,    |
+	 *      |         |           |             secondary.read_delay)  |
+	 *      |----------------------------------------------------------|
+	 */
+
+	/* Primary rxlow: Sweep TX window to find valid RX lower bound */
+
+	rxlow.tx = CQSPI_PHY_TX_LOOKUP_LOW_START;
+	do {
+		dev_dbg(dev, "Searching for Golden Primary rxlow on TX = %d\n",
+			rxlow.tx);
+		rxlow.read_delay = CQSPI_PHY_INIT_RD;
+		ret = cqspi_find_rx_low_ddr(f_pdata, mem, &rxlow);
+		rxlow.tx += CQSPI_PHY_DDR_SEARCH_STEP;
+	} while (ret && rxlow.tx <= CQSPI_PHY_TX_LOOKUP_LOW_END);
+	if (ret)
+		goto out;
+	dev_dbg(dev, "Golden Primary rxlow: RX: %d TX: %d RD: %d\n", rxlow.rx,
+		rxlow.tx, rxlow.read_delay);
+
+	/* Secondary rxlow: Verify at offset TX for robustness */
+
+	if (rxlow.tx <= (CQSPI_PHY_TX_LOOKUP_LOW_END - CQSPI_PHY_SEARCH_OFFSET))
+		srxlow.tx = rxlow.tx + CQSPI_PHY_SEARCH_OFFSET;
+	else
+		srxlow.tx = CQSPI_PHY_TX_LOOKUP_LOW_END;
+	dev_dbg(dev, "Searching for Golden Secondary rxlow on TX = %d\n",
+		srxlow.tx);
+	srxlow.read_delay = CQSPI_PHY_INIT_RD;
+	ret = cqspi_find_rx_low_ddr(f_pdata, mem, &srxlow);
+	if (ret)
+		goto out;
+	dev_dbg(dev, "Golden Secondary rxlow: RX: %d TX: %d RD: %d\n",
+		srxlow.rx, srxlow.tx, srxlow.read_delay);
+
+	rxlow.rx = min(rxlow.rx, srxlow.rx);
+	rxlow.read_delay = min(rxlow.read_delay, srxlow.read_delay);
+	dev_dbg(dev, "Golden Final rxlow: RX: %d TX: %d RD: %d\n", rxlow.rx,
+		rxlow.tx, rxlow.read_delay);
+
+	/* Golden rxhigh search: Find upper RX boundary at fixed TX */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |       |xxxx     ++++++++++++++++++++
+	 *              |       |xxxxx     +++++++++++++++++++
+	 *    search    |       |xxxxxx     ++++++++++++++++++
+	 *    rxhigh  --------->|xxxxxxx     +++++++++++++++++
+	 *    on fixed  |       |xxxxxxxx     ++++++++++++++++
+	 *    tx        |       |xxxxxxxxx     +++++++++++++++
+	 *              |       |xxxxxxxxxx     ++++++++++++++
+	 *              |       xxxxxxxxxxxx     +++++++++++++
+	 *              |       xxxxxxxxxxxxx     ++++++++++++
+	 *              |       xxxxxxxxxxxxxx     +++++++++++
+	 *              |       xxxxxxxxxxxxxxx     ++++++++++
+	 *              |       xxxxxxxxxxxxxxxx     +++++++++
+	 *              |       xxxxxxxxxxxxxxxxx     ++++++++
+	 *              |       xxxxxxxxxxxxxxxxxx     +++++++
+	 *              |
+	 *              -------------------------------------------> tx
+	 *              0                                        127
+	 *
+	 *      |----------------------------------------------------------|
+	 *      | Primary | Secondary | Final                              |
+	 *      | Search  | Search    | Point                              |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Fail    | Fail      | Return Fail                        |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Fail    | Pass      | Choose Secondary                   |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Pass    | Fail      | Choose Primary                     |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Pass    | Pass      | if (secondary.rx > primary.rx)     |
+	 *      |         |           |         Choose Secondary           |
+	 *      |         |           | else                               |
+	 *      |         |           |         Choose Primary             |
+	 *      |----------------------------------------------------------|
+	 */
+
+	/* Primary rxhigh: Search at rxlow's TX, decrement from max read_delay */
+
+	rxhigh.tx = rxlow.tx;
+	dev_dbg(dev, "Searching for Golden Primary rxhigh on TX = %d\n",
+		rxhigh.tx);
+	rxhigh.read_delay = CQSPI_PHY_MAX_RD;
+	ret = cqspi_find_rx_high_ddr(f_pdata, mem, &rxhigh);
+	if (ret)
+		primary = false;
+	dev_dbg(dev, "Golden Primary rxhigh: RX: %d TX: %d RD: %d\n", rxhigh.rx,
+		rxhigh.tx, rxhigh.read_delay);
+
+	/* Secondary rxhigh: Verify at offset TX */
+
+	if (rxhigh.tx <=
+	    (CQSPI_PHY_TX_LOOKUP_LOW_END - CQSPI_PHY_SEARCH_OFFSET))
+		srxhigh.tx = rxhigh.tx + CQSPI_PHY_SEARCH_OFFSET;
+	else
+		srxhigh.tx = CQSPI_PHY_TX_LOOKUP_LOW_END;
+	dev_dbg(dev, "Searching for Golden Secondary rxhigh on TX = %d\n",
+		srxhigh.tx);
+	srxhigh.read_delay = CQSPI_PHY_MAX_RD;
+	ret = cqspi_find_rx_high_ddr(f_pdata, mem, &srxhigh);
+	if (ret)
+		secondary = false;
+	dev_dbg(dev, "Golden Secondary rxhigh: RX: %d TX: %d RD: %d\n",
+		srxhigh.rx, srxhigh.tx, srxhigh.read_delay);
+
+	if (!primary && !secondary)
+		goto out;
+	else if (!primary)
+		rxhigh = srxhigh;
+	else if (secondary && srxhigh.rx > rxhigh.rx)
+		rxhigh = srxhigh;
+	dev_dbg(dev, "Golden Final rxhigh: RX: %d TX: %d RD: %d\n", rxhigh.rx,
+		rxhigh.tx, rxhigh.read_delay);
+
+	primary = true;
+	secondary = true;
+
+	/* If rxlow/rxhigh at same read_delay, search backup at upper TX range */
+
+	if (rxlow.read_delay == rxhigh.read_delay) {
+		dev_dbg(dev, "rxlow and rxhigh at the same read delay.\n");
+
+		/* Backup rxlow: Search at high TX window */
+
+		/*
+		 *              rx
+		 *          127 ^
+		 *              |       xxxxx     ++++++++++++++++++++
+		 *              |       xxxxxx     +++++++++++++++++++
+		 *              |       xxxxxxx     ++++++++++++++++++
+		 *              |       xxxxxxxx     +++++++++++++++++
+		 *              |       xxxxxxxxx     ++++++++++++++++
+		 *              |       xxxxxxxxxx     +++++++++++++++
+		 *              |       xxxxxxxxxxx     ++++++++++++++
+		 *              |       xxxxxxxxxxxx     +++++++|++++|
+		 *              |       xxxxxxxxxxxxx     ++++++|++++|
+		 *      search  |       xxxxxxxxxxxxxx     +++++|++++|
+		 *      rxlow --------------------------------->|++++|
+		 *              |       xxxxxxxxxxxxxxxx     +++|++++|
+		 *              |       xxxxxxxxxxxxxxxxx     ++|++++|
+		 *              |       xxxxxxxxxxxxxxxxxx     +|++++|
+		 *              |                               |    |
+		 *              --------------------------------|----|-----> tx
+		 *              0                               |    |   127
+		 *                                         txhigh    txhigh
+		 *                                          start    end
+		 *
+		 *      |-----------------------------------------------------|
+		 *      | Primary | Secondary | Final                         |
+		 *      | Search  | Search    | Point                         |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Fail    | Fail      | Return Fail                   |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Fail    | Pass      | Return Fail                   |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Pass    | Fail      | Return Fail                   |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Pass    | Pass      | rx =                          |
+		 *      |         |           |  min(primary.rx, secondary.rx)|
+		 *      |         |           | tx = primary.tx               |
+		 *      |         |           | read_delay =                  |
+		 *      |         |           |  min(primary.read_delay,      |
+		 *      |         |           |      secondary.read_delay)    |
+		 *      |-----------------------------------------------------|
+		 */
+
+		/* Primary backup: Decrement TX from high window end */
+
+		backuppoint.tx = CQSPI_PHY_TX_LOOKUP_HIGH_END;
+		do {
+			dev_dbg(dev,
+				"Searching for Backup Primary rxlow on TX = %d\n",
+				backuppoint.tx);
+			backuppoint.read_delay = CQSPI_PHY_INIT_RD;
+			ret = cqspi_find_rx_low_ddr(f_pdata, mem, &backuppoint);
+			backuppoint.tx -= CQSPI_PHY_DDR_SEARCH_STEP;
+		} while (ret &&
+			 backuppoint.tx >= CQSPI_PHY_TX_LOOKUP_HIGH_START);
+		if (ret)
+			goto out;
+		dev_dbg(dev, "Backup Primary rxlow: RX: %d TX: %d RD: %d\n",
+			backuppoint.rx, backuppoint.tx, backuppoint.read_delay);
+
+		/* Secondary backup: Verify at offset TX */
+
+		if (backuppoint.tx >=
+		    (CQSPI_PHY_TX_LOOKUP_HIGH_START + CQSPI_PHY_SEARCH_OFFSET))
+			srxlow.tx = backuppoint.tx - CQSPI_PHY_SEARCH_OFFSET;
+		else
+			srxlow.tx = CQSPI_PHY_TX_LOOKUP_HIGH_START;
+		dev_dbg(dev,
+			"Searching for Backup Secondary rxlow on TX = %d\n",
+			srxlow.tx);
+		srxlow.read_delay = CQSPI_PHY_INIT_RD;
+		ret = cqspi_find_rx_low_ddr(f_pdata, mem, &srxlow);
+		if (ret)
+			goto out;
+		dev_dbg(dev, "Backup Secondary rxlow: RX: %d TX: %d RD: %d\n",
+			srxlow.rx, srxlow.tx, srxlow.read_delay);
+
+		backuppoint.rx = min(backuppoint.rx, srxlow.rx);
+		backuppoint.read_delay =
+			min(backuppoint.read_delay, srxlow.read_delay);
+		dev_dbg(dev, "Backup Final rxlow: RX: %d TX: %d RD: %d\n",
+			backuppoint.rx, backuppoint.tx, backuppoint.read_delay);
+
+		if (backuppoint.rx < rxlow.rx) {
+			rxlow = backuppoint;
+			dev_dbg(dev, "Updating rxlow to the one at TX = %d\n",
+				backuppoint.tx);
+		}
+		dev_dbg(dev, "Final rxlow: RX: %d TX: %d RD: %d\n", rxlow.rx,
+			rxlow.tx, rxlow.read_delay);
+
+		/* Backup rxhigh: Search at fixed backup TX */
+
+		/*
+		 *              rx
+		 *          127 ^
+		 *              |       xxxxx     +++++++++++++++++++|
+		 *              |       xxxxxx     ++++++++++++++++++|
+		 *    search    |       xxxxxxx     +++++++++++++++++|
+		 *    rxhigh  -------------------------------------->|
+		 *    on fixed  |       xxxxxxxxx     +++++++++++++++|
+		 *    tx        |       xxxxxxxxxx     ++++++++++++++|
+		 *              |       xxxxxxxxxxx     +++++++++++++|
+		 *              |       xxxxxxxxxxxx     +++++++++++++
+		 *              |       xxxxxxxxxxxxx     ++++++++++++
+		 *              |       xxxxxxxxxxxxxx     +++++++++++
+		 *              |       xxxxxxxxxxxxxxx     ++++++++++
+		 *              |       xxxxxxxxxxxxxxxx     +++++++++
+		 *              |       xxxxxxxxxxxxxxxxx     ++++++++
+		 *              |       xxxxxxxxxxxxxxxxxx     +++++++
+		 *              |
+		 *              -------------------------------------------> tx
+		 *               0                                        127
+		 *
+		 *      |-----------------------------------------------------|
+		 *      | Primary | Secondary | Final                         |
+		 *      | Search  | Search    | Point                         |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Fail    | Fail      | Return Fail                   |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Fail    | Pass      | Choose Secondary              |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Pass    | Fail      | Choose Primary                |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Pass    | Pass      | if (secondary.rx > primary.rx)|
+		 *      |         |           |         Choose Secondary      |
+		 *      |         |           | else                          |
+		 *      |         |           |         Choose Primary        |
+		 *      |-----------------------------------------------------|
+		 */
+
+		/* Primary backup rxhigh: Use backup TX, decrement from max read_delay */
+
+		dev_dbg(dev, "Searching for Backup Primary rxhigh on TX = %d\n",
+			backuppoint.tx);
+		backuppoint.read_delay = CQSPI_PHY_MAX_RD;
+		ret = cqspi_find_rx_high_ddr(f_pdata, mem, &backuppoint);
+		if (ret)
+			primary = false;
+		dev_dbg(dev, "Backup Primary rxhigh: RX: %d TX: %d RD: %d\n",
+			backuppoint.rx, backuppoint.tx, backuppoint.read_delay);
+
+		/* Secondary backup rxhigh: Verify at offset TX */
+
+		if (backuppoint.tx >=
+		    (CQSPI_PHY_TX_LOOKUP_HIGH_START + CQSPI_PHY_SEARCH_OFFSET))
+			srxhigh.tx = backuppoint.tx - CQSPI_PHY_SEARCH_OFFSET;
+		else
+			srxhigh.tx = CQSPI_PHY_TX_LOOKUP_HIGH_START;
+		dev_dbg(dev,
+			"Searching for Backup Secondary rxhigh on TX = %d\n",
+			srxhigh.tx);
+		srxhigh.read_delay = CQSPI_PHY_MAX_RD;
+		ret = cqspi_find_rx_high_ddr(f_pdata, mem, &srxhigh);
+		if (ret)
+			secondary = false;
+		dev_dbg(dev, "Backup Secondary rxhigh: RX: %d TX: %d RD: %d\n",
+			srxhigh.rx, srxhigh.tx, srxhigh.read_delay);
+
+		if (!primary && !secondary)
+			goto out;
+		else if (!primary)
+			backuppoint = srxhigh;
+		else if (secondary && srxhigh.rx > backuppoint.rx)
+			backuppoint = srxhigh;
+		dev_dbg(dev, "Backup Final rxhigh: RX: %d TX: %d RD: %d\n",
+			backuppoint.rx, backuppoint.tx, backuppoint.read_delay);
+
+		if (backuppoint.rx > rxhigh.rx) {
+			rxhigh = backuppoint;
+			dev_dbg(dev, "Updating rxhigh to the one at TX = %d\n",
+				backuppoint.tx);
+		}
+		dev_dbg(dev, "Final rxhigh: RX: %d TX: %d RD: %d\n", rxhigh.rx,
+			rxhigh.tx, rxhigh.read_delay);
+	}
+
+	/* Golden txlow: Fix RX at 1/4 of RX window, search TX lower bound */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |
+	 *     rxhigh --------->xxxxx     ++++++++++++++++++++
+	 *              |       xxxxxx     +++++++++++++++++++
+	 *              |       xxxxxxx     ++++++++++++++++++
+	 *              |       xxxxxxxx     +++++++++++++++++
+	 *              |       xxxxxxxxx     ++++++++++++++++
+	 *              |       xxxxxxxxxx     +++++++++++++++
+	 *              |       xxxxxxxxxxx     ++++++++++++++
+	 *              |       xxxxxxxxxxxx     +++++++++++++
+	 *    fix rx    |       xxxxxxxxxxxxx     ++++++++++++
+	 *    1/4 b/w ---------><------->xxxxx     +++++++++++
+	 *    rxlow and |       xxxx|xxxxxxxxxx     ++++++++++
+	 *    rxhigh    |       xxxx|xxxxxxxxxxx     +++++++++
+	 *              |       xxxx|xxxxxxxxxxxx     ++++++++
+	 *      rxlow --------->xxxx|xxxxxxxxxxxxx     +++++++
+	 *              |           |
+	 *              ------------|------------------------------> tx
+	 *              0           |                            127
+	 *                     search
+	 *                      txlow
+	 */
+
+	rx_window = rxhigh.rx - rxlow.rx;
+	txlow.rx = rxlow.rx + (rx_window / 4);
+	dev_dbg(dev, "Searching for Golden txlow on RX = %d\n", txlow.rx);
+	txlow.read_delay = CQSPI_PHY_INIT_RD;
+	ret = cqspi_find_tx_low_ddr(f_pdata, mem, &txlow);
+	if (ret)
+		goto out;
+	dev_dbg(dev, "Golden txlow: RX: %d TX: %d RD: %d\n", txlow.rx, txlow.tx,
+		txlow.read_delay);
+
+	/* Golden txhigh: Same RX as txlow, decrement from max read_delay */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |
+	 *     rxhigh --------->xxxxx     ++++++++++++++++++++
+	 *              |       xxxxxx     +++++++++++++++++++
+	 *              |       xxxxxxx     ++++++++++++++++++
+	 *              |       xxxxxxxx     +++++++++++++++++
+	 *              |       xxxxxxxxx     ++++++++++++++++
+	 *              |       xxxxxxxxxx     +++++++++++++++
+	 *              |       xxxxxxxxxxx     ++++++++++++++
+	 *              |       xxxxxxxxxxxx     +++++++++++++
+	 *    fix rx    |       xxxxxxxxxxxxx     ++++++++++++
+	 *    1/4 b/w --------------------------------><----->
+	 *    rxlow and |       xxxxxxxxxxxxxxx     ++++++|+++
+	 *    rxhigh    |       xxxxxxxxxxxxxxxx     +++++|+++
+	 *              |       xxxxxxxxxxxxxxxxx     ++++|+++
+	 *      rxlow --------->xxxxxxxxxxxxxxxxxx     +++|+++
+	 *              |                                 |
+	 *              ----------------------------------|--------> tx
+	 *              0                                 |      127
+	 *                                           search
+	 *                                           txhigh
+	 */
+
+	txhigh.rx = txlow.rx;
+	dev_dbg(dev, "Searching for Golden txhigh on RX = %d\n", txhigh.rx);
+	txhigh.read_delay = CQSPI_PHY_MAX_RD;
+	ret = cqspi_find_tx_high_ddr(f_pdata, mem, &txhigh);
+	if (ret)
+		goto out;
+	dev_dbg(dev, "Golden txhigh: RX: %d TX: %d RD: %d\n", txhigh.rx,
+		txhigh.tx, txhigh.read_delay);
+
+	/* If txlow/txhigh at same read_delay, search backup at 3/4 RX window */
+
+	if (txlow.read_delay == txhigh.read_delay) {
+		/* Backup txlow: Fix RX at 3/4 of RX window */
+
+		/*
+		 *              rx
+		 *          127 ^
+		 *              |
+		 *     rxhigh --------->xxxxx     ++++++++++++++++++++
+		 *              |       xxxxxx     +++++++++++++++++++
+		 *    fix rx    |       xxxxxxx     ++++++++++++++++++
+		 *    3/4 b/w ---------><----->x     +++++++++++++++++
+		 *    rxlow and |       xxxx|xxxx     ++++++++++++++++
+		 *    rxhigh    |       xxxx|xxxxx     +++++++++++++++
+		 *              |       xxxx|xxxxxx     ++++++++++++++
+		 *              |       xxxx|xxxxxxx     +++++++++++++
+		 *              |       xxxx|xxxxxxxx     ++++++++++++
+		 *              |       xxxx|xxxxxxxxx     +++++++++++
+		 *              |       xxxx|xxxxxxxxxx     ++++++++++
+		 *              |       xxxx|xxxxxxxxxxx     +++++++++
+		 *              |       xxxx|xxxxxxxxxxxx     ++++++++
+		 *      rxlow --------->xxxx|xxxxxxxxxxxxx     +++++++
+		 *              |           |
+		 *              ------------|------------------------------> tx
+		 *              0           |                            127
+		 *                     search
+		 *                      txlow
+		 */
+
+		dev_dbg(dev, "txlow and txhigh at the same read delay.\n");
+		backuppoint.rx = rxlow.rx + ((rx_window * 3) / 4);
+		dev_dbg(dev, "Searching for Backup txlow on RX = %d\n",
+			backuppoint.rx);
+		backuppoint.read_delay = CQSPI_PHY_INIT_RD;
+		ret = cqspi_find_tx_low_ddr(f_pdata, mem, &backuppoint);
+		if (ret)
+			goto out;
+		dev_dbg(dev, "Backup txlow: RX: %d TX: %d RD: %d\n",
+			backuppoint.rx, backuppoint.tx, backuppoint.read_delay);
+
+		if (backuppoint.tx < txlow.tx) {
+			txlow = backuppoint;
+			dev_dbg(dev, "Updating txlow with the one at RX = %d\n",
+				backuppoint.rx);
+		}
+		dev_dbg(dev, "Final txlow: RX: %d TX: %d RD: %d\n", txlow.rx,
+			txlow.tx, txlow.read_delay);
+
+		/* Backup txhigh: Same RX as backup txlow, decrement from max */
+
+		/*
+		 *              rx
+		 *          127 ^
+		 *              |
+		 *     rxhigh --------->xxxxx     ++++++++++++++++++++
+		 *              |       xxxxxx     +++++++++++++++++++
+		 *    fix rx    |       xxxxxxx     ++++++++++++++++++
+		 *    3/4 b/w ------------------------------><------->
+		 *    rxlow and |       xxxxxxxxx     +++++++++++|++++
+		 *    rxhigh    |       xxxxxxxxxx     ++++++++++|++++
+		 *              |       xxxxxxxxxxx     +++++++++|++++
+		 *              |       xxxxxxxxxxxx     ++++++++|++++
+		 *              |       xxxxxxxxxxxxx     +++++++|++++
+		 *              |       xxxxxxxxxxxxxx     ++++++|++++
+		 *              |       xxxxxxxxxxxxxxx     +++++|++++
+		 *              |       xxxxxxxxxxxxxxxx     ++++|++++
+		 *              |       xxxxxxxxxxxxxxxxx     +++|++++
+		 *      rxlow --------->xxxxxxxxxxxxxxxxxx     ++|++++
+		 *              |                                |
+		 *              ---------------------------------|---------> tx
+		 *              0                                |       127
+		 *                                               search
+		 *                                               txhigh
+		 */
+
+		dev_dbg(dev, "Searching for Backup txhigh on RX = %d\n",
+			backuppoint.rx);
+		backuppoint.read_delay = CQSPI_PHY_MAX_RD;
+		ret = cqspi_find_tx_high_ddr(f_pdata, mem, &backuppoint);
+		if (ret)
+			goto out;
+		dev_dbg(dev, "Backup txhigh: RX: %d TX: %d RD: %d\n",
+			backuppoint.rx, backuppoint.tx, backuppoint.read_delay);
+
+		if (backuppoint.tx > txhigh.tx) {
+			txhigh = backuppoint;
+			dev_dbg(dev,
+				"Updating txhigh with the one at RX = %d\n",
+				backuppoint.rx);
+		}
+		dev_dbg(dev, "Final txhigh: RX: %d TX: %d RD: %d\n", txhigh.rx,
+			txhigh.tx, txhigh.read_delay);
+	}
+
+	/* Corner points: Define and verify bottomleft and topright boundaries */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |                                 topright
+	 *              |                                    *
+	 *    rxhigh -----------xxxxx     ++++++++++++++++++++
+	 *              |       xxxxxx     +++++++++++++++++++
+	 *              |       xxxxxxx     ++++++++++++++++++
+	 *              |       xxxxxxxx     +++++++++++++++++
+	 *              |       xxxxxxxxx     ++++++++++++++++
+	 *              |       xxxxxxxxxx     +++++++++++++++
+	 *              |       xxxxxxxxxxx     ++++++++++++++
+	 *              |       xxxxxxxxxxxx     +++++++++++++
+	 *              |       xxxxxxxxxxxxx     ++++++++++++
+	 *              |       xxxxxxxxxxxxxx     +++++++++++
+	 *              |       xxxxxxxxxxxxxxx     ++++++++++
+	 *              |       xxxxxxxxxxxxxxxx     +++++++++
+	 *              |       xxxxxxxxxxxxxxxxx     ++++++++
+	 *     rxlow -----------xxxxxxxxxxxxxxxxxx     +++++++
+	 *              |       *                            |
+	 *              | bottom|left                        |
+	 *              --------|----------------------------|---> tx
+	 *              0       |                            | 127
+	 *                      |                            |
+	 *                      txlow                   txhigh
+	 *
+	 * Verification: Test point 4 taps inside each corner, adjust
+	 * read_delay ?1 if needed to ensure valid corners for gap search.
+	 */
+
+	bottomleft.tx = txlow.tx;
+	bottomleft.rx = rxlow.rx;
+	if (txlow.read_delay <= rxlow.read_delay)
+		bottomleft.read_delay = txlow.read_delay;
+	else
+		bottomleft.read_delay = rxlow.read_delay;
+
+	/* Verify bottomleft: Test 4 taps inside, adjust read_delay if needed */
+	backupcornerpoint = bottomleft;
+	backupcornerpoint.tx += 4;
+	backupcornerpoint.rx += 4;
+	ret = cqspi_phy_apply_setting(f_pdata, &backupcornerpoint);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret) {
+		backupcornerpoint.read_delay--;
+		ret = cqspi_phy_apply_setting(f_pdata, &backupcornerpoint);
+		if (!ret)
+			ret = cqspi_phy_check_pattern(f_pdata, mem);
+	}
+
+	if (ret)
+		goto out;
+
+	bottomleft.read_delay = backupcornerpoint.read_delay;
+
+	topright.tx = txhigh.tx;
+	topright.rx = rxhigh.rx;
+	if (txhigh.read_delay >= rxhigh.read_delay)
+		topright.read_delay = txhigh.read_delay;
+	else
+		topright.read_delay = rxhigh.read_delay;
+
+	/* Verify topright: Test 4 taps inside, adjust read_delay if needed */
+	backupcornerpoint = topright;
+	backupcornerpoint.tx -= 4;
+	backupcornerpoint.rx -= 4;
+	ret = cqspi_phy_apply_setting(f_pdata, &backupcornerpoint);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret) {
+		backupcornerpoint.read_delay++;
+		ret = cqspi_phy_apply_setting(f_pdata, &backupcornerpoint);
+		if (!ret)
+			ret = cqspi_phy_check_pattern(f_pdata, mem);
+	}
+
+	if (ret)
+		goto out;
+
+	topright.read_delay = backupcornerpoint.read_delay;
+
+	dev_dbg(dev, "topright: RX: %d TX: %d RD: %d\n", topright.rx,
+		topright.tx, topright.read_delay);
+	dev_dbg(dev, "bottomleft: RX: %d TX: %d RD: %d\n", bottomleft.rx,
+		bottomleft.tx, bottomleft.read_delay);
+	cqspi_phy_find_gaplow_ddr(f_pdata, mem, &bottomleft, &topright, &gaplow);
+	dev_dbg(dev, "gaplow: RX: %d TX: %d RD: %d\n", gaplow.rx, gaplow.tx,
+		gaplow.read_delay);
+
+	/* Final point selection: Handle single vs dual passing regions */
+
+	if (bottomleft.read_delay == topright.read_delay) {
+		/*
+		 * Single region: Use midpoint with temperature compensation.
+		 * Gaplow approximates upper boundary of valid region.
+		 *
+		 *              rx
+		 *          127 ^
+		 *              |              gaplow (approx. topright)
+		 *              |              |
+		 *    rxhigh -----------xxxxxxx|          failing
+		 *              |       xxxxxxx|           region
+		 *              |       xxxxxxx|     <--------------->
+		 *              |       xxxxxxx|     +++++++++++++++++
+		 *              |       xxxxxxxxx     ++++++++++++++++
+		 *              |       xxxxxxxxxx     +++++++++++++++
+		 *              |       xxxxxxxxxxx     ++++++++++++++
+		 *              |       xxxxxxxxxxxx     +++++++++++++
+		 *              |       xxxxxxxxxxxxx     ++++++++++++
+		 *              |       xxxxxxxxxxxxxx     +++++++++++
+		 *              |       xxxxxxxxxxxxxxx     ++++++++++
+		 *              |       xxxxxxxxxxxxxxxx     +++++++++
+		 *              |       xxxxxxxxxxxxxxxxx     ++++++++
+		 *     rxlow -----------xxxxxxxxxxxxxxxxxx     +++++++
+		 *              |       *                            |
+		 *              | bottom|left                        |
+		 *              --------|----------------------------|---> tx
+		 *              0       |                            | 127
+		 *                      |                            |
+		 *                      txlow                   txhigh
+		 *                             (same read_delay)
+		 *
+		 * Temperature compensation: Valid region shifts with temp.
+		 * Offset = region_size / (330 / (temp - 42?C))
+		 * Factor 330 is empirically determined for this hardware.
+		 */
+
+		dev_dbg(dev,
+			"bottomleft and topright at the same read delay.\n");
+
+		topright = gaplow;
+		searchpoint.read_delay = bottomleft.read_delay;
+		searchpoint.tx =
+			bottomleft.tx + ((topright.tx - bottomleft.tx) / 2);
+		searchpoint.rx =
+			bottomleft.rx + ((topright.rx - bottomleft.rx) / 2);
+
+		ret = cqspi_get_temp(&temp);
+		if (ret) {
+			/* Assume room temperature if sensor unavailable */
+			dev_dbg(dev,
+				"Unable to get temperature. Assuming room temperature\n");
+			temp = CQSPI_PHY_DEFAULT_TEMP;
+		}
+
+		if (temp < CQSPI_PHY_MIN_TEMP || temp > CQSPI_PHY_MAX_TEMP) {
+			dev_err(dev,
+				"Temperature outside operating range: %dC\n",
+				temp);
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (temp == CQSPI_PHY_MID_TEMP)
+			temp++; /* Avoid divide-by-zero */
+		dev_dbg(dev, "Temperature: %dC\n", temp);
+
+		/*
+		 * Apply temperature offset: positive at high temp, negative at low.
+		 * Compute the divisor once and apply to both TX and RX. Use int
+		 * arithmetic throughout to avoid u8 wrapping on negative offsets.
+		 */
+		temp = 330 / (temp - CQSPI_PHY_MID_TEMP);
+		searchpoint.tx = clamp((int)searchpoint.tx +
+				       (topright.tx - bottomleft.tx) / temp,
+				       0, CQSPI_PHY_MAX_DELAY);
+		searchpoint.rx = clamp((int)searchpoint.rx +
+				       (topright.rx - bottomleft.rx) / temp,
+				       0, CQSPI_PHY_MAX_DELAY);
+	} else {
+		/*
+		 * Dual regions: Gap separates two valid regions, choose larger.
+		 *
+		 *              rx
+		 *          127 ^
+		 *              |                                 topright
+		 *              |                                    *
+		 *    rxhigh -----------xxxxx     +++++++++++++++++++|
+		 *              |       xxxxxx    <region 2> ++++++++|
+		 *              |       xxxxxxx     +++++++++++++++++|
+		 *              |       xxxxxxxx     ++++++++++++++++|
+		 *              |       xxxxxxxxx     +++++++++++++++|
+		 *              |       xxxxxxxxxx     ++++++++++++++|
+		 *              |                  failing           |
+		 *              |                  region            |
+		 *              |       xxxxxxxxxxxxx     +++++++++++|
+		 *              |       xxxxxxxxxxxxxx     ++++++++++|
+		 *              |       xxxxxxxxxxxxxxx     +++++++++|
+		 *              |       xxxxxxxxxxxxxxxx     ++++++++|
+		 *              |       xxxxxxxxx <region 1>  +++++++|
+		 *     rxlow -----------xxxxxxxxxxxxxxxxxx     ++++++|
+		 *              |       *                            |
+		 *              | bottom|left                        |
+		 *              --------|----------------------------|---> tx
+		 *              0       |                            | 127
+		 *                      |                            |
+		 *                      txlow                   txhigh
+		 *
+		 * Strategy: Compare Manhattan distances from gap boundaries to
+		 * corners. Choose corner furthest from gap (larger region).
+		 * Apply 16-tap margin inward, scale RX proportionally.
+		 */
+
+		cqspi_phy_find_gaphigh_ddr(f_pdata, mem, &bottomleft,
+					   &topright, &gaphigh);
+		dev_dbg(dev, "gaphigh: RX: %d TX: %d RD: %d\n", gaphigh.rx,
+			gaphigh.tx, gaphigh.read_delay);
+
+		if (topright.tx == bottomleft.tx) {
+			dev_err(dev, "zero TX span in dual-region: cannot compute search point\n");
+			ret = -EINVAL;
+			goto out;
+		}
+
+		/* Compare Manhattan distances: choose corner furthest from gap */
+		if ((abs(gaplow.tx - bottomleft.tx) +
+		     abs(gaplow.rx - bottomleft.rx)) <
+		    (abs(gaphigh.tx - topright.tx) +
+		     abs(gaphigh.rx - topright.rx))) {
+			/* Topright further: Use Region 2, 16 taps inward */
+			searchpoint = topright;
+			searchpoint.tx = clamp((int)topright.tx - 16,
+					       0, CQSPI_PHY_MAX_DELAY);
+			searchpoint.rx = clamp((int)topright.rx -
+					       (16 * (topright.rx - bottomleft.rx)) /
+					       (topright.tx - bottomleft.tx),
+					       0, CQSPI_PHY_MAX_DELAY);
+		} else {
+			/* Bottomleft further: Use Region 1, 16 taps inward */
+			searchpoint = bottomleft;
+			searchpoint.tx = clamp((int)bottomleft.tx + 16,
+					       0, CQSPI_PHY_MAX_DELAY);
+			searchpoint.rx = clamp((int)bottomleft.rx +
+					       (16 * (topright.rx - bottomleft.rx)) /
+					       (topright.tx - bottomleft.tx),
+					       0, CQSPI_PHY_MAX_DELAY);
+		}
+	}
+
+	/* Apply and verify final tuning point */
+	dev_dbg(dev, "Final tuning point: RX: %d TX: %d RD: %d\n",
+		searchpoint.rx, searchpoint.tx, searchpoint.read_delay);
+	ret = cqspi_phy_apply_setting(f_pdata, &searchpoint);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret) {
+		dev_err(dev,
+			"Failed to find pattern at final calibration point\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	f_pdata->phy_setting.read_delay = searchpoint.read_delay;
+	f_pdata->phy_setting.rx = searchpoint.rx;
+	f_pdata->phy_setting.tx = searchpoint.tx;
+out:
+	if (ret)
+		f_pdata->use_tuned_phy = false;
+
+	return ret;
+}
+
+static int cqspi_phy_tuning_sdr(struct cqspi_flash_pdata *f_pdata,
+				struct spi_mem *mem)
+{
+	struct cqspi_st *cqspi = f_pdata->cqspi;
+	struct device *dev = &cqspi->pdev->dev;
+	struct phy_setting rxlow, rxhigh, first, second, final;
+	u8 window1 = 0;
+	u8 window2 = 0;
+	int ret;
+
+	/*
+	 * SDR tuning: 1D search for optimal RX delay (TX less critical).
+	 * Find two consecutive windows, choose larger, use midpoint.
+	 *
+	 *              rx
+	 *          127 ^
+	 *              |        |-----window at----------|
+	 *              |        |-----read_delay = n+1---|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        rxlow(n+1)   midpoint    rxhigh(n+1)
+	 *              |
+	 *              |   |---window at--------|
+	 *              |   |---read_delay = n---|
+	 *              |   |xxxxxxxxxxxxxxxxxxxx|
+	 *              |   |xxxxxxxxxxxxxxxxxxxx|
+	 *              |   |xxxxxxxxxxxxxxxxxxxx|
+	 *              |   |xxxxxxxxxxxxxxxxxxxx|
+	 *              |   |xxxxxxxxxxxxxxxxxxxx|
+	 *              |   rxlow(n)   midpoint  rxhigh(n)
+	 *              |
+	 *              -----------------------------------------> tx
+	 *              0                                      127
+	 *                  read_delay=n                  read_delay=n+1
+	 */
+
+	f_pdata->use_tuned_phy = true;
+	cqspi_phy_reset_setting(&rxlow);
+	cqspi_phy_reset_setting(&rxhigh);
+	cqspi_phy_reset_setting(&first);
+
+	/* First window: Find rxlow by incrementing read_delay from 0 */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |   |xxxxxxxxxxxxxxxxxxxx|
+	 *  search      |   |xxxxxxxxxxxxxxxxxxxx|
+	 *  rxlow       |   |xxxxxxxxxxxxxxxxxxxx|
+	 *  increasing  |   |xxxxxxxxxxxxxxxxxxxx|
+	 *           --------->|xxxxxxxxxxxxxxxxxxxx|
+	 *  read_delay  |        |xxxxxxxxxxxxxxxxxxx|
+	 *  until found |        |xxxxxxxxxxxxxxxxxxx|
+	 *              |        rxlow
+	 *              -----------------------------------------> tx
+	 *              0                            tx fixed at 127
+	 */
+
+	do {
+		ret = cqspi_find_rx_low_sdr(f_pdata, mem, &rxlow);
+
+		if (ret)
+			rxlow.read_delay++;
+	} while (ret && rxlow.read_delay <= CQSPI_PHY_MAX_RD);
+
+	if (ret)
+		goto out;
+
+	/* Find rxhigh: Decrement from RX=127 at same read_delay */
+
+	/*
+	 *              rx
+	 *          127 ^        search rxhigh
+	 *              |        (decrement from
+	 *              |        127 until found)
+	 *              |          |
+	 *              |          |
+	 *              |          v
+	 *              |        |------------------------|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        rxlow                    rxhigh
+	 *              -----------------------------------------> tx
+	 *              0                            tx fixed at 127
+	 */
+
+	rxhigh.read_delay = rxlow.read_delay;
+	ret = cqspi_find_rx_high_sdr(f_pdata, mem, &rxhigh, rxlow.rx);
+	if (ret)
+		goto out;
+
+	/* Calculate first window midpoint for max margin */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |        |--------window1---------|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxx * xxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        rxlow        ^           rxhigh
+	 *              ----------------------|------------------> tx
+	 *              0                     |      tx fixed at 127
+	 *                            window1/2
+	 */
+
+	first.read_delay = rxlow.read_delay;
+	window1 = rxhigh.rx - rxlow.rx;
+	first.rx = rxlow.rx + (window1 / 2);
+
+	dev_dbg(dev, "First tuning point: RX: %d TX: %d RD: %d\n", first.rx,
+		first.tx, first.read_delay);
+	ret = cqspi_phy_apply_setting(f_pdata, &first);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret || first.read_delay > CQSPI_PHY_MAX_RD)
+		goto out;
+
+	/* Second window: Search at read_delay+1, may differ in size */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |        |-------|
+	 *              |        |xxxxxxx|
+	 *              |        |xxxxxxx|
+	 *              |        |xxxxxxx|
+	 *              |        |xxxxxxx|
+	 *              |        |xxxxxxx|
+	 *              |        rxlow   rxhigh
+	 *              -----------------------------------------> tx
+	 *              0
+	 *              read_delay = n (smaller window)
+	 *
+	 *              rx
+	 *          127 ^
+	 *              |        |-----------------|
+	 *              |        |xxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxx|
+	 *              |        rxlow             rxhigh
+	 *              -----------------------------------------> tx
+	 *              0
+	 *              read_delay = n+1 (larger window - better)
+	 */
+
+	cqspi_phy_reset_setting(&rxlow);
+	cqspi_phy_reset_setting(&rxhigh);
+	cqspi_phy_reset_setting(&second);
+
+	rxlow.read_delay = first.read_delay + 1;
+	if (rxlow.read_delay > CQSPI_PHY_MAX_RD)
+		goto compare;
+
+	ret = cqspi_find_rx_low_sdr(f_pdata, mem, &rxlow);
+	if (ret)
+		goto compare;
+
+	rxhigh.read_delay = rxlow.read_delay;
+	ret = cqspi_find_rx_high_sdr(f_pdata, mem, &rxhigh, rxlow.rx);
+	if (ret)
+		goto compare;
+
+	/* Calculate second window midpoint */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |        |--------window2---------|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxx * xxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        rxlow        ^           rxhigh
+	 *              ----------------------|------------------> tx
+	 *              0                     |      tx fixed at 127
+	 *                            window2/2
+	 *              read_delay = n+1
+	 */
+
+	window2 = rxhigh.rx - rxlow.rx;
+	second.rx = rxlow.rx + (window2 / 2);
+	second.read_delay = rxlow.read_delay;
+
+	dev_dbg(dev, "Second tuning point: RX: %d TX: %d RD: %d\n", second.rx,
+		second.tx, second.read_delay);
+	ret = cqspi_phy_apply_setting(f_pdata, &second);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret || second.read_delay > CQSPI_PHY_MAX_RD)
+		window2 = 0;
+
+	/* Window comparison: Choose larger window for better margin */
+
+compare:
+	cqspi_phy_reset_setting(&final);
+	if (window2 > window1) {
+		final.rx = second.rx;
+		final.read_delay = second.read_delay;
+	} else {
+		final.rx = first.rx;
+		final.read_delay = first.read_delay;
+	}
+
+	/* Apply and verify final tuning point */
+
+	dev_dbg(dev, "Final tuning point: RX: %d TX: %d RD: %d\n", final.rx,
+		final.tx, final.read_delay);
+	ret = cqspi_phy_apply_setting(f_pdata, &final);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	f_pdata->phy_setting.read_delay = final.read_delay;
+	f_pdata->phy_setting.rx = final.rx;
+	f_pdata->phy_setting.tx = final.tx;
+
+out:
+	if (ret)
+		f_pdata->use_tuned_phy = false;
+
+	return ret;
+}
+
+static int cqspi_am654_ospi_execute_tuning(struct spi_mem *mem,
+					   struct spi_mem_op *read_op,
+					   struct spi_mem_op *write_op)
+{
+	struct cqspi_st *cqspi =
+		spi_controller_get_devdata(mem->spi->controller);
+	struct cqspi_flash_pdata *f_pdata;
+	struct device *dev = &cqspi->pdev->dev;
+	int ret = 0;
+
+	f_pdata = &cqspi->f_pdata[spi_get_chipselect(mem->spi, 0)];
+
+	/*
+	 * spi-max-post-config-frequency-hz must be present for PHY tuning.
+	 * If absent, post_config_max_speed_hz is zero and there is no
+	 * calibration target, so skip tuning gracefully.
+	 */
+	if (!mem->spi->post_config_max_speed_hz) {
+		dev_dbg(dev,
+			"No post-config frequency configured, skipping tuning\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (write_op) {
+		/*
+		 * For NAND: write the calibration pattern to the page cache.
+		 * The write op runs at the conservative base speed (spi->max_speed_hz)
+		 * so the write itself is reliable before PHY calibration.
+		 */
+		ret = cqspi_write_pattern_to_cache(f_pdata, mem, write_op);
+		if (ret) {
+			dev_warn(dev,
+				 "failed to write pattern to cache: %d, skipping tuning\n",
+				 ret);
+			goto out;
+		}
+
+		f_pdata->phy_write_op = *write_op;
+	} else {
+		if (!f_pdata->phy_pattern_partition_valid) {
+			dev_warn(dev, "pattern partition not found, skipping tuning\n");
+			return -EOPNOTSUPP;
+		}
+	}
+
+	/*
+	 * Build the read op template for pattern verification.  For NOR, point
+	 * at the pre-programmed pattern partition rather than the caller's
+	 * address without altering the caller's template.
+	 */
+	f_pdata->phy_read_op = *read_op;
+	if (!write_op)
+		f_pdata->phy_read_op.addr.val =
+			f_pdata->phy_pattern_partition_offset;
+
+	f_pdata->phy_read_op.max_freq = mem->spi->max_speed_hz;
+
+	ret = cqspi_phy_check_pattern(f_pdata, mem);
+	if (ret) {
+		dev_err(dev, "pattern not found: %d, skipping tuning\n", ret);
+		goto out;
+	}
+
+	f_pdata->phy_read_op.max_freq = mem->spi->post_config_max_speed_hz;
+
+	if (read_op->cmd.dtr || read_op->addr.dtr || read_op->dummy.dtr ||
+	    read_op->data.dtr) {
+		f_pdata->use_dqs = true;
+		cqspi_phy_pre_config(cqspi, f_pdata, false);
+		ret = cqspi_phy_tuning_ddr(f_pdata, mem);
+	} else {
+		f_pdata->use_dqs = false;
+		cqspi_phy_pre_config(cqspi, f_pdata, true);
+		ret = cqspi_phy_tuning_sdr(f_pdata, mem);
+	}
+
+	if (ret)
+		dev_warn(dev, "tuning failed: %d\n", ret);
+
+	cqspi_phy_post_config(cqspi, f_pdata->read_delay);
+
+out:
+	if (!ret) {
+		read_op->max_freq = mem->spi->post_config_max_speed_hz;
+		if (write_op) {
+			write_op->max_freq = mem->spi->post_config_max_speed_hz;
+			f_pdata->phy_write_op.max_freq =
+				mem->spi->post_config_max_speed_hz;
+		}
+	}
+
+	return ret;
+}
+
+static int cqspi_mem_op_execute_tuning(struct spi_mem *mem,
+				       struct spi_mem_op *read_op,
+				       struct spi_mem_op *write_op)
+{
+	struct cqspi_st *cqspi =
+		spi_controller_get_devdata(mem->spi->controller);
+
+	if (!cqspi->ddata || !cqspi->ddata->execute_tuning)
+		return -EOPNOTSUPP;
+
+	return cqspi->ddata->execute_tuning(mem, read_op, write_op);
+}
+
 static int cqspi_of_get_flash_pdata(struct platform_device *pdev,
 				    struct cqspi_flash_pdata *f_pdata,
 				    struct device_node *np)
 {
+	struct device_node *part_np;
+
 	if (of_property_read_u32(np, "cdns,read-delay", &f_pdata->read_delay)) {
 		dev_err(&pdev->dev, "couldn't determine read-delay\n");
 		return -ENXIO;
@@ -1588,9 +3326,12 @@ static int cqspi_of_get_flash_pdata(struct platform_device *pdev,
 		return -ENXIO;
 	}
 
-	if (of_property_read_u32(np, "spi-max-frequency", &f_pdata->clk_rate)) {
-		dev_err(&pdev->dev, "couldn't determine spi-max-frequency\n");
-		return -ENXIO;
+	part_np = of_parse_phandle(np, "spi-phy-pattern-partition", 0);
+	if (part_np) {
+		f_pdata->phy_pattern_partition_valid =
+			!of_property_read_u32_index(part_np, "reg", 0,
+						    &f_pdata->phy_pattern_partition_offset);
+		of_node_put(part_np);
 	}
 
 	return 0;
@@ -1740,6 +3481,7 @@ static const struct spi_controller_mem_ops cqspi_mem_ops = {
 	.exec_op = cqspi_exec_mem_op,
 	.get_name = cqspi_get_name,
 	.supports_op = cqspi_supports_mem_op,
+	.execute_tuning = cqspi_mem_op_execute_tuning,
 };
 
 static const struct spi_controller_mem_caps cqspi_mem_caps = {
@@ -2108,6 +3850,7 @@ static const struct cqspi_driver_platdata k2g_qspi = {
 static const struct cqspi_driver_platdata am654_ospi = {
 	.hwcaps_mask = CQSPI_SUPPORTS_OCTAL | CQSPI_SUPPORTS_QUAD,
 	.quirks = CQSPI_NEEDS_WR_DELAY,
+	.execute_tuning = cqspi_am654_ospi_execute_tuning,
 };
 
 static const struct cqspi_driver_platdata intel_lgm_qspi = {
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:33:02 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:03:02 +0530
Subject: [PATCH v7 07/18] spi: cadence-quadspi: add DQS support to read data capture
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-8-s-k6@ti.com>

Add DQS (Data Strobe) parameter to cqspi_readdata_capture() to control
data capture timing. DQS mode uses a dedicated strobe signal for
improved timing margins in high-speed SPI modes.

Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index f8e266372367..046fa5d69b0f 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -192,6 +192,7 @@ struct cqspi_driver_platdata {
 #define CQSPI_REG_READCAPTURE_BYPASS_LSB	0
 #define CQSPI_REG_READCAPTURE_DELAY_LSB		1
 #define CQSPI_REG_READCAPTURE_DELAY_MASK	0xF
+#define CQSPI_REG_READCAPTURE_DQS_LSB		8
 
 #define CQSPI_REG_SIZE				0x14
 #define CQSPI_REG_SIZE_ADDRESS_LSB		0
@@ -458,7 +459,7 @@ static int cqspi_wait_idle(struct cqspi_st *cqspi)
 }
 
 static void cqspi_readdata_capture(struct cqspi_st *cqspi, const bool bypass,
-				   const unsigned int delay)
+				   const bool dqs, const unsigned int delay)
 {
 	void __iomem *reg_base = cqspi->iobase;
 	unsigned int reg;
@@ -476,6 +477,11 @@ static void cqspi_readdata_capture(struct cqspi_st *cqspi, const bool bypass,
 	reg |= (delay & CQSPI_REG_READCAPTURE_DELAY_MASK)
 	       << CQSPI_REG_READCAPTURE_DELAY_LSB;
 
+	if (dqs)
+		reg |= BIT(CQSPI_REG_READCAPTURE_DQS_LSB);
+	else
+		reg &= ~BIT(CQSPI_REG_READCAPTURE_DQS_LSB);
+
 	writel(reg, reg_base + CQSPI_REG_READCAPTURE);
 }
 
@@ -1317,7 +1323,7 @@ static void cqspi_configure(struct cqspi_flash_pdata *f_pdata,
 		cqspi->sclk = sclk;
 		cqspi_config_baudrate_div(cqspi);
 		cqspi_delay(f_pdata);
-		cqspi_readdata_capture(cqspi, !cqspi->rclk_en,
+		cqspi_readdata_capture(cqspi, !cqspi->rclk_en, false,
 				       f_pdata->read_delay);
 	}
 
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:33:10 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:03:10 +0530
Subject: [PATCH v7 15/18] mtd: spinand: extract variant ranking logic into spinand_op_find_best_variant()
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-16-s-k6@ti.com>

Refactor spinand_select_op_variant() to extract the inner loop into a
shared helper spinand_op_find_best_variant(). The helper takes a
skip_mask bitmask of already-tried variant indices, enabling callers to
iterate variants in ranked performance order while skipping those
already attempted. spinand_select_op_variant() becomes a one-liner
wrapper. No functional change.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/mtd/nand/spi/core.c | 32 +++++++++++++++++++++++++++-----
 1 file changed, 27 insertions(+), 5 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 8bf9301f25e7..7c3341f1fca0 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -1542,9 +1542,22 @@ static int spinand_init_odtr_instruction_set(struct spinand_device *spinand)
 	return 0;
 }
 
+/*
+ * spinand_op_find_best_variant() - Find the fastest eligible op variant.
+ * @spinand:    SPI NAND device
+ * @variants:   full variant list to search
+ * @iface:      bus interface to consider (ODTR or SSDR)
+ * @skip_mask:  bitmask of variant indices to skip (already tried)
+ *
+ * Iterates @variants, evaluates transfer duration for each eligible op, and
+ * returns a pointer to the fastest one not in @skip_mask.  Returns NULL when
+ * no eligible variant remains.  Used by both variant selection at init time
+ * (skip_mask == 0) and ranked PHY tuning iteration.
+ */
 static const struct spi_mem_op *
-spinand_select_op_variant(struct spinand_device *spinand, enum spinand_bus_interface iface,
-			  const struct spinand_op_variants *variants)
+spinand_op_find_best_variant(struct spinand_device *spinand,
+			     const struct spinand_op_variants *variants,
+			     enum spinand_bus_interface iface, u32 skip_mask)
 {
 	struct nand_device *nand = spinand_to_nand(spinand);
 	const struct spi_mem_op *best_variant = NULL;
@@ -1552,15 +1565,16 @@ spinand_select_op_variant(struct spinand_device *spinand, enum spinand_bus_inter
 	unsigned int i;
 
 	for (i = 0; i < variants->nops; i++) {
-		struct spi_mem_op op = variants->ops[i];
+		struct spi_mem_op op;
 		u64 op_duration_ns = 0;
 		unsigned int nbytes;
 		int ret;
 
-		if ((iface == SSDR && spinand_op_is_odtr(&op)) ||
-		    (iface == ODTR && !spinand_op_is_odtr(&op)))
+		if ((skip_mask & BIT(i)) ||
+		    spinand_op_is_odtr(&variants->ops[i]) != (iface == ODTR))
 			continue;
 
+		op = variants->ops[i];
 		nbytes = nanddev_per_page_oobsize(nand) +
 			 nanddev_page_size(nand);
 
@@ -1589,6 +1603,14 @@ spinand_select_op_variant(struct spinand_device *spinand, enum spinand_bus_inter
 	return best_variant;
 }
 
+static const struct spi_mem_op *
+spinand_select_op_variant(struct spinand_device *spinand,
+			  enum spinand_bus_interface iface,
+			  const struct spinand_op_variants *variants)
+{
+	return spinand_op_find_best_variant(spinand, variants, iface, 0);
+}
+
 /**
  * spinand_match_and_init() - Try to find a match between a device ID and an
  *			      entry in a spinand_info table
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:33:06 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:03:06 +0530
Subject: [PATCH v7 11/18] spi: cadence-quadspi: enable PHY for direct reads
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-12-s-k6@ti.com>

Add cqspi_tune_phy() to toggle PHY mode around a DMA transfer. On
enable: reprogramme the DLL from the per-CS calibration result when
the hardware values differ (handles alternating PHY reads across two
chip selects with different calibration), configure read-capture for
the tuned delay, assert PHY_EN and PHY_PIPELINE in CONFIG, and
decrement the dummy cycle count by one since the PHY data-capture
pipeline absorbs that latency. The original count is saved in
cqspi->phy_saved_dummy so the disable path restores it exactly,
handling the case where the original count was zero. On disable,
all settings are reversed using the saved count.

Add cqspi_use_tuned_phy() predicate: PHY is activated only when
calibration completed successfully and the op frequency equals the
post-config rate.

For direct reads, PHY is used only when both the start address and
end address are 16-byte aligned. Unaligned transfers fall back to
non-PHY DMA.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 119 +++++++++++++++++++++++++++++-
 1 file changed, 118 insertions(+), 1 deletion(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 99a0ec0e769c..8903b4cf4f6f 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -1074,6 +1074,102 @@ static int cqspi_resync_dll(struct cqspi_st *cqspi)
 	return ret;
 }
 
+static int cqspi_tune_phy(struct cqspi_flash_pdata *f_pdata, bool enable)
+{
+	struct cqspi_st *cqspi = f_pdata->cqspi;
+	void __iomem *reg_base = cqspi->iobase;
+	u32 reg;
+	u8 dummy;
+	int ret;
+
+	ret = cqspi_wait_idle(cqspi);
+	if (ret)
+		return ret;
+
+	if (enable) {
+		/*
+		 * CQSPI_REG_PHY_CONFIG holds the RX/TX delay line tap counts
+		 * and is a single global register with no per-CS equivalent.
+		 * Reprogramme and resync the DLL when the active device's
+		 * calibrated values differ from what is currently in hardware,
+		 * which happens when alternating PHY reads between two chip
+		 * selects with different calibration results.
+		 */
+		reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+		if (((reg >> CQSPI_REG_PHY_CONFIG_RX_DEL_LSB) &
+		     CQSPI_REG_PHY_CONFIG_RX_DEL_MASK) != f_pdata->phy_setting.rx ||
+		    ((reg >> CQSPI_REG_PHY_CONFIG_TX_DEL_LSB) &
+		     CQSPI_REG_PHY_CONFIG_TX_DEL_MASK) != f_pdata->phy_setting.tx) {
+			cqspi_set_dll(reg_base, f_pdata->phy_setting.rx,
+				      f_pdata->phy_setting.tx);
+			ret = cqspi_resync_dll(cqspi);
+			if (ret)
+				return ret;
+		}
+
+		cqspi_readdata_capture(cqspi, !f_pdata->use_dqs, f_pdata->use_dqs,
+				       f_pdata->phy_setting.read_delay);
+
+		reg = readl(reg_base + CQSPI_REG_CONFIG);
+		reg |= CQSPI_REG_CONFIG_PHY_EN | CQSPI_REG_CONFIG_PHY_PIPELINE;
+		writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+		/*
+		 * The PHY data-capture pipeline absorbs one dummy cycle's
+		 * worth of latency; reduce the count to avoid over-compensation.
+		 * Save the original count so the disable path can restore it
+		 * exactly, regardless of whether the original was zero.
+		 */
+		reg = readl(reg_base + CQSPI_REG_RD_INSTR);
+		dummy = FIELD_GET(CQSPI_REG_RD_INSTR_DUMMY_MASK
+					  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+				  reg);
+		cqspi->phy_saved_dummy = dummy;
+		if (dummy)
+			dummy--;
+		reg &= ~(CQSPI_REG_RD_INSTR_DUMMY_MASK
+			 << CQSPI_REG_RD_INSTR_DUMMY_LSB);
+		reg |= FIELD_PREP(CQSPI_REG_RD_INSTR_DUMMY_MASK
+					  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+				  dummy);
+		writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+
+		ret = cqspi_wait_idle(cqspi);
+		if (ret) {
+			reg = readl(reg_base + CQSPI_REG_CONFIG);
+			reg &= ~(CQSPI_REG_CONFIG_PHY_EN |
+				 CQSPI_REG_CONFIG_PHY_PIPELINE);
+			writel(reg, reg_base + CQSPI_REG_CONFIG);
+			reg = readl(reg_base + CQSPI_REG_RD_INSTR);
+			reg &= ~(CQSPI_REG_RD_INSTR_DUMMY_MASK
+				 << CQSPI_REG_RD_INSTR_DUMMY_LSB);
+			reg |= FIELD_PREP(CQSPI_REG_RD_INSTR_DUMMY_MASK
+						  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+					  cqspi->phy_saved_dummy);
+			writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+		}
+		return ret;
+	}
+
+	cqspi_readdata_capture(cqspi, !cqspi->rclk_en, false,
+			       f_pdata->read_delay);
+
+	reg = readl(reg_base + CQSPI_REG_CONFIG);
+	reg &= ~(CQSPI_REG_CONFIG_PHY_EN |
+		 CQSPI_REG_CONFIG_PHY_PIPELINE);
+	writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+	reg = readl(reg_base + CQSPI_REG_RD_INSTR);
+	reg &= ~(CQSPI_REG_RD_INSTR_DUMMY_MASK
+		 << CQSPI_REG_RD_INSTR_DUMMY_LSB);
+	reg |= FIELD_PREP(CQSPI_REG_RD_INSTR_DUMMY_MASK
+				  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+			  cqspi->phy_saved_dummy);
+	writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+
+	return cqspi_wait_idle(cqspi);
+}
+
 static int cqspi_versal_indirect_read_dma(struct cqspi_flash_pdata *f_pdata,
 					  u_char *rxbuf, loff_t from_addr,
 					  size_t n_rx)
@@ -1524,6 +1620,14 @@ static ssize_t cqspi_write(struct cqspi_flash_pdata *f_pdata,
 	return cqspi_indirect_write_execute(f_pdata, to, buf, len);
 }
 
+static bool cqspi_use_tuned_phy(struct cqspi_flash_pdata *f_pdata,
+				const struct spi_mem_op *op,
+				u32 post_config_max_speed_hz)
+{
+	return f_pdata->use_tuned_phy &&
+	       op->max_freq == post_config_max_speed_hz;
+}
+
 static void cqspi_rx_dma_callback(void *param)
 {
 	struct cqspi_st *cqspi = param;
@@ -1629,6 +1733,7 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
 	loff_t from = op->addr.val;
 	size_t len = op->data.nbytes;
 	u_char *buf = op->data.buf.in;
+	int ret;
 
 	if (!cqspi->rx_chan || !virt_addr_valid(buf) ||
 	    len < CQSPI_PHY_MIN_DIRECT_READ_LEN) {
@@ -1636,7 +1741,19 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
 		return 0;
 	}
 
-	return cqspi_direct_read_dma(f_pdata, buf, from, len);
+	if (!cqspi_use_tuned_phy(f_pdata, op, post_config_max_speed_hz))
+		return cqspi_direct_read_dma(f_pdata, buf, from, len);
+
+	if (!IS_ALIGNED(from, 16) || !IS_ALIGNED(from + len, 16))
+		return cqspi_direct_read_dma(f_pdata, buf, from, len);
+
+	ret = cqspi_tune_phy(f_pdata, true);
+	if (ret)
+		return ret;
+	ret = cqspi_direct_read_dma(f_pdata, buf, from, len);
+	cqspi_tune_phy(f_pdata, false);
+
+	return ret;
 }
 
 static ssize_t cqspi_read(struct cqspi_flash_pdata *f_pdata,
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:33:07 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:03:07 +0530
Subject: [PATCH v7 12/18] spi: cadence-quadspi: enable PHY for indirect writes
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-13-s-k6@ti.com>

Enable PHY for indirect writes of at least CQSPI_PHY_MIN_INDIRECT_WRITE_LEN
bytes. PHY is activated only when tuning completed successfully and the
write op was calibrated at the post-config frequency (phy_write_op.max_freq
matches), which restricts PHY writes to NAND devices where a write op
template was provided to execute_tuning. NOR flash writes are unaffected.

The indirect write is cancelled before disabling PHY in the error path
so that cqspi_wait_idle() inside cqspi_tune_phy() does not time out
while the controller is still waiting for AHB data.

Thread post_config_max_speed_hz from cqspi_mem_process() through
cqspi_write() into cqspi_indirect_write_execute() for the frequency check.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 39 +++++++++++++++++++++++++++----
 1 file changed, 34 insertions(+), 5 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 8903b4cf4f6f..940cfb72b946 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -157,6 +157,8 @@ struct cqspi_driver_platdata {
 #define CQSPI_DLL_TIMEOUT_US			300
 /* Minimum transfer length to use DMA for direct reads */
 #define CQSPI_PHY_MIN_DIRECT_READ_LEN		17
+/* Minimum indirect write length to amortize PHY enable/disable overhead */
+#define CQSPI_PHY_MIN_INDIRECT_WRITE_LEN	SZ_1K
 
 /* Runtime_pm autosuspend delay */
 #define CQSPI_AUTOSUSPEND_TIMEOUT		2000
@@ -1362,18 +1364,32 @@ static int cqspi_write_setup(struct cqspi_flash_pdata *f_pdata,
 
 static int cqspi_indirect_write_execute(struct cqspi_flash_pdata *f_pdata,
 					loff_t to_addr, const u8 *txbuf,
-					const size_t n_tx)
+					const size_t n_tx,
+					u32 post_config_max_speed_hz)
 {
 	struct cqspi_st *cqspi = f_pdata->cqspi;
 	struct device *dev = &cqspi->pdev->dev;
 	void __iomem *reg_base = cqspi->iobase;
 	unsigned int remaining = n_tx;
 	unsigned int write_bytes;
+	bool use_tuned_phy_write;
 	int ret;
 
 	if (!refcount_read(&cqspi->refcount))
 		return -ENODEV;
 
+	/* Use PHY only for large writes at the calibrated rate */
+	use_tuned_phy_write = n_tx >= CQSPI_PHY_MIN_INDIRECT_WRITE_LEN &&
+			      f_pdata->use_tuned_phy &&
+			      f_pdata->phy_write_op.max_freq ==
+				      post_config_max_speed_hz;
+
+	if (use_tuned_phy_write) {
+		ret = cqspi_tune_phy(f_pdata, true);
+		if (ret)
+			return ret;
+	}
+
 	writel(to_addr, reg_base + CQSPI_REG_INDIRECTWRSTARTADDR);
 	writel(remaining, reg_base + CQSPI_REG_INDIRECTWRBYTES);
 
@@ -1452,15 +1468,26 @@ static int cqspi_indirect_write_execute(struct cqspi_flash_pdata *f_pdata,
 
 	cqspi_wait_idle(cqspi);
 
+	if (use_tuned_phy_write)
+		cqspi_tune_phy(f_pdata, false);
+
 	return 0;
 
 failwr:
 	/* Disable interrupt. */
 	writel(0, reg_base + CQSPI_REG_IRQMASK);
 
-	/* Cancel the indirect write */
+	/*
+	 * Cancel the indirect write before disabling PHY so that
+	 * cqspi_wait_idle() inside cqspi_tune_phy() does not time out
+	 * while the controller is still waiting for AHB data.
+	 */
 	writel(CQSPI_REG_INDIRECTWR_CANCEL_MASK,
 	       reg_base + CQSPI_REG_INDIRECTWR);
+
+	if (use_tuned_phy_write)
+		cqspi_tune_phy(f_pdata, false);
+
 	return ret;
 }
 
@@ -1590,7 +1617,8 @@ static void cqspi_configure(struct cqspi_flash_pdata *f_pdata,
 }
 
 static ssize_t cqspi_write(struct cqspi_flash_pdata *f_pdata,
-			   const struct spi_mem_op *op)
+			   const struct spi_mem_op *op,
+			   u32 post_config_max_speed_hz)
 {
 	struct cqspi_st *cqspi = f_pdata->cqspi;
 	loff_t to = op->addr.val;
@@ -1617,7 +1645,8 @@ static ssize_t cqspi_write(struct cqspi_flash_pdata *f_pdata,
 		return cqspi_wait_idle(cqspi);
 	}
 
-	return cqspi_indirect_write_execute(f_pdata, to, buf, len);
+	return cqspi_indirect_write_execute(f_pdata, to, buf, len,
+					    post_config_max_speed_hz);
 }
 
 static bool cqspi_use_tuned_phy(struct cqspi_flash_pdata *f_pdata,
@@ -1810,7 +1839,7 @@ static int cqspi_mem_process(struct spi_mem *mem, const struct spi_mem_op *op)
 	if (!op->addr.nbytes || !op->data.buf.out)
 		return cqspi_command_write(f_pdata, op);
 
-	return cqspi_write(f_pdata, op);
+	return cqspi_write(f_pdata, op, mem->spi->post_config_max_speed_hz);
 }
 
 static int cqspi_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op)
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:33:11 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:03:11 +0530
Subject: [PATCH v7 16/18] mtd: spinand: negotiate optimal controller operating point before dirmap creation
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-17-s-k6@ti.com>

Add spinand_optimize_controller() which calls spi_mem_execute_tuning()
to negotiate the best operating point for the SPI NAND device before
creating dirmaps. The validated max_freq is then embedded into the
dirmap op templates so the controller uses the optimised frequency for
all subsequent page I/O.

The function tries the pre-selected op variant first. If the controller
signals that optimisation is not applicable for that specific op
(ret == 0 with max_freq still zero), spinand_try_ranked_variant()
iterates the remaining variants in descending performance order. For
devices supporting both ODTR and SSDR interfaces, ODTR variants are
tried first; if all ODTR variants fail, the bus is switched to SSDR and
SSDR variants are tried. On full failure the device reverts to ODTR
non-optimised mode.

Add spinand_reset_max_freq_ops() to copy op templates with max_freq
zeroed before each execute_tuning call, enforcing the invariant that a
non-zero max_freq only results from a successful operation.

Optimisation failure is never fatal; the device operates at the
conservative base rate.

When optimisation permanently selects SSDR (either because a ranked SSDR
variant succeeds or because reverting to ODTR after exhausting all
variants fails), clear the odtr_op_templates pointers. This prevents
spinand_configure_chip() on resume from re-entering ODTR mode with
dirmaps that were built for SSDR, which would cause data corruption.
Reusing the existing NULL-check mechanism avoids the need for a separate
flag and is consistent with how spinand_configure_chip() itself handles
ODTR configure_chip() failure.

Move spinand_create_dirmaps() from spinand_init() to spinand_probe() so
the validated frequency is available at dirmap construction time.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/mtd/nand/spi/core.c | 221 ++++++++++++++++++++++++++++++++++--
 include/linux/mtd/spinand.h |  11 ++
 2 files changed, 223 insertions(+), 9 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 7c3341f1fca0..dfc9d0871e91 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -1284,6 +1284,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
 	info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
 	info.primary_op_tmpl = *spinand->op_templates->update_cache;
 	info.primary_op_tmpl.data.ecc = enable_ecc;
+	info.primary_op_tmpl.max_freq = spinand->max_write_op.max_freq;
 	desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
 					  spinand->spimem, &info);
 	if (IS_ERR(desc))
@@ -1294,9 +1295,11 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
 	/* Read descriptor */
 	info.primary_op_tmpl = *spinand->op_templates->read_cache;
 	info.primary_op_tmpl.data.ecc = enable_ecc;
+	info.primary_op_tmpl.max_freq = spinand->max_read_op.max_freq;
 	if (secondary_op) {
 		info.secondary_op_tmpl = *spinand->op_templates->cont_read_cache;
 		info.secondary_op_tmpl.data.ecc = enable_ecc;
+		info.secondary_op_tmpl.max_freq = spinand->max_read_op.max_freq;
 	}
 	desc = spinand_create_rdesc(spinand, &info);
 	if (IS_ERR(desc))
@@ -1745,6 +1748,17 @@ int spinand_match_and_init(struct spinand_device *spinand,
 				spinand->cont_read_possible = false;
 		}
 
+		/*
+		 * Save the full read variant list (ODTR and SSDR ops) for
+		 * ranked controller optimization. Only saved when all ODTR
+		 * templates are valid; spinand_optimize_controller() uses this
+		 * to fall back to the next-best variant when needed.
+		 */
+		if (spinand->odtr_op_templates.read_cache &&
+		    spinand->odtr_op_templates.write_cache &&
+		    spinand->odtr_op_templates.update_cache)
+			spinand->all_read_variants = info->op_variants.read_cache;
+
 		return 0;
 	}
 
@@ -1923,7 +1937,6 @@ static int spinand_mtd_suspend(struct mtd_info *mtd)
 
 static int spinand_init(struct spinand_device *spinand)
 {
-	struct device *dev = &spinand->spimem->spi->dev;
 	struct mtd_info *mtd = spinand_to_mtd(spinand);
 	struct nand_device *nand = mtd_to_nanddev(mtd);
 	int ret;
@@ -2015,14 +2028,6 @@ static int spinand_init(struct spinand_device *spinand)
 	mtd->ecc_step_size = nanddev_get_ecc_conf(nand)->step_size;
 	mtd->bitflip_threshold = DIV_ROUND_UP(mtd->ecc_strength * 3, 4);
 
-	ret = spinand_create_dirmaps(spinand);
-	if (ret) {
-		dev_err(dev,
-			"Failed to create direct mappings for read/write operations (err = %d)\n",
-			ret);
-		goto err_cleanup_ecc_engine;
-	}
-
 	return 0;
 
 err_cleanup_ecc_engine:
@@ -2051,6 +2056,190 @@ static void spinand_cleanup(struct spinand_device *spinand)
 	kfree(spinand->scratchbuf);
 }
 
+/*
+ * spinand_try_ranked_variant() - Try controller optimization on variants in
+ *				  performance order.
+ * @spinand:    SPI NAND device
+ * @mem:        SPI memory device
+ * @iface:      bus interface to iterate (ODTR or SSDR)
+ * @tried_mask: bitmask of already-tried variant indices; updated on each try
+ *
+ * Iterates the full read variant list in descending performance order,
+ * skipping variants in @tried_mask, and calls execute_tuning on each until
+ * one succeeds. Ranked iteration finds the best available variant without
+ * re-trying already-attempted ones.
+ *
+ * On success, sets spinand->max_read_op and updates the matching
+ * odtr_op_templates.read_cache or ssdr_op_templates.read_cache.
+ */
+static bool spinand_try_ranked_variant(struct spinand_device *spinand,
+				       struct spi_mem *mem,
+				       enum spinand_bus_interface iface,
+				       u32 *tried_mask)
+{
+	const struct spinand_op_variants *variants = spinand->all_read_variants;
+	const struct spi_mem_op *best;
+	int ret;
+
+	if (!variants)
+		return false;
+
+	while ((best = spinand_op_find_best_variant(spinand, variants, iface,
+						    *tried_mask))) {
+		*tried_mask |= BIT(best - variants->ops);
+		spinand->max_read_op = *best;
+		spinand->max_read_op.max_freq = 0;
+		spinand->max_write_op.max_freq = 0;
+		ret = spi_mem_execute_tuning(mem, &spinand->max_read_op,
+					     &spinand->max_write_op);
+		if (ret && ret != -EOPNOTSUPP)
+			dev_dbg(&mem->spi->dev, "%s optimization failed: %d\n",
+				iface == ODTR ? "ODTR" : "SSDR", ret);
+		if (!ret && spinand->max_read_op.max_freq) {
+			if (iface == ODTR)
+				spinand->odtr_op_templates.read_cache = best;
+			else
+				spinand->ssdr_op_templates.read_cache = best;
+			spinand->cont_read_possible = false;
+			return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * spinand_reset_max_freq_ops() - Copy op templates and zero max_freq on both.
+ * @spinand:    SPI NAND device
+ * @templates:  op template set to copy from
+ *
+ * Called before execute_tuning so max_freq starts at zero; execute_tuning sets
+ * it to the validated clock rate only on success. A non-zero max_freq means
+ * controller-optimized; zero means the base rate applies.
+ */
+static void spinand_reset_max_freq_ops(struct spinand_device *spinand,
+				       struct spinand_mem_ops *templates)
+{
+	spinand->max_read_op = *templates->read_cache;
+	spinand->max_read_op.max_freq = 0;
+	spinand->max_write_op = *templates->write_cache;
+	spinand->max_write_op.max_freq = 0;
+}
+
+/*
+ * spinand_optimize_controller() - Negotiate the optimal controller operating
+ *				   point for the SPI NAND device.
+ * @spinand:    SPI NAND device
+ * @mem:        SPI memory device
+ *
+ * Tries the pre-selected variant first.  If the controller signals that
+ * optimization is not applicable for that specific op, iterates all remaining
+ * variants in performance order.  For devices that support both DTR and SDR
+ * interfaces, DTR variants are tried first; if all fail the device is
+ * switched to SDR mode and SDR variants are tried.  On full failure the
+ * device falls back to the best available non-optimized mode.  Devices that
+ * support only SDR skip the DTR ranked pass entirely.
+ *
+ * Optimization failure is never fatal.
+ *
+ * Note: tried_mask is u32, supporting up to 32 variants total across both
+ * ODTR and SSDR. Flash devices with more than 32 read variants are not
+ * supported.
+ */
+static void spinand_optimize_controller(struct spinand_device *spinand,
+					struct spi_mem *mem)
+{
+	u32 tried_mask;
+	int ret;
+
+	/* Skip entirely when no post-config target is configured. */
+	if (!mem->spi->post_config_max_speed_hz)
+		return;
+
+	spinand_reset_max_freq_ops(spinand, spinand->op_templates);
+
+	ret = spi_mem_execute_tuning(mem, &spinand->max_read_op,
+				     &spinand->max_write_op);
+	if (ret && ret != -EOPNOTSUPP)
+		dev_dbg(&mem->spi->dev, "Controller optimization failed: %d\n",
+			ret);
+
+	/*
+	 * Any non-zero return or a set max_freq means we are done (error,
+	 * unsupported, or success). Fallback only for the op-specific "skip"
+	 * signal: ret == 0 with max_freq still 0.
+	 */
+	if (ret || spinand->max_read_op.max_freq)
+		return;
+
+	/* SSDR-only devices have no ranked ODTR fallback available. */
+	if (spinand->bus_iface == SSDR || !spinand->all_read_variants)
+		return;
+
+	if (WARN_ON(spinand->all_read_variants->nops > 32))
+		return;
+
+	/* Mark the pre-selected ODTR variant as already tried. */
+	tried_mask = BIT(spinand->odtr_op_templates.read_cache -
+			 spinand->all_read_variants->ops);
+
+	dev_dbg(&mem->spi->dev,
+		"Optimization skipped for current op; searching for best variant\n");
+
+	/* Pass 1: try all remaining ODTR variants in performance order. */
+	if (spinand_try_ranked_variant(spinand, mem, ODTR, &tried_mask))
+		return;
+
+	/*
+	 * Pass 2: switch to SSDR and try all SSDR variants in performance
+	 * order. configure_chip is guaranteed non-NULL here: reaching ODTR
+	 * mode requires it.
+	 */
+	if (WARN_ON(!spinand->configure_chip))
+		goto use_odtr_fallback;
+
+	if (spinand->configure_chip(spinand, SSDR))
+		goto use_odtr_fallback;
+
+	spinand->op_templates = &spinand->ssdr_op_templates;
+	spinand->bus_iface = SSDR;
+	spinand->max_write_op = *spinand->ssdr_op_templates.write_cache;
+	spinand->max_write_op.max_freq = 0;
+
+	/*
+	 * Only ODTR variants were candidates in Pass 1; SSDR bits are clear.
+	 * Clear ODTR templates on success so spinand_configure_chip() on
+	 * resume does not re-enter ODTR with mismatched SSDR dirmaps.
+	 */
+	if (spinand_try_ranked_variant(spinand, mem, SSDR, &tried_mask)) {
+		spinand->odtr_op_templates.read_cache = NULL;
+		spinand->odtr_op_templates.write_cache = NULL;
+		spinand->odtr_op_templates.update_cache = NULL;
+		return;
+	}
+
+	/*
+	 * All attempts exhausted.  Revert to ODTR for non-optimized DTR
+	 * operation.  If revert fails, stay in SSDR ? a mode mismatch
+	 * (ODTR op templates on SSDR-mode device) would corrupt data.
+	 * Clear ODTR templates in either case to prevent resume from
+	 * re-entering ODTR with mismatched SSDR dirmaps.
+	 */
+	if (spinand->configure_chip(spinand, ODTR)) {
+		dev_warn(&mem->spi->dev,
+			 "Failed to revert to ODTR, staying in SSDR\n");
+		spinand->odtr_op_templates.read_cache = NULL;
+		spinand->odtr_op_templates.write_cache = NULL;
+		spinand->odtr_op_templates.update_cache = NULL;
+		spinand_reset_max_freq_ops(spinand, &spinand->ssdr_op_templates);
+		return;
+	}
+
+use_odtr_fallback:
+	spinand->op_templates = &spinand->odtr_op_templates;
+	spinand->bus_iface = ODTR;
+	spinand_reset_max_freq_ops(spinand, &spinand->odtr_op_templates);
+}
+
 static int spinand_probe(struct spi_mem *mem)
 {
 	struct spinand_device *spinand;
@@ -2073,6 +2262,20 @@ static int spinand_probe(struct spi_mem *mem)
 	if (ret)
 		return ret;
 
+	/*
+	 * Negotiate the best controller operating point before creating dirmaps
+	 * so the validated frequency is available at dirmap construction time.
+	 */
+	spinand_optimize_controller(spinand, mem);
+
+	ret = spinand_create_dirmaps(spinand);
+	if (ret) {
+		dev_err(&mem->spi->dev,
+			"Failed to create direct mappings for read/write operations (err = %d)\n",
+			ret);
+		goto err_spinand_cleanup;
+	}
+
 	ret = mtd_device_register(mtd, NULL, 0);
 	if (ret)
 		goto err_spinand_cleanup;
diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
index 5f4c00ae72a7..7eacda949719 100644
--- a/include/linux/mtd/spinand.h
+++ b/include/linux/mtd/spinand.h
@@ -792,8 +792,19 @@ struct spinand_device {
 	struct spinand_mem_ops *op_templates;
 	enum spinand_bus_interface bus_iface;
 
+	/*
+	 * Full read variant list (ODTR and SSDR ops together), saved when ODTR
+	 * templates are valid. Used by spinand_optimize_controller() for ranked
+	 * fallback when the pre-selected variant cannot be controller-optimized.
+	 */
+	const struct spinand_op_variants *all_read_variants;
+
 	struct spinand_dirmap *dirmaps;
 
+	/* Persistent op templates updated by execute_tuning with validated speed. */
+	struct spi_mem_op max_read_op;
+	struct spi_mem_op max_write_op;
+
 	int (*select_target)(struct spinand_device *spinand,
 			     unsigned int target);
 	unsigned int cur_target;
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:33:13 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:03:13 +0530
Subject: [PATCH v7 18/18] mtd: spi-nor: run controller optimization before dirmap creation
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-19-s-k6@ti.com>

Run controller optimization in spi_nor_probe() before creating dirmaps
so the validated frequency is available at dirmap construction time.

Store the configured read op template in nor->max_read_op and pass it
to spi_mem_execute_tuning(). On success the controller sets
max_read_op.max_freq to the calibrated rate.
spi_nor_spimem_get_read_op() propagates nor->max_read_op.max_freq into
every op it returns, so the validated frequency flows automatically into
the dirmap template and into regular read ops.

Optimization failure is non-fatal; the device operates at the
conservative base rate.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/mtd/spi-nor/core.c  | 14 ++++++++++++++
 include/linux/mtd/spi-nor.h |  3 +++
 2 files changed, 17 insertions(+)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index b683c077a233..2efcdc7f637f 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -216,6 +216,9 @@ static struct spi_mem_op spi_nor_spimem_get_read_op(struct spi_nor *nor)
 	if (spi_nor_protocol_is_dtr(nor->read_proto))
 		op.dummy.nbytes *= 2;
 
+	/* Propagate the validated frequency; zero before tuning. */
+	op.max_freq = nor->max_read_op.max_freq;
+
 	return op;
 }
 
@@ -3843,6 +3846,17 @@ static int spi_nor_probe(struct spi_mem *spimem)
 			return -ENOMEM;
 	}
 
+	/*
+	 * Populate the persistent template and run PHY tuning before dirmap
+	 * creation so the validated frequency feeds into the dirmap op.
+	 * Tuning failure is non-fatal; the device operates at base speed.
+	 */
+	nor->max_read_op = spi_nor_spimem_get_read_op(nor);
+
+	ret = spi_mem_execute_tuning(spimem, &nor->max_read_op, NULL);
+	if (ret && ret != -EOPNOTSUPP)
+		dev_dbg(dev, "Controller optimization failed: %d\n", ret);
+
 	ret = spi_nor_create_read_dirmap(nor);
 	if (ret)
 		return ret;
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 4b92494827b1..ab498a50f15f 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -422,6 +422,9 @@ struct spi_nor {
 		struct spi_mem_dirmap_desc *wdesc;
 	} dirmap;
 
+	/* Persistent op template updated by execute_tuning with validated speed. */
+	struct spi_mem_op max_read_op;
+
 	void *priv;
 };
 
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:33:09 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:03:09 +0530
Subject: [PATCH v7 14/18] spi: cadence-quadspi: reprogram PHY DLL on runtime resume
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-15-s-k6@ti.com>

The PHY DLL loses its programmed delay values and lock when the OSPI
controller is power-gated during runtime suspend. On resume,
cqspi_controller_init() reinitialises the controller but does not
touch the PHY DLL registers, leaving the DLL in an unlocked state.
Any subsequent PHY read or write would then fail at the cqspi_resync_dll()
call inside cqspi_tune_phy().

After the controller re-initialisation, iterate over all chip selects
and reprogram the DLL with each CS's stored calibrated RX/TX tap counts,
then resync to relock it. One invocation per calibrated CS allows each
calibration to be independently validated after resume. DLL resync
failure is non-fatal: a warning is emitted and resume continues, so the
system degrades to non-PHY speed rather than blocking the resume path.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index d4181b501d8d..c78d4ce1c930 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -4017,7 +4017,7 @@ static int cqspi_runtime_suspend(struct device *dev)
 static int cqspi_runtime_resume(struct device *dev)
 {
 	struct cqspi_st *cqspi = dev_get_drvdata(dev);
-	int ret;
+	int cs, ret;
 
 	ret = clk_bulk_prepare_enable(CLK_QSPI_NUM, cqspi->clks);
 	if (ret)
@@ -4030,6 +4030,23 @@ static int cqspi_runtime_resume(struct device *dev)
 
 	cqspi->current_cs = -1;
 	cqspi->sclk = 0;
+
+	for (cs = 0; cs < CQSPI_MAX_CHIPSELECT; cs++) {
+		struct cqspi_flash_pdata *f_pdata = &cqspi->f_pdata[cs];
+
+		if (!f_pdata->use_tuned_phy)
+			continue;
+
+		cqspi_phy_set_dll_master(cqspi);
+
+		cqspi_set_dll(cqspi->iobase, f_pdata->phy_setting.rx,
+			      f_pdata->phy_setting.tx);
+		ret = cqspi_resync_dll(cqspi);
+		if (ret)
+			dev_warn(dev, "cs%d: PHY DLL resync failed on resume: %d\n",
+				 cs, ret);
+	}
+
 	return 0;
 }
 
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:33:08 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:03:08 +0530
Subject: [PATCH v7 13/18] spi: cadence-quadspi: reprogram CS timing on every chip-select switch
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-14-s-k6@ti.com>

cqspi_configure() only reprogrammed per-device CS timing (CQSPI_REG_DELAY)
and the non-PHY read-capture register when the clock frequency changed, not
when the chip-select changed. In a multi-device setup where both devices
operate at the same frequency, a CS switch leaves the previous device's
timing parameters in hardware.

Split the update condition: baud rate divisor is updated on clock change
only (it is frequency-derived), but the per-device delay and read-capture
registers are now updated on any CS or clock switch.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 940cfb72b946..d4181b501d8d 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -1602,10 +1602,19 @@ static void cqspi_configure(struct cqspi_flash_pdata *f_pdata,
 		cqspi_chipselect(f_pdata);
 	}
 
-	/* Setup baudrate divisor and delays */
+	/* Update baudrate only on clock change. */
 	if (switch_ck) {
 		cqspi->sclk = sclk;
 		cqspi_config_baudrate_div(cqspi);
+	}
+
+	/*
+	 * Reprogram per-device CS timing and non-PHY read capture on any
+	 * chip-select or clock switch. Without this, two devices at the same
+	 * frequency would each see the other's timing parameters after a CS
+	 * switch, since the lazy clock-only check would never trigger.
+	 */
+	if (switch_cs || switch_ck) {
 		cqspi_delay(f_pdata);
 		if (!cqspi->phy_tuning_active)
 			cqspi_readdata_capture(cqspi, !cqspi->rclk_en, false,
-- 
2.34.1



From s-k6 at ti.com  Tue Aug 11 11:33:12 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Wed, 12 Aug 2026 00:03:12 +0530
Subject: [PATCH v7 17/18] mtd: spi-nor: extract read op template construction into helper
In-Reply-To: <20260811183313.1550425-1-s-k6@ti.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
Message-ID: <20260811183313.1550425-18-s-k6@ti.com>

From: Pratyush Yadav <pratyush at kernel.org>

Extract spi_nor_spimem_get_read_op() from the duplicated op-template
construction in spi_nor_spimem_read_data() and
spi_nor_create_read_dirmap().

Using data.nbytes = 2 as a non-zero placeholder lets
spi_nor_spimem_setup_op() configure the data buswidth without a separate
override; callers replace data.nbytes with the actual transfer length
before use. No functional change.

Signed-off-by: Pratyush Yadav <pratyush at kernel.org>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/mtd/spi-nor/core.c | 66 +++++++++++++++++++++-----------------
 1 file changed, 36 insertions(+), 30 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ccf4396cdcd0..b683c077a233 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -188,6 +188,37 @@ static int spi_nor_controller_ops_erase(struct spi_nor *nor, loff_t offs)
 	return nor->controller_ops->erase(nor, offs);
 }
 
+/**
+ * spi_nor_spimem_get_read_op() - build a configured read op template
+ * @nor:	the spi-nor device
+ *
+ * Returns a spi_mem_op with the command, address format, dummy cycles,
+ * and data buswidth configured for @nor. For direct reads, the caller
+ * must fill in addr.val, data.nbytes, and data.buf.in before use.
+ */
+static struct spi_mem_op spi_nor_spimem_get_read_op(struct spi_nor *nor)
+{
+	/*
+	 * data.nbytes must be non-zero so spi_nor_spimem_setup_op()
+	 * configures the data buswidth; callers replace it with the
+	 * actual transfer length.
+	 */
+	struct spi_mem_op op =
+		SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0),
+			   SPI_MEM_OP_ADDR(nor->addr_nbytes, 0, 0),
+			   SPI_MEM_OP_DUMMY(nor->read_dummy, 0),
+			   SPI_MEM_OP_DATA_IN(2, NULL, 0));
+
+	spi_nor_spimem_setup_op(nor, &op, nor->read_proto);
+
+	/* convert the dummy cycles to the number of bytes */
+	op.dummy.nbytes = (nor->read_dummy * op.dummy.buswidth) / 8;
+	if (spi_nor_protocol_is_dtr(nor->read_proto))
+		op.dummy.nbytes *= 2;
+
+	return op;
+}
+
 /**
  * spi_nor_spimem_read_data() - read data from flash's memory region via
  *                              spi-mem
@@ -201,21 +232,14 @@ static int spi_nor_controller_ops_erase(struct spi_nor *nor, loff_t offs)
 static ssize_t spi_nor_spimem_read_data(struct spi_nor *nor, loff_t from,
 					size_t len, u8 *buf)
 {
-	struct spi_mem_op op =
-		SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0),
-			   SPI_MEM_OP_ADDR(nor->addr_nbytes, from, 0),
-			   SPI_MEM_OP_DUMMY(nor->read_dummy, 0),
-			   SPI_MEM_OP_DATA_IN(len, buf, 0));
+	struct spi_mem_op op = spi_nor_spimem_get_read_op(nor);
 	bool usebouncebuf;
 	ssize_t nbytes;
 	int error;
 
-	spi_nor_spimem_setup_op(nor, &op, nor->read_proto);
-
-	/* convert the dummy cycles to the number of bytes */
-	op.dummy.nbytes = (nor->read_dummy * op.dummy.buswidth) / 8;
-	if (spi_nor_protocol_is_dtr(nor->read_proto))
-		op.dummy.nbytes *= 2;
+	op.addr.val = from;
+	op.data.nbytes = len;
+	op.data.buf.in = buf;
 
 	usebouncebuf = spi_nor_spimem_bounce(nor, &op);
 
@@ -3712,28 +3736,10 @@ static int spi_nor_create_read_dirmap(struct spi_nor *nor)
 {
 	struct spi_mem_dirmap_info info = {
 		.op_tmpl = &info.primary_op_tmpl,
-		.primary_op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0),
-					      SPI_MEM_OP_ADDR(nor->addr_nbytes, 0, 0),
-					      SPI_MEM_OP_DUMMY(nor->read_dummy, 0),
-					      SPI_MEM_OP_DATA_IN(0, NULL, 0)),
+		.primary_op_tmpl = spi_nor_spimem_get_read_op(nor),
 		.offset = 0,
 		.length = nor->params->size,
 	};
-	struct spi_mem_op *op = info.op_tmpl;
-
-	spi_nor_spimem_setup_op(nor, op, nor->read_proto);
-
-	/* convert the dummy cycles to the number of bytes */
-	op->dummy.nbytes = (nor->read_dummy * op->dummy.buswidth) / 8;
-	if (spi_nor_protocol_is_dtr(nor->read_proto))
-		op->dummy.nbytes *= 2;
-
-	/*
-	 * Since spi_nor_spimem_setup_op() only sets buswidth when the number
-	 * of data bytes is non-zero, the data buswidth won't be set here. So,
-	 * do it explicitly.
-	 */
-	op->data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto);
 
 	nor->dirmap.rdesc = devm_spi_mem_dirmap_create(nor->dev, nor->spimem,
 						       &info);
-- 
2.34.1



From rw at kernel.org  Tue Aug 11 14:05:53 2026
From: rw at kernel.org (Richard Weinberger (sigma star gmbh))
Date: Tue, 11 Aug 2026 23:05:53 +0200
Subject: [PATCH] ubifs: headers: clean up some kernel-doc comments
In-Reply-To: <20260729172044.1793865-1-rdunlap@infradead.org>
References: <20260729172044.1793865-1-rdunlap@infradead.org>
Message-ID: <178648234018.3948622.4036568993369014811.b4-ty@b4>

From: Richard Weinberger <richard at nod.at>


On Wed, 29 Jul 2026 10:20:41 -0700, Randy Dunlap wrote:
> Clean up kernel-doc comments in 3 ubifs header files:
> 
>  [PATCH 1/3] ubifs: debug.h: fix kernel-doc struct prototypes
>  [PATCH 2/3] ubifs: key.h: use correct function parameter name
>  [PATCH 3/3] ubifs: ubifs.h: clean up kernel-doc comments
> 
>  fs/ubifs/debug.h |    4 ++--
>  fs/ubifs/key.h   |    2 +-
>  fs/ubifs/ubifs.h |   20 ++++++++++++--------
>  3 files changed, 15 insertions(+), 11 deletions(-)
> 
> [...]

Applied, thanks!

[1/3] ubifs: debug.h: fix kernel-doc struct prototypes
      commit: f06711486c12c1fbb3ea9c0c595ca244b111891f
[2/3] ubifs: key.h: use correct function parameter name
      commit: abd6591fb5cc1490ab2cf5d9ff513a6677fa57b2
[3/3] ubifs: ubifs.h: clean up kernel-doc comments
      commit: 4e5e02340a18183b0f8ad633da1516cad92ee43e

Best regards,
-- 
Richard Weinberger <richard at nod.at>


From rw at kernel.org  Tue Aug 11 14:43:04 2026
From: rw at kernel.org (Richard Weinberger (sigma star gmbh))
Date: Tue, 11 Aug 2026 23:43:04 +0200
Subject: [PATCH 0/2] UBI: Fix torture flag handling in erase and fastmap paths
In-Reply-To: <20260704083042.459915-1-chengzhihao1@huawei.com>
References: <20260704083042.459915-1-chengzhihao1@huawei.com>
Message-ID: <178648450813.3956437.1095449689683161957.b4-ty@b4>

From: Richard Weinberger <rw at kernel.org>


On Sat, 04 Jul 2026 16:30:39 +0800, Zhihao Cheng wrote:
> This series fixes two issues where the UBI torture test flag is
> mishandled, causing physical eraseblocks that should be tortured to
> skip the test
> 
> Zhihao Cheng (2):
>   UBI: Preserve torture flag when rescheduling failed erasures
>   UBI: fastmap: Pass to_be_tortured when reusing old fastmap PEBs
> 
> [...]

Applied, thanks!

[1/2] UBI: Preserve torture flag when rescheduling failed erasures
      commit: 420477a84f1e52babfe60a43dbdd0988ed956845
[2/2] UBI: fastmap: Pass to_be_tortured when reusing old fastmap PEBs
      commit: be04fce9945aac2a843f5d16725391eb80d948ef


From rw at kernel.org  Tue Aug 11 14:47:58 2026
From: rw at kernel.org (Richard Weinberger)
Date: Tue, 11 Aug 2026 23:47:58 +0200
Subject: [PATCH] ubifs: fix out-of-bounds read in signature length
 check
In-Reply-To: <20260724074327.73582-1-security@auditcode.ai>
References: <20260724074327.73582-1-security@auditcode.ai>
Message-ID: <178648487886.3958324.18328006518346578815.b4-ty@b4>

On Fri, 24 Jul 2026 09:43:27 +0200, Ibrahim Hashimov wrote:
> ubifs_sb_verify_signature() bounds the on-disk ubifs_sig_node->len field
> before handing the signature payload to verify_pkcs7_signature(), but the
> check has the wrong sign:
> 
> 	if (le32_to_cpu(signode->len) > snod->len + sizeof(struct ubifs_sig_node))
> 
> The signature bytes start sizeof(struct ubifs_sig_node) (UBIFS_SIG_NODE_SZ,
> 64 bytes) into the node, so the payload is at most
> 
> [...]

Applied, thanks!

[1/1] ubifs: fix out-of-bounds read in signature length check
      commit: d68851fbac230fabaeeae25b19fe60ece173da88



From rw at kernel.org  Tue Aug 11 15:10:42 2026
From: rw at kernel.org (Richard Weinberger)
Date: Wed, 12 Aug 2026 00:10:42 +0200
Subject: [PATCH v1] mtd: ubi: Release device reference on busy detach
In-Reply-To: <20260807204550.1016755-1-dbgh9129@gmail.com>
References: <20260807204550.1016755-1-dbgh9129@gmail.com>
Message-ID: <178648624274.3963792.14878212710032601553.b4-ty@b4>

On Fri, 07 Aug 2026 16:45:50 -0400, Yuho Choi wrote:
> ubi_detach_mtd_dev() obtains a device reference through ubi_get_device()
> before checking whether the UBI device is busy. The busy return path drops
> ubi->ref_count but leaves the device reference held, so the device object
> cannot be released after a later detach.
> 
> Drop the device reference before returning -EBUSY.
> 
> [...]

Applied, thanks!

[1/1] mtd: ubi: Release device reference on busy detach
      commit: 31dd710cd84d5dd63c49f640d3a9f36c9699ca95



From rw at kernel.org  Tue Aug 11 15:10:45 2026
From: rw at kernel.org (Richard Weinberger)
Date: Wed, 12 Aug 2026 00:10:45 +0200
Subject: [PATCH 0/2] UBI: fix ubi.mtd MODULE_PARM_DESC issues and add
 per-device wear-leveling threshold
In-Reply-To: <20260723020233.3079997-1-ranhongyun1@huawei.com>
References: <20260723020233.3079997-1-ranhongyun1@huawei.com>
Message-ID: <178648624539.3963792.13357340650428197657.b4-ty@b4>

On Thu, 23 Jul 2026 10:02:31 +0800, Ran Hongyun wrote:
> This series adds a per-device wear-leveling threshold for UBI devices,
> with a preparatory fix to the MODULE_PARM_DESC.
> 
> Patch 1 fixes two pre-existing issues in the ubi.mtd MODULE_PARM_DESC:
> the parameter format string was missing the enable_fm and need_resv_pool
> tokens, and the bad-block reservation note was misplaced after Example 5
> instead of after Example 3.
> 
> [...]

Applied, thanks!

[1/2] UBI: fix two issues in the ubi.mtd MODULE_PARM_DESC
      commit: ff7937d14f5333ec7e6fb50d00a152d7974f4105
[2/2] UBI: support per-device wear-leveling threshold
      commit: a5e0055eac837a1168c781653943d4a0d9920af3



From dominique.martinet at atmark-techno.com  Tue Aug 11 16:23:33 2026
From: dominique.martinet at atmark-techno.com (Dominique Martinet)
Date: Wed, 12 Aug 2026 08:23:33 +0900
Subject: [PATCH 1/2] mtd: spinand: winbond: add support for W25N04LW
In-Reply-To: <ansYUoNKJyDQ+6f2@hu-mdalam-blr.qualcomm.com>
References: <20260807064339.1632249-1-md.alam@oss.qualcomm.com>
 <20260807064339.1632249-2-md.alam@oss.qualcomm.com>
 <anmLsPcXRk0VAQSW@atmark-techno.com>
 <ansYUoNKJyDQ+6f2@hu-mdalam-blr.qualcomm.com>
Message-ID: <anuu9USjU4GXBMC3@atmark-techno.com>

Md Sadre Alam wrote on Tue, Aug 11, 2026 at 06:10:50PM +0530:
> > I agree with Miqu?l that your patch looks odd, you wrote
> > > +		     NAND_MEMORG(1, 4096, 128, 64, 2048, 40, 1, 1, 1),
> > but the erase blocks are 256K long so it should be
> > +		     NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),

(I misremembered NAND_MEMORG() fields, the 256 is indeed the OOB area
size as you pointed out; the erase blocks were already correct at 64
pages = 256KB so while the settings were wrong this might actually not
be visible at all in quick testing; sorry)

> > FWIW, I've confirmed it works with the following diff (at least up to
> > the point of creating an ubi volume and booting from it); happy to send
> > this as a patch if this has indeed not been tested
>
> This device was supported in our downstream tree and was tested there. I
> picked that patch and posted it upstream assuming the configuration was
> already correct.
> The `128` in:
> NAND_MEMORG(1, 4096, 128, 64, 2048, 40, 1, 1, 1)
> was my mistake. It should indeed be `256`, matching the 4096 + 256 page
> layout in the W25N04LW datasheet.
> Please go ahead and post your tested patch. I do not currently have this
> device available to retest the upstream version.

Ok, let me do some slightly more thorough checks and clean up a bit and
I'll send a v2 later this week/early next week

The continuous read feature also definitely is interesting to me and
I'll be testing it later, but it's "just" a performance improvement so
I'll be taking a bit longer; I'll come back with either a patch or
questions hopefully by next month ish

Thanks,
-- 
Dominique



From xiaopei01 at kylinos.cn  Tue Aug 11 18:45:44 2026
From: xiaopei01 at kylinos.cn (Pei Xiao)
Date: Wed, 12 Aug 2026 09:45:44 +0800
Subject: [syzbot] [mtd?] divide error in block2mtd_setup2
In-Reply-To: <antnynHuZ2W8JmCc@cashel.logfs.org>
References: <6a791c94.01d0871a.3a0d52.009b.GAE@google.com>
 <ankxaiwtTltL0xMj@cashel.logfs.org>
 <29397db9-7db4-4969-bb0e-5bb71f0c0303@kylinos.cn>
 <87se4mqhc3.fsf@bootlin.com>
 <bbbff0ba-09c4-49dc-9aac-371c0c7d6902@kylinos.cn>
 <antnynHuZ2W8JmCc@cashel.logfs.org>
Message-ID: <29b0eb3f-8d9f-47ba-b0bd-c605a411c0ec@kylinos.cn>



? 2026/8/12 02:19, J?rn Engel ??:
> On Tue, Aug 11, 2026 at 11:02:53AM +0800, Pei Xiao wrote:
>>>
>> Thank you for your reply. I am waiting for a reply on whether the cast
>> on 'long' needs to be removed. Once that is confirmed, I would be happy
>> to do it. Thank you.
> 
> It doesn't need to be removed, but it should be removed.  Any
> unnecessary code should be removed in general and unnecessary casts in
> particular should.
> 
> One of better parts of the C language is that casts are hardly ever
> necessary.  Which means that any remaining necessary casts should
> immediately draw attention of the reader - something tricky and
> potentially dangerous is going on here.
> 
> But you stop paying attention when things become too common.  Which
> means that unnecessary casts are not just noise, they actively do harm.
> 
> 
> In this particular case, we can simply test whether the cast makes any
> difference:
> 
> #include <assert.h>
> #include <stdio.h>
> 
> int main(void)
> {
> 	for (int i=0; i<=1<<16; i++) {
> 		for (int k=1; k<=1<<16; k++) {
> 			long long size = (long long)i << 32;
> 			int erase_size = k;
> 
> 			long long a = size % erase_size;
> 			long long b = (long)size % erase_size;
> 			assert(a==b);
> 		}
> 	}
> }
Great test! You're truly professional, and I've learned a lot. I'll send
the v2 patch. Thank you for your guidance.

Pei.
> 
> Compile with optimizations and the compiler decides to remove the loop.
> Unless you suspect a compiler bug, I guess that settles the question. ;)
> 
> J?rn
> 
> --
> Semper ubi sub ubi ubique.
> -- latin pun



From xiaopei01 at kylinos.cn  Tue Aug 11 18:52:42 2026
From: xiaopei01 at kylinos.cn (Pei Xiao)
Date: Wed, 12 Aug 2026 09:52:42 +0800
Subject: [PATCH v2] mtd: block2mtd: Fix divide error when erase_size is zero
Message-ID: <48230456575d27aa83ce8640de8fc07cc62e8efb.1786499433.git.xiaopei01@kylinos.cn>

The erase size is parsed from the "block2mtd" module parameter and can
be set to zero. add_device() then evaluates

        if (size % erase_size)

with a zero divisor, which triggers a divide error:

        divide error: 0000 [#1] PREEMPT SMP PTI
        RIP: 0010:add_device drivers/mtd/devices/block2mtd.c:296 [inline]
        RIP: 0010:block2mtd_setup2+0x592/0xda0 drivers/mtd/devices/block2mtd.c:459
        Call Trace:
         block2mtd_setup+0x27/0xe0 drivers/mtd/devices/block2mtd.c:476
         param_attr_store+0x214/0x310 kernel/params.c:589
         module_attr_store+0x65/0x90 kernel/params.c:904
         kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345
         ...

Reject a zero erase size before performing the modulo operation so the
existing "erasesize must be a divisor of device size" error path
reports the invalid argument and frees the device.

While at it, drop the unnecessary (long) cast from the size operand of
the modulo.

Fixes: ea6d833a3fdd ("mtd: block2mtd: check device size")
Reported-by: syzbot+b320a4d5f65a61dbbf89 at syzkaller.appspotmail.com
Closes: https://lore.kernel.org/lkml/6a7b58ba.ac361c09.22ff0a.0045.GAE at google.com/
Suggested-by: J?rn Engel <joern at barelysecure.org>
Cc: stable at vger.kernel.org
Signed-off-by: Pei Xiao <xiaopei01 at kylinos.cn>
---
changlogs in v2:
1.Add Suggested-by tag
2.remove unnecessary (long) cast from the size
---
 drivers/mtd/devices/block2mtd.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c
index 03e80b2c4f5a..3e2367dfff88 100644
--- a/drivers/mtd/devices/block2mtd.c
+++ b/drivers/mtd/devices/block2mtd.c
@@ -293,7 +293,7 @@ static struct block2mtd_dev *add_device(char *devname, int erase_size,
 	}
 
 	size = bdev_nr_bytes(bdev);
-	if ((long)size % erase_size) {
+	if (!erase_size || size % erase_size) {
 		pr_err("erasesize must be a divisor of device size\n");
 		goto err_free_block2mtd;
 	}
-- 
2.25.1



From joern at barelysecure.org  Tue Aug 11 19:51:09 2026
From: joern at barelysecure.org (=?iso-8859-1?Q?J=F6rn?= Engel)
Date: Tue, 11 Aug 2026 19:51:09 -0700
Subject: [syzbot] [mtd?] divide error in block2mtd_setup2
In-Reply-To: <29b0eb3f-8d9f-47ba-b0bd-c605a411c0ec@kylinos.cn>
References: <6a791c94.01d0871a.3a0d52.009b.GAE@google.com>
 <ankxaiwtTltL0xMj@cashel.logfs.org>
 <29397db9-7db4-4969-bb0e-5bb71f0c0303@kylinos.cn>
 <87se4mqhc3.fsf@bootlin.com>
 <bbbff0ba-09c4-49dc-9aac-371c0c7d6902@kylinos.cn>
 <antnynHuZ2W8JmCc@cashel.logfs.org>
 <29b0eb3f-8d9f-47ba-b0bd-c605a411c0ec@kylinos.cn>
Message-ID: <anvfndFgcs1g52gQ@cashel.logfs.org>

On Wed, Aug 12, 2026 at 09:45:44AM +0800, Pei Xiao wrote:
>
> Great test! You're truly professional, and I've learned a lot. I'll send
> the v2 patch. Thank you for your guidance.

The test was actually a failure of sorts. :)

Actual idea was some kind of fuzzing.  Up to 32bit, computers can test
every possibility in a few seconds.  The number space here is 96bit,
which is a bit much.  But we can still test 4B random combinations or
exhaustively test some subset that is equivalent to about 4B
combinations.

While this is not a mathematical proof, a little bit of brute force is
generally quite cheap and catches the vast majority of bugs.  In this
case, the compiler refused to even generate a brute force test.  So I
guess that gave us something similar to a mathematical proof.

J?rn

--
The rabbit runs faster than the fox, because the rabbit is running for
his life while the fox is only running for his dinner.
-- Aesop


From dominique.martinet at atmark-techno.com  Tue Aug 11 21:14:02 2026
From: dominique.martinet at atmark-techno.com (Dominique Martinet)
Date: Wed, 12 Aug 2026 04:14:02 +0000
Subject: [PATCH RFC v2] mtd: spinand: winbond: add support for W25N04LW
Message-ID: <20260812-w25n04lw-v2-1-deee97602fc4@atmark-techno.com>

Add support for Winbond W25N04LW 4Gbit SPI-NAND:
> 1.8V 4G-bit
> Raw serial SLC NAND flash memory
> Dual/Quad SPI with 104MHz
> buffer read, continuous read & sequential read

Highlights copied from datasheet:
? Page size: 4,352 Bytes (4096 + 256 Bytes)
? Block size: 64 pages (256K + 16K Bytes)
- Built-in 8-Bit ECC for memory array
? ECC status bits indicate ECC results
? Bad Block Management and LUT access
? Software and Hardware Write-Protect
? Power Supply Lock-Down and OTP protection
? Unique ID and Parameter page
? Ten 4KB OTP pages per die
? Read Level Setting for Read Retry

The 4096+256 byte page layout is as follow:
- 4096 bytes ECC protected "main memory array", subdivided in 8
  512 bytes sectors
- 128 bytes spare array, subdivided in 8 16 bytes sectors as follow:
  - 2 bytes bad block marker (no ECC protection)
  - 2 bytes "user data II" (no ECC protection)
  - 12 bytes "user data I" (ECC protected)
- 128 bytes parity array, subdivided in 8 16 bytes sectors:
  - 13 bytes ECC for main data and user data
  - 3 unused bytes

This commit does not support most advanced features such as continuous
read, LUT management and OTP pages, and only provides basic usage.

OOB layout was defined to only show ECC-protected "user data I", leaving
"user data II" unavailable.

Signed-off-by: Daisuke Mizobuchi <mizo at atmark-techno.com>
Signed-off-by: Dominique Martinet <dominique.martinet at atmark-techno.com>
---
Changes in v2:
- Link to v1: https://patch.msgid.link/ansYUoNKJyDQ+6f2 at hu-mdalam-blr.qualcomm.com
- fixed oob layout
- fixed NAND_MEMORG oob size

I've tested the NAND with various mtd test utils (mtd_oobtest,
mtd_pagetest, mtd_subpagetest, mtd_nandbiterrs and some mtd-utils
userspace counterparts) with no problem, but I'm not sure what to do
with the ooblayout free() informations: as written in the commit
message, this chip has 2+12 bytes of user data per sector where the
first two are not ECC protected and the later 12 are.

I believe user should make the choice of which oob bytes they want to
use, but afaik we can't say "this is not protected", so we can't let
them choose and must either say both are free or only provide one like I
did here.

As an extra data point, this "user area I and II" distinction is the same
in W25N04KW (same 2+12), but w25n02kv_ooblayout_free() use there returns
the whole 14 bytes as a single chunk, so I explicitly made a different
choice here.
(I believe that's not something that can be changed easily, so we should
discuss this before merging)

That aside:
- Alam, would you like your name somewhere in the commit? I didn't keep
  anything from your commit because I already had one, but happy to add
  a Co-developed-by or something
- I kept NAND_ECCREQ(8, 512) like W25N04KW but the datasheet says it's
  based on 8-bits/544-bytes ECC, so I should set it as (8, 544)?
  (since it's protecting 512+12 bytes for up to 8 bits corruption)
  In practice ECC is done by hardware and looks like it is correctly
  reported (e.g. manually overwriting a couple of bits with nandwrite
  and checking with nanddump properly corrects and reports number of
  corrected bits), but might as well get this right, even if as far as I
  understand nothing actually uses the ECC oob data to double-check
  hardware status?
- I'll shamefully admit I do not understand the read / write /
  update_cache_variants() I copied from KW,
  Alam had the same but it'd be great to confirm using the same
  callbacks makes sense?
- (I wrote about OTP in the commit message but have no plan of
  implementing it at this point as we have no use for it, I still
  intend to look at continuous read after some other work)

Thanks!

-Dominique
---
 drivers/mtd/nand/spi/winbond.c | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
index 9b78c1e6cbc9..5b86e5cb06a3 100644
--- a/drivers/mtd/nand/spi/winbond.c
+++ b/drivers/mtd/nand/spi/winbond.c
@@ -335,6 +335,34 @@ static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section,
 	return 0;
 }
 
+static int w25n04lw_ooblayout_ecc(struct mtd_info *mtd, int section,
+				  struct mtd_oob_region *region)
+{
+	if (section > 7)
+		return -ERANGE;
+
+	region->offset = 128 + (16 * section);
+	region->length = 13;
+
+	return 0;
+}
+
+static int w25n04lw_ooblayout_free(struct mtd_info *mtd, int section,
+				   struct mtd_oob_region *region)
+{
+	if (section > 7)
+		return -ERANGE;
+
+	// the W25N04LW chip actually has two free ranges per section:
+	// "User Data I" at (16 * section) + 4, length 12
+	// "User Data II" at (16 * section) + 2, length 2
+	// The later is not ECC protected so this only returns User Data I
+	region->offset = (16 * section) + 4;
+	region->length = 12;
+
+	return 0;
+}
+
 static const struct mtd_ooblayout_ops w25n01kv_ooblayout = {
 	.ecc = w25n01kv_ooblayout_ecc,
 	.free = w25n02kv_ooblayout_free,
@@ -345,6 +373,11 @@ static const struct mtd_ooblayout_ops w25n02kv_ooblayout = {
 	.free = w25n02kv_ooblayout_free,
 };
 
+static const struct mtd_ooblayout_ops w25n04lw_ooblayout = {
+	.ecc = w25n04lw_ooblayout_ecc,
+	.free = w25n04lw_ooblayout_free,
+};
+
 static int w25n01jw_ooblayout_ecc(struct mtd_info *mtd, int section,
 				  struct mtd_oob_region *region)
 {
@@ -768,6 +801,15 @@ static const struct spinand_info winbond_spinand_table[] = {
 					      &update_cache_variants),
 		     0,
 		     SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
+	SPINAND_INFO("W25N04LW", /* 1.8V */
+		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb2, 0x23),
+		     NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
+		     NAND_ECCREQ(8, 512),
+		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+					      &write_cache_variants,
+					      &update_cache_variants),
+		     0,
+		     SPINAND_ECCINFO(&w25n04lw_ooblayout, w25n02kv_ecc_get_status)),
 	SPINAND_INFO("W35N04JW", /* 1.8V */
 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x23),
 		     NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 4, 1),

---
base-commit: db2ddb87143519e20a95aa36c60b36107b736a58
change-id: 20260812-w25n04lw-3e9146d8340f

Best regards,
--  
Dominique Martinet <dominique.martinet at atmark-techno.com>




From syzbot+3a8099322b09d8d073d1 at syzkaller.appspotmail.com  Tue Aug 11 22:00:39 2026
From: syzbot+3a8099322b09d8d073d1 at syzkaller.appspotmail.com (syzbot)
Date: Tue, 11 Aug 2026 22:00:39 -0700
Subject: [syzbot] [jffs2?] KASAN: slab-use-after-free Read in jffs2_xattr_delete_inode
Message-ID: <6a7bfdf7.ed659fcc.23056a.007f.GAE@google.com>

Hello,

syzbot found the following issue on:

HEAD commit:    bcc44b6785f2 Merge tag 'drm-fixes-2026-08-08' of https://g..
git tree:       upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=147d7e49580000
kernel config:  https://syzkaller.appspot.com/x/.config?x=158601bb8cb292dd
dashboard link: https://syzkaller.appspot.com/bug?extid=3a8099322b09d8d073d1
compiler:       Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8

Unfortunately, I don't have any reproducer for this issue yet.

Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/e4ca3ce763a0/disk-bcc44b67.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/face1dc3e618/vmlinux-bcc44b67.xz
kernel image: https://storage.googleapis.com/syzbot-assets/185a6a3df821/bzImage-bcc44b67.xz

IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+3a8099322b09d8d073d1 at syzkaller.appspotmail.com

RBP: 00007f5b86286090 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
R13: 00007f5b882b6038 R14: 00007f5b882b5fa0 R15: 00007ffe7e6b8828
 </TASK>
==================================================================
BUG: KASAN: slab-use-after-free in jffs2_xattr_delete_inode+0x2c8/0x2d0 fs/jffs2/xattr.c:602
Read of size 4 at addr ffff888029725ee8 by task syz.8.1428/17657

CPU: 0 UID: 0 PID: 17657 Comm: syz.8.1428 Not tainted syzkaller #0 PREEMPT_{RT,(full)} 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/24/2026
Call Trace:
 <TASK>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 print_address_description+0x55/0x1e0 mm/kasan/report.c:378
 print_report+0x58/0x70 mm/kasan/report.c:482
 kasan_report+0x117/0x150 mm/kasan/report.c:595
 jffs2_xattr_delete_inode+0x2c8/0x2d0 fs/jffs2/xattr.c:602
 jffs2_do_clear_inode+0x58/0x390 fs/jffs2/readinode.c:1418
 evict+0x624/0xb50 fs/inode.c:825
 jffs2_new_inode+0x49c/0xd00 fs/jffs2/fs.c:-1
 jffs2_mkdir+0x192/0xeb0 fs/jffs2/dir.c:485
 vfs_mkdir+0x402/0x620 fs/namei.c:5276
 filename_mkdirat+0x289/0x520 fs/namei.c:5309
 __do_sys_mkdirat fs/namei.c:5330 [inline]
 __se_sys_mkdirat+0x35/0x150 fs/namei.c:5327
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f5b8802e0d9
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f5b86286028 EFLAGS: 00000246 ORIG_RAX: 0000000000000102
RAX: ffffffffffffffda RBX: 00007f5b882b5fa0 RCX: 00007f5b8802e0d9
RDX: 0000000000000000 RSI: 0000200000000640 RDI: ffffffffffffff9c
RBP: 00007f5b86286090 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
R13: 00007f5b882b6038 R14: 00007f5b882b5fa0 R15: 00007ffe7e6b8828
 </TASK>

Allocated by task 14460:
 kasan_save_stack mm/kasan/common.c:57 [inline]
 kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
 unpoison_slab_object mm/kasan/common.c:340 [inline]
 __kasan_slab_alloc+0x6c/0x80 mm/kasan/common.c:366
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4584 [inline]
 slab_alloc_node mm/slub.c:4917 [inline]
 kmem_cache_alloc_noprof+0x355/0x650 mm/slub.c:4931
 jffs2_scan_make_ino_cache+0x89/0x1d0 fs/jffs2/scan.c:978
 jffs2_scan_dirent_node fs/jffs2/scan.c:1099 [inline]
 jffs2_scan_eraseblock fs/jffs2/scan.c:831 [inline]
 jffs2_scan_medium+0x2b58/0x70c0 fs/jffs2/scan.c:151
 jffs2_build_filesystem fs/jffs2/build.c:112 [inline]
 jffs2_do_mount_fs+0x775/0x22c0 fs/jffs2/build.c:416
 jffs2_do_fill_super+0x555/0xc50 fs/jffs2/fs.c:574
 mtd_get_sb+0x18a/0x410 drivers/mtd/mtdsuper.c:57
 mtd_get_sb_by_nr drivers/mtd/mtdsuper.c:88 [inline]
 get_tree_mtd+0x4bd/0x7d0 drivers/mtd/mtdsuper.c:158
 vfs_get_tree+0x92/0x2a0 fs/super.c:1700
 fc_mount fs/namespace.c:1198 [inline]
 do_new_mount_fc fs/namespace.c:3765 [inline]
 do_new_mount+0x319/0xdc0 fs/namespace.c:3841
 do_mount fs/namespace.c:4174 [inline]
 __do_sys_mount fs/namespace.c:4390 [inline]
 __se_sys_mount+0x31d/0x420 fs/namespace.c:4367
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Freed by task 7915:
 kasan_save_stack mm/kasan/common.c:57 [inline]
 kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
 kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
 poison_slab_object mm/kasan/common.c:253 [inline]
 __kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
 kasan_slab_free include/linux/kasan.h:235 [inline]
 slab_free_hook mm/slub.c:2677 [inline]
 slab_free mm/slub.c:6377 [inline]
 kmem_cache_free+0x187/0x6c0 mm/slub.c:6504
 jffs2_free_ino_caches+0xec/0x1c0 fs/jffs2/nodelist.c:497
 jffs2_put_super+0x6c/0x170 fs/jffs2/super.c:335
 generic_shutdown_super+0x13d/0x2d0 fs/super.c:647
 kill_mtd_super+0x23/0x70 drivers/mtd/mtdsuper.c:174
 jffs2_kill_sb+0x96/0xb0 fs/jffs2/super.c:350
 deactivate_locked_super+0xbc/0x130 fs/super.c:477
 cleanup_mnt+0x3d3/0x460 fs/namespace.c:1317
 task_work_run+0x1d9/0x270 kernel/task_work.c:233
 resume_user_mode_work include/linux/resume_user_mode.h:50 [inline]
 __exit_to_user_mode_loop kernel/entry/common.c:70 [inline]
 exit_to_user_mode_loop+0x1fa/0x730 kernel/entry/common.c:101
 __exit_to_user_mode_prepare include/linux/irq-entry-common.h:207 [inline]
 syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:230 [inline]
 syscall_exit_to_user_mode include/linux/entry-common.h:318 [inline]
 do_syscall_64+0x353/0x580 arch/x86/entry/syscall_64.c:100
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

The buggy address belongs to the object at ffff888029725ec0
 which belongs to the cache jffs2_inode_cache of size 48
The buggy address is located 40 bytes inside of
 freed 48-byte region [ffff888029725ec0, ffff888029725ef0)

The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0xffff888029725700 pfn:0x29725
flags: 0x80000000000200(workingset|node=0|zone=1)
page_type: f5(slab)
raw: 0080000000000200 ffff888140435280 ffffea000085b790 ffff88801eb7ae88
raw: ffff888029725700 000000080040001c 00000000f5000000 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0xd2cc0(GFP_KERNEL|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5878, tgid 5877 (syz.2.27), ts 110407811516, free_ts 110388090000
 set_page_owner include/linux/page_owner.h:32 [inline]
 post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1859
 prep_new_page mm/page_alloc.c:1867 [inline]
 get_page_from_freelist+0x262a/0x26a0 mm/page_alloc.c:3946
 __alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5304
 alloc_slab_page mm/slub.c:3266 [inline]
 allocate_slab+0x79/0x5e0 mm/slub.c:3380
 new_slab mm/slub.c:3426 [inline]
 refill_objects+0x2d8/0x350 mm/slub.c:7310
 refill_sheaf mm/slub.c:2804 [inline]
 __pcs_replace_empty_main+0x330/0x690 mm/slub.c:4675
 alloc_from_pcs mm/slub.c:4773 [inline]
 slab_alloc_node mm/slub.c:4905 [inline]
 kmem_cache_alloc_noprof+0x452/0x650 mm/slub.c:4931
 jffs2_do_new_inode+0x27/0x500 fs/jffs2/write.c:28
 jffs2_new_inode+0x46e/0xd00 fs/jffs2/fs.c:466
 jffs2_create+0x87/0x320 fs/jffs2/dir.c:182
 lookup_open fs/namei.c:4508 [inline]
 open_last_lookups fs/namei.c:4608 [inline]
 path_openat+0x1344/0x3850 fs/namei.c:4860
 do_file_open+0x23e/0x4a0 fs/namei.c:4892
 do_sys_openat2+0x115/0x200 fs/open.c:1368
 do_sys_open fs/open.c:1374 [inline]
 __do_sys_openat fs/open.c:1390 [inline]
 __se_sys_openat fs/open.c:1385 [inline]
 __x64_sys_openat+0x138/0x170 fs/open.c:1385
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
page last free pid 20 tgid 20 stack trace:
 reset_page_owner include/linux/page_owner.h:25 [inline]
 __free_pages_prepare mm/page_alloc.c:1406 [inline]
 __free_frozen_pages+0x10e7/0x11c0 mm/page_alloc.c:2950
 rcu_do_batch kernel/rcu/tree.c:2645 [inline]
 rcu_core kernel/rcu/tree.c:2897 [inline]
 rcu_cpu_kthread+0x950/0x1480 kernel/rcu/tree.c:2985
 smpboot_thread_fn+0x57c/0xa80 kernel/smpboot.c:160
 kthread+0x388/0x470 kernel/kthread.c:436
 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245

Memory state around the buggy address:
 ffff888029725d80: fa fb fb fb fb fb fc fc fa fb fb fb fb fb fc fc
 ffff888029725e00: fa fb fb fb fb fb fc fc fa fb fb fb fb fb fc fc
>ffff888029725e80: fa fb fb fb fb fb fc fc fa fb fb fb fb fb fc fc
                                                          ^
 ffff888029725f00: fa fb fb fb fb fb fc fc fa fb fb fb fb fb fc fc
 ffff888029725f80: fa fb fb fb fb fb fc fc fa fb fb fb fb fb fc fc
==================================================================


---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller at googlegroups.com.

syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.

If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title

If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)

If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report

If you want to undo deduplication, reply with:
#syz undup


From dominique.martinet at atmark-techno.com  Tue Aug 11 22:04:30 2026
From: dominique.martinet at atmark-techno.com (Dominique Martinet)
Date: Wed, 12 Aug 2026 14:04:30 +0900
Subject: [PATCH RFC v2] mtd: spinand: winbond: add support for W25N04LW
In-Reply-To: <20260812-w25n04lw-v2-1-deee97602fc4@atmark-techno.com>
References: <20260812-w25n04lw-v2-1-deee97602fc4@atmark-techno.com>
Message-ID: <anv-3hSRMVgJAsOt@atmark-techno.com>

Dominique Martinet wrote on Wed, Aug 12, 2026 at 04:14:02AM +0000:
> +	// the W25N04LW chip actually has two free ranges per section:
> +	// "User Data I" at (16 * section) + 4, length 12
> +	// "User Data II" at (16 * section) + 2, length 2
> +	// The later is not ECC protected so this only returns User Data I

(will fix to use block comment like the rest of the file + latter typo
as per Sashiko comment in v3, after we've reached an agreement on what
to do here -- there were no other Sashiko comment)

-- 
Dominique




From mwalle at kernel.org  Tue Aug 11 23:14:18 2026
From: mwalle at kernel.org (Michael Walle)
Date: Wed, 12 Aug 2026 08:14:18 +0200
Subject: [PATCH] mtd: spi-nor: scope the exclusive RWW lock
In-Reply-To: <6e208a8b-da11-4ba1-adfc-f1e80c891310@linaro.org>
References: <20260809084223.3596259-1-runyu.xiao@seu.edu.cn>
 <DKL4CBQNKCO2.3NAZ6S25Q60XN@kernel.org>
 <6e208a8b-da11-4ba1-adfc-f1e80c891310@linaro.org>
Message-ID: <DKMQSZRM4QEZ.2EXQD5U58EVTI@kernel.org>

On Tue Aug 11, 2026 at 12:05 PM CEST, Tudor Ambarus wrote:
>
>
> On 8/10/26 11:25 AM, Michael Walle wrote:
>> Tudor, do
>> you know why you've omitted the guard() in spi_nor_rww_start_exclusive()?
>
> because using guard changes the behavior of the code.

But that commit removed the mutex_unlock(), thus keeping the lock.
That wasn't the case before the commit. IMHO the Fixes: tag is
correct.

-michael


From miquel.raynal at bootlin.com  Wed Aug 12 00:35:55 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 12 Aug 2026 09:35:55 +0200
Subject: [PATCH RFC v2] mtd: spinand: winbond: add support for W25N04LW
In-Reply-To: <anv-3hSRMVgJAsOt@atmark-techno.com> (Dominique Martinet's
	message of "Wed, 12 Aug 2026 14:04:30 +0900")
References: <20260812-w25n04lw-v2-1-deee97602fc4@atmark-techno.com>
	<anv-3hSRMVgJAsOt@atmark-techno.com>
Message-ID: <87tsozn63o.fsf@bootlin.com>

On 12/08/2026 at 14:04:30 +09, Dominique Martinet <dominique.martinet at atmark-techno.com> wrote:

> Dominique Martinet wrote on Wed, Aug 12, 2026 at 04:14:02AM +0000:
>> +	// the W25N04LW chip actually has two free ranges per section:
>> +	// "User Data I" at (16 * section) + 4, length 12
>> +	// "User Data II" at (16 * section) + 2, length 2
>> +	// The later is not ECC protected so this only returns User Data I
>
> (will fix to use block comment like the rest of the file + latter typo
> as per Sashiko comment in v3, after we've reached an agreement on what
> to do here -- there were no other Sashiko comment)

I haven't checked the diff yet, but the commit message contains a SoB
from anther person which is listed first. You must be Author + first SoB or
you can give someone else authorship + first SoB and take 2nd SoB (which
means you carried the patch without changing it). If you want to credit
another person, please have a look at the Co-developed-by wording.

Thanks!
Miqu?l


From dominique.martinet at atmark-techno.com  Wed Aug 12 01:08:14 2026
From: dominique.martinet at atmark-techno.com (Dominique Martinet)
Date: Wed, 12 Aug 2026 17:08:14 +0900
Subject: [PATCH RFC v2] mtd: spinand: winbond: add support for W25N04LW
In-Reply-To: <87tsozn63o.fsf@bootlin.com>
References: <20260812-w25n04lw-v2-1-deee97602fc4@atmark-techno.com>
 <anv-3hSRMVgJAsOt@atmark-techno.com>
 <87tsozn63o.fsf@bootlin.com>
Message-ID: <anwp7lxvdKOEr3Vw@atmark-techno.com>

Miquel Raynal wrote on Wed, Aug 12, 2026 at 09:35:55AM +0200:
> On 12/08/2026 at 14:04:30 +09, Dominique Martinet <dominique.martinet at atmark-techno.com> wrote:
> 
> > Dominique Martinet wrote on Wed, Aug 12, 2026 at 04:14:02AM +0000:
> >> +	// the W25N04LW chip actually has two free ranges per section:
> >> +	// "User Data I" at (16 * section) + 4, length 12
> >> +	// "User Data II" at (16 * section) + 2, length 2
> >> +	// The later is not ECC protected so this only returns User Data I
> >
> > (will fix to use block comment like the rest of the file + latter typo
> > as per Sashiko comment in v3, after we've reached an agreement on what
> > to do here -- there were no other Sashiko comment)
> 
> I haven't checked the diff yet, but the commit message contains a SoB
> from anther person which is listed first. You must be Author + first SoB or
> you can give someone else authorship + first SoB and take 2nd SoB (which
> means you carried the patch without changing it). If you want to credit
> another person, please have a look at the Co-developed-by wording.

Thanks, will add a Co-developed-by tag for them in v3 after other
discussions are settled


I've also been stressing/torturing the nand a bit and I'm noticing a lot
of ECC errors (regardless of erase cycle count); all ECC errors are
apparently focused on two bits regardless of the sector the error
happened on (bit 0x20 at offset either 0x7e or 0x80 in any sector,
from checking data with nanddump -n and diffing the output)

This error is reliably readable so it happened on write, but
erase+nandwrite properly clears the error, and there's just too many
(~20-40 over 1MB of data, so 2048 512 bytes sectors), so I think that's
worth investigating further before applying even if I don't see what
could possibly cause this in the nand code

(Well, I guess it could also just be how this batch turned out, but I'm
surprised that the errors would always happen on the same two bits...
It didn't happen with the 04KW so hopefully not a SPI bug either...)

I need to wait next week to test on different hardware to rule out a
hardware bug and I'll reach out to our contact at winbond as well

Thanks,
-- 
Dominique




From mwalle at kernel.org  Wed Aug 12 02:44:41 2026
From: mwalle at kernel.org (Michael Walle)
Date: Wed, 12 Aug 2026 11:44:41 +0200
Subject: [PATCH] mtd: spi-nor: Unprepare legacy controllers upon RWW
 wait interruption
In-Reply-To: <20260811-perso-rww-fixes-v1-1-5866a70e5d1c@bootlin.com>
References: <20260811-perso-rww-fixes-v1-1-5866a70e5d1c@bootlin.com>
Message-ID: <DKMVA2HIPB2Z.J0W6OM4H3ARL@kernel.org>

On Tue Aug 11, 2026 at 5:56 PM CEST, Miquel Raynal wrote:
> All the parallel locking helpers (spi_nor_prep_and_lock(),
> spi_nor_prep_and_lock_pe() and spi_nor_prep_and_lock_rd()) call
> spi_nor_prep() then wait with a killable sleep. If the sleep gets
> killed, we leave the controller in its prepared state.
>

Why does this only affect legacy controllers?

> Call ->unprepare() in these cases.
>
> Fixes: 74df43b3f626 ("mtd: spi-nor: Enhance locking to support reads while writes")
> Cc: stable at vger.kernel.org
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>

-michael


From tudor.ambarus at linaro.org  Wed Aug 12 02:48:22 2026
From: tudor.ambarus at linaro.org (Tudor Ambarus)
Date: Wed, 12 Aug 2026 12:48:22 +0300
Subject: [PATCH] mtd: spi-nor: scope the exclusive RWW lock
In-Reply-To: <DKMQSZRM4QEZ.2EXQD5U58EVTI@kernel.org>
References: <20260809084223.3596259-1-runyu.xiao@seu.edu.cn>
 <DKL4CBQNKCO2.3NAZ6S25Q60XN@kernel.org>
 <6e208a8b-da11-4ba1-adfc-f1e80c891310@linaro.org>
 <DKMQSZRM4QEZ.2EXQD5U58EVTI@kernel.org>
Message-ID: <754d6dc1-1993-4fe7-ba45-31d02f7e7a0d@linaro.org>



On 8/12/26 9:14 AM, Michael Walle wrote:
> On Tue Aug 11, 2026 at 12:05 PM CEST, Tudor Ambarus wrote:
>>
>>
>> On 8/10/26 11:25 AM, Michael Walle wrote:
>>> Tudor, do
>>> you know why you've omitted the guard() in spi_nor_rww_start_exclusive()?
>>
>> because using guard changes the behavior of the code.
> 
> But that commit removed the mutex_unlock(), thus keeping the lock.

oh, the horror, indeed.

> That wasn't the case before the commit. IMHO the Fixes: tag is
> correct.

+1




From tudor.ambarus at linaro.org  Wed Aug 12 02:49:20 2026
From: tudor.ambarus at linaro.org (Tudor Ambarus)
Date: Wed, 12 Aug 2026 12:49:20 +0300
Subject: [PATCH v3] mtd: spi-nor: use guard() in
 spi_nor_rww_start_exclusive
In-Reply-To: <20260811083111.403453-1-runyu.xiao@seu.edu.cn>
References: <20260811041349.333176-1-runyu.xiao@seu.edu.cn>
 <20260811083111.403453-1-runyu.xiao@seu.edu.cn>
Message-ID: <5a4670bb-e24c-4adb-889a-01f54f04d32c@linaro.org>



On 8/11/26 11:31 AM, Runyu Xiao wrote:
> Commit 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup
> helpers") converted the RWW start helpers to scoped mutex cleanup, but
> left spi_nor_rww_start_exclusive() using a plain mutex_lock().
> 
> spi_nor_rww_start_exclusive() is used as a wait_event_killable()
> condition. When it returns false on the busy path, the raw mutex_lock()
> leaves nor->lock held and blocks the operation that must clear the RWW
> state in spi_nor_rww_end_exclusive().
> 
> Use guard(mutex) here as well so this helper matches the other RWW
> start helpers and releases nor->lock on the false return path.
> 
> Fixes: 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex cleanup helpers")
> Cc: stable at vger.kernel.org
> Signed-off-by: Runyu Xiao <runyu.xiao at seu.edu.cn>

Reviewed-by: Tudor Ambarus <tudor.ambarus at linaro.org>

> ---
> Changes in v3:
> - Reframe the changelog around the scoped-helper conversion that missed
>   spi_nor_rww_start_exclusive().
> 
> Changes in v2:
> - Replace the analysis-style explanation with a short maintainer-facing
>   description of the busy-path lock hold.
> - Keep the fix focused on using guard(mutex) so the mutex is released on
>   both paths.
> 
> drivers/mtd/spi-nor/core.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index ccf4396cdcd0..8bc117b46e02 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
> @@ -1310,7 +1310,7 @@ static bool spi_nor_rww_start_exclusive(struct spi_nor *nor)
>  {
>  	struct spi_nor_rww *rww = &nor->rww;
>  
> -	mutex_lock(&nor->lock);
> +	guard(mutex)(&nor->lock);
>  
>  	if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe)
>  		return false;



From tudor.ambarus at linaro.org  Wed Aug 12 02:52:48 2026
From: tudor.ambarus at linaro.org (Tudor Ambarus)
Date: Wed, 12 Aug 2026 12:52:48 +0300
Subject: [PATCH] mtd: spi-nor: Unprepare legacy controllers upon RWW wait
 interruption
In-Reply-To: <20260811-perso-rww-fixes-v1-1-5866a70e5d1c@bootlin.com>
References: <20260811-perso-rww-fixes-v1-1-5866a70e5d1c@bootlin.com>
Message-ID: <e0d2710a-2a67-40e0-aacb-5d18742cea76@linaro.org>



On 8/11/26 6:56 PM, Miquel Raynal wrote:
> All the parallel locking helpers (spi_nor_prep_and_lock(),
> spi_nor_prep_and_lock_pe() and spi_nor_prep_and_lock_rd()) call
> spi_nor_prep() then wait with a killable sleep. If the sleep gets
> killed, we leave the controller in its prepared state.
> 
> Call ->unprepare() in these cases.


We don't even care about SPI NOR controllers. I stopped adding/accepting
new support for them few years ago, so that we encourage people to update
the drivers to use SPI MEM.

The alternative is to drop prep unprep entirely.

Anyway, all good, regardless the path chosen:

Acked-by: Tudor Ambarus <tudor.ambarus at linaro.org>
> 
> Fixes: 74df43b3f626 ("mtd: spi-nor: Enhance locking to support reads while writes")
> Cc: stable at vger.kernel.org
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
> ---
>  drivers/mtd/spi-nor/core.c | 9 +++++++++
>  1 file changed, 9 insertions(+)
> 
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index 8bc117b46e02..250e996284f9 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
> @@ -1346,6 +1346,9 @@ int spi_nor_prep_and_lock(struct spi_nor *nor)
>  		ret = wait_event_killable(nor->rww.wait,
>  					  spi_nor_rww_start_exclusive(nor));
>  
> +	if (ret)
> +		spi_nor_unprep(nor);
> +
>  	return ret;
>  }
>  
> @@ -1417,6 +1420,9 @@ static int spi_nor_prep_and_lock_pe(struct spi_nor *nor, loff_t start, size_t le
>  		ret = wait_event_killable(nor->rww.wait,
>  					  spi_nor_rww_start_pe(nor, start, len));
>  
> +	if (ret)
> +		spi_nor_unprep(nor);
> +
>  	return ret;
>  }
>  
> @@ -1490,6 +1496,9 @@ static int spi_nor_prep_and_lock_rd(struct spi_nor *nor, loff_t start, size_t le
>  		ret = wait_event_killable(nor->rww.wait,
>  					  spi_nor_rww_start_rd(nor, start, len));
>  
> +	if (ret)
> +		spi_nor_unprep(nor);
> +
>  	return ret;
>  }
>  
> 
> ---
> base-commit: db5f6418501f9795a96026b764d747179a231b2b
> change-id: 20260811-perso-rww-fixes-016cdd229647
> 
> Best regards,



From miquel.raynal at bootlin.com  Wed Aug 12 03:02:17 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 12 Aug 2026 12:02:17 +0200
Subject: [PATCH] mtd: spi-nor: Unprepare legacy controllers upon RWW
 wait interruption
In-Reply-To: <e0d2710a-2a67-40e0-aacb-5d18742cea76@linaro.org> (Tudor
	Ambarus's message of "Wed, 12 Aug 2026 12:52:48 +0300")
References: <20260811-perso-rww-fixes-v1-1-5866a70e5d1c@bootlin.com>
	<e0d2710a-2a67-40e0-aacb-5d18742cea76@linaro.org>
Message-ID: <87o6f7mzbq.fsf@bootlin.com>

On 12/08/2026 at 12:52:48 +03, Tudor Ambarus <tudor.ambarus at linaro.org> wrote:

> On 8/11/26 6:56 PM, Miquel Raynal wrote:
>> All the parallel locking helpers (spi_nor_prep_and_lock(),
>> spi_nor_prep_and_lock_pe() and spi_nor_prep_and_lock_rd()) call
>> spi_nor_prep() then wait with a killable sleep. If the sleep gets
>> killed, we leave the controller in its prepared state.
>> 
>> Call ->unprepare() in these cases.
>
>
> We don't even care about SPI NOR controllers. I stopped adding/accepting
> new support for them few years ago, so that we encourage people to update
> the drivers to use SPI MEM.

Yeah, totally agreed.

> The alternative is to drop prep unprep entirely.

That's indeed what we aim for, but in order to "fix" that report, the
current solution is to call ->unprepare(). Hopefully we can remove them
at some point.

>
> Anyway, all good, regardless the path chosen:
>
> Acked-by: Tudor Ambarus <tudor.ambarus at linaro.org>

Thanks,
Miqu?l


From miquel.raynal at bootlin.com  Wed Aug 12 03:05:39 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 12 Aug 2026 12:05:39 +0200
Subject: [PATCH] mtd: spi-nor: Unprepare legacy controllers upon RWW
 wait interruption
In-Reply-To: <DKMVA2HIPB2Z.J0W6OM4H3ARL@kernel.org> (Michael Walle's message
	of "Wed, 12 Aug 2026 11:44:41 +0200")
References: <20260811-perso-rww-fixes-v1-1-5866a70e5d1c@bootlin.com>
	<DKMVA2HIPB2Z.J0W6OM4H3ARL@kernel.org>
Message-ID: <87ik5fmz64.fsf@bootlin.com>

Hello,

> On Tue Aug 11, 2026 at 5:56 PM CEST, Miquel Raynal wrote:
>> All the parallel locking helpers (spi_nor_prep_and_lock(),
>> spi_nor_prep_and_lock_pe() and spi_nor_prep_and_lock_rd()) call
>> spi_nor_prep() then wait with a killable sleep. If the sleep gets
>> killed, we leave the controller in its prepared state.
>>
>
> Why does this only affect legacy controllers?

Aren't ->prepare()/->unprepare() callbacks only implemented by legacy
spi-nor controllers?

Thanks,
Miqu?l


From tudor.ambarus at linaro.org  Wed Aug 12 03:19:25 2026
From: tudor.ambarus at linaro.org (Tudor Ambarus)
Date: Wed, 12 Aug 2026 13:19:25 +0300
Subject: [PATCH v3] mtd: spi-nor: use guard() in
 spi_nor_rww_start_exclusive
In-Reply-To: <878q6coefo.fsf@bootlin.com>
References: <20260811041349.333176-1-runyu.xiao@seu.edu.cn>
 <20260811083111.403453-1-runyu.xiao@seu.edu.cn>
 <0ff8afdc-7a11-4a6c-9f62-6f8ba712c4ac@linaro.org>
 <56991b48-cbe9-45bf-a24c-d7aa771c55d2@linaro.org>
 <87jypwoha6.fsf@bootlin.com> <878q6coefo.fsf@bootlin.com>
Message-ID: <0a15f6e8-f1ca-4945-9bd5-2b64a2802a58@linaro.org>



On 8/11/26 6:38 PM, Miquel Raynal wrote:
>>> Have you seen sashiko's review at 
>>> https://sashiko.dev/#/patchset/20260811083111.403453-1-runyu.xiao%40seu.edu.cn?
>>>
>>> Both concerns look valid to me. Do you think you can allocate time to
>>> fix them?
>> Sashiko is becoming a nightmare. I'll add this to my todo list, but if
>> someone in lack of technical challenge wants to propose fixes, be my
>> guest.
> Actually, the might_sleep() warning seems to be an hallucination. It
> mixes different concepts in its explanation (might_sleep() is not a
> problem here, we are in process context) and I believe it got mislead by
> the current state of the code which lacks a mutex_unlock().

I haven't tested but sashiko seems sane on this.

wait_event_killable()
  __wait_event_killable()
    ___wait_event(wq, condition, TASK_KILLABLE, 0, 0, schedule())  <- TASK_KILLABLE!

Here there's a for loop where it calls prepare_to_wait_event() which calls
set_current_state(TASK_KILLABLE), setting current->__state = TASK_KILLABLE and
storing the callsite in current->task_state_change.

After prepare_to_wait_event(), ___wait_event() evaluates condition
spi_nor_rww_start_exclusive(nor) which calls mutex_lock().
mutex_lock() calls might_sleep(). You hit the WARN_ONCE in __might_sleep() because
state == TASK_KILLABLE. You should catch this if you enable CONFIG_DEBUG_ATOMIC_SLEEP.

Cheers,
ta


From mwalle at kernel.org  Wed Aug 12 03:33:39 2026
From: mwalle at kernel.org (Michael Walle)
Date: Wed, 12 Aug 2026 12:33:39 +0200
Subject: [PATCH] mtd: spi-nor: Unprepare legacy controllers upon RWW
 wait interruption
In-Reply-To: <87ik5fmz64.fsf@bootlin.com>
References: <20260811-perso-rww-fixes-v1-1-5866a70e5d1c@bootlin.com>
 <DKMVA2HIPB2Z.J0W6OM4H3ARL@kernel.org> <87ik5fmz64.fsf@bootlin.com>
Message-ID: <DKMWBK3M5W6V.1V1JTUV6Q8IDT@kernel.org>

On Wed Aug 12, 2026 at 12:05 PM CEST, Miquel Raynal wrote:
> Hello,
>
>> On Tue Aug 11, 2026 at 5:56 PM CEST, Miquel Raynal wrote:
>>> All the parallel locking helpers (spi_nor_prep_and_lock(),
>>> spi_nor_prep_and_lock_pe() and spi_nor_prep_and_lock_rd()) call
>>> spi_nor_prep() then wait with a killable sleep. If the sleep gets
>>> killed, we leave the controller in its prepared state.
>>>
>>
>> Why does this only affect legacy controllers?
>
> Aren't ->prepare()/->unprepare() callbacks only implemented by legacy
> spi-nor controllers?

ah ok :)

-michael


From phucduc.bui at gmail.com  Wed Aug 12 03:37:28 2026
From: phucduc.bui at gmail.com (phucduc.bui at gmail.com)
Date: Wed, 12 Aug 2026 17:37:28 +0700
Subject: [PATCH] mtd: rawnand: brcmnand: Propagate errors from optional IRQ lookup
Message-ID: <20260812103728.39291-1-phucduc.bui@gmail.com>

From: bui duc phuc <phucduc.bui at gmail.com>

platform_get_irq_optional() returns a positive IRQ number on success or
a negative error code on failure. For an optional IRQ, -ENXIO indicates
that no optional IRQ is available, in which case the driver falls back
to using the ctlrdy IRQ. Other errors should be propagated so that the
caller can handle them appropriately.

Treat -ENXIO as the only case where the driver falls back to the ctlrdy
IRQ, and propagate all other errors returned by
platform_get_irq_optional().

Signed-off-by: bui duc phuc <phucduc.bui at gmail.com>
---
 drivers/mtd/nand/raw/brcmnand/brcmnand.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.c b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
index 5b9dadd5405e..4a1f5e736a1c 100644
--- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c
+++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
@@ -3272,6 +3272,8 @@ static int brcmnand_edu_setup(struct platform_device *pdev)
 
 		ctrl->edu_irq = platform_get_irq_optional(pdev, 1);
 		if (ctrl->edu_irq < 0) {
+			if (ctrl->edu_irq != -ENXIO)
+				return ctrl->edu_irq;
 			dev_warn(dev,
 				 "FLASH EDU enabled, using ctlrdy irq\n");
 		} else {
-- 
2.43.0



From manikandan.m at microchip.com  Wed Aug 12 03:49:48 2026
From: manikandan.m at microchip.com (Manikandan Muralidharan)
Date: Wed, 12 Aug 2026 16:19:48 +0530
Subject: [PATCH v7 0/7] Read MAC address from SST vendor specific SFDP region
Message-ID: <20260812104955.485667-1-manikandan.m@microchip.com>

Some Microchip/SST QSPI flashes (e.g. the SST26VF064BEUI) are factory
programmed with globally unique, write-protected EUI-48 and EUI-64
identifiers stored in a vendor-specific SFDP parameter table. On boards
that have no on-board EEPROM (sama5d27_wlsom1, sama5d29 curiosity,
sam9x75 curiosity) this is a reliable source for an Ethernet MAC address,
instead of relying on a U-Boot-provided or random address.

The series exposes the SFDP as a generic NVMEM framework with no vendor
code in the SPI NOR core:
 - The SPI NOR core exposes the entire SFDP as a generic read-only NVMEM
   device, rooted at a new "sfdp" child node of the flash.
 - A new NVMEM layout driver (drivers/nvmem/layouts/) locates the
   Microchip vendor parameter table at runtime and presents the EUI-48 as
   a "mac-address" cell.
 - Arbitrary parameters can be read with a standard fixed-layout
   (known offset) or with an nvmem-layout parser (location discovered at runtime).

Changes in v7:
 - 1/7 - Add blank lines in the "sfdp" schema per review;
 		 Add Reviewed-by: Rob Herring.
 - 2/7 - Add Reviewed-by: Rob Herring.
 - 3/7 - Drop the spi_nor_sfdp_nvmem_put_np() helper and
 		 call of_node_put() right after devm_nvmem_register().
 - 6/7 - Note in the commit message that the sama5d27_wlsom1 SoM ships the
         factory-programmed SST26VF064BEUI, so the EUI-48 "mac-address"
         cell is always present.

Changes in v6:
 - 3/7 - Move the SFDP NVMEM registration from sfdp.c into core.c, since
         sfdp.c is only for parsing the tables;use of_get_compatible_child()
		 instead of the open-coded child-node lookup.
 - 2/7, 6/7, 7/7 - Add Reviewed-by: Linus Walleij.

Changes in v5:
 - 1/7 and 5/7 - Add R-b tags
 - 3/7- Rework the comments, function name and commit message.Check all the nodes
        (for_each_available_child) for the right compatible "jedec,sfdp" instead
		of node name.

Changes in v4:
 - Rework per v3 review: remove the vendor-specific SFDP handling from the
   SPI NOR core; expose the whole SFDP as a generic read-only NVMEM device
   and move the EUI extraction into an nvmem-layout driver.
 - Introduce a new nvmem-layout driver to discover the vendor-table location
   at runtime; no offset hardcoded in the device tree.
 - Describe the SFDP via a dedicated "sfdp" subnode (compatible
   "jedec,sfdp"), which also resolves the v3 dtbs_check "Unevaluated
   properties ('nvmem-layout')" warning.
 - Reverse the stored EUI bytes into canonical MAC order.
 - Enable the layout in sama5_defconfig.

 Changes in v3:
 - 2/3 - add support to update the QSPI partition into 'fixed-partition'
   binding in sama5d27_wlsom1
 - 3/3 - add nvmem-layout in qspi node for EUI48 MAC Address and nvmem cell
   properties for macb node in sama5d27_wlsom1

Changes in v2:
 - 1/3 - parse the SST vendor table, read and store the addresses
  into a resource - managed space. Register the addresses
  into NVMEM framework
 - 2/3 - add support to update the QSPI partition into 'fixed-partition'
  binding

v6: https://lore.kernel.org/linux-devicetree/20260729043026.1811147-1-manikandan.m at microchip.com/

Manikandan Muralidharan (7):
  dt-bindings: mtd: jedec,spi-nor: allow the SFDP to be exposed via
    NVMEM
  dt-bindings: nvmem: layouts: add Microchip/SST SFDP EUI layout
  mtd: spi-nor: sfdp: expose the SFDP as a read-only NVMEM device
  nvmem: layouts: add Microchip/SST SFDP EUI layout driver
  ARM: dts: microchip: sama5d27_wlsom1: use fixed-partitions for QSPI
    flash
  ARM: dts: microchip: sama5d27_wlsom1: read MAC address from QSPI SFDP
  ARM: configs: sama5: enable Microchip/SST SFDP EUI NVMEM layout

 .../bindings/mtd/jedec,spi-nor.yaml           |  20 ++
 .../layouts/microchip,sst26vf-sfdp-eui.yaml   |  60 ++++++
 .../bindings/nvmem/layouts/nvmem-layout.yaml  |   1 +
 MAINTAINERS                                   |   6 +
 .../dts/microchip/at91-sama5d27_wlsom1.dtsi   |  61 +++---
 .../dts/microchip/at91-sama5d27_wlsom1_ek.dts |   2 +
 arch/arm/configs/sama5_defconfig              |   1 +
 drivers/mtd/spi-nor/core.c                    |  78 ++++++++
 drivers/nvmem/layouts/Kconfig                 |  10 +
 drivers/nvmem/layouts/Makefile                |   1 +
 drivers/nvmem/layouts/sst26vf-sfdp-eui.c      | 182 ++++++++++++++++++
 11 files changed, 400 insertions(+), 22 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/nvmem/layouts/microchip,sst26vf-sfdp-eui.yaml
 create mode 100644 drivers/nvmem/layouts/sst26vf-sfdp-eui.c


base-commit: f5bbbfec59b4e2fb7520a91de3df8a6174325d6a
-- 
2.43.0



From manikandan.m at microchip.com  Wed Aug 12 03:49:49 2026
From: manikandan.m at microchip.com (Manikandan Muralidharan)
Date: Wed, 12 Aug 2026 16:19:49 +0530
Subject: [PATCH v7 1/7] dt-bindings: mtd: jedec,spi-nor: allow the SFDP to be exposed via NVMEM
In-Reply-To: <20260812104955.485667-1-manikandan.m@microchip.com>
References: <20260812104955.485667-1-manikandan.m@microchip.com>
Message-ID: <20260812104955.485667-2-manikandan.m@microchip.com>

Add an optional "sfdp" child node (compatible "jedec,sfdp") that
describes the SFDP as a read-only NVMEM provider via nvmem.yaml, so its
contents (e.g. a vendor EUI-48/EUI-64) can be read through NVMEM cells.

Reviewed-by: Linus Walleij <linusw at kernel.org>
Reviewed-by: Rob Herring (Arm) <robh at kernel.org>
Signed-off-by: Manikandan Muralidharan <manikandan.m at microchip.com>
---
 .../bindings/mtd/jedec,spi-nor.yaml           | 20 +++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml b/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml
index 587af4968255..d7e659912b4a 100644
--- a/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml
+++ b/Documentation/devicetree/bindings/mtd/jedec,spi-nor.yaml
@@ -103,6 +103,22 @@ properties:
   spi-cpol: true
   spi-cpha: true
 
+  sfdp:
+    $ref: /schemas/nvmem/nvmem.yaml#
+    unevaluatedProperties: false
+    description:
+      The Serial Flash Discoverable Parameters (SFDP) tables exposed as a
+      read-only NVMEM device. This allows standard or vendor-specific SFDP
+      data (for example a factory-programmed EUI-48/EUI-64 identifier) to be
+      consumed through NVMEM cells.
+
+    properties:
+      compatible:
+        const: jedec,sfdp
+
+    required:
+      - compatible
+
 dependencies:
   spi-cpol: [ spi-cpha ]
   spi-cpha: [ spi-cpol ]
@@ -122,6 +138,10 @@ examples:
             spi-max-frequency = <40000000>;
             m25p,fast-read;
             reset-gpios = <&gpio 12 GPIO_ACTIVE_LOW>;
+
+            sfdp {
+                compatible = "jedec,sfdp";
+            };
         };
     };
 ...
-- 
2.43.0



From manikandan.m at microchip.com  Wed Aug 12 03:49:50 2026
From: manikandan.m at microchip.com (Manikandan Muralidharan)
Date: Wed, 12 Aug 2026 16:19:50 +0530
Subject: [PATCH v7 2/7] dt-bindings: nvmem: layouts: add Microchip/SST SFDP EUI layout
In-Reply-To: <20260812104955.485667-1-manikandan.m@microchip.com>
References: <20260812104955.485667-1-manikandan.m@microchip.com>
Message-ID: <20260812104955.485667-3-manikandan.m@microchip.com>

Add a binding for the NVMEM layout that exposes the factory-programmed
EUI-48 identifier from the Microchip/SST vendor-specific SFDP parameter
table (e.g. SST26VF064BEUI) as a "mac-address" NVMEM cell, and reference
it from nvmem-layout.yaml.

Reviewed-by: Linus Walleij <linusw at kernel.org>
Reviewed-by: Rob Herring (Arm) <robh at kernel.org>
Signed-off-by: Manikandan Muralidharan <manikandan.m at microchip.com>
---
 .../layouts/microchip,sst26vf-sfdp-eui.yaml   | 60 +++++++++++++++++++
 .../bindings/nvmem/layouts/nvmem-layout.yaml  |  1 +
 2 files changed, 61 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/nvmem/layouts/microchip,sst26vf-sfdp-eui.yaml

diff --git a/Documentation/devicetree/bindings/nvmem/layouts/microchip,sst26vf-sfdp-eui.yaml b/Documentation/devicetree/bindings/nvmem/layouts/microchip,sst26vf-sfdp-eui.yaml
new file mode 100644
index 000000000000..37357efb7840
--- /dev/null
+++ b/Documentation/devicetree/bindings/nvmem/layouts/microchip,sst26vf-sfdp-eui.yaml
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/nvmem/layouts/microchip,sst26vf-sfdp-eui.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NVMEM layout of the Microchip/SST SFDP EUI-48 identifier
+
+maintainers:
+  - Manikandan Muralidharan <manikandan.m at microchip.com>
+
+description:
+  Some Microchip/SST serial flashes (for example the SST26VF064BEUI) are
+  factory programmed with a globally unique EUI-48 identifier stored in a
+  vendor-specific SFDP parameter table and permanently write-protected. This
+  layout locates that table and exposes the EUI-48 as an NVMEM cell so that,
+  for example, a network driver can use it as a MAC address. The location of
+  the data is discovered at runtime from the SFDP; no offset is encoded in the
+  device tree.
+
+select: false
+
+properties:
+  compatible:
+    const: microchip,sst26vf-sfdp-eui
+
+  mac-address:
+    type: object
+    description:
+      The factory-programmed EUI-48 identifier, usable as a MAC address.
+    additionalProperties: false
+
+required:
+  - compatible
+
+additionalProperties: false
+
+examples:
+  - |
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        flash at 0 {
+            compatible = "jedec,spi-nor";
+            reg = <0>;
+
+            sfdp {
+                compatible = "jedec,sfdp";
+
+                nvmem-layout {
+                    compatible = "microchip,sst26vf-sfdp-eui";
+
+                    mac-address {
+                    };
+                };
+            };
+        };
+    };
+...
diff --git a/Documentation/devicetree/bindings/nvmem/layouts/nvmem-layout.yaml b/Documentation/devicetree/bindings/nvmem/layouts/nvmem-layout.yaml
index 382507060651..e63b93083821 100644
--- a/Documentation/devicetree/bindings/nvmem/layouts/nvmem-layout.yaml
+++ b/Documentation/devicetree/bindings/nvmem/layouts/nvmem-layout.yaml
@@ -20,6 +20,7 @@ description: |
 oneOf:
   - $ref: fixed-layout.yaml
   - $ref: kontron,sl28-vpd.yaml
+  - $ref: microchip,sst26vf-sfdp-eui.yaml
   - $ref: onie,tlv-layout.yaml
   - $ref: u-boot,env.yaml
 
-- 
2.43.0



From manikandan.m at microchip.com  Wed Aug 12 03:49:51 2026
From: manikandan.m at microchip.com (Manikandan Muralidharan)
Date: Wed, 12 Aug 2026 16:19:51 +0530
Subject: [PATCH v7 3/7] mtd: spi-nor: sfdp: expose the SFDP as a read-only NVMEM device
In-Reply-To: <20260812104955.485667-1-manikandan.m@microchip.com>
References: <20260812104955.485667-1-manikandan.m@microchip.com>
Message-ID: <20260812104955.485667-4-manikandan.m@microchip.com>

The SPI NOR core already reads the SFDP tables during enumeration and
caches them in nor->sfdp->dwords (see spi_nor_parse_sfdp()). Re-expose
that cached data as a read-only NVMEM device, in on-flash byte order,
rooted at the flash's SFDP child node (compatible "jedec,sfdp").

This lets NVMEM cells reference any SFDP data: a fixed-layout for
parameters at a known offset, or an nvmem-layout parser for vendor data
whose location must be discovered at runtime. The device is only registered
when an "sfdp" node is present in the device tree.

Signed-off-by: Manikandan Muralidharan <manikandan.m at microchip.com>
---
 drivers/mtd/spi-nor/core.c | 78 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 78 insertions(+)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ccf4396cdcd0..0425af6e898f 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -17,6 +17,7 @@
 #include <linux/mtd/mtd.h>
 #include <linux/mtd/spi-nor.h>
 #include <linux/mutex.h>
+#include <linux/nvmem-provider.h>
 #include <linux/of.h>
 #include <linux/regulator/consumer.h>
 #include <linux/sched/task_stack.h>
@@ -3001,6 +3002,75 @@ static void spi_nor_init_fixup_flags(struct spi_nor *nor)
 		nor->flags |= SNOR_F_IO_MODE_EN_VOLATILE;
 }
 
+static int spi_nor_sfdp_reg_read(void *priv, unsigned int offset,
+				 void *val, size_t bytes)
+{
+	struct spi_nor *nor = priv;
+	struct sfdp *sfdp = nor->sfdp;
+	size_t sfdp_size = sfdp->num_dwords * sizeof(*sfdp->dwords);
+
+	if (offset >= sfdp_size || bytes > sfdp_size - offset)
+		return -EINVAL;
+
+	/* The cached SFDP is kept in on-flash (little-endian) byte order. */
+	memcpy(val, (u8 *)sfdp->dwords + offset, bytes);
+
+	return 0;
+}
+
+/**
+ * spi_nor_register_sfdp_nvmem() - expose the SFDP as a read-only NVMEM device
+ * @nor:	pointer to a 'struct spi_nor'
+ *
+ * Expose the whole SFDP, in on-flash byte order, as a read-only NVMEM device
+ * rooted at the flash's SFDP child node (compatible "jedec,sfdp"). This lets
+ * generic (fixed-layout) or vendor (nvmem-layout) cells reference any SFDP
+ * data. The device is only registered when a child node with the "jedec,sfdp"
+ * compatible is described in the device tree.
+ *
+ * Return: 0 on success or if there is nothing to do, -errno otherwise.
+ */
+static int spi_nor_register_sfdp_nvmem(struct spi_nor *nor)
+{
+	struct device *dev = nor->dev;
+	struct nvmem_config config = { };
+	struct nvmem_device *nvmem;
+	struct device_node *np;
+
+	if (!nor->sfdp)
+		return 0;
+
+	np = of_get_compatible_child(dev_of_node(dev), "jedec,sfdp");
+	if (!np)
+		return 0;
+
+	config.dev = dev;
+	config.of_node = np;
+	config.name = "sfdp";
+	config.id = NVMEM_DEVID_AUTO;
+	config.owner = THIS_MODULE;
+	config.read_only = true;
+	config.word_size = 1;
+	config.stride = 1;
+	config.size = (int)(nor->sfdp->num_dwords * sizeof(*nor->sfdp->dwords));
+	config.reg_read = spi_nor_sfdp_reg_read;
+	config.priv = nor;
+
+	nvmem = devm_nvmem_register(dev, &config);
+	of_node_put(np);
+	if (IS_ERR(nvmem)) {
+		/* NVMEM support is optional. */
+		if (PTR_ERR(nvmem) == -EOPNOTSUPP)
+			return 0;
+		return dev_err_probe(dev, PTR_ERR(nvmem),
+				     "failed to register SFDP NVMEM device\n");
+	}
+
+	dev_dbg(dev, "exposed %d-byte SFDP as an NVMEM device\n", config.size);
+
+	return 0;
+}
+
 /**
  * spi_nor_late_init_params() - Late initialization of default flash parameters.
  * @nor:	pointer to a 'struct spi_nor'
@@ -3204,6 +3274,14 @@ static int spi_nor_init_params(struct spi_nor *nor)
 		spi_nor_init_params_deprecated(nor);
 	}
 
+	/*
+	 * Expose the SFDP table as an NVMEM device only when
+	 * the flash actually provides one
+	 */
+	ret = spi_nor_register_sfdp_nvmem(nor);
+	if (ret)
+		return ret;
+
 	ret = spi_nor_late_init_params(nor);
 	if (ret)
 		return ret;
-- 
2.43.0



From manikandan.m at microchip.com  Wed Aug 12 03:49:52 2026
From: manikandan.m at microchip.com (Manikandan Muralidharan)
Date: Wed, 12 Aug 2026 16:19:52 +0530
Subject: [PATCH v7 4/7] nvmem: layouts: add Microchip/SST SFDP EUI layout driver
In-Reply-To: <20260812104955.485667-1-manikandan.m@microchip.com>
References: <20260812104955.485667-1-manikandan.m@microchip.com>
Message-ID: <20260812104955.485667-5-manikandan.m@microchip.com>

Add an NVMEM layout that exposes the factory-programmed EUI-48 identifier
from the Microchip/SST vendor SFDP parameter table (e.g. SST26VF064BEUI)
as a "mac-address" cell, for use as a network MAC address. The vendor
table is located at runtime via the SFDP NVMEM device (no offset in DT),
and a read_post_process callback reverses the LSB-first bytes into
canonical MAC order. Binds to an "nvmem-layout" node with compatible
"microchip,sst26vf-sfdp-eui".

Signed-off-by: Manikandan Muralidharan <manikandan.m at microchip.com>
---
 MAINTAINERS                              |   6 +
 drivers/nvmem/layouts/Kconfig            |  10 ++
 drivers/nvmem/layouts/Makefile           |   1 +
 drivers/nvmem/layouts/sst26vf-sfdp-eui.c | 182 +++++++++++++++++++++++
 4 files changed, 199 insertions(+)
 create mode 100644 drivers/nvmem/layouts/sst26vf-sfdp-eui.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 8014b9f8253e..c0eb6c16e2c3 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -17813,6 +17813,12 @@ F:	Documentation/devicetree/bindings/sound/atmel,at91-ssc.yaml
 F:	drivers/misc/atmel-ssc.c
 F:	include/linux/atmel-ssc.h
 
+MICROCHIP SST SFDP EUI NVMEM LAYOUT DRIVER
+M:	Manikandan Muralidharan <manikandan.m at microchip.com>
+S:	Maintained
+F:	Documentation/devicetree/bindings/nvmem/layouts/microchip,sst26vf-sfdp-eui.yaml
+F:	drivers/nvmem/layouts/sst26vf-sfdp-eui.c
+
 Microchip Timer Counter Block (TCB) Capture Driver
 M:	Kamel Bouhara <kamel.bouhara at bootlin.com>
 L:	linux-arm-kernel at lists.infradead.org (moderated for non-subscribers)
diff --git a/drivers/nvmem/layouts/Kconfig b/drivers/nvmem/layouts/Kconfig
index 5e586dfebe47..855c7db530da 100644
--- a/drivers/nvmem/layouts/Kconfig
+++ b/drivers/nvmem/layouts/Kconfig
@@ -26,6 +26,16 @@ config NVMEM_LAYOUT_ONIE_TLV
 
 	  If unsure, say N.
 
+config NVMEM_LAYOUT_SST26VF_SFDP_EUI
+	tristate "Microchip/SST SFDP EUI-48 layout support"
+	help
+	  Say Y here if you want to expose the factory-programmed EUI-48
+	  identifier stored in the Microchip/SST vendor-specific SFDP parameter
+	  table (e.g. SST26VF064BEUI) as NVMEM cells, so that network drivers
+	  can use them as a MAC address.
+
+	  If unsure, say N.
+
 config NVMEM_LAYOUT_U_BOOT_ENV
 	tristate "U-Boot environment variables layout"
 	select CRC32
diff --git a/drivers/nvmem/layouts/Makefile b/drivers/nvmem/layouts/Makefile
index dd6c6c70b1a9..ab4e996075b2 100644
--- a/drivers/nvmem/layouts/Makefile
+++ b/drivers/nvmem/layouts/Makefile
@@ -6,4 +6,5 @@
 obj-$(CONFIG_NVMEM_LAYOUTS) += fixed-layout.o
 obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o
 obj-$(CONFIG_NVMEM_LAYOUT_ONIE_TLV) += onie-tlv.o
+obj-$(CONFIG_NVMEM_LAYOUT_SST26VF_SFDP_EUI) += sst26vf-sfdp-eui.o
 obj-$(CONFIG_NVMEM_LAYOUT_U_BOOT_ENV) += u-boot-env.o
diff --git a/drivers/nvmem/layouts/sst26vf-sfdp-eui.c b/drivers/nvmem/layouts/sst26vf-sfdp-eui.c
new file mode 100644
index 000000000000..641318d6f0af
--- /dev/null
+++ b/drivers/nvmem/layouts/sst26vf-sfdp-eui.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * NVMEM layout for the factory-programmed EUI-48 identifier stored in the
+ * Microchip/SST vendor-specific SFDP parameter table (e.g. SST26VF064BEUI).
+ *
+ * The whole SFDP is exposed as a read-only NVMEM device by the SPI NOR core.
+ * This layout locates the Microchip vendor parameter table at runtime and
+ * registers the EUI-48 address as an NVMEM cell, so that a network driver can
+ * consume it as a MAC address. No offset is hardcoded in the device tree.
+ *
+ * Copyright (C) 2026 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Manikandan Muralidharan <manikandan.m at microchip.com>
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/minmax.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/nvmem-provider.h>
+#include <linux/of.h>
+#include <linux/unaligned.h>
+#include <uapi/linux/if_ether.h>
+
+/* SFDP header and parameter header, as laid out on the flash. */
+struct sfdp_header {
+	u8 signature[4];
+	u8 minor;
+	u8 major;
+	u8 nph;
+	u8 unused;
+};
+
+struct sfdp_parameter_header {
+	u8 id_lsb;
+	u8 minor;
+	u8 major;
+	u8 length;
+	u8 parameter_table_pointer[3];
+	u8 id_msb;
+};
+
+#define SFDP_SIGNATURE			0x50444653U
+
+#define SFDP_PARAM_HEADER_ID(h)		(((h)->id_msb << 8) | (h)->id_lsb)
+#define SFDP_PARAM_HEADER_PTP(h)	get_unaligned_le24((h)->parameter_table_pointer)
+
+/* Microchip (vendor) parameter table identifier: id_msb << 8 | id_lsb. */
+#define SFDP_MCHP_VENDOR_ID		0x01bf
+
+#define SFDP_MCHP_EUI48_MARKER_OFFSET	0x60
+#define SFDP_MCHP_EUI48_MARKER		0x30
+#define SFDP_MCHP_EUI48_OFFSET		0x61
+
+static int sfdp_eui_read_post_process(void *priv, const char *id, int index,
+				      unsigned int offset, void *buf,
+				      size_t bytes)
+{
+	u8 *data = buf;
+	int i;
+
+	/* SFDP stores the address least-significant octet first; reverse it. */
+	for (i = 0; i < bytes / 2; i++)
+		swap(data[i], data[bytes - 1 - i]);
+
+	if (bytes == ETH_ALEN && !is_valid_ether_addr(buf))
+		return -EINVAL;
+
+	return 0;
+}
+
+static int sfdp_eui_find_vendor_table(struct nvmem_device *nvmem, u32 *ptp)
+{
+	struct sfdp_parameter_header ph;
+	struct sfdp_header hdr;
+	int nph, i, ret;
+
+	ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr);
+	if (ret < 0)
+		return ret;
+
+	if (get_unaligned_le32(hdr.signature) != SFDP_SIGNATURE)
+		return -EINVAL;
+
+	/* The number of parameter headers (NPH) field is zero-based. */
+	nph = hdr.nph;
+
+	for (i = 0; i <= nph; i++) {
+		ret = nvmem_device_read(nvmem, sizeof(hdr) + i * sizeof(ph),
+					sizeof(ph), &ph);
+		if (ret < 0)
+			return ret;
+
+		if (SFDP_PARAM_HEADER_ID(&ph) != SFDP_MCHP_VENDOR_ID)
+			continue;
+
+		*ptp = SFDP_PARAM_HEADER_PTP(&ph);
+		return 0;
+	}
+
+	return -ENOENT;
+}
+
+static int sfdp_eui_add_cells(struct nvmem_layout *layout)
+{
+	struct nvmem_device *nvmem = layout->nvmem;
+	struct device *dev = &layout->dev;
+	struct nvmem_cell_info info = { };
+	struct device_node *layout_np;
+	u32 base = 0;
+	u8 marker;
+	int ret;
+
+	ret = sfdp_eui_find_vendor_table(nvmem, &base);
+	if (ret == -ENOENT) {
+		dev_dbg(dev, "no Microchip SFDP vendor table found\n");
+		return 0;
+	}
+	if (ret)
+		return ret;
+
+	/* The EUI-48 is present only if its marker byte is programmed. */
+	ret = nvmem_device_read(nvmem, base + SFDP_MCHP_EUI48_MARKER_OFFSET,
+				1, &marker);
+	if (ret < 0)
+		return ret;
+	if (marker != SFDP_MCHP_EUI48_MARKER) {
+		dev_dbg(dev, "EUI-48 not programmed (marker 0x%02x)\n", marker);
+		return 0;
+	}
+
+	layout_np = of_nvmem_layout_get_container(nvmem);
+	if (!layout_np)
+		return -ENOENT;
+
+	info.name = "mac-address";
+	info.offset = base + SFDP_MCHP_EUI48_OFFSET;
+	info.bytes = ETH_ALEN;
+	info.np = of_get_child_by_name(layout_np, "mac-address");
+	info.read_post_process = sfdp_eui_read_post_process;
+
+	ret = nvmem_add_one_cell(nvmem, &info);
+	if (ret)
+		of_node_put(info.np);
+	else
+		dev_dbg(dev, "exposed EUI-48 at SFDP offset 0x%x\n", info.offset);
+
+	of_node_put(layout_np);
+
+	return ret;
+}
+
+static int sfdp_eui_probe(struct nvmem_layout *layout)
+{
+	layout->add_cells = sfdp_eui_add_cells;
+
+	return nvmem_layout_register(layout);
+}
+
+static void sfdp_eui_remove(struct nvmem_layout *layout)
+{
+	nvmem_layout_unregister(layout);
+}
+
+static const struct of_device_id sfdp_eui_of_match_table[] = {
+	{ .compatible = "microchip,sst26vf-sfdp-eui" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, sfdp_eui_of_match_table);
+
+static struct nvmem_layout_driver sfdp_eui_layout = {
+	.driver = {
+		.name = "microchip-sst26vf-sfdp-eui-layout",
+		.of_match_table = sfdp_eui_of_match_table,
+	},
+	.probe = sfdp_eui_probe,
+	.remove = sfdp_eui_remove,
+};
+module_nvmem_layout_driver(sfdp_eui_layout);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Manikandan Muralidharan <manikandan.m at microchip.com>");
+MODULE_DESCRIPTION("NVMEM layout for the EUI-48 in the Microchip/SST SFDP vendor table");
-- 
2.43.0



From manikandan.m at microchip.com  Wed Aug 12 03:49:53 2026
From: manikandan.m at microchip.com (Manikandan Muralidharan)
Date: Wed, 12 Aug 2026 16:19:53 +0530
Subject: [PATCH v7 5/7] ARM: dts: microchip: sama5d27_wlsom1: use fixed-partitions for QSPI flash
In-Reply-To: <20260812104955.485667-1-manikandan.m@microchip.com>
References: <20260812104955.485667-1-manikandan.m@microchip.com>
Message-ID: <20260812104955.485667-6-manikandan.m@microchip.com>

Move the QSPI flash partitions under a "partitions" node with the
"fixed-partitions" compatible, as required by the current MTD partition
binding, instead of declaring them as direct children of the flash node.
No functional change.

Reviewed-by: Linus Walleij <linusw at kernel.org>
Signed-off-by: Manikandan Muralidharan <manikandan.m at microchip.com>
---
 .../dts/microchip/at91-sama5d27_wlsom1.dtsi   | 52 +++++++++++--------
 1 file changed, 29 insertions(+), 23 deletions(-)

diff --git a/arch/arm/boot/dts/microchip/at91-sama5d27_wlsom1.dtsi b/arch/arm/boot/dts/microchip/at91-sama5d27_wlsom1.dtsi
index 0417f53b3e96..062aa02a98ed 100644
--- a/arch/arm/boot/dts/microchip/at91-sama5d27_wlsom1.dtsi
+++ b/arch/arm/boot/dts/microchip/at91-sama5d27_wlsom1.dtsi
@@ -240,34 +240,40 @@ qspi1_flash: flash at 0 {
 		m25p,fast-read;
 		status = "disabled";
 
-		at91bootstrap at 0 {
-			label = "at91bootstrap";
-			reg = <0x0 0x40000>;
-		};
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			at91bootstrap at 0 {
+				label = "at91bootstrap";
+				reg = <0x0 0x40000>;
+			};
 
-		bootloader at 40000 {
-			label = "bootloader";
-			reg = <0x40000 0xc0000>;
-		};
+			bootloader at 40000 {
+				label = "bootloader";
+				reg = <0x40000 0xc0000>;
+			};
 
-		bootloaderenvred at 100000 {
-			label = "bootloader env redundant";
-			reg = <0x100000 0x40000>;
-		};
+			bootloaderenvred at 100000 {
+				label = "bootloader env redundant";
+				reg = <0x100000 0x40000>;
+			};
 
-		bootloaderenv at 140000 {
-			label = "bootloader env";
-			reg = <0x140000 0x40000>;
-		};
+			bootloaderenv at 140000 {
+				label = "bootloader env";
+				reg = <0x140000 0x40000>;
+			};
 
-		dtb at 180000 {
-			label = "device tree";
-			reg = <0x180000 0x80000>;
-		};
+			dtb at 180000 {
+				label = "device tree";
+				reg = <0x180000 0x80000>;
+			};
 
-		kernel at 200000 {
-			label = "kernel";
-			reg = <0x200000 0x600000>;
+			kernel at 200000 {
+				label = "kernel";
+				reg = <0x200000 0x600000>;
+			};
 		};
 	};
 };
-- 
2.43.0



From manikandan.m at microchip.com  Wed Aug 12 03:49:54 2026
From: manikandan.m at microchip.com (Manikandan Muralidharan)
Date: Wed, 12 Aug 2026 16:19:54 +0530
Subject: [PATCH v7 6/7] ARM: dts: microchip: sama5d27_wlsom1: read MAC address from QSPI SFDP
In-Reply-To: <20260812104955.485667-1-manikandan.m@microchip.com>
References: <20260812104955.485667-1-manikandan.m@microchip.com>
Message-ID: <20260812104955.485667-7-manikandan.m@microchip.com>

Describe the QSPI flash SFDP as an NVMEM provider with the
microchip,sst26vf-sfdp-eui layout, which exposes the factory-programmed
EUI-48 as a "mac-address" cell, and point macb0 at it through
nvmem-cells. This yields a stable MAC address on boards where U-Boot does
not program one, instead of falling back to a random address.

The sama5d27_wlsom1 SoM is fitted with the factory-programmed
SST26VF064BEUI, and the QSPI flash node is only enabled on boards carrying
that part, so the EUI-48 marker and its "mac-address" cell are always
present. Boards without an EUI-programmed part simply do not reference the
cell, so macb0 never defers waiting for it.

Reviewed-by: Linus Walleij <linusw at kernel.org>
Signed-off-by: Manikandan Muralidharan <manikandan.m at microchip.com>
---
 arch/arm/boot/dts/microchip/at91-sama5d27_wlsom1.dtsi | 11 +++++++++++
 .../boot/dts/microchip/at91-sama5d27_wlsom1_ek.dts    |  2 ++
 2 files changed, 13 insertions(+)

diff --git a/arch/arm/boot/dts/microchip/at91-sama5d27_wlsom1.dtsi b/arch/arm/boot/dts/microchip/at91-sama5d27_wlsom1.dtsi
index 062aa02a98ed..6016d7f2a39c 100644
--- a/arch/arm/boot/dts/microchip/at91-sama5d27_wlsom1.dtsi
+++ b/arch/arm/boot/dts/microchip/at91-sama5d27_wlsom1.dtsi
@@ -240,6 +240,17 @@ qspi1_flash: flash at 0 {
 		m25p,fast-read;
 		status = "disabled";
 
+		sfdp {
+			compatible = "jedec,sfdp";
+
+			nvmem-layout {
+				compatible = "microchip,sst26vf-sfdp-eui";
+
+				mac_address_eui48: mac-address {
+				};
+			};
+		};
+
 		partitions {
 			compatible = "fixed-partitions";
 			#address-cells = <1>;
diff --git a/arch/arm/boot/dts/microchip/at91-sama5d27_wlsom1_ek.dts b/arch/arm/boot/dts/microchip/at91-sama5d27_wlsom1_ek.dts
index 35a933eec573..5e87bf04bc47 100644
--- a/arch/arm/boot/dts/microchip/at91-sama5d27_wlsom1_ek.dts
+++ b/arch/arm/boot/dts/microchip/at91-sama5d27_wlsom1_ek.dts
@@ -97,6 +97,8 @@ uart6: serial at 200 {
 
 &macb0 {
 	status = "okay";
+	nvmem-cells = <&mac_address_eui48>;
+	nvmem-cell-names = "mac-address";
 };
 
 &pioA {
-- 
2.43.0



From manikandan.m at microchip.com  Wed Aug 12 03:49:55 2026
From: manikandan.m at microchip.com (Manikandan Muralidharan)
Date: Wed, 12 Aug 2026 16:19:55 +0530
Subject: [PATCH v7 7/7] ARM: configs: sama5: enable Microchip/SST SFDP EUI NVMEM layout
In-Reply-To: <20260812104955.485667-1-manikandan.m@microchip.com>
References: <20260812104955.485667-1-manikandan.m@microchip.com>
Message-ID: <20260812104955.485667-8-manikandan.m@microchip.com>

Enable CONFIG_NVMEM_LAYOUT_SST26VF_SFDP_EUI so the factory EUI-48 stored
in the SST26VF QSPI flash SFDP can be used as a MAC address on boards
such as the sama5d27_wlsom1.

Reviewed-by: Linus Walleij <linusw at kernel.org>
Signed-off-by: Manikandan Muralidharan <manikandan.m at microchip.com>
---
 arch/arm/configs/sama5_defconfig | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm/configs/sama5_defconfig b/arch/arm/configs/sama5_defconfig
index bd7f0b5f7d66..14dda4b0cfd0 100644
--- a/arch/arm/configs/sama5_defconfig
+++ b/arch/arm/configs/sama5_defconfig
@@ -220,6 +220,7 @@ CONFIG_PWM=y
 CONFIG_PWM_ATMEL=y
 CONFIG_PWM_ATMEL_HLCDC_PWM=y
 CONFIG_PWM_ATMEL_TCB=y
+CONFIG_NVMEM_LAYOUT_SST26VF_SFDP_EUI=y
 CONFIG_EXT4_FS=y
 CONFIG_FANOTIFY=y
 CONFIG_AUTOFS_FS=m
-- 
2.43.0



From miquel.raynal at bootlin.com  Wed Aug 12 05:34:04 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Wed, 12 Aug 2026 14:34:04 +0200
Subject: [PATCH v2] mtd: block2mtd: Fix divide error when erase_size is
 zero
In-Reply-To: <48230456575d27aa83ce8640de8fc07cc62e8efb.1786499433.git.xiaopei01@kylinos.cn>
	(Pei Xiao's message of "Wed, 12 Aug 2026 09:52:42 +0800")
References: <48230456575d27aa83ce8640de8fc07cc62e8efb.1786499433.git.xiaopei01@kylinos.cn>
Message-ID: <871pc3msar.fsf@bootlin.com>

On 12/08/2026 at 09:52:42 +08, Pei Xiao <xiaopei01 at kylinos.cn> wrote:

> The erase size is parsed from the "block2mtd" module parameter and can
> be set to zero. add_device() then evaluates
>
>         if (size % erase_size)
>
> with a zero divisor, which triggers a divide error:
>
>         divide error: 0000 [#1] PREEMPT SMP PTI
>         RIP: 0010:add_device drivers/mtd/devices/block2mtd.c:296 [inline]
>         RIP: 0010:block2mtd_setup2+0x592/0xda0 drivers/mtd/devices/block2mtd.c:459
>         Call Trace:
>          block2mtd_setup+0x27/0xe0 drivers/mtd/devices/block2mtd.c:476
>          param_attr_store+0x214/0x310 kernel/params.c:589
>          module_attr_store+0x65/0x90 kernel/params.c:904
>          kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345
>          ...
>
> Reject a zero erase size before performing the modulo operation so the
> existing "erasesize must be a divisor of device size" error path
> reports the invalid argument and frees the device.
>
> While at it, drop the unnecessary (long) cast from the size operand of
> the modulo.
>
> Fixes: ea6d833a3fdd ("mtd: block2mtd: check device size")
> Reported-by: syzbot+b320a4d5f65a61dbbf89 at syzkaller.appspotmail.com
> Closes: https://lore.kernel.org/lkml/6a7b58ba.ac361c09.22ff0a.0045.GAE at google.com/
> Suggested-by: J?rn Engel <joern at barelysecure.org>
> Cc: stable at vger.kernel.org
> Signed-off-by: Pei Xiao <xiaopei01 at kylinos.cn>

Apparently, removing the cast leads to an unsupported operation with a
64-bit operand on 32-bit architectures (See Sashiko report).

I guess it is best to keep that cast in the end.

Sorry for the changes back-and-forth.

Miqu?l


From fonkism at icloud.com  Wed Aug 12 08:18:37 2026
From: fonkism at icloud.com (fonkism at icloud.com)
Date: Wed, 12 Aug 2026 17:18:37 +0200
Subject: [PATCH] mtd: spi-nor: xmc: add locking to XM25QH128A
In-Reply-To: <20260811064512.42520-1-mwalle@kernel.org>
References: <20260811064512.42520-1-mwalle@kernel.org>
Message-ID: <7985B1FF-7BBA-4807-9632-35CA59A9446C@icloud.com>


> On 11 Aug 2026, at 08:44, Michael Walle <mwalle at kernel.org> wrote:
> 
> Add support for locking to the XM25QH128A part.
> 
> Reported-by: Takino <stayer.opt1f at icloud.com>
> Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221825
> Signed-off-by: Michael Walle <mwalle at kernel.org>

Tested-by: Wei, Takino <stayer.opt1f at icloud.com> # on Fedora 44 / 7.1.6-201

Tested to the max of my abilities here; the driver no longer returns ?EOPNOTSUPP?.
It now returns ?unlocked" which is technically correct as the bits are not set.
Intel locks it using a different mechanism and could?ve/should?ve also set the bits.
This likely needs a BIOS update to properly fix it. Which may never happen.

Regards,
Takino.

> ---
> Hi Takino,
> 
> please test it and reply with a "Tested-by" tag. With your full name if
> you like. I didn't get that from the bug report.
> ---
> drivers/mtd/spi-nor/xmc.c | 1 +
> 1 file changed, 1 insertion(+)
> 
> diff --git a/drivers/mtd/spi-nor/xmc.c b/drivers/mtd/spi-nor/xmc.c
> index d5a06054b0dd..d3b52eb93e2c 100644
> --- a/drivers/mtd/spi-nor/xmc.c
> +++ b/drivers/mtd/spi-nor/xmc.c
> @@ -19,6 +19,7 @@ static const struct flash_info xmc_nor_parts[] = {
> .name = "XM25QH128A",
> .size = SZ_16M,
> .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
> + .flags = SPI_NOR_HAS_LOCK | SPI_NOR_4BIT_BP | SPI_NOR_HAS_TB,
> },
> };
> 
> -- 
> 2.47.3
> 



From sashal at kernel.org  Wed Aug 12 09:26:43 2026
From: sashal at kernel.org (Sasha Levin)
Date: Wed, 12 Aug 2026 12:26:43 -0400
Subject: [PATCH 6.12.y 0/3] Backport fixes for SPI NAND on i.MX6UL
In-Reply-To: <20260811161342.533280-1-frieder@fris.de>
References: <20260811161342.533280-1-frieder@fris.de>
Message-ID: <20260812161500.10006-1-sashal@kernel.org>

On Tue, Aug 11, 2026 at 06:13:35PM +0200, Frieder Schrempf wrote:
> This series contains these fixes picked from mainline, applying to stable 6.12
> cleanly.

Queued the series for 6.12, thanks.

-- 
Thanks,
Sasha


From han.xu at oss.nxp.com  Wed Aug 12 09:34:26 2026
From: han.xu at oss.nxp.com (han.xu)
Date: Wed, 12 Aug 2026 11:34:26 -0500
Subject: [PATCH] mtd: spinand: Enable QE on all dies
In-Reply-To: <87qzk6s8yr.fsf@bootlin.com>
References: <20260807-spinand_qe-v1-1-c102c79834a3@nxp.com>
 <87qzk6s8yr.fsf@bootlin.com>
Message-ID: <anowMUOYurNYphfU@cozumel>

On 26/08/10 09:58AM, Miquel Raynal wrote:
> Hello Han,
> 
> On 07/08/2026 at 18:11:27 -05, han.xu at oss.nxp.com wrote:
> 
> > From: Han Xu <han.xu at nxp.com>
> >
> > The QUAD ENABLE (QE) bit is stored in a per-die configuration
> > register on some SPI-NAND devices. When a device contains multiple
> > dies, updating the QE bit only on the currently selected die can
> > leave the remaining dies operating in non-quad mode.
> > ?
> > Iterate over all targets and update the QE setting on each die
> > during initialization to ensure consistent quad I/O operation
> > across the entire device.
> >
> > Tested on ISSI IS38SMW04G8B.
> >
> > Fixes: 7529df465248 ("mtd: nand: Add core infrastructure to support
> > SPI NANDs")
> 
> There was no ISSI device back then, but I guess this can be useful for
> other devices. However this shall be backported, so Cc: stable.

It should be a common issue, I will CC stable in v2 and try to upstream ISSI
drivers.

> 
> > Signed-off-by: Han Xu <han.xu at nxp.com>
> > ---
> >  drivers/mtd/nand/spi/core.c | 22 ++++++++++++++++++++--
> >  1 file changed, 20 insertions(+), 2 deletions(-)
> >
> > diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
> > index 35365b67dd8e..744662533f37 100644
> > --- a/drivers/mtd/nand/spi/core.c
> > +++ b/drivers/mtd/nand/spi/core.c
> > @@ -281,8 +281,26 @@ static int spinand_init_cfg_cache(struct spinand_device *spinand)
> >  static int spinand_init_quad_enable(struct spinand_device *spinand,
> >  				    bool enable)
> >  {
> > -	return spinand_upd_cfg(spinand, CFG_QUAD_ENABLE,
> > -			       enable ? CFG_QUAD_ENABLE : 0);
> > +	struct nand_device *nand = spinand_to_nand(spinand);
> > +	unsigned int target;
> > +	int ret;
> > +
> > +	/*
> > +	 * QE is a per-die setting on some devices. Program each target
> > +	 * individually when enabling or disabling quad I/O mode.
> > +	 */
> > +	for (target = 0; target < nand->memorg.ntargets; target++) {
> > +		ret = spinand_select_target(spinand, target);
> > +		if (ret)
> > +			return ret;
> > +
> > +		ret = spinand_upd_cfg(spinand, CFG_QUAD_ENABLE,
> > +				      enable ? CFG_QUAD_ENABLE : 0);
> > +		if (ret)
> > +			return ret;
> 
> If there is an issue in the middle of the loop, the chip will become
> unusable. Perhaps we should reset the dies to the !QE state and return
> an error to continue in degraded (single) mode if that happens?


Single-lane is the most basic op - if the QE set_feature write fails, the link
is broken and single-lane won't work anyway. And falling back needs the vendor's
own 1S op stored somewhere, but those variant tables are discarded after probe.
So it likely requires refactoring struct spinand_device to cache them. Given that,
maybe just keep the current return-error implementation.


> 
> Thanks,
> Miqu?l


From ziy at nvidia.com  Wed Aug 12 12:58:50 2026
From: ziy at nvidia.com (Zi Yan)
Date: Wed, 12 Aug 2026 15:58:50 -0400
Subject: [PATCH RFC 13/14] treewide: adjust comments on PagePrivate and
 PG_private
In-Reply-To: <20260731-remove-pg_private-v1-13-142c97ba3562@nvidia.com>
References: <20260731-remove-pg_private-v1-0-142c97ba3562@nvidia.com>
 <20260731-remove-pg_private-v1-13-142c97ba3562@nvidia.com>
Message-ID: <DKN8CAY6257F.6TB3KM9OX4AV@nvidia.com>

On Fri Jul 31, 2026 at 10:13 PM EDT, Zi Yan wrote:
> PG_private is going to be deleted. Adjust related comments to refer to
> page/folio->private instead.
>
> Assisted-by: Claude:claude-opus-4-8
> Assisted-by: Codex:gpt-5
> Signed-off-by: Zi Yan <ziy at nvidia.com>
> To: Ilya Dryomov <idryomov at gmail.com>
> To: Alex Markuze <amarkuze at redhat.com>
> To: Viacheslav Dubeyko <slava at dubeyko.com>
> To: Trond Myklebust <trondmy at kernel.org>
> To: Anna Schumaker <anna at kernel.org>
> To: Richard Weinberger <richard at nod.at>
> To: Andrew Morton <akpm at linux-foundation.org>
> To: David Hildenbrand <david at kernel.org>
> Cc: Zhihao Cheng <chengzhihao1 at huawei.com>
> Cc: Lorenzo Stoakes <ljs at kernel.org>
> Cc: "Liam R. Howlett" <liam at infradead.org>
> Cc: Vlastimil Babka <vbabka at kernel.org>
> Cc: Mike Rapoport <rppt at kernel.org>
> Cc: Suren Baghdasaryan <surenb at google.com>
> Cc: Michal Hocko <mhocko at suse.com>
> Cc: ceph-devel at vger.kernel.org
> Cc: linux-kernel at vger.kernel.org
> Cc: linux-nfs at vger.kernel.org
> Cc: linux-mtd at lists.infradead.org
> Cc: linux-mm at kvack.org
> ---
>  fs/ceph/addr.c           | 4 ++--
>  fs/nfs/file.c            | 4 ++--
>  fs/ubifs/file.c          | 6 +++---
>  include/linux/mm.h       | 6 +++---
>  include/linux/mm_types.h | 4 ++--
>  5 files changed, 12 insertions(+), 12 deletions(-)
>

I will fix the issues pointed out by Sashiko in the comments from
include/linux/mm.h, fs/ceph/addr.c, and fs/ubifs/file.c[1].

[1] https://sashiko.dev/#/patchset/20260731-remove-pg_private-v1-9-142c97ba3562%40nvidia.com?part=13

-- 
Best Regards,
Yan, Zi



From Takahiro.Kuwano at infineon.com  Wed Aug 12 17:48:11 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 13 Aug 2026 00:48:11 +0000
Subject: [PATCH] mtd: spi-nor: xmc: add locking to XM25QH128A
In-Reply-To: <20260811064512.42520-1-mwalle@kernel.org>
References: <20260811064512.42520-1-mwalle@kernel.org>
Message-ID: <ead6aa2a38c64ed78e6c9972a8dfc2fe@infineon.com>

> Add support for locking to the XM25QH128A part.
> 
> Reported-by: Takino <stayer.opt1f at icloud.com>
> Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221825
> Signed-off-by: Michael Walle <mwalle at kernel.org>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>

> ---
> Hi Takino,
> 
> please test it and reply with a "Tested-by" tag. With your full name if
> you like. I didn't get that from the bug report.
> ---
>  drivers/mtd/spi-nor/xmc.c | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/drivers/mtd/spi-nor/xmc.c b/drivers/mtd/spi-nor/xmc.c
> index d5a06054b0dd..d3b52eb93e2c 100644
> --- a/drivers/mtd/spi-nor/xmc.c
> +++ b/drivers/mtd/spi-nor/xmc.c
> @@ -19,6 +19,7 @@ static const struct flash_info xmc_nor_parts[] = {
>                 .name = "XM25QH128A",
>                 .size = SZ_16M,
>                 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
> +               .flags = SPI_NOR_HAS_LOCK | SPI_NOR_4BIT_BP | SPI_NOR_HAS_TB,
>         },
>  };
> 
> --
> 2.47.3



From xiaopei01 at kylinos.cn  Wed Aug 12 19:30:10 2026
From: xiaopei01 at kylinos.cn (Pei Xiao)
Date: Thu, 13 Aug 2026 10:30:10 +0800
Subject: [PATCH v2] mtd: block2mtd: Fix divide error when erase_size is
 zero
In-Reply-To: <871pc3msar.fsf@bootlin.com>
References: <48230456575d27aa83ce8640de8fc07cc62e8efb.1786499433.git.xiaopei01@kylinos.cn>
 <871pc3msar.fsf@bootlin.com>
Message-ID: <00cf942e-4e0e-43b6-b4fb-3dc1e50a158a@kylinos.cn>



? 2026/8/12 20:34, Miquel Raynal ??:
> On 12/08/2026 at 09:52:42 +08, Pei Xiao <xiaopei01 at kylinos.cn> wrote:
> 
>> The erase size is parsed from the "block2mtd" module parameter and can
>> be set to zero. add_device() then evaluates
>>
>>         if (size % erase_size)
>>
>> with a zero divisor, which triggers a divide error:
>>
>>         divide error: 0000 [#1] PREEMPT SMP PTI
>>         RIP: 0010:add_device drivers/mtd/devices/block2mtd.c:296 [inline]
>>         RIP: 0010:block2mtd_setup2+0x592/0xda0 drivers/mtd/devices/block2mtd.c:459
>>         Call Trace:
>>          block2mtd_setup+0x27/0xe0 drivers/mtd/devices/block2mtd.c:476
>>          param_attr_store+0x214/0x310 kernel/params.c:589
>>          module_attr_store+0x65/0x90 kernel/params.c:904
>>          kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345
>>          ...
>>
>> Reject a zero erase size before performing the modulo operation so the
>> existing "erasesize must be a divisor of device size" error path
>> reports the invalid argument and frees the device.
>>
>> While at it, drop the unnecessary (long) cast from the size operand of
>> the modulo.
>>
>> Fixes: ea6d833a3fdd ("mtd: block2mtd: check device size")
>> Reported-by: syzbot+b320a4d5f65a61dbbf89 at syzkaller.appspotmail.com
>> Closes: https://lore.kernel.org/lkml/6a7b58ba.ac361c09.22ff0a.0045.GAE at google.com/
>> Suggested-by: J?rn Engel <joern at barelysecure.org>
>> Cc: stable at vger.kernel.org
>> Signed-off-by: Pei Xiao <xiaopei01 at kylinos.cn>
> 
> Apparently, removing the cast leads to an unsupported operation with a
> 64-bit operand on 32-bit architectures (See Sashiko report).
Using i386 for testing does reveal compilation issues; I will retain
this (long) cast and send the third version of the patch.

make ARCH=i386 -j$(nproc)

ld: drivers/mtd/devices/block2mtd.o: in function `block2mtd_setup2':
block2mtd.c:(.text+0x315): undefined reference to `__moddi3'
make[2]: *** [scripts/Makefile.vmlinux:72?vmlinux.unstripped] ?? 1

> 
> I guess it is best to keep that cast in the end.
> 
> Sorry for the changes back-and-forth.
It is my pleasure.

Pei.
> 
> Miqu?l



From xiaopei01 at kylinos.cn  Wed Aug 12 19:48:12 2026
From: xiaopei01 at kylinos.cn (Pei Xiao)
Date: Thu, 13 Aug 2026 10:48:12 +0800
Subject: [PATCH v3] mtd: block2mtd: Fix divide error when erase_size is zero
Message-ID: <25ec7ba58facdf29b42ca43dcf11cd98e2089a5f.1786589041.git.xiaopei01@kylinos.cn>

The erase size is parsed from the "block2mtd" module parameter and can
be set to zero. add_device() then evaluates

        if (size % erase_size)

with a zero divisor, which triggers a divide error:

divide error: 0000 [#1] PREEMPT SMP PTI
RIP: 0010:add_device drivers/mtd/devices/block2mtd.c:296 [inline]
RIP: 0010:block2mtd_setup2+0x592/0xda0 drivers/mtd/devices/block2mtd.c:459
Call Trace:
block2mtd_setup+0x27/0xe0 drivers/mtd/devices/block2mtd.c:476
param_attr_store+0x214/0x310 kernel/params.c:589
module_attr_store+0x65/0x90 kernel/params.c:904
kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345
...

Reject a zero erase size before performing the modulo operation so the
existing "erasesize must be a divisor of device size" error path
reports the invalid argument and frees the device.

Fixes: ea6d833a3fdd ("mtd: block2mtd: check device size")
Reported-by: syzbot+b320a4d5f65a61dbbf89 at syzkaller.appspotmail.com
Closes: https://lore.kernel.org/lkml/6a7b58ba.ac361c09.22ff0a.0045.GAE at google.com/
Suggested-by: J?rn Engel <joern at barelysecure.org>
Cc: stable at vger.kernel.org
Signed-off-by: Pei Xiao <xiaopei01 at kylinos.cn>
---
changes in v3:
1.remove (long) cast due to build errors in 32bit config systems
2.changes git log about (long) cast

changlogs in v2:
1.Add Suggested-by tag
2.remove unnecessary (long) cast from the size
---
 drivers/mtd/devices/block2mtd.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c
index 03e80b2c4f5a..349fa07be314 100644
--- a/drivers/mtd/devices/block2mtd.c
+++ b/drivers/mtd/devices/block2mtd.c
@@ -293,7 +293,7 @@ static struct block2mtd_dev *add_device(char *devname, int erase_size,
 	}
 
 	size = bdev_nr_bytes(bdev);
-	if ((long)size % erase_size) {
+	if (!erase_size || (long)size % erase_size) {
 		pr_err("erasesize must be a divisor of device size\n");
 		goto err_free_block2mtd;
 	}
-- 
2.25.1



From miquel.raynal at bootlin.com  Wed Aug 12 23:48:47 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 08:48:47 +0200
Subject: [PATCH] mtd: spinand: Enable QE on all dies
In-Reply-To: <anowMUOYurNYphfU@cozumel> (han xu's message of "Wed, 12 Aug 2026
	11:34:26 -0500")
References: <20260807-spinand_qe-v1-1-c102c79834a3@nxp.com>
	<87qzk6s8yr.fsf@bootlin.com> <anowMUOYurNYphfU@cozumel>
Message-ID: <87v79eldm8.fsf@bootlin.com>


>> > +	/*
>> > +	 * QE is a per-die setting on some devices. Program each target
>> > +	 * individually when enabling or disabling quad I/O mode.
>> > +	 */
>> > +	for (target = 0; target < nand->memorg.ntargets; target++) {
>> > +		ret = spinand_select_target(spinand, target);
>> > +		if (ret)
>> > +			return ret;
>> > +
>> > +		ret = spinand_upd_cfg(spinand, CFG_QUAD_ENABLE,
>> > +				      enable ? CFG_QUAD_ENABLE : 0);
>> > +		if (ret)
>> > +			return ret;
>> 
>> If there is an issue in the middle of the loop, the chip will become
>> unusable. Perhaps we should reset the dies to the !QE state and return
>> an error to continue in degraded (single) mode if that happens?
>
>
> Single-lane is the most basic op - if the QE set_feature write fails, the link
> is broken and single-lane won't work anyway. And falling back needs the vendor's
> own 1S op stored somewhere, but those variant tables are discarded after probe.
> So it likely requires refactoring struct spinand_device to cache them. Given that,
> maybe just keep the current return-error implementation.

Ack.


From miquel.raynal at bootlin.com  Thu Aug 13 01:19:08 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 10:19:08 +0200
Subject: [PATCH RFC v2] mtd: spinand: winbond: add support for W25N04LW
In-Reply-To: <20260812-w25n04lw-v2-1-deee97602fc4@atmark-techno.com>
	(Dominique Martinet's message of "Wed, 12 Aug 2026 04:14:02 +0000")
References: <20260812-w25n04lw-v2-1-deee97602fc4@atmark-techno.com>
Message-ID: <87mruql9fn.fsf@bootlin.com>

Hi Dominique,

A bit more feedback...

On 12/08/2026 at 04:14:02 GMT, Dominique Martinet <dominique.martinet at atmark-techno.com> wrote:

> Add support for Winbond W25N04LW 4Gbit SPI-NAND:
>> 1.8V 4G-bit
>> Raw serial SLC NAND flash memory
>> Dual/Quad SPI with 104MHz
>> buffer read, continuous read & sequential read
>
> Highlights copied from datasheet:
> ? Page size: 4,352 Bytes (4096 + 256 Bytes)
> ? Block size: 64 pages (256K + 16K Bytes)
> - Built-in 8-Bit ECC for memory array
> ? ECC status bits indicate ECC results
> ? Bad Block Management and LUT access
> ? Software and Hardware Write-Protect
> ? Power Supply Lock-Down and OTP protection
> ? Unique ID and Parameter page
> ? Ten 4KB OTP pages per die
> ? Read Level Setting for Read Retry
>
> The 4096+256 byte page layout is as follow:
> - 4096 bytes ECC protected "main memory array", subdivided in 8
>   512 bytes sectors
> - 128 bytes spare array, subdivided in 8 16 bytes sectors as follow:
>   - 2 bytes bad block marker (no ECC protection)
>   - 2 bytes "user data II" (no ECC protection)
>   - 12 bytes "user data I" (ECC protected)
> - 128 bytes parity array, subdivided in 8 16 bytes sectors:
>   - 13 bytes ECC for main data and user data
>   - 3 unused bytes
>
> This commit does not support most advanced features such as continuous
> read, LUT management and OTP pages, and only provides basic usage.
>
> OOB layout was defined to only show ECC-protected "user data I", leaving
> "user data II" unavailable.

Fine, there is not strong rule, and anyway this is only useful for non
UBI users (jffs2, typically).

> Signed-off-by: Daisuke Mizobuchi <mizo at atmark-techno.com>
> Signed-off-by: Dominique Martinet <dominique.martinet at atmark-techno.com>
> ---
> Changes in v2:
> - Link to v1: https://patch.msgid.link/ansYUoNKJyDQ+6f2 at hu-mdalam-blr.qualcomm.com
> - fixed oob layout
> - fixed NAND_MEMORG oob size
>
> I've tested the NAND with various mtd test utils (mtd_oobtest,
> mtd_pagetest, mtd_subpagetest, mtd_nandbiterrs and some mtd-utils
> userspace counterparts) with no problem, but I'm not sure what to do
> with the ooblayout free() informations: as written in the commit
> message, this chip has 2+12 bytes of user data per sector where the
> first two are not ECC protected and the later 12 are.
>
> I believe user should make the choice of which oob bytes they want to
> use, but afaik we can't say "this is not protected", so we can't let
> them choose and must either say both are free or only provide one like I
> did here.

Indeed.

> As an extra data point, this "user area I and II" distinction is the same
> in W25N04KW (same 2+12), but w25n02kv_ooblayout_free() use there returns
> the whole 14 bytes as a single chunk, so I explicitly made a different
> choice here.
> (I believe that's not something that can be changed easily, so we should
> discuss this before merging)

If it's the same layout, I would go for keeping the existing one and
avoid the proliferation of these helpers.

> That aside:
> - Alam, would you like your name somewhere in the commit? I didn't keep
>   anything from your commit because I already had one, but happy to add
>   a Co-developed-by or something
> - I kept NAND_ECCREQ(8, 512) like W25N04KW but the datasheet says it's
>   based on 8-bits/544-bytes ECC, so I should set it as (8, 544)?

No, we are protecting 512 bytes of data. 544 is the area including bytes
used for the ECC operation itself and we are not interested by those in
this field.

>   (since it's protecting 512+12 bytes for up to 8 bits corruption)
>   In practice ECC is done by hardware and looks like it is correctly
>   reported (e.g. manually overwriting a couple of bits with nandwrite
>   and checking with nanddump properly corrects and reports number of
>   corrected bits), but might as well get this right, even if as far as I
>   understand nothing actually uses the ECC oob data to double-check
>   hardware status?

No we do not. If nandbiterrs pass, normally it's all fine.

> - I'll shamefully admit I do not understand the read / write /
>   update_cache_variants() I copied from KW,
>   Alam had the same but it'd be great to confirm using the same
>   callbacks makes sense?

There are various operations available for I/Os. Depending on the spi
controller capability and the routing at the board level, you might want
to use single, dual, quad, dtr or not transfers. And depending on the
speed you may need extra dummy cycles. All of this is captured in this
array.

> - (I wrote about OTP in the commit message but have no plan of
>   implementing it at this point as we have no use for it, I still
>   intend to look at continuous read after some other work)

Fine!

Thanks,
Miqu?l


From miquel.raynal at bootlin.com  Thu Aug 13 01:24:36 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 10:24:36 +0200
Subject: [PATCH v7 15/18] mtd: spinand: extract variant ranking logic
 into spinand_op_find_best_variant()
In-Reply-To: <20260811183313.1550425-16-s-k6@ti.com> (Santhosh Kumar K.'s
	message of "Wed, 12 Aug 2026 00:03:10 +0530")
References: <20260811183313.1550425-1-s-k6@ti.com>
	<20260811183313.1550425-16-s-k6@ti.com>
Message-ID: <87h5kyl96j.fsf@bootlin.com>

On 12/08/2026 at 00:03:10 +0530, Santhosh Kumar K <s-k6 at ti.com> wrote:

> Refactor spinand_select_op_variant() to extract the inner loop into a
> shared helper spinand_op_find_best_variant(). The helper takes a
> skip_mask bitmask of already-tried variant indices, enabling callers to
> iterate variants in ranked performance order while skipping those
> already attempted. spinand_select_op_variant() becomes a one-liner
> wrapper. No functional change.
>
> Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>

Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>


From miquel.raynal at bootlin.com  Thu Aug 13 01:35:52 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 10:35:52 +0200
Subject: [PATCH v7 16/18] mtd: spinand: negotiate optimal controller
 operating point before dirmap creation
In-Reply-To: <20260811183313.1550425-17-s-k6@ti.com> (Santhosh Kumar K.'s
	message of "Wed, 12 Aug 2026 00:03:11 +0530")
References: <20260811183313.1550425-1-s-k6@ti.com>
	<20260811183313.1550425-17-s-k6@ti.com>
Message-ID: <87bjb6l8nr.fsf@bootlin.com>

Hi Santhosh,

I have only one comment on the spinand bits.

> +/*
> + * spinand_try_ranked_variant() - Try controller optimization on variants in
> + *				  performance order.
> + * @spinand:    SPI NAND device
> + * @mem:        SPI memory device
> + * @iface:      bus interface to iterate (ODTR or SSDR)
> + * @tried_mask: bitmask of already-tried variant indices; updated on each try
> + *
> + * Iterates the full read variant list in descending performance order,
> + * skipping variants in @tried_mask, and calls execute_tuning on each until
> + * one succeeds. Ranked iteration finds the best available variant without
> + * re-trying already-attempted ones.
> + *
> + * On success, sets spinand->max_read_op and updates the matching
> + * odtr_op_templates.read_cache or ssdr_op_templates.read_cache.
> + */
> +static bool spinand_try_ranked_variant(struct spinand_device *spinand,
> +				       struct spi_mem *mem,
> +				       enum spinand_bus_interface iface,
> +				       u32 *tried_mask)
> +{
> +	const struct spinand_op_variants *variants = spinand->all_read_variants;
> +	const struct spi_mem_op *best;
> +	int ret;
> +
> +	if (!variants)
> +		return false;
> +
> +	while ((best = spinand_op_find_best_variant(spinand, variants, iface,
> +						    *tried_mask))) {
> +		*tried_mask |= BIT(best - variants->ops);
> +		spinand->max_read_op = *best;
> +		spinand->max_read_op.max_freq = 0;
> +		spinand->max_write_op.max_freq = 0;
> +		ret = spi_mem_execute_tuning(mem, &spinand->max_read_op,
> +					     &spinand->max_write_op);
> +		if (ret && ret != -EOPNOTSUPP)
> +			dev_dbg(&mem->spi->dev, "%s optimization failed: %d\n",
> +				iface == ODTR ? "ODTR" : "SSDR", ret);
> +		if (!ret && spinand->max_read_op.max_freq) {
> +			if (iface == ODTR)
> +				spinand->odtr_op_templates.read_cache = best;
> +			else
> +				spinand->ssdr_op_templates.read_cache = best;
> +			spinand->cont_read_possible = false;

Why do you disable continuous reads? I know it is not the same as the
read template, but it only differs by a few dummy cycles, so everything
should work as expected. I believe without complexifying much the logic
we should be able to support it.

Thanks,
Miqu?l


From dominique.martinet at atmark-techno.com  Thu Aug 13 02:30:28 2026
From: dominique.martinet at atmark-techno.com (Dominique Martinet)
Date: Thu, 13 Aug 2026 18:30:28 +0900
Subject: [PATCH RFC v2] mtd: spinand: winbond: add support for W25N04LW
In-Reply-To: <87mruql9fn.fsf@bootlin.com>
References: <20260812-w25n04lw-v2-1-deee97602fc4@atmark-techno.com>
 <87mruql9fn.fsf@bootlin.com>
Message-ID: <an2OtHX62YRg0ZYZ@atmark-techno.com>

Thanks for the feedback!

Miquel Raynal wrote on Thu, Aug 13, 2026 at 10:19:08AM +0200:
> > OOB layout was defined to only show ECC-protected "user data I", leaving
> > "user data II" unavailable.
> 
> Fine, there is not strong rule, and anyway this is only useful for non
> UBI users (jffs2, typically).

Yeah, I was expecting UBI to use it too but I looked it up after posting
and saw it doesn't, so I agree it doesn't matter much for us either way.

> > As an extra data point, this "user area I and II" distinction is the same
> > in W25N04KW (same 2+12), but w25n02kv_ooblayout_free() use there returns
> > the whole 14 bytes as a single chunk, so I explicitly made a different
> > choice here.
> > (I believe that's not something that can be changed easily, so we should
> > discuss this before merging)
> 
> If it's the same layout, I would go for keeping the existing one and
> avoid the proliferation of these helpers.

The layout is similar (each section has 2 bytes for BB marker / 2 bytes
user data II / 12 bytes user data I), but the number of sections per
erase block is different (went from 4 to 8)
For ECC it's the same (4 to 8 sections, and offset changed from 64 to
128 accordingly because there were more data sections before)

I see similar helpers in other drivers (e.g. alliancememory.c) use
mtd->oobsize; so if you prefer I can reuse the existing
w25n02kv_ooblayout* helpers based on this?
----(untested)

static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section,
                                  struct mtd_oob_region *region)
{
	/* 4 sections for oobsize=128, 8 for oobsize=256 */
        if (section >= mtd->oobsize / 32)
                return -ERANGE;

        region->offset = (mtd->oobsize / 2) + (16 * section);
        region->length = 13;

        return 0;
}

static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section,
                                   struct mtd_oob_region *region)
{
        if (section >= mtd->oobsize / 32)
                return -ERANGE;

        region->offset = (16 * section) + 2;
        region->length = 14;

        return 0;
}
----

(
  Technically could go as far as checking the ecc strength[1] and also
  merge in w25n01kv_ooblayout_ecc (variable ecc length/offset step),
  then if we're greedy also handle oobsize == 64 to replace
  w25m02gv_ooblayout_ecc/w25m02gv_ooblayout_free, but that's starting to
  make the functions more complex than I'm confident in... :)
  [1] mtd_to_nanddev(mtd)->eccreq.strength
)


(And in that case I'll happily give up on the non-ECC distinction and
keep the current behavior that exposes the non-protected bits)

> > That aside:
> > - Alam, would you like your name somewhere in the commit? I didn't keep
> >   anything from your commit because I already had one, but happy to add
> >   a Co-developed-by or something
> > - I kept NAND_ECCREQ(8, 512) like W25N04KW but the datasheet says it's
> >   based on 8-bits/544-bytes ECC, so I should set it as (8, 544)?
> 
> No, we are protecting 512 bytes of data. 544 is the area including bytes
> used for the ECC operation itself and we are not interested by those in
> this field.

There's also the 12 bytes in "user data I", but I agree it's probably
fine to ignore here.
nandbiterrs passes, so I'll leave it as is.

> > - I'll shamefully admit I do not understand the read / write /
> >   update_cache_variants() I copied from KW,
> >   Alam had the same but it'd be great to confirm using the same
> >   callbacks makes sense?
> 
> There are various operations available for I/Os. Depending on the spi
> controller capability and the routing at the board level, you might want
> to use single, dual, quad, dtr or not transfers. And depending on the
> speed you may need extra dummy cycles. All of this is captured in this
> array.

Ok, so I ought to check that e.g.
SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP still needs 2 dummy op
delay?
(I couldn't find the _1S_4S_4S_ meaning explained anywhere, but looking
at the commands used the number is the number of lines used for the
command instruction itself, the column address and the data itself?
the datasheet doesn't seem to handle any command with a *D so I'll
ignore the S/D...)

Ah, okay, the dummy delay is in byte and looking at the datasheet I need
one byte worth for 0x6b (1S_1S_4S, 8 cycles at 1 line) and two bytes
worth for 0xeb (1S_4S_4S, 4 cycles at 4 lines) so this makes sense, I'll
take some time to check the other ops before submitting v3.


(And I guess that's where I'll need to add stuff for continuous read
too, it doesn't look like I can reuse the
cont_read_cache_dual_quad_dtr_variants as the ops you added don't seem
to exist here, if I understood it right I *think* I need to set some
state register then with the same commands keeping CS down/clock ticking
should keep providing data...? Well; I'll look it up some more too
before bothering you again)

Thank you again,
-- 
Dominique




From miquel.raynal at bootlin.com  Thu Aug 13 03:44:32 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 12:44:32 +0200
Subject: [PATCH RFC v2] mtd: spinand: winbond: add support for W25N04LW
In-Reply-To: <an2OtHX62YRg0ZYZ@atmark-techno.com> (Dominique Martinet's
	message of "Thu, 13 Aug 2026 18:30:28 +0900")
References: <20260812-w25n04lw-v2-1-deee97602fc4@atmark-techno.com>
	<87mruql9fn.fsf@bootlin.com> <an2OtHX62YRg0ZYZ@atmark-techno.com>
Message-ID: <875x1el2pb.fsf@bootlin.com>

On 13/08/2026 at 18:30:28 +09, Dominique Martinet <dominique.martinet at atmark-techno.com> wrote:

> Thanks for the feedback!
>
> Miquel Raynal wrote on Thu, Aug 13, 2026 at 10:19:08AM +0200:
>> > OOB layout was defined to only show ECC-protected "user data I", leaving
>> > "user data II" unavailable.
>> 
>> Fine, there is not strong rule, and anyway this is only useful for non
>> UBI users (jffs2, typically).
>
> Yeah, I was expecting UBI to use it too but I looked it up after posting
> and saw it doesn't, so I agree it doesn't matter much for us either way.
>
>> > As an extra data point, this "user area I and II" distinction is the same
>> > in W25N04KW (same 2+12), but w25n02kv_ooblayout_free() use there returns
>> > the whole 14 bytes as a single chunk, so I explicitly made a different
>> > choice here.
>> > (I believe that's not something that can be changed easily, so we should
>> > discuss this before merging)
>> 
>> If it's the same layout, I would go for keeping the existing one and
>> avoid the proliferation of these helpers.
>
> The layout is similar (each section has 2 bytes for BB marker / 2 bytes
> user data II / 12 bytes user data I), but the number of sections per
> erase block is different (went from 4 to 8)
> For ECC it's the same (4 to 8 sections, and offset changed from 64 to
> 128 accordingly because there were more data sections before)
>
> I see similar helpers in other drivers (e.g. alliancememory.c) use
> mtd->oobsize; so if you prefer I can reuse the existing
> w25n02kv_ooblayout* helpers based on this?

Yes, that would be ideal.

> (
>   Technically could go as far as checking the ecc strength[1] and also
>   merge in w25n01kv_ooblayout_ecc (variable ecc length/offset step),
>   then if we're greedy also handle oobsize == 64 to replace
>   w25m02gv_ooblayout_ecc/w25m02gv_ooblayout_free, but that's starting to
>   make the functions more complex than I'm confident in... :)
>   [1] mtd_to_nanddev(mtd)->eccreq.strength
> )

Up to you :)

> (And in that case I'll happily give up on the non-ECC distinction and
> keep the current behavior that exposes the non-protected bits)
>
>> > That aside:
>> > - Alam, would you like your name somewhere in the commit? I didn't keep
>> >   anything from your commit because I already had one, but happy to add
>> >   a Co-developed-by or something
>> > - I kept NAND_ECCREQ(8, 512) like W25N04KW but the datasheet says it's
>> >   based on 8-bits/544-bytes ECC, so I should set it as (8, 544)?
>> 
>> No, we are protecting 512 bytes of data. 544 is the area including bytes
>> used for the ECC operation itself and we are not interested by those in
>> this field.
>
> There's also the 12 bytes in "user data I", but I agree it's probably
> fine to ignore here.
> nandbiterrs passes, so I'll leave it as is.

https://elixir.bootlin.com/linux/v7.1.8/source/include/linux/mtd/nand.h#L187
It is not very clear, but this macro is about the main data ("bytes per step").

>> > - I'll shamefully admit I do not understand the read / write /
>> >   update_cache_variants() I copied from KW,
>> >   Alam had the same but it'd be great to confirm using the same
>> >   callbacks makes sense?
>> 
>> There are various operations available for I/Os. Depending on the spi
>> controller capability and the routing at the board level, you might want
>> to use single, dual, quad, dtr or not transfers. And depending on the
>> speed you may need extra dummy cycles. All of this is captured in this
>> array.
>
> Ok, so I ought to check that e.g.
> SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP still needs 2 dummy op
> delay?
> (I couldn't find the _1S_4S_4S_ meaning explained anywhere, but looking
> at the commands used the number is the number of lines used for the
> command instruction itself, the column address and the data itself?

Yes.

Dummy width is the same as address width BTW.

> the datasheet doesn't seem to handle any command with a *D so I'll
> ignore the S/D...)

SDR vs DTR. You chip may or may not support it.

Good luck!
Miqu?l


From han.xu at oss.nxp.com  Thu Aug 13 06:07:24 2026
From: han.xu at oss.nxp.com (han.xu at oss.nxp.com)
Date: Thu, 13 Aug 2026 08:07:24 -0500
Subject: [PATCH v2] mtd: spinand: Enable QE on all dies
Message-ID: <20260813-spinand_qe-v2-1-376193ac7b1a@nxp.com>

From: Han Xu <han.xu at nxp.com>

The QUAD ENABLE (QE) bit is stored in a per-die configuration
register on some SPI-NAND devices. When a device contains multiple
dies, updating the QE bit only on the currently selected die can
leave the remaining dies operating in non-quad mode.
?
Iterate over all targets and update the QE setting on each die
during initialization to ensure consistent quad I/O operation
across the entire device.

Tested on ISSI IS38SMW04G8B.

Fixes: 7529df465248 ("mtd: nand: Add core infrastructure to support SPI NANDs")
Cc: stable at vger.kernel.org
Signed-off-by: Han Xu <han.xu at nxp.com>
---
Changes in v2:
- Add CC stable
- Link to v1: https://patch.msgid.link/20260807-spinand_qe-v1-1-c102c79834a3 at nxp.com

To: Miquel Raynal <miquel.raynal at bootlin.com>
To: Richard Weinberger <richard at nod.at>
To: Vignesh Raghavendra <vigneshr at ti.com>
To: Peter Pan <peterpandong at micron.com>
To: Boris Brezillon <bbrezillon at kernel.org>
Cc: linux-mtd at lists.infradead.org
Cc: linux-kernel at vger.kernel.org
---
 drivers/mtd/nand/spi/core.c | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 35365b67dd8e..744662533f37 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -281,8 +281,26 @@ static int spinand_init_cfg_cache(struct spinand_device *spinand)
 static int spinand_init_quad_enable(struct spinand_device *spinand,
 				    bool enable)
 {
-	return spinand_upd_cfg(spinand, CFG_QUAD_ENABLE,
-			       enable ? CFG_QUAD_ENABLE : 0);
+	struct nand_device *nand = spinand_to_nand(spinand);
+	unsigned int target;
+	int ret;
+
+	/*
+	 * QE is a per-die setting on some devices. Program each target
+	 * individually when enabling or disabling quad I/O mode.
+	 */
+	for (target = 0; target < nand->memorg.ntargets; target++) {
+		ret = spinand_select_target(spinand, target);
+		if (ret)
+			return ret;
+
+		ret = spinand_upd_cfg(spinand, CFG_QUAD_ENABLE,
+				      enable ? CFG_QUAD_ENABLE : 0);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
 
 static int spinand_ecc_enable(struct spinand_device *spinand,

---
base-commit: 113f62225e98febabba0fee00afd29d4eaf65abb
change-id: 20260807-spinand_qe-dd6148042f63

Best regards,
--  
Han Xu <han.xu at nxp.com>



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:00 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:00 +0200
Subject: [PATCH v3 00/23] mtd: spi-nor: QE handling cleanup + fixup reworks
 + Winbond RV chips addition
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>

Hello,

Following my "software protection" enhancement series, there were
several comments about the need to simplify and reorganize the handling
of the QE bits, which is strongly impacted by the various read/write
status register capabilities.

The third patch is all about this. It is a massive cleanup, everything
is explained inside, the how and the why. This is a commit that must be
tested, so if you have a SPI NOR chip with one of the not yet tested
SFDP QER configuration, please consider applying that patch (just patch
1 is enough) and boot. If your chip is recognized, it's probably all
good.

Since it was pointed that the legacy .name fields were part of the ABI,
my previous deletion was no longer possible, so I now propose a
(writable) indirection for accessing this field, which will allow
developers to hide that name using fixups.

Talking about fixups, I continued with another rework that has been
asked for: changing the way fixups are listed. I am now creating a table
of fixups, which can be matched by ID and/or with a match function, as
per Michael Walle's suggestion. This cleans quite a bit the fixup lists
in the winbond driver, especially because of the new name hiding fixup,
so thanks for the suggestion!

Then I add support for JESD216 revision F and its new QER field.

Finally, I rework a bit the fixups in the Winbond driver, which allows
a clean addition of the new Winbond RV chips.

All chips I will ever be able to test are now listed and proof of
testing is below the commit message.

Thanks,
Miqu?l

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
Changes in v3:
- Added support for two new chips that I could test (W25Q51RV-M and
  W25Q02RV-Q/N). Edited the last commit which is now only adding the
  256M-bit densities without the possibility to test, since I will not
  receive any of these any time soon.
- Reworked completely the way fixups are now being applied.
- Re-instated the legacy .names, created a way to overwrite them and
  enabled a fixup for that.
- Link to v2: https://lore.kernel.org/r/20260807-winbond-v7-1-spi-nor-rv-addition-v2-0-7797642dfc40 at bootlin.com

Changes in v2:
- Added support for more chips that I received and could test. The main
  logic in the first patches is completely untouched.
- Fixed the name of a parameter (s/sr2/srs/) in the low level read status
  registers helper.
- Decided that there was no specific handling of the WP pin being
  strapped wrt the read check over the SR content. This would be new,
  and I don't think it is wide to handle it in the verify helper, but
  instead this should be let to the caller to decide whether it needs
  to hard fail or just degrade into single mode (typically if the QE
  bit has not been written). But this would require an extra DT
  property indicating the WP strap state which cannot be read back by
  software.
- Created a structure to store the flash specific opcodes.
- Proposed a rename of BFPT_DWORD15_QER_SR2_BIT1_BUGGY in favour of
  the _NO_1B_WR suffix.
- After checking, decided that the .name field is legacy and not part
  of any stable API, thus it can be removed. So added back a commit
  for that, removing the names for the whole JV family.
- Exposed all opcodes through debugfs, including the QE bit position.
- OTP and SWP writes are now read back like before.
- 2 known deviations are now explicitely stated in the commit log.
- I changed a little bit the QER field parsing in the sfdp.c driver, to
  limit deviations, as much as my understanding would allow me.
- Link to v1: https://lore.kernel.org/r/20260529-winbond-v7-1-spi-nor-rv-addition-v1-0-f3ae18502d5a at bootlin.com

---
Miquel Raynal (23):
      mtd: spi-nor: Rename BFPT_DWORD15_QER_SR2_BIT1_BUGGY
      mtd: spi-nor: Create a structure containing the flash opcodes
      mtd: spi-nor: Refactor Read Status/Write Status support
      mtd: spi-nor: Add support for the new JESD216 rev F QER field
      mtd: spi-nor: Create the concept of fixup table with match function
      mtd: spi-nor: Create an indirection on the part name
      mtd: spi-nor: Move the SFDP header structure to a C header
      mtd: spi-nor: winbond: Prepare introduction of W25QxxRV-Q/N parts
      mtd: spi-nor: winbond: Add support for W25Q32RV-Q/N
      mtd: spi-nor: winbond: Add support for W25Q64RV-Q/N
      mtd: spi-nor: winbond: Add support for W25Q12RV-Q/N
      mtd: spi-nor: winbond: Add support for W25Q51RV-Q/N
      mtd: spi-nor: winbond: Add support for W25Q01RV-Q/N
      mtd: spi-nor: winbond: Add support for W25Q02RV-Q/N
      mtd: spi-nor: winbond: Prepare introduction of W25QxxRV-M parts
      mtd: spi-nor: winbond: Add support for W25Q32RV-M
      mtd: spi-nor: winbond: Add support for W25Q64RV-M
      mtd: spi-nor: winbond: Add support for W25Q12RV-M
      mtd: spi-nor: winbond: Add support for W25Q51RV-M
      mtd: spi-nor: winbond: Add support for W25Q01RV-M
      mtd: spi-nor: winbond: Add support for W25Q02RV-M
      mtd: spi-nor: winbond: Add support for W25Q51RV-Q/N/M
      mtd: spi-nor: debugfs: Expose SR opcodes and QE mask

 Documentation/driver-api/mtd/spi-nor.rst |   2 +-
 drivers/mtd/spi-nor/atmel.c              |  75 ++-
 drivers/mtd/spi-nor/core.c               | 875 ++++++++++++-------------------
 drivers/mtd/spi-nor/core.h               |  94 ++--
 drivers/mtd/spi-nor/debugfs.c            |  22 +-
 drivers/mtd/spi-nor/everspin.c           |   7 +-
 drivers/mtd/spi-nor/gigadevice.c         |  13 +-
 drivers/mtd/spi-nor/issi.c               |  25 +-
 drivers/mtd/spi-nor/macronix.c           |  35 +-
 drivers/mtd/spi-nor/micron-st.c          |  51 +-
 drivers/mtd/spi-nor/otp.c                |  16 +-
 drivers/mtd/spi-nor/sfdp.c               | 114 ++--
 drivers/mtd/spi-nor/sfdp.h               |  21 +-
 drivers/mtd/spi-nor/spansion.c           |  61 ++-
 drivers/mtd/spi-nor/sst.c                |  16 +-
 drivers/mtd/spi-nor/swp.c                |  58 +-
 drivers/mtd/spi-nor/sysfs.c              |   4 +-
 drivers/mtd/spi-nor/winbond.c            | 106 +++-
 include/linux/mtd/spi-nor.h              |   6 +-
 19 files changed, 802 insertions(+), 799 deletions(-)
---
base-commit: 728e68a889bcf257b1e67298b12c360e5c3a13e0
change-id: 20260529-winbond-v7-1-spi-nor-rv-addition-cea2f1f158be
prerequisite-message-id: <20260805-winbond-v7-1-spi-nor-jv-cleanup-v5-0-87238e577794 at bootlin.com>
prerequisite-patch-id: b4c64eddd0cbb8a67a3ceb3d5a8fb7118ee8bbd3
prerequisite-patch-id: ea4fe4b14c3613b81a14c7af7ef5b7783c8cc633
prerequisite-patch-id: 4386d6ade3a3cd161669fec71550a9ccd96313f1
prerequisite-patch-id: c8a25a85853dc662591c1099350a37ff842d072b
prerequisite-patch-id: 74656376c96b96fc1b3cdc7074de6b3cb87d9ca7
prerequisite-patch-id: 90271b7e055db3079d3897546c3480a285b740c8
prerequisite-patch-id: 600c72d0a18dc003917f774209625f24c37d11d5
prerequisite-patch-id: e4543f8bf8f0fc69ce6dffc1b18858cf9ba6c04c
prerequisite-patch-id: 260a5ff56d2e5fc5583b875e0e0eca14786f23c6
prerequisite-patch-id: 2895240e7ee5b7d3f0792e7c964be474c583a4c6
prerequisite-patch-id: d429c7951d6bc134fb9f8990d72ff678b55e39d2
prerequisite-patch-id: 4733bf85a0015bf832f46909518c5c3b291824ff
prerequisite-patch-id: 876879d9ca584afcfda5ea388a39f71ba5e88847
prerequisite-patch-id: 849e3782c7a6263bce97a359f1e8521dc2b2ec77
prerequisite-patch-id: a4d9ceb8d99e527d9b9b184cbb1923c99e15fe1a
prerequisite-patch-id: 8c9246e1485d06429b1caaae7a1efb7af5122723
prerequisite-patch-id: 66880ad35a1681b724b51cb40e591270a65429ce
prerequisite-patch-id: a750eb3c350e1a88aef8749506332663fb44e007
prerequisite-patch-id: 6e9d4b20e02afb09defda703c74d56ea222d10c1
prerequisite-patch-id: 808ce773f9ecb493ba9362e088453638ee93b222
prerequisite-patch-id: 462a6751e3e9074102e8d31b23bf06140f6ee6e6
prerequisite-patch-id: 309ee450a2ac8eb24516c6839b944bc5f36a6db6
prerequisite-patch-id: 07059d14bb516ba19fc0a678a96b3224f91caf24
prerequisite-patch-id: 1e221a9bbfb1fc53982beaf75bccf48baaba491f
prerequisite-patch-id: 20ce5105adc4c7f8ebc6c779b107fd54def4f1f8
prerequisite-patch-id: cd8fddc6d97316419b52997207a198037b41b32d
prerequisite-patch-id: 759f747940c26df6ec85fecb4779fd54e0ea3735
prerequisite-patch-id: eaeebc9df5221bab68a6cabc64a97e9c2608899b

Best regards,
-- 
Miquel Raynal <miquel.raynal at bootlin.com>



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:01 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:01 +0200
Subject: [PATCH v3 01/23] mtd: spi-nor: Rename
 BFPT_DWORD15_QER_SR2_BIT1_BUGGY
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-1-b637cf120d5c@bootlin.com>

The _BUGGY suffix is not super accurate. There is a behavior that indeed
seems strange, but it is somewhat documented. The fact is, with this QER
field the status register is considered 16-bit wide and if one writes
the first 8 bits (SR1) without the next 8 bits (SR2) then the content of
SR2 will be assumed to be 0x00 and will be cleared.

Rename it BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR to align with the other
namings (_NO_RD) and clarify the meaning of this field.

No functional change.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/sfdp.c | 2 +-
 drivers/mtd/spi-nor/sfdp.h | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index cf0e2dc7c120..526763c79f55 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -567,7 +567,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 		params->quad_enable = NULL;
 		break;
 
-	case BFPT_DWORD15_QER_SR2_BIT1_BUGGY:
+	case BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR:
 		/*
 		 * Writing only one byte to the Status Register has the
 		 * side-effect of clearing Status Register 2.
diff --git a/drivers/mtd/spi-nor/sfdp.h b/drivers/mtd/spi-nor/sfdp.h
index f74a0eb339ea..d976c6c9a4ad 100644
--- a/drivers/mtd/spi-nor/sfdp.h
+++ b/drivers/mtd/spi-nor/sfdp.h
@@ -84,7 +84,7 @@ struct sfdp_bfpt {
  */
 #define BFPT_DWORD15_QER_MASK			GENMASK(22, 20)
 #define BFPT_DWORD15_QER_NONE			(0x0UL << 20) /* Micron */
-#define BFPT_DWORD15_QER_SR2_BIT1_BUGGY		(0x1UL << 20)
+#define BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR	(0x1UL << 20)
 #define BFPT_DWORD15_QER_SR1_BIT6		(0x2UL << 20) /* Macronix */
 #define BFPT_DWORD15_QER_SR2_BIT7		(0x3UL << 20)
 #define BFPT_DWORD15_QER_SR2_BIT1_NO_RD		(0x4UL << 20)

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:02 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:02 +0200
Subject: [PATCH v3 02/23] mtd: spi-nor: Create a structure containing the
 flash opcodes
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-2-b637cf120d5c@bootlin.com>

Create a structure named opcodes in the flash parameters structures to
store all the flash specific opcodes. They are typically discovered
dynamically through SFDP parsing. Move the die_erase opcode
inside. Later on we will add new fields there, like the supported
opcodes for accessing the status registers, which are almost vendor
specific.

No fonctional change.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/core.c      |  6 +++---
 drivers/mtd/spi-nor/core.h      | 14 +++++++++++---
 drivers/mtd/spi-nor/debugfs.c   |  2 +-
 drivers/mtd/spi-nor/micron-st.c |  4 ++--
 drivers/mtd/spi-nor/spansion.c  |  4 ++--
 5 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index fef7f426a3a2..29b8fe3d1c65 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -1140,7 +1140,7 @@ static int spi_nor_erase_die(struct spi_nor *nor, loff_t addr, size_t die_size)
 
 	if (nor->spimem) {
 		struct spi_mem_op op =
-			SPI_NOR_DIE_ERASE_OP(nor->params->die_erase_opcode,
+			SPI_NOR_DIE_ERASE_OP(nor->params->opcodes.die_erase,
 					     nor->addr_nbytes, addr, multi_die);
 
 		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
@@ -3030,8 +3030,8 @@ static int spi_nor_late_init_params(struct spi_nor *nor)
 			return ret;
 	}
 
-	if (!nor->params->die_erase_opcode)
-		nor->params->die_erase_opcode = SPINOR_OP_CHIP_ERASE;
+	if (!nor->params->opcodes.die_erase)
+		nor->params->opcodes.die_erase = SPINOR_OP_CHIP_ERASE;
 
 	/* Default method kept for backward compatibility. */
 	if (!params->set_4byte_addr_mode)
diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
index ba2d1a862c9d..2cc0c80da3fa 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -340,6 +340,16 @@ struct spi_nor_otp {
 	const struct spi_nor_otp_ops *ops;
 };
 
+/**
+ * struct spi_nor_opcodes - SPI NOR flash specific opcodes.
+ * List of variable opcodes used by the chip.
+ *
+ * @die_erase: opcode for erasing a die, defaults to SPINOR_OP_CHIP_ERASE
+ */
+struct spi_nor_opcodes {
+	u8 die_erase;
+};
+
 /**
  * struct spi_nor_flash_parameter - SPI NOR flash parameters and settings.
  * Includes legacy flash parameters and settings that can be overwritten
@@ -362,7 +372,6 @@ struct spi_nor_otp {
  *			command in octal DTR mode.
  * @n_banks:		number of banks.
  * @n_dice:		number of dice in the flash memory.
- * @die_erase_opcode:	die erase opcode. Defaults to SPINOR_OP_CHIP_ERASE.
  * @vreg_offset:	volatile register offset for each die.
  * @hwcaps:		describes the read and page program hardware
  *			capabilities.
@@ -393,13 +402,12 @@ struct spi_nor_flash_parameter {
 	u8				rdsr_addr_nbytes;
 	u8				n_banks;
 	u8				n_dice;
-	u8				die_erase_opcode;
 	u32				*vreg_offset;
 
 	struct spi_nor_hwcaps		hwcaps;
 	struct spi_nor_read_command	reads[SNOR_CMD_READ_MAX];
 	struct spi_nor_pp_command	page_programs[SNOR_CMD_PP_MAX];
-
+	struct spi_nor_opcodes		opcodes;
 	struct spi_nor_erase_map        erase_map;
 	struct spi_nor_otp		otp;
 
diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index 288e2866daed..ef6f0ffcd6bb 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -146,7 +146,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
 
 	if (!(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
 		string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf));
-		seq_printf(s, " %02x (%s)\n", params->die_erase_opcode, buf);
+		seq_printf(s, " %02x (%s)\n", params->opcodes.die_erase, buf);
 	}
 
 	seq_puts(s, "\nsector map\n");
diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c
index c75b0a1cd567..031cf887318c 100644
--- a/drivers/mtd/spi-nor/micron-st.c
+++ b/drivers/mtd/spi-nor/micron-st.c
@@ -131,7 +131,7 @@ static int micron_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->opcodes.die_erase = SPINOR_OP_MT_DIE_ERASE;
 	params->n_dice = 4;
 
 	/*
@@ -147,7 +147,7 @@ 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->opcodes.die_erase = SPINOR_OP_MT_DIE_ERASE;
 	params->n_dice = 2;
 
 	/*
diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c
index 15dcec09332a..2a8238c707e1 100644
--- a/drivers/mtd/spi-nor/spansion.c
+++ b/drivers/mtd/spi-nor/spansion.c
@@ -675,7 +675,7 @@ static int s25hx_t_late_init(struct spi_nor *nor)
 	cypress_nor_ecc_init(nor);
 
 	if (params->n_dice > 1)
-		params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE;
+		params->opcodes.die_erase = SPINOR_OP_CYPRESS_DIE_ERASE;
 
 	return 0;
 }
@@ -763,7 +763,7 @@ static int s28hx_t_late_init(struct spi_nor *nor)
 	cypress_nor_ecc_init(nor);
 
 	if (params->n_dice > 1)
-		params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE;
+		params->opcodes.die_erase = SPINOR_OP_CYPRESS_DIE_ERASE;
 
 	return 0;
 }

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:03 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:03 +0200
Subject: [PATCH v3 03/23] mtd: spi-nor: Refactor Read Status/Write Status
 support
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-3-b637cf120d5c@bootlin.com>

SPI NOR has a lot of history. Additions over additions, the subtleties
of the JESD216 specification, their implementations by the manufacturers
and the hardware mistakes have generated a gigantic maze, let's try to
understand what is really needed.

The specification explains the QE (Quad Enable) bitfield as describing
where the bit to enable the Quad capability is, but also how to set
it. Unfortunately, the specification is not precise about what opcodes
are supported exactly in all the cases. There is an introduction that
basically states:
- Opcode 0x05 reads SR1
- Opcode 0x35 reads SR2
- Opcode 0x01 writes SR1, and then SR2 if another byte is written

Then the bitfield, among indicating the location of the QE bit, may
indicate:
- Reading SR1 and SR2 in one operation is not possible (loops over the
  content of SR1)
- Reading SR2 directly is possible
- Only writing SR1 zeroes SR2.

One problem with the current implementation, is that it only focuses on
the QE bit. A quad_enable function was created for each case, even
though in practice, the logic was always the same: read, modify, write,
read back and verify. One problem comes when other features need to play
with the status registers, like software block protection or OTP: you
never know how to properly handle the QE bit, nor where it is, nor how
to read/write the Status registers. This lead to
approximations/guessing (in swp.c, otp.c and obviously in legacy
controller drivers like atmel.c) but also to the implementation of a
gazillon of helpers for reading/writing/checking the status registers.

In addition, I believe some design decisions had a negative impact over
the years.

- All possible situations had to be flagged by the core. This is likely
  wrong, because we no longer know why we need specific quirks. It was
  ineherent to the state of the SPI NOR core before the great cleanup
  that had happened the past few years. I believe this creates confusion
  in the core today, and we should push this to vendor fixups
  instead. As an example, in 2023 Hsin-Yi was facing an issue because
  his chip was setting the wrong QER value, leading to RDCR being
  prevented, thus falling into a condition blindly setting a random QE
  bit (I strongy believe it is done like that for wrong reasons). His
  chip actually had RDCR support! The correct fix should have been to
  mark the capability in a device fixup instead of handling this in the
  core.

  Link: https://lore.kernel.org/lkml/CAJMQK-hR0eaO0b4Vd0U8_KAndLyZapqdHjVLAoe42rWi9rdLkA at mail.gmail.com/

- SFDP parsing is over cautious. I believe
  BFPT_DWORD15_QER_SR2_BIT1_NO_RD is abusive (nothing states that RDCR is
  not supported), and BFPT_DWORD15_QER_SR2_BIT1 is also out of
  specification when forcing 16-bit Status writes.

- SNOR_F_HAS_16BIT_SR is only imposing 16bit Status writes, whereas
  reads can still be 8-bit wide.

- The usage of helpers verifying the writes was also spread for IMHO no
  really good reason. Why shouldn't we trust spi operations when it
  comes to Status Registers? We do not read back our page reads, so why
  status registers should be treated with so much care, if it's not
  because we are unsure of what is being done? My proposal includes a
  check when it comes to the QE bit (done once) but we don't need these
  checks otherwise. If the QE bit was written properly, there are high
  chances that the other register accesses will just be fine, no?

Asde from my main quest, I also observed no good reason to ask the
read/write status register callers to use nor->bouncebuf while the
low-level helpers could do it themselves (we are talking about one or
two bytes being copied).

So after these observations, my proposal is the following:
- Create private low level helpers that just read or write a status
  register. They are flexible, we can give the opcode (which varies
  based on the SFDP QER field) and the length (1 or 2).
- Create public generic accessors which will be used to read/write sr1
  and/or sr2. This is where all the cleverness shall be. The helpers use
  the available opcodes for a given chip in order to fullfill the
  request.
- Provide a single generic ->quad_enable() hook which generically does
  all the steps mentioned above (read, modify, write, read back and
  verify).
- Create a list of opcodes for all 6 possible situations:
  {read, write} {sr1, sr2, sr1 and sr2}. These opcodes are
  filled/cleared based on the QER field. An opcode set to 0 indicates
  the absence of support (there is no 0x00 opcode in SPI NOR).

I tried my best to analyze the current behavior and to mimic it as much
as possible, but this is a risky cleanup. However, if we go for this, it
will be *much* easier in the future to handle all kind of chip
variations. We won't be limited to a couple of flags anymore, but rather
we'll be able to just disable a read or write capability using a single
line.

Known deviations:
- The Atmel manufacturer driver drives non SFDP chips which by default
  were receiving the HAS_16B_WR flag, forcing SR2 writes all the times,
  but it was deliberately making single bytes writes to SR1 for its
  locking operations. The flag is likely wrong for them, so I removed
  it.
- I am proposing on purpose a single quad_enable helper. It should match
  all the cases, with 1 identified difference which I believe is
  harmless: in the SR2_BIT7 case, the helper would make an extra SR1
  read/write which was not done before. This entry explicitly supports
  reading and writing the two registers. Note: no manufacturer has been
  identified to actually use that entry yet.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
This is a really hard to review patch, it is big, full of changes
everywhere, but I cannot really split it further. The commit log is
really important, it tries to explain the steps I went through when
trying to refactor the whole QE/SR handling for which I received
feedback from Michael, Tudor and Sashiko.

Ideally we could expect test coverage of each case:
- BFPT_DWORD15_QER_NONE: Probably not needed
- BFPT_DWORD15_QER_SR2_BIT1_BUGGY: Expected from Hsin-Yi (flag is being
  renamed _NO_1B_WR)
- BFPT_DWORD15_QER_SR2_BIT1_NO_RD: Tested with a chip re-enabling RDCR
  in a fixup
- BFPT_DWORD15_QER_SR1_BIT6: TBD
- BFPT_DWORD15_QER_SR2_BIT7: TBD
- BFPT_DWORD15_QER_SR2_BIT1: TBD
- BFPT_DWORD15_QER_SR2_BIT1_1B: Done.
There will probably be breakages on older chips. These cannot be
guessed because they are not properly listed in manufacturer
fixups (yet). If we want a cleanup/simplification, we will have to cope
with this risk.

I hope the diff stats will motivate people to have a look and report
their testing.

I will also eagerly monitor Sashiko's output which will probably be
very useful to catch niche weird cases where these changes might break.
---
 Documentation/driver-api/mtd/spi-nor.rst |   2 +-
 drivers/mtd/spi-nor/atmel.c              |  52 +-
 drivers/mtd/spi-nor/core.c               | 784 +++++++++++--------------------
 drivers/mtd/spi-nor/core.h               |  57 +--
 drivers/mtd/spi-nor/debugfs.c            |   2 -
 drivers/mtd/spi-nor/gigadevice.c         |   6 +-
 drivers/mtd/spi-nor/issi.c               |   3 +-
 drivers/mtd/spi-nor/macronix.c           |  12 +-
 drivers/mtd/spi-nor/micron-st.c          |   4 +-
 drivers/mtd/spi-nor/otp.c                |  16 +-
 drivers/mtd/spi-nor/sfdp.c               |  41 +-
 drivers/mtd/spi-nor/spansion.c           |  16 +-
 drivers/mtd/spi-nor/sst.c                |   5 +-
 drivers/mtd/spi-nor/swp.c                |  58 +--
 drivers/mtd/spi-nor/winbond.c            |   4 +-
 include/linux/mtd/spi-nor.h              |   4 -
 16 files changed, 416 insertions(+), 650 deletions(-)

diff --git a/Documentation/driver-api/mtd/spi-nor.rst b/Documentation/driver-api/mtd/spi-nor.rst
index 747a326fb6c0..5441fa7d4f04 100644
--- a/Documentation/driver-api/mtd/spi-nor.rst
+++ b/Documentation/driver-api/mtd/spi-nor.rst
@@ -118,7 +118,7 @@ section, after the ``---`` marker.
     write size		1
     page size		256
     address nbytes	3
-    flags		HAS_LOCK | HAS_16BIT_SR | SOFT_RESET | SWP_IS_VOLATILE
+    flags		HAS_LOCK | SOFT_RESET | SWP_IS_VOLATILE
 
     opcodes
      read		0xeb
diff --git a/drivers/mtd/spi-nor/atmel.c b/drivers/mtd/spi-nor/atmel.c
index 82c592f0a1e1..8741c0bda497 100644
--- a/drivers/mtd/spi-nor/atmel.c
+++ b/drivers/mtd/spi-nor/atmel.c
@@ -23,18 +23,28 @@ static int at25fs_nor_lock(struct spi_nor *nor, loff_t ofs, u64 len)
 
 static int at25fs_nor_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
 {
+	/* Write 0x00 to the status register to disable write protection */
+	u8 sr = 0;
 	int ret;
 
 	/* We only support unlocking the whole flash array */
 	if (ofs || len != nor->params->size)
 		return -EINVAL;
 
-	/* Write 0x00 to the status register to disable write protection */
-	ret = spi_nor_write_sr_and_check(nor, 0);
+	ret = spi_nor_write_sr1(nor, &sr);
 	if (ret)
-		dev_dbg(nor->dev, "unable to clear BP bits, WP# asserted?\n");
+		return ret;
 
-	return ret;
+	ret = spi_nor_read_sr1(nor, &sr);
+	if (ret)
+		return ret;
+
+	if (sr) {
+		dev_dbg(nor->dev, "unable to clear BP bits, WP# asserted?\n");
+		return -EIO;
+	}
+
+	return 0;
 }
 
 static int at25fs_nor_is_locked(struct spi_nor *nor, loff_t ofs, u64 len)
@@ -50,6 +60,7 @@ static const struct spi_nor_locking_ops at25fs_nor_locking_ops = {
 
 static int at25fs_nor_late_init(struct spi_nor *nor)
 {
+	nor->params->opcodes.write_sr1 = SPINOR_OP_WRSR;
 	nor->params->locking_ops = &at25fs_nor_locking_ops;
 
 	return 0;
@@ -69,6 +80,7 @@ static const struct spi_nor_fixups at25fs_nor_fixups = {
  * Return: 0 on success, -error otherwise.
  */
 static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs,
+
 					   u64 len, bool is_protect)
 {
 	int ret;
@@ -78,19 +90,24 @@ static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs,
 	if (ofs || len != nor->params->size)
 		return -EINVAL;
 
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr1(nor, &sr);
 	if (ret)
 		return ret;
 
-	sr = nor->bouncebuf[0];
-
 	/* SRWD bit needs to be cleared, otherwise the protection doesn't change */
 	if (sr & SR_SRWD) {
 		sr &= ~SR_SRWD;
-		ret = spi_nor_write_sr_and_check(nor, sr);
-		if (ret) {
-			dev_dbg(nor->dev, "unable to clear SRWD bit, WP# asserted?\n");
+		ret = spi_nor_write_sr1(nor, &sr);
+		if (ret)
 			return ret;
+
+		ret = spi_nor_read_sr1(nor, &sr);
+		if (ret)
+			return ret;
+
+		if (sr & SR_SRWD) {
+			dev_dbg(nor->dev, "unable to clear SRWD bit, WP# asserted?\n");
+			return -EIO;
 		}
 	}
 
@@ -108,14 +125,7 @@ static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs,
 		sr &= ~ATMEL_SR_GLOBAL_PROTECT_MASK;
 	}
 
-	nor->bouncebuf[0] = sr;
-
-	/*
-	 * We cannot use the spi_nor_write_sr_and_check() because this command
-	 * isn't really setting any bits, instead it is an pseudo command for
-	 * "Global Unprotect" or "Global Protect"
-	 */
-	return spi_nor_write_sr(nor, nor->bouncebuf, 1);
+	return spi_nor_write_sr1(nor, &sr);
 }
 
 static int atmel_nor_global_protect(struct spi_nor *nor, loff_t ofs, u64 len)
@@ -131,16 +141,17 @@ static int atmel_nor_global_unprotect(struct spi_nor *nor, loff_t ofs, u64 len)
 static int atmel_nor_is_global_protected(struct spi_nor *nor, loff_t ofs,
 					 u64 len)
 {
+	u8 sr;
 	int ret;
 
 	if (ofs >= nor->params->size || (ofs + len) > nor->params->size)
 		return -EINVAL;
 
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr1(nor, &sr);
 	if (ret)
 		return ret;
 
-	return ((nor->bouncebuf[0] & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK);
+	return ((sr & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK);
 }
 
 static const struct spi_nor_locking_ops atmel_nor_global_protection_ops = {
@@ -151,6 +162,7 @@ static const struct spi_nor_locking_ops atmel_nor_global_protection_ops = {
 
 static int atmel_nor_global_protection_late_init(struct spi_nor *nor)
 {
+	nor->params->opcodes.write_sr1 = SPINOR_OP_WRSR;
 	nor->params->locking_ops = &atmel_nor_global_protection_ops;
 
 	return 0;
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 29b8fe3d1c65..ac7812562047 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -443,75 +443,6 @@ int spi_nor_read_id(struct spi_nor *nor, u8 naddr, u8 ndummy, u8 *id,
 	return ret;
 }
 
-/**
- * spi_nor_read_sr() - Read the Status Register.
- * @nor:	pointer to 'struct spi_nor'.
- * @sr:		pointer to a DMA-able buffer where the value of the
- *              Status Register will be written. Should be at least 2 bytes.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_read_sr(struct spi_nor *nor, u8 *sr)
-{
-	int ret;
-
-	if (nor->spimem) {
-		struct spi_mem_op op = SPI_NOR_RDSR_OP(sr);
-
-		if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) {
-			op.addr.nbytes = nor->params->rdsr_addr_nbytes;
-			op.dummy.nbytes = nor->params->rdsr_dummy;
-			/*
-			 * We don't want to read only one byte in DTR mode. So,
-			 * read 2 and then discard the second byte.
-			 */
-			op.data.nbytes = 2;
-		}
-
-		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
-
-		ret = spi_mem_exec_op(nor->spimem, &op);
-	} else {
-		ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDSR, sr,
-						      1);
-	}
-
-	if (ret)
-		dev_dbg(nor->dev, "error %d reading SR\n", ret);
-
-	return ret;
-}
-
-/**
- * spi_nor_read_cr() - Read the Configuration Register using the
- * SPINOR_OP_RDCR (35h) command.
- * @nor:	pointer to 'struct spi_nor'
- * @cr:		pointer to a DMA-able buffer where the value of the
- *              Configuration Register will be written.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_read_cr(struct spi_nor *nor, u8 *cr)
-{
-	int ret;
-
-	if (nor->spimem) {
-		struct spi_mem_op op = SPI_NOR_RDCR_OP(cr);
-
-		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
-
-		ret = spi_mem_exec_op(nor->spimem, &op);
-	} else {
-		ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDCR, cr,
-						      1);
-	}
-
-	if (ret)
-		dev_dbg(nor->dev, "error %d reading CR\n", ret);
-
-	return ret;
-}
-
 /**
  * spi_nor_set_4byte_addr_mode_en4b_ex4b() - Enter/Exit 4-byte address mode
  *			using SPINOR_OP_EN4B/SPINOR_OP_EX4B. Typically used by
@@ -617,12 +548,13 @@ int spi_nor_set_4byte_addr_mode_brwr(struct spi_nor *nor, bool enable)
 int spi_nor_sr_ready(struct spi_nor *nor)
 {
 	int ret;
+	u8 sr;
 
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr1(nor, &sr);
 	if (ret)
 		return ret;
 
-	return !(nor->bouncebuf[0] & SR_WIP);
+	return !(sr & SR_WIP);
 }
 
 /**
@@ -784,345 +716,297 @@ int spi_nor_global_block_unlock(struct spi_nor *nor)
 }
 
 /**
- * spi_nor_write_sr() - Write the Status Register.
+ * spi_nor_read_sr_ll() - Low-level Status Registers read.
  * @nor:	pointer to 'struct spi_nor'.
- * @sr:		pointer to DMA-able buffer to write to the Status Register.
- * @len:	number of bytes to write to the Status Register.
+ * @opcode:	opcode for the status register read operation.
+ * @sr:		pointer to buffer for storing the content of the status registers.
+ * @len:	number of status registers to read (1 or 2).
  *
  * Return: 0 on success, -errno otherwise.
  */
-int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len)
+static int spi_nor_read_sr_ll(struct spi_nor *nor, u8 opcode, u8 *sr,
+			      unsigned int len)
 {
-	int ret;
+	int ret, i;
 
-	ret = spi_nor_write_enable(nor);
-	if (ret)
-		return ret;
+	if (len > 2)
+		return -EINVAL;
+
+	for (i = 0; i < len; i++)
+		nor->bouncebuf[i] = 0;
 
 	if (nor->spimem) {
-		struct spi_mem_op op = SPI_NOR_WRSR_OP(sr, len);
+		struct spi_mem_op op = SPI_NOR_RDSR_OP(opcode, nor->bouncebuf, len);
+
+		if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) {
+			op.addr.nbytes = nor->params->rdsr_addr_nbytes;
+			op.dummy.nbytes = nor->params->rdsr_dummy;
+			/*
+			 * We don't want to read only one byte in DTR mode. So,
+			 * read 2 and then discard the second byte.
+			 */
+			op.data.nbytes = 2;
+		}
 
 		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
 
 		ret = spi_mem_exec_op(nor->spimem, &op);
 	} else {
-		ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WRSR, sr,
-						       len);
+		ret = spi_nor_controller_ops_read_reg(nor, opcode, nor->bouncebuf, len);
 	}
 
-	if (ret) {
-		dev_dbg(nor->dev, "error %d writing SR\n", ret);
-		return ret;
-	}
-
-	return spi_nor_wait_till_ready(nor);
-}
-
-/**
- * spi_nor_write_sr1_and_check() - Write one byte to the Status Register 1 and
- * ensure that the byte written match the received value.
- * @nor:	pointer to a 'struct spi_nor'.
- * @sr1:	byte value to be written to the Status Register.
- *
- * Return: 0 on success, -errno otherwise.
- */
-static int spi_nor_write_sr1_and_check(struct spi_nor *nor, u8 sr1)
-{
-	int ret;
-
-	nor->bouncebuf[0] = sr1;
-
-	ret = spi_nor_write_sr(nor, nor->bouncebuf, 1);
-	if (ret)
-		return ret;
-
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
-	if (ret)
-		return ret;
-
-	if (nor->bouncebuf[0] != sr1) {
-		dev_dbg(nor->dev, "SR1: read back test failed\n");
-		return -EIO;
-	}
-
-	return 0;
-}
-
-/**
- * spi_nor_write_16bit_sr_and_check() - Write the Status Register 1 and the
- * Status Register 2 in one shot. Ensure that the byte written in the Status
- * Register 1 match the received value, and that the 16-bit Write did not
- * affect what was already in the Status Register 2.
- * @nor:	pointer to a 'struct spi_nor'.
- * @sr1:	byte value to be written to the Status Register 1.
- *
- * Return: 0 on success, -errno otherwise.
- */
-static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1)
-{
-	int ret;
-	u8 *sr_cr = nor->bouncebuf;
-	u8 cr_written;
-
-	/* Make sure we don't overwrite the contents of Status Register 2. */
-	if (!(nor->flags & SNOR_F_NO_READ_CR)) {
-		ret = spi_nor_read_cr(nor, &sr_cr[1]);
-		if (ret)
-			return ret;
-	} else if ((spi_nor_get_protocol_width(nor->read_proto) == 4 ||
-		    spi_nor_get_protocol_width(nor->write_proto) == 4) &&
-		   nor->params->quad_enable) {
-		/*
-		 * If the Status Register 2 Read command (35h) is not
-		 * supported, we should at least be sure we don't
-		 * change the value of the SR2 Quad Enable bit.
-		 *
-		 * When the Quad Enable method is set and the buswidth is 4, we
-		 * can safely assume that the value of the QE bit is one, as a
-		 * consequence of the nor->params->quad_enable() call.
-		 *
-		 * According to the JESD216 revB standard, BFPT DWORDS[15],
-		 * bits 22:20, the 16-bit Write Status (01h) command is
-		 * available just for the cases in which the QE bit is
-		 * described in SR2 at BIT(1).
-		 */
-		sr_cr[1] = SR2_QUAD_EN_BIT1;
-	} else {
-		sr_cr[1] = 0;
-	}
-
-	sr_cr[0] = sr1;
-
-	ret = spi_nor_write_sr(nor, sr_cr, 2);
-	if (ret)
-		return ret;
-
-	ret = spi_nor_read_sr(nor, sr_cr);
-	if (ret)
-		return ret;
-
-	if (sr1 != sr_cr[0]) {
-		dev_dbg(nor->dev, "SR: Read back test failed\n");
-		return -EIO;
-	}
-
-	if (nor->flags & SNOR_F_NO_READ_CR)
-		return 0;
-
-	cr_written = sr_cr[1];
-
-	ret = spi_nor_read_cr(nor, &sr_cr[1]);
-	if (ret)
-		return ret;
-
-	if (cr_written != sr_cr[1]) {
-		dev_dbg(nor->dev, "CR: read back test failed\n");
-		return -EIO;
-	}
-
-	return 0;
-}
-
-/**
- * spi_nor_write_16bit_cr_and_check() - Write the Status Register 1 and the
- * Configuration Register in one shot. Ensure that the byte written in the
- * Configuration Register match the received value, and that the 16-bit Write
- * did not affect what was already in the Status Register 1.
- * @nor:	pointer to a 'struct spi_nor'.
- * @cr:		byte value to be written to the Configuration Register.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr)
-{
-	int ret;
-	u8 *sr_cr = nor->bouncebuf;
-	u8 sr_written;
-
-	/* Keep the current value of the Status Register 1. */
-	ret = spi_nor_read_sr(nor, sr_cr);
-	if (ret)
-		return ret;
-
-	sr_cr[1] = cr;
-
-	ret = spi_nor_write_sr(nor, sr_cr, 2);
-	if (ret)
-		return ret;
-
-	sr_written = sr_cr[0];
-
-	ret = spi_nor_read_sr(nor, sr_cr);
-	if (ret)
-		return ret;
-
-	if (sr_written != sr_cr[0]) {
-		dev_dbg(nor->dev, "SR: Read back test failed\n");
-		return -EIO;
-	}
-
-	if (nor->flags & SNOR_F_NO_READ_CR)
-		return 0;
-
-	ret = spi_nor_read_cr(nor, &sr_cr[1]);
-	if (ret)
-		return ret;
-
-	if (cr != sr_cr[1]) {
-		dev_dbg(nor->dev, "CR: read back test failed\n");
-		return -EIO;
-	}
-
-	return 0;
-}
-
-/**
- * spi_nor_write_16bit_sr_cr_and_check() - Write the Status Register 1 and the
- * Configuration Register in one shot. Ensure that the bytes written in both
- * registers match the received value.
- * @nor:	pointer to a 'struct spi_nor'.
- * @regs:	two-byte array with values to be written to the status and
- *		configuration registers.
- *
- * Return: 0 on success, -errno otherwise.
- */
-static int spi_nor_write_16bit_sr_cr_and_check(struct spi_nor *nor, const u8 *regs)
-{
-	u8 written_regs[2];
-	int ret;
-
-	written_regs[0] = regs[0];
-	written_regs[1] = regs[1];
-	nor->bouncebuf[0] = regs[0];
-	nor->bouncebuf[1] = regs[1];
-
-	ret = spi_nor_write_sr(nor, nor->bouncebuf, 2);
-	if (ret)
-		return ret;
-
-	ret = spi_nor_read_sr(nor, &nor->bouncebuf[0]);
-	if (ret)
-		return ret;
-
-	if (written_regs[0] != nor->bouncebuf[0]) {
-		dev_dbg(nor->dev, "SR: Read back test failed\n");
-		return -EIO;
-	}
-
-	if (nor->flags & SNOR_F_NO_READ_CR)
-		return 0;
-
-	ret = spi_nor_read_cr(nor, &nor->bouncebuf[1]);
-	if (ret)
-		return ret;
-
-	if (written_regs[1] != nor->bouncebuf[1]) {
-		dev_dbg(nor->dev, "CR: read back test failed\n");
-		return -EIO;
-	}
-
-	return 0;
-}
-
-/**
- * spi_nor_write_sr_and_check() - Write the Status Register 1 and ensure that
- * the byte written match the received value without affecting other bits in the
- * Status Register 1 and 2.
- * @nor:	pointer to a 'struct spi_nor'.
- * @sr1:	byte value to be written to the Status Register.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1)
-{
-	if (nor->flags & SNOR_F_HAS_16BIT_SR)
-		return spi_nor_write_16bit_sr_and_check(nor, sr1);
-
-	return spi_nor_write_sr1_and_check(nor, sr1);
-}
-
-/**
- * spi_nor_write_sr_cr_and_check() - Write the Status Register 1 and ensure that
- * the byte written match the received value. Same for the Control Register if
- * available.
- * @nor:	pointer to a 'struct spi_nor'.
- * @regs:	byte array to be written to the registers.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs)
-{
-	if (nor->flags & SNOR_F_HAS_16BIT_SR)
-		return spi_nor_write_16bit_sr_cr_and_check(nor, regs);
-
-	return spi_nor_write_sr1_and_check(nor, regs[0]);
-}
-
-/**
- * spi_nor_write_sr2() - Write the Status Register 2 using the
- * SPINOR_OP_WRSR2 (3eh) command.
- * @nor:	pointer to 'struct spi_nor'.
- * @sr2:	pointer to DMA-able buffer to write to the Status Register 2.
- *
- * Return: 0 on success, -errno otherwise.
- */
-static int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2)
-{
-	int ret;
-
-	ret = spi_nor_write_enable(nor);
-	if (ret)
-		return ret;
-
-	if (nor->spimem) {
-		struct spi_mem_op op = SPI_NOR_WRSR2_OP(sr2);
-
-		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
-
-		ret = spi_mem_exec_op(nor->spimem, &op);
-	} else {
-		ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WRSR2,
-						       sr2, 1);
-	}
-
-	if (ret) {
-		dev_dbg(nor->dev, "error %d writing SR2\n", ret);
-		return ret;
-	}
-
-	return spi_nor_wait_till_ready(nor);
-}
-
-/**
- * spi_nor_read_sr2() - Read the Status Register 2 using the
- * SPINOR_OP_RDSR2 (3fh) command.
- * @nor:	pointer to 'struct spi_nor'.
- * @sr2:	pointer to DMA-able buffer where the value of the
- *		Status Register 2 will be written.
- *
- * Return: 0 on success, -errno otherwise.
- */
-static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2)
-{
-	int ret;
-
-	if (nor->spimem) {
-		struct spi_mem_op op = SPI_NOR_RDSR2_OP(sr2);
-
-		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
-
-		ret = spi_mem_exec_op(nor->spimem, &op);
-	} else {
-		ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDSR2, sr2,
-						      1);
-	}
+	memcpy(sr, nor->bouncebuf, len);
 
 	if (ret)
-		dev_dbg(nor->dev, "error %d reading SR2\n", ret);
+		dev_dbg(nor->dev, "Error %d reading Status Registers\n", ret);
 
 	return ret;
 }
 
+/**
+ * spi_nor_write_sr_ll() - Low-level Status Registers write.
+ * @nor:	pointer to 'struct spi_nor'.
+ * @opcode:	opcode for the status register write operation.
+ * @sr:		pointer to status registers buffer to write.
+ * @len:	number of status registers to write.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_write_sr_ll(struct spi_nor *nor, u8 opcode,	const u8 *sr,
+			       unsigned int len)
+{
+	int ret, i;
+
+	if (len > 2)
+		return -EINVAL;
+
+	ret = spi_nor_write_enable(nor);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < len; i++)
+		nor->bouncebuf[i] = sr[i];
+
+	if (nor->spimem) {
+		struct spi_mem_op op = SPI_NOR_WRSR_OP(opcode,
+						       nor->bouncebuf, len);
+
+		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
+
+		ret = spi_mem_exec_op(nor->spimem, &op);
+	} else {
+		ret = spi_nor_controller_ops_write_reg(nor, opcode,
+						       nor->bouncebuf, len);
+	}
+
+	if (ret) {
+		dev_dbg(nor->dev, "Error %d writing Status Registers\n", ret);
+		return ret;
+	}
+
+	return spi_nor_wait_till_ready(nor);
+}
+
+/**
+ * spi_nor_read_sr1() - Read SR1 only
+ * Useful for:
+ *   - The core to offer a generic "read SR1 and SR2" capability
+ *   - Manufacturer drivers (since they know the chip SR layout)
+ *   - Polling the BUSY bit in the core
+ *
+ * @nor: the spi_nor structure
+ * @sr1: pointer to a valid SR1 buffer
+ *
+ * Return 0 or errno.
+ */
+int spi_nor_read_sr1(struct spi_nor *nor, u8 *sr1)
+{
+	return spi_nor_read_sr_ll(nor, nor->params->opcodes.read_sr1, sr1, 1);
+}
+
+/**
+ * spi_nor_read_sr2() - Read SR2 only
+ * Useful for:
+ *   - The core to offer a generic "read SR1 and SR2" capability
+ *   - Manufacturer drivers (since they know the chip SR layout)
+ *   - SR2 based OTP configuration
+ *
+ * @nor: the spi_nor structure
+ * @sr2: pointer to a valid SR2 buffer
+ *
+ * Return 0 or errno.
+ */
+int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2)
+{
+	struct spi_nor_flash_parameter *params = nor->params;
+	int ret;
+
+	if (params->opcodes.read_sr2) {
+		ret = spi_nor_read_sr_ll(nor, params->opcodes.read_sr2, sr2, 1);
+		if (ret)
+			return ret;
+	} else if (spi_nor_get_protocol_width(nor->read_proto) == 4 ||
+		   spi_nor_get_protocol_width(nor->write_proto) == 4) {
+		/* Make sure the QE bit is persistently kept */
+		*sr2 = params->qe_mask[1];
+	} else {
+		*sr2 = 0;
+	}
+
+	return 0;
+}
+
+/**
+ * spi_nor_read_sr1_and_sr2() - Read SR1 then SR2
+ * General purpose helper, always safe to call.
+ *
+ * @nor: the spi_nor structure
+ * @sr: pointer to a valid 2-byte array
+ *
+ * Return 0 or errno.
+ */
+int spi_nor_read_sr1_and_sr2(struct spi_nor *nor, u8 *sr)
+{
+	int ret;
+
+	ret = spi_nor_read_sr1(nor, &sr[0]);
+	if (ret)
+		return ret;
+
+	return spi_nor_read_sr2(nor, &sr[1]);
+}
+
+/**
+ * spi_nor_write_sr1() - Write SR1 only
+ * Useful for:
+ *   - Manufacturer drivers (since they know the chip SR layout)
+ *
+ * @nor: the spi_nor structure
+ * @sr1: pointer to a valid SR1 buffer
+ *
+ * Return 0 or errno.
+ */
+int spi_nor_write_sr1(struct spi_nor *nor, const u8 *sr1)
+{
+	if (WARN_ONCE(!nor->params->opcodes.write_sr1,
+		      "Restricted helper use, write SR1 not supported"))
+		return -EIO;
+
+	return spi_nor_write_sr_ll(nor, nor->params->opcodes.write_sr1, sr1, 1);
+}
+
+/**
+ * spi_nor_write_sr2() - Write SR2 only
+ * Useful for:
+ *   - Manufacturer drivers (since they know the chip SR layout)
+ *   - SR2 based OTP configuration
+ *
+ * @nor: the spi_nor structure
+ * @sr2: pointer to a valid SR2 buffer
+ *
+ * Return 0 or errno.
+ */
+int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2)
+{
+	if (WARN_ONCE(!nor->params->opcodes.write_sr2,
+		      "Restricted helper use, write SR2 not supported"))
+		return -EIO;
+
+	return spi_nor_write_sr_ll(nor, nor->params->opcodes.write_sr2, sr2, 1);
+}
+
+/**
+ * spi_nor_write_sr1_and_sr2() - Write SR1 then SR2
+ * General purpose helper, always safe to call. Will expectedly ignore
+ * SR2 on certain chips.
+ *
+ * @nor: the spi_nor structure
+ * @sr: pointer to a valid 2-byte array
+ *
+ * Return 0 or errno.
+ */
+static int spi_nor_write_sr1_and_sr2(struct spi_nor *nor, const u8 *sr)
+{
+	struct spi_nor_flash_parameter *params = nor->params;
+	int ret;
+
+	if (params->opcodes.write_sr1_and_sr2)
+		return spi_nor_write_sr_ll(nor,
+					   params->opcodes.write_sr1_and_sr2,
+					   sr, 2);
+
+	ret = spi_nor_write_sr1(nor, &sr[0]);
+	if (ret)
+		return ret;
+
+	if (params->opcodes.write_sr2)
+		return spi_nor_write_sr2(nor, &sr[1]);
+
+	return 0;
+}
+
+/**
+ * spi_nor_write_sr1_and_sr2_and_check() - Write SR1 then SR2, then read
+ *					   them back and verifies
+ * General purpose helper, always safe to call. Will expectedly ignore
+ * SR2 on certain chips.
+ *
+ * @nor: the spi_nor structure
+ * @sr: pointer to a valid 2-byte array
+ *
+ * Return 0 or errno.
+ */
+int spi_nor_write_sr1_and_sr2_and_check(struct spi_nor *nor, const u8 *sr)
+{
+	u8 tmp[2];
+	int ret;
+
+	ret = spi_nor_write_sr1_and_sr2(nor, sr);
+	if (ret)
+		return ret;
+
+	ret = spi_nor_read_sr1_and_sr2(nor, tmp);
+	if (ret)
+		return ret;
+
+	if (sr[0] != tmp[0] || sr[1] != tmp[1])
+		return -EIO;
+
+	return 0;
+}
+
+/**
+ * spi_nor_generic_quad_enable() - Read the status registers,
+ *				   apply the QE bit mask,
+ *				   write the status registers,
+ *				   read them back and check the content.
+ *
+ * @nor:	pointer to 'struct spi_nor'
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_generic_quad_enable(struct spi_nor *nor)
+{
+	u8 *qe_mask = nor->params->qe_mask;
+	u8 sr[2] = {};
+	int ret;
+
+	if (!qe_mask[0] && !qe_mask[1])
+		return 0;
+
+	ret = spi_nor_read_sr1_and_sr2(nor, sr);
+	if (ret)
+		return ret;
+
+	if (sr[0] & qe_mask[0] || sr[1] & qe_mask[1])
+		return 0;
+
+	sr[0] |= qe_mask[0];
+	sr[1] |= qe_mask[1];
+
+	return spi_nor_write_sr1_and_sr2_and_check(nor, sr);
+}
+
 /**
  * spi_nor_erase_die() - Erase the entire die.
  * @nor:	pointer to 'struct spi_nor'.
@@ -1904,106 +1788,6 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
 	return ret;
 }
 
-/**
- * spi_nor_sr1_bit6_quad_enable() - Set the Quad Enable BIT(6) in the Status
- * Register 1.
- * @nor:	pointer to a 'struct spi_nor'
- *
- * Bit 6 of the Status Register 1 is the QE bit for Macronix like QSPI memories.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor)
-{
-	int ret;
-
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
-	if (ret)
-		return ret;
-
-	if (nor->bouncebuf[0] & SR1_QUAD_EN_BIT6)
-		return 0;
-
-	nor->bouncebuf[0] |= SR1_QUAD_EN_BIT6;
-
-	return spi_nor_write_sr1_and_check(nor, nor->bouncebuf[0]);
-}
-
-/**
- * spi_nor_sr2_bit1_quad_enable() - set the Quad Enable BIT(1) in the Status
- * Register 2.
- * @nor:       pointer to a 'struct spi_nor'.
- *
- * Bit 1 of the Status Register 2 is the QE bit for Spansion like QSPI memories.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor)
-{
-	int ret;
-
-	if (nor->flags & SNOR_F_NO_READ_CR)
-		return spi_nor_write_16bit_cr_and_check(nor, SR2_QUAD_EN_BIT1);
-
-	ret = spi_nor_read_cr(nor, nor->bouncebuf);
-	if (ret)
-		return ret;
-
-	if (nor->bouncebuf[0] & SR2_QUAD_EN_BIT1)
-		return 0;
-
-	nor->bouncebuf[0] |= SR2_QUAD_EN_BIT1;
-
-	return spi_nor_write_16bit_cr_and_check(nor, nor->bouncebuf[0]);
-}
-
-/**
- * spi_nor_sr2_bit7_quad_enable() - set QE bit in Status Register 2.
- * @nor:	pointer to a 'struct spi_nor'
- *
- * Set the Quad Enable (QE) bit in the Status Register 2.
- *
- * This is one of the procedures to set the QE bit described in the SFDP
- * (JESD216 rev B) specification but no manufacturer using this procedure has
- * been identified yet, hence the name of the function.
- *
- * Return: 0 on success, -errno otherwise.
- */
-int spi_nor_sr2_bit7_quad_enable(struct spi_nor *nor)
-{
-	u8 *sr2 = nor->bouncebuf;
-	int ret;
-	u8 sr2_written;
-
-	/* Check current Quad Enable bit value. */
-	ret = spi_nor_read_sr2(nor, sr2);
-	if (ret)
-		return ret;
-	if (*sr2 & SR2_QUAD_EN_BIT7)
-		return 0;
-
-	/* Update the Quad Enable bit. */
-	*sr2 |= SR2_QUAD_EN_BIT7;
-
-	ret = spi_nor_write_sr2(nor, sr2);
-	if (ret)
-		return ret;
-
-	sr2_written = *sr2;
-
-	/* Read back and check it. */
-	ret = spi_nor_read_sr2(nor, sr2);
-	if (ret)
-		return ret;
-
-	if (*sr2 != sr2_written) {
-		dev_dbg(nor->dev, "SR2: Read back test failed\n");
-		return -EIO;
-	}
-
-	return 0;
-}
-
 static const struct spi_nor_manufacturer *manufacturers[] = {
 	&spi_nor_atmel,
 	&spi_nor_eon,
@@ -2494,6 +2278,7 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps)
 {
 	struct spi_nor_flash_parameter *params = nor->params;
 	unsigned int cap;
+	u8 opcode;
 
 	/* X-X-X modes are not supported yet, mask them all. */
 	*hwcaps &= ~SNOR_HWCAPS_X_X_X;
@@ -2525,14 +2310,14 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps)
 			*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);
-
+	/* Some SPI controllers might not support reading SR2 */
+	opcode = nor->params->opcodes.read_sr2;
+	if (opcode) {
+		struct spi_mem_op op = SPI_NOR_RDSR_OP(opcode, nor->bouncebuf, 1);
 		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
 
 		if (!spi_mem_supports_op(nor->spimem, &op))
-			nor->flags |= SNOR_F_NO_READ_CR;
+			nor->params->opcodes.read_sr2 = 0;
 	}
 }
 
@@ -3107,11 +2892,14 @@ static void spi_nor_init_default_params(struct spi_nor *nor)
 	const struct flash_info *info = nor->info;
 	struct device_node *np = spi_nor_get_flash_node(nor);
 
-	params->quad_enable = spi_nor_sr2_bit1_quad_enable;
-	params->otp.org = info->otp;
+	/* Default to 16-bit Read/Write Status commands */
+	params->opcodes.read_sr1 = SPINOR_OP_RDSR;
+	params->opcodes.read_sr2 = SPINOR_OP_RDCR;
+	params->opcodes.write_sr1_and_sr2 = SPINOR_OP_WRSR;
+	params->quad_enable = spi_nor_generic_quad_enable;
+	params->qe_mask[1] = BIT(1);
 
-	/* Default to 16-bit Write Status (01h) Command */
-	nor->flags |= SNOR_F_HAS_16BIT_SR;
+	params->otp.org = info->otp;
 
 	/* Set SPI NOR sizes. */
 	params->writesize = 1;
diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
index 2cc0c80da3fa..7d43d3ba47ee 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -37,36 +37,18 @@
 		   SPI_MEM_OP_NO_DUMMY,					\
 		   SPI_MEM_OP_NO_DATA)
 
-#define SPI_NOR_RDSR_OP(buf)						\
-	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 0),			\
+#define SPI_NOR_RDSR_OP(opcode, buf, len)				\
+	SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0),				\
 		   SPI_MEM_OP_NO_ADDR,					\
 		   SPI_MEM_OP_NO_DUMMY,					\
-		   SPI_MEM_OP_DATA_IN(1, buf, 0))
+		   SPI_MEM_OP_DATA_IN(len, buf, 0))
 
-#define SPI_NOR_WRSR_OP(buf, len)					\
-	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 0),			\
+#define SPI_NOR_WRSR_OP(opcode, buf, len)				\
+	SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0),				\
 		   SPI_MEM_OP_NO_ADDR,					\
 		   SPI_MEM_OP_NO_DUMMY,					\
 		   SPI_MEM_OP_DATA_OUT(len, buf, 0))
 
-#define SPI_NOR_RDSR2_OP(buf)						\
-	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 0),			\
-		   SPI_MEM_OP_NO_ADDR,					\
-		   SPI_MEM_OP_NO_DUMMY,					\
-		   SPI_MEM_OP_DATA_OUT(1, buf, 0))
-
-#define SPI_NOR_WRSR2_OP(buf)						\
-	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 0),			\
-		   SPI_MEM_OP_NO_ADDR,					\
-		   SPI_MEM_OP_NO_DUMMY,					\
-		   SPI_MEM_OP_DATA_OUT(1, buf, 0))
-
-#define SPI_NOR_RDCR_OP(buf)						\
-	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 0),			\
-		   SPI_MEM_OP_NO_ADDR,					\
-		   SPI_MEM_OP_NO_DUMMY,					\
-		   SPI_MEM_OP_DATA_IN(1, buf, 0))
-
 #define SPI_NOR_EN4B_EX4B_OP(enable)					\
 	SPI_MEM_OP(SPI_MEM_OP_CMD(enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B, 0),	\
 		   SPI_MEM_OP_NO_ADDR,					\
@@ -129,8 +111,6 @@ enum spi_nor_option_flags {
 	SNOR_F_4B_OPCODES	= BIT(3),
 	SNOR_F_HAS_4BAIT	= BIT(4),
 	SNOR_F_HAS_LOCK		= BIT(5),
-	SNOR_F_HAS_16BIT_SR	= BIT(6),
-	SNOR_F_NO_READ_CR	= BIT(7),
 	SNOR_F_HAS_SR_TB_BIT6	= BIT(8),
 	SNOR_F_HAS_4BIT_BP      = BIT(9),
 	SNOR_F_HAS_SR_BP3_BIT6  = BIT(10),
@@ -345,9 +325,19 @@ struct spi_nor_otp {
  * List of variable opcodes used by the chip.
  *
  * @die_erase: opcode for erasing a die, defaults to SPINOR_OP_CHIP_ERASE
+ * @read_sr1: opcode for reading SR1 alone
+ * @read_sr2: opcode for reading SR2 alone
+ * @write_sr1: opcode for writing SR1 alone
+ * @write_sr2: opcode for writing SR2 alone
+ * @write_sr1_and_sr2: opcode for writing SR1 then SR2 in one operation
  */
 struct spi_nor_opcodes {
 	u8 die_erase;
+	u8 read_sr1;
+	u8 read_sr2;
+	u8 write_sr1;
+	u8 write_sr2;
+	u8 write_sr1_and_sr2;
 };
 
 /**
@@ -384,6 +374,7 @@ struct spi_nor_opcodes {
  * @otp:		SPI NOR OTP info.
  * @set_octal_dtr:	enables or disables SPI NOR octal DTR mode.
  * @quad_enable:	enables SPI NOR quad mode.
+ * @qe_mask:		two bytes mask used to set/clear the QE bit
  * @set_4byte_addr_mode: puts the SPI NOR in 4 byte addressing mode.
  * @ready:		(optional) flashes might use a different mechanism
  *			than reading the status register to indicate they
@@ -413,6 +404,7 @@ struct spi_nor_flash_parameter {
 
 	int (*set_octal_dtr)(struct spi_nor *nor, bool enable);
 	int (*quad_enable)(struct spi_nor *nor);
+	u8				qe_mask[2];
 	int (*set_4byte_addr_mode)(struct spi_nor *nor, bool enable);
 	int (*ready)(struct spi_nor *nor);
 
@@ -641,18 +633,15 @@ int spi_nor_wait_till_ready(struct spi_nor *nor);
 int spi_nor_global_block_unlock(struct spi_nor *nor);
 int spi_nor_prep_and_lock(struct spi_nor *nor);
 void spi_nor_unlock_and_unprep(struct spi_nor *nor);
-int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor);
-int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor);
-int spi_nor_sr2_bit7_quad_enable(struct spi_nor *nor);
 int spi_nor_read_id(struct spi_nor *nor, u8 naddr, u8 ndummy, u8 *id,
 		    enum spi_nor_protocol reg_proto);
-int spi_nor_read_sr(struct spi_nor *nor, u8 *sr);
 int spi_nor_sr_ready(struct spi_nor *nor);
-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_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_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs);
+int spi_nor_read_sr1(struct spi_nor *nor, u8 *sr1);
+int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2);
+int spi_nor_read_sr1_and_sr2(struct spi_nor *nor, u8 *sr);
+int spi_nor_write_sr1(struct spi_nor *nor, const u8 *sr1);
+int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2);
+int spi_nor_write_sr1_and_sr2_and_check(struct spi_nor *nor, const u8 *sr);
 
 ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
 			  u8 *buf);
diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index ef6f0ffcd6bb..512825c3fc85 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -19,8 +19,6 @@ static const char *const snor_f_names[] = {
 	SNOR_F_NAME(4B_OPCODES),
 	SNOR_F_NAME(HAS_4BAIT),
 	SNOR_F_NAME(HAS_LOCK),
-	SNOR_F_NAME(HAS_16BIT_SR),
-	SNOR_F_NAME(NO_READ_CR),
 	SNOR_F_NAME(HAS_SR_TB_BIT6),
 	SNOR_F_NAME(HAS_4BIT_BP),
 	SNOR_F_NAME(HAS_SR_BP3_BIT6),
diff --git a/drivers/mtd/spi-nor/gigadevice.c b/drivers/mtd/spi-nor/gigadevice.c
index ef1edd0add70..4070a692e968 100644
--- a/drivers/mtd/spi-nor/gigadevice.c
+++ b/drivers/mtd/spi-nor/gigadevice.c
@@ -23,8 +23,10 @@ gd25q256_post_bfpt(struct spi_nor *nor,
 	 *      GD25Q256E      | SFDP_JESD216_MAJOR | SFDP_JESD216B_MINOR
 	 */
 	if (bfpt_header->major == SFDP_JESD216_MAJOR &&
-	    bfpt_header->minor == SFDP_JESD216_MINOR)
-		nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
+	    bfpt_header->minor == SFDP_JESD216_MINOR) {
+		nor->params->qe_mask[0] = BIT(6);
+		nor->params->qe_mask[1] = 0;
+	}
 
 	return 0;
 }
diff --git a/drivers/mtd/spi-nor/issi.c b/drivers/mtd/spi-nor/issi.c
index 18d9a00aa22e..04db42b5141e 100644
--- a/drivers/mtd/spi-nor/issi.c
+++ b/drivers/mtd/spi-nor/issi.c
@@ -131,7 +131,8 @@ static const struct flash_info issi_nor_parts[] = {
 
 static void issi_nor_default_init(struct spi_nor *nor)
 {
-	nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
+	nor->params->qe_mask[0] = BIT(6);
+	nor->params->qe_mask[1] = 0;
 }
 
 static const struct spi_nor_fixups issi_fixups = {
diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c
index e97f5cbd9aad..55612237c1ef 100644
--- a/drivers/mtd/spi-nor/macronix.c
+++ b/drivers/mtd/spi-nor/macronix.c
@@ -65,11 +65,12 @@ mx25l3255e_late_init_fixups(struct spi_nor *nor)
 
 	/*
 	 * SFDP of MX25L3255E is JESD216, which does not include the Quad
-	 * Enable bit Requirement in BFPT. As a result, during BFPT parsing,
-	 * the quad_enable method is not set to spi_nor_sr1_bit6_quad_enable.
-	 * Therefore, it is necessary to correct this setting by late_init.
+	 * Enable bit Requirement in BFPT. As a result, during BFPT parsing
+	 * the quad_enable mask is reset. Therefore, it is necessary to
+	 * correct this setting by late_init.
 	 */
-	params->quad_enable = spi_nor_sr1_bit6_quad_enable;
+	params->qe_mask[0] = BIT(6);
+	params->qe_mask[1] = 0;
 
 	/*
 	 * In addition, MX25L3255E also supports 1-4-4 page program in 3-byte
@@ -314,7 +315,8 @@ static int macronix_nor_set_octal_dtr(struct spi_nor *nor, bool enable)
 
 static void macronix_nor_default_init(struct spi_nor *nor)
 {
-	nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
+	nor->params->qe_mask[0] = BIT(6);
+	nor->params->qe_mask[1] = 0;
 }
 
 static int macronix_nor_late_init(struct spi_nor *nor)
diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c
index 031cf887318c..344539d2a36a 100644
--- a/drivers/mtd/spi-nor/micron-st.c
+++ b/drivers/mtd/spi-nor/micron-st.c
@@ -229,7 +229,7 @@ static int mt25qu512a_post_bfpt_fixup(struct spi_nor *nor,
 				      const struct sfdp_parameter_header *bfpt_header,
 				      const struct sfdp_bfpt *bfpt)
 {
-	nor->flags &= ~SNOR_F_HAS_16BIT_SR;
+	nor->params->opcodes.write_sr1 = 0;
 	return 0;
 }
 
@@ -633,7 +633,7 @@ static int micron_st_nor_ready(struct spi_nor *nor)
 static void micron_st_nor_default_init(struct spi_nor *nor)
 {
 	nor->flags |= SNOR_F_HAS_LOCK;
-	nor->flags &= ~SNOR_F_HAS_16BIT_SR;
+	nor->params->opcodes.write_sr1 = 0;
 	nor->params->quad_enable = NULL;
 }
 
diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c
index 7d0b145d78d8..1eeb33a20091 100644
--- a/drivers/mtd/spi-nor/otp.c
+++ b/drivers/mtd/spi-nor/otp.c
@@ -175,24 +175,24 @@ static int spi_nor_otp_lock_bit_cr(unsigned int region)
  */
 int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region)
 {
-	u8 *cr = nor->bouncebuf;
 	int ret, lock_bit;
+	u8 sr[2];
 
 	lock_bit = spi_nor_otp_lock_bit_cr(region);
 	if (lock_bit < 0)
 		return lock_bit;
 
-	ret = spi_nor_read_cr(nor, cr);
+	ret = spi_nor_read_sr1_and_sr2(nor, sr);
 	if (ret)
 		return ret;
 
 	/* no need to write the register if region is already locked */
-	if (cr[0] & lock_bit)
+	if (sr[1] & lock_bit)
 		return 0;
 
-	cr[0] |= lock_bit;
+	sr[1] |= lock_bit;
 
-	return spi_nor_write_16bit_cr_and_check(nor, cr[0]);
+	return spi_nor_write_sr1_and_sr2_and_check(nor, sr);
 }
 
 /**
@@ -207,18 +207,18 @@ 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)
 {
-	u8 *cr = nor->bouncebuf;
 	int ret, lock_bit;
+	u8 sr2;
 
 	lock_bit = spi_nor_otp_lock_bit_cr(region);
 	if (lock_bit < 0)
 		return lock_bit;
 
-	ret = spi_nor_read_cr(nor, cr);
+	ret = spi_nor_read_sr2(nor, &sr2);
 	if (ret)
 		return ret;
 
-	return cr[0] & lock_bit;
+	return sr2 & lock_bit;
 }
 
 static loff_t spi_nor_otp_region_start(const struct spi_nor *nor, unsigned int region)
diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index 526763c79f55..1d799f02b8e5 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -561,10 +561,21 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 	val >>= BFPT_DWORD11_PAGE_SIZE_SHIFT;
 	params->page_size = 1U << val;
 
+	/*
+	 * The standard declares various read and write status methods, some of
+	 * them will be overloaded based on the QER field.
+	 */
+	params->opcodes.read_sr1 = SPINOR_OP_RDSR;
+	params->opcodes.read_sr2 = SPINOR_OP_RDCR;
+	params->opcodes.write_sr1 = SPINOR_OP_WRSR;
+	params->opcodes.write_sr1_and_sr2 = SPINOR_OP_WRSR;
+
+	params->qe_mask[0] = 0;
+	params->qe_mask[1] = 0;
+
 	/* Quad Enable Requirements. */
 	switch (bfpt.dwords[SFDP_DWORD(15)] & BFPT_DWORD15_QER_MASK) {
 	case BFPT_DWORD15_QER_NONE:
-		params->quad_enable = NULL;
 		break;
 
 	case BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR:
@@ -572,23 +583,28 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 		 * Writing only one byte to the Status Register has the
 		 * side-effect of clearing Status Register 2.
 		 */
+		fallthrough;
 	case BFPT_DWORD15_QER_SR2_BIT1_NO_RD:
 		/*
 		 * Read Configuration Register (35h) instruction is not
-		 * supported.
+		 * supported. 16-bit writes expected.
 		 */
-		nor->flags |= SNOR_F_HAS_16BIT_SR | SNOR_F_NO_READ_CR;
-		params->quad_enable = spi_nor_sr2_bit1_quad_enable;
+		params->opcodes.read_sr2 = 0;
+		params->opcodes.write_sr1 = 0;
+		params->qe_mask[1] = BIT(1);
 		break;
 
 	case BFPT_DWORD15_QER_SR1_BIT6:
-		nor->flags &= ~SNOR_F_HAS_16BIT_SR;
-		params->quad_enable = spi_nor_sr1_bit6_quad_enable;
+		params->opcodes.read_sr2 = 0;
+		params->opcodes.write_sr1_and_sr2 = 0;
+		params->qe_mask[0] = BIT(6);
 		break;
 
 	case BFPT_DWORD15_QER_SR2_BIT7:
-		nor->flags &= ~SNOR_F_HAS_16BIT_SR;
-		params->quad_enable = spi_nor_sr2_bit7_quad_enable;
+		params->opcodes.read_sr2 = SPINOR_OP_RDSR2;
+		params->opcodes.write_sr1_and_sr2 = 0;
+		params->opcodes.write_sr2 = SPINOR_OP_WRSR2;
+		params->qe_mask[1] = BIT(7);
 		break;
 
 	case BFPT_DWORD15_QER_SR2_BIT1:
@@ -598,9 +614,8 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 		 * Register 2, so let's be cautious and keep the default
 		 * assumption of a 16-bit Write Status (01h) command.
 		 */
-		nor->flags |= SNOR_F_HAS_16BIT_SR;
-
-		params->quad_enable = spi_nor_sr2_bit1_quad_enable;
+		params->opcodes.write_sr1 = 0;
+		params->qe_mask[1] = BIT(1);
 		break;
 
 	default:
@@ -608,6 +623,10 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 		break;
 	}
 
+	/* opcodes sanity check */
+	WARN_ON(!params->opcodes.read_sr1 ||
+		(!params->opcodes.write_sr1 && !params->opcodes.write_sr1_and_sr2));
+
 	dword = bfpt.dwords[SFDP_DWORD(16)] & BFPT_DWORD16_4B_ADDR_MODE_MASK;
 	if (SFDP_MASK_CHECK(dword, BFPT_DWORD16_4B_ADDR_MODE_BRWR))
 		params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode_brwr;
diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c
index 2a8238c707e1..ca2703a5a338 100644
--- a/drivers/mtd/spi-nor/spansion.c
+++ b/drivers/mtd/spi-nor/spansion.c
@@ -399,8 +399,9 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor,
 					  nor->bouncebuf);
 	bool is3byte, is4byte;
 	int ret;
+	u8 sr;
 
-	ret = spi_nor_read_sr(nor, &nor->bouncebuf[1]);
+	ret = spi_nor_read_sr1(nor, &sr);
 	if (ret)
 		return ret;
 
@@ -408,7 +409,7 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor,
 	if (ret)
 		return ret;
 
-	is3byte = (nor->bouncebuf[0] == nor->bouncebuf[1]);
+	is3byte = (nor->bouncebuf[0] == sr);
 
 	op = (struct spi_mem_op)
 		CYPRESS_NOR_RD_ANY_REG_OP(4, SPINOR_REG_CYPRESS_STR1V, 0,
@@ -417,7 +418,7 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor,
 	if (ret)
 		return ret;
 
-	is4byte = (nor->bouncebuf[0] == nor->bouncebuf[1]);
+	is4byte = (nor->bouncebuf[0] == sr);
 
 	if (is3byte == is4byte)
 		return -EIO;
@@ -1103,13 +1104,14 @@ static const struct flash_info spansion_nor_parts[] = {
 static int spansion_nor_sr_ready_and_clear(struct spi_nor *nor)
 {
 	int ret;
+	u8 sr;
 
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr1(nor, &sr);
 	if (ret)
 		return ret;
 
-	if (nor->bouncebuf[0] & (SR_E_ERR | SR_P_ERR)) {
-		if (nor->bouncebuf[0] & SR_E_ERR)
+	if (sr & (SR_E_ERR | SR_P_ERR)) {
+		if (sr & SR_E_ERR)
 			dev_err(nor->dev, "Erase Error occurred\n");
 		else
 			dev_err(nor->dev, "Programming Error occurred\n");
@@ -1129,7 +1131,7 @@ static int spansion_nor_sr_ready_and_clear(struct spi_nor *nor)
 		return -EIO;
 	}
 
-	return !(nor->bouncebuf[0] & SR_WIP);
+	return !(sr & SR_WIP);
 }
 
 static int spansion_nor_late_init(struct spi_nor *nor)
diff --git a/drivers/mtd/spi-nor/sst.c b/drivers/mtd/spi-nor/sst.c
index db02c14ba16f..77a0bc41d4e5 100644
--- a/drivers/mtd/spi-nor/sst.c
+++ b/drivers/mtd/spi-nor/sst.c
@@ -21,16 +21,17 @@ static int sst26vf_nor_lock(struct spi_nor *nor, loff_t ofs, u64 len)
 static int sst26vf_nor_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
 {
 	int ret;
+	u8 cr;
 
 	/* We only support unlocking the entire flash array. */
 	if (ofs != 0 || len != nor->params->size)
 		return -EINVAL;
 
-	ret = spi_nor_read_cr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr2(nor, &cr);
 	if (ret)
 		return ret;
 
-	if (!(nor->bouncebuf[0] & SST26VF_CR_BPNV)) {
+	if (!(cr & SST26VF_CR_BPNV)) {
 		dev_dbg(nor->dev, "Any block has been permanently locked\n");
 		return -EINVAL;
 	}
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index 235070b215d1..b7b291e15917 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -36,7 +36,7 @@ static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor)
 
 static u8 spi_nor_get_sr_cmp_mask(struct spi_nor *nor)
 {
-	if (!(nor->flags & SNOR_F_NO_READ_CR) &&
+	if (nor->params->opcodes.read_sr2 &&
 	    nor->flags & SNOR_F_HAS_SR2_CMP_BIT6)
 		return SR2_CMP_BIT6;
 	else
@@ -207,17 +207,9 @@ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr)
 
 
 	if (!sr) {
-		if (spi_nor_read_sr(nor, nor->bouncebuf))
+		if (spi_nor_read_sr1_and_sr2(nor, sr_cr))
 			return;
 
-		sr_cr[0] = nor->bouncebuf[0];
-
-		if (!(nor->flags & SNOR_F_NO_READ_CR)) {
-			if (spi_nor_read_cr(nor, nor->bouncebuf))
-				return;
-		}
-
-		sr_cr[1] = nor->bouncebuf[0];
 		sr = sr_cr;
 	}
 
@@ -273,20 +265,10 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
 	int ret;
 	u8 pow;
 
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr1_and_sr2(nor, status_old);
 	if (ret)
 		return ret;
 
-	status_old[0] = nor->bouncebuf[0];
-
-	if (!(nor->flags & SNOR_F_NO_READ_CR)) {
-		ret = spi_nor_read_cr(nor, nor->bouncebuf);
-		if (ret)
-			return ret;
-
-		status_old[1] = 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;
@@ -378,10 +360,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
 	    (ofs_old < ofs_new || (ofs_new + len_new) < (ofs_old + len_old)))
 		return -EINVAL;
 
-	if (nor->flags & SNOR_F_NO_READ_CR)
-		ret = spi_nor_write_sr_and_check(nor, best_status_new[0]);
-	else
-		ret = spi_nor_write_sr_cr_and_check(nor, best_status_new);
+	ret = spi_nor_write_sr1_and_sr2_and_check(nor, best_status_new);
 	if (ret)
 		return ret;
 
@@ -409,20 +388,10 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
 	int ret;
 	u8 pow;
 
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr1_and_sr2(nor, status_old);
 	if (ret)
 		return ret;
 
-	status_old[0] = nor->bouncebuf[0];
-
-	if (!(nor->flags & SNOR_F_NO_READ_CR)) {
-		ret = spi_nor_read_cr(nor, nor->bouncebuf);
-		if (ret)
-			return ret;
-
-		status_old[1] = 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;
@@ -512,10 +481,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
 	    (ofs_new < ofs_old || (ofs_old + len_old) < (ofs_new + len_new)))
 		return -EINVAL;
 
-	if (nor->flags & SNOR_F_NO_READ_CR)
-		ret = spi_nor_write_sr_and_check(nor, best_status_new[0]);
-	else
-		ret = spi_nor_write_sr_cr_and_check(nor, best_status_new);
+	ret = spi_nor_write_sr1_and_sr2_and_check(nor, best_status_new);
 	if (ret)
 		return ret;
 
@@ -536,20 +502,10 @@ static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, u64 len)
 	u8 sr_cr[2] = {};
 	int ret;
 
-	ret = spi_nor_read_sr(nor, nor->bouncebuf);
+	ret = spi_nor_read_sr1_and_sr2(nor, sr_cr);
 	if (ret)
 		return ret;
 
-	sr_cr[0] = nor->bouncebuf[0];
-
-	if (!(nor->flags & SNOR_F_NO_READ_CR)) {
-		ret = spi_nor_read_cr(nor, nor->bouncebuf);
-		if (ret)
-			return ret;
-
-		sr_cr[1] = nor->bouncebuf[0];
-	}
-
 	return spi_nor_is_locked_sr(nor, ofs, len, sr_cr);
 }
 
diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 593429bd6cdf..01a693033448 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -520,12 +520,12 @@ static int winbond_nor_late_init(struct spi_nor *nor)
 	 * been to declare CR reads as unsupported, whereas the Jedec
 	 * specification doesn't clearly state that. In practice, all these
 	 * chips do support reading back the CR, which is needed for SWP support,
-	 * so make sure that capability remains enabled (needed for SWP).
+	 * so make sure that capability remains enabled.
 	 * In practice, only exclude the old W25X family (JEDEC ID: EF 30 xx)
 	 * which actually does not support this feature.
 	 */
 	if (nor->id[1] > 0x30)
-		nor->flags &= ~SNOR_F_NO_READ_CR;
+		params->opcodes.read_sr2 = SPINOR_OP_RDCR;
 
 	/*
 	 * Winbond has reused many IDs, up to four times at this
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 4b92494827b1..f0b5a1989881 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -113,20 +113,16 @@
 #define SR_E_ERR		BIT(5)
 #define SR_P_ERR		BIT(6)
 
-#define SR1_QUAD_EN_BIT6	BIT(6)
-
 #define SR_BP_SHIFT		2
 
 /* Enhanced Volatile Configuration Register bits */
 #define EVCR_QUAD_EN_MICRON	BIT(7)	/* Micron Quad I/O */
 
 /* Status Register 2 bits. */
-#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_CMP_BIT6		BIT(6)
-#define SR2_QUAD_EN_BIT7	BIT(7)
 
 /* Supported SPI protocols */
 #define SNOR_PROTO_INST_MASK	GENMASK(23, 16)

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:04 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:04 +0200
Subject: [PATCH v3 04/23] mtd: spi-nor: Add support for the new JESD216 rev
 F QER field
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-4-b637cf120d5c@bootlin.com>

Revision F of the JEDEC JESD216 standard includes yet another
supported setting in the QER 3-bit bitfield: 110b.

This setting implies that the QE bit is bit 1 of SR2 (which is an
already supported case) but this time it makes it very clear that both
reading and writing to SR2 is supported, but not with the usual
opcodes. The write operation uses an alternate WRSR2 opcode:
0x31 (instead of 0x3E) and both the reads and writes manipulate a single
register at a time.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/sfdp.c  | 6 ++++++
 drivers/mtd/spi-nor/sfdp.h  | 8 ++++++++
 include/linux/mtd/spi-nor.h | 1 +
 3 files changed, 15 insertions(+)

diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index 1d799f02b8e5..285ac781fba8 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -618,6 +618,12 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
 		params->qe_mask[1] = BIT(1);
 		break;
 
+	case BFPT_DWORD15_QER_SR2_BIT1_1B:
+		params->qe_mask[1] = BIT(1);
+		params->opcodes.write_sr1_and_sr2 = 0;
+		params->opcodes.write_sr2 = SPINOR_OP_WRSR2_ALT;
+		break;
+
 	default:
 		dev_dbg(nor->dev, "BFPT QER reserved value used\n");
 		break;
diff --git a/drivers/mtd/spi-nor/sfdp.h b/drivers/mtd/spi-nor/sfdp.h
index d976c6c9a4ad..e8ed814c6530 100644
--- a/drivers/mtd/spi-nor/sfdp.h
+++ b/drivers/mtd/spi-nor/sfdp.h
@@ -12,6 +12,7 @@
 #define SFDP_JESD216_MINOR	0
 #define SFDP_JESD216A_MINOR	5
 #define SFDP_JESD216B_MINOR	6
+#define SFDP_JESD216F_MINOR	10
 
 /* SFDP DWORDS are indexed from 1 but C arrays are indexed from 0. */
 #define SFDP_DWORD(i)		((i) - 1)
@@ -81,6 +82,12 @@ struct sfdp_bfpt {
  *         instruction 35h. QE is set via Write Status instruction 01h with
  *         two data bytes where bit 1 of the second byte is one.
  *         [...]
+ * (from JESD216 rev F)
+ * - 110b: QE is bit 1 of status register 2. Status register 1 is read using
+ *         Read Status instruction 05h. Status register2 is read using
+ *         instruction 35h, and status register 3 is read using instruction 15h.
+ *         QE is set via Write Status Register instruction 31h with one data
+ *         byte.
  */
 #define BFPT_DWORD15_QER_MASK			GENMASK(22, 20)
 #define BFPT_DWORD15_QER_NONE			(0x0UL << 20) /* Micron */
@@ -89,6 +96,7 @@ struct sfdp_bfpt {
 #define BFPT_DWORD15_QER_SR2_BIT7		(0x3UL << 20)
 #define BFPT_DWORD15_QER_SR2_BIT1_NO_RD		(0x4UL << 20)
 #define BFPT_DWORD15_QER_SR2_BIT1		(0x5UL << 20) /* Spansion */
+#define BFPT_DWORD15_QER_SR2_BIT1_1B		(0x6UL << 20) /* Winbond */
 
 #define BFPT_DWORD16_EN4B_MASK			GENMASK(31, 24)
 #define BFPT_DWORD16_EN4B_ALWAYS_4B		BIT(30)
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index f0b5a1989881..3a55d76ea77d 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -25,6 +25,7 @@
 #define SPINOR_OP_WRSR		0x01	/* Write status register 1 */
 #define SPINOR_OP_RDSR2		0x3f	/* Read status register 2 */
 #define SPINOR_OP_WRSR2		0x3e	/* Write status register 2 */
+#define SPINOR_OP_WRSR2_ALT	0x31	/* Write status register 2 (alternative) */
 #define SPINOR_OP_READ		0x03	/* Read data bytes (low frequency) */
 #define SPINOR_OP_READ_FAST	0x0b	/* Read data bytes (high frequency) */
 #define SPINOR_OP_READ_1_1_2	0x3b	/* Read data bytes (Dual Output SPI) */

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:05 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:05 +0200
Subject: [PATCH v3 05/23] mtd: spi-nor: Create the concept of fixup table
 with match function
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-5-b637cf120d5c@bootlin.com>

Manufacturer ID tables increase and fixup() hooks proliferate. Having
one possible structure per chip was fine until the fixups started being
more and more common and needed, to some extend due to ID reuses. Mixing
fixups and chips becomes hard and requires extra helpers to sort which
ones are needed for a given chip, which every time this happens requires
a lot of rework.

Replace the two-level fixup association (a manufacturer wide hook and a
per flash_info hook) with a per-manufacturer list of fixups that can be
looked up by flash ID and/or match function.

The match logic works as follows:
- If there is an ID, it must match
- If there is a match function, it must match (cumulative)
- If there is no identifier (no ID nor any match function), it's a
  catch-all entry typically used for flagging manufacturer fixups.

Suggested-by: Michael Walle <mwalle at kernel.org>
Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/atmel.c      | 23 ++++++-----
 drivers/mtd/spi-nor/core.c       | 82 ++++++++++++++++++++++++++++------------
 drivers/mtd/spi-nor/core.h       | 23 +++++++++--
 drivers/mtd/spi-nor/everspin.c   |  7 +++-
 drivers/mtd/spi-nor/gigadevice.c |  7 +++-
 drivers/mtd/spi-nor/issi.c       | 22 ++++++++---
 drivers/mtd/spi-nor/macronix.c   | 23 ++++++-----
 drivers/mtd/spi-nor/micron-st.c  | 43 +++++++++++++++------
 drivers/mtd/spi-nor/sfdp.c       | 56 ++++++++++++++++++---------
 drivers/mtd/spi-nor/spansion.c   | 41 +++++++++++---------
 drivers/mtd/spi-nor/sst.c        | 11 ++++--
 drivers/mtd/spi-nor/winbond.c    | 17 ++++++---
 12 files changed, 247 insertions(+), 108 deletions(-)

diff --git a/drivers/mtd/spi-nor/atmel.c b/drivers/mtd/spi-nor/atmel.c
index 8741c0bda497..bccc702a2c4a 100644
--- a/drivers/mtd/spi-nor/atmel.c
+++ b/drivers/mtd/spi-nor/atmel.c
@@ -194,47 +194,40 @@ static const struct flash_info atmel_nor_parts[] = {
 		.size = SZ_512K,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
 		.no_sfdp_flags = SECT_4K,
-		.fixups = &atmel_nor_global_protection_fixups,
 	}, {
 		.id = SNOR_ID(0x1f, 0x45, 0x01),
 		.name = "at26df081a",
 		.size = SZ_1M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
 		.no_sfdp_flags = SECT_4K,
-		.fixups = &atmel_nor_global_protection_fixups
 	}, {
 		.id = SNOR_ID(0x1f, 0x46, 0x01),
 		.name = "at26df161a",
 		.size = SZ_2M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
 		.no_sfdp_flags = SECT_4K,
-		.fixups = &atmel_nor_global_protection_fixups
 	}, {
 		.id = SNOR_ID(0x1f, 0x47, 0x00),
 		.name = "at25df321",
 		.size = SZ_4M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
 		.no_sfdp_flags = SECT_4K,
-		.fixups = &atmel_nor_global_protection_fixups
 	}, {
 		.id = SNOR_ID(0x1f, 0x47, 0x01),
 		.name = "at25df321a",
 		.size = SZ_4M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
 		.no_sfdp_flags = SECT_4K,
-		.fixups = &atmel_nor_global_protection_fixups
 	}, {
 		.id = SNOR_ID(0x1f, 0x47, 0x08),
 		.name = "at25ff321a",
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
-		.fixups = &atmel_nor_global_protection_fixups
 	}, {
 		.id = SNOR_ID(0x1f, 0x48, 0x00),
 		.name = "at25df641",
 		.size = SZ_8M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
 		.no_sfdp_flags = SECT_4K,
-		.fixups = &atmel_nor_global_protection_fixups
 	}, {
 		.id = SNOR_ID(0x1f, 0x66, 0x01),
 		.name = "at25fs010",
@@ -242,14 +235,12 @@ static const struct flash_info atmel_nor_parts[] = {
 		.size = SZ_128K,
 		.flags = SPI_NOR_HAS_LOCK,
 		.no_sfdp_flags = SECT_4K,
-		.fixups = &at25fs_nor_fixups
 	}, {
 		.id = SNOR_ID(0x1f, 0x66, 0x04),
 		.name = "at25fs040",
 		.size = SZ_512K,
 		.flags = SPI_NOR_HAS_LOCK,
 		.no_sfdp_flags = SECT_4K,
-		.fixups = &at25fs_nor_fixups
 	}, {
 		.id = SNOR_ID(0x1f, 0x87, 0x01),
 		.size = SZ_4M,
@@ -257,8 +248,22 @@ static const struct flash_info atmel_nor_parts[] = {
 	},
 };
 
+static const struct spi_nor_fixup atmel_fixups[] = {
+	{ .id = SNOR_ID(0x1f, 0x44, 0x01), .fixups = &atmel_nor_global_protection_fixups },
+	{ .id = SNOR_ID(0x1f, 0x45, 0x01), .fixups = &atmel_nor_global_protection_fixups },
+	{ .id = SNOR_ID(0x1f, 0x46, 0x01), .fixups = &atmel_nor_global_protection_fixups },
+	{ .id = SNOR_ID(0x1f, 0x47, 0x00), .fixups = &atmel_nor_global_protection_fixups },
+	{ .id = SNOR_ID(0x1f, 0x47, 0x01), .fixups = &atmel_nor_global_protection_fixups },
+	{ .id = SNOR_ID(0x1f, 0x47, 0x08), .fixups = &atmel_nor_global_protection_fixups },
+	{ .id = SNOR_ID(0x1f, 0x48, 0x00), .fixups = &atmel_nor_global_protection_fixups },
+	{ .id = SNOR_ID(0x1f, 0x66, 0x01), .fixups = &at25fs_nor_fixups },
+	{ .id = SNOR_ID(0x1f, 0x66, 0x04), .fixups = &at25fs_nor_fixups },
+};
+
 const struct spi_nor_manufacturer spi_nor_atmel = {
 	.name = "atmel",
 	.parts = atmel_nor_parts,
 	.nparts = ARRAY_SIZE(atmel_nor_parts),
+	.fixups = atmel_fixups,
+	.nfixups = ARRAY_SIZE(atmel_fixups),
 };
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ac7812562047..3a611283a9e6 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -2367,19 +2367,24 @@ int spi_nor_post_bfpt_fixups(struct spi_nor *nor,
 			     const struct sfdp_parameter_header *bfpt_header,
 			     const struct sfdp_bfpt *bfpt)
 {
+	const struct spi_nor_fixup *fixups;
 	int ret;
+	unsigned int i;
 
-	if (nor->manufacturer && nor->manufacturer->fixups &&
-	    nor->manufacturer->fixups->post_bfpt) {
-		ret = nor->manufacturer->fixups->post_bfpt(nor, bfpt_header,
-							   bfpt);
-		if (ret)
-			return ret;
+	if (!nor->manufacturer || !nor->manufacturer->fixups)
+		return 0;
+
+	fixups = nor->manufacturer->fixups;
+
+	for (i = 0; i < nor->manufacturer->nfixups; i++) {
+		if (fixups[i].fixups->post_bfpt &&
+		    spi_nor_fixup_match(nor, &fixups[i])) {
+			ret = fixups[i].fixups->post_bfpt(nor, bfpt_header, bfpt);
+			if (ret)
+				return ret;
+		}
 	}
 
-	if (nor->info->fixups && nor->info->fixups->post_bfpt)
-		return nor->info->fixups->post_bfpt(nor, bfpt_header, bfpt);
-
 	return 0;
 }
 
@@ -2636,6 +2641,26 @@ static int spi_nor_setup(struct spi_nor *nor,
 	return spi_nor_set_addr_nbytes(nor);
 }
 
+bool spi_nor_fixup_match(const struct spi_nor *nor,
+			 const struct spi_nor_fixup *fixup)
+{
+	const struct spi_nor_id *id = nor->info ? nor->info->id : NULL;
+
+	/* Filter by ID first, if available */
+	if (fixup->id) {
+		if (!id || fixup->id->len > id->len ||
+		    memcmp(id->bytes, fixup->id->bytes, fixup->id->len))
+			return false;
+	}
+
+	/* Further filter with the match callback, if provided */
+	if (fixup->match)
+		return fixup->match(nor);
+
+	/* Either there was an ID and it matched, or it is a catch-all entry */
+	return true;
+}
+
 /**
  * spi_nor_manufacturer_init_params() - Initialize the flash's parameters and
  * settings based on MFR register and ->default_init() hook.
@@ -2643,12 +2668,19 @@ static int spi_nor_setup(struct spi_nor *nor,
  */
 static void spi_nor_manufacturer_init_params(struct spi_nor *nor)
 {
-	if (nor->manufacturer && nor->manufacturer->fixups &&
-	    nor->manufacturer->fixups->default_init)
-		nor->manufacturer->fixups->default_init(nor);
+	const struct spi_nor_fixup *fixups;
+	unsigned int i;
 
-	if (nor->info->fixups && nor->info->fixups->default_init)
-		nor->info->fixups->default_init(nor);
+	if (!nor->manufacturer || !nor->manufacturer->fixups)
+		return;
+
+	fixups = nor->manufacturer->fixups;
+
+	for (i = 0; i < nor->manufacturer->nfixups; i++) {
+		if (fixups[i].fixups->default_init &&
+		    spi_nor_fixup_match(nor, &fixups[i]))
+			fixups[i].fixups->default_init(nor);
+	}
 }
 
 /**
@@ -2797,22 +2829,24 @@ static void spi_nor_init_fixup_flags(struct spi_nor *nor)
 static int spi_nor_late_init_params(struct spi_nor *nor)
 {
 	struct spi_nor_flash_parameter *params = nor->params;
+	const struct spi_nor_fixup *fixups;
+	unsigned int i;
 	int ret;
 
 	/* Needed by some late_init hooks */
 	spi_nor_init_flags(nor);
 
-	if (nor->manufacturer && nor->manufacturer->fixups &&
-	    nor->manufacturer->fixups->late_init) {
-		ret = nor->manufacturer->fixups->late_init(nor);
-		if (ret)
-			return ret;
-	}
+	if (nor->manufacturer && nor->manufacturer->fixups) {
+		fixups = nor->manufacturer->fixups;
 
-	if (nor->info->fixups && nor->info->fixups->late_init) {
-		ret = nor->info->fixups->late_init(nor);
-		if (ret)
-			return ret;
+		for (i = 0; i < nor->manufacturer->nfixups; i++) {
+			if (fixups[i].fixups->late_init &&
+			    spi_nor_fixup_match(nor, &fixups[i])) {
+				ret = fixups[i].fixups->late_init(nor);
+				if (ret)
+					return ret;
+			}
+		}
 	}
 
 	if (!nor->params->opcodes.die_erase)
diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
index 7d43d3ba47ee..b650778a10de 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -445,6 +445,19 @@ struct spi_nor_fixups {
 	int (*late_init)(struct spi_nor *nor);
 };
 
+/**
+ * struct spi_nor_fixup - SPI NOR fixup registration.
+ * @id:		(optional) flash ID this fixup applies to, may only match the
+ *		ID prefix, eg. just the first few bytes to match a whole family
+ * @match:	(optional) custom match function (can be used together with @id)
+ * @fixups:	the fixup hooks to apply when this entry matches
+ */
+struct spi_nor_fixup {
+	const struct spi_nor_id *id;
+	bool (*match)(const struct spi_nor *nor);
+	const struct spi_nor_fixups *fixups;
+};
+
 /**
  * struct spi_nor_id - SPI NOR flash ID.
  *
@@ -560,7 +573,6 @@ struct flash_info {
 	u8 mfr_flags;
 
 	const struct spi_nor_otp_organization *otp;
-	const struct spi_nor_fixups *fixups;
 };
 
 #define SNOR_ID(...)							\
@@ -582,13 +594,16 @@ struct flash_info {
  * @name: manufacturer name
  * @parts: array of parts supported by this manufacturer
  * @nparts: number of entries in the parts array
- * @fixups: hooks called at various points in time during spi_nor_scan()
+ * @fixups: list of fixups, each matched by ID (or a custom match function),
+ *          applied to any part of this manufacturer.
+ * @nfixups: number of entries in the fixups array
  */
 struct spi_nor_manufacturer {
 	const char *name;
 	const struct flash_info *parts;
 	unsigned int nparts;
-	const struct spi_nor_fixups *fixups;
+	const struct spi_nor_fixup *fixups;
+	unsigned int nfixups;
 };
 
 /**
@@ -619,6 +634,8 @@ extern const struct spi_nor_manufacturer spi_nor_xmc;
 
 extern const struct attribute_group *spi_nor_sysfs_groups[];
 
+bool spi_nor_fixup_match(const struct spi_nor *nor,
+			 const struct spi_nor_fixup *fixup);
 void spi_nor_spimem_setup_op(const struct spi_nor *nor,
 			     struct spi_mem_op *op,
 			     const enum spi_nor_protocol proto);
diff --git a/drivers/mtd/spi-nor/everspin.c b/drivers/mtd/spi-nor/everspin.c
index add37104d673..864dffba0e9c 100644
--- a/drivers/mtd/spi-nor/everspin.c
+++ b/drivers/mtd/spi-nor/everspin.c
@@ -44,9 +44,14 @@ static const struct spi_nor_fixups everspin_nor_fixups = {
 	.default_init = everspin_nor_default_init,
 };
 
+static const struct spi_nor_fixup everspin_fixups[] = {
+	{ .fixups = &everspin_nor_fixups },
+};
+
 const struct spi_nor_manufacturer spi_nor_everspin = {
 	.name = "everspin",
 	.parts = everspin_nor_parts,
 	.nparts = ARRAY_SIZE(everspin_nor_parts),
-	.fixups = &everspin_nor_fixups,
+	.fixups = everspin_fixups,
+	.nfixups = ARRAY_SIZE(everspin_fixups),
 };
diff --git a/drivers/mtd/spi-nor/gigadevice.c b/drivers/mtd/spi-nor/gigadevice.c
index 4070a692e968..f76fd0dedd0a 100644
--- a/drivers/mtd/spi-nor/gigadevice.c
+++ b/drivers/mtd/spi-nor/gigadevice.c
@@ -64,7 +64,6 @@ static const struct flash_info gigadevice_nor_parts[] = {
 		.id = SNOR_ID(0xc8, 0x40, 0x19),
 		.name = "gd25q256",
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6,
-		.fixups = &gd25q256_fixups,
 		.fixup_flags = SPI_NOR_4B_OPCODES,
 	}, {
 		.id = SNOR_ID(0xc8, 0x60, 0x16),
@@ -87,8 +86,14 @@ static const struct flash_info gigadevice_nor_parts[] = {
 	},
 };
 
+static const struct spi_nor_fixup gigadevice_fixups[] = {
+	{ .id = SNOR_ID(0xc8, 0x40, 0x19), .fixups = &gd25q256_fixups },
+};
+
 const struct spi_nor_manufacturer spi_nor_gigadevice = {
 	.name = "gigadevice",
 	.parts = gigadevice_nor_parts,
 	.nparts = ARRAY_SIZE(gigadevice_nor_parts),
+	.fixups = gigadevice_fixups,
+	.nfixups = ARRAY_SIZE(gigadevice_fixups),
 };
diff --git a/drivers/mtd/spi-nor/issi.c b/drivers/mtd/spi-nor/issi.c
index 04db42b5141e..4842f8acd074 100644
--- a/drivers/mtd/spi-nor/issi.c
+++ b/drivers/mtd/spi-nor/issi.c
@@ -52,13 +52,11 @@ static const struct flash_info issi_nor_parts[] = {
 		.sector_size = SZ_32K,
 		.size = SZ_64K,
 		.no_sfdp_flags = SECT_4K,
-		.fixups = &pm25lv_nor_fixups
 	}, {
 		.name = "pm25lv010",
 		.sector_size = SZ_32K,
 		.size = SZ_128K,
 		.no_sfdp_flags = SECT_4K,
-		.fixups = &pm25lv_nor_fixups
 	}, {
 		.id = SNOR_ID(0x7f, 0x9d, 0x20),
 		.name = "is25cd512",
@@ -103,7 +101,6 @@ static const struct flash_info issi_nor_parts[] = {
 	}, {
 		.id = SNOR_ID(0x9d, 0x60, 0x19),
 		.name = "is25lp256",
-		.fixups = &is25lp256_fixups,
 		.fixup_flags = SPI_NOR_4B_OPCODES,
 	}, {
 		.id = SNOR_ID(0x9d, 0x70, 0x16),
@@ -124,7 +121,6 @@ static const struct flash_info issi_nor_parts[] = {
 		.id = SNOR_ID(0x9d, 0x70, 0x19),
 		.name = "is25wp256",
 		.flags = SPI_NOR_QUAD_PP,
-		.fixups = &is25lp256_fixups,
 		.fixup_flags = SPI_NOR_4B_OPCODES,
 	}
 };
@@ -139,9 +135,25 @@ static const struct spi_nor_fixups issi_fixups = {
 	.default_init = issi_nor_default_init,
 };
 
+/* PM25LV parts have no JEDEC ID and are likely matched by name */
+static bool issi_pm25lv_match(const struct spi_nor *nor)
+{
+	const char *name = nor->info ? nor->info->name : NULL;
+
+	return name && !strncmp(name, "pm25lv", 6);
+}
+
+static const struct spi_nor_fixup issi_fixup_list[] = {
+	{ .fixups = &issi_fixups },
+	{ .match = issi_pm25lv_match, .fixups = &pm25lv_nor_fixups },
+	{ .id = SNOR_ID(0x9d, 0x60, 0x19), .fixups = &is25lp256_fixups },
+	{ .id = SNOR_ID(0x9d, 0x70, 0x19), .fixups = &is25lp256_fixups },
+};
+
 const struct spi_nor_manufacturer spi_nor_issi = {
 	.name = "issi",
 	.parts = issi_nor_parts,
 	.nparts = ARRAY_SIZE(issi_nor_parts),
-	.fixups = &issi_fixups,
+	.fixups = issi_fixup_list,
+	.nfixups = ARRAY_SIZE(issi_fixup_list),
 };
diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c
index 55612237c1ef..04b431bf5f13 100644
--- a/drivers/mtd/spi-nor/macronix.c
+++ b/drivers/mtd/spi-nor/macronix.c
@@ -137,20 +137,16 @@ static const struct flash_info macronix_nor_parts[] = {
 	}, {
 		/* MX25L25635E, MX25L25645G */
 		.id = SNOR_ID(0xc2, 0x20, 0x19),
-		.fixups = &mx25l25635_fixups
 	}, {
 		/* MX66L51235F */
 		.id = SNOR_ID(0xc2, 0x20, 0x1a),
 		.fixup_flags = SPI_NOR_4B_OPCODES,
-		.fixups = &macronix_qpp4b_fixups,
 	}, {
 		/* MX66L1G45G */
 		.id = SNOR_ID(0xc2, 0x20, 0x1b),
-		.fixups = &macronix_qpp4b_fixups,
 	}, {
 		/* MX66L2G45G */
 		.id = SNOR_ID(0xc2, 0x20, 0x1c),
-		.fixups = &macronix_qpp4b_fixups,
 	}, {
 		.id = SNOR_ID(0xc2, 0x23, 0x14),
 		.name = "mx25v8035f",
@@ -189,15 +185,12 @@ static const struct flash_info macronix_nor_parts[] = {
 	}, {
 		/* MX25U51245G */
 		.id = SNOR_ID(0xc2, 0x25, 0x3a),
-		.fixups = &macronix_qpp4b_fixups,
 	}, {
 		/* MX66U1G45G */
 		.id = SNOR_ID(0xc2, 0x25, 0x3b),
-		.fixups = &macronix_qpp4b_fixups,
 	}, {
 		/* MX66U2G45G */
 		.id = SNOR_ID(0xc2, 0x25, 0x3c),
-		.fixups = &macronix_qpp4b_fixups,
 	}, {
 		.id = SNOR_ID(0xc2, 0x26, 0x18),
 		.name = "mx25l12855e",
@@ -229,7 +222,6 @@ static const struct flash_info macronix_nor_parts[] = {
 	}, {
 		/* MX25L3255E */
 		.id = SNOR_ID(0xc2, 0x9e, 0x16),
-		.fixups = &mx25l3255e_fixups,
 	},
 	/*
 	 * This spares us of adding new flash entries for flashes that can be
@@ -333,9 +325,22 @@ static const struct spi_nor_fixups macronix_nor_fixups = {
 	.late_init = macronix_nor_late_init,
 };
 
+static const struct spi_nor_fixup macronix_fixups[] = {
+	{ .fixups = &macronix_nor_fixups },
+	{ .id = SNOR_ID(0xc2, 0x20, 0x19), .fixups = &mx25l25635_fixups },
+	{ .id = SNOR_ID(0xc2, 0x20, 0x1a), .fixups = &macronix_qpp4b_fixups },
+	{ .id = SNOR_ID(0xc2, 0x20, 0x1b), .fixups = &macronix_qpp4b_fixups },
+	{ .id = SNOR_ID(0xc2, 0x20, 0x1c), .fixups = &macronix_qpp4b_fixups },
+	{ .id = SNOR_ID(0xc2, 0x25, 0x3a), .fixups = &macronix_qpp4b_fixups },
+	{ .id = SNOR_ID(0xc2, 0x25, 0x3b), .fixups = &macronix_qpp4b_fixups },
+	{ .id = SNOR_ID(0xc2, 0x25, 0x3c), .fixups = &macronix_qpp4b_fixups },
+	{ .id = SNOR_ID(0xc2, 0x9e, 0x16), .fixups = &mx25l3255e_fixups },
+};
+
 const struct spi_nor_manufacturer spi_nor_macronix = {
 	.name = "macronix",
 	.parts = macronix_nor_parts,
 	.nparts = ARRAY_SIZE(macronix_nor_parts),
-	.fixups = &macronix_nor_fixups,
+	.fixups = macronix_fixups,
+	.nfixups = ARRAY_SIZE(macronix_fixups),
 };
diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c
index 344539d2a36a..2aabd6d487ba 100644
--- a/drivers/mtd/spi-nor/micron-st.c
+++ b/drivers/mtd/spi-nor/micron-st.c
@@ -206,13 +206,11 @@ static const struct flash_info micron_nor_parts[] = {
 		.id = SNOR_ID(0x2c, 0x5b, 0x1a),
 		.mfr_flags = USE_FSR,
 		.fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE,
-		.fixups = &mt35xu512aba_fixups,
 	}, {
 		/* MT35XU01GBBA */
 		.id = SNOR_ID(0x2c, 0x5b, 0x1b),
 		.mfr_flags = USE_FSR,
 		.fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE,
-		.fixups = &mt35_two_die_fixups,
 	}, {
 		.id = SNOR_ID(0x2c, 0x5b, 0x1c),
 		.name = "mt35xu02g",
@@ -221,7 +219,6 @@ static const struct flash_info micron_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_OCTAL_READ,
 		.mfr_flags = USE_FSR,
 		.fixup_flags = SPI_NOR_4B_OPCODES | SPI_NOR_IO_MODE_EN_VOLATILE,
-		.fixups = &mt35_two_die_fixups,
 	},
 };
 
@@ -233,6 +230,17 @@ static int mt25qu512a_post_bfpt_fixup(struct spi_nor *nor,
 	return 0;
 }
 
+/*
+ * n25q00a parts share the first same 3 ID bytes with mt25qu01g.
+ * In order to not mix the fixups, further filter out using the part name.
+ */
+static bool n25q00a_match(const struct spi_nor *nor)
+{
+	const char *name = nor->info ? nor->info->name : NULL;
+
+	return name && !strncmp(name, "n25q00a", 7);
+}
+
 static const struct spi_nor_fixups mt25qu512a_fixups = {
 	.post_bfpt = mt25qu512a_post_bfpt_fixup,
 };
@@ -426,14 +434,12 @@ static const struct flash_info st_nor_parts[] = {
 			 SPI_NOR_BP3_SR_BIT6,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_QUAD_READ,
 		.mfr_flags = USE_FSR,
-		.fixups = &n25q00_fixups,
 	}, {
 		.id = SNOR_ID(0x20, 0xba, 0x22),
 		.name = "mt25ql02g",
 		.size = SZ_256M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_QUAD_READ,
 		.mfr_flags = USE_FSR,
-		.fixups = &mt25q02_fixups,
 	}, {
 		.id = SNOR_ID(0x20, 0xbb, 0x15),
 		.name = "n25q016a",
@@ -480,7 +486,6 @@ static const struct flash_info st_nor_parts[] = {
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_4BIT_BP |
 			 SPI_NOR_BP3_SR_BIT6,
 		.mfr_flags = USE_FSR,
-		.fixups = &mt25qu512a_fixups,
 	}, {
 		.id = SNOR_ID(0x20, 0xbb, 0x20),
 		.name = "n25q512a",
@@ -493,21 +498,18 @@ static const struct flash_info st_nor_parts[] = {
 		.id = SNOR_ID(0x20, 0xbb, 0x21, 0x10, 0x44, 0x00),
 		.name = "mt25qu01g",
 		.mfr_flags = USE_FSR,
-		.fixups = &mt25q01_fixups,
 	}, {
 		.id = SNOR_ID(0x20, 0xbb, 0x21),
 		.name = "n25q00a",
 		.size = SZ_128M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_QUAD_READ,
 		.mfr_flags = USE_FSR,
-		.fixups = &n25q00_fixups,
 	}, {
 		.id = SNOR_ID(0x20, 0xbb, 0x22),
 		.name = "mt25qu02g",
 		.size = SZ_256M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.mfr_flags = USE_FSR,
-		.fixups = &mt25q02_fixups,
 	}
 };
 
@@ -657,16 +659,35 @@ static const struct spi_nor_fixups micron_st_nor_fixups = {
 	.late_init = micron_st_nor_late_init,
 };
 
+static const struct spi_nor_fixup micron_fixups[] = {
+	{ .fixups = &micron_st_nor_fixups },
+	{ .id = SNOR_ID(0x2c, 0x5b, 0x1a), .fixups = &mt35xu512aba_fixups },
+	{ .id = SNOR_ID(0x2c, 0x5b, 0x1b), .fixups = &mt35_two_die_fixups },
+	{ .id = SNOR_ID(0x2c, 0x5b, 0x1c), .fixups = &mt35_two_die_fixups },
+};
+
+static const struct spi_nor_fixup st_fixups[] = {
+	{ .fixups = &micron_st_nor_fixups },
+	{ .id = SNOR_ID(0x20, 0xba, 0x21), .fixups = &n25q00_fixups },
+	{ .id = SNOR_ID(0x20, 0xba, 0x22), .fixups = &mt25q02_fixups },
+	{ .id = SNOR_ID(0x20, 0xbb, 0x20, 0x10, 0x44, 0x00), .fixups = &mt25qu512a_fixups },
+	{ .id = SNOR_ID(0x20, 0xbb, 0x21, 0x10, 0x44, 0x00), .fixups = &mt25q01_fixups },
+	{ .id = SNOR_ID(0x20, 0xbb, 0x21), .match = n25q00a_match, .fixups = &n25q00_fixups },
+	{ .id = SNOR_ID(0x20, 0xbb, 0x22), .fixups = &mt25q02_fixups },
+};
+
 const struct spi_nor_manufacturer spi_nor_micron = {
 	.name = "micron",
 	.parts = micron_nor_parts,
 	.nparts = ARRAY_SIZE(micron_nor_parts),
-	.fixups = &micron_st_nor_fixups,
+	.fixups = micron_fixups,
+	.nfixups = ARRAY_SIZE(micron_fixups),
 };
 
 const struct spi_nor_manufacturer spi_nor_st = {
 	.name = "st",
 	.parts = st_nor_parts,
 	.nparts = ARRAY_SIZE(st_nor_parts),
-	.fixups = &micron_st_nor_fixups,
+	.fixups = st_fixups,
+	.nfixups = ARRAY_SIZE(st_fixups),
 };
diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index 285ac781fba8..39cb8ac3a037 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -727,12 +727,19 @@ 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);
+	const struct spi_nor_fixup *fixups;
+	unsigned int i;
 
-	if (nor->info->fixups && nor->info->fixups->smpt_read_dummy)
-		nor->info->fixups->smpt_read_dummy(nor, read_dummy);
+	if (!nor->manufacturer || !nor->manufacturer->fixups)
+		return;
+
+	fixups = nor->manufacturer->fixups;
+
+	for (i = 0; i < nor->manufacturer->nfixups; i++) {
+		if (fixups[i].fixups->smpt_read_dummy &&
+		    spi_nor_fixup_match(nor, &fixups[i]))
+			fixups[i].fixups->smpt_read_dummy(nor, read_dummy);
+	}
 }
 
 /**
@@ -757,12 +764,19 @@ static u8 spi_nor_smpt_read_dummy(const struct spi_nor *nor, const u32 settings)
 
 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);
+	const struct spi_nor_fixup *fixups;
+	unsigned int i;
 
-	if (nor->info->fixups && nor->info->fixups->smpt_map_id)
-		nor->info->fixups->smpt_map_id(nor, map_id);
+	if (!nor->manufacturer || !nor->manufacturer->fixups)
+		return;
+
+	fixups = nor->manufacturer->fixups;
+
+	for (i = 0; i < nor->manufacturer->nfixups; i++) {
+		if (fixups[i].fixups->smpt_map_id &&
+		    spi_nor_fixup_match(nor, &fixups[i]))
+			fixups[i].fixups->smpt_map_id(nor, map_id);
+	}
 }
 
 /**
@@ -1406,17 +1420,23 @@ static int spi_nor_parse_sccr_mc(struct spi_nor *nor,
  */
 static int spi_nor_post_sfdp_fixups(struct spi_nor *nor)
 {
+	const struct spi_nor_fixup *fixups;
+	unsigned int i;
 	int ret;
 
-	if (nor->manufacturer && nor->manufacturer->fixups &&
-	    nor->manufacturer->fixups->post_sfdp) {
-		ret = nor->manufacturer->fixups->post_sfdp(nor);
-		if (ret)
-			return ret;
-	}
+	if (!nor->manufacturer || !nor->manufacturer->fixups)
+		return 0;
 
-	if (nor->info->fixups && nor->info->fixups->post_sfdp)
-		return nor->info->fixups->post_sfdp(nor);
+	fixups = nor->manufacturer->fixups;
+
+	for (i = 0; i < nor->manufacturer->nfixups; i++) {
+		if (fixups[i].fixups->post_sfdp &&
+		    spi_nor_fixup_match(nor, &fixups[i])) {
+			ret = fixups[i].fixups->post_sfdp(nor);
+			if (ret)
+				return ret;
+		}
+	}
 
 	return 0;
 }
diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c
index ca2703a5a338..f3862eb1a23a 100644
--- a/drivers/mtd/spi-nor/spansion.c
+++ b/drivers/mtd/spi-nor/spansion.c
@@ -905,7 +905,6 @@ static const struct flash_info spansion_nor_parts[] = {
 		.sector_size = SZ_256K,
 		.no_sfdp_flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.mfr_flags = USE_CLSR,
-		.fixups = &s25fs_s_nor_fixups,
 	}, {
 		.id = SNOR_ID(0x01, 0x20, 0x18, 0x03, 0x00),
 		.name = "s25sl12800",
@@ -941,7 +940,6 @@ static const struct flash_info spansion_nor_parts[] = {
 		.size = SZ_16M,
 		.no_sfdp_flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.mfr_flags = USE_CLSR,
-		.fixups = &s25fs_s_nor_fixups,
 	}, {
 		.id = SNOR_ID(0x01, 0x20, 0x18, 0x4d, 0x01),
 		.name = "s25fl129p1",
@@ -1001,76 +999,61 @@ static const struct flash_info spansion_nor_parts[] = {
 		.id = SNOR_ID(0x34, 0x2a, 0x1a, 0x0f, 0x03, 0x90),
 		.name = "s25hl512t",
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s25hx_t_fixups
 	}, {
 		.id = SNOR_ID(0x34, 0x2a, 0x1b, 0x0f, 0x03, 0x90),
 		.name = "s25hl01gt",
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s25hx_t_fixups
 	}, {
 		.id = SNOR_ID(0x34, 0x2a, 0x1c, 0x0f, 0x00, 0x90),
 		.name = "s25hl02gt",
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s25hx_t_fixups
 	}, {
 		.id = SNOR_ID(0x34, 0x2b, 0x19, 0x0f, 0x08, 0x90),
 		.name = "s25fs256t",
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s25fs256t_fixups
 	}, {
 		.id = SNOR_ID(0x34, 0x2b, 0x1a, 0x0f, 0x03, 0x90),
 		.name = "s25hs512t",
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s25hx_t_fixups
 	}, {
 		.id = SNOR_ID(0x34, 0x2b, 0x1b, 0x0f, 0x03, 0x90),
 		.name = "s25hs01gt",
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s25hx_t_fixups
 	}, {
 		.id = SNOR_ID(0x34, 0x2b, 0x1c, 0x0f, 0x00, 0x90),
 		.name = "s25hs02gt",
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s25hx_t_fixups
 	}, {
 		/* S28HL256T */
 		.id = SNOR_ID(0x34, 0x5a, 0x19),
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s28hx_t_fixups,
 	}, {
 		.id = SNOR_ID(0x34, 0x5a, 0x1a),
 		.name = "s28hl512t",
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s28hx_t_fixups,
 	}, {
 		.id = SNOR_ID(0x34, 0x5a, 0x1b),
 		.name = "s28hl01gt",
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s28hx_t_fixups,
 	}, {
 		/* S28HL02GT */
 		.id = SNOR_ID(0x34, 0x5a, 0x1c),
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s28hx_t_fixups,
 	}, {
 		.id = SNOR_ID(0x34, 0x5b, 0x19),
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s28hx_t_fixups,
 	}, {
 		.id = SNOR_ID(0x34, 0x5b, 0x1a),
 		.name = "s28hs512t",
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s28hx_t_fixups,
 	}, {
 		.id = SNOR_ID(0x34, 0x5b, 0x1b),
 		.name = "s28hs01gt",
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s28hx_t_fixups,
 	}, {
 		.id = SNOR_ID(0x34, 0x5b, 0x1c),
 		.name = "s28hs02gt",
 		.mfr_flags = USE_CLPEF,
-		.fixups = &s28hx_t_fixups,
 	}, {
 		.id = SNOR_ID(0xef, 0x40, 0x13),
 		.name = "s25fl004k",
@@ -1170,9 +1153,31 @@ static const struct spi_nor_fixups spansion_nor_fixups = {
 	.late_init = spansion_nor_late_init,
 };
 
+static const struct spi_nor_fixup spansion_fixups[] = {
+	{ .fixups = &spansion_nor_fixups },
+	{ .id = SNOR_ID(0x01, 0x02, 0x20, 0x4d, 0x00, 0x81), .fixups = &s25fs_s_nor_fixups },
+	{ .id = SNOR_ID(0x01, 0x20, 0x18, 0x4d, 0x01, 0x81), .fixups = &s25fs_s_nor_fixups },
+	{ .id = SNOR_ID(0x34, 0x2a, 0x1a, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups },
+	{ .id = SNOR_ID(0x34, 0x2a, 0x1b, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups },
+	{ .id = SNOR_ID(0x34, 0x2a, 0x1c, 0x0f, 0x00, 0x90), .fixups = &s25hx_t_fixups },
+	{ .id = SNOR_ID(0x34, 0x2b, 0x19, 0x0f, 0x08, 0x90), .fixups = &s25fs256t_fixups },
+	{ .id = SNOR_ID(0x34, 0x2b, 0x1a, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups },
+	{ .id = SNOR_ID(0x34, 0x2b, 0x1b, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups },
+	{ .id = SNOR_ID(0x34, 0x2b, 0x1c, 0x0f, 0x00, 0x90), .fixups = &s25hx_t_fixups },
+	{ .id = SNOR_ID(0x34, 0x5a, 0x19), .fixups = &s28hx_t_fixups },
+	{ .id = SNOR_ID(0x34, 0x5a, 0x1a), .fixups = &s28hx_t_fixups },
+	{ .id = SNOR_ID(0x34, 0x5a, 0x1b), .fixups = &s28hx_t_fixups },
+	{ .id = SNOR_ID(0x34, 0x5a, 0x1c), .fixups = &s28hx_t_fixups },
+	{ .id = SNOR_ID(0x34, 0x5b, 0x19), .fixups = &s28hx_t_fixups },
+	{ .id = SNOR_ID(0x34, 0x5b, 0x1a), .fixups = &s28hx_t_fixups },
+	{ .id = SNOR_ID(0x34, 0x5b, 0x1b), .fixups = &s28hx_t_fixups },
+	{ .id = SNOR_ID(0x34, 0x5b, 0x1c), .fixups = &s28hx_t_fixups },
+};
+
 const struct spi_nor_manufacturer spi_nor_spansion = {
 	.name = "spansion",
 	.parts = spansion_nor_parts,
 	.nparts = ARRAY_SIZE(spansion_nor_parts),
-	.fixups = &spansion_nor_fixups,
+	.fixups = spansion_fixups,
+	.nfixups = ARRAY_SIZE(spansion_fixups),
 };
diff --git a/drivers/mtd/spi-nor/sst.c b/drivers/mtd/spi-nor/sst.c
index 77a0bc41d4e5..c460803b278b 100644
--- a/drivers/mtd/spi-nor/sst.c
+++ b/drivers/mtd/spi-nor/sst.c
@@ -152,14 +152,12 @@ static const struct flash_info sst_nor_parts[] = {
 		.id = SNOR_ID(0xbf, 0x26, 0x42),
 		.name = "sst26vf032b",
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
-		.fixups = &sst26vf_nor_fixups,
 	}, {
 		.id = SNOR_ID(0xbf, 0x26, 0x43),
 		.name = "sst26vf064b",
 		.size = SZ_8M,
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
-		.fixups = &sst26vf_nor_fixups,
 	}, {
 		.id = SNOR_ID(0xbf, 0x26, 0x51),
 		.name = "sst26wf016b",
@@ -278,9 +276,16 @@ static const struct spi_nor_fixups sst_nor_fixups = {
 	.late_init = sst_nor_late_init,
 };
 
+static const struct spi_nor_fixup sst_fixups[] = {
+	{ .fixups = &sst_nor_fixups },
+	{ .id = SNOR_ID(0xbf, 0x26, 0x42), .fixups = &sst26vf_nor_fixups },
+	{ .id = SNOR_ID(0xbf, 0x26, 0x43), .fixups = &sst26vf_nor_fixups },
+};
+
 const struct spi_nor_manufacturer spi_nor_sst = {
 	.name = "sst",
 	.parts = sst_nor_parts,
 	.nparts = ARRAY_SIZE(sst_nor_parts),
-	.fixups = &sst_nor_fixups,
+	.fixups = sst_fixups,
+	.nfixups = ARRAY_SIZE(sst_fixups),
 };
diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 01a693033448..583b1669270f 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -219,7 +219,6 @@ static const struct flash_info winbond_nor_parts[] = {
 		.size = SZ_16M,
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
-		.fixups = &w25q128_fixups,
 	}, {
 		/* W25Q256JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x19),
@@ -228,7 +227,6 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
 			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &w25q256_fixups,
 	}, {
 		/* W25Q512JV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x20),
@@ -242,7 +240,6 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x40, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
 		.id = SNOR_ID(0xef, 0x50, 0x12),
 		.name = "w25q20bw",
@@ -345,13 +342,11 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x70, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
 		/* W25Q02JV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
-		.fixups = &winbond_nor_multi_die_fixups,
 	}, {
 		.id = SNOR_ID(0xef, 0x71, 0x19),
 		.name = "w25m512jv",
@@ -553,9 +548,19 @@ static const struct spi_nor_fixups winbond_nor_fixups = {
 	.late_init = winbond_nor_late_init,
 };
 
+static const struct spi_nor_fixup winbond_fixups[] = {
+	{ .fixups = &winbond_nor_fixups },
+	{ .id = SNOR_ID(0xef, 0x40, 0x18), .fixups = &w25q128_fixups },
+	{ .id = SNOR_ID(0xef, 0x40, 0x19), .fixups = &w25q256_fixups },
+	{ .id = SNOR_ID(0xef, 0x40, 0x21), .fixups = &winbond_nor_multi_die_fixups },
+	{ .id = SNOR_ID(0xef, 0x70, 0x21), .fixups = &winbond_nor_multi_die_fixups },
+	{ .id = SNOR_ID(0xef, 0x70, 0x22), .fixups = &winbond_nor_multi_die_fixups },
+};
+
 const struct spi_nor_manufacturer spi_nor_winbond = {
 	.name = "winbond",
 	.parts = winbond_nor_parts,
 	.nparts = ARRAY_SIZE(winbond_nor_parts),
-	.fixups = &winbond_nor_fixups,
+	.fixups = winbond_fixups,
+	.nfixups = ARRAY_SIZE(winbond_fixups),
 };

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:06 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:06 +0200
Subject: [PATCH v3 06/23] mtd: spi-nor: Create an indirection on the part
 name
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-6-b637cf120d5c@bootlin.com>

We currently print through sysfs and debugfs the name of the part if the
.name field, which is legacy, has been filled in the ID table (otherwise
"(null)" is printed). These IDs had an interest until manufacturers
started re-using more and more extensively the so called unique JEDEC
IDs. At this stage, a name that was tailored for a chip sold in 2016 may
no longer be relevant for a chip sold in 2026. Still showing this name
through the various filesystems may confuse people, removing these names
cannot be done since they are now part of the user ABI (because of
sysfs).

In order to allow overwriting the .name field (which is part of a
read-only structure), let's create a spi-nor pointer which will by
default point to that .name, but which can be cleared in fixup hooks to
make sure the name is hidden on newer parts.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/core.c    | 1 +
 drivers/mtd/spi-nor/debugfs.c | 3 +--
 drivers/mtd/spi-nor/sysfs.c   | 4 ++--
 include/linux/mtd/spi-nor.h   | 1 +
 4 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 3a611283a9e6..cf6eccb4c423 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -3492,6 +3492,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
 		return PTR_ERR(info);
 
 	nor->info = info;
+	nor->partname = info->name;
 
 	mutex_init(&nor->lock);
 
diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index 512825c3fc85..275671222588 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -82,13 +82,12 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
 	struct spi_nor_flash_parameter *params = nor->params;
 	struct spi_nor_erase_map *erase_map = &params->erase_map;
 	struct spi_nor_erase_region *region = erase_map->regions;
-	const struct flash_info *info = nor->info;
 	char buf[16], *str;
 	loff_t lock_start;
 	u64 lock_length;
 	unsigned int i;
 
-	seq_printf(s, "name\t\t%s\n", info->name);
+	seq_printf(s, "name\t\t%s\n", nor->partname);
 	seq_printf(s, "id\t\t%*ph\n", SPI_NOR_MAX_ID_LEN, nor->id);
 	string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf));
 	seq_printf(s, "size\t\t%s\n", buf);
diff --git a/drivers/mtd/spi-nor/sysfs.c b/drivers/mtd/spi-nor/sysfs.c
index 643513ee891b..a2dabe4c6422 100644
--- a/drivers/mtd/spi-nor/sysfs.c
+++ b/drivers/mtd/spi-nor/sysfs.c
@@ -25,7 +25,7 @@ static ssize_t partname_show(struct device *dev,
 	struct spi_mem *spimem = spi_get_drvdata(spi);
 	struct spi_nor *nor = spi_mem_get_drvdata(spimem);
 
-	return sysfs_emit(buf, "%s\n", nor->info->name);
+	return sysfs_emit(buf, "%s\n", nor->partname);
 }
 static DEVICE_ATTR_RO(partname);
 
@@ -78,7 +78,7 @@ static umode_t spi_nor_sysfs_is_visible(struct kobject *kobj,
 
 	if (attr == &dev_attr_manufacturer.attr && !nor->manufacturer)
 		return 0;
-	if (attr == &dev_attr_partname.attr && !nor->info->name)
+	if (attr == &dev_attr_partname.attr && !nor->partname)
 		return 0;
 	if (attr == &dev_attr_jedec_id.attr && !nor->info->id && !nor->id)
 		return 0;
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 3a55d76ea77d..b1e573367604 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -395,6 +395,7 @@ struct spi_nor {
 	u8			*id;
 	const struct flash_info	*info;
 	const struct spi_nor_manufacturer *manufacturer;
+	const char		*partname;
 	u8			addr_nbytes;
 	u8			erase_opcode;
 	u8			read_opcode;

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:07 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:07 +0200
Subject: [PATCH v3 07/23] mtd: spi-nor: Move the SFDP header structure to a
 C header
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-7-b637cf120d5c@bootlin.com>

The sfdp_header structure is currently defined in sfdp.c, whereas the
sfdp_parameter_header structure is defined in sfdp.h. In order to ease
the reuse of this structure outside of the sfdp.c file, move this
structure to the sfdp C header (.h).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/sfdp.c | 11 -----------
 drivers/mtd/spi-nor/sfdp.h | 11 +++++++++++
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index 39cb8ac3a037..8e39bacddf16 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -34,17 +34,6 @@
 
 #define SFDP_SIGNATURE		0x50444653U
 
-struct sfdp_header {
-	u32		signature; /* Ox50444653U <=> "SFDP" */
-	u8		minor;
-	u8		major;
-	u8		nph; /* 0-base number of parameter headers */
-	u8		unused;
-
-	/* Basic Flash Parameter Table. */
-	struct sfdp_parameter_header	bfpt_header;
-};
-
 /* Fast Read settings. */
 struct sfdp_bfpt_read {
 	/* The Fast Read x-y-z hardware capability in params->hwcaps.mask. */
diff --git a/drivers/mtd/spi-nor/sfdp.h b/drivers/mtd/spi-nor/sfdp.h
index e8ed814c6530..2cbe22246c5e 100644
--- a/drivers/mtd/spi-nor/sfdp.h
+++ b/drivers/mtd/spi-nor/sfdp.h
@@ -149,4 +149,15 @@ struct sfdp_parameter_header {
 	u8		id_msb;
 };
 
+struct sfdp_header {
+	u32		signature; /* Ox50444653U <=> "SFDP" */
+	u8		minor;
+	u8		major;
+	u8		nph; /* 0-base number of parameter headers */
+	u8		unused;
+
+	/* Basic Flash Parameter Table. */
+	struct sfdp_parameter_header	bfpt_header;
+};
+
 #endif /* __LINUX_MTD_SFDP_H */

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:08 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:08 +0200
Subject: [PATCH v3 08/23] mtd: spi-nor: winbond: Prepare introduction of
 W25QxxRV-Q/N parts
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-8-b637cf120d5c@bootlin.com>

There is an ID collision between the JV and RV families. Both chips are
very similar in practice, it is mostly a matter of electrical
differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2 opcode
(0x31). They also do not require the multi-die fixups which must remain
assigned to the JV chips.

Finally, since they share the IDs but not the names, we must hide the
names using a fixup.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 56 ++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 53 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 583b1669270f..8c1cad9e21b4 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -146,6 +146,51 @@ static const struct spi_nor_fixups winbond_nor_multi_die_fixups = {
 	.post_sfdp = winbond_nor_multi_die_post_sfdp_fixups,
 };
 
+static int winbond_nor_partname_post_sfdp_fixups(struct spi_nor *nor)
+{
+	/*
+	 * W25QxxRV parts re-use the JEDEC IDs of the JV family. Their name
+	 * being a legacy field, it is kept for the already established JV parts
+	 * but must not be exposed by the newer RV ones.
+	 */
+	nor->partname = NULL;
+
+	return 0;
+}
+
+static const struct spi_nor_fixups winbond_nor_partname_fixups = {
+	.post_sfdp = winbond_nor_partname_post_sfdp_fixups,
+};
+
+static bool is_w25qxxrv(const struct spi_nor *nor)
+{
+	struct sfdp_header *sfdp_h = (struct sfdp_header *)nor->sfdp->dwords;
+
+	/*
+	 * W25QxxRV chips re-use the same ID as the W25QxxJV family.
+	 *
+	 * Chips are very similar, W25QxxRV brings mostly performance and power
+	 * consumption improvements. The RV family does not require the multi
+	 * die fixup.
+	 *
+	 * They can be distinguished based on their SFDP minor revision:
+	 * W25QxxJV:        JESD216A, minor revision == 05h
+	 * W25Q512/01/02JV: JESD216B, minor revision == 06h
+	 * W25QxxRV:        JESD216F, minor revision >= 0Ah
+	 */
+	return sfdp_h->minor >= SFDP_JESD216F_MINOR;
+}
+
+static bool winbond_jv_match(const struct spi_nor *nor)
+{
+	return !nor->sfdp || !is_w25qxxrv(nor);
+}
+
+static bool winbond_rv_match(const struct spi_nor *nor)
+{
+	return nor->sfdp && is_w25qxxrv(nor);
+}
+
 static const struct flash_info winbond_nor_parts[] = {
 	{
 		.id = SNOR_ID(0xef, 0x30, 0x10),
@@ -552,9 +597,14 @@ static const struct spi_nor_fixup winbond_fixups[] = {
 	{ .fixups = &winbond_nor_fixups },
 	{ .id = SNOR_ID(0xef, 0x40, 0x18), .fixups = &w25q128_fixups },
 	{ .id = SNOR_ID(0xef, 0x40, 0x19), .fixups = &w25q256_fixups },
-	{ .id = SNOR_ID(0xef, 0x40, 0x21), .fixups = &winbond_nor_multi_die_fixups },
-	{ .id = SNOR_ID(0xef, 0x70, 0x21), .fixups = &winbond_nor_multi_die_fixups },
-	{ .id = SNOR_ID(0xef, 0x70, 0x22), .fixups = &winbond_nor_multi_die_fixups },
+	{ .id = SNOR_ID(0xef, 0x40), .match = winbond_rv_match,
+	  .fixups = &winbond_nor_partname_fixups },
+	{ .id = SNOR_ID(0xef, 0x40, 0x21), .match = winbond_jv_match,
+	  .fixups = &winbond_nor_multi_die_fixups },
+	{ .id = SNOR_ID(0xef, 0x70, 0x21), .match = winbond_jv_match,
+	  .fixups = &winbond_nor_multi_die_fixups },
+	{ .id = SNOR_ID(0xef, 0x70, 0x22), .match = winbond_jv_match,
+	  .fixups = &winbond_nor_multi_die_fixups },
 };
 
 const struct spi_nor_manufacturer spi_nor_winbond = {

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:09 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:09 +0200
Subject: [PATCH v3 09/23] mtd: spi-nor: winbond: Add support for
 W25Q32RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-9-b637cf120d5c@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef4016
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0100ff00080117800000ffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f9ffffffff0144eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d800001432010183e314c2e96376337a757a75
f7a2d55c39f66dffe930f8800000000000002c0000000000f6f4ffff0f00
0000060d44bd27ed080d
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
2c0c54d6e360dae30b72615eac5f0faff59463aeb2237d46d08d6cfd77d6c9f6  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x03
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3b
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbb
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6b
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x02
 1S-1S-4S
  opcode	0x32
+ cat /sys/kernel/debug/spi-nor/spi0.0/p[   84.112272] random: crng init done
arams
name		(null)
id		ef 40 16 00 00 00
size		4.00 MiB
write size	1
page size	256
address nbytes	3
flags		HAS_SR_TB | HAS_LOCK | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xeb
  dummy cycles	6
 erase		0xd8
 program	0x32
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 20 (4.00 KiB) [1]
 52 (32.0 KiB) [2]
 d8 (64.0 KiB) [3]
 c7 (4.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-003fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff | unlocked | 64
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_read
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 4194304 (4M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_read
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_read
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_read
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_read2
9f76d5d40e1d8fc620967a019cc0cf223a3b11cf9c5bed9a02fb8e2c06b1c805  spi_test
831e9e5cc6efa7d3afe7b0584100549d93b9953d64ad237fd1df15f790f23ae6  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=4194304
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 4063232 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff | unlocked | 62
 003e0000-003fffff |   locked | 2
+ flash_lock -u /dev/mtd0 4063232 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff | unlocked | 63
 003f0000-003fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-003fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-003fffff | unlocked | 63
+ all_but_one=63
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 63
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-003fffff |   locked | 63
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-003fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 63
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff |   locked | 63
 003f0000-003fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 4063232 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff |   locked | 62
 003e0000-003fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 8c1cad9e21b4..69c02732d5db 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -243,7 +243,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.size = SZ_1M,
 		.no_sfdp_flags = SECT_4K,
 	}, {
-		/* W25Q32JV-Q/N */
+		/* W25Q32JV-Q/N, W25Q32RV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x16),
 		.name = "w25q32",
 		.size = SZ_4M,

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:11 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:11 +0200
Subject: [PATCH v3 11/23] mtd: spi-nor: winbond: Add support for
 W25Q12RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-11-b637cf120d5c@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef4018
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0100ff00080117800000ffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f9ffffffff0344eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d80000142a010186e114c1e96376337a757a75
f7a2d55c39f66dffe930f880000000000000ac0000000000f7f5ffff0f00
0000060d44bd27ed080d
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
94b52d6e903a2046e144eab0760384b6932c6fdbfec17eb7336f1778c3898daf  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x03
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3b
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbb
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6b
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x02
 1S-1S-4S
  opcode	0x32
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 18 00 00 00
size		8.00 MiB
write size	1
page size	256
address nbytes	3
flags		HAS_SR_TB | HAS_LOCK | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xeb
  dummy cycles	6
 erase		0xd8
 program	0x32
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 20 (4.00 KiB) [1]
 52 (32.0 KiB) [2]
 d8 (64.0 KiB) [3]
 c7 (8.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-007fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   15.791564] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_read
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 8388608 (8M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_read
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_read
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_read
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_read2
b3260b68e0373cd253fc558fa9a5c89009d3edb2393f4110702318f69a6d74c2  spi_test
884cedd0494868db17c609edc62e2471ef344025e30cd511fa28b41b9b50e50d  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=8388608
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=131072
+ bps=2
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 8126464 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff | unlocked | 62
 007c0000-007fffff |   locked | 2
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff | unlocked | 63
 007e0000-007fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 2
 00040000-007fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 1
 00020000-007fffff | unlocked | 63
+ all_but_one=126
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 131072 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 1
 00020000-007fffff |   locked | 63
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 2
 00040000-007fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff |   locked | 63
 007e0000-007fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff |   locked | 62
 007c0000-007fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 24f3ff52427b..568dc05d3c25 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -257,7 +257,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q128JV-Q/N */
+		/* W25Q128JV-Q/N, W25Q12RV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x18),
 		/* Flavors w/ and w/o SFDP. */
 		.name = "w25q128",

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:10 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:10 +0200
Subject: [PATCH v3 10/23] mtd: spi-nor: winbond: Add support for
 W25Q64RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-10-b637cf120d5c@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef4017
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f3ffffffff0344eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d80000d639a50081d614c3e96376337a757a75
f7a4d55c39de6dffe970f9a50000000000002c000000000066ffffff0000
00000000000000000000ffffffffff0a00fe21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
c7fa99bdcdef9a930e609215704dd349dbed161487dee0ece8ea1b25d2d4dd25  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 17 00 00 00
size		8.00 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (8.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-007fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[    7.803395] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_read
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 8388608 (8M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_read
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_read
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_read
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_read2
eca2ec05a4fbf9f14490a0147e43c8f3616298bf8f6e2b59f5c5e5076c1cfd02  spi_test
64f4a5c5a05eae4a6dffb9e76171d97328257a96110214f52222870681b7b28c  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=8388608
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=131072
+ bps=2
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 8126464 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff | unlocked | 62
 007c0000-007fffff |   locked | 2
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff | unlocked | 63
 007e0000-007fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 2
 00040000-007fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 1
 00020000-007fffff | unlocked | 63
+ all_but_one=126
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 131072 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 1
 00020000-007fffff |   locked | 63
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 2
 00040000-007fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff |   locked | 63
 007e0000-007fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff |   locked | 62
 007c0000-007fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 69c02732d5db..24f3ff52427b 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -250,7 +250,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q64JV-Q/N */
+		/* W25Q64JV-Q/N, W25Q64RV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x17),
 		.name = "w25q64",
 		.size = SZ_8M,

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:12 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:12 +0200
Subject: [PATCH v3 12/23] mtd: spi-nor: winbond: Add support for
 W25Q51RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-12-b637cf120d5c@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef4020
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f3ffffffff1f44eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d800001602a60081e214dde96376337a757a75
f7a4d55c39f66dffe970f9a50000000000002c0c0000000066ffffff0000
00000000000000000000ffffffffff0a00fe21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
74d21fecc7faa297b3f77f1136b1dc53f78b2a432629e226675b6384153fd9f9  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 20 00 00 00
size		64.0 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (64.0 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-03ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   25.427347] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_read
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 67108864 (64M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_read
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_read
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_read
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_read2
cd699acc82353b7769b98547efed68d5300854ed0bbc84bf831b07937bf941a5  spi_test
f506020804f91fb750e8250e091fb2f732319221a645ed575e1b6ed3553d584c  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff |   locked | 1024
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=67108864
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=1024
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 66977792 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff | unlocked | 1022
 03fe0000-03ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff | unlocked | 1023
 03ff0000-03ffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 58720256 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-037fffff | unlocked | 896
 03800000-03ffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-03ffffff | unlocked | 1022
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-03ffffff | unlocked | 1023
+ all_but_one=1023
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-03ffffff |   locked | 1023
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-03ffffff |   locked | 1022
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff |   locked | 1023
 03ff0000-03ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff |   locked | 1022
 03fe0000-03ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 568dc05d3c25..47a5c2cf71de 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -273,7 +273,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
 			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q512JV-Q/N */
+		/* W25Q512JV-Q/N, W25Q51RV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x20),
 		.name = "w25q512jvq",
 		.size = SZ_64M,

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:13 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:13 +0200
Subject: [PATCH v3 13/23] mtd: spi-nor: winbond: Add support for
 W25Q01RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-13-b637cf120d5c@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef4020
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f3ffffffff1f44eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d800001602a60081e214dde96376337a757a75
f7a4d55c39f66dffe970f9a50000000000002c0c0000000066ffffff0000
00000000000000000000ffffffffff0a00fe21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
74d21fecc7faa297b3f77f1136b1dc53f78b2a432629e226675b6384153fd9f9  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 20 00 00 00
size		64.0 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (64.0 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-03ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 67108864 (64M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read2
950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
643412dbe0f46c2780ec8d4f609d369923829945f4bc3d7842138b2fbbc86046  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff |   locked | 1024
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=67108864
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=1024
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 66977792 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff | unlocked | 1022
 03fe0000-03ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff | unlocked | 1023
 03ff0000-03ffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 58720256 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-037fffff | unlocked | 896
 03800000-03ffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-03ffffff | unlocked | 1022
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-03ffffff | unlocked | 1023
+ all_but_one=1023
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-03ffffff |   locked | 1023
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-03ffffff |   locked | 1022
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff |   locked | 1023
 03ff0000-03ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff |   locked | 1022
 03fe0000-03ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 47a5c2cf71de..1c67a652ddcd 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -281,7 +281,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q01JV-Q/N */
+		/* W25Q01JV-Q/N, W25Q01RV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:14 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:14 +0200
Subject: [PATCH v3 14/23] mtd: spi-nor: winbond: Add support for
 W25Q02RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-14-b637cf120d5c@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef4022
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f3ffffffff7f44eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d800001602a60081e214dde9e378477a757a75
f7a4d55c39f66dffe970f9a50000000000002c0c0000000066ffffff0000
00000000000000000000ffffffffff0a00fe21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
623c2663966d70cc6418f65db6c182a7d9146e15b190263f46ea81d9a934866c  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 40 22 00 00 00
size		256 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 SR1 read	0x05
 SR2 read	0x35
 SR1 write	0x01
 SR2 write	0x31
 QE		SR2 bit 1
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (256 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-0fffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff | unlocked | 4096
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   14.840645] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
c1c18ff55582ccab09e88cd6e6c14fe46c601c6461cb0012ac79949615f6c682  spi_read
c1c18ff55582ccab09e88cd6e6c14fe46c601c6461cb0012ac79949615f6c682  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
c1c18ff55582ccab09e88cd6e6c14fe46c601c6461cb0012ac79949615f6c682  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 268435456 (256M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x10000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
c1c18ff55582ccab09e88cd6e6c14fe46c601c6461cb0012ac79949615f6c682  spi_read
c1c18ff55582ccab09e88cd6e6c14fe46c601c6461cb0012ac79949615f6c682  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff | unlocked | 4096
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x10000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
c1c18ff55582ccab09e88cd6e6c14fe46c601c6461cb0012ac79949615f6c682  spi_read
c1c18ff55582ccab09e88cd6e6c14fe46c601c6461cb0012ac79949615f6c682  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
c1c18ff55582ccab09e88cd6e6c14fe46c601c6461cb0012ac79949615f6c682  spi_read
c1c18ff55582ccab09e88cd6e6c14fe46c601c6461cb0012ac79949615f6c682  spi_read2
c1c18ff55582ccab09e88cd6e6c14fe46c601c6461cb0012ac79949615f6c682  spi_test
28e04c28384aafd356a3e545ba79bf02e995cfb1e765ec663e35924c3f92b88d  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff |   locked | 4096
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=268435456
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=4096
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 268304384 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffdffff | unlocked | 4094
 0ffe0000-0fffffff |   locked | 2
+ flash_lock -u /dev/mtd0 268304384 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffeffff | unlocked | 4095
 0fff0000-0fffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 260046848 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0f7fffff | unlocked | 3968
 0f800000-0fffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-0fffffff | unlocked | 4094
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-0fffffff | unlocked | 4095
+ all_but_one=4095
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 4095
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-0fffffff |   locked | 4095
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-0fffffff |   locked | 4094
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4095
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffeffff |   locked | 4095
 0fff0000-0fffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 268304384 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffdffff |   locked | 4094
 0ffe0000-0fffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 1c67a652ddcd..055c5833f687 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -285,6 +285,11 @@ static const struct flash_info winbond_nor_parts[] = {
 		.id = SNOR_ID(0xef, 0x40, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+	}, {
+		/* W25Q02RV-Q/N */
+		.id = SNOR_ID(0xef, 0x40, 0x22),
+		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
 		.id = SNOR_ID(0xef, 0x50, 0x12),
 		.name = "w25q20bw",
@@ -601,6 +606,8 @@ static const struct spi_nor_fixup winbond_fixups[] = {
 	  .fixups = &winbond_nor_partname_fixups },
 	{ .id = SNOR_ID(0xef, 0x40, 0x21), .match = winbond_jv_match,
 	  .fixups = &winbond_nor_multi_die_fixups },
+	{ .id = SNOR_ID(0xef, 0x40, 0x22), .match = winbond_jv_match,
+	  .fixups = &winbond_nor_multi_die_fixups },
 	{ .id = SNOR_ID(0xef, 0x70, 0x21), .match = winbond_jv_match,
 	  .fixups = &winbond_nor_multi_die_fixups },
 	{ .id = SNOR_ID(0xef, 0x70, 0x22), .match = winbond_jv_match,

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:15 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:15 +0200
Subject: [PATCH v3 15/23] mtd: spi-nor: winbond: Prepare introduction of
 W25QxxRV-M parts
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-15-b637cf120d5c@bootlin.com>

There is an ID collision between the JV and RV families. Both chips are
very similar in practice, it is mostly a matter of electrical
differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
The new SFDP (rev F) field which forces an alternate write SR2 opcode
(0x31).

Since they share the IDs but not the names, we must hide the names using
a fixup.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 055c5833f687..b86458b22838 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -608,6 +608,8 @@ static const struct spi_nor_fixup winbond_fixups[] = {
 	  .fixups = &winbond_nor_multi_die_fixups },
 	{ .id = SNOR_ID(0xef, 0x40, 0x22), .match = winbond_jv_match,
 	  .fixups = &winbond_nor_multi_die_fixups },
+	{ .id = SNOR_ID(0xef, 0x70), .match = winbond_rv_match,
+	  .fixups = &winbond_nor_partname_fixups },
 	{ .id = SNOR_ID(0xef, 0x70, 0x21), .match = winbond_jv_match,
 	  .fixups = &winbond_nor_multi_die_fixups },
 	{ .id = SNOR_ID(0xef, 0x70, 0x22), .match = winbond_jv_match,

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:16 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:16 +0200
Subject: [PATCH v3 16/23] mtd: spi-nor: winbond: Add support for W25Q32RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-16-b637cf120d5c@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7016
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0100ff00080117800000ffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f9ffffffff0144eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d800001432010183e314c2e96376337a757a75
f7a2d55c39f66dffe930f8800000000000002c0000000000f6f4ffff0f00
0000060d44bd27ed080d
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
2c0c54d6e360dae30b72615eac5f0faff59463aeb2237d46d08d6cfd77d6c9f6  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x03
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3b
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbb
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6b
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x02
 1S-1S-4S
  opcode	0x32
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 16 00 00 00
size		4.00 MiB
write size	1
page size	256
address nbytes	3
flags		HAS_SR_TB | HAS_LOCK | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xeb
  dummy cycles	6
 erase		0xd8
 program	0x32
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 20 (4.00 KiB) [1]
 52 (32.0 KiB) [2]
 d8 (64.0 KiB) [3]
 c7 (4.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-003fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff | unlocked | 64
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   15.820716] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_read
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 4194304 (4M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_read
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x400000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_read
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_read
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_read2
c73e2828631d8f745c118e706e4ea7179328e6e03e49cb2d30784eb73ff6167f  spi_test
d26d11f7de825c1f2397e52307997c9befb07b93e6cd520d15a87d06abef2529  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=4194304
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 4063232 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff | unlocked | 62
 003e0000-003fffff |   locked | 2
+ flash_lock -u /dev/mtd0 4063232 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff | unlocked | 63
 003f0000-003fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-003fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-003fffff | unlocked | 63
+ all_but_one=63
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 63
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-003fffff |   locked | 63
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-003fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 63
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003effff |   locked | 63
 003f0000-003fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 4063232 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-003dffff |   locked | 62
 003e0000-003fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index b86458b22838..3fe0af416178 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -356,7 +356,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q32JV-M */
+		/* W25Q32JV-M, W25Q32RV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x16),
 		.name = "w25q32jv",
 		.size = SZ_4M,

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:18 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:18 +0200
Subject: [PATCH v3 18/23] mtd: spi-nor: winbond: Add support for W25Q12RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-18-b637cf120d5c@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7018
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0100ff00080117800000ffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520f9ffffffff0344eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d80000142a010186e114c1e96376337a757a75
f7a2d55c39f66dffe930f880000000000000ac0000000000f7f5ffff0f00
0000060d44bd27ed080d
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
94b52d6e903a2046e144eab0760384b6932c6fdbfec17eb7336f1778c3898daf  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x03
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3b
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbb
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6b
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xeb
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x02
 1S-1S-4S
  opcode	0x32
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 18 00 00 00
size		8.00 MiB
write size	1
page size	256
address nbytes	3
flags		HAS_SR_TB | HAS_LOCK | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xeb
  dummy cycles	6
 erase		0xd8
 program	0x32
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 20 (4.00 KiB) [1]
 52 (32.0 KiB) [2]
 d8 (64.0 KiB) [3]
 c7 (8.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-007fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   28.249430] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_read
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 8388608 (8M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_read
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_read
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_read
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_read2
48b1df493c0c35b1c6c8a50880c6e10adcd103c383a7324033cb760d20d9acf0  spi_test
df6c52ecc59473c5ed9bd10a0479b52ee248adbf7d032aa7cde35e86acd461ca  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=8388608
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=131072
+ bps=2
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 8126464 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff | unlocked | 62
 007c0000-007fffff |   locked | 2
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff | unlocked | 63
 007e0000-007fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 2
 00040000-007fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 1
 00020000-007fffff | unlocked | 63
+ all_but_one=126
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 131072 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 1
 00020000-007fffff |   locked | 63
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 2
 00040000-007fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff |   locked | 63
 007e0000-007fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff |   locked | 62
 007c0000-007fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 3019258bf65e..b661e2c56474 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -370,7 +370,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q128JV-M */
+		/* W25Q128JV-M, W25Q12RV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x18),
 		.name = "w25q128jv",
 		.size = SZ_16M,

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:19 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:19 +0200
Subject: [PATCH v3 19/23] mtd: spi-nor: winbond: Add support for W25Q51RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-19-b637cf120d5c@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7020
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff1f44eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d800001602a60081e214dde96376337a757a75
f7a4d55c39f66dffe970f9a50000000000002c0c000000006655ffff0f00
0000060d44bd27ed080dffffffffff8a00fe21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
d74b1b27fa4e5b8fba7db8fcac198bfe5a28c5e17a565d5e694a6b8d21b62104  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 20 00 00 00
size		64.0 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 SR1 read	0x05
 SR2 read	0x35
 SR1 write	0x01
 SR2 write	0x31
 QE		SR2 bit 1
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (64.0 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-03ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
640094386802e8c1ddca74f4f4848140736a1b275dfc5a2cefbd7f6acd0e6727  spi_read
640094386802e8c1ddca74f4f4848140736a1b275dfc5a2cefbd7f6acd0e6727  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
640094386802e8c1ddca74f4f4848140736a1b275dfc5a2cefbd7f6acd0e6727  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 67108864 (64M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
640094386802e8c1ddca74f4f4848140736a1b275dfc5a2cefbd7f6acd0e6727  spi_read
640094386802e8c1ddca74f4f4848140736a1b275dfc5a2cefbd7f6acd0e6727  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff | unlocked | 1024
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x4000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
640094386802e8c1ddca74f4f4848140736a1b275dfc5a2cefbd7f6acd0e6727  spi_read
640094386802e8c1ddca74f4f4848140736a1b275dfc5a2cefbd7f6acd0e6727  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
640094386802e8c1ddca74f4f4848140736a1b275dfc5a2cefbd7f6acd0e6727  spi_read
640094386802e8c1ddca74f4f4848140736a1b275dfc5a2cefbd7f6acd0e6727  spi_read2
640094386802e8c1ddca74f4f4848140736a1b275dfc5a2cefbd7f6acd0e6727  spi_test
12582dd4eecd173d184b51b0281065e10062901d66d0f6b1709f4240227b81a9  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03ffffff |   locked | 1024
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=67108864
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=1024
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 66977792 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff | unlocked | 1022
 03fe0000-03ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff | unlocked | 1023
 03ff0000-03ffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 58720256 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-037fffff | unlocked | 896
 03800000-03ffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-03ffffff | unlocked | 1022
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-03ffffff | unlocked | 1023
+ all_but_one=1023
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-03ffffff |   locked | 1023
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-03ffffff |   locked | 1022
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 1023
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03feffff |   locked | 1023
 03ff0000-03ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 66977792 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-03fdffff |   locked | 1022
 03fe0000-03ffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index b661e2c56474..fc9e84e500c5 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -383,7 +383,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
 			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q512JV-M */
+		/* W25Q512JV-M, W25Q51RV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x20),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:20 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:20 +0200
Subject: [PATCH v3 20/23] mtd: spi-nor: winbond: Add support for W25Q01RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-20-b637cf120d5c@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7021
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff3f44eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d800001602a60081e214dde9e378477a757a75
f7a4d55c39f66dffe970f9a50000000000002c0c000000006655ffff0f00
0000060d44bd27ed080dffffffffff8a00fe21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
f0ba26954c84f126916616ac4311057151ea46cae2eda1752709c3a5adfe4b55  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 21 00 00 00
size		128 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (128 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-07ffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   19.799786] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_read
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 134217728 (128M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_read
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff | unlocked | 2048
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x8000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_read
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_read
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_read2
89d206670689b1bdd4688313bcc0ea3ac67a9ac055e75fd76220387c9e4fde3e  spi_test
792b00a5bf62350ee8f4868980820577046401c3e6f79075ea636034cff7cef2  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07ffffff |   locked | 2048
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=134217728
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=2048
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 134086656 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff | unlocked | 2046
 07fe0000-07ffffff |   locked | 2
+ flash_lock -u /dev/mtd0 134086656 1
+ grep -A4 'locked sectors' /sys/kernel/debbg//spi-nr/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff | unlocked | 2047
 07ff0000-07ffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 125829120 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-077fffff | unlocked | 1920
 07800000-07ffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-07ffffff | unlocked | 2046
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-07ffffff | unlocked | 2047
+ all_but_one=2047
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 2047
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-07ffffff |   locked | 2047
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-07ffffff |   locked | 2046
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2047
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07feffff |   locked | 2047
 07ff0000-07ffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 134086656 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-07fdffff |   locked | 2046
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index fc9e84e500c5..8bcfc63a75f3 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -388,7 +388,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q01JV-M */
+		/* W25Q01JV-M, W25Q01RV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x21),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:17 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:17 +0200
Subject: [PATCH v3 17/23] mtd: spi-nor: winbond: Add support for W25Q64RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-17-b637cf120d5c@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7017
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff0344eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d80000d639a50081d614c3e96376337a757a75
f7a4d55c39de6dffe970f9a50000000000002c00000000006655ffff0f00
0000060d44bd27ed080dffffffffff8a00fe21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
ef6c5eb61fa86c6daac85436446b97ca078a46e4cfd4c0fed4c9f9400d79fa86  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 17 00 00 00
size		8.00 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (8.00 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-007fffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   15.931750] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_read
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 8388608 (8M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_read
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff | unlocked | 64
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x800000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_read
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_read
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_read2
d4a37ad2760e69823409d7e9321471b7f7ed6728d40f7f15329921fbe7393db6  spi_test
154432cc7e89d18c1e4ddb1604dfd2a0038c75aa5521a74808944ce19a320a42  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007fffff |   locked | 64
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=8388608
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=64
+ ss=131072
+ bps=2
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 8126464 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff | unlocked | 62
 007c0000-007fffff |   locked | 2
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff | unlocked | 63
 007e0000-007fffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff |   locked | 2
 00040000-007fffff | unlocked | 62
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 1
 00020000-007fffff | unlocked | 63
+ all_but_one=126
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 131072 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 1
 00020000-007fffff |   locked | 63
+ flash_lock -u /dev/mtd0 131072 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0003ffff | unlocked | 2
 00040000-007fffff |   locked | 62
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 126
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007dffff |   locked | 63
 007e0000-007fffff | unlocked | 1
+ flash_lock -u /dev/mtd0 8126464 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-007bffff |   locked | 62
 007c0000-007fffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 3fe0af416178..3019258bf65e 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -363,7 +363,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q64JV-M */
+		/* W25Q64JV-M, W25Q64RV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x17),
 		.name = "w25q64jvm",
 		.size = SZ_8M,

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:23 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:23 +0200
Subject: [PATCH v3 23/23] mtd: spi-nor: debugfs: Expose SR opcodes and QE
 mask
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-23-b637cf120d5c@bootlin.com>

Expose Status Register opcodes based on the flash capabilities
discovered while parsing the SFDP QER field.

Value '0' indicates an absence of opcode (non supported), we do not
print anything in that case.

The QE bit is also displayed.

Example on a W25Q02JV-M:

$ grep opcodes -A10 /sys/kernel/debug/spi-nor/spi0.0/params
opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 SR1 read	0x05
 SR2 read	0x35
 SR1+SR2 write	0x01
 QE		SR2 bit 1
 8D extension	none

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/debugfs.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index 275671222588..a82858f0328a 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -105,6 +105,21 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
 	seq_printf(s, "  dummy cycles\t%u\n", nor->read_dummy);
 	seq_printf(s, " erase\t\t0x%02x\n", nor->erase_opcode);
 	seq_printf(s, " program\t0x%02x\n", nor->program_opcode);
+	seq_printf(s, " SR1 read\t0x%02x\n", params->opcodes.read_sr1);
+	if (params->opcodes.read_sr2)
+		seq_printf(s, " SR2 read\t0x%02x\n", params->opcodes.read_sr2);
+	if (params->opcodes.write_sr1)
+		seq_printf(s, " SR1 write\t0x%02x\n", params->opcodes.write_sr1);
+	if (params->opcodes.write_sr2)
+		seq_printf(s, " SR2 write\t0x%02x\n", params->opcodes.write_sr2);
+	if (params->opcodes.write_sr1_and_sr2)
+		seq_printf(s, " SR1+SR2 write\t0x%02x\n",
+			   params->opcodes.write_sr1_and_sr2);
+
+	if (params->qe_mask[0] || params->qe_mask[1])
+		seq_printf(s, " QE\t\tSR%d bit %d\n",
+			   params->qe_mask[0] ? 1 : 2,
+			   ffs(params->qe_mask[0] | params->qe_mask[1]) - 1);
 
 	switch (nor->cmd_ext_type) {
 	case SPI_NOR_EXT_NONE:

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:21 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:21 +0200
Subject: [PATCH v3 21/23] mtd: spi-nor: winbond: Add support for W25Q02RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-21-b637cf120d5c@bootlin.com>

There is an ID collision with the chip of same density from the JV
family. Both chips are very similar in practice, it is mostly a matter
of electrical differences (mostly power consumption being lower).

As a significant difference, RV chips identify themselves as supporting
the new SFDP (rev F) field which forces an alternate write SR2
opcode (0x31).

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
ef7022
+ cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
winbond
+ xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
ffffffffffffffffe520fbffffffff7f44eb086b083b42bbfeffffffffff
0000ffff44eb0c200f5210d800001602a60081e214dde9e378477a757a75
f7a4d55c39f66dffe970f9a50000000000002c0c000000006655ffff0f00
0000060d44bd27ed080dffffffffff8a00fe21ffdcff
+ sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
fe929abb1ba38dd6a8668200051298a8140bfeb56bd6d58f56b86b074a150f12  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
+ cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
Supported read modes by the flash
 1S-1S-1S
  opcode	0x13
  mode cycles	0
  dummy cycles	0
 1S-1S-2S
  opcode	0x3c
  mode cycles	0
  dummy cycles	8
 1S-2S-2S
  opcode	0xbc
  mode cycles	2
  dummy cycles	2
 1S-1S-4S
  opcode	0x6c
  mode cycles	0
  dummy cycles	8
 1S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4
 4S-4S-4S
  opcode	0xec
  mode cycles	2
  dummy cycles	4

Supported page program modes by the flash
 1S-1S-1S
  opcode	0x12
 1S-1S-4S
  opcode	0x34
+ cat /sys/kernel/debug/spi-nor/spi0.0/params
name		(null)
id		ef 70 22 00 00 00
size		256 MiB
write size	1
page size	256
address nbytes	4
flags		HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP | HAS_SR2_CMP_BIT6

opcodes
 read		0xec
  dummy cycles	6
 erase		0xdc
 program	0x34
 8D extension	repeat

protocols
 read		1S-4S-4S
 write		1S-1S-4S
 register	1S-1S-1S

erase commands
 21 (4.00 KiB) [1]
 dc (64.0 KiB) [3]
 c7 (256 MiB)

sector map
 region (in hex)   | erase mask | overlaid
 ------------------+------------+---------
 00000000-0fffffff |     [   3] | no

locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff | unlocked | 4096
+ dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
[   11.527038] random: crng init done
2+0 records in
2+0 records out
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ hexdump spi_read
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
*
0200000
+ sha256sum spi_read
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_read
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_test
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_test
+ mtd_debug info /dev/mtd0
mtd.type = MTD_NORFLASH
mtd.flags = MTD_CAP_NORFLASH
mtd.size = 268435456 (256M)
mtd.erasesize = 65536 (64K)
mtd.writesize = 1
mtd.oobsize = 0
regions = 0

+ alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
+ flash_lock -u /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x10000000
Lock status: unlocked
Return code: 0
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug write /dev/mtd0 0 2097152 spi_test
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_read
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_test
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff | unlocked | 4096
+ flash_lock -l /dev/mtd0
+ flash_lock -i /dev/mtd0
Device: /dev/mtd0
Start: 0
Len: 0x10000000
Lock status: locked
Return code: 1
+ mtd_debug erase /dev/mtd0 0 2097152
Erased 2097152 bytes from address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
+ sha256sum spi_read spi_test
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_read
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_test
+ dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
2+0 records in
2+0 records out
+ mtd_debug write /dev/mtd0 0 2097152 spi_test2
Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
+ mtd_debug read /dev/mtd0 0 2097152 spi_read2
Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
+ sha256sum spi_read spi_read2 spi_test spi_test2
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_read
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_read2
32e1a8def4cf674413ff0b7a2738e704f03f7bddcaf7ee2d320ab83aa7bf813e  spi_test
4b1329df073cd497567d8cb6878aa20af6af71df98cc7c445d31a69eaf0d69f1  spi_test2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0fffffff |   locked | 4096
+ flash_lock -u /dev/mtd0
+ cat /sys/class/mtd/mtd0/size
+ size=268435456
+ cat /sys/class/mtd/mtd0/erasesize
+ bs=65536
+ grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
+ sed -e 's/.*unlocked | //'
+ nsectors=4096
+ ss=65536
+ bps=1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 268304384 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffdffff | unlocked | 4094
 0ffe0000-0fffffff |   locked | 2
+ flash_lock -u /dev/mtd0 268304384 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffeffff | unlocked | 4095
 0fff0000-0fffffff |   locked | 1
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 260046848 128
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0f7fffff | unlocked | 3968
 0f800000-0fffffff |   locked | 128
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 2
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff |   locked | 2
 00020000-0fffffff | unlocked | 4094
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff |   locked | 1
 00010000-0fffffff | unlocked | 4095
+ all_but_one=4095
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 65536 4095
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0000ffff | unlocked | 1
 00010000-0fffffff |   locked | 4095
+ flash_lock -u /dev/mtd0 65536 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0001ffff | unlocked | 2
 00020000-0fffffff |   locked | 4094
+ flash_lock -u /dev/mtd0
+ flash_lock -l /dev/mtd0 0 4095
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffeffff |   locked | 4095
 0fff0000-0fffffff | unlocked | 1
+ flash_lock -u /dev/mtd0 268304384 1
+ grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
locked sectors
 region (in hex)   | status   | #sectors
 ------------------+----------+---------
 00000000-0ffdffff |   locked | 4094
 0ffe0000-0fffffff | unlocked | 2
---
 drivers/mtd/spi-nor/winbond.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 8bcfc63a75f3..6ad7d4a7da8d 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -393,7 +393,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q02JV-M */
+		/* W25Q02JV-M, W25Q02RV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x22),
 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
 			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:19:22 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:19:22 +0200
Subject: [PATCH v3 22/23] mtd: spi-nor: winbond: Add support for
 W25Q51RV-Q/N/M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
Message-ID: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-22-b637cf120d5c@bootlin.com>

I currently do not have access to any chips from the W25Q25RV
density. Yet, I successfully tested the entire JV family and all other
chips from the RV family. They all behaved strictly identically, so I
propose to nevertheless add the missing comments for the sake of clarity
and completeness.

Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
---
 drivers/mtd/spi-nor/winbond.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 6ad7d4a7da8d..b4ed41c692a8 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -265,7 +265,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q256JV-Q/N */
+		/* W25Q256JV-Q/N, W25Q25RV-Q/N */
 		.id = SNOR_ID(0xef, 0x40, 0x19),
 		.name = "w25q256",
 		.size = SZ_32M,
@@ -377,7 +377,7 @@ static const struct flash_info winbond_nor_parts[] = {
 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
 	}, {
-		/* W25Q256JV-M */
+		/* W25Q256JV-M, W25Q25RV-M */
 		.id = SNOR_ID(0xef, 0x70, 0x19),
 		.name = "w25q256jvm",
 		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |

-- 
2.54.0



From miquel.raynal at bootlin.com  Thu Aug 13 08:40:23 2026
From: miquel.raynal at bootlin.com (Miquel Raynal)
Date: Thu, 13 Aug 2026 17:40:23 +0200
Subject: [GIT PULL] mtd: nand: Changes for 7.3
Message-ID: <87tsoyjafs.fsf@bootlin.com>

Hello,

This is the NAND PR for 7.3.

Thanks,
Miqu?l

The following changes since commit dc59e4fea9d83f03bad6bddf3fa2e52491777482:

  Linux 7.2-rc1 (2026-06-28 12:01:31 -0700)

are available in the Git repository at:

  git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git tags/nand/for-7.3

for you to fetch changes up to 15a3cbce32994141252bb4ecfe3ff3a5d22d0b4f:

  mtd: rawnand: sunxi: fix H6/H616 controller timings (2026-08-10 16:24:26 +0200)

----------------------------------------------------------------
* Raw NAND changes
- Sunxi: Support added for the H616 compatible
- Qcom: Support added for the MDM9607 compatible
- Support for the Toshiba TC58NVG1S3H part
- GPMI: New debugfs entry to expose the chip geometry
- PL353: Timing updates and software ECC support have been fixed

* SPI NAND changes
- fmsh: Support added for FM25G{01,02}B chips
- HeYangTek: Support added for HYF1GQ4UDACAE

Aside from these main changes, there is the usual load of misc fixes and
hardening changes.

----------------------------------------------------------------
Aleksandr Mineev (1):
      mtd: spinand: fmsh: fix FM25G01B/FM25G02B Quad I/O read dummy cycles

Aleksei Sviridkin (1):
      mtd: spinand: add support for HeYangTek HYF1GQ4UDACAE

Han Xu (1):
      mtd: rawnand: gpmi: add debugfs entry for BCH geometry

James Hilliard (5):
      mtd: rawnand: add Toshiba TC58NVG1S3H
      mtd: rawnand: sunxi: add H616 MBUS DMA support
      mtd: rawnand: sunxi: group controller delay tables
      mtd: rawnand: sunxi: describe tADL and tWHR delays
      mtd: rawnand: sunxi: fix H6/H616 controller timings

Miquel Raynal (DAVE) (3):
      mtd: rawnand: pl353: Update timings at the right moment
      mtd: rawnand: pl353: Make sure we use the monolithic helpers for raw accesses
      mtd: rawnand: pl353: Fix debug prints

Pengpeng Hou (3):
      mtd: parsers: redboot: reject unterminated FIS names
      mtd: nand: realtek-ecc: add missing MODULE_DEVICE_TABLE()
      mtd: rawnand: validate ONFI extended parameter page sections

Rosen Penev (1):
      mtd: rawnand: atmel: use struct_size

Stephan Gerhold (4):
      dt-bindings: mtd: qcom,nandc: Add MDM9607 QPIC NAND controller
      mtd: rawnand: qcom: Make "aon" clock optional
      mtd: rawnand: qcom: Make has_onfi_read_op separate from qpic_version2
      mtd: rawnand: qcom: Add MDM9607 compatible

Uwe Kleine-K?nig (The Capable Hub) (1):
      mtd: nand-omap2: Move omap_nand_ids[] to raw nand driver

Ziyang Huang (1):
      mtd: spinand: fmsh: add support for FM25G{01,02}B

 .../devicetree/bindings/mtd/qcom,nandc.yaml        |  25 +++
 drivers/memory/omap-gpmc.c                         |   9 +-
 drivers/mtd/nand/ecc-realtek.c                     |   1 +
 drivers/mtd/nand/raw/atmel/nand-controller.c       |   3 +-
 drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c         |  29 +++
 drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h         |   6 +
 drivers/mtd/nand/raw/nand_ids.c                    |   3 +
 drivers/mtd/nand/raw/nand_onfi.c                   |  27 ++-
 drivers/mtd/nand/raw/nand_toshiba.c                |   4 +-
 drivers/mtd/nand/raw/omap2.c                       |   6 +-
 drivers/mtd/nand/raw/pl35x-nand-controller.c       |  20 +-
 drivers/mtd/nand/raw/qcom_nandc.c                  |  30 ++-
 drivers/mtd/nand/raw/sunxi_nand.c                  | 233 +++++++++++++++++----
 drivers/mtd/nand/spi/Makefile                      |   2 +-
 drivers/mtd/nand/spi/core.c                        |   1 +
 drivers/mtd/nand/spi/fmsh.c                        | 103 +++++++++
 drivers/mtd/nand/spi/heyangtek.c                   | 132 ++++++++++++
 drivers/mtd/parsers/redboot.c                      |   9 +-
 include/linux/mtd/nand-qpic-common.h               |   2 +
 include/linux/mtd/spinand.h                        |   1 +
 include/linux/platform_data/mtd-nand-omap2.h       |   7 -
 21 files changed, 576 insertions(+), 77 deletions(-)
 create mode 100644 drivers/mtd/nand/spi/heyangtek.c


From mwalle at kernel.org  Fri Aug 14 00:30:47 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 09:30:47 +0200
Subject: [PATCH v3 01/23] mtd: spi-nor: Rename
 BFPT_DWORD15_QER_SR2_BIT1_BUGGY
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-1-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-1-b637cf120d5c@bootlin.com>
Message-ID: <DKOHON3JP11L.1VAWG1VGCDUGM@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> The _BUGGY suffix is not super accurate. There is a behavior that indeed
> seems strange, but it is somewhat documented. The fact is, with this QER
> field the status register is considered 16-bit wide and if one writes
> the first 8 bits (SR1) without the next 8 bits (SR2) then the content of
> SR2 will be assumed to be 0x00 and will be cleared.
>
> Rename it BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR to align with the other
> namings (_NO_RD) and clarify the meaning of this field.
>
> No functional change.
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
Reviewed-by: Michael Walle <mwalle at kernel.org>

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/530fdb18/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 00:31:06 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 09:31:06 +0200
Subject: [PATCH v3 02/23] mtd: spi-nor: Create a structure containing
 the flash opcodes
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-2-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-2-b637cf120d5c@bootlin.com>
Message-ID: <DKOHOVZ9UJ41.2FS5JYPQ2WBTU@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> Create a structure named opcodes in the flash parameters structures to
> store all the flash specific opcodes. They are typically discovered
> dynamically through SFDP parsing. Move the die_erase opcode
> inside. Later on we will add new fields there, like the supported
> opcodes for accessing the status registers, which are almost vendor
> specific.
>
> No fonctional change.
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/3066b9b8/attachment.sig>

From dominique.martinet at atmark-techno.com  Fri Aug 14 00:36:53 2026
From: dominique.martinet at atmark-techno.com (Dominique Martinet)
Date: Fri, 14 Aug 2026 07:36:53 +0000
Subject: [PATCH mtd-utils] mtd-tests/flash_stress: allow picking any erase block
Message-ID: <20260814073653.7-1-dominique.martinet@atmark-techno.com>

unlike the kernel module mtd_stresstest, the read and write operations
in this test program only ever span a single erase block, so we can pick
any erase block in rand_eb()
---
Noticed this while looking at the code (to get a rough idea of how many
erase cycles I'm going through on average for each erase block with this)

 tests/mtd-tests/flash_stress.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/tests/mtd-tests/flash_stress.c b/tests/mtd-tests/flash_stress.c
index da39e1408ed3..ada8596382e9 100644
--- a/tests/mtd-tests/flash_stress.c
+++ b/tests/mtd-tests/flash_stress.c
@@ -150,9 +150,8 @@ static int rand_eb(void)
 {
 	unsigned int eb;
 
-	/* Read or write up 2 eraseblocks at a time - hence 'mtd.eb_cnt - 1' */
 	do {
-		eb = rand() % (mtd.eb_cnt - 1);
+		eb = rand() % mtd.eb_cnt;
 	} while (bbt[eb]);
 
 	return eb;
-- 
2.55.0.dirty




From dominique.martinet at atmark-techno.com  Fri Aug 14 01:12:11 2026
From: dominique.martinet at atmark-techno.com (Dominique Martinet)
Date: Fri, 14 Aug 2026 08:12:11 +0000
Subject: [PATCH v3] mtd: spinand: winbond: add support for W25N04LW
Message-ID: <20260814-w25n04lw-v3-1-c6a7423ac7d0@atmark-techno.com>

Add support for Winbond W25N04LW 4Gbit SPI-NAND:
> 1.8V 4G-bit
> Raw serial SLC NAND flash memory
> Dual/Quad SPI with 104MHz
> buffer read, continuous read & sequential read

Highlights copied from datasheet:
? Page size: 4,352 Bytes (4096 + 256 Bytes)
? Block size: 64 pages (256K + 16K Bytes)
- Built-in 8-Bit ECC for memory array
? ECC status bits indicate ECC results
? Bad Block Management and LUT access
? Software and Hardware Write-Protect
? Power Supply Lock-Down and OTP protection
? Unique ID and Parameter page
? Ten 4KB OTP pages per die
? Read Level Setting for Read Retry

The 4096+256 byte page layout is as follow:
- 4096 bytes ECC protected "main memory array", subdivided in 8
  512 bytes sectors
- 128 bytes spare array, subdivided in 8 16 bytes sectors as follow:
  - 2 bytes bad block marker (no ECC protection)
  - 2 bytes "user data II" (no ECC protection)
  - 12 bytes "user data I" (ECC protected)
- 128 bytes parity array, subdivided in 8 16 bytes sectors:
  - 13 bytes ECC for main data and user data
  - 3 unused bytes

This commit does not support most advanced features such as continuous
read, LUT management and OTP pages, and only provides basic usage.

OOB layout was kept compatible with previous models (exposing both user
data I and II), dependent on mtd->oobsize.

Co-developed-by: Daisuke Mizobuchi <mizo at atmark-techno.com>
Signed-off-by: Daisuke Mizobuchi <mizo at atmark-techno.com>
Signed-off-by: Dominique Martinet <dominique.martinet at atmark-techno.com>
---
Changes in v3:
- Fixed Co-authored-by / comment style
- Merged ooblayout helper with w25n02kv_ooblayout, using mtd->oobsize
(with an extra comment describing User Data I/II, but keeping current
behaviour)
- Link to v2: https://patch.msgid.link/20260812-w25n04lw-v2-1-deee97602fc4 at atmark-techno.com

Changes in v2:
- Link to v1: https://patch.msgid.link/ansYUoNKJyDQ+6f2 at hu-mdalam-blr.qualcomm.com
- fixed oob layout
- fixed NAND_MEMORG oob size

Testwise:
- debugfs ooblayout_ecc/ooblayout_free is fine with new version
- I still need to check about the high number of ecc errors next week,
but I don't think it's likely to be related to this patch so sending
ahead... Feel free to wait a bit (there's no hurry on my end for this to
get into 7.3; it's fine to skip a cycle as you see fit)
- I checked the SPINAND_INFO_OP_VARIANTS and there is no problem with
current values for this model.

Thanks!

-Dominique
---
 drivers/mtd/nand/spi/winbond.c | 23 ++++++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
index 9b78c1e6cbc9..171f4c264220 100644
--- a/drivers/mtd/nand/spi/winbond.c
+++ b/drivers/mtd/nand/spi/winbond.c
@@ -314,10 +314,11 @@ static int w25n01kv_ooblayout_ecc(struct mtd_info *mtd, int section,
 static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section,
 				  struct mtd_oob_region *region)
 {
-	if (section > 3)
+	/* 4 sections for 128 bytes, 8 for 256 */
+	if (section >= mtd->oobsize / 32)
 		return -ERANGE;
 
-	region->offset = 64 + (16 * section);
+	region->offset = (mtd->oobsize / 2) + (16 * section);
 	region->length = 13;
 
 	return 0;
@@ -326,9 +327,16 @@ static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section,
 static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section,
 				   struct mtd_oob_region *region)
 {
-	if (section > 3)
+	if (section >= mtd->oobsize / 32)
 		return -ERANGE;
 
+	/*
+	 * For at least W25N04KW and W25N04LW, this region is actually
+	 * split in two:
+	 * ECC-protected "User Data I" at + 4 offset, length 12
+	 * unprotected "User Data II" at + 2 offset, length 2
+	 * This returns the full region for backwards compatibility.
+	 */
 	region->offset = (16 * section) + 2;
 	region->length = 14;
 
@@ -768,6 +776,15 @@ static const struct spinand_info winbond_spinand_table[] = {
 					      &update_cache_variants),
 		     0,
 		     SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
+	SPINAND_INFO("W25N04LW", /* 1.8V */
+		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb2, 0x23),
+		     NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
+		     NAND_ECCREQ(8, 512),
+		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+					      &write_cache_variants,
+					      &update_cache_variants),
+		     0,
+		     SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
 	SPINAND_INFO("W35N04JW", /* 1.8V */
 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x23),
 		     NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 4, 1),

---
base-commit: db2ddb87143519e20a95aa36c60b36107b736a58
change-id: 20260812-w25n04lw-3e9146d8340f

Best regards,
--  
Dominique Martinet <dominique.martinet at atmark-techno.com>




From dominique.martinet at atmark-techno.com  Fri Aug 14 01:15:58 2026
From: dominique.martinet at atmark-techno.com (Dominique Martinet)
Date: Fri, 14 Aug 2026 08:15:58 +0000
Subject: [PATCH RFC] mtd: spinand: winbond: enable continuous read for
 W25N04LW
Message-ID: <20260814-w25n04lw-contread-v1-1-2968b07c962d@atmark-techno.com>

This enables continuous read for W25N04LW:
- The ops are basically the same as cont_read_cache_dual_quad_dtr_variants
without the speed limit (this chip has no HFREQ flag that would change
the required delays) and without DTR ops (not supported)
- w25n0xjw_set_cont_read() can be reused

Unfortunately on my system (i.MX 8ULP LPSPI) the first time continuous
read is used spinand_read_from_cache_op() falls into this if and
disables continuous read, so this didn't go any further:
     /*
      * Dirmap accesses are allowed to toggle the CS.
      * Toggling the CS during a continuous read is forbidden.
      */
     if (nbytes && req->continuous) {
             /*
              * Spi controller with broken support of continuous
              * reading was detected. Disable future use of
              * continuous reading and return -EAGAIN to retry
              * reading within regular mode.
              */
             spinand->cont_read_possible = false;
             return -EAGAIN;
     }

I'm leaving this patch for anyone who could have hardware for this (or
eventually myself after I have time to check the SPI driver...)

Fun fact:
nanddump -C is about 9% faster than nanddump on large data (tried 10MB)
even if continuous read is not supported.

Signed-off-by: Dominique Martinet <dominique.martinet at atmark-techno.com>
---
 drivers/mtd/nand/spi/winbond.c | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
index 171f4c264220..ad735f73945b 100644
--- a/drivers/mtd/nand/spi/winbond.c
+++ b/drivers/mtd/nand/spi/winbond.c
@@ -203,6 +203,13 @@ static SPINAND_OP_VARIANTS(cont_read_cache_dual_quad_dtr_variants,
 		/* The 1S_1D_1D variant would require 4.5 dummy bytes, this is not possible */
 		WINBOND_CONT_READ_FROM_CACHE_FAST_1S_1S_1S_OP(4, NULL, 0, 0));
 
+static SPINAND_OP_VARIANTS(cont_read_cache_variants,
+		WINBOND_CONT_READ_FROM_CACHE_1S_4S_4S_OP(6, NULL, 0, 0),
+		WINBOND_CONT_READ_FROM_CACHE_1S_1S_4S_OP(4, NULL, 0, 0),
+		WINBOND_CONT_READ_FROM_CACHE_1S_2S_2S_OP(4, NULL, 0, 0),
+		WINBOND_CONT_READ_FROM_CACHE_1S_1S_2S_OP(4, NULL, 0, 0),
+		WINBOND_CONT_READ_FROM_CACHE_FAST_1S_1S_1S_OP(4, NULL, 0, 0));
+
 static SPINAND_OP_VARIANTS(read_cache_variants,
 		SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0),
 		SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
@@ -780,11 +787,13 @@ static const struct spinand_info winbond_spinand_table[] = {
 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb2, 0x23),
 		     NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
 		     NAND_ECCREQ(8, 512),
-		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
-					      &write_cache_variants,
-					      &update_cache_variants),
+		     SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_variants,
+							&write_cache_variants,
+							&update_cache_variants,
+							&cont_read_cache_variants),
 		     0,
-		     SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
+		     SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status),
+		     SPINAND_CONT_READ(w25n0xjw_set_cont_read)),
 	SPINAND_INFO("W35N04JW", /* 1.8V */
 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x23),
 		     NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 4, 1),

---
base-commit: 0f1b5abff55b580facccc16324ede60b048bd23c
change-id: 20260814-w25n04lw-contread-03485e60c88e
prerequisite-change-id: 20260812-w25n04lw-3e9146d8340f:v3
prerequisite-patch-id: 3fbc1f74bfc7bbcd5dd7ecf62a7e5b0352cacbb2

Best regards,
--  
Dominique Martinet <dominique.martinet at atmark-techno.com>




From mwalle at kernel.org  Fri Aug 14 02:40:21 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:40:21 +0200
Subject: [PATCH v3 05/23] mtd: spi-nor: Create the concept of fixup
 table with match function
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-5-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-5-b637cf120d5c@bootlin.com>
Message-ID: <DKOKFU8AD2U6.151AX0VOBX67H@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> Manufacturer ID tables increase and fixup() hooks proliferate. Having
> one possible structure per chip was fine until the fixups started being
> more and more common and needed, to some extend due to ID reuses. Mixing
> fixups and chips becomes hard and requires extra helpers to sort which
> ones are needed for a given chip, which every time this happens requires
> a lot of rework.

Thanks, this patch looks really promising!

> Replace the two-level fixup association (a manufacturer wide hook and a
> per flash_info hook) with a per-manufacturer list of fixups that can be
> looked up by flash ID and/or match function.
>
> The match logic works as follows:
> - If there is an ID, it must match
> - If there is a match function, it must match (cumulative)
> - If there is no identifier (no ID nor any match function), it's a
>   catch-all entry typically used for flagging manufacturer fixups.

This mimics, how it's currently handled. But since the fixups are
now treated as a separate list, i.e. it has it's own SNOR_ID, we
shall move away from this manufacturer thingy too. IMHO that really
clutters the code. As a fun fact, spi_nor_match_id() will have the
unexpected side effect of setting nor->manufacturer.

So here's how I'd do it: 
(1) remove spi_nor_manufacturer.
(2) have a list of initcalls in the core to call into the individual
    vendor modules (that could later be replaced by something more
    sophiticated)
(3) that initcall will then call spi_nor_register_parts(const struct
    flash_info *parts, int nparts)
(4) (optionally) calls spi_nor_register_fixups(const struct
    spi_nor_fixup *fixups, int nfixups)

Also both could be a macro, then we can get rid of the size
argument.

The current manufacturer fixups gets registered with a
"SNOR_ID(vendor)". 

That leaves us with the manufacturer name, which should be set by
that vendor catch all fixup.

Btw, I'm fine with deprecating the name as well as the manufacturer
sysfs entry, but it shouldn't be as in "it just goes away". There
should be a transition period. Although I'm not sure, how a user
could be notified, that this property will go away soon, honestly.
Print a warning on the kernel buffer? Not sure someone actually
notice that. Put it behind a Kconfig option which is "default y"
then after some time, that default is dropped and eventually removed
altogether? Looks like quite some churn and distros might just
enable it in their config. Any other ideas?

> Suggested-by: Michael Walle <mwalle at kernel.org>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
> ---
>  drivers/mtd/spi-nor/atmel.c      | 23 ++++++-----
>  drivers/mtd/spi-nor/core.c       | 82 ++++++++++++++++++++++++++++------------
>  drivers/mtd/spi-nor/core.h       | 23 +++++++++--
>  drivers/mtd/spi-nor/everspin.c   |  7 +++-
>  drivers/mtd/spi-nor/gigadevice.c |  7 +++-
>  drivers/mtd/spi-nor/issi.c       | 22 ++++++++---
>  drivers/mtd/spi-nor/macronix.c   | 23 ++++++-----
>  drivers/mtd/spi-nor/micron-st.c  | 43 +++++++++++++++------
>  drivers/mtd/spi-nor/sfdp.c       | 56 ++++++++++++++++++---------
>  drivers/mtd/spi-nor/spansion.c   | 41 +++++++++++---------
>  drivers/mtd/spi-nor/sst.c        | 11 ++++--
>  drivers/mtd/spi-nor/winbond.c    | 17 ++++++---
>  12 files changed, 247 insertions(+), 108 deletions(-)
>
> diff --git a/drivers/mtd/spi-nor/atmel.c b/drivers/mtd/spi-nor/atmel.c
> index 8741c0bda497..bccc702a2c4a 100644
> --- a/drivers/mtd/spi-nor/atmel.c
> +++ b/drivers/mtd/spi-nor/atmel.c
> @@ -194,47 +194,40 @@ static const struct flash_info atmel_nor_parts[] = {
>  		.size = SZ_512K,
>  		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
>  		.no_sfdp_flags = SECT_4K,
> -		.fixups = &atmel_nor_global_protection_fixups,
>  	}, {
>  		.id = SNOR_ID(0x1f, 0x45, 0x01),
>  		.name = "at26df081a",
>  		.size = SZ_1M,
>  		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
>  		.no_sfdp_flags = SECT_4K,
> -		.fixups = &atmel_nor_global_protection_fixups
>  	}, {
>  		.id = SNOR_ID(0x1f, 0x46, 0x01),
>  		.name = "at26df161a",
>  		.size = SZ_2M,
>  		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
>  		.no_sfdp_flags = SECT_4K,
> -		.fixups = &atmel_nor_global_protection_fixups
>  	}, {
>  		.id = SNOR_ID(0x1f, 0x47, 0x00),
>  		.name = "at25df321",
>  		.size = SZ_4M,
>  		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
>  		.no_sfdp_flags = SECT_4K,
> -		.fixups = &atmel_nor_global_protection_fixups
>  	}, {
>  		.id = SNOR_ID(0x1f, 0x47, 0x01),
>  		.name = "at25df321a",
>  		.size = SZ_4M,
>  		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
>  		.no_sfdp_flags = SECT_4K,
> -		.fixups = &atmel_nor_global_protection_fixups
>  	}, {
>  		.id = SNOR_ID(0x1f, 0x47, 0x08),
>  		.name = "at25ff321a",
>  		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
> -		.fixups = &atmel_nor_global_protection_fixups
>  	}, {
>  		.id = SNOR_ID(0x1f, 0x48, 0x00),
>  		.name = "at25df641",
>  		.size = SZ_8M,
>  		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
>  		.no_sfdp_flags = SECT_4K,
> -		.fixups = &atmel_nor_global_protection_fixups
>  	}, {
>  		.id = SNOR_ID(0x1f, 0x66, 0x01),
>  		.name = "at25fs010",
> @@ -242,14 +235,12 @@ static const struct flash_info atmel_nor_parts[] = {
>  		.size = SZ_128K,
>  		.flags = SPI_NOR_HAS_LOCK,
>  		.no_sfdp_flags = SECT_4K,
> -		.fixups = &at25fs_nor_fixups
>  	}, {
>  		.id = SNOR_ID(0x1f, 0x66, 0x04),
>  		.name = "at25fs040",
>  		.size = SZ_512K,
>  		.flags = SPI_NOR_HAS_LOCK,
>  		.no_sfdp_flags = SECT_4K,
> -		.fixups = &at25fs_nor_fixups
>  	}, {
>  		.id = SNOR_ID(0x1f, 0x87, 0x01),
>  		.size = SZ_4M,
> @@ -257,8 +248,22 @@ static const struct flash_info atmel_nor_parts[] = {
>  	},
>  };
>  
> +static const struct spi_nor_fixup atmel_fixups[] = {
> +	{ .id = SNOR_ID(0x1f, 0x44, 0x01), .fixups = &atmel_nor_global_protection_fixups },
> +	{ .id = SNOR_ID(0x1f, 0x45, 0x01), .fixups = &atmel_nor_global_protection_fixups },
> +	{ .id = SNOR_ID(0x1f, 0x46, 0x01), .fixups = &atmel_nor_global_protection_fixups },
> +	{ .id = SNOR_ID(0x1f, 0x47, 0x00), .fixups = &atmel_nor_global_protection_fixups },
> +	{ .id = SNOR_ID(0x1f, 0x47, 0x01), .fixups = &atmel_nor_global_protection_fixups },
> +	{ .id = SNOR_ID(0x1f, 0x47, 0x08), .fixups = &atmel_nor_global_protection_fixups },
> +	{ .id = SNOR_ID(0x1f, 0x48, 0x00), .fixups = &atmel_nor_global_protection_fixups },
> +	{ .id = SNOR_ID(0x1f, 0x66, 0x01), .fixups = &at25fs_nor_fixups },
> +	{ .id = SNOR_ID(0x1f, 0x66, 0x04), .fixups = &at25fs_nor_fixups },
> +};
> +
>  const struct spi_nor_manufacturer spi_nor_atmel = {
>  	.name = "atmel",
>  	.parts = atmel_nor_parts,
>  	.nparts = ARRAY_SIZE(atmel_nor_parts),
> +	.fixups = atmel_fixups,
> +	.nfixups = ARRAY_SIZE(atmel_fixups),
>  };
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index ac7812562047..3a611283a9e6 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
> @@ -2367,19 +2367,24 @@ int spi_nor_post_bfpt_fixups(struct spi_nor *nor,
>  			     const struct sfdp_parameter_header *bfpt_header,
>  			     const struct sfdp_bfpt *bfpt)
>  {
> +	const struct spi_nor_fixup *fixups;
>  	int ret;
> +	unsigned int i;
>  
> -	if (nor->manufacturer && nor->manufacturer->fixups &&
> -	    nor->manufacturer->fixups->post_bfpt) {
> -		ret = nor->manufacturer->fixups->post_bfpt(nor, bfpt_header,
> -							   bfpt);
> -		if (ret)
> -			return ret;
> +	if (!nor->manufacturer || !nor->manufacturer->fixups)
> +		return 0;
> +
> +	fixups = nor->manufacturer->fixups;
> +
> +	for (i = 0; i < nor->manufacturer->nfixups; i++) {
> +		if (fixups[i].fixups->post_bfpt &&
> +		    spi_nor_fixup_match(nor, &fixups[i])) {
> +			ret = fixups[i].fixups->post_bfpt(nor, bfpt_header, bfpt);
> +			if (ret)
> +				return ret;
> +		}

This is repeated several times, can we have something like

spi_nor_apply_fixups(struct spi_nor *nor, int (*fixup_fn), void *fixup_args);

int spi_nor_post_bfpt_fixup(struct spi_nor *nor, const struct spi_nor_fixup *fixup, void *_args)
{
	struct post_bfpt_fixup_args *args = _args;

	if (!fixup->post_bfpt)
		return 0;

	return fixup->post_bfpt(nor, args->bfpt_header, args->bfpt);
}

which then will be called with
spi_nor_apply_fixups(nor, spi_nor_post_bfpt_fixup, args);

> +	for (i = 0; i < nor->manufacturer->nfixups; i++) {
> +		if (fixups[i].fixups->post_bfpt &&
> +		    spi_nor_fixup_match(nor, &fixups[i])) {
> +			ret = fixups[i].fixups->post_bfpt(nor, bfpt_header, bfpt);
> +			if (ret)
> +				return ret;
>  	}
>  
> -	if (nor->info->fixups && nor->info->fixups->post_bfpt)
> -		return nor->info->fixups->post_bfpt(nor, bfpt_header, bfpt);
> -
>  	return 0;
>  }
>  
> @@ -2636,6 +2641,26 @@ static int spi_nor_setup(struct spi_nor *nor,
>  	return spi_nor_set_addr_nbytes(nor);
>  }
>  
> +bool spi_nor_fixup_match(const struct spi_nor *nor,
> +			 const struct spi_nor_fixup *fixup)
> +{
> +	const struct spi_nor_id *id = nor->info ? nor->info->id : NULL;
> +
> +	/* Filter by ID first, if available */
> +	if (fixup->id) {
> +		if (!id || fixup->id->len > id->len ||
> +		    memcmp(id->bytes, fixup->id->bytes, fixup->id->len))
> +			return false;
> +	}
> +
> +	/* Further filter with the match callback, if provided */
> +	if (fixup->match)
> +		return fixup->match(nor);
> +
> +	/* Either there was an ID and it matched, or it is a catch-all entry */
> +	return true;
> +}
> +
>  /**
>   * spi_nor_manufacturer_init_params() - Initialize the flash's parameters and
>   * settings based on MFR register and ->default_init() hook.
> @@ -2643,12 +2668,19 @@ static int spi_nor_setup(struct spi_nor *nor,
>   */
>  static void spi_nor_manufacturer_init_params(struct spi_nor *nor)
>  {
> -	if (nor->manufacturer && nor->manufacturer->fixups &&
> -	    nor->manufacturer->fixups->default_init)
> -		nor->manufacturer->fixups->default_init(nor);
> +	const struct spi_nor_fixup *fixups;
> +	unsigned int i;
>  
> -	if (nor->info->fixups && nor->info->fixups->default_init)
> -		nor->info->fixups->default_init(nor);
> +	if (!nor->manufacturer || !nor->manufacturer->fixups)
> +		return;
> +
> +	fixups = nor->manufacturer->fixups;
> +
> +	for (i = 0; i < nor->manufacturer->nfixups; i++) {
> +		if (fixups[i].fixups->default_init &&
> +		    spi_nor_fixup_match(nor, &fixups[i]))
> +			fixups[i].fixups->default_init(nor);
> +	}
>  }
>  
>  /**
> @@ -2797,22 +2829,24 @@ static void spi_nor_init_fixup_flags(struct spi_nor *nor)
>  static int spi_nor_late_init_params(struct spi_nor *nor)
>  {
>  	struct spi_nor_flash_parameter *params = nor->params;
> +	const struct spi_nor_fixup *fixups;
> +	unsigned int i;
>  	int ret;
>  
>  	/* Needed by some late_init hooks */
>  	spi_nor_init_flags(nor);
>  
> -	if (nor->manufacturer && nor->manufacturer->fixups &&
> -	    nor->manufacturer->fixups->late_init) {
> -		ret = nor->manufacturer->fixups->late_init(nor);
> -		if (ret)
> -			return ret;
> -	}
> +	if (nor->manufacturer && nor->manufacturer->fixups) {
> +		fixups = nor->manufacturer->fixups;
>  
> -	if (nor->info->fixups && nor->info->fixups->late_init) {
> -		ret = nor->info->fixups->late_init(nor);
> -		if (ret)
> -			return ret;
> +		for (i = 0; i < nor->manufacturer->nfixups; i++) {
> +			if (fixups[i].fixups->late_init &&
> +			    spi_nor_fixup_match(nor, &fixups[i])) {
> +				ret = fixups[i].fixups->late_init(nor);
> +				if (ret)
> +					return ret;
> +			}
> +		}
>  	}
>  
>  	if (!nor->params->opcodes.die_erase)
> diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
> index 7d43d3ba47ee..b650778a10de 100644
> --- a/drivers/mtd/spi-nor/core.h
> +++ b/drivers/mtd/spi-nor/core.h
> @@ -445,6 +445,19 @@ struct spi_nor_fixups {
>  	int (*late_init)(struct spi_nor *nor);
>  };
>  
> +/**
> + * struct spi_nor_fixup - SPI NOR fixup registration.
> + * @id:		(optional) flash ID this fixup applies to, may only match the
> + *		ID prefix, eg. just the first few bytes to match a whole family
> + * @match:	(optional) custom match function (can be used together with @id)
> + * @fixups:	the fixup hooks to apply when this entry matches
> + */
> +struct spi_nor_fixup {
> +	const struct spi_nor_id *id;
> +	bool (*match)(const struct spi_nor *nor);
> +	const struct spi_nor_fixups *fixups;
> +};
> +
>  /**
>   * struct spi_nor_id - SPI NOR flash ID.
>   *
> @@ -560,7 +573,6 @@ struct flash_info {
>  	u8 mfr_flags;
>  
>  	const struct spi_nor_otp_organization *otp;
> -	const struct spi_nor_fixups *fixups;
>  };
>  
>  #define SNOR_ID(...)							\
> @@ -582,13 +594,16 @@ struct flash_info {
>   * @name: manufacturer name
>   * @parts: array of parts supported by this manufacturer
>   * @nparts: number of entries in the parts array
> - * @fixups: hooks called at various points in time during spi_nor_scan()
> + * @fixups: list of fixups, each matched by ID (or a custom match function),
> + *          applied to any part of this manufacturer.
> + * @nfixups: number of entries in the fixups array
>   */
>  struct spi_nor_manufacturer {
>  	const char *name;
>  	const struct flash_info *parts;
>  	unsigned int nparts;
> -	const struct spi_nor_fixups *fixups;
> +	const struct spi_nor_fixup *fixups;
> +	unsigned int nfixups;
>  };
>  
>  /**
> @@ -619,6 +634,8 @@ extern const struct spi_nor_manufacturer spi_nor_xmc;
>  
>  extern const struct attribute_group *spi_nor_sysfs_groups[];
>  
> +bool spi_nor_fixup_match(const struct spi_nor *nor,
> +			 const struct spi_nor_fixup *fixup);
>  void spi_nor_spimem_setup_op(const struct spi_nor *nor,
>  			     struct spi_mem_op *op,
>  			     const enum spi_nor_protocol proto);
> diff --git a/drivers/mtd/spi-nor/everspin.c b/drivers/mtd/spi-nor/everspin.c
> index add37104d673..864dffba0e9c 100644
> --- a/drivers/mtd/spi-nor/everspin.c
> +++ b/drivers/mtd/spi-nor/everspin.c
> @@ -44,9 +44,14 @@ static const struct spi_nor_fixups everspin_nor_fixups = {
>  	.default_init = everspin_nor_default_init,
>  };
>  
> +static const struct spi_nor_fixup everspin_fixups[] = {
> +	{ .fixups = &everspin_nor_fixups },
> +};
> +
>  const struct spi_nor_manufacturer spi_nor_everspin = {
>  	.name = "everspin",
>  	.parts = everspin_nor_parts,
>  	.nparts = ARRAY_SIZE(everspin_nor_parts),
> -	.fixups = &everspin_nor_fixups,
> +	.fixups = everspin_fixups,
> +	.nfixups = ARRAY_SIZE(everspin_fixups),
>  };
> diff --git a/drivers/mtd/spi-nor/gigadevice.c b/drivers/mtd/spi-nor/gigadevice.c
> index 4070a692e968..f76fd0dedd0a 100644
> --- a/drivers/mtd/spi-nor/gigadevice.c
> +++ b/drivers/mtd/spi-nor/gigadevice.c
> @@ -64,7 +64,6 @@ static const struct flash_info gigadevice_nor_parts[] = {
>  		.id = SNOR_ID(0xc8, 0x40, 0x19),
>  		.name = "gd25q256",
>  		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6,
> -		.fixups = &gd25q256_fixups,
>  		.fixup_flags = SPI_NOR_4B_OPCODES,

The fixup_flags should also go into the fixups list.

>  	}, {
>  		.id = SNOR_ID(0xc8, 0x60, 0x16),
> @@ -87,8 +86,14 @@ static const struct flash_info gigadevice_nor_parts[] = {
>  	},
>  };
>  
> +static const struct spi_nor_fixup gigadevice_fixups[] = {
> +	{ .id = SNOR_ID(0xc8, 0x40, 0x19), .fixups = &gd25q256_fixups },
> +};
> +
>  const struct spi_nor_manufacturer spi_nor_gigadevice = {
>  	.name = "gigadevice",
>  	.parts = gigadevice_nor_parts,
>  	.nparts = ARRAY_SIZE(gigadevice_nor_parts),
> +	.fixups = gigadevice_fixups,
> +	.nfixups = ARRAY_SIZE(gigadevice_fixups),
>  };
> diff --git a/drivers/mtd/spi-nor/issi.c b/drivers/mtd/spi-nor/issi.c
> index 04db42b5141e..4842f8acd074 100644
> --- a/drivers/mtd/spi-nor/issi.c
> +++ b/drivers/mtd/spi-nor/issi.c
> @@ -52,13 +52,11 @@ static const struct flash_info issi_nor_parts[] = {
>  		.sector_size = SZ_32K,
>  		.size = SZ_64K,
>  		.no_sfdp_flags = SECT_4K,
> -		.fixups = &pm25lv_nor_fixups
>  	}, {
>  		.name = "pm25lv010",
>  		.sector_size = SZ_32K,
>  		.size = SZ_128K,
>  		.no_sfdp_flags = SECT_4K,
> -		.fixups = &pm25lv_nor_fixups
>  	}, {
>  		.id = SNOR_ID(0x7f, 0x9d, 0x20),
>  		.name = "is25cd512",
> @@ -103,7 +101,6 @@ static const struct flash_info issi_nor_parts[] = {
>  	}, {
>  		.id = SNOR_ID(0x9d, 0x60, 0x19),
>  		.name = "is25lp256",
> -		.fixups = &is25lp256_fixups,
>  		.fixup_flags = SPI_NOR_4B_OPCODES,
>  	}, {
>  		.id = SNOR_ID(0x9d, 0x70, 0x16),
> @@ -124,7 +121,6 @@ static const struct flash_info issi_nor_parts[] = {
>  		.id = SNOR_ID(0x9d, 0x70, 0x19),
>  		.name = "is25wp256",
>  		.flags = SPI_NOR_QUAD_PP,
> -		.fixups = &is25lp256_fixups,
>  		.fixup_flags = SPI_NOR_4B_OPCODES,
>  	}
>  };
> @@ -139,9 +135,25 @@ static const struct spi_nor_fixups issi_fixups = {
>  	.default_init = issi_nor_default_init,
>  };
>  
> +/* PM25LV parts have no JEDEC ID and are likely matched by name */
> +static bool issi_pm25lv_match(const struct spi_nor *nor)
> +{
> +	const char *name = nor->info ? nor->info->name : NULL;
> +
> +	return name && !strncmp(name, "pm25lv", 6);

Won't just strcmp("pm25lv", name) do it? Both arguments are
guaranteed to be NUL terminated. The above would also match
"pm25lvextrabytes", no, and we get rid of that redundant size
argument.

> +}
> +
> +static const struct spi_nor_fixup issi_fixup_list[] = {
> +	{ .fixups = &issi_fixups },
> +	{ .match = issi_pm25lv_match, .fixups = &pm25lv_nor_fixups },
> +	{ .id = SNOR_ID(0x9d, 0x60, 0x19), .fixups = &is25lp256_fixups },
> +	{ .id = SNOR_ID(0x9d, 0x70, 0x19), .fixups = &is25lp256_fixups },
> +};
> +
>  const struct spi_nor_manufacturer spi_nor_issi = {
>  	.name = "issi",
>  	.parts = issi_nor_parts,
>  	.nparts = ARRAY_SIZE(issi_nor_parts),
> -	.fixups = &issi_fixups,
> +	.fixups = issi_fixup_list,
> +	.nfixups = ARRAY_SIZE(issi_fixup_list),
>  };
> diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c
> index 55612237c1ef..04b431bf5f13 100644
> --- a/drivers/mtd/spi-nor/macronix.c
> +++ b/drivers/mtd/spi-nor/macronix.c
> @@ -137,20 +137,16 @@ static const struct flash_info macronix_nor_parts[] = {
>  	}, {
>  		/* MX25L25635E, MX25L25645G */
>  		.id = SNOR_ID(0xc2, 0x20, 0x19),
> -		.fixups = &mx25l25635_fixups
>  	}, {
>  		/* MX66L51235F */
>  		.id = SNOR_ID(0xc2, 0x20, 0x1a),
>  		.fixup_flags = SPI_NOR_4B_OPCODES,
> -		.fixups = &macronix_qpp4b_fixups,
>  	}, {
>  		/* MX66L1G45G */
>  		.id = SNOR_ID(0xc2, 0x20, 0x1b),
> -		.fixups = &macronix_qpp4b_fixups,
>  	}, {
>  		/* MX66L2G45G */
>  		.id = SNOR_ID(0xc2, 0x20, 0x1c),
> -		.fixups = &macronix_qpp4b_fixups,
>  	}, {
>  		.id = SNOR_ID(0xc2, 0x23, 0x14),
>  		.name = "mx25v8035f",
> @@ -189,15 +185,12 @@ static const struct flash_info macronix_nor_parts[] = {
>  	}, {
>  		/* MX25U51245G */
>  		.id = SNOR_ID(0xc2, 0x25, 0x3a),
> -		.fixups = &macronix_qpp4b_fixups,
>  	}, {
>  		/* MX66U1G45G */
>  		.id = SNOR_ID(0xc2, 0x25, 0x3b),
> -		.fixups = &macronix_qpp4b_fixups,
>  	}, {
>  		/* MX66U2G45G */
>  		.id = SNOR_ID(0xc2, 0x25, 0x3c),
> -		.fixups = &macronix_qpp4b_fixups,
>  	}, {
>  		.id = SNOR_ID(0xc2, 0x26, 0x18),
>  		.name = "mx25l12855e",
> @@ -229,7 +222,6 @@ static const struct flash_info macronix_nor_parts[] = {
>  	}, {
>  		/* MX25L3255E */
>  		.id = SNOR_ID(0xc2, 0x9e, 0x16),
> -		.fixups = &mx25l3255e_fixups,
>  	},
>  	/*
>  	 * This spares us of adding new flash entries for flashes that can be
> @@ -333,9 +325,22 @@ static const struct spi_nor_fixups macronix_nor_fixups = {
>  	.late_init = macronix_nor_late_init,
>  };
>  
> +static const struct spi_nor_fixup macronix_fixups[] = {
> +	{ .fixups = &macronix_nor_fixups },
> +	{ .id = SNOR_ID(0xc2, 0x20, 0x19), .fixups = &mx25l25635_fixups },
> +	{ .id = SNOR_ID(0xc2, 0x20, 0x1a), .fixups = &macronix_qpp4b_fixups },
> +	{ .id = SNOR_ID(0xc2, 0x20, 0x1b), .fixups = &macronix_qpp4b_fixups },
> +	{ .id = SNOR_ID(0xc2, 0x20, 0x1c), .fixups = &macronix_qpp4b_fixups },
> +	{ .id = SNOR_ID(0xc2, 0x25, 0x3a), .fixups = &macronix_qpp4b_fixups },
> +	{ .id = SNOR_ID(0xc2, 0x25, 0x3b), .fixups = &macronix_qpp4b_fixups },
> +	{ .id = SNOR_ID(0xc2, 0x25, 0x3c), .fixups = &macronix_qpp4b_fixups },
> +	{ .id = SNOR_ID(0xc2, 0x9e, 0x16), .fixups = &mx25l3255e_fixups },
> +};
> +
>  const struct spi_nor_manufacturer spi_nor_macronix = {
>  	.name = "macronix",
>  	.parts = macronix_nor_parts,
>  	.nparts = ARRAY_SIZE(macronix_nor_parts),
> -	.fixups = &macronix_nor_fixups,
> +	.fixups = macronix_fixups,
> +	.nfixups = ARRAY_SIZE(macronix_fixups),
>  };
> diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c
> index 344539d2a36a..2aabd6d487ba 100644
> --- a/drivers/mtd/spi-nor/micron-st.c
> +++ b/drivers/mtd/spi-nor/micron-st.c
> @@ -206,13 +206,11 @@ static const struct flash_info micron_nor_parts[] = {
>  		.id = SNOR_ID(0x2c, 0x5b, 0x1a),
>  		.mfr_flags = USE_FSR,
>  		.fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE,
> -		.fixups = &mt35xu512aba_fixups,
>  	}, {
>  		/* MT35XU01GBBA */
>  		.id = SNOR_ID(0x2c, 0x5b, 0x1b),
>  		.mfr_flags = USE_FSR,
>  		.fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE,
> -		.fixups = &mt35_two_die_fixups,
>  	}, {
>  		.id = SNOR_ID(0x2c, 0x5b, 0x1c),
>  		.name = "mt35xu02g",
> @@ -221,7 +219,6 @@ static const struct flash_info micron_nor_parts[] = {
>  		.no_sfdp_flags = SECT_4K | SPI_NOR_OCTAL_READ,
>  		.mfr_flags = USE_FSR,
>  		.fixup_flags = SPI_NOR_4B_OPCODES | SPI_NOR_IO_MODE_EN_VOLATILE,
> -		.fixups = &mt35_two_die_fixups,
>  	},
>  };
>  
> @@ -233,6 +230,17 @@ static int mt25qu512a_post_bfpt_fixup(struct spi_nor *nor,
>  	return 0;
>  }
>  
> +/*
> + * n25q00a parts share the first same 3 ID bytes with mt25qu01g.
> + * In order to not mix the fixups, further filter out using the part name.

How would that work? The part name is also set by the duplicated ID,
so the first one wins, no?

-michael

> + */
> +static bool n25q00a_match(const struct spi_nor *nor)
> +{
> +	const char *name = nor->info ? nor->info->name : NULL;
> +
> +	return name && !strncmp(name, "n25q00a", 7);
> +}
> +
>  static const struct spi_nor_fixups mt25qu512a_fixups = {
>  	.post_bfpt = mt25qu512a_post_bfpt_fixup,
>  };
> @@ -426,14 +434,12 @@ static const struct flash_info st_nor_parts[] = {
>  			 SPI_NOR_BP3_SR_BIT6,
>  		.no_sfdp_flags = SECT_4K | SPI_NOR_QUAD_READ,
>  		.mfr_flags = USE_FSR,
> -		.fixups = &n25q00_fixups,
>  	}, {
>  		.id = SNOR_ID(0x20, 0xba, 0x22),
>  		.name = "mt25ql02g",
>  		.size = SZ_256M,
>  		.no_sfdp_flags = SECT_4K | SPI_NOR_QUAD_READ,
>  		.mfr_flags = USE_FSR,
> -		.fixups = &mt25q02_fixups,
>  	}, {
>  		.id = SNOR_ID(0x20, 0xbb, 0x15),
>  		.name = "n25q016a",
> @@ -480,7 +486,6 @@ static const struct flash_info st_nor_parts[] = {
>  		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_4BIT_BP |
>  			 SPI_NOR_BP3_SR_BIT6,
>  		.mfr_flags = USE_FSR,
> -		.fixups = &mt25qu512a_fixups,
>  	}, {
>  		.id = SNOR_ID(0x20, 0xbb, 0x20),
>  		.name = "n25q512a",
> @@ -493,21 +498,18 @@ static const struct flash_info st_nor_parts[] = {
>  		.id = SNOR_ID(0x20, 0xbb, 0x21, 0x10, 0x44, 0x00),
>  		.name = "mt25qu01g",
>  		.mfr_flags = USE_FSR,
> -		.fixups = &mt25q01_fixups,
>  	}, {
>  		.id = SNOR_ID(0x20, 0xbb, 0x21),
>  		.name = "n25q00a",
>  		.size = SZ_128M,
>  		.no_sfdp_flags = SECT_4K | SPI_NOR_QUAD_READ,
>  		.mfr_flags = USE_FSR,
> -		.fixups = &n25q00_fixups,
>  	}, {
>  		.id = SNOR_ID(0x20, 0xbb, 0x22),
>  		.name = "mt25qu02g",
>  		.size = SZ_256M,
>  		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
>  		.mfr_flags = USE_FSR,
> -		.fixups = &mt25q02_fixups,
>  	}
>  };
>  
> @@ -657,16 +659,35 @@ static const struct spi_nor_fixups micron_st_nor_fixups = {
>  	.late_init = micron_st_nor_late_init,
>  };
>  
> +static const struct spi_nor_fixup micron_fixups[] = {
> +	{ .fixups = &micron_st_nor_fixups },
> +	{ .id = SNOR_ID(0x2c, 0x5b, 0x1a), .fixups = &mt35xu512aba_fixups },
> +	{ .id = SNOR_ID(0x2c, 0x5b, 0x1b), .fixups = &mt35_two_die_fixups },
> +	{ .id = SNOR_ID(0x2c, 0x5b, 0x1c), .fixups = &mt35_two_die_fixups },
> +};
> +
> +static const struct spi_nor_fixup st_fixups[] = {
> +	{ .fixups = &micron_st_nor_fixups },
> +	{ .id = SNOR_ID(0x20, 0xba, 0x21), .fixups = &n25q00_fixups },
> +	{ .id = SNOR_ID(0x20, 0xba, 0x22), .fixups = &mt25q02_fixups },
> +	{ .id = SNOR_ID(0x20, 0xbb, 0x20, 0x10, 0x44, 0x00), .fixups = &mt25qu512a_fixups },
> +	{ .id = SNOR_ID(0x20, 0xbb, 0x21, 0x10, 0x44, 0x00), .fixups = &mt25q01_fixups },
> +	{ .id = SNOR_ID(0x20, 0xbb, 0x21), .match = n25q00a_match, .fixups = &n25q00_fixups },
> +	{ .id = SNOR_ID(0x20, 0xbb, 0x22), .fixups = &mt25q02_fixups },
> +};
> +
>  const struct spi_nor_manufacturer spi_nor_micron = {
>  	.name = "micron",
>  	.parts = micron_nor_parts,
>  	.nparts = ARRAY_SIZE(micron_nor_parts),
> -	.fixups = &micron_st_nor_fixups,
> +	.fixups = micron_fixups,
> +	.nfixups = ARRAY_SIZE(micron_fixups),
>  };
>  
>  const struct spi_nor_manufacturer spi_nor_st = {
>  	.name = "st",
>  	.parts = st_nor_parts,
>  	.nparts = ARRAY_SIZE(st_nor_parts),
> -	.fixups = &micron_st_nor_fixups,
> +	.fixups = st_fixups,
> +	.nfixups = ARRAY_SIZE(st_fixups),
>  };
> diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
> index 285ac781fba8..39cb8ac3a037 100644
> --- a/drivers/mtd/spi-nor/sfdp.c
> +++ b/drivers/mtd/spi-nor/sfdp.c
> @@ -727,12 +727,19 @@ 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);
> +	const struct spi_nor_fixup *fixups;
> +	unsigned int i;
>  
> -	if (nor->info->fixups && nor->info->fixups->smpt_read_dummy)
> -		nor->info->fixups->smpt_read_dummy(nor, read_dummy);
> +	if (!nor->manufacturer || !nor->manufacturer->fixups)
> +		return;
> +
> +	fixups = nor->manufacturer->fixups;
> +
> +	for (i = 0; i < nor->manufacturer->nfixups; i++) {
> +		if (fixups[i].fixups->smpt_read_dummy &&
> +		    spi_nor_fixup_match(nor, &fixups[i]))
> +			fixups[i].fixups->smpt_read_dummy(nor, read_dummy);
> +	}
>  }
>  
>  /**
> @@ -757,12 +764,19 @@ static u8 spi_nor_smpt_read_dummy(const struct spi_nor *nor, const u32 settings)
>  
>  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);
> +	const struct spi_nor_fixup *fixups;
> +	unsigned int i;
>  
> -	if (nor->info->fixups && nor->info->fixups->smpt_map_id)
> -		nor->info->fixups->smpt_map_id(nor, map_id);
> +	if (!nor->manufacturer || !nor->manufacturer->fixups)
> +		return;
> +
> +	fixups = nor->manufacturer->fixups;
> +
> +	for (i = 0; i < nor->manufacturer->nfixups; i++) {
> +		if (fixups[i].fixups->smpt_map_id &&
> +		    spi_nor_fixup_match(nor, &fixups[i]))
> +			fixups[i].fixups->smpt_map_id(nor, map_id);
> +	}
>  }
>  
>  /**
> @@ -1406,17 +1420,23 @@ static int spi_nor_parse_sccr_mc(struct spi_nor *nor,
>   */
>  static int spi_nor_post_sfdp_fixups(struct spi_nor *nor)
>  {
> +	const struct spi_nor_fixup *fixups;
> +	unsigned int i;
>  	int ret;
>  
> -	if (nor->manufacturer && nor->manufacturer->fixups &&
> -	    nor->manufacturer->fixups->post_sfdp) {
> -		ret = nor->manufacturer->fixups->post_sfdp(nor);
> -		if (ret)
> -			return ret;
> -	}
> +	if (!nor->manufacturer || !nor->manufacturer->fixups)
> +		return 0;
>  
> -	if (nor->info->fixups && nor->info->fixups->post_sfdp)
> -		return nor->info->fixups->post_sfdp(nor);
> +	fixups = nor->manufacturer->fixups;
> +
> +	for (i = 0; i < nor->manufacturer->nfixups; i++) {
> +		if (fixups[i].fixups->post_sfdp &&
> +		    spi_nor_fixup_match(nor, &fixups[i])) {
> +			ret = fixups[i].fixups->post_sfdp(nor);
> +			if (ret)
> +				return ret;
> +		}
> +	}
>  
>  	return 0;
>  }
> diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c
> index ca2703a5a338..f3862eb1a23a 100644
> --- a/drivers/mtd/spi-nor/spansion.c
> +++ b/drivers/mtd/spi-nor/spansion.c
> @@ -905,7 +905,6 @@ static const struct flash_info spansion_nor_parts[] = {
>  		.sector_size = SZ_256K,
>  		.no_sfdp_flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
>  		.mfr_flags = USE_CLSR,
> -		.fixups = &s25fs_s_nor_fixups,
>  	}, {
>  		.id = SNOR_ID(0x01, 0x20, 0x18, 0x03, 0x00),
>  		.name = "s25sl12800",
> @@ -941,7 +940,6 @@ static const struct flash_info spansion_nor_parts[] = {
>  		.size = SZ_16M,
>  		.no_sfdp_flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
>  		.mfr_flags = USE_CLSR,
> -		.fixups = &s25fs_s_nor_fixups,
>  	}, {
>  		.id = SNOR_ID(0x01, 0x20, 0x18, 0x4d, 0x01),
>  		.name = "s25fl129p1",
> @@ -1001,76 +999,61 @@ static const struct flash_info spansion_nor_parts[] = {
>  		.id = SNOR_ID(0x34, 0x2a, 0x1a, 0x0f, 0x03, 0x90),
>  		.name = "s25hl512t",
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s25hx_t_fixups
>  	}, {
>  		.id = SNOR_ID(0x34, 0x2a, 0x1b, 0x0f, 0x03, 0x90),
>  		.name = "s25hl01gt",
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s25hx_t_fixups
>  	}, {
>  		.id = SNOR_ID(0x34, 0x2a, 0x1c, 0x0f, 0x00, 0x90),
>  		.name = "s25hl02gt",
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s25hx_t_fixups
>  	}, {
>  		.id = SNOR_ID(0x34, 0x2b, 0x19, 0x0f, 0x08, 0x90),
>  		.name = "s25fs256t",
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s25fs256t_fixups
>  	}, {
>  		.id = SNOR_ID(0x34, 0x2b, 0x1a, 0x0f, 0x03, 0x90),
>  		.name = "s25hs512t",
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s25hx_t_fixups
>  	}, {
>  		.id = SNOR_ID(0x34, 0x2b, 0x1b, 0x0f, 0x03, 0x90),
>  		.name = "s25hs01gt",
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s25hx_t_fixups
>  	}, {
>  		.id = SNOR_ID(0x34, 0x2b, 0x1c, 0x0f, 0x00, 0x90),
>  		.name = "s25hs02gt",
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s25hx_t_fixups
>  	}, {
>  		/* S28HL256T */
>  		.id = SNOR_ID(0x34, 0x5a, 0x19),
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s28hx_t_fixups,
>  	}, {
>  		.id = SNOR_ID(0x34, 0x5a, 0x1a),
>  		.name = "s28hl512t",
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s28hx_t_fixups,
>  	}, {
>  		.id = SNOR_ID(0x34, 0x5a, 0x1b),
>  		.name = "s28hl01gt",
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s28hx_t_fixups,
>  	}, {
>  		/* S28HL02GT */
>  		.id = SNOR_ID(0x34, 0x5a, 0x1c),
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s28hx_t_fixups,
>  	}, {
>  		.id = SNOR_ID(0x34, 0x5b, 0x19),
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s28hx_t_fixups,
>  	}, {
>  		.id = SNOR_ID(0x34, 0x5b, 0x1a),
>  		.name = "s28hs512t",
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s28hx_t_fixups,
>  	}, {
>  		.id = SNOR_ID(0x34, 0x5b, 0x1b),
>  		.name = "s28hs01gt",
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s28hx_t_fixups,
>  	}, {
>  		.id = SNOR_ID(0x34, 0x5b, 0x1c),
>  		.name = "s28hs02gt",
>  		.mfr_flags = USE_CLPEF,
> -		.fixups = &s28hx_t_fixups,
>  	}, {
>  		.id = SNOR_ID(0xef, 0x40, 0x13),
>  		.name = "s25fl004k",
> @@ -1170,9 +1153,31 @@ static const struct spi_nor_fixups spansion_nor_fixups = {
>  	.late_init = spansion_nor_late_init,
>  };
>  
> +static const struct spi_nor_fixup spansion_fixups[] = {
> +	{ .fixups = &spansion_nor_fixups },
> +	{ .id = SNOR_ID(0x01, 0x02, 0x20, 0x4d, 0x00, 0x81), .fixups = &s25fs_s_nor_fixups },
> +	{ .id = SNOR_ID(0x01, 0x20, 0x18, 0x4d, 0x01, 0x81), .fixups = &s25fs_s_nor_fixups },
> +	{ .id = SNOR_ID(0x34, 0x2a, 0x1a, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups },
> +	{ .id = SNOR_ID(0x34, 0x2a, 0x1b, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups },
> +	{ .id = SNOR_ID(0x34, 0x2a, 0x1c, 0x0f, 0x00, 0x90), .fixups = &s25hx_t_fixups },
> +	{ .id = SNOR_ID(0x34, 0x2b, 0x19, 0x0f, 0x08, 0x90), .fixups = &s25fs256t_fixups },
> +	{ .id = SNOR_ID(0x34, 0x2b, 0x1a, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups },
> +	{ .id = SNOR_ID(0x34, 0x2b, 0x1b, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups },
> +	{ .id = SNOR_ID(0x34, 0x2b, 0x1c, 0x0f, 0x00, 0x90), .fixups = &s25hx_t_fixups },
> +	{ .id = SNOR_ID(0x34, 0x5a, 0x19), .fixups = &s28hx_t_fixups },
> +	{ .id = SNOR_ID(0x34, 0x5a, 0x1a), .fixups = &s28hx_t_fixups },
> +	{ .id = SNOR_ID(0x34, 0x5a, 0x1b), .fixups = &s28hx_t_fixups },
> +	{ .id = SNOR_ID(0x34, 0x5a, 0x1c), .fixups = &s28hx_t_fixups },
> +	{ .id = SNOR_ID(0x34, 0x5b, 0x19), .fixups = &s28hx_t_fixups },
> +	{ .id = SNOR_ID(0x34, 0x5b, 0x1a), .fixups = &s28hx_t_fixups },
> +	{ .id = SNOR_ID(0x34, 0x5b, 0x1b), .fixups = &s28hx_t_fixups },
> +	{ .id = SNOR_ID(0x34, 0x5b, 0x1c), .fixups = &s28hx_t_fixups },
> +};
> +
>  const struct spi_nor_manufacturer spi_nor_spansion = {
>  	.name = "spansion",
>  	.parts = spansion_nor_parts,
>  	.nparts = ARRAY_SIZE(spansion_nor_parts),
> -	.fixups = &spansion_nor_fixups,
> +	.fixups = spansion_fixups,
> +	.nfixups = ARRAY_SIZE(spansion_fixups),
>  };
> diff --git a/drivers/mtd/spi-nor/sst.c b/drivers/mtd/spi-nor/sst.c
> index 77a0bc41d4e5..c460803b278b 100644
> --- a/drivers/mtd/spi-nor/sst.c
> +++ b/drivers/mtd/spi-nor/sst.c
> @@ -152,14 +152,12 @@ static const struct flash_info sst_nor_parts[] = {
>  		.id = SNOR_ID(0xbf, 0x26, 0x42),
>  		.name = "sst26vf032b",
>  		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
> -		.fixups = &sst26vf_nor_fixups,
>  	}, {
>  		.id = SNOR_ID(0xbf, 0x26, 0x43),
>  		.name = "sst26vf064b",
>  		.size = SZ_8M,
>  		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
>  		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
> -		.fixups = &sst26vf_nor_fixups,
>  	}, {
>  		.id = SNOR_ID(0xbf, 0x26, 0x51),
>  		.name = "sst26wf016b",
> @@ -278,9 +276,16 @@ static const struct spi_nor_fixups sst_nor_fixups = {
>  	.late_init = sst_nor_late_init,
>  };
>  
> +static const struct spi_nor_fixup sst_fixups[] = {
> +	{ .fixups = &sst_nor_fixups },
> +	{ .id = SNOR_ID(0xbf, 0x26, 0x42), .fixups = &sst26vf_nor_fixups },
> +	{ .id = SNOR_ID(0xbf, 0x26, 0x43), .fixups = &sst26vf_nor_fixups },
> +};
> +
>  const struct spi_nor_manufacturer spi_nor_sst = {
>  	.name = "sst",
>  	.parts = sst_nor_parts,
>  	.nparts = ARRAY_SIZE(sst_nor_parts),
> -	.fixups = &sst_nor_fixups,
> +	.fixups = sst_fixups,
> +	.nfixups = ARRAY_SIZE(sst_fixups),
>  };
> diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
> index 01a693033448..583b1669270f 100644
> --- a/drivers/mtd/spi-nor/winbond.c
> +++ b/drivers/mtd/spi-nor/winbond.c
> @@ -219,7 +219,6 @@ static const struct flash_info winbond_nor_parts[] = {
>  		.size = SZ_16M,
>  		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
>  		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_HAS_CMP,
> -		.fixups = &w25q128_fixups,
>  	}, {
>  		/* W25Q256JV-Q/N */
>  		.id = SNOR_ID(0xef, 0x40, 0x19),
> @@ -228,7 +227,6 @@ static const struct flash_info winbond_nor_parts[] = {
>  		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
>  		.flags = SPI_NOR_QUAD_PP | SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
>  			 SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
> -		.fixups = &w25q256_fixups,
>  	}, {
>  		/* W25Q512JV-Q/N */
>  		.id = SNOR_ID(0xef, 0x40, 0x20),
> @@ -242,7 +240,6 @@ static const struct flash_info winbond_nor_parts[] = {
>  		.id = SNOR_ID(0xef, 0x40, 0x21),
>  		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
>  			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
> -		.fixups = &winbond_nor_multi_die_fixups,
>  	}, {
>  		.id = SNOR_ID(0xef, 0x50, 0x12),
>  		.name = "w25q20bw",
> @@ -345,13 +342,11 @@ static const struct flash_info winbond_nor_parts[] = {
>  		.id = SNOR_ID(0xef, 0x70, 0x21),
>  		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
>  			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
> -		.fixups = &winbond_nor_multi_die_fixups,
>  	}, {
>  		/* W25Q02JV-M */
>  		.id = SNOR_ID(0xef, 0x70, 0x22),
>  		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
>  			 SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
> -		.fixups = &winbond_nor_multi_die_fixups,
>  	}, {
>  		.id = SNOR_ID(0xef, 0x71, 0x19),
>  		.name = "w25m512jv",
> @@ -553,9 +548,19 @@ static const struct spi_nor_fixups winbond_nor_fixups = {
>  	.late_init = winbond_nor_late_init,
>  };
>  
> +static const struct spi_nor_fixup winbond_fixups[] = {
> +	{ .fixups = &winbond_nor_fixups },
> +	{ .id = SNOR_ID(0xef, 0x40, 0x18), .fixups = &w25q128_fixups },
> +	{ .id = SNOR_ID(0xef, 0x40, 0x19), .fixups = &w25q256_fixups },
> +	{ .id = SNOR_ID(0xef, 0x40, 0x21), .fixups = &winbond_nor_multi_die_fixups },
> +	{ .id = SNOR_ID(0xef, 0x70, 0x21), .fixups = &winbond_nor_multi_die_fixups },
> +	{ .id = SNOR_ID(0xef, 0x70, 0x22), .fixups = &winbond_nor_multi_die_fixups },
> +};
> +
>  const struct spi_nor_manufacturer spi_nor_winbond = {
>  	.name = "winbond",
>  	.parts = winbond_nor_parts,
>  	.nparts = ARRAY_SIZE(winbond_nor_parts),
> -	.fixups = &winbond_nor_fixups,
> +	.fixups = winbond_fixups,
> +	.nfixups = ARRAY_SIZE(winbond_fixups),
>  };

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/e9466a04/attachment-0001.sig>

From mwalle at kernel.org  Fri Aug 14 02:41:25 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:41:25 +0200
Subject: [PATCH v3 06/23] mtd: spi-nor: Create an indirection on the
 part name
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-6-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-6-b637cf120d5c@bootlin.com>
Message-ID: <DKOKGNRZQ63P.3E1LM63BEAQ5Z@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> We currently print through sysfs and debugfs the name of the part if the
> .name field, which is legacy, has been filled in the ID table (otherwise
> "(null)" is printed). These IDs had an interest until manufacturers
> started re-using more and more extensively the so called unique JEDEC
> IDs. At this stage, a name that was tailored for a chip sold in 2016 may
> no longer be relevant for a chip sold in 2026. Still showing this name
> through the various filesystems may confuse people, removing these names
> cannot be done since they are now part of the user ABI (because of
> sysfs).
>
> In order to allow overwriting the .name field (which is part of a
> read-only structure), let's create a spi-nor pointer which will by
> default point to that .name, but which can be cleared in fixup hooks to
> make sure the name is hidden on newer parts.
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/faeb8d68/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 02:41:58 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:41:58 +0200
Subject: [PATCH v3 07/23] mtd: spi-nor: Move the SFDP header structure
 to a C header
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-7-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-7-b637cf120d5c@bootlin.com>
Message-ID: <DKOKH2Z53AWN.16GH2NERDWLJ0@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> The sfdp_header structure is currently defined in sfdp.c, whereas the
> sfdp_parameter_header structure is defined in sfdp.h. In order to ease
> the reuse of this structure outside of the sfdp.c file, move this
> structure to the sfdp C header (.h).
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/551bf3c1/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 02:46:14 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:46:14 +0200
Subject: [PATCH v3 08/23] mtd: spi-nor: winbond: Prepare introduction of
 W25QxxRV-Q/N parts
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-8-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-8-b637cf120d5c@bootlin.com>
Message-ID: <DKOKKCF45CY6.2RXHF51EW6VV0@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> There is an ID collision between the JV and RV families. Both chips are
> very similar in practice, it is mostly a matter of electrical
> differences (mostly power consumption being lower).
>
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2 opcode
> (0x31). They also do not require the multi-die fixups which must remain
> assigned to the JV chips.
>
> Finally, since they share the IDs but not the names, we must hide the
> names using a fixup.
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>

With a comment below.

> ---
>  drivers/mtd/spi-nor/winbond.c | 56 ++++++++++++++++++++++++++++++++++++++++---
>  1 file changed, 53 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
> index 583b1669270f..8c1cad9e21b4 100644
> --- a/drivers/mtd/spi-nor/winbond.c
> +++ b/drivers/mtd/spi-nor/winbond.c
> @@ -146,6 +146,51 @@ static const struct spi_nor_fixups winbond_nor_multi_die_fixups = {
>  	.post_sfdp = winbond_nor_multi_die_post_sfdp_fixups,
>  };
>  
> +static int winbond_nor_partname_post_sfdp_fixups(struct spi_nor *nor)
> +{
> +	/*
> +	 * W25QxxRV parts re-use the JEDEC IDs of the JV family. Their name
> +	 * being a legacy field, it is kept for the already established JV parts
> +	 * but must not be exposed by the newer RV ones.
> +	 */
> +	nor->partname = NULL;
> +
> +	return 0;
> +}
> +
> +static const struct spi_nor_fixups winbond_nor_partname_fixups = {
> +	.post_sfdp = winbond_nor_partname_post_sfdp_fixups,
> +};
> +
> +static bool is_w25qxxrv(const struct spi_nor *nor)
> +{
> +	struct sfdp_header *sfdp_h = (struct sfdp_header *)nor->sfdp->dwords;

nitpick, spi_nor_sfdp_get_header()?

> +
> +	/*
> +	 * W25QxxRV chips re-use the same ID as the W25QxxJV family.
> +	 *
> +	 * Chips are very similar, W25QxxRV brings mostly performance and power
> +	 * consumption improvements. The RV family does not require the multi
> +	 * die fixup.
> +	 *
> +	 * They can be distinguished based on their SFDP minor revision:
> +	 * W25QxxJV:        JESD216A, minor revision == 05h
> +	 * W25Q512/01/02JV: JESD216B, minor revision == 06h
> +	 * W25QxxRV:        JESD216F, minor revision >= 0Ah
> +	 */
> +	return sfdp_h->minor >= SFDP_JESD216F_MINOR;
> +}
> +
> +static bool winbond_jv_match(const struct spi_nor *nor)
> +{
> +	return !nor->sfdp || !is_w25qxxrv(nor);

So how do we know if nor->sfdp is already there for a given fixup.
Without having looked at the code, there could potentially be fixups
before SFDP is parsed (and the nor->sfdp is populated), right? Might
be worth to be mentioned somewhere.

-michael

> +}
> +
> +static bool winbond_rv_match(const struct spi_nor *nor)
> +{
> +	return nor->sfdp && is_w25qxxrv(nor);
> +}
> +
>  static const struct flash_info winbond_nor_parts[] = {
>  	{
>  		.id = SNOR_ID(0xef, 0x30, 0x10),
> @@ -552,9 +597,14 @@ static const struct spi_nor_fixup winbond_fixups[] = {
>  	{ .fixups = &winbond_nor_fixups },
>  	{ .id = SNOR_ID(0xef, 0x40, 0x18), .fixups = &w25q128_fixups },
>  	{ .id = SNOR_ID(0xef, 0x40, 0x19), .fixups = &w25q256_fixups },
> -	{ .id = SNOR_ID(0xef, 0x40, 0x21), .fixups = &winbond_nor_multi_die_fixups },
> -	{ .id = SNOR_ID(0xef, 0x70, 0x21), .fixups = &winbond_nor_multi_die_fixups },
> -	{ .id = SNOR_ID(0xef, 0x70, 0x22), .fixups = &winbond_nor_multi_die_fixups },
> +	{ .id = SNOR_ID(0xef, 0x40), .match = winbond_rv_match,
> +	  .fixups = &winbond_nor_partname_fixups },
> +	{ .id = SNOR_ID(0xef, 0x40, 0x21), .match = winbond_jv_match,
> +	  .fixups = &winbond_nor_multi_die_fixups },
> +	{ .id = SNOR_ID(0xef, 0x70, 0x21), .match = winbond_jv_match,
> +	  .fixups = &winbond_nor_multi_die_fixups },
> +	{ .id = SNOR_ID(0xef, 0x70, 0x22), .match = winbond_jv_match,
> +	  .fixups = &winbond_nor_multi_die_fixups },
>  };
>  
>  const struct spi_nor_manufacturer spi_nor_winbond = {

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/0e609d6f/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 02:46:34 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:46:34 +0200
Subject: [PATCH v3 09/23] mtd: spi-nor: winbond: Add support for
 W25Q32RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-9-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-9-b637cf120d5c@bootlin.com>
Message-ID: <DKOKKLO117YI.2IPV0PKS6CVRE@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
>
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/8293dad0/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 02:46:51 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:46:51 +0200
Subject: [PATCH v3 10/23] mtd: spi-nor: winbond: Add support for
 W25Q64RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-10-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-10-b637cf120d5c@bootlin.com>
Message-ID: <DKOKKTM4UCIG.14QV6331B2K7U@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
>
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/5cab19b3/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 02:47:03 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:47:03 +0200
Subject: [PATCH v3 11/23] mtd: spi-nor: winbond: Add support for
 W25Q12RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-11-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-11-b637cf120d5c@bootlin.com>
Message-ID: <DKOKKYUYF350.2OY5OV9GH1F9E@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
>
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/a7757a39/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 02:47:23 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:47:23 +0200
Subject: [PATCH v3 12/23] mtd: spi-nor: winbond: Add support for
 W25Q51RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-12-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-12-b637cf120d5c@bootlin.com>
Message-ID: <DKOKL8BG1W6I.2F1SN6BTV61BA@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
>
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/e00eb1e1/attachment-0001.sig>

From mwalle at kernel.org  Fri Aug 14 02:47:48 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:47:48 +0200
Subject: [PATCH v3 13/23] mtd: spi-nor: winbond: Add support for
 W25Q01RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-13-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-13-b637cf120d5c@bootlin.com>
Message-ID: <DKOKLJVLFN8J.2M6H2X3LPF04J@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
>
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).

Reviewed-by: Michael Walle <mwalle at kernel.org>

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/150eb92f/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 02:48:13 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:48:13 +0200
Subject: [PATCH v3 14/23] mtd: spi-nor: winbond: Add support for
 W25Q02RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-14-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-14-b637cf120d5c@bootlin.com>
Message-ID: <DKOKLV5T1BNI.2CW9UBFLOD83Q@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
>
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/b83a66bf/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 02:50:42 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:50:42 +0200
Subject: [PATCH v3 15/23] mtd: spi-nor: winbond: Prepare introduction of
 W25QxxRV-M parts
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-15-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-15-b637cf120d5c@bootlin.com>
Message-ID: <DKOKNRVNIE82.3SO99KA3HJ3TT@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> There is an ID collision between the JV and RV families. Both chips are
> very similar in practice, it is mostly a matter of electrical
> differences (mostly power consumption being lower).
>
> As a significant difference, RV chips identify themselves as supporting
> The new SFDP (rev F) field which forces an alternate write SR2 opcode
> (0x31).
>
> Since they share the IDs but not the names, we must hide the names using
> a fixup.
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/45774fdf/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 02:50:57 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:50:57 +0200
Subject: [PATCH v3 16/23] mtd: spi-nor: winbond: Add support for
 W25Q32RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-16-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-16-b637cf120d5c@bootlin.com>
Message-ID: <DKOKNYMTF9LY.3OHC0QB0DW0BP@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
>
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/489ec1a0/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 02:51:28 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:51:28 +0200
Subject: [PATCH v3 17/23] mtd: spi-nor: winbond: Add support for
 W25Q64RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-17-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-17-b637cf120d5c@bootlin.com>
Message-ID: <DKOKOCLARP0E.12YCLA6X5CZLG@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
>
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/b977ba44/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 02:51:48 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:51:48 +0200
Subject: [PATCH v3 18/23] mtd: spi-nor: winbond: Add support for
 W25Q12RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-18-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-18-b637cf120d5c@bootlin.com>
Message-ID: <DKOKOM0RYY5Z.1CLGM0HL98EQU@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
>
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/d632484e/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 02:52:03 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:52:03 +0200
Subject: [PATCH v3 19/23] mtd: spi-nor: winbond: Add support for
 W25Q51RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-19-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-19-b637cf120d5c@bootlin.com>
Message-ID: <DKOKOT054B79.38O3IU11OM7J2@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
>
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/b5a8bece/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 02:52:16 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:52:16 +0200
Subject: [PATCH v3 20/23] mtd: spi-nor: winbond: Add support for
 W25Q01RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-20-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-20-b637cf120d5c@bootlin.com>
Message-ID: <DKOKOYTY1313.1J88PSPUUDFZK@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
>
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/4a343e4f/attachment-0001.sig>

From mwalle at kernel.org  Fri Aug 14 02:52:31 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:52:31 +0200
Subject: [PATCH v3 21/23] mtd: spi-nor: winbond: Add support for
 W25Q02RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-21-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-21-b637cf120d5c@bootlin.com>
Message-ID: <DKOKP5L9W231.2DH3ODA0MGU8Y@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
>
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/49cd20aa/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 02:53:07 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 11:53:07 +0200
Subject: [PATCH v3 22/23] mtd: spi-nor: winbond: Add support for
 W25Q51RV-Q/N/M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-22-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-22-b637cf120d5c@bootlin.com>
Message-ID: <DKOKPM4GS867.1EU3SKAU14NLG@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> I currently do not have access to any chips from the W25Q25RV
> density. Yet, I successfully tested the entire JV family and all other
> chips from the RV family. They all behaved strictly identically, so I
> propose to nevertheless add the missing comments for the sake of clarity
> and completeness.
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/7969ee74/attachment.sig>

From mwalle at kernel.org  Fri Aug 14 05:25:56 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 14:25:56 +0200
Subject: [PATCH v3 03/23] mtd: spi-nor: Refactor Read Status/Write
 Status support
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-3-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-3-b637cf120d5c@bootlin.com>
Message-ID: <DKONYM8W4BAW.25PBIPPWCILG1@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> SPI NOR has a lot of history. Additions over additions, the subtleties
> of the JESD216 specification, their implementations by the manufacturers
> and the hardware mistakes have generated a gigantic maze, let's try to
> understand what is really needed.
>
> The specification explains the QE (Quad Enable) bitfield as describing
> where the bit to enable the Quad capability is, but also how to set
> it. Unfortunately, the specification is not precise about what opcodes
> are supported exactly in all the cases. There is an introduction that
> basically states:
> - Opcode 0x05 reads SR1
> - Opcode 0x35 reads SR2
> - Opcode 0x01 writes SR1, and then SR2 if another byte is written
>
> Then the bitfield, among indicating the location of the QE bit, may
> indicate:
> - Reading SR1 and SR2 in one operation is not possible (loops over the
>   content of SR1)
> - Reading SR2 directly is possible
> - Only writing SR1 zeroes SR2.
>
> One problem with the current implementation, is that it only focuses on
> the QE bit. A quad_enable function was created for each case, even
> though in practice, the logic was always the same: read, modify, write,
> read back and verify. One problem comes when other features need to play
> with the status registers, like software block protection or OTP: you
> never know how to properly handle the QE bit, nor where it is, nor how
> to read/write the Status registers. This lead to
> approximations/guessing (in swp.c, otp.c and obviously in legacy
> controller drivers like atmel.c) but also to the implementation of a
> gazillon of helpers for reading/writing/checking the status registers.
>
> In addition, I believe some design decisions had a negative impact over
> the years.
>
> - All possible situations had to be flagged by the core. This is likely
>   wrong, because we no longer know why we need specific quirks. It was
>   ineherent to the state of the SPI NOR core before the great cleanup
>   that had happened the past few years. I believe this creates confusion
>   in the core today, and we should push this to vendor fixups
>   instead. As an example, in 2023 Hsin-Yi was facing an issue because
>   his chip was setting the wrong QER value, leading to RDCR being
>   prevented, thus falling into a condition blindly setting a random QE
>   bit (I strongy believe it is done like that for wrong reasons). His
>   chip actually had RDCR support! The correct fix should have been to
>   mark the capability in a device fixup instead of handling this in the
>   core.
>
>   Link: https://lore.kernel.org/lkml/CAJMQK-hR0eaO0b4Vd0U8_KAndLyZapqdHjVLAoe42rWi9rdLkA at mail.gmail.com/
>
> - SFDP parsing is over cautious. I believe
>   BFPT_DWORD15_QER_SR2_BIT1_NO_RD is abusive (nothing states that RDCR is
>   not supported), and BFPT_DWORD15_QER_SR2_BIT1 is also out of
>   specification when forcing 16-bit Status writes.
>
> - SNOR_F_HAS_16BIT_SR is only imposing 16bit Status writes, whereas
>   reads can still be 8-bit wide.
>
> - The usage of helpers verifying the writes was also spread for IMHO no
>   really good reason. Why shouldn't we trust spi operations when it
>   comes to Status Registers? We do not read back our page reads, so why
>   status registers should be treated with so much care, if it's not
>   because we are unsure of what is being done? My proposal includes a
>   check when it comes to the QE bit (done once) but we don't need these
>   checks otherwise. If the QE bit was written properly, there are high
>   chances that the other register accesses will just be fine, no?
>
> Asde from my main quest, I also observed no good reason to ask the
> read/write status register callers to use nor->bouncebuf while the
> low-level helpers could do it themselves (we are talking about one or
> two bytes being copied).
>
> So after these observations, my proposal is the following:
> - Create private low level helpers that just read or write a status
>   register. They are flexible, we can give the opcode (which varies
>   based on the SFDP QER field) and the length (1 or 2).
> - Create public generic accessors which will be used to read/write sr1
>   and/or sr2. This is where all the cleverness shall be. The helpers use
>   the available opcodes for a given chip in order to fullfill the
>   request.
> - Provide a single generic ->quad_enable() hook which generically does
>   all the steps mentioned above (read, modify, write, read back and
>   verify).
> - Create a list of opcodes for all 6 possible situations:
>   {read, write} {sr1, sr2, sr1 and sr2}. These opcodes are
>   filled/cleared based on the QER field. An opcode set to 0 indicates
>   the absence of support (there is no 0x00 opcode in SPI NOR).
>
> I tried my best to analyze the current behavior and to mimic it as much
> as possible, but this is a risky cleanup. However, if we go for this, it
> will be *much* easier in the future to handle all kind of chip
> variations. We won't be limited to a couple of flags anymore, but rather
> we'll be able to just disable a read or write capability using a single
> line.
>
> Known deviations:
> - The Atmel manufacturer driver drives non SFDP chips which by default
>   were receiving the HAS_16B_WR flag, forcing SR2 writes all the times,
>   but it was deliberately making single bytes writes to SR1 for its
>   locking operations. The flag is likely wrong for them, so I removed
>   it.
> - I am proposing on purpose a single quad_enable helper. It should match
>   all the cases, with 1 identified difference which I believe is
>   harmless: in the SR2_BIT7 case, the helper would make an extra SR1
>   read/write which was not done before. This entry explicitly supports
>   reading and writing the two registers. Note: no manufacturer has been
>   identified to actually use that entry yet.
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
> ---
> This is a really hard to review patch, it is big, full of changes
> everywhere, but I cannot really split it further. The commit log is
> really important, it tries to explain the steps I went through when
> trying to refactor the whole QE/SR handling for which I received
> feedback from Michael, Tudor and Sashiko.
>
> Ideally we could expect test coverage of each case:
> - BFPT_DWORD15_QER_NONE: Probably not needed
> - BFPT_DWORD15_QER_SR2_BIT1_BUGGY: Expected from Hsin-Yi (flag is being
>   renamed _NO_1B_WR)
> - BFPT_DWORD15_QER_SR2_BIT1_NO_RD: Tested with a chip re-enabling RDCR
>   in a fixup
> - BFPT_DWORD15_QER_SR1_BIT6: TBD
> - BFPT_DWORD15_QER_SR2_BIT7: TBD
> - BFPT_DWORD15_QER_SR2_BIT1: TBD
> - BFPT_DWORD15_QER_SR2_BIT1_1B: Done.
> There will probably be breakages on older chips. These cannot be
> guessed because they are not properly listed in manufacturer
> fixups (yet). If we want a cleanup/simplification, we will have to cope
> with this risk.
>
> I hope the diff stats will motivate people to have a look and report
> their testing.
>
> I will also eagerly monitor Sashiko's output which will probably be
> very useful to catch niche weird cases where these changes might break.


Honestly, I'm still not sold at the sr1 and sr2 split. But since
spi-nor is know for it's code churn. I could just rework that later
:)

Still I had some questions in the old thread:
I don't get why there should be any endianess problems?
Just the low level read and write would have to figure out how to
read and write the status registers.

> ---
>  Documentation/driver-api/mtd/spi-nor.rst |   2 +-
>  drivers/mtd/spi-nor/atmel.c              |  52 +-
>  drivers/mtd/spi-nor/core.c               | 784 +++++++++++--------------------
>  drivers/mtd/spi-nor/core.h               |  57 +--
>  drivers/mtd/spi-nor/debugfs.c            |   2 -
>  drivers/mtd/spi-nor/gigadevice.c         |   6 +-
>  drivers/mtd/spi-nor/issi.c               |   3 +-
>  drivers/mtd/spi-nor/macronix.c           |  12 +-
>  drivers/mtd/spi-nor/micron-st.c          |   4 +-
>  drivers/mtd/spi-nor/otp.c                |  16 +-
>  drivers/mtd/spi-nor/sfdp.c               |  41 +-
>  drivers/mtd/spi-nor/spansion.c           |  16 +-
>  drivers/mtd/spi-nor/sst.c                |   5 +-
>  drivers/mtd/spi-nor/swp.c                |  58 +--
>  drivers/mtd/spi-nor/winbond.c            |   4 +-
>  include/linux/mtd/spi-nor.h              |   4 -
>  16 files changed, 416 insertions(+), 650 deletions(-)
>
> diff --git a/Documentation/driver-api/mtd/spi-nor.rst b/Documentation/driver-api/mtd/spi-nor.rst
> index 747a326fb6c0..5441fa7d4f04 100644
> --- a/Documentation/driver-api/mtd/spi-nor.rst
> +++ b/Documentation/driver-api/mtd/spi-nor.rst
> @@ -118,7 +118,7 @@ section, after the ``---`` marker.
>      write size		1
>      page size		256
>      address nbytes	3
> -    flags		HAS_LOCK | HAS_16BIT_SR | SOFT_RESET | SWP_IS_VOLATILE
> +    flags		HAS_LOCK | SOFT_RESET | SWP_IS_VOLATILE
>  
>      opcodes
>       read		0xeb
> diff --git a/drivers/mtd/spi-nor/atmel.c b/drivers/mtd/spi-nor/atmel.c
> index 82c592f0a1e1..8741c0bda497 100644
> --- a/drivers/mtd/spi-nor/atmel.c
> +++ b/drivers/mtd/spi-nor/atmel.c
> @@ -23,18 +23,28 @@ static int at25fs_nor_lock(struct spi_nor *nor, loff_t ofs, u64 len)
>  
>  static int at25fs_nor_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
>  {
> +	/* Write 0x00 to the status register to disable write protection */
> +	u8 sr = 0;
>  	int ret;
>  
>  	/* We only support unlocking the whole flash array */
>  	if (ofs || len != nor->params->size)
>  		return -EINVAL;
>  
> -	/* Write 0x00 to the status register to disable write protection */
> -	ret = spi_nor_write_sr_and_check(nor, 0);
> +	ret = spi_nor_write_sr1(nor, &sr);
>  	if (ret)
> -		dev_dbg(nor->dev, "unable to clear BP bits, WP# asserted?\n");
> +		return ret;
>  
> -	return ret;
> +	ret = spi_nor_read_sr1(nor, &sr);
> +	if (ret)
> +		return ret;
> +
> +	if (sr) {
> +		dev_dbg(nor->dev, "unable to clear BP bits, WP# asserted?\n");
> +		return -EIO;
> +	}
> +
> +	return 0;
>  }
>  
>  static int at25fs_nor_is_locked(struct spi_nor *nor, loff_t ofs, u64 len)
> @@ -50,6 +60,7 @@ static const struct spi_nor_locking_ops at25fs_nor_locking_ops = {
>  
>  static int at25fs_nor_late_init(struct spi_nor *nor)
>  {
> +	nor->params->opcodes.write_sr1 = SPINOR_OP_WRSR;
>  	nor->params->locking_ops = &at25fs_nor_locking_ops;
>  
>  	return 0;
> @@ -69,6 +80,7 @@ static const struct spi_nor_fixups at25fs_nor_fixups = {
>   * Return: 0 on success, -error otherwise.
>   */
>  static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs,
> +
>  					   u64 len, bool is_protect)
>  {
>  	int ret;
> @@ -78,19 +90,24 @@ static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs,
>  	if (ofs || len != nor->params->size)
>  		return -EINVAL;
>  
> -	ret = spi_nor_read_sr(nor, nor->bouncebuf);
> +	ret = spi_nor_read_sr1(nor, &sr);
>  	if (ret)
>  		return ret;
>  
> -	sr = nor->bouncebuf[0];
> -
>  	/* SRWD bit needs to be cleared, otherwise the protection doesn't change */
>  	if (sr & SR_SRWD) {
>  		sr &= ~SR_SRWD;
> -		ret = spi_nor_write_sr_and_check(nor, sr);
> -		if (ret) {
> -			dev_dbg(nor->dev, "unable to clear SRWD bit, WP# asserted?\n");
> +		ret = spi_nor_write_sr1(nor, &sr);
> +		if (ret)
>  			return ret;
> +
> +		ret = spi_nor_read_sr1(nor, &sr);
> +		if (ret)
> +			return ret;
> +
> +		if (sr & SR_SRWD) {
> +			dev_dbg(nor->dev, "unable to clear SRWD bit, WP# asserted?\n");
> +			return -EIO;
>  		}
>  	}
>  
> @@ -108,14 +125,7 @@ static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs,
>  		sr &= ~ATMEL_SR_GLOBAL_PROTECT_MASK;
>  	}
>  
> -	nor->bouncebuf[0] = sr;
> -
> -	/*
> -	 * We cannot use the spi_nor_write_sr_and_check() because this command
> -	 * isn't really setting any bits, instead it is an pseudo command for
> -	 * "Global Unprotect" or "Global Protect"
> -	 */
> -	return spi_nor_write_sr(nor, nor->bouncebuf, 1);
> +	return spi_nor_write_sr1(nor, &sr);
>  }
>  
>  static int atmel_nor_global_protect(struct spi_nor *nor, loff_t ofs, u64 len)
> @@ -131,16 +141,17 @@ static int atmel_nor_global_unprotect(struct spi_nor *nor, loff_t ofs, u64 len)
>  static int atmel_nor_is_global_protected(struct spi_nor *nor, loff_t ofs,
>  					 u64 len)
>  {
> +	u8 sr;
>  	int ret;
>  
>  	if (ofs >= nor->params->size || (ofs + len) > nor->params->size)
>  		return -EINVAL;
>  
> -	ret = spi_nor_read_sr(nor, nor->bouncebuf);
> +	ret = spi_nor_read_sr1(nor, &sr);
>  	if (ret)
>  		return ret;
>  
> -	return ((nor->bouncebuf[0] & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK);
> +	return ((sr & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK);
>  }
>  
>  static const struct spi_nor_locking_ops atmel_nor_global_protection_ops = {
> @@ -151,6 +162,7 @@ static const struct spi_nor_locking_ops atmel_nor_global_protection_ops = {
>  
>  static int atmel_nor_global_protection_late_init(struct spi_nor *nor)
>  {
> +	nor->params->opcodes.write_sr1 = SPINOR_OP_WRSR;
>  	nor->params->locking_ops = &atmel_nor_global_protection_ops;
>  
>  	return 0;
> diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
> index 29b8fe3d1c65..ac7812562047 100644
> --- a/drivers/mtd/spi-nor/core.c
> +++ b/drivers/mtd/spi-nor/core.c
> @@ -443,75 +443,6 @@ int spi_nor_read_id(struct spi_nor *nor, u8 naddr, u8 ndummy, u8 *id,
>  	return ret;
>  }
>  
> -/**
> - * spi_nor_read_sr() - Read the Status Register.
> - * @nor:	pointer to 'struct spi_nor'.
> - * @sr:		pointer to a DMA-able buffer where the value of the
> - *              Status Register will be written. Should be at least 2 bytes.
> - *
> - * Return: 0 on success, -errno otherwise.
> - */
> -int spi_nor_read_sr(struct spi_nor *nor, u8 *sr)
> -{
> -	int ret;
> -
> -	if (nor->spimem) {
> -		struct spi_mem_op op = SPI_NOR_RDSR_OP(sr);
> -
> -		if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) {
> -			op.addr.nbytes = nor->params->rdsr_addr_nbytes;
> -			op.dummy.nbytes = nor->params->rdsr_dummy;
> -			/*
> -			 * We don't want to read only one byte in DTR mode. So,
> -			 * read 2 and then discard the second byte.
> -			 */
> -			op.data.nbytes = 2;
> -		}
> -
> -		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
> -
> -		ret = spi_mem_exec_op(nor->spimem, &op);
> -	} else {
> -		ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDSR, sr,
> -						      1);
> -	}
> -
> -	if (ret)
> -		dev_dbg(nor->dev, "error %d reading SR\n", ret);
> -
> -	return ret;
> -}
> -
> -/**
> - * spi_nor_read_cr() - Read the Configuration Register using the
> - * SPINOR_OP_RDCR (35h) command.
> - * @nor:	pointer to 'struct spi_nor'
> - * @cr:		pointer to a DMA-able buffer where the value of the
> - *              Configuration Register will be written.
> - *
> - * Return: 0 on success, -errno otherwise.
> - */
> -int spi_nor_read_cr(struct spi_nor *nor, u8 *cr)
> -{
> -	int ret;
> -
> -	if (nor->spimem) {
> -		struct spi_mem_op op = SPI_NOR_RDCR_OP(cr);
> -
> -		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
> -
> -		ret = spi_mem_exec_op(nor->spimem, &op);
> -	} else {
> -		ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDCR, cr,
> -						      1);
> -	}
> -
> -	if (ret)
> -		dev_dbg(nor->dev, "error %d reading CR\n", ret);
> -
> -	return ret;
> -}
> -
>  /**
>   * spi_nor_set_4byte_addr_mode_en4b_ex4b() - Enter/Exit 4-byte address mode
>   *			using SPINOR_OP_EN4B/SPINOR_OP_EX4B. Typically used by
> @@ -617,12 +548,13 @@ int spi_nor_set_4byte_addr_mode_brwr(struct spi_nor *nor, bool enable)
>  int spi_nor_sr_ready(struct spi_nor *nor)
>  {
>  	int ret;
> +	u8 sr;
>  
> -	ret = spi_nor_read_sr(nor, nor->bouncebuf);
> +	ret = spi_nor_read_sr1(nor, &sr);
>  	if (ret)
>  		return ret;
>  
> -	return !(nor->bouncebuf[0] & SR_WIP);
> +	return !(sr & SR_WIP);
>  }
>  
>  /**
> @@ -784,345 +716,297 @@ int spi_nor_global_block_unlock(struct spi_nor *nor)
>  }
>  
>  /**
> - * spi_nor_write_sr() - Write the Status Register.
> + * spi_nor_read_sr_ll() - Low-level Status Registers read.
>   * @nor:	pointer to 'struct spi_nor'.
> - * @sr:		pointer to DMA-able buffer to write to the Status Register.
> - * @len:	number of bytes to write to the Status Register.
> + * @opcode:	opcode for the status register read operation.
> + * @sr:		pointer to buffer for storing the content of the status registers.
> + * @len:	number of status registers to read (1 or 2).
>   *
>   * Return: 0 on success, -errno otherwise.
>   */
> -int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len)
> +static int spi_nor_read_sr_ll(struct spi_nor *nor, u8 opcode, u8 *sr,
> +			      unsigned int len)
>  {
> -	int ret;
> +	int ret, i;
>  
> -	ret = spi_nor_write_enable(nor);
> -	if (ret)
> -		return ret;
> +	if (len > 2)
> +		return -EINVAL;
> +
> +	for (i = 0; i < len; i++)
> +		nor->bouncebuf[i] = 0;
>  
>  	if (nor->spimem) {
> -		struct spi_mem_op op = SPI_NOR_WRSR_OP(sr, len);
> +		struct spi_mem_op op = SPI_NOR_RDSR_OP(opcode, nor->bouncebuf, len);
> +
> +		if (nor->reg_proto == SNOR_PROTO_8_8_8_DTR) {
> +			op.addr.nbytes = nor->params->rdsr_addr_nbytes;
> +			op.dummy.nbytes = nor->params->rdsr_dummy;
> +			/*
> +			 * We don't want to read only one byte in DTR mode. So,
> +			 * read 2 and then discard the second byte.
> +			 */
> +			op.data.nbytes = 2;
> +		}
>  
>  		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
>  
>  		ret = spi_mem_exec_op(nor->spimem, &op);
>  	} else {
> -		ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WRSR, sr,
> -						       len);
> +		ret = spi_nor_controller_ops_read_reg(nor, opcode, nor->bouncebuf, len);
>  	}
>  
> -	if (ret) {
> -		dev_dbg(nor->dev, "error %d writing SR\n", ret);
> -		return ret;
> -	}
> -
> -	return spi_nor_wait_till_ready(nor);
> -}
> -
> -/**
> - * spi_nor_write_sr1_and_check() - Write one byte to the Status Register 1 and
> - * ensure that the byte written match the received value.
> - * @nor:	pointer to a 'struct spi_nor'.
> - * @sr1:	byte value to be written to the Status Register.
> - *
> - * Return: 0 on success, -errno otherwise.
> - */
> -static int spi_nor_write_sr1_and_check(struct spi_nor *nor, u8 sr1)
> -{
> -	int ret;
> -
> -	nor->bouncebuf[0] = sr1;
> -
> -	ret = spi_nor_write_sr(nor, nor->bouncebuf, 1);
> -	if (ret)
> -		return ret;
> -
> -	ret = spi_nor_read_sr(nor, nor->bouncebuf);
> -	if (ret)
> -		return ret;
> -
> -	if (nor->bouncebuf[0] != sr1) {
> -		dev_dbg(nor->dev, "SR1: read back test failed\n");
> -		return -EIO;
> -	}
> -
> -	return 0;
> -}
> -
> -/**
> - * spi_nor_write_16bit_sr_and_check() - Write the Status Register 1 and the
> - * Status Register 2 in one shot. Ensure that the byte written in the Status
> - * Register 1 match the received value, and that the 16-bit Write did not
> - * affect what was already in the Status Register 2.
> - * @nor:	pointer to a 'struct spi_nor'.
> - * @sr1:	byte value to be written to the Status Register 1.
> - *
> - * Return: 0 on success, -errno otherwise.
> - */
> -static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1)
> -{
> -	int ret;
> -	u8 *sr_cr = nor->bouncebuf;
> -	u8 cr_written;
> -
> -	/* Make sure we don't overwrite the contents of Status Register 2. */
> -	if (!(nor->flags & SNOR_F_NO_READ_CR)) {
> -		ret = spi_nor_read_cr(nor, &sr_cr[1]);
> -		if (ret)
> -			return ret;
> -	} else if ((spi_nor_get_protocol_width(nor->read_proto) == 4 ||
> -		    spi_nor_get_protocol_width(nor->write_proto) == 4) &&
> -		   nor->params->quad_enable) {
> -		/*
> -		 * If the Status Register 2 Read command (35h) is not
> -		 * supported, we should at least be sure we don't
> -		 * change the value of the SR2 Quad Enable bit.
> -		 *
> -		 * When the Quad Enable method is set and the buswidth is 4, we
> -		 * can safely assume that the value of the QE bit is one, as a
> -		 * consequence of the nor->params->quad_enable() call.
> -		 *
> -		 * According to the JESD216 revB standard, BFPT DWORDS[15],
> -		 * bits 22:20, the 16-bit Write Status (01h) command is
> -		 * available just for the cases in which the QE bit is
> -		 * described in SR2 at BIT(1).
> -		 */
> -		sr_cr[1] = SR2_QUAD_EN_BIT1;
> -	} else {
> -		sr_cr[1] = 0;
> -	}
> -
> -	sr_cr[0] = sr1;
> -
> -	ret = spi_nor_write_sr(nor, sr_cr, 2);
> -	if (ret)
> -		return ret;
> -
> -	ret = spi_nor_read_sr(nor, sr_cr);
> -	if (ret)
> -		return ret;
> -
> -	if (sr1 != sr_cr[0]) {
> -		dev_dbg(nor->dev, "SR: Read back test failed\n");
> -		return -EIO;
> -	}
> -
> -	if (nor->flags & SNOR_F_NO_READ_CR)
> -		return 0;
> -
> -	cr_written = sr_cr[1];
> -
> -	ret = spi_nor_read_cr(nor, &sr_cr[1]);
> -	if (ret)
> -		return ret;
> -
> -	if (cr_written != sr_cr[1]) {
> -		dev_dbg(nor->dev, "CR: read back test failed\n");
> -		return -EIO;
> -	}
> -
> -	return 0;
> -}
> -
> -/**
> - * spi_nor_write_16bit_cr_and_check() - Write the Status Register 1 and the
> - * Configuration Register in one shot. Ensure that the byte written in the
> - * Configuration Register match the received value, and that the 16-bit Write
> - * did not affect what was already in the Status Register 1.
> - * @nor:	pointer to a 'struct spi_nor'.
> - * @cr:		byte value to be written to the Configuration Register.
> - *
> - * Return: 0 on success, -errno otherwise.
> - */
> -int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr)
> -{
> -	int ret;
> -	u8 *sr_cr = nor->bouncebuf;
> -	u8 sr_written;
> -
> -	/* Keep the current value of the Status Register 1. */
> -	ret = spi_nor_read_sr(nor, sr_cr);
> -	if (ret)
> -		return ret;
> -
> -	sr_cr[1] = cr;
> -
> -	ret = spi_nor_write_sr(nor, sr_cr, 2);
> -	if (ret)
> -		return ret;
> -
> -	sr_written = sr_cr[0];
> -
> -	ret = spi_nor_read_sr(nor, sr_cr);
> -	if (ret)
> -		return ret;
> -
> -	if (sr_written != sr_cr[0]) {
> -		dev_dbg(nor->dev, "SR: Read back test failed\n");
> -		return -EIO;
> -	}
> -
> -	if (nor->flags & SNOR_F_NO_READ_CR)
> -		return 0;
> -
> -	ret = spi_nor_read_cr(nor, &sr_cr[1]);
> -	if (ret)
> -		return ret;
> -
> -	if (cr != sr_cr[1]) {
> -		dev_dbg(nor->dev, "CR: read back test failed\n");
> -		return -EIO;
> -	}
> -
> -	return 0;
> -}
> -
> -/**
> - * spi_nor_write_16bit_sr_cr_and_check() - Write the Status Register 1 and the
> - * Configuration Register in one shot. Ensure that the bytes written in both
> - * registers match the received value.
> - * @nor:	pointer to a 'struct spi_nor'.
> - * @regs:	two-byte array with values to be written to the status and
> - *		configuration registers.
> - *
> - * Return: 0 on success, -errno otherwise.
> - */
> -static int spi_nor_write_16bit_sr_cr_and_check(struct spi_nor *nor, const u8 *regs)
> -{
> -	u8 written_regs[2];
> -	int ret;
> -
> -	written_regs[0] = regs[0];
> -	written_regs[1] = regs[1];
> -	nor->bouncebuf[0] = regs[0];
> -	nor->bouncebuf[1] = regs[1];
> -
> -	ret = spi_nor_write_sr(nor, nor->bouncebuf, 2);
> -	if (ret)
> -		return ret;
> -
> -	ret = spi_nor_read_sr(nor, &nor->bouncebuf[0]);
> -	if (ret)
> -		return ret;
> -
> -	if (written_regs[0] != nor->bouncebuf[0]) {
> -		dev_dbg(nor->dev, "SR: Read back test failed\n");
> -		return -EIO;
> -	}
> -
> -	if (nor->flags & SNOR_F_NO_READ_CR)
> -		return 0;
> -
> -	ret = spi_nor_read_cr(nor, &nor->bouncebuf[1]);
> -	if (ret)
> -		return ret;
> -
> -	if (written_regs[1] != nor->bouncebuf[1]) {
> -		dev_dbg(nor->dev, "CR: read back test failed\n");
> -		return -EIO;
> -	}
> -
> -	return 0;
> -}
> -
> -/**
> - * spi_nor_write_sr_and_check() - Write the Status Register 1 and ensure that
> - * the byte written match the received value without affecting other bits in the
> - * Status Register 1 and 2.
> - * @nor:	pointer to a 'struct spi_nor'.
> - * @sr1:	byte value to be written to the Status Register.
> - *
> - * Return: 0 on success, -errno otherwise.
> - */
> -int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1)
> -{
> -	if (nor->flags & SNOR_F_HAS_16BIT_SR)
> -		return spi_nor_write_16bit_sr_and_check(nor, sr1);
> -
> -	return spi_nor_write_sr1_and_check(nor, sr1);
> -}
> -
> -/**
> - * spi_nor_write_sr_cr_and_check() - Write the Status Register 1 and ensure that
> - * the byte written match the received value. Same for the Control Register if
> - * available.
> - * @nor:	pointer to a 'struct spi_nor'.
> - * @regs:	byte array to be written to the registers.
> - *
> - * Return: 0 on success, -errno otherwise.
> - */
> -int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs)
> -{
> -	if (nor->flags & SNOR_F_HAS_16BIT_SR)
> -		return spi_nor_write_16bit_sr_cr_and_check(nor, regs);
> -
> -	return spi_nor_write_sr1_and_check(nor, regs[0]);
> -}
> -
> -/**
> - * spi_nor_write_sr2() - Write the Status Register 2 using the
> - * SPINOR_OP_WRSR2 (3eh) command.
> - * @nor:	pointer to 'struct spi_nor'.
> - * @sr2:	pointer to DMA-able buffer to write to the Status Register 2.
> - *
> - * Return: 0 on success, -errno otherwise.
> - */
> -static int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2)
> -{
> -	int ret;
> -
> -	ret = spi_nor_write_enable(nor);
> -	if (ret)
> -		return ret;
> -
> -	if (nor->spimem) {
> -		struct spi_mem_op op = SPI_NOR_WRSR2_OP(sr2);
> -
> -		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
> -
> -		ret = spi_mem_exec_op(nor->spimem, &op);
> -	} else {
> -		ret = spi_nor_controller_ops_write_reg(nor, SPINOR_OP_WRSR2,
> -						       sr2, 1);
> -	}
> -
> -	if (ret) {
> -		dev_dbg(nor->dev, "error %d writing SR2\n", ret);
> -		return ret;
> -	}
> -
> -	return spi_nor_wait_till_ready(nor);
> -}
> -
> -/**
> - * spi_nor_read_sr2() - Read the Status Register 2 using the
> - * SPINOR_OP_RDSR2 (3fh) command.
> - * @nor:	pointer to 'struct spi_nor'.
> - * @sr2:	pointer to DMA-able buffer where the value of the
> - *		Status Register 2 will be written.
> - *
> - * Return: 0 on success, -errno otherwise.
> - */
> -static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2)
> -{
> -	int ret;
> -
> -	if (nor->spimem) {
> -		struct spi_mem_op op = SPI_NOR_RDSR2_OP(sr2);
> -
> -		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
> -
> -		ret = spi_mem_exec_op(nor->spimem, &op);
> -	} else {
> -		ret = spi_nor_controller_ops_read_reg(nor, SPINOR_OP_RDSR2, sr2,
> -						      1);
> -	}
> +	memcpy(sr, nor->bouncebuf, len);
>  
>  	if (ret)
> -		dev_dbg(nor->dev, "error %d reading SR2\n", ret);
> +		dev_dbg(nor->dev, "Error %d reading Status Registers\n", ret);
>  
>  	return ret;
>  }
>  
> +/**
> + * spi_nor_write_sr_ll() - Low-level Status Registers write.
> + * @nor:	pointer to 'struct spi_nor'.
> + * @opcode:	opcode for the status register write operation.
> + * @sr:		pointer to status registers buffer to write.
> + * @len:	number of status registers to write.
> + *
> + * Return: 0 on success, -errno otherwise.
> + */
> +static int spi_nor_write_sr_ll(struct spi_nor *nor, u8 opcode,	const u8 *sr,
> +			       unsigned int len)
> +{
> +	int ret, i;
> +
> +	if (len > 2)
> +		return -EINVAL;
> +
> +	ret = spi_nor_write_enable(nor);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < len; i++)
> +		nor->bouncebuf[i] = sr[i];
> +
> +	if (nor->spimem) {
> +		struct spi_mem_op op = SPI_NOR_WRSR_OP(opcode,
> +						       nor->bouncebuf, len);
> +
> +		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
> +
> +		ret = spi_mem_exec_op(nor->spimem, &op);
> +	} else {
> +		ret = spi_nor_controller_ops_write_reg(nor, opcode,
> +						       nor->bouncebuf, len);
> +	}
> +
> +	if (ret) {
> +		dev_dbg(nor->dev, "Error %d writing Status Registers\n", ret);
> +		return ret;
> +	}
> +
> +	return spi_nor_wait_till_ready(nor);
> +}
> +
> +/**
> + * spi_nor_read_sr1() - Read SR1 only
> + * Useful for:
> + *   - The core to offer a generic "read SR1 and SR2" capability
> + *   - Manufacturer drivers (since they know the chip SR layout)
> + *   - Polling the BUSY bit in the core
> + *
> + * @nor: the spi_nor structure
> + * @sr1: pointer to a valid SR1 buffer
> + *
> + * Return 0 or errno.
> + */
> +int spi_nor_read_sr1(struct spi_nor *nor, u8 *sr1)
> +{
> +	return spi_nor_read_sr_ll(nor, nor->params->opcodes.read_sr1, sr1, 1);
> +}
> +
> +/**
> + * spi_nor_read_sr2() - Read SR2 only
> + * Useful for:
> + *   - The core to offer a generic "read SR1 and SR2" capability
> + *   - Manufacturer drivers (since they know the chip SR layout)
> + *   - SR2 based OTP configuration
> + *
> + * @nor: the spi_nor structure
> + * @sr2: pointer to a valid SR2 buffer
> + *
> + * Return 0 or errno.
> + */
> +int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2)
> +{
> +	struct spi_nor_flash_parameter *params = nor->params;
> +	int ret;
> +
> +	if (params->opcodes.read_sr2) {
> +		ret = spi_nor_read_sr_ll(nor, params->opcodes.read_sr2, sr2, 1);
> +		if (ret)
> +			return ret;
> +	} else if (spi_nor_get_protocol_width(nor->read_proto) == 4 ||
> +		   spi_nor_get_protocol_width(nor->write_proto) == 4) {
> +		/* Make sure the QE bit is persistently kept */
> +		*sr2 = params->qe_mask[1];

Mh. So before there was at least a check if a quad_enable cb is set.
Is this now implicit? because qe_mask[1] is zero if there is no
quad_enable? That should at *least* get some comment.

I tried to actually get the original use case, but all I've found
is Tudors commit 39d1e3340c73 ("mtd: spi-nor: Fix clearing of QE bit
on lock()/unlock()"). There wasn't much of a discussion on the ML,
and I couldn't find where Boris was suggesting it.

So before we cannot get rid of it anymore, we should just handle it
in the one caller which actually uses it: spi_nor_sr_{un,}lock().
I've checked the current spi_nor_write_sr_and_check() and the only
actual user is the swp.c. atmel.c is doing global write protection
on/off, which IIRC only uses SR1.

So let's move that weird handling there. Then chances are, we can
get rid of it as we know what flashes do locking because that is not
discoverable at runtime. It's just tedious work to check all the
datasheets. But as soon as we now put that into a more generic path,
we can't do anything about it anymore.


> +	} else {
> +		*sr2 = 0;

I'd expect an -EINVAL or -EIO here.

-michael

> +	}
> +
> +	return 0;
> +}
> +
> +/**
> + * spi_nor_read_sr1_and_sr2() - Read SR1 then SR2
> + * General purpose helper, always safe to call.
> + *
> + * @nor: the spi_nor structure
> + * @sr: pointer to a valid 2-byte array
> + *
> + * Return 0 or errno.
> + */
> +int spi_nor_read_sr1_and_sr2(struct spi_nor *nor, u8 *sr)
> +{
> +	int ret;
> +
> +	ret = spi_nor_read_sr1(nor, &sr[0]);
> +	if (ret)
> +		return ret;
> +
> +	return spi_nor_read_sr2(nor, &sr[1]);
> +}
> +
> +/**
> + * spi_nor_write_sr1() - Write SR1 only
> + * Useful for:
> + *   - Manufacturer drivers (since they know the chip SR layout)
> + *
> + * @nor: the spi_nor structure
> + * @sr1: pointer to a valid SR1 buffer
> + *
> + * Return 0 or errno.
> + */
> +int spi_nor_write_sr1(struct spi_nor *nor, const u8 *sr1)
> +{
> +	if (WARN_ONCE(!nor->params->opcodes.write_sr1,
> +		      "Restricted helper use, write SR1 not supported"))
> +		return -EIO;
> +
> +	return spi_nor_write_sr_ll(nor, nor->params->opcodes.write_sr1, sr1, 1);
> +}
> +
> +/**
> + * spi_nor_write_sr2() - Write SR2 only
> + * Useful for:
> + *   - Manufacturer drivers (since they know the chip SR layout)
> + *   - SR2 based OTP configuration
> + *
> + * @nor: the spi_nor structure
> + * @sr2: pointer to a valid SR2 buffer
> + *
> + * Return 0 or errno.
> + */
> +int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2)
> +{
> +	if (WARN_ONCE(!nor->params->opcodes.write_sr2,
> +		      "Restricted helper use, write SR2 not supported"))
> +		return -EIO;
> +
> +	return spi_nor_write_sr_ll(nor, nor->params->opcodes.write_sr2, sr2, 1);
> +}
> +
> +/**
> + * spi_nor_write_sr1_and_sr2() - Write SR1 then SR2
> + * General purpose helper, always safe to call. Will expectedly ignore
> + * SR2 on certain chips.
> + *
> + * @nor: the spi_nor structure
> + * @sr: pointer to a valid 2-byte array
> + *
> + * Return 0 or errno.
> + */
> +static int spi_nor_write_sr1_and_sr2(struct spi_nor *nor, const u8 *sr)
> +{
> +	struct spi_nor_flash_parameter *params = nor->params;
> +	int ret;
> +
> +	if (params->opcodes.write_sr1_and_sr2)
> +		return spi_nor_write_sr_ll(nor,
> +					   params->opcodes.write_sr1_and_sr2,
> +					   sr, 2);
> +
> +	ret = spi_nor_write_sr1(nor, &sr[0]);
> +	if (ret)
> +		return ret;
> +
> +	if (params->opcodes.write_sr2)
> +		return spi_nor_write_sr2(nor, &sr[1]);
> +
> +	return 0;
> +}
> +
> +/**
> + * spi_nor_write_sr1_and_sr2_and_check() - Write SR1 then SR2, then read
> + *					   them back and verifies
> + * General purpose helper, always safe to call. Will expectedly ignore
> + * SR2 on certain chips.
> + *
> + * @nor: the spi_nor structure
> + * @sr: pointer to a valid 2-byte array
> + *
> + * Return 0 or errno.
> + */
> +int spi_nor_write_sr1_and_sr2_and_check(struct spi_nor *nor, const u8 *sr)
> +{
> +	u8 tmp[2];
> +	int ret;
> +
> +	ret = spi_nor_write_sr1_and_sr2(nor, sr);
> +	if (ret)
> +		return ret;
> +
> +	ret = spi_nor_read_sr1_and_sr2(nor, tmp);
> +	if (ret)
> +		return ret;
> +
> +	if (sr[0] != tmp[0] || sr[1] != tmp[1])
> +		return -EIO;
> +
> +	return 0;
> +}
> +
> +/**
> + * spi_nor_generic_quad_enable() - Read the status registers,
> + *				   apply the QE bit mask,
> + *				   write the status registers,
> + *				   read them back and check the content.
> + *
> + * @nor:	pointer to 'struct spi_nor'
> + *
> + * Return: 0 on success, -errno otherwise.
> + */
> +static int spi_nor_generic_quad_enable(struct spi_nor *nor)
> +{
> +	u8 *qe_mask = nor->params->qe_mask;
> +	u8 sr[2] = {};
> +	int ret;
> +
> +	if (!qe_mask[0] && !qe_mask[1])
> +		return 0;
> +
> +	ret = spi_nor_read_sr1_and_sr2(nor, sr);
> +	if (ret)
> +		return ret;
> +
> +	if (sr[0] & qe_mask[0] || sr[1] & qe_mask[1])
> +		return 0;
> +
> +	sr[0] |= qe_mask[0];
> +	sr[1] |= qe_mask[1];
> +
> +	return spi_nor_write_sr1_and_sr2_and_check(nor, sr);
> +}
> +
>  /**
>   * spi_nor_erase_die() - Erase the entire die.
>   * @nor:	pointer to 'struct spi_nor'.
> @@ -1904,106 +1788,6 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
>  	return ret;
>  }
>  
> -/**
> - * spi_nor_sr1_bit6_quad_enable() - Set the Quad Enable BIT(6) in the Status
> - * Register 1.
> - * @nor:	pointer to a 'struct spi_nor'
> - *
> - * Bit 6 of the Status Register 1 is the QE bit for Macronix like QSPI memories.
> - *
> - * Return: 0 on success, -errno otherwise.
> - */
> -int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor)
> -{
> -	int ret;
> -
> -	ret = spi_nor_read_sr(nor, nor->bouncebuf);
> -	if (ret)
> -		return ret;
> -
> -	if (nor->bouncebuf[0] & SR1_QUAD_EN_BIT6)
> -		return 0;
> -
> -	nor->bouncebuf[0] |= SR1_QUAD_EN_BIT6;
> -
> -	return spi_nor_write_sr1_and_check(nor, nor->bouncebuf[0]);
> -}
> -
> -/**
> - * spi_nor_sr2_bit1_quad_enable() - set the Quad Enable BIT(1) in the Status
> - * Register 2.
> - * @nor:       pointer to a 'struct spi_nor'.
> - *
> - * Bit 1 of the Status Register 2 is the QE bit for Spansion like QSPI memories.
> - *
> - * Return: 0 on success, -errno otherwise.
> - */
> -int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor)
> -{
> -	int ret;
> -
> -	if (nor->flags & SNOR_F_NO_READ_CR)
> -		return spi_nor_write_16bit_cr_and_check(nor, SR2_QUAD_EN_BIT1);
> -
> -	ret = spi_nor_read_cr(nor, nor->bouncebuf);
> -	if (ret)
> -		return ret;
> -
> -	if (nor->bouncebuf[0] & SR2_QUAD_EN_BIT1)
> -		return 0;
> -
> -	nor->bouncebuf[0] |= SR2_QUAD_EN_BIT1;
> -
> -	return spi_nor_write_16bit_cr_and_check(nor, nor->bouncebuf[0]);
> -}
> -
> -/**
> - * spi_nor_sr2_bit7_quad_enable() - set QE bit in Status Register 2.
> - * @nor:	pointer to a 'struct spi_nor'
> - *
> - * Set the Quad Enable (QE) bit in the Status Register 2.
> - *
> - * This is one of the procedures to set the QE bit described in the SFDP
> - * (JESD216 rev B) specification but no manufacturer using this procedure has
> - * been identified yet, hence the name of the function.
> - *
> - * Return: 0 on success, -errno otherwise.
> - */
> -int spi_nor_sr2_bit7_quad_enable(struct spi_nor *nor)
> -{
> -	u8 *sr2 = nor->bouncebuf;
> -	int ret;
> -	u8 sr2_written;
> -
> -	/* Check current Quad Enable bit value. */
> -	ret = spi_nor_read_sr2(nor, sr2);
> -	if (ret)
> -		return ret;
> -	if (*sr2 & SR2_QUAD_EN_BIT7)
> -		return 0;
> -
> -	/* Update the Quad Enable bit. */
> -	*sr2 |= SR2_QUAD_EN_BIT7;
> -
> -	ret = spi_nor_write_sr2(nor, sr2);
> -	if (ret)
> -		return ret;
> -
> -	sr2_written = *sr2;
> -
> -	/* Read back and check it. */
> -	ret = spi_nor_read_sr2(nor, sr2);
> -	if (ret)
> -		return ret;
> -
> -	if (*sr2 != sr2_written) {
> -		dev_dbg(nor->dev, "SR2: Read back test failed\n");
> -		return -EIO;
> -	}
> -
> -	return 0;
> -}
> -
>  static const struct spi_nor_manufacturer *manufacturers[] = {
>  	&spi_nor_atmel,
>  	&spi_nor_eon,
> @@ -2494,6 +2278,7 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps)
>  {
>  	struct spi_nor_flash_parameter *params = nor->params;
>  	unsigned int cap;
> +	u8 opcode;
>  
>  	/* X-X-X modes are not supported yet, mask them all. */
>  	*hwcaps &= ~SNOR_HWCAPS_X_X_X;
> @@ -2525,14 +2310,14 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps)
>  			*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);
> -
> +	/* Some SPI controllers might not support reading SR2 */
> +	opcode = nor->params->opcodes.read_sr2;
> +	if (opcode) {
> +		struct spi_mem_op op = SPI_NOR_RDSR_OP(opcode, nor->bouncebuf, 1);
>  		spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
>  
>  		if (!spi_mem_supports_op(nor->spimem, &op))
> -			nor->flags |= SNOR_F_NO_READ_CR;
> +			nor->params->opcodes.read_sr2 = 0;
>  	}
>  }
>  
> @@ -3107,11 +2892,14 @@ static void spi_nor_init_default_params(struct spi_nor *nor)
>  	const struct flash_info *info = nor->info;
>  	struct device_node *np = spi_nor_get_flash_node(nor);
>  
> -	params->quad_enable = spi_nor_sr2_bit1_quad_enable;
> -	params->otp.org = info->otp;
> +	/* Default to 16-bit Read/Write Status commands */
> +	params->opcodes.read_sr1 = SPINOR_OP_RDSR;
> +	params->opcodes.read_sr2 = SPINOR_OP_RDCR;
> +	params->opcodes.write_sr1_and_sr2 = SPINOR_OP_WRSR;
> +	params->quad_enable = spi_nor_generic_quad_enable;
> +	params->qe_mask[1] = BIT(1);
>  
> -	/* Default to 16-bit Write Status (01h) Command */
> -	nor->flags |= SNOR_F_HAS_16BIT_SR;
> +	params->otp.org = info->otp;
>  
>  	/* Set SPI NOR sizes. */
>  	params->writesize = 1;
> diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
> index 2cc0c80da3fa..7d43d3ba47ee 100644
> --- a/drivers/mtd/spi-nor/core.h
> +++ b/drivers/mtd/spi-nor/core.h
> @@ -37,36 +37,18 @@
>  		   SPI_MEM_OP_NO_DUMMY,					\
>  		   SPI_MEM_OP_NO_DATA)
>  
> -#define SPI_NOR_RDSR_OP(buf)						\
> -	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR, 0),			\
> +#define SPI_NOR_RDSR_OP(opcode, buf, len)				\
> +	SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0),				\
>  		   SPI_MEM_OP_NO_ADDR,					\
>  		   SPI_MEM_OP_NO_DUMMY,					\
> -		   SPI_MEM_OP_DATA_IN(1, buf, 0))
> +		   SPI_MEM_OP_DATA_IN(len, buf, 0))
>  
> -#define SPI_NOR_WRSR_OP(buf, len)					\
> -	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR, 0),			\
> +#define SPI_NOR_WRSR_OP(opcode, buf, len)				\
> +	SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0),				\
>  		   SPI_MEM_OP_NO_ADDR,					\
>  		   SPI_MEM_OP_NO_DUMMY,					\
>  		   SPI_MEM_OP_DATA_OUT(len, buf, 0))
>  
> -#define SPI_NOR_RDSR2_OP(buf)						\
> -	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDSR2, 0),			\
> -		   SPI_MEM_OP_NO_ADDR,					\
> -		   SPI_MEM_OP_NO_DUMMY,					\
> -		   SPI_MEM_OP_DATA_OUT(1, buf, 0))
> -
> -#define SPI_NOR_WRSR2_OP(buf)						\
> -	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_WRSR2, 0),			\
> -		   SPI_MEM_OP_NO_ADDR,					\
> -		   SPI_MEM_OP_NO_DUMMY,					\
> -		   SPI_MEM_OP_DATA_OUT(1, buf, 0))
> -
> -#define SPI_NOR_RDCR_OP(buf)						\
> -	SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_RDCR, 0),			\
> -		   SPI_MEM_OP_NO_ADDR,					\
> -		   SPI_MEM_OP_NO_DUMMY,					\
> -		   SPI_MEM_OP_DATA_IN(1, buf, 0))
> -
>  #define SPI_NOR_EN4B_EX4B_OP(enable)					\
>  	SPI_MEM_OP(SPI_MEM_OP_CMD(enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B, 0),	\
>  		   SPI_MEM_OP_NO_ADDR,					\
> @@ -129,8 +111,6 @@ enum spi_nor_option_flags {
>  	SNOR_F_4B_OPCODES	= BIT(3),
>  	SNOR_F_HAS_4BAIT	= BIT(4),
>  	SNOR_F_HAS_LOCK		= BIT(5),
> -	SNOR_F_HAS_16BIT_SR	= BIT(6),
> -	SNOR_F_NO_READ_CR	= BIT(7),
>  	SNOR_F_HAS_SR_TB_BIT6	= BIT(8),
>  	SNOR_F_HAS_4BIT_BP      = BIT(9),
>  	SNOR_F_HAS_SR_BP3_BIT6  = BIT(10),
> @@ -345,9 +325,19 @@ struct spi_nor_otp {
>   * List of variable opcodes used by the chip.
>   *
>   * @die_erase: opcode for erasing a die, defaults to SPINOR_OP_CHIP_ERASE
> + * @read_sr1: opcode for reading SR1 alone
> + * @read_sr2: opcode for reading SR2 alone
> + * @write_sr1: opcode for writing SR1 alone
> + * @write_sr2: opcode for writing SR2 alone
> + * @write_sr1_and_sr2: opcode for writing SR1 then SR2 in one operation
>   */
>  struct spi_nor_opcodes {
>  	u8 die_erase;
> +	u8 read_sr1;
> +	u8 read_sr2;
> +	u8 write_sr1;
> +	u8 write_sr2;
> +	u8 write_sr1_and_sr2;
>  };
>  
>  /**
> @@ -384,6 +374,7 @@ struct spi_nor_opcodes {
>   * @otp:		SPI NOR OTP info.
>   * @set_octal_dtr:	enables or disables SPI NOR octal DTR mode.
>   * @quad_enable:	enables SPI NOR quad mode.
> + * @qe_mask:		two bytes mask used to set/clear the QE bit
>   * @set_4byte_addr_mode: puts the SPI NOR in 4 byte addressing mode.
>   * @ready:		(optional) flashes might use a different mechanism
>   *			than reading the status register to indicate they
> @@ -413,6 +404,7 @@ struct spi_nor_flash_parameter {
>  
>  	int (*set_octal_dtr)(struct spi_nor *nor, bool enable);
>  	int (*quad_enable)(struct spi_nor *nor);
> +	u8				qe_mask[2];
>  	int (*set_4byte_addr_mode)(struct spi_nor *nor, bool enable);
>  	int (*ready)(struct spi_nor *nor);
>  
> @@ -641,18 +633,15 @@ int spi_nor_wait_till_ready(struct spi_nor *nor);
>  int spi_nor_global_block_unlock(struct spi_nor *nor);
>  int spi_nor_prep_and_lock(struct spi_nor *nor);
>  void spi_nor_unlock_and_unprep(struct spi_nor *nor);
> -int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor);
> -int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor);
> -int spi_nor_sr2_bit7_quad_enable(struct spi_nor *nor);
>  int spi_nor_read_id(struct spi_nor *nor, u8 naddr, u8 ndummy, u8 *id,
>  		    enum spi_nor_protocol reg_proto);
> -int spi_nor_read_sr(struct spi_nor *nor, u8 *sr);
>  int spi_nor_sr_ready(struct spi_nor *nor);
> -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_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_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs);
> +int spi_nor_read_sr1(struct spi_nor *nor, u8 *sr1);
> +int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2);
> +int spi_nor_read_sr1_and_sr2(struct spi_nor *nor, u8 *sr);
> +int spi_nor_write_sr1(struct spi_nor *nor, const u8 *sr1);
> +int spi_nor_write_sr2(struct spi_nor *nor, const u8 *sr2);
> +int spi_nor_write_sr1_and_sr2_and_check(struct spi_nor *nor, const u8 *sr);
>  
>  ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
>  			  u8 *buf);
> diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
> index ef6f0ffcd6bb..512825c3fc85 100644
> --- a/drivers/mtd/spi-nor/debugfs.c
> +++ b/drivers/mtd/spi-nor/debugfs.c
> @@ -19,8 +19,6 @@ static const char *const snor_f_names[] = {
>  	SNOR_F_NAME(4B_OPCODES),
>  	SNOR_F_NAME(HAS_4BAIT),
>  	SNOR_F_NAME(HAS_LOCK),
> -	SNOR_F_NAME(HAS_16BIT_SR),
> -	SNOR_F_NAME(NO_READ_CR),
>  	SNOR_F_NAME(HAS_SR_TB_BIT6),
>  	SNOR_F_NAME(HAS_4BIT_BP),
>  	SNOR_F_NAME(HAS_SR_BP3_BIT6),
> diff --git a/drivers/mtd/spi-nor/gigadevice.c b/drivers/mtd/spi-nor/gigadevice.c
> index ef1edd0add70..4070a692e968 100644
> --- a/drivers/mtd/spi-nor/gigadevice.c
> +++ b/drivers/mtd/spi-nor/gigadevice.c
> @@ -23,8 +23,10 @@ gd25q256_post_bfpt(struct spi_nor *nor,
>  	 *      GD25Q256E      | SFDP_JESD216_MAJOR | SFDP_JESD216B_MINOR
>  	 */
>  	if (bfpt_header->major == SFDP_JESD216_MAJOR &&
> -	    bfpt_header->minor == SFDP_JESD216_MINOR)
> -		nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
> +	    bfpt_header->minor == SFDP_JESD216_MINOR) {
> +		nor->params->qe_mask[0] = BIT(6);
> +		nor->params->qe_mask[1] = 0;
> +	}
>  
>  	return 0;
>  }
> diff --git a/drivers/mtd/spi-nor/issi.c b/drivers/mtd/spi-nor/issi.c
> index 18d9a00aa22e..04db42b5141e 100644
> --- a/drivers/mtd/spi-nor/issi.c
> +++ b/drivers/mtd/spi-nor/issi.c
> @@ -131,7 +131,8 @@ static const struct flash_info issi_nor_parts[] = {
>  
>  static void issi_nor_default_init(struct spi_nor *nor)
>  {
> -	nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
> +	nor->params->qe_mask[0] = BIT(6);
> +	nor->params->qe_mask[1] = 0;
>  }
>  
>  static const struct spi_nor_fixups issi_fixups = {
> diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c
> index e97f5cbd9aad..55612237c1ef 100644
> --- a/drivers/mtd/spi-nor/macronix.c
> +++ b/drivers/mtd/spi-nor/macronix.c
> @@ -65,11 +65,12 @@ mx25l3255e_late_init_fixups(struct spi_nor *nor)
>  
>  	/*
>  	 * SFDP of MX25L3255E is JESD216, which does not include the Quad
> -	 * Enable bit Requirement in BFPT. As a result, during BFPT parsing,
> -	 * the quad_enable method is not set to spi_nor_sr1_bit6_quad_enable.
> -	 * Therefore, it is necessary to correct this setting by late_init.
> +	 * Enable bit Requirement in BFPT. As a result, during BFPT parsing
> +	 * the quad_enable mask is reset. Therefore, it is necessary to
> +	 * correct this setting by late_init.
>  	 */
> -	params->quad_enable = spi_nor_sr1_bit6_quad_enable;
> +	params->qe_mask[0] = BIT(6);
> +	params->qe_mask[1] = 0;
>  
>  	/*
>  	 * In addition, MX25L3255E also supports 1-4-4 page program in 3-byte
> @@ -314,7 +315,8 @@ static int macronix_nor_set_octal_dtr(struct spi_nor *nor, bool enable)
>  
>  static void macronix_nor_default_init(struct spi_nor *nor)
>  {
> -	nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
> +	nor->params->qe_mask[0] = BIT(6);
> +	nor->params->qe_mask[1] = 0;
>  }
>  
>  static int macronix_nor_late_init(struct spi_nor *nor)
> diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c
> index 031cf887318c..344539d2a36a 100644
> --- a/drivers/mtd/spi-nor/micron-st.c
> +++ b/drivers/mtd/spi-nor/micron-st.c
> @@ -229,7 +229,7 @@ static int mt25qu512a_post_bfpt_fixup(struct spi_nor *nor,
>  				      const struct sfdp_parameter_header *bfpt_header,
>  				      const struct sfdp_bfpt *bfpt)
>  {
> -	nor->flags &= ~SNOR_F_HAS_16BIT_SR;
> +	nor->params->opcodes.write_sr1 = 0;
>  	return 0;
>  }
>  
> @@ -633,7 +633,7 @@ static int micron_st_nor_ready(struct spi_nor *nor)
>  static void micron_st_nor_default_init(struct spi_nor *nor)
>  {
>  	nor->flags |= SNOR_F_HAS_LOCK;
> -	nor->flags &= ~SNOR_F_HAS_16BIT_SR;
> +	nor->params->opcodes.write_sr1 = 0;
>  	nor->params->quad_enable = NULL;
>  }
>  
> diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c
> index 7d0b145d78d8..1eeb33a20091 100644
> --- a/drivers/mtd/spi-nor/otp.c
> +++ b/drivers/mtd/spi-nor/otp.c
> @@ -175,24 +175,24 @@ static int spi_nor_otp_lock_bit_cr(unsigned int region)
>   */
>  int spi_nor_otp_lock_sr2(struct spi_nor *nor, unsigned int region)
>  {
> -	u8 *cr = nor->bouncebuf;
>  	int ret, lock_bit;
> +	u8 sr[2];
>  
>  	lock_bit = spi_nor_otp_lock_bit_cr(region);
>  	if (lock_bit < 0)
>  		return lock_bit;
>  
> -	ret = spi_nor_read_cr(nor, cr);
> +	ret = spi_nor_read_sr1_and_sr2(nor, sr);
>  	if (ret)
>  		return ret;
>  
>  	/* no need to write the register if region is already locked */
> -	if (cr[0] & lock_bit)
> +	if (sr[1] & lock_bit)
>  		return 0;
>  
> -	cr[0] |= lock_bit;
> +	sr[1] |= lock_bit;
>  
> -	return spi_nor_write_16bit_cr_and_check(nor, cr[0]);
> +	return spi_nor_write_sr1_and_sr2_and_check(nor, sr);
>  }
>  
>  /**
> @@ -207,18 +207,18 @@ 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)
>  {
> -	u8 *cr = nor->bouncebuf;
>  	int ret, lock_bit;
> +	u8 sr2;
>  
>  	lock_bit = spi_nor_otp_lock_bit_cr(region);
>  	if (lock_bit < 0)
>  		return lock_bit;
>  
> -	ret = spi_nor_read_cr(nor, cr);
> +	ret = spi_nor_read_sr2(nor, &sr2);
>  	if (ret)
>  		return ret;
>  
> -	return cr[0] & lock_bit;
> +	return sr2 & lock_bit;
>  }
>  
>  static loff_t spi_nor_otp_region_start(const struct spi_nor *nor, unsigned int region)
> diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
> index 526763c79f55..1d799f02b8e5 100644
> --- a/drivers/mtd/spi-nor/sfdp.c
> +++ b/drivers/mtd/spi-nor/sfdp.c
> @@ -561,10 +561,21 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
>  	val >>= BFPT_DWORD11_PAGE_SIZE_SHIFT;
>  	params->page_size = 1U << val;
>  
> +	/*
> +	 * The standard declares various read and write status methods, some of
> +	 * them will be overloaded based on the QER field.
> +	 */
> +	params->opcodes.read_sr1 = SPINOR_OP_RDSR;
> +	params->opcodes.read_sr2 = SPINOR_OP_RDCR;
> +	params->opcodes.write_sr1 = SPINOR_OP_WRSR;
> +	params->opcodes.write_sr1_and_sr2 = SPINOR_OP_WRSR;
> +
> +	params->qe_mask[0] = 0;
> +	params->qe_mask[1] = 0;
> +
>  	/* Quad Enable Requirements. */
>  	switch (bfpt.dwords[SFDP_DWORD(15)] & BFPT_DWORD15_QER_MASK) {
>  	case BFPT_DWORD15_QER_NONE:
> -		params->quad_enable = NULL;
>  		break;
>  
>  	case BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR:
> @@ -572,23 +583,28 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
>  		 * Writing only one byte to the Status Register has the
>  		 * side-effect of clearing Status Register 2.
>  		 */
> +		fallthrough;
>  	case BFPT_DWORD15_QER_SR2_BIT1_NO_RD:
>  		/*
>  		 * Read Configuration Register (35h) instruction is not
> -		 * supported.
> +		 * supported. 16-bit writes expected.
>  		 */
> -		nor->flags |= SNOR_F_HAS_16BIT_SR | SNOR_F_NO_READ_CR;
> -		params->quad_enable = spi_nor_sr2_bit1_quad_enable;
> +		params->opcodes.read_sr2 = 0;
> +		params->opcodes.write_sr1 = 0;
> +		params->qe_mask[1] = BIT(1);
>  		break;
>  
>  	case BFPT_DWORD15_QER_SR1_BIT6:
> -		nor->flags &= ~SNOR_F_HAS_16BIT_SR;
> -		params->quad_enable = spi_nor_sr1_bit6_quad_enable;
> +		params->opcodes.read_sr2 = 0;
> +		params->opcodes.write_sr1_and_sr2 = 0;
> +		params->qe_mask[0] = BIT(6);
>  		break;
>  
>  	case BFPT_DWORD15_QER_SR2_BIT7:
> -		nor->flags &= ~SNOR_F_HAS_16BIT_SR;
> -		params->quad_enable = spi_nor_sr2_bit7_quad_enable;
> +		params->opcodes.read_sr2 = SPINOR_OP_RDSR2;
> +		params->opcodes.write_sr1_and_sr2 = 0;
> +		params->opcodes.write_sr2 = SPINOR_OP_WRSR2;
> +		params->qe_mask[1] = BIT(7);
>  		break;
>  
>  	case BFPT_DWORD15_QER_SR2_BIT1:
> @@ -598,9 +614,8 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
>  		 * Register 2, so let's be cautious and keep the default
>  		 * assumption of a 16-bit Write Status (01h) command.
>  		 */
> -		nor->flags |= SNOR_F_HAS_16BIT_SR;
> -
> -		params->quad_enable = spi_nor_sr2_bit1_quad_enable;
> +		params->opcodes.write_sr1 = 0;
> +		params->qe_mask[1] = BIT(1);
>  		break;
>  
>  	default:
> @@ -608,6 +623,10 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
>  		break;
>  	}
>  
> +	/* opcodes sanity check */
> +	WARN_ON(!params->opcodes.read_sr1 ||
> +		(!params->opcodes.write_sr1 && !params->opcodes.write_sr1_and_sr2));
> +
>  	dword = bfpt.dwords[SFDP_DWORD(16)] & BFPT_DWORD16_4B_ADDR_MODE_MASK;
>  	if (SFDP_MASK_CHECK(dword, BFPT_DWORD16_4B_ADDR_MODE_BRWR))
>  		params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode_brwr;
> diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c
> index 2a8238c707e1..ca2703a5a338 100644
> --- a/drivers/mtd/spi-nor/spansion.c
> +++ b/drivers/mtd/spi-nor/spansion.c
> @@ -399,8 +399,9 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor,
>  					  nor->bouncebuf);
>  	bool is3byte, is4byte;
>  	int ret;
> +	u8 sr;
>  
> -	ret = spi_nor_read_sr(nor, &nor->bouncebuf[1]);
> +	ret = spi_nor_read_sr1(nor, &sr);
>  	if (ret)
>  		return ret;
>  
> @@ -408,7 +409,7 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor,
>  	if (ret)
>  		return ret;
>  
> -	is3byte = (nor->bouncebuf[0] == nor->bouncebuf[1]);
> +	is3byte = (nor->bouncebuf[0] == sr);
>  
>  	op = (struct spi_mem_op)
>  		CYPRESS_NOR_RD_ANY_REG_OP(4, SPINOR_REG_CYPRESS_STR1V, 0,
> @@ -417,7 +418,7 @@ static int cypress_nor_determine_addr_mode_by_sr1(struct spi_nor *nor,
>  	if (ret)
>  		return ret;
>  
> -	is4byte = (nor->bouncebuf[0] == nor->bouncebuf[1]);
> +	is4byte = (nor->bouncebuf[0] == sr);
>  
>  	if (is3byte == is4byte)
>  		return -EIO;
> @@ -1103,13 +1104,14 @@ static const struct flash_info spansion_nor_parts[] = {
>  static int spansion_nor_sr_ready_and_clear(struct spi_nor *nor)
>  {
>  	int ret;
> +	u8 sr;
>  
> -	ret = spi_nor_read_sr(nor, nor->bouncebuf);
> +	ret = spi_nor_read_sr1(nor, &sr);
>  	if (ret)
>  		return ret;
>  
> -	if (nor->bouncebuf[0] & (SR_E_ERR | SR_P_ERR)) {
> -		if (nor->bouncebuf[0] & SR_E_ERR)
> +	if (sr & (SR_E_ERR | SR_P_ERR)) {
> +		if (sr & SR_E_ERR)
>  			dev_err(nor->dev, "Erase Error occurred\n");
>  		else
>  			dev_err(nor->dev, "Programming Error occurred\n");
> @@ -1129,7 +1131,7 @@ static int spansion_nor_sr_ready_and_clear(struct spi_nor *nor)
>  		return -EIO;
>  	}
>  
> -	return !(nor->bouncebuf[0] & SR_WIP);
> +	return !(sr & SR_WIP);
>  }
>  
>  static int spansion_nor_late_init(struct spi_nor *nor)
> diff --git a/drivers/mtd/spi-nor/sst.c b/drivers/mtd/spi-nor/sst.c
> index db02c14ba16f..77a0bc41d4e5 100644
> --- a/drivers/mtd/spi-nor/sst.c
> +++ b/drivers/mtd/spi-nor/sst.c
> @@ -21,16 +21,17 @@ static int sst26vf_nor_lock(struct spi_nor *nor, loff_t ofs, u64 len)
>  static int sst26vf_nor_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
>  {
>  	int ret;
> +	u8 cr;
>  
>  	/* We only support unlocking the entire flash array. */
>  	if (ofs != 0 || len != nor->params->size)
>  		return -EINVAL;
>  
> -	ret = spi_nor_read_cr(nor, nor->bouncebuf);
> +	ret = spi_nor_read_sr2(nor, &cr);
>  	if (ret)
>  		return ret;
>  
> -	if (!(nor->bouncebuf[0] & SST26VF_CR_BPNV)) {
> +	if (!(cr & SST26VF_CR_BPNV)) {
>  		dev_dbg(nor->dev, "Any block has been permanently locked\n");
>  		return -EINVAL;
>  	}
> diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
> index 235070b215d1..b7b291e15917 100644
> --- a/drivers/mtd/spi-nor/swp.c
> +++ b/drivers/mtd/spi-nor/swp.c
> @@ -36,7 +36,7 @@ static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor)
>  
>  static u8 spi_nor_get_sr_cmp_mask(struct spi_nor *nor)
>  {
> -	if (!(nor->flags & SNOR_F_NO_READ_CR) &&
> +	if (nor->params->opcodes.read_sr2 &&
>  	    nor->flags & SNOR_F_HAS_SR2_CMP_BIT6)
>  		return SR2_CMP_BIT6;
>  	else
> @@ -207,17 +207,9 @@ void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr)
>  
>  
>  	if (!sr) {
> -		if (spi_nor_read_sr(nor, nor->bouncebuf))
> +		if (spi_nor_read_sr1_and_sr2(nor, sr_cr))
>  			return;
>  
> -		sr_cr[0] = nor->bouncebuf[0];
> -
> -		if (!(nor->flags & SNOR_F_NO_READ_CR)) {
> -			if (spi_nor_read_cr(nor, nor->bouncebuf))
> -				return;
> -		}
> -
> -		sr_cr[1] = nor->bouncebuf[0];
>  		sr = sr_cr;
>  	}
>  
> @@ -273,20 +265,10 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
>  	int ret;
>  	u8 pow;
>  
> -	ret = spi_nor_read_sr(nor, nor->bouncebuf);
> +	ret = spi_nor_read_sr1_and_sr2(nor, status_old);
>  	if (ret)
>  		return ret;
>  
> -	status_old[0] = nor->bouncebuf[0];
> -
> -	if (!(nor->flags & SNOR_F_NO_READ_CR)) {
> -		ret = spi_nor_read_cr(nor, nor->bouncebuf);
> -		if (ret)
> -			return ret;
> -
> -		status_old[1] = 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;
> @@ -378,10 +360,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
>  	    (ofs_old < ofs_new || (ofs_new + len_new) < (ofs_old + len_old)))
>  		return -EINVAL;
>  
> -	if (nor->flags & SNOR_F_NO_READ_CR)
> -		ret = spi_nor_write_sr_and_check(nor, best_status_new[0]);
> -	else
> -		ret = spi_nor_write_sr_cr_and_check(nor, best_status_new);
> +	ret = spi_nor_write_sr1_and_sr2_and_check(nor, best_status_new);
>  	if (ret)
>  		return ret;
>  
> @@ -409,20 +388,10 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
>  	int ret;
>  	u8 pow;
>  
> -	ret = spi_nor_read_sr(nor, nor->bouncebuf);
> +	ret = spi_nor_read_sr1_and_sr2(nor, status_old);
>  	if (ret)
>  		return ret;
>  
> -	status_old[0] = nor->bouncebuf[0];
> -
> -	if (!(nor->flags & SNOR_F_NO_READ_CR)) {
> -		ret = spi_nor_read_cr(nor, nor->bouncebuf);
> -		if (ret)
> -			return ret;
> -
> -		status_old[1] = 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;
> @@ -512,10 +481,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
>  	    (ofs_new < ofs_old || (ofs_old + len_old) < (ofs_new + len_new)))
>  		return -EINVAL;
>  
> -	if (nor->flags & SNOR_F_NO_READ_CR)
> -		ret = spi_nor_write_sr_and_check(nor, best_status_new[0]);
> -	else
> -		ret = spi_nor_write_sr_cr_and_check(nor, best_status_new);
> +	ret = spi_nor_write_sr1_and_sr2_and_check(nor, best_status_new);
>  	if (ret)
>  		return ret;
>  
> @@ -536,20 +502,10 @@ static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, u64 len)
>  	u8 sr_cr[2] = {};
>  	int ret;
>  
> -	ret = spi_nor_read_sr(nor, nor->bouncebuf);
> +	ret = spi_nor_read_sr1_and_sr2(nor, sr_cr);
>  	if (ret)
>  		return ret;
>  
> -	sr_cr[0] = nor->bouncebuf[0];
> -
> -	if (!(nor->flags & SNOR_F_NO_READ_CR)) {
> -		ret = spi_nor_read_cr(nor, nor->bouncebuf);
> -		if (ret)
> -			return ret;
> -
> -		sr_cr[1] = nor->bouncebuf[0];
> -	}
> -
>  	return spi_nor_is_locked_sr(nor, ofs, len, sr_cr);
>  }
>  
> diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
> index 593429bd6cdf..01a693033448 100644
> --- a/drivers/mtd/spi-nor/winbond.c
> +++ b/drivers/mtd/spi-nor/winbond.c
> @@ -520,12 +520,12 @@ static int winbond_nor_late_init(struct spi_nor *nor)
>  	 * been to declare CR reads as unsupported, whereas the Jedec
>  	 * specification doesn't clearly state that. In practice, all these
>  	 * chips do support reading back the CR, which is needed for SWP support,
> -	 * so make sure that capability remains enabled (needed for SWP).
> +	 * so make sure that capability remains enabled.
>  	 * In practice, only exclude the old W25X family (JEDEC ID: EF 30 xx)
>  	 * which actually does not support this feature.
>  	 */
>  	if (nor->id[1] > 0x30)
> -		nor->flags &= ~SNOR_F_NO_READ_CR;
> +		params->opcodes.read_sr2 = SPINOR_OP_RDCR;
>  
>  	/*
>  	 * Winbond has reused many IDs, up to four times at this
> diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
> index 4b92494827b1..f0b5a1989881 100644
> --- a/include/linux/mtd/spi-nor.h
> +++ b/include/linux/mtd/spi-nor.h
> @@ -113,20 +113,16 @@
>  #define SR_E_ERR		BIT(5)
>  #define SR_P_ERR		BIT(6)
>  
> -#define SR1_QUAD_EN_BIT6	BIT(6)
> -
>  #define SR_BP_SHIFT		2
>  
>  /* Enhanced Volatile Configuration Register bits */
>  #define EVCR_QUAD_EN_MICRON	BIT(7)	/* Micron Quad I/O */
>  
>  /* Status Register 2 bits. */
> -#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_CMP_BIT6		BIT(6)
> -#define SR2_QUAD_EN_BIT7	BIT(7)
>  
>  /* Supported SPI protocols */
>  #define SNOR_PROTO_INST_MASK	GENMASK(23, 16)

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/94f13c38/attachment-0001.sig>

From mwalle at kernel.org  Fri Aug 14 05:26:17 2026
From: mwalle at kernel.org (Michael Walle)
Date: Fri, 14 Aug 2026 14:26:17 +0200
Subject: [PATCH v3 04/23] mtd: spi-nor: Add support for the new JESD216
 rev F QER field
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-4-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com> <20260813-winbond-v7-1-spi-nor-rv-addition-v3-4-b637cf120d5c@bootlin.com>
Message-ID: <DKONYW7IRL1X.39T3OTS81X2EG@kernel.org>

On Thu Aug 13, 2026 at 5:19 PM CEST, Miquel Raynal wrote:
> Revision F of the JEDEC JESD216 standard includes yet another
> supported setting in the QER 3-bit bitfield: 110b.
>
> This setting implies that the QE bit is bit 1 of SR2 (which is an
> already supported case) but this time it makes it very clear that both
> reading and writing to SR2 is supported, but not with the usual
> opcodes. The write operation uses an alternate WRSR2 opcode:
> 0x31 (instead of 0x3E) and both the reads and writes manipulate a single
> register at a time.
>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Michael Walle <mwalle at kernel.org>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 297 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260814/ff817f45/attachment.sig>

From syzbot+list368dbb700bdc61d661c0 at syzkaller.appspotmail.com  Fri Aug 14 21:32:33 2026
From: syzbot+list368dbb700bdc61d661c0 at syzkaller.appspotmail.com (syzbot)
Date: Fri, 14 Aug 2026 21:32:33 -0700
Subject: [syzbot] Monthly jffs2 report (Aug 2026)
Message-ID: <6a7febe1.9ebadd4d.20b15e.0009.GAE@google.com>

Hello jffs2 maintainers/developers,

This is a 31-day syzbot report for the jffs2 subsystem.
All related reports/information can be found at:
https://syzkaller.appspot.com/upstream/s/jffs2

During the period, 2 new issues were detected and 0 were fixed.
In total, 5 issues are still open and 1 has already been fixed.
There is also 1 low-priority issue.

Some of the still happening issues:

Ref Crashes Repro Title
<1> 3       No    KASAN: slab-use-after-free Read in jffs2_xattr_delete_inode
                  https://syzkaller.appspot.com/bug?extid=3a8099322b09d8d073d1
<2> 2       No    kernel BUG in jffs2_garbage_collect_pass
                  https://syzkaller.appspot.com/bug?extid=9bbc6788187abbabb2ee
<3> 2237    Yes   kernel BUG in jffs2_del_ino_cache
                  https://syzkaller.appspot.com/bug?extid=44664704c1494ad5f7a0

---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller at googlegroups.com.

To disable reminders for individual bugs, reply with the following command:
#syz set <Ref> no-reminders

To change bug's subsystems, reply with:
#syz set <Ref> subsystems: new-subsystem

You may send multiple commands in a single email message.


From russell at personaltelco.net  Sat Aug 15 03:48:02 2026
From: russell at personaltelco.net (Russell Senior)
Date: Sat, 15 Aug 2026 03:48:02 -0700
Subject: Regression: mx25l12845e stops working on my device after commit 947c86e481a
Message-ID: <CAHP3WfMMK8GU3jpwxv4=C=Pc_9H4ZkmqAJqxZ0gN_rYqPDU3XQ@mail.gmail.com>

I am running OpenWrt (master branch) on a Buffalo WZR600DHP. This
particular instance has MX25L12845E SPI NOR chips (I have another
WZR600DHP with Winbond W25Q128BVFG, but haven't tested there, just to
say they don't all necessarily use the same flash).

I just built a new firmware version last night, and it started
unhappily boot looping. The most recent previous build I had done was
from June. Boot looping messages look like this:

[...]
[    0.752843] printk: legacy bootconsole [early0] disabled
[    0.778756] spi-nor spi0.0: supply vcc not found, using dummy regulator
[    0.785909] spi-nor spi0.0: BFPT parsing failed. Please consider
using SPI_NOR_SKIP_SFDP when declaring the flash
[    0.796238] spi-nor spi0.0: probe with driver spi-nor failed with error -22
[    0.804457] spi-nor spi0.1: supply vcc not found, using dummy regulator
[    0.824085] i2c_dev: i2c /dev entries driver
[    0.830852] NET: Registered PF_INET6 protocol family
[    0.872875] Segment Routing with IPv6
[    0.876671] In-situ OAM (IOAM) with IPv6
[    0.880798] NET: Registered PF_PACKET protocol family
[    0.886278] 8021q: 802.1Q VLAN Support v1.8
[    0.921429] clk: Disabling unused clocks
[    0.925661] check access for rdinit=/init failed: -2, ignoring
[    0.932181] MTD: Couldn't look up '': -22
[    0.936335] /dev/root: Can't open blockdev
[    0.940520] VFS: Cannot open root device "" or unknown-block(0,0): error -6
[    0.947472] Please append a correct "root=" boot option; here are
the available partitions:
[    0.955831] 1f00           16384 mtdblock0
[    0.955843]  (driver?)
[    0.962379] List of all bdev filesystems:
[    0.966374]  squashfs
[    0.966379]
[    0.970139] Kernel panic - not syncing: VFS: Unable to mount root
fs on unknown-block(0,0)
[    0.978392] Rebooting in 1 seconds..

Between June and last night, OpenWrt had switched their kernel from a
6.12.x to a 6.18.x, so I immediately suspected something in that
transition was the culprit and demonstrated that by building the last
commit before the change to the 6.18.x kernel, and it worked. Then
tested the commit of the change to the 6.18.x kernel and it
bootlooped.

Then I started looking through the mtd changes between 6.12.x and
6.18.x and spotted 947c86e481a.

  commit 947c86e481a027ed5948434096e506f307bd7114
  Author: Cheng Ming Lin <chengminglin at mxic.com.tw>
  Date:   Mon Apr 7 15:53:58 2025 +0800

      mtd: spi-nor: macronix: Drop the redundant flash info fields

The following revert of just the part that affected my chip made the
bootloop vanish:

diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c
index e97f5cbd9aad2..3524ed08ff85d 100644
--- a/drivers/mtd/spi-nor/macronix.c
+++ b/drivers/mtd/spi-nor/macronix.c
@@ -132,7 +132,10 @@ static const struct flash_info macronix_nor_parts[] = {
        }, {
                /* MX25L12805D */
                .id = SNOR_ID(0xc2, 0x20, 0x18),
+               .name = "mx25l12805d",
+               .size = SZ_16M,
                .flags = SPI_NOR_HAS_LOCK | SPI_NOR_4BIT_BP,
+               .no_sfdp_flags = SECT_4K,
        }, {
                /* MX25L25635E, MX25L25645G */
                .id = SNOR_ID(0xc2, 0x20, 0x19),
-- 
2.55.0


-- 
Russell Senior
russell at personaltelco.net


From huasinliao at gmail.com  Sun Aug 16 02:31:24 2026
From: huasinliao at gmail.com (Huasin Liao)
Date: Sun, 16 Aug 2026 17:31:24 +0800
Subject: [PATCH] mtd: spinand: winbond: add support for W25N04LV
Message-ID: <20260816093124.12325-1-huasinliao@gmail.com>

Add support for the Winbond W25N04LV. Tested on an Airoha
AN7581-based board with the on-board W25N04LV, verified
erase/read/write.

Link: https://www.winbond.com/hq/product/code-storage-flash-memory/qspinand-flash/?__locale=en&partNo=W25N04LV
Cc: huasin.liao at airoha.com
Signed-off-by: Huasin Liao <huasinliao at gmail.com>
---
 drivers/mtd/nand/spi/winbond.c | 38 ++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
index 9b78c1e6cbc9..892220a12b22 100644
--- a/drivers/mtd/nand/spi/winbond.c
+++ b/drivers/mtd/nand/spi/winbond.c
@@ -345,6 +345,35 @@ static const struct mtd_ooblayout_ops w25n02kv_ooblayout = {
 	.free = w25n02kv_ooblayout_free,
 };
 
+static int w25n04lv_ooblayout_ecc(struct mtd_info *mtd, int section,
+				  struct mtd_oob_region *region)
+{
+	if (section > 7)
+		return -ERANGE;
+
+	region->offset = 128 + (16 * section);
+	region->length = 13;
+
+	return 0;
+}
+
+static int w25n04lv_ooblayout_free(struct mtd_info *mtd, int section,
+				   struct mtd_oob_region *region)
+{
+	if (section > 7)
+		return -ERANGE;
+
+	region->offset = (16 * section) + 2;
+	region->length = 14;
+
+	return 0;
+}
+
+static const struct mtd_ooblayout_ops w25n04lv_ooblayout = {
+	.ecc = w25n04lv_ooblayout_ecc,
+	.free = w25n04lv_ooblayout_free,
+};
+
 static int w25n01jw_ooblayout_ecc(struct mtd_info *mtd, int section,
 				  struct mtd_oob_region *region)
 {
@@ -759,6 +788,15 @@ static const struct spinand_info winbond_spinand_table[] = {
 					      &update_cache_variants),
 		     0,
 		     SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
+	SPINAND_INFO("W25N04LV", /* 3.3V */
+		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x8b, 0x23),
+		     NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
+		     NAND_ECCREQ(8, 512),
+		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+					      &write_cache_variants,
+					      &update_cache_variants),
+		     0,
+		     SPINAND_ECCINFO(&w25n04lv_ooblayout, w25n02kv_ecc_get_status)),
 	SPINAND_INFO("W25N04KW", /* 1.8V */
 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 0x23),
 		     NAND_MEMORG(1, 2048, 128, 64, 4096, 40, 1, 1, 1),
-- 
2.39.3 (Apple Git-146)



From asmadeus at codewreck.org  Sun Aug 16 04:19:02 2026
From: asmadeus at codewreck.org (Dominique Martinet)
Date: Sun, 16 Aug 2026 20:19:02 +0900
Subject: [PATCH] mtd: spinand: winbond: add support for W25N04LV
In-Reply-To: <20260816093124.12325-1-huasinliao@gmail.com>
References: <20260816093124.12325-1-huasinliao@gmail.com>
Message-ID: <aoGcpsZFxXdtkspZ@codewreck.org>

Huasin Liao wrote on Sun, Aug 16, 2026 at 05:31:24PM +0800:
> diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
> index 9b78c1e6cbc9..892220a12b22 100644
> --- a/drivers/mtd/nand/spi/winbond.c
> +++ b/drivers/mtd/nand/spi/winbond.c
> @@ -345,6 +345,35 @@ static const struct mtd_ooblayout_ops w25n02kv_ooblayout = {
>  	.free = w25n02kv_ooblayout_free,
>  };
>  
> +static int w25n04lv_ooblayout_ecc(struct mtd_info *mtd, int section,
> +				  struct mtd_oob_region *region)
> +{
> +	if (section > 7)
> +		return -ERANGE;
> +
> +	region->offset = 128 + (16 * section);
> +	region->length = 13;
> +
> +	return 0;
> +}
> +
> +static int w25n04lv_ooblayout_free(struct mtd_info *mtd, int section,
> +				   struct mtd_oob_region *region)
> +{
> +	if (section > 7)
> +		return -ERANGE;
> +
> +	region->offset = (16 * section) + 2;
> +	region->length = 14;
> +
> +	return 0;
> +}
> +
> +static const struct mtd_ooblayout_ops w25n04lv_ooblayout = {
> +	.ecc = w25n04lv_ooblayout_ecc,
> +	.free = w25n04lv_ooblayout_free,

FWIW, these two are the same as what I consolidated in
https://lore.kernel.org/r/20260814-w25n04lw-v3-1-c6a7423ac7d0 at atmark-techno.com
using oobsize, so we probably want to reuse that here too

(Should I split the patch?)

-- 
Dominique Martinet | Asmadeus


From kmehltretter at gmail.com  Sun Aug 16 09:30:27 2026
From: kmehltretter at gmail.com (Karl Mehltretter)
Date: Sun, 16 Aug 2026 18:30:27 +0200
Subject: [PATCH] mtd: core: avoid double-free of OTP NVMEM device
Message-ID: <20260816163027.10044-1-kmehltretter@gmail.com>

If factory OTP setup fails after the user OTP NVMEM device has been
registered, mtd_otp_nvmem_add() unregisters the user device but leaves
mtd->otp_user_nvmem set. On an error, the caller unregisters it again.
For -EOPNOTSUPP, registration continues and normal teardown unregisters
it again.

Clear mtd->otp_user_nvmem after unregistering it.

Fixes: e0489f6e221f ("mtd: core: fix error path for nvmem provider")
Fixes: fe0b8213c012 ("mtd: core: Don't fail mtd_otp_nvmem_add() if OTP is unsupported")
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Karl Mehltretter <kmehltretter at gmail.com>
---
 drivers/mtd/mtdcore.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 16629382a787b..6158452be24d6 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -1083,6 +1083,7 @@ static int mtd_otp_nvmem_add(struct mtd_info *mtd)
 
 err:
 	nvmem_unregister(mtd->otp_user_nvmem);
+	mtd->otp_user_nvmem = NULL;
 	/* Don't report error if OTP is not supported. */
 	if (err == -EOPNOTSUPP)
 		return 0;

base-commit: 3eb40771c00a8488fa6ed2cc1fe203477908bf38
-- 
2.53.0


From kmehltretter at gmail.com  Sun Aug 16 09:30:58 2026
From: kmehltretter at gmail.com (Karl Mehltretter)
Date: Sun, 16 Aug 2026 18:30:58 +0200
Subject: [PATCH] mtd: core: call _get_device() with the master MTD
Message-ID: <20260816163058.10099-1-kmehltretter@gmail.com>

__get_mtd_device() calls the master's _get_device() callback with mtd,
which may be a partition. __put_mtd_device() passes the master.

This breaks gluebi partitions because gluebi_get_device() uses
container_of() and therefore requires the master MTD. Pass the master
to _get_device(), matching _put_device().

Fixes: 46b5889cc2c5 ("mtd: implement proper partition handling")
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Karl Mehltretter <kmehltretter at gmail.com>
---
 drivers/mtd/mtdcore.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 16629382a787b..ab840922b5b1 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -1333,7 +1333,7 @@ int __get_mtd_device(struct mtd_info *mtd)
 	int err;
 
 	if (master->_get_device) {
-		err = master->_get_device(mtd);
+		err = master->_get_device(master);
 		if (err)
 			return err;
 	}

base-commit: 3eb40771c00a8488fa6ed2cc1fe203477908bf38
-- 
2.53.0


From huasinliao at gmail.com  Sun Aug 16 09:56:14 2026
From: huasinliao at gmail.com (Huasin Liao)
Date: Mon, 17 Aug 2026 00:56:14 +0800
Subject: [PATCH] mtd: spinand: winbond: add support for W25N04LV
In-Reply-To: <aoGcpsZFxXdtkspZ@codewreck.org>
References: <aoGcpsZFxXdtkspZ@codewreck.org>
Message-ID: <20260816165614.16262-1-huasinliao@gmail.com>

Dominique Martinet wrote on Sun, Aug 16, 2026 at 20:19:02 +0900:
> Huasin Liao wrote on Sun, Aug 16, 2026 at 05:31:24PM +0800:
>> diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
>> index 9b78c1e6cbc9..892220a12b22 100644
>> --- a/drivers/mtd/nand/spi/winbond.c
>> +++ b/drivers/mtd/nand/spi/winbond.c
>> @@ -345,6 +345,35 @@ static const struct mtd_ooblayout_ops w25n02kv_ooblayout = {
>>  	.free = w25n02kv_ooblayout_free,
>>  };
>>  
>> +static int w25n04lv_ooblayout_ecc(struct mtd_info *mtd, int section,
>> +				  struct mtd_oob_region *region)
>> +{
>> +	if (section > 7)
>> +		return -ERANGE;
>> +
>> +	region->offset = 128 + (16 * section);
>> +	region->length = 13;
>> +
>> +	return 0;
>> +}
>> +
>> +static int w25n04lv_ooblayout_free(struct mtd_info *mtd, int section,
>> +				   struct mtd_oob_region *region)
>> +{
>> +	if (section > 7)
>> +		return -ERANGE;
>> +
>> +	region->offset = (16 * section) + 2;
>> +	region->length = 14;
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct mtd_ooblayout_ops w25n04lv_ooblayout = {
>> +	.ecc = w25n04lv_ooblayout_ecc,
>> +	.free = w25n04lv_ooblayout_free,
> 
> FWIW, these two are the same as what I consolidated in
> https://lore.kernel.org/r/20260814-w25n04lw-v3-1-c6a7423ac7d0 at atmark-techno.com
> using oobsize, so we probably want to reuse that here too

Thanks for the pointer, and good to see Miquel is fine with reusing
w25n02kv_ooblayout. Once your patch lands, I'll rebase on top of it
and switch from w25n04lv_ooblayout to w25n02kv_ooblayout ? is that in
line with what you'd expect?

Huasin Liao


From dominique.martinet at atmark-techno.com  Sun Aug 16 22:21:04 2026
From: dominique.martinet at atmark-techno.com (Dominique Martinet)
Date: Mon, 17 Aug 2026 14:21:04 +0900
Subject: [PATCH v3] mtd: spinand: winbond: add support for W25N04LW
In-Reply-To: <20260814-w25n04lw-v3-1-c6a7423ac7d0@atmark-techno.com>
References: <20260814-w25n04lw-v3-1-c6a7423ac7d0@atmark-techno.com>
Message-ID: <aoKaQLEY6bAyg5Iv@atmark-techno.com>

Dominique Martinet wrote on Fri, Aug 14, 2026 at 08:12:11AM +0000:
> - I still need to check about the high number of ecc errors next week,
> but I don't think it's likely to be related to this patch so sending
> ahead... Feel free to wait a bit (there's no hurry on my end for this to
> get into 7.3; it's fine to skip a cycle as you see fit)

That looks like to have been hardware related, I've tried two other
identical boards and these worked fine (one had zero ECC error, the
other had a handful but nothing like what I was seeing)

That was the last item left on my list of things to verify,
so I thought this patch was now OK, but sashiko's comment is spot on...

> diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
> index 9b78c1e6cbc9..171f4c264220 100644
> --- a/drivers/mtd/nand/spi/winbond.c
> +++ b/drivers/mtd/nand/spi/winbond.c
> @@ -326,9 +327,16 @@ static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section,
>  static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section,
>  				   struct mtd_oob_region *region)
>  {
> -	if (section > 3)
> +	if (section >= mtd->oobsize / 32)
>  		return -ERANGE;

>From sashiko:
> Does this change cause a regression for the W25N01KV device?
> The W25N01KV chip uses this shared w25n02kv_ooblayout_free() function
> and has an oobsize of 96 bytes.
> With mtd->oobsize = 96, 96 / 32 evaluates to 3. The new check 
> (section >= mtd->oobsize / 32) will return -ERANGE for section 3, whereas 
> the original code (section > 3) allowed it. This drops the 4th free 
> OOB region for W25N01KV devices.

I could find the W25N01KV datasheet online and it's correctly 96 bytes
with 4x 16 bytes user data and 4x 8 bytes ECC, so this patch is
incorrect (I had only checked w25n02kv_ooblayout was only used with
oobsize = 128, but w25n01kv_ooblayout also uses
w25n02kv_ooblayout_free...)

I'll send a v4 with fixed functions, even if it's less elegant.

-- 
Dominique




From dominique.martinet at atmark-techno.com  Sun Aug 16 22:29:17 2026
From: dominique.martinet at atmark-techno.com (Dominique Martinet)
Date: Mon, 17 Aug 2026 05:29:17 +0000
Subject: [PATCH v4] mtd: spinand: winbond: add support for W25N04LW
Message-ID: <20260817-w25n04lw-v4-1-1b19c2a629e5@atmark-techno.com>

Add support for Winbond W25N04LW 4Gbit SPI-NAND:
> 1.8V 4G-bit
> Raw serial SLC NAND flash memory
> Dual/Quad SPI with 104MHz
> buffer read, continuous read & sequential read

Highlights copied from datasheet:
? Page size: 4,352 Bytes (4096 + 256 Bytes)
? Block size: 64 pages (256K + 16K Bytes)
- Built-in 8-Bit ECC for memory array
? ECC status bits indicate ECC results
? Bad Block Management and LUT access
? Software and Hardware Write-Protect
? Power Supply Lock-Down and OTP protection
? Unique ID and Parameter page
? Ten 4KB OTP pages per die
? Read Level Setting for Read Retry

The 4096+256 byte page layout is as follow:
- 4096 bytes ECC protected "main memory array", subdivided in 8
  512 bytes sectors
- 128 bytes spare array, subdivided in 8 16 bytes sectors as follow:
  - 2 bytes bad block marker (no ECC protection)
  - 2 bytes "user data II" (no ECC protection)
  - 12 bytes "user data I" (ECC protected)
- 128 bytes parity array, subdivided in 8 16 bytes sectors:
  - 13 bytes ECC for main data and user data
  - 3 unused bytes

This commit does not support most advanced features such as continuous
read, LUT management and OTP pages, and only provides basic usage.

OOB layout was kept compatible with previous models (exposing both user
data I and II), dependent on mtd->oobsize.

Co-developed-by: Daisuke Mizobuchi <mizo at atmark-techno.com>
Signed-off-by: Daisuke Mizobuchi <mizo at atmark-techno.com>
Signed-off-by: Dominique Martinet <dominique.martinet at atmark-techno.com>
---
Changes in v4:
- Updated ooblayout function to if oobsize = 256 instead of use oobsize
  directly
- Link to v3: https://patch.msgid.link/20260814-w25n04lw-v3-1-c6a7423ac7d0 at atmark-techno.com

Changes in v3:
- Fixed Co-authored-by / comment style
- Merged ooblayout helper with w25n02kv_ooblayout, using mtd->oobsize
(with an extra comment describing User Data I/II, but keeping current
behaviour)
- Link to v2: https://patch.msgid.link/20260812-w25n04lw-v2-1-deee97602fc4 at atmark-techno.com

Changes in v2:
- Link to v1: https://patch.msgid.link/ansYUoNKJyDQ+6f2 at hu-mdalam-blr.qualcomm.com
- fixed oob layout
- fixed NAND_MEMORG oob size
---
 drivers/mtd/nand/spi/winbond.c | 35 ++++++++++++++++++++++++++++++++---
 1 file changed, 32 insertions(+), 3 deletions(-)

diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
index 9b78c1e6cbc9..a1807fa9ae03 100644
--- a/drivers/mtd/nand/spi/winbond.c
+++ b/drivers/mtd/nand/spi/winbond.c
@@ -314,10 +314,18 @@ static int w25n01kv_ooblayout_ecc(struct mtd_info *mtd, int section,
 static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section,
 				  struct mtd_oob_region *region)
 {
-	if (section > 3)
+	int num_sections = 4;
+	int offset = 64;
+
+	if (mtd->oobsize == 256) {
+		num_sections = 8;
+		offset = 128;
+	}
+
+	if (section >= num_sections)
 		return -ERANGE;
 
-	region->offset = 64 + (16 * section);
+	region->offset = offset + (16 * section);
 	region->length = 13;
 
 	return 0;
@@ -326,9 +334,21 @@ static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section,
 static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section,
 				   struct mtd_oob_region *region)
 {
-	if (section > 3)
+	int num_sections = 4;
+
+	if (mtd->oobsize == 256)
+		num_sections = 8;
+
+	if (section >= num_sections)
 		return -ERANGE;
 
+	/*
+	 * For at least W25N04KW and W25N04LW, this region is actually
+	 * split in two:
+	 * ECC-protected "User Data I" at + 4 offset, length 12
+	 * unprotected "User Data II" at + 2 offset, length 2
+	 * This returns the full region for backwards compatibility.
+	 */
 	region->offset = (16 * section) + 2;
 	region->length = 14;
 
@@ -768,6 +788,15 @@ static const struct spinand_info winbond_spinand_table[] = {
 					      &update_cache_variants),
 		     0,
 		     SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
+	SPINAND_INFO("W25N04LW", /* 1.8V */
+		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb2, 0x23),
+		     NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
+		     NAND_ECCREQ(8, 512),
+		     SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+					      &write_cache_variants,
+					      &update_cache_variants),
+		     0,
+		     SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
 	SPINAND_INFO("W35N04JW", /* 1.8V */
 		     SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x23),
 		     NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 4, 1),

---
base-commit: db2ddb87143519e20a95aa36c60b36107b736a58
change-id: 20260812-w25n04lw-3e9146d8340f

Best regards,
--  
Dominique Martinet <dominique.martinet at atmark-techno.com>




From dominique.martinet at atmark-techno.com  Mon Aug 17 19:09:33 2026
From: dominique.martinet at atmark-techno.com (Dominique Martinet)
Date: Tue, 18 Aug 2026 02:09:33 +0000
Subject: [PATCH 0/2] Backport fixes for UBI
Message-ID: <20260818-ubi-backports-v1-0-324d6816d2ae@atmark-techno.com>

While looking at recent UBI patches I found these two commits that I
believe are worth backporting.

We're not using fastmap so I don't care much about the leak there, but
reducing wear is always good to take

Thanks!

Signed-off-by: Dominique Martinet <dominique.martinet at atmark-techno.com>
---
Cheng Ming Lin (1):
      mtd: ubi: skip programming unused bits in ubi headers

Liyuan Pang (1):
      ubi: fastmap: fix ubi->fm memory leak

 drivers/mtd/ubi/attach.c     |  4 +++-
 drivers/mtd/ubi/fastmap-wl.c |  8 +-------
 drivers/mtd/ubi/io.c         | 10 ++++++++++
 drivers/mtd/ubi/ubi.h        | 12 ++++++++++++
 4 files changed, 26 insertions(+), 8 deletions(-)
---
base-commit: 98c5a5b9f23ede08be1dcd8be0a13b32fbe2b462
change-id: 20260818-ubi-backports-2f5d76dedbed

Best regards,
--  
Dominique Martinet <dominique.martinet at atmark-techno.com>




From dominique.martinet at atmark-techno.com  Mon Aug 17 19:09:34 2026
From: dominique.martinet at atmark-techno.com (Dominique Martinet)
Date: Tue, 18 Aug 2026 02:09:34 +0000
Subject: [PATCH v6.12.y 1/2] mtd: ubi: skip programming unused bits in ubi
 headers
In-Reply-To: <20260818-ubi-backports-v1-0-324d6816d2ae@atmark-techno.com>
References: <20260818-ubi-backports-v1-0-324d6816d2ae@atmark-techno.com>
Message-ID: <20260818-ubi-backports-v1-1-324d6816d2ae@atmark-techno.com>

From: Cheng Ming Lin <chengminglin at mxic.com.tw>

This patch prevents unnecessary programming of bits in ec_hdr and
vid_hdr that are not used or read during normal UBI operation. These
unused bits are typcially already set to 1 in erased flash and do not
need to be explicitly programmed to 0 if they are not used.

Programming such unused areas offers no functional benefit and may
result in unnecessary flash wear, reducing the overall lifetime of the
device. By skipping these writes, we preserve the flash state as much as
possible and minimize wear caused by redundant operations.

This change ensures that only necessary fields are written when preparing
UBI headers, improving flash efficiency without affecting functionality.

Additionally, the Kioxia TC58NVG1S3HTA00 datasheet (page 63) also notes
that continuous program/erase cycling with a high percentage of '0' bits
in the data pattern can accelerate block endurance degradation.
This further supports avoiding large 0x00 patterns.

Link: https://europe.kioxia.com/content/dam/kioxia/newidr/productinfo/datasheet/201910/DST_TC58NVG1S3HTA00-TDE_EN_31442.pdf

Signed-off-by: Cheng Ming Lin <chengminglin at mxic.com.tw>
Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>
Reviewed-by: Zhihao Cheng <chengzhihao1 at huawei.com>
Signed-off-by: Richard Weinberger <richard at nod.at>
(cherry picked from commit 77530d1a78ca5c274e37d6494a965223672630b2)
Signed-off-by: Dominique Martinet <dominique.martinet at atmark-techno.com>
---
 drivers/mtd/ubi/io.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c
index a4999bce435f..915eb64cb001 100644
--- a/drivers/mtd/ubi/io.c
+++ b/drivers/mtd/ubi/io.c
@@ -868,6 +868,8 @@ int ubi_io_write_ec_hdr(struct ubi_device *ubi, int pnum,
 		return -EROFS;
 	}
 
+	memset((char *)ec_hdr + UBI_EC_HDR_SIZE, 0xFF, ubi->ec_hdr_alsize - UBI_EC_HDR_SIZE);
+
 	err = ubi_io_write(ubi, ec_hdr, pnum, 0, ubi->ec_hdr_alsize);
 	return err;
 }
@@ -1150,6 +1152,14 @@ int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum,
 		return -EROFS;
 	}
 
+	if (ubi->vid_hdr_shift) {
+		memset((char *)p, 0xFF, ubi->vid_hdr_shift);
+		memset((char *)p + ubi->vid_hdr_shift + UBI_VID_HDR_SIZE, 0xFF,
+		       ubi->vid_hdr_alsize - (ubi->vid_hdr_shift + UBI_VID_HDR_SIZE));
+	} else {
+		memset((char *)p + UBI_VID_HDR_SIZE, 0xFF, ubi->vid_hdr_alsize - UBI_VID_HDR_SIZE);
+	}
+
 	err = ubi_io_write(ubi, p, pnum, ubi->vid_hdr_aloffset,
 			   ubi->vid_hdr_alsize);
 	return err;

-- 
2.55.0.dirty




From dominique.martinet at atmark-techno.com  Mon Aug 17 19:09:35 2026
From: dominique.martinet at atmark-techno.com (Dominique Martinet)
Date: Tue, 18 Aug 2026 02:09:35 +0000
Subject: [PATCH v6.12.y 2/2] ubi: fastmap: fix ubi->fm memory leak
In-Reply-To: <20260818-ubi-backports-v1-0-324d6816d2ae@atmark-techno.com>
References: <20260818-ubi-backports-v1-0-324d6816d2ae@atmark-techno.com>
Message-ID: <20260818-ubi-backports-v1-2-324d6816d2ae@atmark-techno.com>

From: Liyuan Pang <pangliyuan1 at huawei.com>

The problem is that scan_fast() allocate memory for ubi->fm
and ubi->fm->e[x], but if the following attach process fails
in ubi_wl_init or ubi_read_volume_table, the whole attach
process will fail without executing ubi_wl_close to free the
memory under ubi->fm.

Fix this by add a new ubi_free_fastmap function in fastmap.c
to free the memory allocated for fm.

If SLUB_DEBUG and KUNIT are enabled, the following warning messages
will show:
ubi0: detaching mtd0
ubi0: mtd0 is detached
ubi0: default fastmap pool size: 200
ubi0: default fastmap WL pool size: 100
ubi0: attaching mtd0
ubi0: attached by fastmap
ubi0: fastmap pool size: 200
ubi0: fastmap WL pool size: 100
ubi0 error: ubi_wl_init [ubi]: no enough physical eraseblocks (4, need 203)
ubi0 error: ubi_attach_mtd_dev [ubi]: failed to attach mtd0, error -28
UBI error: cannot attach mtd0
=================================================================
BUG ubi_wl_entry_slab (Tainted: G    B      O L   ): Objects remaining in ubi_wl_entry_slab on __kmem_cache_shutdown()
-----------------------------------------------------------------------------

Slab 0xffff2fd23a40cd00 objects=22 used=1 fp=0xffff2fd1d0334fd8 flags=0x883fffc010200(slab|head|section=34|node=0|zone=1|lastcpupid=0x7fff)
CPU: 0 PID: 5884 Comm: insmod Tainted: G    B      O L    5.10.0 #1
Hardware name: LS1043A RDB Board (DT)
Call trace:
 dump_backtrace+0x0/0x198
 show_stack+0x18/0x28
 dump_stack+0xe8/0x15c
 slab_err+0x94/0xc0
 __kmem_cache_shutdown+0x1fc/0x39c
 kmem_cache_destroy+0x48/0x138
 ubi_init+0x1d4/0xf34 [ubi]
 do_one_initcall+0xb4/0x24c
 do_init_module+0x4c/0x1dc
 load_module+0x212c/0x2260
 __se_sys_finit_module+0xb4/0xd8
 __arm64_sys_finit_module+0x18/0x28
 el0_svc_common.constprop.0+0x78/0x1a0
 do_el0_svc+0x78/0x90
 el0_svc+0x20/0x38
 el0_sync_handler+0xf0/0x140
 normal+0x3d8/0x400
Object 0xffff2fd1d0334e68 @offset=3688
Allocated in ubi_scan_fastmap+0xf04/0xf40 [ubi] age=80 cpu=0 pid=5884
	__slab_alloc.isra.21+0x6c/0xb4
	kmem_cache_alloc+0x1e4/0x80c
	ubi_scan_fastmap+0xf04/0xf40 [ubi]
	ubi_attach+0x1f0/0x3a8 [ubi]
	ubi_attach_mtd_dev+0x810/0xbc8 [ubi]
	ubi_init+0x238/0xf34 [ubi]
	do_one_initcall+0xb4/0x24c
	do_init_module+0x4c/0x1dc
	load_module+0x212c/0x2260
	__se_sys_finit_module+0xb4/0xd8
	__arm64_sys_finit_module+0x18/0x28
	el0_svc_common.constprop.0+0x78/0x1a0
	do_el0_svc+0x78/0x90
	el0_svc+0x20/0x38
	el0_sync_handler+0xf0/0x140
	normal+0x3d8/0x400

Link: https://bugzilla.kernel.org/show_bug.cgi?id=220744

Signed-off-by: Liyuan Pang <pangliyuan1 at huawei.com>
Reviewed-by: Zhihao Cheng <chengzhihao1 at huawei.com>
Signed-off-by: Richard Weinberger <richard at nod.at>
(cherry picked from commit d133e30aabc7c8eb8206827f8fbe0f3679adb911)
Signed-off-by: Dominique Martinet <dominique.martinet at atmark-techno.com>
---
 drivers/mtd/ubi/attach.c     |  4 +++-
 drivers/mtd/ubi/fastmap-wl.c |  8 +-------
 drivers/mtd/ubi/ubi.h        | 12 ++++++++++++
 3 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c
index adc47b87b38a..884171871d0e 100644
--- a/drivers/mtd/ubi/attach.c
+++ b/drivers/mtd/ubi/attach.c
@@ -1600,7 +1600,7 @@ int ubi_attach(struct ubi_device *ubi, int force_scan)
 
 	err = ubi_read_volume_table(ubi, ai);
 	if (err)
-		goto out_ai;
+		goto out_fm;
 
 	err = ubi_wl_init(ubi, ai);
 	if (err)
@@ -1642,6 +1642,8 @@ int ubi_attach(struct ubi_device *ubi, int force_scan)
 out_vtbl:
 	ubi_free_all_volumes(ubi);
 	vfree(ubi->vtbl);
+out_fm:
+	ubi_free_fastmap(ubi);
 out_ai:
 	destroy_ai(ai);
 	return err;
diff --git a/drivers/mtd/ubi/fastmap-wl.c b/drivers/mtd/ubi/fastmap-wl.c
index 9bdb6525f128..e2bc1122bfd3 100644
--- a/drivers/mtd/ubi/fastmap-wl.c
+++ b/drivers/mtd/ubi/fastmap-wl.c
@@ -530,8 +530,6 @@ int ubi_is_erase_work(struct ubi_work *wrk)
 
 static void ubi_fastmap_close(struct ubi_device *ubi)
 {
-	int i;
-
 	return_unused_pool_pebs(ubi, &ubi->fm_pool);
 	return_unused_pool_pebs(ubi, &ubi->fm_wl_pool);
 
@@ -540,11 +538,7 @@ static void ubi_fastmap_close(struct ubi_device *ubi)
 		ubi->fm_anchor = NULL;
 	}
 
-	if (ubi->fm) {
-		for (i = 0; i < ubi->fm->used_blocks; i++)
-			kfree(ubi->fm->e[i]);
-	}
-	kfree(ubi->fm);
+	ubi_free_fastmap(ubi);
 }
 
 /**
diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h
index 1c9e874e8ede..450a7b9a5bd7 100644
--- a/drivers/mtd/ubi/ubi.h
+++ b/drivers/mtd/ubi/ubi.h
@@ -970,10 +970,22 @@ int ubi_scan_fastmap(struct ubi_device *ubi, struct ubi_attach_info *ai,
 		     struct ubi_attach_info *scan_ai);
 int ubi_fastmap_init_checkmap(struct ubi_volume *vol, int leb_count);
 void ubi_fastmap_destroy_checkmap(struct ubi_volume *vol);
+static inline void ubi_free_fastmap(struct ubi_device *ubi)
+{
+	if (ubi->fm) {
+		int i;
+
+		for (i = 0; i < ubi->fm->used_blocks; i++)
+			kmem_cache_free(ubi_wl_entry_slab, ubi->fm->e[i]);
+		kfree(ubi->fm);
+		ubi->fm = NULL;
+	}
+}
 #else
 static inline int ubi_update_fastmap(struct ubi_device *ubi) { return 0; }
 static inline int ubi_fastmap_init_checkmap(struct ubi_volume *vol, int leb_count) { return 0; }
 static inline void ubi_fastmap_destroy_checkmap(struct ubi_volume *vol) {}
+static inline void ubi_free_fastmap(struct ubi_device *ubi) { }
 #endif
 
 /* block.c */

-- 
2.55.0.dirty




From chengzhihao1 at huawei.com  Mon Aug 17 21:46:58 2026
From: chengzhihao1 at huawei.com (Zhihao Cheng)
Date: Tue, 18 Aug 2026 12:46:58 +0800
Subject: [PATCH v6.12.y 1/2] mtd: ubi: skip programming unused bits in ubi
 headers
In-Reply-To: <20260818-ubi-backports-v1-1-324d6816d2ae@atmark-techno.com>
References: <20260818-ubi-backports-v1-0-324d6816d2ae@atmark-techno.com>
 <20260818-ubi-backports-v1-1-324d6816d2ae@atmark-techno.com>
Message-ID: <3fec694c-386d-2227-fe72-25e2c909a5a4@huawei.com>

? 2026/8/18 10:09, Dominique Martinet ??:
> From: Cheng Ming Lin <chengminglin at mxic.com.tw>
> 
> This patch prevents unnecessary programming of bits in ec_hdr and
> vid_hdr that are not used or read during normal UBI operation. These
> unused bits are typcially already set to 1 in erased flash and do not
> need to be explicitly programmed to 0 if they are not used.
> 
> Programming such unused areas offers no functional benefit and may
> result in unnecessary flash wear, reducing the overall lifetime of the
> device. By skipping these writes, we preserve the flash state as much as
> possible and minimize wear caused by redundant operations.
> 
> This change ensures that only necessary fields are written when preparing
> UBI headers, improving flash efficiency without affecting functionality.
> 
> Additionally, the Kioxia TC58NVG1S3HTA00 datasheet (page 63) also notes
> that continuous program/erase cycling with a high percentage of '0' bits
> in the data pattern can accelerate block endurance degradation.
> This further supports avoiding large 0x00 patterns.
> 
> Link: https://europe.kioxia.com/content/dam/kioxia/newidr/productinfo/datasheet/201910/DST_TC58NVG1S3HTA00-TDE_EN_31442.pdf
> 
> Signed-off-by: Cheng Ming Lin <chengminglin at mxic.com.tw>
> Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>
> Reviewed-by: Zhihao Cheng <chengzhihao1 at huawei.com>
> Signed-off-by: Richard Weinberger <richard at nod.at>
> (cherry picked from commit 77530d1a78ca5c274e37d6494a965223672630b2)
> Signed-off-by: Dominique Martinet <dominique.martinet at atmark-techno.com>
> ---
>   drivers/mtd/ubi/io.c | 10 ++++++++++
>   1 file changed, 10 insertions(+)
> 


Reviewed-by: Zhihao Cheng <chengzhihao1 at huawei.com>
> diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c
> index a4999bce435f..915eb64cb001 100644
> --- a/drivers/mtd/ubi/io.c
> +++ b/drivers/mtd/ubi/io.c
> @@ -868,6 +868,8 @@ int ubi_io_write_ec_hdr(struct ubi_device *ubi, int pnum,
>   		return -EROFS;
>   	}
>   
> +	memset((char *)ec_hdr + UBI_EC_HDR_SIZE, 0xFF, ubi->ec_hdr_alsize - UBI_EC_HDR_SIZE);
> +
>   	err = ubi_io_write(ubi, ec_hdr, pnum, 0, ubi->ec_hdr_alsize);
>   	return err;
>   }
> @@ -1150,6 +1152,14 @@ int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum,
>   		return -EROFS;
>   	}
>   
> +	if (ubi->vid_hdr_shift) {
> +		memset((char *)p, 0xFF, ubi->vid_hdr_shift);
> +		memset((char *)p + ubi->vid_hdr_shift + UBI_VID_HDR_SIZE, 0xFF,
> +		       ubi->vid_hdr_alsize - (ubi->vid_hdr_shift + UBI_VID_HDR_SIZE));
> +	} else {
> +		memset((char *)p + UBI_VID_HDR_SIZE, 0xFF, ubi->vid_hdr_alsize - UBI_VID_HDR_SIZE);
> +	}
> +
>   	err = ubi_io_write(ubi, p, pnum, ubi->vid_hdr_aloffset,
>   			   ubi->vid_hdr_alsize);
>   	return err;
> 



From chengzhihao1 at huawei.com  Mon Aug 17 21:47:20 2026
From: chengzhihao1 at huawei.com (Zhihao Cheng)
Date: Tue, 18 Aug 2026 12:47:20 +0800
Subject: [PATCH v6.12.y 2/2] ubi: fastmap: fix ubi->fm memory leak
In-Reply-To: <20260818-ubi-backports-v1-2-324d6816d2ae@atmark-techno.com>
References: <20260818-ubi-backports-v1-0-324d6816d2ae@atmark-techno.com>
 <20260818-ubi-backports-v1-2-324d6816d2ae@atmark-techno.com>
Message-ID: <819387b2-0fb8-4cb7-5645-181619eb6fc3@huawei.com>

? 2026/8/18 10:09, Dominique Martinet ??:
> From: Liyuan Pang <pangliyuan1 at huawei.com>
> 
> The problem is that scan_fast() allocate memory for ubi->fm
> and ubi->fm->e[x], but if the following attach process fails
> in ubi_wl_init or ubi_read_volume_table, the whole attach
> process will fail without executing ubi_wl_close to free the
> memory under ubi->fm.
> 
> Fix this by add a new ubi_free_fastmap function in fastmap.c
> to free the memory allocated for fm.
> 
> If SLUB_DEBUG and KUNIT are enabled, the following warning messages
> will show:
> ubi0: detaching mtd0
> ubi0: mtd0 is detached
> ubi0: default fastmap pool size: 200
> ubi0: default fastmap WL pool size: 100
> ubi0: attaching mtd0
> ubi0: attached by fastmap
> ubi0: fastmap pool size: 200
> ubi0: fastmap WL pool size: 100
> ubi0 error: ubi_wl_init [ubi]: no enough physical eraseblocks (4, need 203)
> ubi0 error: ubi_attach_mtd_dev [ubi]: failed to attach mtd0, error -28
> UBI error: cannot attach mtd0
> =================================================================
> BUG ubi_wl_entry_slab (Tainted: G    B      O L   ): Objects remaining in ubi_wl_entry_slab on __kmem_cache_shutdown()
> -----------------------------------------------------------------------------
> 
> Slab 0xffff2fd23a40cd00 objects=22 used=1 fp=0xffff2fd1d0334fd8 flags=0x883fffc010200(slab|head|section=34|node=0|zone=1|lastcpupid=0x7fff)
> CPU: 0 PID: 5884 Comm: insmod Tainted: G    B      O L    5.10.0 #1
> Hardware name: LS1043A RDB Board (DT)
> Call trace:
>   dump_backtrace+0x0/0x198
>   show_stack+0x18/0x28
>   dump_stack+0xe8/0x15c
>   slab_err+0x94/0xc0
>   __kmem_cache_shutdown+0x1fc/0x39c
>   kmem_cache_destroy+0x48/0x138
>   ubi_init+0x1d4/0xf34 [ubi]
>   do_one_initcall+0xb4/0x24c
>   do_init_module+0x4c/0x1dc
>   load_module+0x212c/0x2260
>   __se_sys_finit_module+0xb4/0xd8
>   __arm64_sys_finit_module+0x18/0x28
>   el0_svc_common.constprop.0+0x78/0x1a0
>   do_el0_svc+0x78/0x90
>   el0_svc+0x20/0x38
>   el0_sync_handler+0xf0/0x140
>   normal+0x3d8/0x400
> Object 0xffff2fd1d0334e68 @offset=3688
> Allocated in ubi_scan_fastmap+0xf04/0xf40 [ubi] age=80 cpu=0 pid=5884
> 	__slab_alloc.isra.21+0x6c/0xb4
> 	kmem_cache_alloc+0x1e4/0x80c
> 	ubi_scan_fastmap+0xf04/0xf40 [ubi]
> 	ubi_attach+0x1f0/0x3a8 [ubi]
> 	ubi_attach_mtd_dev+0x810/0xbc8 [ubi]
> 	ubi_init+0x238/0xf34 [ubi]
> 	do_one_initcall+0xb4/0x24c
> 	do_init_module+0x4c/0x1dc
> 	load_module+0x212c/0x2260
> 	__se_sys_finit_module+0xb4/0xd8
> 	__arm64_sys_finit_module+0x18/0x28
> 	el0_svc_common.constprop.0+0x78/0x1a0
> 	do_el0_svc+0x78/0x90
> 	el0_svc+0x20/0x38
> 	el0_sync_handler+0xf0/0x140
> 	normal+0x3d8/0x400
> 
> Link: https://bugzilla.kernel.org/show_bug.cgi?id=220744
> 
> Signed-off-by: Liyuan Pang <pangliyuan1 at huawei.com>
> Reviewed-by: Zhihao Cheng <chengzhihao1 at huawei.com>
> Signed-off-by: Richard Weinberger <richard at nod.at>
> (cherry picked from commit d133e30aabc7c8eb8206827f8fbe0f3679adb911)
> Signed-off-by: Dominique Martinet <dominique.martinet at atmark-techno.com>
> ---
>   drivers/mtd/ubi/attach.c     |  4 +++-
>   drivers/mtd/ubi/fastmap-wl.c |  8 +-------
>   drivers/mtd/ubi/ubi.h        | 12 ++++++++++++
>   3 files changed, 16 insertions(+), 8 deletions(-)
> 

Reviewed-by: Zhihao Cheng <chengzhihao1 at huawei.com>
> diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c
> index adc47b87b38a..884171871d0e 100644
> --- a/drivers/mtd/ubi/attach.c
> +++ b/drivers/mtd/ubi/attach.c
> @@ -1600,7 +1600,7 @@ int ubi_attach(struct ubi_device *ubi, int force_scan)
>   
>   	err = ubi_read_volume_table(ubi, ai);
>   	if (err)
> -		goto out_ai;
> +		goto out_fm;
>   
>   	err = ubi_wl_init(ubi, ai);
>   	if (err)
> @@ -1642,6 +1642,8 @@ int ubi_attach(struct ubi_device *ubi, int force_scan)
>   out_vtbl:
>   	ubi_free_all_volumes(ubi);
>   	vfree(ubi->vtbl);
> +out_fm:
> +	ubi_free_fastmap(ubi);
>   out_ai:
>   	destroy_ai(ai);
>   	return err;
> diff --git a/drivers/mtd/ubi/fastmap-wl.c b/drivers/mtd/ubi/fastmap-wl.c
> index 9bdb6525f128..e2bc1122bfd3 100644
> --- a/drivers/mtd/ubi/fastmap-wl.c
> +++ b/drivers/mtd/ubi/fastmap-wl.c
> @@ -530,8 +530,6 @@ int ubi_is_erase_work(struct ubi_work *wrk)
>   
>   static void ubi_fastmap_close(struct ubi_device *ubi)
>   {
> -	int i;
> -
>   	return_unused_pool_pebs(ubi, &ubi->fm_pool);
>   	return_unused_pool_pebs(ubi, &ubi->fm_wl_pool);
>   
> @@ -540,11 +538,7 @@ static void ubi_fastmap_close(struct ubi_device *ubi)
>   		ubi->fm_anchor = NULL;
>   	}
>   
> -	if (ubi->fm) {
> -		for (i = 0; i < ubi->fm->used_blocks; i++)
> -			kfree(ubi->fm->e[i]);
> -	}
> -	kfree(ubi->fm);
> +	ubi_free_fastmap(ubi);
>   }
>   
>   /**
> diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h
> index 1c9e874e8ede..450a7b9a5bd7 100644
> --- a/drivers/mtd/ubi/ubi.h
> +++ b/drivers/mtd/ubi/ubi.h
> @@ -970,10 +970,22 @@ int ubi_scan_fastmap(struct ubi_device *ubi, struct ubi_attach_info *ai,
>   		     struct ubi_attach_info *scan_ai);
>   int ubi_fastmap_init_checkmap(struct ubi_volume *vol, int leb_count);
>   void ubi_fastmap_destroy_checkmap(struct ubi_volume *vol);
> +static inline void ubi_free_fastmap(struct ubi_device *ubi)
> +{
> +	if (ubi->fm) {
> +		int i;
> +
> +		for (i = 0; i < ubi->fm->used_blocks; i++)
> +			kmem_cache_free(ubi_wl_entry_slab, ubi->fm->e[i]);
> +		kfree(ubi->fm);
> +		ubi->fm = NULL;
> +	}
> +}
>   #else
>   static inline int ubi_update_fastmap(struct ubi_device *ubi) { return 0; }
>   static inline int ubi_fastmap_init_checkmap(struct ubi_volume *vol, int leb_count) { return 0; }
>   static inline void ubi_fastmap_destroy_checkmap(struct ubi_volume *vol) {}
> +static inline void ubi_free_fastmap(struct ubi_device *ubi) { }
>   #endif
>   
>   /* block.c */
> 



From linchengming884 at gmail.com  Mon Aug 17 22:29:16 2026
From: linchengming884 at gmail.com (Cheng Ming Lin)
Date: Tue, 18 Aug 2026 13:29:16 +0800
Subject: Regression: mx25l12845e stops working on my device after commit 947c86e481a
In-Reply-To: <CAHP3WfMMK8GU3jpwxv4=C=Pc_9H4ZkmqAJqxZ0gN_rYqPDU3XQ@mail.gmail.com>
References: <CAHP3WfMMK8GU3jpwxv4=C=Pc_9H4ZkmqAJqxZ0gN_rYqPDU3XQ@mail.gmail.com>
Message-ID: <20260818052916.135931-1-chengminglin@mxic.com.tw>

Hi Russell,

Thanks for the report, and for narrowing it down to that commit.

Some background on commit 947c86e481a0 ("mtd: spi-nor: macronix: Drop
the redundant flash info fields"): it dropped .name/.size/.no_sfdp_flags
from that entry, because from that point on all of those parameters are
supposed to come from the flash's SFDP tables. spi_nor_needs_sfdp() keys
off .size being zero:

  static inline bool spi_nor_needs_sfdp(const struct spi_nor *nor)
  {
          return !nor->info->size;
  }

so once .size is gone, SFDP parsing becomes mandatory and any failure is
fatal (-EINVAL) instead of silently falling back to the legacy values.
That is exactly the path you are hitting:

  spi-nor spi0.0: BFPT parsing failed. Please consider using
  SPI_NOR_SKIP_SFDP when declaring the flash
  spi-nor spi0.0: probe with driver spi-nor failed with error -22

MX25L12845E does have an SFDP table, so in principle the entry without
.size should work. Please see the datasheet:

Link: https://www.mxic.com.tw/Lists/Datasheet/Attachments/8693/MX25L12845E,%203V,%20128Mb,%20v1.9.pdf

section "(33) Read SFDP Mode (RDSFDP)". It documents SFDP as per JEDEC
JESD216 v1.0: signature "SFDP" (50444653h) at offset 00h, two parameter
headers (the JEDEC Basic Flash Parameter Table, 9 DWORDs at PTP 30h, and
a Macronix vendor table, 4 DWORDs at PTP 60h), and the flash density
field at 37h:34h reads 07FFFFFFh, i.e. 128Mb / 16MB. So a part that
answers RDSFDP correctly should get its size discovered just fine.

This makes me suspect that the part on your board is not returning a
valid SFDP table, so the driver ends up with no flash size at all and
bails out.

In my opinion the best way forward here is a local patch for the single
flash that is affected: just add the size back in your own tree, and
your board will boot again.

Thanks,
Cheng Ming Lin


From mwalle at kernel.org  Mon Aug 17 23:43:55 2026
From: mwalle at kernel.org (Michael Walle)
Date: Tue, 18 Aug 2026 08:43:55 +0200
Subject: Regression: mx25l12845e stops working on my device after commit
 947c86e481a
In-Reply-To: <CAHP3WfMMK8GU3jpwxv4=C=Pc_9H4ZkmqAJqxZ0gN_rYqPDU3XQ@mail.gmail.com>
References: <CAHP3WfMMK8GU3jpwxv4=C=Pc_9H4ZkmqAJqxZ0gN_rYqPDU3XQ@mail.gmail.com>
Message-ID: <DKRV6XPSSU73.1VF5FG13NPGWE@kernel.org>

Hi Russell,

Thanks for testing and reporting! Did you know

 ./scripts/get_maintainer.pl -f drivers/mtd/spi-nor/macronix.c

That way you get all mail addresses of the maintainers you can add
to your mail. Not everyone is subscribed to the mailinglist.

On Sat Aug 15, 2026 at 12:48 PM CEST, Russell Senior wrote:
> I am running OpenWrt (master branch) on a Buffalo WZR600DHP. This
> particular instance has MX25L12845E SPI NOR chips (I have another
> WZR600DHP with Winbond W25Q128BVFG, but haven't tested there, just to
> say they don't all necessarily use the same flash).

Did you actually look at the chip or do you just know because linux
is printing the name? FWIW, the name linux is printing isn't always
correct due to flash ID reuse. I'm just curious.

> I just built a new firmware version last night, and it started
> unhappily boot looping. The most recent previous build I had done was
> from June. Boot looping messages look like this:
>
> [...]
> [    0.752843] printk: legacy bootconsole [early0] disabled
> [    0.778756] spi-nor spi0.0: supply vcc not found, using dummy regulator
> [    0.785909] spi-nor spi0.0: BFPT parsing failed. Please consider
> using SPI_NOR_SKIP_SFDP when declaring the flash
> [    0.796238] spi-nor spi0.0: probe with driver spi-nor failed with error -22
> [    0.804457] spi-nor spi0.1: supply vcc not found, using dummy regulator
> [    0.824085] i2c_dev: i2c /dev entries driver
> [    0.830852] NET: Registered PF_INET6 protocol family
> [    0.872875] Segment Routing with IPv6
> [    0.876671] In-situ OAM (IOAM) with IPv6
> [    0.880798] NET: Registered PF_PACKET protocol family
> [    0.886278] 8021q: 802.1Q VLAN Support v1.8
> [    0.921429] clk: Disabling unused clocks
> [    0.925661] check access for rdinit=/init failed: -2, ignoring
> [    0.932181] MTD: Couldn't look up '': -22
> [    0.936335] /dev/root: Can't open blockdev
> [    0.940520] VFS: Cannot open root device "" or unknown-block(0,0): error -6
> [    0.947472] Please append a correct "root=" boot option; here are
> the available partitions:
> [    0.955831] 1f00           16384 mtdblock0
> [    0.955843]  (driver?)
> [    0.962379] List of all bdev filesystems:
> [    0.966374]  squashfs
> [    0.966379]
> [    0.970139] Kernel panic - not syncing: VFS: Unable to mount root
> fs on unknown-block(0,0)
> [    0.978392] Rebooting in 1 seconds..
>
> Between June and last night, OpenWrt had switched their kernel from a
> 6.12.x to a 6.18.x, so I immediately suspected something in that
> transition was the culprit and demonstrated that by building the last
> commit before the change to the 6.18.x kernel, and it worked. Then
> tested the commit of the change to the 6.18.x kernel and it
> bootlooped.
>
> Then I started looking through the mtd changes between 6.12.x and
> 6.18.x and spotted 947c86e481a.
>
>   commit 947c86e481a027ed5948434096e506f307bd7114
>   Author: Cheng Ming Lin <chengminglin at mxic.com.tw>
>   Date:   Mon Apr 7 15:53:58 2025 +0800
>
>       mtd: spi-nor: macronix: Drop the redundant flash info fields
>
> The following revert of just the part that affected my chip made the
> bootloop vanish:
>
> diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c
> index e97f5cbd9aad2..3524ed08ff85d 100644
> --- a/drivers/mtd/spi-nor/macronix.c
> +++ b/drivers/mtd/spi-nor/macronix.c
> @@ -132,7 +132,10 @@ static const struct flash_info macronix_nor_parts[] = {
>         }, {
>                 /* MX25L12805D */
>                 .id = SNOR_ID(0xc2, 0x20, 0x18),
> +               .name = "mx25l12805d",
> +               .size = SZ_16M,
>                 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_4BIT_BP,
> +               .no_sfdp_flags = SECT_4K,
>         }, {
>                 /* MX25L25635E, MX25L25645G */
>                 .id = SNOR_ID(0xc2, 0x20, 0x19),

Yeah, that commit removed the size assuming there will only be parts
with SFDP for that ID.

There is already a patch series which addresses that issue, but it
wasn't picked up yet.

  https://lore.kernel.org/all/20260605084837.1875896-1-linchengming884 at gmail.com/

Russell, could you try to apply that series to your tree and try
again? If it works, give that series a Tested-by tag?

If you didn't know, there is a tool called b4
(https://b4.docs.kernel.org/en/latest/), which can be used to fetch
the patches from the mailingist (b4 am). Also, if you want to reply
to that patches, you can import the thread into your mailbox using
the "lei" (https://korg.docs.kernel.org/lore-lei.html).

Thanks,
-michael


From russell at personaltelco.net  Tue Aug 18 03:48:27 2026
From: russell at personaltelco.net (Russell Senior)
Date: Tue, 18 Aug 2026 03:48:27 -0700
Subject: [PATCH v3 0/2] mtd: spi-nor: macronix: Add support for MX25L12833F and MX25L12845G
In-Reply-To: <20260605084837.1875896-1-linchengming884@gmail.com>
References: <20260605084837.1875896-1-linchengming884@gmail.com>
Message-ID: <20260818104827.1869620-1-russell@personaltelco.net>

I noticed a few days ago that a Buffalo WZR600DHP running OpenWrt
started boot looping.

I traced the problem to the transition from a v6.12.x kernel to a
v6.18.x kernel, and from there, noticed the change in the mx25l128
support. After reporting the regression, I was directed to this patch
series and just tested it on top of the master branch of OpenWrt,
with linux kernel 6.18.44, and the boot looping problem is resolved.

Tested-by: Russell Senior <russell at personaltelco.net>


From mehmet.fide at gmail.com  Tue Aug 18 04:42:06 2026
From: mehmet.fide at gmail.com (Mehmet Fide)
Date: Tue, 18 Aug 2026 13:42:06 +0200
Subject: [PATCH 0/2] mtd: rawnand: vf610_nfc: two fixes for chips with large OOB and for erased pages
Message-ID: <20260818114208.2780311-1-mehmet.fide@gmail.com>

From: Mehmet Fide <mehmet.fide at screeningeagle.com>

Two independent fixes for the Vybrid NAND flash controller, both found
while bringing a Colibri VF61 up on mainline and both verified on
hardware.

Patch 1 is a regression. The driver clamps mtd->oobsize to the 64 bytes
its ECC layout uses, but since commit a7ab085d7c16 ("mtd: rawnand:
Initialize the nand_device object") nand_scan_tail() restores the value
from the memory organization right after ->attach_chip(), so the clamp
is silently lost. On a chip with more than 64 bytes of OOB every
ECC-protected read then fails and the board does not boot. Clamping the
memory organization as well is what the rest of the tree does when a
driver has to change the OOB size (nand_samsung.c, nand_onfi.c,
nand_jedec.c and denali.c all write memorg->oobsize); mpc5121_nfc.c
touches mtd->oobsize directly but does so before nand_scan(), so it is
not affected.

Doing this in ->attach_chip() is deliberate: the chip really does have
112 spare bytes, it is this controller that can only use 64, and
->attach_chip() is where a controller adapts to the detected chip. The
patch also touches the memory organization because nanddev_init(), which
runs later in nand_scan_tail(), re-derives mtd->{erasesize, writesize,
writebufsize, oobsize, size} from it, so an adjustment made only in mtd
does not survive. If you would rather have the core preserve what
->attach_chip() set up, or a dedicated way for a driver to declare that
it uses fewer OOB bytes than the chip provides, I am happy to respin; I
went with the minimal form because this is a regression fix that should
be backportable.

Patch 2 makes the erased-page check look at the flash instead of at the
controller buffer. When the ECC engine fails to decode a page it leaves
a bogus single-bit "correction" in that buffer, which the check counts
as a real bitflip; on the 60-byte ECC mode every erased page is
reported with one corrected bitflip, which renders the MTD statistics
useless for flash health monitoring.

Testing, all booting from NAND on an Iris carrier with Linux 6.18.44 and
U-Boot 2026.07:

  0010 Colibri VF50 128MB V1.2A     Macronix MX30LF1G08AA, 64-byte OOB
  0013 Colibri VF50 128MB IT V1.2B  Macronix MX30LF1G18AC, 64-byte OOB
  0012 Colibri VF61 256MB IT V1.2B  Macronix MX30LF4G28AC, 112-byte OOB

Without patch 1 the VF61 cannot attach UBI at all. With the series all
three boot, mtd oobsize stays 64, the bad block table written by an
older kernel reads back without ECC errors and the corrected-bitflip
counter stays at zero over a full-partition nanddump. The clamp only
triggers on the VF61 chip, so both patches are a no-op on the two VF50
revisions, which is what I wanted to confirm before sending them.

The credit for spotting both problems goes to Edward Karpicz, who
reported them on the Toradex community forum.

Mehmet Fide (2):
  mtd: rawnand: vf610_nfc: fix reads on chips with more than 64 bytes of
    OOB
  mtd: rawnand: vf610_nfc: fix false bitflips on reads of erased pages

 drivers/mtd/nand/raw/vf610_nfc.c | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)


base-commit: 15a3cbce32994141252bb4ecfe3ff3a5d22d0b4f
-- 
2.54.0



From mehmet.fide at gmail.com  Tue Aug 18 04:42:08 2026
From: mehmet.fide at gmail.com (Mehmet Fide)
Date: Tue, 18 Aug 2026 13:42:08 +0200
Subject: [PATCH 2/2] mtd: rawnand: vf610_nfc: fix false bitflips on reads of erased pages
In-Reply-To: <20260818114208.2780311-1-mehmet.fide@gmail.com>
References: <20260818114208.2780311-1-mehmet.fide@gmail.com>
Message-ID: <20260818114208.2780311-3-mehmet.fide@gmail.com>

From: Mehmet Fide <mehmet.fide at screeningeagle.com>

When the ECC engine fails to decode a page, the driver re-reads the OOB
area with the engine bypassed, but runs the erased-page check for the
data area on the buffer left in the controller SRAM by the failed
transfer.

That buffer does not hold what is on the flash: the failing engine
writes a bogus single-bit "correction" into it. In the 60-byte ECC mode
the all-0xff content of an erased page always decodes to the same error
location, so every erased page shows one stale zero bit at data offset
0x5FD, which the erased-page check then reports as a corrected bitflip.

Edward Karpicz discovered this behaviour and identified the offset on a
Colibri VF61; the analysis and the fix build on his finding. Measured
with an instrumented driver on a Colibri VF50 (MX30LF1G18AC, 32-bit
ECC): reading a 126 MiB partition with nanddump increased the corrected
counter by 18035, exactly one per erased page, while raw reads of the
same pages return clean 0xff. A v4.4 kernel on the VF61 (MX30LF4G28AC)
accumulates the same false counts, so the behaviour follows the
controller rather than the chip or the driver generation. Neither the
Vybrid reference manual nor the published mask set errata (VFXXX_2N02G)
document it. The 45-byte ECC mode is not affected.

Restoring the known byte is not enough: on pages that fail to decode
with content other than all-0xff the engine writes its correction
wherever the syndrome points (measured at a different offset on such a
page), so the check has to run on what the flash holds. Re-read the data
area with the ECC engine bypassed, exactly as already done for the OOB
area. The corrected counter then stays at zero on both boards.

Reported-by: Edward Karpicz <webmaster at toradex.com>
Link: https://community.toradex.com/t/colibri-vf50-vf61-on-the-current-bsp-mainline-u-boot-v2026-07-and-linux-6-18-lts/30735
Signed-off-by: Mehmet Fide <mehmet.fide at screeningeagle.com>
---
 drivers/mtd/nand/raw/vf610_nfc.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/nand/raw/vf610_nfc.c b/drivers/mtd/nand/raw/vf610_nfc.c
index f27ef2b0884d..d41750a4352c 100644
--- a/drivers/mtd/nand/raw/vf610_nfc.c
+++ b/drivers/mtd/nand/raw/vf610_nfc.c
@@ -514,6 +514,7 @@ static inline int vf610_nfc_correct_data(struct nand_chip *chip, uint8_t *dat,
 	u8 ecc_status;
 	u8 ecc_count;
 	int flips_threshold = nfc->chip.ecc.strength / 2;
+	int ret;
 
 	ecc_status = vf610_nfc_read(nfc, ecc_status_off) & 0xff;
 	ecc_count = ecc_status & ECC_STATUS_ERR_COUNT;
@@ -521,9 +522,17 @@ static inline int vf610_nfc_correct_data(struct nand_chip *chip, uint8_t *dat,
 	if (!(ecc_status & ECC_STATUS_MASK))
 		return ecc_count;
 
+	/*
+	 * The failed decode leaves a bogus "correction" in the SRAM buffer,
+	 * so re-read the data without ECC too, as already done for the OOB.
+	 */
 	nfc->data_access = true;
-	nand_read_oob_op(&nfc->chip, page, 0, oob, mtd->oobsize);
+	ret = nand_read_page_op(&nfc->chip, page, 0, dat, nfc->chip.ecc.size);
+	if (!ret)
+		ret = nand_read_oob_op(&nfc->chip, page, 0, oob, mtd->oobsize);
 	nfc->data_access = false;
+	if (ret)
+		return ret;
 
 	/*
 	 * On an erased page, bit count (including OOB) should be zero or
-- 
2.54.0



From mehmet.fide at gmail.com  Tue Aug 18 04:42:07 2026
From: mehmet.fide at gmail.com (Mehmet Fide)
Date: Tue, 18 Aug 2026 13:42:07 +0200
Subject: [PATCH 1/2] mtd: rawnand: vf610_nfc: fix reads on chips with more than 64 bytes of OOB
In-Reply-To: <20260818114208.2780311-1-mehmet.fide@gmail.com>
References: <20260818114208.2780311-1-mehmet.fide@gmail.com>
Message-ID: <20260818114208.2780311-2-mehmet.fide@gmail.com>

From: Mehmet Fide <mehmet.fide at screeningeagle.com>

The driver only implements the 64-byte OOB layout, so attach_chip()
shrinks mtd->oobsize when the chip provides more. That clamp does not
survive: nand_scan_tail() runs nanddev_init() after ->attach_chip(), and
it restores mtd->oobsize from the memory organization, which still holds
the value detected from the chip.

The driver therefore transfers writesize + the chip's full OOB size, so
the hardware ECC parity ends up at a different offset than the layout the
controller was set up for, and every ECC-protected read fails with
-EBADMSG.

Measured on a Colibri VF61 (MX30LF4G28AC, 2048-byte pages, 112 bytes of
OOB): with the clamp lost, UBI cannot read the erase counter headers of
the pages U-Boot has just written, and the on-flash bad block table
written by an older kernel reads back with ECC errors, so the board does
not boot. Kernels before commit a7ab085d7c16 ("mtd: rawnand: Initialize
the nand_device object") are not affected because nothing overwrote the
clamp there, which is why the same chip works with a v4.4 kernel and
with U-Boot, whose copy of this driver has no memory organization to
restore the value from. Edward Karpicz reported that the clamp no longer
takes effect on this chip; see the link below.

Clamp the memory organization as well so the driver's 64-byte layout
stays in effect, and log the truncation once, as it decides which
on-flash layout the system uses.

Reported-by: Edward Karpicz <webmaster at toradex.com>
Link: https://community.toradex.com/t/colibri-vf50-vf61-on-the-current-bsp-mainline-u-boot-v2026-07-and-linux-6-18-lts/30735
Fixes: a7ab085d7c16 ("mtd: rawnand: Initialize the nand_device object")
Cc: stable at vger.kernel.org
Signed-off-by: Mehmet Fide <mehmet.fide at screeningeagle.com>
---
 drivers/mtd/nand/raw/vf610_nfc.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/mtd/nand/raw/vf610_nfc.c b/drivers/mtd/nand/raw/vf610_nfc.c
index 9940681810cf..f27ef2b0884d 100644
--- a/drivers/mtd/nand/raw/vf610_nfc.c
+++ b/drivers/mtd/nand/raw/vf610_nfc.c
@@ -771,8 +771,14 @@ static int vf610_nfc_attach_chip(struct nand_chip *chip)
 	}
 
 	/* Only 64 byte ECC layouts known */
-	if (mtd->oobsize > 64)
+	if (mtd->oobsize > 64) {
+		dev_info(nfc->dev,
+			 "using 64 of %d OOB bytes, ECC layout is unchanged\n",
+			 mtd->oobsize);
 		mtd->oobsize = 64;
+		/* nand_scan_tail() restores mtd->oobsize from the memorg */
+		nanddev_get_memorg(&chip->base)->oobsize = 64;
+	}
 
 	/* Use default large page ECC layout defined in NAND core */
 	mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout());
-- 
2.54.0



From syzbot+250fde257a3eebba4426 at syzkaller.appspotmail.com  Tue Aug 18 09:40:27 2026
From: syzbot+250fde257a3eebba4426 at syzkaller.appspotmail.com (syzbot)
Date: Tue, 18 Aug 2026 09:40:27 -0700
Subject: [syzbot] [jffs2?] WARNING: bad unlock balance in jffs2_do_create
Message-ID: <6a848afb.c8649fcc.3bb87.0005.GAE@google.com>

Hello,

syzbot found the following issue on:

HEAD commit:    2f1baf1fc892 Merge tag 'trace-v7.2-rc7' of git://git.kerne..
git tree:       upstream
console output: https://syzkaller.appspot.com/x/log.txt?x=13d46279580000
kernel config:  https://syzkaller.appspot.com/x/.config?x=ead6a6de2292ff28
dashboard link: https://syzkaller.appspot.com/bug?extid=250fde257a3eebba4426
compiler:       Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8

Unfortunately, I don't have any reproducer for this issue yet.

Downloadable assets:
disk image (non-bootable): https://storage.googleapis.com/syzbot-assets/d900f083ada3/non_bootable_disk-2f1baf1f.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/3cf4d3de19da/vmlinux-2f1baf1f.xz
kernel image: https://storage.googleapis.com/syzbot-assets/2c309c87fcb0/bzImage-2f1baf1f.xz

IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+250fde257a3eebba4426 at syzkaller.appspotmail.com

Zero length message leads to an empty skb
jffs2: notice: (5313) jffs2_build_xattr_subsystem: complete building xattr subsystem, 0 of xdatum (0 unchecked, 0 orphan) and 0 of xref (0 dead, 0 orphan) found.
overlayfs: upper fs does not support tmpfile.
FAULT_INJECTION: forcing a failure.
name failslab, interval 1, probability 0, space 0, times 1
CPU: 0 UID: 0 PID: 5313 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
 <TASK>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 fail_dump lib/fault-inject.c:73 [inline]
 should_fail_ex+0x40c/0x560 lib/fault-inject.c:174
 should_failslab+0xa8/0x100 mm/failslab.c:46
 slab_pre_alloc_hook mm/slub.c:4539 [inline]
 slab_alloc_node mm/slub.c:4897 [inline]
 __kmalloc_cache_noprof+0xa8/0x660 mm/slub.c:5485
 _kmalloc_noprof include/linux/slab.h:988 [inline]
 jffs2_sum_add_kvec+0x858/0x1870 fs/jffs2/summary.c:266
 jffs2_flash_direct_writev+0xaa/0xe0 fs/jffs2/writev.c:22
 jffs2_flash_writev+0x156/0x1550 fs/jffs2/wbuf.c:805
 jffs2_write_dnode+0x485/0xe20 fs/jffs2/write.c:109
 jffs2_do_create+0x18e/0xe00 fs/jffs2/write.c:465
 jffs2_create+0x1c8/0x320 fs/jffs2/dir.c:205
 vfs_create+0x2c4/0x450 fs/namei.c:4202
 ovl_do_create+0x81/0xf0 fs/overlayfs/overlayfs.h:244
 ovl_create_real+0x1a8/0x740 fs/overlayfs/dir.c:180
 ovl_create_temp+0x169/0x240 fs/overlayfs/dir.c:267
 ovl_check_rename_whiteout fs/overlayfs/super.c:575 [inline]
 ovl_make_workdir fs/overlayfs/super.c:713 [inline]
 ovl_get_workdir fs/overlayfs/super.c:836 [inline]
 ovl_fill_super_creds fs/overlayfs/super.c:1449 [inline]
 ovl_fill_super+0x3c9b/0x5d40 fs/overlayfs/super.c:1560
 vfs_get_super fs/super.c:1273 [inline]
 get_tree_nodev+0xbb/0x150 fs/super.c:1292
 vfs_get_tree+0x92/0x2a0 fs/super.c:1700
 fc_mount fs/namespace.c:1198 [inline]
 do_new_mount_fc fs/namespace.c:3765 [inline]
 do_new_mount+0x319/0xdc0 fs/namespace.c:3841
 do_mount fs/namespace.c:4174 [inline]
 __do_sys_mount fs/namespace.c:4390 [inline]
 __se_sys_mount+0x31d/0x420 fs/namespace.c:4367
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fe775d9e0d9
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fe776d50fe8 EFLAGS: 00000246 ORIG_RAX: 00000000000000a5
RAX: ffffffffffffffda RBX: 00007fe776025fa0 RCX: 00007fe775d9e0d9
RDX: 0000200000000b80 RSI: 0000200000000100 RDI: 0000000000000000
RBP: 00007fe776d51050 R08: 0000200000000240 R09: 0000000000000000
R10: 0000000000000008 R11: 0000000000000246 R12: 0000000000000002
R13: 00007fe776026038 R14: 00007fe776025fa0 R15: 00007ffdf4560bd8
 </TASK>
jffs2: warning: (5313) jffs2_sum_add_kvec: MEMORY ALLOCATION ERROR!
jffs2: Write of 68 bytes at 0x0001e218 failed. returned -12, retlen 0
jffs2: Not marking the space at 0x0001e218 as dirty because the flash driver returned retlen zero

=====================================
WARNING: bad unlock balance detected!
syzkaller #0 Not tainted
-------------------------------------
syz.0.0/5313 is trying to release lock (&c->alloc_sem) at:
[<ffffffff833a6ebe>] jffs2_do_create+0x1ae/0xe00 fs/jffs2/write.c:474
but there are no more locks to release!

other info that might help us debug this:
3 locks held by syz.0.0/5313:
 #0: ffff8880424140d8 (&type->s_umount_key#52/1){+.+.}-{4:4}, at: alloc_super fs/super.c:345 [inline]
 #0: ffff8880424140d8 (&type->s_umount_key#52/1){+.+.}-{4:4}, at: sget_fc+0x938/0x1900 fs/super.c:766
 #1: ffff888012c72450 (sb_writers#12){.+.+}-{0:0}, at: mnt_want_write+0x41/0x90 fs/namespace.c:494
 #2: ffff88801229a880 (&type->i_mutex_dir_key#8/1){+.+.}-{4:4}, at: inode_lock_nested include/linux/fs.h:1069 [inline]
 #2: ffff88801229a880 (&type->i_mutex_dir_key#8/1){+.+.}-{4:4}, at: __start_dirop fs/namei.c:2918 [inline]
 #2: ffff88801229a880 (&type->i_mutex_dir_key#8/1){+.+.}-{4:4}, at: start_dirop fs/namei.c:2942 [inline]
 #2: ffff88801229a880 (&type->i_mutex_dir_key#8/1){+.+.}-{4:4}, at: start_creating+0xbe/0x100 fs/namei.c:3406

stack backtrace:
CPU: 0 UID: 0 PID: 5313 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
 <TASK>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 print_unlock_imbalance_bug+0xdc/0xf0 kernel/locking/lockdep.c:5298
 __lock_release kernel/locking/lockdep.c:5537 [inline]
 lock_release+0x248/0x3c0 kernel/locking/lockdep.c:5889
 __mutex_unlock_slowpath+0x88/0x900 kernel/locking/mutex.c:989
 jffs2_do_create+0x1ae/0xe00 fs/jffs2/write.c:474
 jffs2_create+0x1c8/0x320 fs/jffs2/dir.c:205
 vfs_create+0x2c4/0x450 fs/namei.c:4202
 ovl_do_create+0x81/0xf0 fs/overlayfs/overlayfs.h:244
 ovl_create_real+0x1a8/0x740 fs/overlayfs/dir.c:180
 ovl_create_temp+0x169/0x240 fs/overlayfs/dir.c:267
 ovl_check_rename_whiteout fs/overlayfs/super.c:575 [inline]
 ovl_make_workdir fs/overlayfs/super.c:713 [inline]
 ovl_get_workdir fs/overlayfs/super.c:836 [inline]
 ovl_fill_super_creds fs/overlayfs/super.c:1449 [inline]
 ovl_fill_super+0x3c9b/0x5d40 fs/overlayfs/super.c:1560
 vfs_get_super fs/super.c:1273 [inline]
 get_tree_nodev+0xbb/0x150 fs/super.c:1292
 vfs_get_tree+0x92/0x2a0 fs/super.c:1700
 fc_mount fs/namespace.c:1198 [inline]
 do_new_mount_fc fs/namespace.c:3765 [inline]
 do_new_mount+0x319/0xdc0 fs/namespace.c:3841
 do_mount fs/namespace.c:4174 [inline]
 __do_sys_mount fs/namespace.c:4390 [inline]
 __se_sys_mount+0x31d/0x420 fs/namespace.c:4367
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fe775d9e0d9
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fe776d50fe8 EFLAGS: 00000246 ORIG_RAX: 00000000000000a5
RAX: ffffffffffffffda RBX: 00007fe776025fa0 RCX: 00007fe775d9e0d9
RDX: 0000200000000b80 RSI: 0000200000000100 RDI: 0000000000000000
RBP: 00007fe776d51050 R08: 0000200000000240 R09: 0000000000000000
R10: 0000000000000008 R11: 0000000000000246 R12: 0000000000000002
R13: 00007fe776026038 R14: 00007fe776025fa0 R15: 00007ffdf4560bd8
 </TASK>


---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller at googlegroups.com.

syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.

If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title

If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)

If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report

If you want to undo deduplication, reply with:
#syz undup


From s-k6 at ti.com  Tue Aug 18 11:13:31 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Tue, 18 Aug 2026 23:43:31 +0530
Subject: [PATCH v7 16/18] mtd: spinand: negotiate optimal controller
 operating point before dirmap creation
In-Reply-To: <87bjb6l8nr.fsf@bootlin.com>
References: <20260811183313.1550425-1-s-k6@ti.com>
 <20260811183313.1550425-17-s-k6@ti.com> <87bjb6l8nr.fsf@bootlin.com>
Message-ID: <0a18a8b8-2e93-4375-a2ae-3e7316cbaab8@ti.com>

Hello Miquel,

On 13/08/26 14:05, Miquel Raynal wrote:
> Hi Santhosh,
> 
> I have only one comment on the spinand bits.
> 
>> +/*
>> + * spinand_try_ranked_variant() - Try controller optimization on variants in
>> + *				  performance order.
>> + * @spinand:    SPI NAND device
>> + * @mem:        SPI memory device
>> + * @iface:      bus interface to iterate (ODTR or SSDR)
>> + * @tried_mask: bitmask of already-tried variant indices; updated on each try
>> + *
>> + * Iterates the full read variant list in descending performance order,
>> + * skipping variants in @tried_mask, and calls execute_tuning on each until
>> + * one succeeds. Ranked iteration finds the best available variant without
>> + * re-trying already-attempted ones.
>> + *
>> + * On success, sets spinand->max_read_op and updates the matching
>> + * odtr_op_templates.read_cache or ssdr_op_templates.read_cache.
>> + */
>> +static bool spinand_try_ranked_variant(struct spinand_device *spinand,
>> +				       struct spi_mem *mem,
>> +				       enum spinand_bus_interface iface,
>> +				       u32 *tried_mask)
>> +{
>> +	const struct spinand_op_variants *variants = spinand->all_read_variants;
>> +	const struct spi_mem_op *best;
>> +	int ret;
>> +
>> +	if (!variants)
>> +		return false;
>> +
>> +	while ((best = spinand_op_find_best_variant(spinand, variants, iface,
>> +						    *tried_mask))) {
>> +		*tried_mask |= BIT(best - variants->ops);
>> +		spinand->max_read_op = *best;
>> +		spinand->max_read_op.max_freq = 0;
>> +		spinand->max_write_op.max_freq = 0;
>> +		ret = spi_mem_execute_tuning(mem, &spinand->max_read_op,
>> +					     &spinand->max_write_op);
>> +		if (ret && ret != -EOPNOTSUPP)
>> +			dev_dbg(&mem->spi->dev, "%s optimization failed: %d\n",
>> +				iface == ODTR ? "ODTR" : "SSDR", ret);
>> +		if (!ret && spinand->max_read_op.max_freq) {
>> +			if (iface == ODTR)
>> +				spinand->odtr_op_templates.read_cache = best;
>> +			else
>> +				spinand->ssdr_op_templates.read_cache = best;
>> +			spinand->cont_read_possible = false;
> 
> Why do you disable continuous reads? I know it is not the same as the
> read template, but it only differs by a few dummy cycles, so everything
> should work as expected. I believe without complexifying much the logic
> we should be able to support it.

Yeah, right! No need to disable continuous read as the only difference
is the dummy cycle count, my bad!

I've updated the logic to re-pair cont_read_cache with the selected
read_cache variant based on the protocol shape, while ignoring dummy
cycles. This keeps continuous read enabled without any unnecessary
complexity. I'll include this in v8.

Thanks,
Santhosh.

> 
> Thanks,
> Miqu?l



From sashal at kernel.org  Tue Aug 18 20:32:22 2026
From: sashal at kernel.org (Sasha Levin)
Date: Tue, 18 Aug 2026 23:32:22 -0400
Subject: [PATCH 0/2] Backport fixes for UBI
In-Reply-To: <20260818-ubi-backports-v1-0-324d6816d2ae@atmark-techno.com>
References: <20260818-ubi-backports-v1-0-324d6816d2ae@atmark-techno.com>
Message-ID: <20260818220420.8@kernel.org>

On Tue, Aug 18, 2026 at 02:09:33AM +0000, Dominique Martinet wrote:
> While looking at recent UBI patches I found these two commits that I
> believe are worth backporting.

Queued for 6.18 and 6.12, thanks - 6.18 is missing both as well, so it
gets them first.

-- 
Thanks,
Sasha


From xiaopei01 at kylinos.cn  Tue Aug 18 22:45:55 2026
From: xiaopei01 at kylinos.cn (Pei Xiao)
Date: Wed, 19 Aug 2026 13:45:55 +0800
Subject: [PATCH v3] mtd: block2mtd: Fix divide error when erase_size is
 zero
In-Reply-To: <25ec7ba58facdf29b42ca43dcf11cd98e2089a5f.1786589041.git.xiaopei01@kylinos.cn>
References: <25ec7ba58facdf29b42ca43dcf11cd98e2089a5f.1786589041.git.xiaopei01@kylinos.cn>
Message-ID: <cc164348-2b5a-45a8-8322-7efa9a392248@kylinos.cn>



? 2026/8/13 10:48, Pei Xiao ??:
> The erase size is parsed from the "block2mtd" module parameter and can
> be set to zero. add_device() then evaluates
> 
>         if (size % erase_size)
> 
> with a zero divisor, which triggers a divide error:
> 
> divide error: 0000 [#1] PREEMPT SMP PTI
> RIP: 0010:add_device drivers/mtd/devices/block2mtd.c:296 [inline]
> RIP: 0010:block2mtd_setup2+0x592/0xda0 drivers/mtd/devices/block2mtd.c:459
> Call Trace:
> block2mtd_setup+0x27/0xe0 drivers/mtd/devices/block2mtd.c:476
> param_attr_store+0x214/0x310 kernel/params.c:589
> module_attr_store+0x65/0x90 kernel/params.c:904
> kernfs_fop_write_iter+0x3a4/0x540 fs/kernfs/file.c:345
> ...
> 
> Reject a zero erase size before performing the modulo operation so the
> existing "erasesize must be a divisor of device size" error path
> reports the invalid argument and frees the device.
> 
> Fixes: ea6d833a3fdd ("mtd: block2mtd: check device size")
> Reported-by: syzbot+b320a4d5f65a61dbbf89 at syzkaller.appspotmail.com
> Closes: https://lore.kernel.org/lkml/6a7b58ba.ac361c09.22ff0a.0045.GAE at google.com/
> Suggested-by: J?rn Engel <joern at barelysecure.org>
> Cc: stable at vger.kernel.org
> Signed-off-by: Pei Xiao <xiaopei01 at kylinos.cn>
Add cc Maintainers.
> ---
> changes in v3:
> 1.remove (long) cast due to build errors in 32bit config systems
> 2.changes git log about (long) cast
> 
> changlogs in v2:
> 1.Add Suggested-by tag
> 2.remove unnecessary (long) cast from the size
> ---
>  drivers/mtd/devices/block2mtd.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c
> index 03e80b2c4f5a..349fa07be314 100644
> --- a/drivers/mtd/devices/block2mtd.c
> +++ b/drivers/mtd/devices/block2mtd.c
> @@ -293,7 +293,7 @@ static struct block2mtd_dev *add_device(char *devname, int erase_size,
>  	}
>  
>  	size = bdev_nr_bytes(bdev);
> -	if ((long)size % erase_size) {
> +	if (!erase_size || (long)size % erase_size) {
>  		pr_err("erasesize must be a divisor of device size\n");
>  		goto err_free_block2mtd;
>  	}



From error27 at gmail.com  Tue Aug 18 22:56:42 2026
From: error27 at gmail.com (Dan Carpenter)
Date: Wed, 19 Aug 2026 08:56:42 +0300
Subject: [bug report] mtd: rawnand: ndfc: Stop using nand_release()
Message-ID: <aoVFmrEnQcDuI1fQ@stanley.mountain>

Hello Miquel Raynal,

Commit a9384f95fe77 ("mtd: rawnand: ndfc: Stop using nand_release()")
from May 19, 2020 (linux-next), leads to the following Smatch static
checker warning:

	drivers/mtd/nand/raw/ndfc.c:254 ndfc_remove()
	error: dereferencing freed memory 'mtd' (line 251)

drivers/mtd/nand/raw/ndfc.c
    244 static void ndfc_remove(struct platform_device *ofdev)
    245 {
    246         struct ndfc_controller *ndfc = dev_get_drvdata(&ofdev->dev);
    247         struct nand_chip *chip = &ndfc->chip;
    248         struct mtd_info *mtd = nand_to_mtd(chip);
    249         int ret;
    250 
    251         ret = mtd_device_unregister(mtd);
    252         WARN_ON(ret);

This WARN_ON() would trigger if the use after free were a real issue...

Smatch is complaining that the mtd_virt_concat_destroy() function frees
mtd->name and mtd.

    253         nand_cleanup(chip);
--> 254         kfree(mtd->name);
                      ^^^^^^^^^
So this is a use after free double free.

I feel like the free in mtd_virt_concat_destroy() is done at the wrong
layer and should just be removed.  But I haven't looked at it very
closely.

    255 }

This email is a free service from the Smatch-CI project [smatch.sf.net].

regards,
dan carpenter


From jgross at suse.com  Wed Aug 19 03:23:01 2026
From: jgross at suse.com (Juergen Gross)
Date: Wed, 19 Aug 2026 12:23:01 +0200
Subject: [PATCH v2 00/13] x86/msr: Drop 32-bit MSR interfaces
Message-ID: <20260819102314.1499258-1-jgross@suse.com>

For accessing the MSR registers on the local CPU, there are 2 types of
interfaces: the "modern" 64-bit ones (rdmsrq() etc.) and the 32-bit
ones (rdmsr() etc.) which are using the upper and lower 32-bit halves
of the 64-bit wide MSR register values.

The 32-bit interfaces are not optimal for 3 reasons:

- They are based on primitives using 64-bit sized values anyway.

- Modern x86 CPUs have added support for MSR access instructions using
  an immediate value instead of a register for addressing the MSR,
  while the value is in a 64-bit register.

- rdmsr() is a macro storing the upper and lower 32-bit halves in
  variables specified as macro parameters. This is obscuring variable
  assignment through a macro. Additionally rdmsrq() is mimicking this
  pattern by being a macro, too, with the target variable specified as
  a parameter as well.

For those reasons drop the 32-bit interfaces for accessing the x86 MSR
registers completely and only use the 64-bit variants.

This allows to switch all "high-level" MSR access macros to inline
functions in the end.

This series will be used as the base for further reorganisation of the
MSR access functions, especially for completely inlining the MSR
access instructions even with paravirtualization being active.

Based on kernel 7.3 as of 2026-08-19.

Changes in V2:
- dropped already applied patches
- added patch 1
- rebased

Juergen Gross (13):
  x86/cpu: Fix coding style violation
  x86/msr: Remove wrmsr_safe()
  x86/msr: Remove rdmsr_safe()
  drivers/ata: Stop using 32-bit MSR interfaces
  agp/nvidia: Stop using 32-bit MSR interfaces
  fbdev/geode: Stop using 32-bit MSR interfaces
  hw_random/via-rng: Stop using 32-bit MSR interfaces
  drivers/gpio: Stop using 32-bit MSR interfaces
  drivers/misc: Stop using 32-bit MSR interfaces
  x86/msr: Remove wrmsr()
  x86/msr: Remove rdmsr()
  treewide: convert rdmsrq() from a macro to an inline function
  x86/msr: Simplify some rdmsrq() use cases

 arch/x86/coco/sev/core.c                      |  2 +-
 arch/x86/events/amd/brs.c                     |  4 +-
 arch/x86/events/amd/core.c                    |  8 +--
 arch/x86/events/amd/ibs.c                     | 18 +++----
 arch/x86/events/amd/lbr.c                     | 16 ++----
 arch/x86/events/amd/power.c                   |  8 +--
 arch/x86/events/amd/uncore.c                  |  4 +-
 arch/x86/events/core.c                        | 20 ++++----
 arch/x86/events/intel/core.c                  | 15 ++----
 arch/x86/events/intel/cstate.c                |  5 +-
 arch/x86/events/intel/ds.c                    |  2 +-
 arch/x86/events/intel/knc.c                   | 10 ++--
 arch/x86/events/intel/lbr.c                   | 25 +++-------
 arch/x86/events/intel/p4.c                    |  6 +--
 arch/x86/events/intel/p6.c                    |  4 +-
 arch/x86/events/intel/pt.c                    | 12 ++---
 arch/x86/events/intel/uncore.c                |  6 +--
 arch/x86/events/intel/uncore_nhmex.c          |  4 +-
 arch/x86/events/intel/uncore_snb.c            |  2 +-
 arch/x86/events/intel/uncore_snbep.c          |  6 +--
 arch/x86/events/msr.c                         |  2 +-
 arch/x86/events/perf_event.h                  |  6 +--
 arch/x86/events/rapl.c                        |  6 +--
 arch/x86/events/zhaoxin/core.c                | 10 ++--
 arch/x86/hyperv/hv_apic.c                     |  9 ++--
 arch/x86/hyperv/hv_init.c                     | 26 +++++-----
 arch/x86/hyperv/hv_spinlock.c                 |  2 +-
 arch/x86/include/asm/apic.h                   |  7 +--
 arch/x86/include/asm/debugreg.h               |  6 +--
 arch/x86/include/asm/fsgsbase.h               |  2 +-
 arch/x86/include/asm/kvm_host.h               |  5 +-
 arch/x86/include/asm/msr.h                    | 39 ++-------------
 arch/x86/include/asm/paravirt.h               | 26 +---------
 arch/x86/kernel/apic/apic.c                   | 14 +++---
 arch/x86/kernel/apic/apic_numachip.c          |  6 +--
 arch/x86/kernel/cet.c                         |  2 +-
 arch/x86/kernel/cpu/amd.c                     | 14 +++---
 arch/x86/kernel/cpu/aperfmperf.c              |  8 +--
 arch/x86/kernel/cpu/bugs.c                    | 12 ++---
 arch/x86/kernel/cpu/bus_lock.c                |  8 +--
 arch/x86/kernel/cpu/centaur.c                 |  8 +--
 arch/x86/kernel/cpu/common.c                  | 12 ++---
 arch/x86/kernel/cpu/feat_ctl.c                |  4 +-
 arch/x86/kernel/cpu/hygon.c                   |  4 +-
 arch/x86/kernel/cpu/intel.c                   |  6 +--
 arch/x86/kernel/cpu/intel_epb.c               |  4 +-
 arch/x86/kernel/cpu/mce/amd.c                 |  4 +-
 arch/x86/kernel/cpu/mce/core.c                |  8 +--
 arch/x86/kernel/cpu/mce/inject.c              |  2 +-
 arch/x86/kernel/cpu/mce/intel.c               | 18 +++----
 arch/x86/kernel/cpu/mce/p5.c                  |  8 +--
 arch/x86/kernel/cpu/mce/winchip.c             |  2 +-
 arch/x86/kernel/cpu/microcode/intel.c         |  2 +-
 arch/x86/kernel/cpu/mshyperv.c                |  6 +--
 arch/x86/kernel/cpu/mtrr/amd.c                |  4 +-
 arch/x86/kernel/cpu/mtrr/cleanup.c            |  4 +-
 arch/x86/kernel/cpu/mtrr/generic.c            | 32 ++++++------
 arch/x86/kernel/cpu/mtrr/mtrr.c               |  2 +-
 arch/x86/kernel/cpu/resctrl/core.c            |  2 +-
 arch/x86/kernel/cpu/resctrl/monitor.c         |  4 +-
 arch/x86/kernel/cpu/resctrl/pseudo_lock.c     |  4 +-
 arch/x86/kernel/cpu/resctrl/rdtgroup.c        |  2 +-
 arch/x86/kernel/cpu/topology.c                |  2 +-
 arch/x86/kernel/cpu/topology_amd.c            |  4 +-
 arch/x86/kernel/cpu/transmeta.c               |  8 +--
 arch/x86/kernel/cpu/tsx.c                     | 10 ++--
 arch/x86/kernel/cpu/umwait.c                  |  2 +-
 arch/x86/kernel/cpu/zhaoxin.c                 |  4 +-
 arch/x86/kernel/fpu/core.c                    |  2 +-
 arch/x86/kernel/hpet.c                        |  2 +-
 arch/x86/kernel/kvm.c                         |  2 +-
 arch/x86/kernel/mmconf-fam10h_64.c            |  6 +--
 arch/x86/kernel/process.c                     |  4 +-
 arch/x86/kernel/process_64.c                  | 14 +++---
 arch/x86/kernel/shstk.c                       |  8 +--
 arch/x86/kernel/traps.c                       |  4 +-
 arch/x86/kernel/tsc.c                         |  2 +-
 arch/x86/kernel/tsc_msr.c                     |  6 +--
 arch/x86/kernel/tsc_sync.c                    |  6 +--
 arch/x86/kvm/svm/pmu.c                        |  4 +-
 arch/x86/kvm/svm/svm.c                        |  4 +-
 arch/x86/kvm/vmx/nested.c                     |  4 +-
 arch/x86/kvm/vmx/pmu_intel.c                  |  8 +--
 arch/x86/kvm/vmx/sgx.c                        |  6 +--
 arch/x86/kvm/vmx/vmx.c                        | 36 ++++++-------
 arch/x86/kvm/x86.c                            |  8 +--
 arch/x86/lib/insn-eval.c                      |  6 +--
 arch/x86/lib/msr-smp.c                        |  2 +-
 arch/x86/mm/pat/memtype.c                     |  2 +-
 arch/x86/pci/amd_bus.c                        |  8 +--
 arch/x86/platform/olpc/olpc-xo1-rtc.c         |  6 +--
 arch/x86/platform/olpc/olpc-xo1-sci.c         |  2 +-
 arch/x86/power/cpu.c                          | 10 ++--
 arch/x86/realmode/init.c                      |  2 +-
 arch/x86/virt/hw.c                            |  8 +--
 arch/x86/virt/svm/sev.c                       | 18 +++----
 arch/x86/virt/vmx/tdx/tdx.c                   |  2 +-
 arch/x86/xen/suspend.c                        |  2 +-
 drivers/acpi/processor_perflib.c              |  2 +-
 drivers/ata/pata_cs5535.c                     | 20 ++++----
 drivers/ata/pata_cs5536.c                     | 17 +++----
 drivers/char/agp/nvidia-agp.c                 | 32 ++++++------
 drivers/char/hw_random/via-rng.c              | 29 +++++------
 drivers/cpufreq/acpi-cpufreq.c                |  8 +--
 drivers/cpufreq/amd-pstate.c                  |  4 +-
 drivers/cpufreq/e_powersaver.c                | 20 ++++----
 drivers/cpufreq/intel_pstate.c                | 30 +++++------
 drivers/cpufreq/longhaul.c                    | 12 ++---
 drivers/cpufreq/longrun.c                     | 16 +++---
 drivers/cpufreq/powernow-k7.c                 | 10 ++--
 drivers/cpufreq/powernow-k8.c                 |  8 +--
 drivers/cpufreq/speedstep-centrino.c          |  4 +-
 drivers/cpufreq/speedstep-lib.c               | 14 +++---
 drivers/edac/amd64_edac.c                     |  6 +--
 drivers/gpio/gpio-cs5535.c                    | 10 ++--
 drivers/hv/mshv_vtl_main.c                    |  2 +-
 drivers/hwmon/hwmon-vid.c                     |  4 +-
 drivers/idle/intel_idle.c                     | 26 +++++-----
 drivers/misc/cs5535-mfgpt.c                   | 33 ++++++------
 drivers/mtd/nand/raw/cs553x_nand.c            |  6 +--
 drivers/platform/x86/intel/ifs/load.c         | 10 ++--
 drivers/platform/x86/intel/ifs/runtest.c      |  8 +--
 drivers/platform/x86/intel/pmc/cnp.c          |  2 +-
 .../intel/speed_select_if/isst_if_mbox_msr.c  |  6 +--
 .../intel/speed_select_if/isst_tpmi_core.c    |  2 +-
 drivers/platform/x86/intel_ips.c              | 20 ++++----
 drivers/powercap/intel_rapl_msr.c             |  2 +-
 drivers/thermal/intel/intel_hfi.c             |  8 +--
 drivers/thermal/intel/therm_throt.c           | 22 ++++----
 drivers/thermal/intel/x86_pkg_temp_thermal.c  |  6 +--
 drivers/video/fbdev/geode/display_gx.c        |  8 +--
 drivers/video/fbdev/geode/gxfb_core.c         |  2 +-
 drivers/video/fbdev/geode/lxfb_ops.c          | 50 +++++++++----------
 drivers/video/fbdev/geode/suspend_gx.c        | 24 +++++----
 drivers/video/fbdev/geode/video_gx.c          |  8 +--
 include/linux/cs5535.h                        | 10 ++--
 136 files changed, 571 insertions(+), 683 deletions(-)

-- 
2.55.0



From jgross at suse.com  Wed Aug 19 03:23:13 2026
From: jgross at suse.com (Juergen Gross)
Date: Wed, 19 Aug 2026 12:23:13 +0200
Subject: [PATCH v2 12/13] treewide: convert rdmsrq() from a macro to an inline function
In-Reply-To: <20260819102314.1499258-1-jgross@suse.com>
References: <20260819102314.1499258-1-jgross@suse.com>
Message-ID: <20260819102314.1499258-13-jgross@suse.com>

Today rdmsrq() is a macro using its second parameter as the target for
storing the read MSR value.

Convert rdmsrq() to an inline function returning the MSR value.

The users have been converted using the following semantic patch:

  // Options: --include-headers

  virtual patch
  virtual report

  @@
  expression msr, val;
  @@
  (
  - rdmsrq(msr,val)
  + val = rdmsrq(msr)
  )

Signed-off-by: Juergen Gross <jgross at suse.com>
---
 arch/x86/coco/sev/core.c                      |  2 +-
 arch/x86/events/amd/brs.c                     |  4 +--
 arch/x86/events/amd/core.c                    |  4 +--
 arch/x86/events/amd/ibs.c                     | 18 +++++-----
 arch/x86/events/amd/lbr.c                     |  8 ++---
 arch/x86/events/amd/power.c                   |  8 ++---
 arch/x86/events/amd/uncore.c                  |  4 +--
 arch/x86/events/core.c                        | 20 +++++------
 arch/x86/events/intel/core.c                  | 11 +++---
 arch/x86/events/intel/cstate.c                |  2 +-
 arch/x86/events/intel/ds.c                    |  2 +-
 arch/x86/events/intel/knc.c                   |  6 ++--
 arch/x86/events/intel/lbr.c                   | 14 ++++----
 arch/x86/events/intel/p4.c                    |  6 ++--
 arch/x86/events/intel/p6.c                    |  4 +--
 arch/x86/events/intel/pt.c                    | 12 +++----
 arch/x86/events/intel/uncore.c                |  2 +-
 arch/x86/events/intel/uncore_nhmex.c          |  4 +--
 arch/x86/events/intel/uncore_snb.c            |  2 +-
 arch/x86/events/intel/uncore_snbep.c          |  6 ++--
 arch/x86/events/msr.c                         |  2 +-
 arch/x86/events/perf_event.h                  |  6 ++--
 arch/x86/events/rapl.c                        |  4 +--
 arch/x86/events/zhaoxin/core.c                |  6 ++--
 arch/x86/hyperv/hv_apic.c                     |  6 ++--
 arch/x86/hyperv/hv_init.c                     | 26 +++++++-------
 arch/x86/hyperv/hv_spinlock.c                 |  2 +-
 arch/x86/include/asm/apic.h                   |  4 +--
 arch/x86/include/asm/debugreg.h               |  2 +-
 arch/x86/include/asm/fsgsbase.h               |  2 +-
 arch/x86/include/asm/kvm_host.h               |  2 +-
 arch/x86/include/asm/msr.h                    | 15 ++++----
 arch/x86/include/asm/paravirt.h               |  8 ++---
 arch/x86/kernel/apic/apic.c                   | 14 ++++----
 arch/x86/kernel/apic/apic_numachip.c          |  6 ++--
 arch/x86/kernel/cet.c                         |  2 +-
 arch/x86/kernel/cpu/amd.c                     | 14 ++++----
 arch/x86/kernel/cpu/aperfmperf.c              |  8 ++---
 arch/x86/kernel/cpu/bugs.c                    | 12 +++----
 arch/x86/kernel/cpu/bus_lock.c                |  8 ++---
 arch/x86/kernel/cpu/centaur.c                 |  8 ++---
 arch/x86/kernel/cpu/common.c                  | 12 +++----
 arch/x86/kernel/cpu/feat_ctl.c                |  4 +--
 arch/x86/kernel/cpu/hygon.c                   |  4 +--
 arch/x86/kernel/cpu/intel.c                   |  6 ++--
 arch/x86/kernel/cpu/intel_epb.c               |  4 +--
 arch/x86/kernel/cpu/mce/amd.c                 |  4 +--
 arch/x86/kernel/cpu/mce/core.c                |  8 ++---
 arch/x86/kernel/cpu/mce/inject.c              |  2 +-
 arch/x86/kernel/cpu/mce/intel.c               | 18 +++++-----
 arch/x86/kernel/cpu/mce/p5.c                  |  8 ++---
 arch/x86/kernel/cpu/mce/winchip.c             |  2 +-
 arch/x86/kernel/cpu/microcode/intel.c         |  2 +-
 arch/x86/kernel/cpu/mshyperv.c                |  6 ++--
 arch/x86/kernel/cpu/mtrr/amd.c                |  4 +--
 arch/x86/kernel/cpu/mtrr/cleanup.c            |  4 +--
 arch/x86/kernel/cpu/mtrr/generic.c            | 32 ++++++++---------
 arch/x86/kernel/cpu/mtrr/mtrr.c               |  2 +-
 arch/x86/kernel/cpu/resctrl/core.c            |  2 +-
 arch/x86/kernel/cpu/resctrl/monitor.c         |  4 +--
 arch/x86/kernel/cpu/resctrl/pseudo_lock.c     |  4 +--
 arch/x86/kernel/cpu/resctrl/rdtgroup.c        |  2 +-
 arch/x86/kernel/cpu/topology.c                |  2 +-
 arch/x86/kernel/cpu/topology_amd.c            |  4 +--
 arch/x86/kernel/cpu/transmeta.c               |  2 +-
 arch/x86/kernel/cpu/tsx.c                     | 10 +++---
 arch/x86/kernel/cpu/umwait.c                  |  2 +-
 arch/x86/kernel/cpu/zhaoxin.c                 |  4 +--
 arch/x86/kernel/fpu/core.c                    |  2 +-
 arch/x86/kernel/hpet.c                        |  2 +-
 arch/x86/kernel/kvm.c                         |  2 +-
 arch/x86/kernel/mmconf-fam10h_64.c            |  6 ++--
 arch/x86/kernel/process.c                     |  4 +--
 arch/x86/kernel/process_64.c                  | 14 ++++----
 arch/x86/kernel/shstk.c                       |  8 ++---
 arch/x86/kernel/traps.c                       |  4 +--
 arch/x86/kernel/tsc.c                         |  2 +-
 arch/x86/kernel/tsc_msr.c                     |  6 ++--
 arch/x86/kernel/tsc_sync.c                    |  6 ++--
 arch/x86/kvm/svm/pmu.c                        |  4 +--
 arch/x86/kvm/svm/svm.c                        |  4 +--
 arch/x86/kvm/vmx/nested.c                     |  4 +--
 arch/x86/kvm/vmx/pmu_intel.c                  |  8 ++---
 arch/x86/kvm/vmx/sgx.c                        |  6 ++--
 arch/x86/kvm/vmx/vmx.c                        | 36 +++++++++----------
 arch/x86/kvm/x86.c                            |  8 ++---
 arch/x86/lib/insn-eval.c                      |  6 ++--
 arch/x86/lib/msr-smp.c                        |  2 +-
 arch/x86/mm/pat/memtype.c                     |  2 +-
 arch/x86/pci/amd_bus.c                        |  8 ++---
 arch/x86/platform/olpc/olpc-xo1-rtc.c         |  6 ++--
 arch/x86/platform/olpc/olpc-xo1-sci.c         |  2 +-
 arch/x86/power/cpu.c                          | 10 +++---
 arch/x86/realmode/init.c                      |  2 +-
 arch/x86/virt/hw.c                            |  8 ++---
 arch/x86/virt/svm/sev.c                       | 18 +++++-----
 arch/x86/virt/vmx/tdx/tdx.c                   |  2 +-
 arch/x86/xen/suspend.c                        |  2 +-
 drivers/acpi/processor_perflib.c              |  2 +-
 drivers/ata/pata_cs5535.c                     |  4 +--
 drivers/ata/pata_cs5536.c                     |  2 +-
 drivers/char/agp/nvidia-agp.c                 |  6 ++--
 drivers/char/hw_random/via-rng.c              |  4 +--
 drivers/cpufreq/acpi-cpufreq.c                |  8 ++---
 drivers/cpufreq/amd-pstate.c                  |  4 +--
 drivers/cpufreq/e_powersaver.c                | 20 +++++------
 drivers/cpufreq/intel_pstate.c                | 30 ++++++++--------
 drivers/cpufreq/longhaul.c                    | 12 +++----
 drivers/cpufreq/longrun.c                     | 16 ++++-----
 drivers/cpufreq/powernow-k7.c                 | 10 +++---
 drivers/cpufreq/powernow-k8.c                 |  8 ++---
 drivers/cpufreq/speedstep-centrino.c          |  4 +--
 drivers/cpufreq/speedstep-lib.c               | 14 ++++----
 drivers/edac/amd64_edac.c                     |  6 ++--
 drivers/gpio/gpio-cs5535.c                    |  2 +-
 drivers/hv/mshv_vtl_main.c                    |  2 +-
 drivers/hwmon/hwmon-vid.c                     |  4 +--
 drivers/idle/intel_idle.c                     | 26 +++++++-------
 drivers/misc/cs5535-mfgpt.c                   |  6 ++--
 drivers/mtd/nand/raw/cs553x_nand.c            |  6 ++--
 drivers/platform/x86/intel/ifs/load.c         | 10 +++---
 drivers/platform/x86/intel/ifs/runtest.c      |  8 ++---
 drivers/platform/x86/intel/pmc/cnp.c          |  2 +-
 .../intel/speed_select_if/isst_if_mbox_msr.c  |  6 ++--
 .../intel/speed_select_if/isst_tpmi_core.c    |  2 +-
 drivers/platform/x86/intel_ips.c              | 20 +++++------
 drivers/powercap/intel_rapl_msr.c             |  2 +-
 drivers/thermal/intel/intel_hfi.c             |  8 ++---
 drivers/thermal/intel/therm_throt.c           | 22 ++++++------
 drivers/thermal/intel/x86_pkg_temp_thermal.c  |  6 ++--
 drivers/video/fbdev/geode/display_gx.c        |  2 +-
 drivers/video/fbdev/geode/gxfb_core.c         |  2 +-
 drivers/video/fbdev/geode/lxfb_ops.c          | 18 +++++-----
 drivers/video/fbdev/geode/suspend_gx.c        |  8 ++---
 drivers/video/fbdev/geode/video_gx.c          |  8 ++---
 include/linux/cs5535.h                        |  2 +-
 136 files changed, 484 insertions(+), 486 deletions(-)

diff --git a/arch/x86/coco/sev/core.c b/arch/x86/coco/sev/core.c
index cc292d7c6fd1..2a1875642cd4 100644
--- a/arch/x86/coco/sev/core.c
+++ b/arch/x86/coco/sev/core.c
@@ -2026,7 +2026,7 @@ void __init snp_secure_tsc_init(void)
 	secrets = (__force struct snp_secrets_page *)mem;
 
 	setup_force_cpu_cap(X86_FEATURE_TSC_KNOWN_FREQ);
-	rdmsrq(MSR_AMD64_GUEST_TSC_FREQ, tsc_freq_mhz);
+	tsc_freq_mhz = rdmsrq(MSR_AMD64_GUEST_TSC_FREQ);
 
 	/* Extract the GUEST TSC MHZ from BIT[17:0], rest is reserved space */
 	tsc_freq_mhz &= GENMASK_ULL(17, 0);
diff --git a/arch/x86/events/amd/brs.c b/arch/x86/events/amd/brs.c
index dc564688f3d7..4df3a2a736e0 100644
--- a/arch/x86/events/amd/brs.c
+++ b/arch/x86/events/amd/brs.c
@@ -325,7 +325,7 @@ void amd_brs_drain(void)
 		u32 brs_idx = tos - i;
 		u64 from, to;
 
-		rdmsrq(brs_to(brs_idx), to);
+		to = rdmsrq(brs_to(brs_idx));
 
 		/* Entry does not belong to us (as marked by kernel) */
 		if (to == BRS_POISON)
@@ -338,7 +338,7 @@ void amd_brs_drain(void)
 		 */
 		to = (u64)(((s64)to << shift) >> shift);
 
-		rdmsrq(brs_from(brs_idx), from);
+		from = rdmsrq(brs_from(brs_idx));
 
 		if (!amd_brs_match_plm(event, from, to))
 			continue;
diff --git a/arch/x86/events/amd/core.c b/arch/x86/events/amd/core.c
index 49b6b8fce566..80ed49dba255 100644
--- a/arch/x86/events/amd/core.c
+++ b/arch/x86/events/amd/core.c
@@ -663,7 +663,7 @@ static inline u64 amd_pmu_get_global_status(void)
 	u64 status;
 
 	/* PerfCntrGlobalStatus is read-only */
-	rdmsrq(MSR_AMD64_PERF_CNTR_GLOBAL_STATUS, status);
+	status = rdmsrq(MSR_AMD64_PERF_CNTR_GLOBAL_STATUS);
 
 	return status;
 }
@@ -683,7 +683,7 @@ static bool amd_pmu_test_overflow_topbit(int idx)
 {
 	u64 counter;
 
-	rdmsrq(x86_pmu_event_addr(idx), counter);
+	counter = rdmsrq(x86_pmu_event_addr(idx));
 
 	return !(counter & BIT_ULL(x86_pmu.cntval_bits - 1));
 }
diff --git a/arch/x86/events/amd/ibs.c b/arch/x86/events/amd/ibs.c
index 3531f9c23b8c..17eab32164df 100644
--- a/arch/x86/events/amd/ibs.c
+++ b/arch/x86/events/amd/ibs.c
@@ -505,7 +505,7 @@ perf_ibs_event_update(struct perf_ibs *perf_ibs, struct perf_event *event,
 	 * prev count manually on overflow.
 	 */
 	while (!perf_event_try_update(event, count, 64)) {
-		rdmsrq(event->hw.config_base, *config);
+		*config = rdmsrq(event->hw.config_base);
 		count = perf_ibs->get_count(*config);
 	}
 }
@@ -610,7 +610,7 @@ static void perf_ibs_stop(struct perf_event *event, int flags)
 	if (!stopping && (hwc->state & PERF_HES_UPTODATE))
 		return;
 
-	rdmsrq(hwc->config_base, config);
+	config = rdmsrq(hwc->config_base);
 
 	if (stopping) {
 		/*
@@ -1437,7 +1437,7 @@ static int perf_ibs_handle_irq(struct perf_ibs *perf_ibs, struct pt_regs *iregs)
 	hwc = &event->hw;
 	msr = hwc->config_base;
 	buf = ibs_data.regs;
-	rdmsrq(msr, *buf);
+	*buf = rdmsrq(msr);
 	if (!(*buf++ & perf_ibs->valid_mask))
 		goto fail;
 
@@ -1455,7 +1455,7 @@ static int perf_ibs_handle_irq(struct perf_ibs *perf_ibs, struct pt_regs *iregs)
 	offset_max = perf_ibs_get_offset_max(perf_ibs, event, check_rip);
 
 	do {
-		rdmsrq(msr + offset, *buf++);
+		*buf++ = rdmsrq(msr + offset);
 		size++;
 		offset = find_next_bit(perf_ibs->offset_mask,
 				       perf_ibs->offset_max,
@@ -1497,17 +1497,17 @@ static int perf_ibs_handle_irq(struct perf_ibs *perf_ibs, struct pt_regs *iregs)
 	if (event->attr.sample_type & PERF_SAMPLE_RAW) {
 		if (perf_ibs == &perf_ibs_op) {
 			if (ibs_caps & IBS_CAPS_BRNTRGT) {
-				rdmsrq(MSR_AMD64_IBSBRTARGET, *buf++);
+				*buf++ = rdmsrq(MSR_AMD64_IBSBRTARGET);
 				br_target_idx = size;
 				size++;
 			}
 			if (ibs_caps & IBS_CAPS_OPDATA4) {
-				rdmsrq(MSR_AMD64_IBSOPDATA4, *buf++);
+				*buf++ = rdmsrq(MSR_AMD64_IBSOPDATA4);
 				size++;
 			}
 		}
 		if (perf_ibs == &perf_ibs_fetch && (ibs_caps & IBS_CAPS_FETCHCTLEXTD)) {
-			rdmsrq(MSR_AMD64_ICIBSEXTDCTL, *buf++);
+			*buf++ = rdmsrq(MSR_AMD64_ICIBSEXTDCTL);
 			size++;
 		}
 	}
@@ -1768,7 +1768,7 @@ static inline int ibs_eilvt_valid(void)
 
 	preempt_disable();
 
-	rdmsrq(MSR_AMD64_IBSCTL, val);
+	val = rdmsrq(MSR_AMD64_IBSCTL);
 	offset = val & IBSCTL_LVT_OFFSET_MASK;
 
 	if (!(val & IBSCTL_LVT_OFFSET_VALID)) {
@@ -1883,7 +1883,7 @@ static inline int get_ibs_lvt_offset(void)
 {
 	u64 val;
 
-	rdmsrq(MSR_AMD64_IBSCTL, val);
+	val = rdmsrq(MSR_AMD64_IBSCTL);
 	if (!(val & IBSCTL_LVT_OFFSET_VALID))
 		return -EINVAL;
 
diff --git a/arch/x86/events/amd/lbr.c b/arch/x86/events/amd/lbr.c
index 9d9c961989d5..29628af6a023 100644
--- a/arch/x86/events/amd/lbr.c
+++ b/arch/x86/events/amd/lbr.c
@@ -76,7 +76,7 @@ static __always_inline u64 amd_pmu_lbr_get_from(unsigned int idx)
 {
 	u64 val;
 
-	rdmsrq(MSR_AMD_SAMP_BR_FROM + idx * 2, val);
+	val = rdmsrq(MSR_AMD_SAMP_BR_FROM + idx * 2);
 
 	return val;
 }
@@ -85,7 +85,7 @@ static __always_inline u64 amd_pmu_lbr_get_to(unsigned int idx)
 {
 	u64 val;
 
-	rdmsrq(MSR_AMD_SAMP_BR_FROM + idx * 2 + 1, val);
+	val = rdmsrq(MSR_AMD_SAMP_BR_FROM + idx * 2 + 1);
 
 	return val;
 }
@@ -404,11 +404,11 @@ void amd_pmu_lbr_enable_all(void)
 	}
 
 	if (cpu_feature_enabled(X86_FEATURE_AMD_LBR_PMC_FREEZE)) {
-		rdmsrq(MSR_IA32_DEBUGCTLMSR, dbg_ctl);
+		dbg_ctl = rdmsrq(MSR_IA32_DEBUGCTLMSR);
 		wrmsrq(MSR_IA32_DEBUGCTLMSR, dbg_ctl | DEBUGCTLMSR_FREEZE_LBRS_ON_PMI);
 	}
 
-	rdmsrq(MSR_AMD_DBG_EXTN_CFG, dbg_extn_cfg);
+	dbg_extn_cfg = rdmsrq(MSR_AMD_DBG_EXTN_CFG);
 	wrmsrq(MSR_AMD_DBG_EXTN_CFG, dbg_extn_cfg | DBG_EXTN_CFG_LBRV2EN);
 }
 
diff --git a/arch/x86/events/amd/power.c b/arch/x86/events/amd/power.c
index 744dffa42dee..553a6d0c8a14 100644
--- a/arch/x86/events/amd/power.c
+++ b/arch/x86/events/amd/power.c
@@ -52,8 +52,8 @@ static void event_update(struct perf_event *event)
 
 	prev_pwr_acc = hwc->pwr_acc;
 	prev_ptsc = hwc->ptsc;
-	rdmsrq(MSR_F15H_CU_PWR_ACCUMULATOR, new_pwr_acc);
-	rdmsrq(MSR_F15H_PTSC, new_ptsc);
+	new_pwr_acc = rdmsrq(MSR_F15H_CU_PWR_ACCUMULATOR);
+	new_ptsc = rdmsrq(MSR_F15H_PTSC);
 
 	/*
 	 * Calculate the CU power consumption over a time period, the unit of
@@ -79,8 +79,8 @@ static void __pmu_event_start(struct perf_event *event)
 
 	event->hw.state = 0;
 
-	rdmsrq(MSR_F15H_PTSC, event->hw.ptsc);
-	rdmsrq(MSR_F15H_CU_PWR_ACCUMULATOR, event->hw.pwr_acc);
+	event->hw.ptsc = rdmsrq(MSR_F15H_PTSC);
+	event->hw.pwr_acc = rdmsrq(MSR_F15H_CU_PWR_ACCUMULATOR);
 }
 
 static void pmu_event_start(struct perf_event *event, int mode)
diff --git a/arch/x86/events/amd/uncore.c b/arch/x86/events/amd/uncore.c
index 222dfab9225f..9d81b24cd963 100644
--- a/arch/x86/events/amd/uncore.c
+++ b/arch/x86/events/amd/uncore.c
@@ -151,7 +151,7 @@ static void amd_uncore_read(struct perf_event *event)
 	 * read counts directly from the corresponding PERF_CTR.
 	 */
 	if (hwc->event_base_rdpmc < 0)
-		rdmsrq(hwc->event_base, new);
+		new = rdmsrq(hwc->event_base);
 	else
 		new = rdpmc(hwc->event_base_rdpmc);
 
@@ -998,7 +998,7 @@ static void amd_uncore_umc_read(struct perf_event *event)
 	 * UMC counters do not have RDPMC assignments. Read counts directly
 	 * from the corresponding PERF_CTR.
 	 */
-	rdmsrq(hwc->event_base, new);
+	new = rdmsrq(hwc->event_base);
 
 	/*
 	 * Unlike the other uncore counters, UMC counters saturate and set the
diff --git a/arch/x86/events/core.c b/arch/x86/events/core.c
index 8b3ea0adb965..34bd80bb2c9e 100644
--- a/arch/x86/events/core.c
+++ b/arch/x86/events/core.c
@@ -711,7 +711,7 @@ void x86_pmu_disable_all(void)
 
 		if (!test_bit(idx, cpuc->active_mask))
 			continue;
-		rdmsrq(x86_pmu_config_addr(idx), val);
+		val = rdmsrq(x86_pmu_config_addr(idx));
 		if (!(val & ARCH_PERFMON_EVENTSEL_ENABLE))
 			continue;
 		val &= ~ARCH_PERFMON_EVENTSEL_ENABLE;
@@ -1592,10 +1592,10 @@ void perf_event_print_debug(void)
 		return;
 
 	if (x86_pmu.version >= 2) {
-		rdmsrq(MSR_CORE_PERF_GLOBAL_CTRL, ctrl);
-		rdmsrq(MSR_CORE_PERF_GLOBAL_STATUS, status);
-		rdmsrq(MSR_CORE_PERF_GLOBAL_OVF_CTRL, overflow);
-		rdmsrq(MSR_ARCH_PERFMON_FIXED_CTR_CTRL, fixed);
+		ctrl = rdmsrq(MSR_CORE_PERF_GLOBAL_CTRL);
+		status = rdmsrq(MSR_CORE_PERF_GLOBAL_STATUS);
+		overflow = rdmsrq(MSR_CORE_PERF_GLOBAL_OVF_CTRL);
+		fixed = rdmsrq(MSR_ARCH_PERFMON_FIXED_CTR_CTRL);
 
 		pr_info("\n");
 		pr_info("CPU#%d: ctrl:       %016llx\n", cpu, ctrl);
@@ -1603,19 +1603,19 @@ void perf_event_print_debug(void)
 		pr_info("CPU#%d: overflow:   %016llx\n", cpu, overflow);
 		pr_info("CPU#%d: fixed:      %016llx\n", cpu, fixed);
 		if (pebs_constraints) {
-			rdmsrq(MSR_IA32_PEBS_ENABLE, pebs);
+			pebs = rdmsrq(MSR_IA32_PEBS_ENABLE);
 			pr_info("CPU#%d: pebs:       %016llx\n", cpu, pebs);
 		}
 		if (x86_pmu.lbr_nr) {
-			rdmsrq(MSR_IA32_DEBUGCTLMSR, debugctl);
+			debugctl = rdmsrq(MSR_IA32_DEBUGCTLMSR);
 			pr_info("CPU#%d: debugctl:   %016llx\n", cpu, debugctl);
 		}
 	}
 	pr_info("CPU#%d: active:     %016llx\n", cpu, *(u64 *)cpuc->active_mask);
 
 	for_each_set_bit(idx, cntr_mask, X86_PMC_IDX_MAX) {
-		rdmsrq(x86_pmu_config_addr(idx), pmc_ctrl);
-		rdmsrq(x86_pmu_event_addr(idx), pmc_count);
+		pmc_ctrl = rdmsrq(x86_pmu_config_addr(idx));
+		pmc_count = rdmsrq(x86_pmu_event_addr(idx));
 
 		prev_left = per_cpu(pmc_prev_left[idx], cpu);
 
@@ -1627,7 +1627,7 @@ void perf_event_print_debug(void)
 			cpu, idx, prev_left);
 	}
 	for_each_set_bit(idx, fixed_cntr_mask, X86_PMC_IDX_MAX) {
-		rdmsrq(x86_pmu_fixed_ctr_addr(idx), pmc_count);
+		pmc_count = rdmsrq(x86_pmu_fixed_ctr_addr(idx));
 
 		pr_info("CPU#%d: fixed-PMC%d count: %016llx\n",
 			cpu, idx, pmc_count);
diff --git a/arch/x86/events/intel/core.c b/arch/x86/events/intel/core.c
index 27cda71c96ce..47a2abe73221 100644
--- a/arch/x86/events/intel/core.c
+++ b/arch/x86/events/intel/core.c
@@ -2965,7 +2965,7 @@ static inline u64 intel_pmu_get_status(void)
 {
 	u64 status;
 
-	rdmsrq(MSR_CORE_PERF_GLOBAL_STATUS, status);
+	status = rdmsrq(MSR_CORE_PERF_GLOBAL_STATUS);
 
 	return status;
 }
@@ -3494,7 +3494,7 @@ static void intel_pmu_enable_event_ext(struct perf_event *event)
 		else
 			new.thresh = ARCH_PEBS_THRESH_SINGLE;
 
-		rdmsrq(MSR_IA32_PEBS_INDEX, old.whole);
+		old.whole = rdmsrq(MSR_IA32_PEBS_INDEX);
 		if (new.thresh != old.thresh || !old.en) {
 			if (old.thresh == ARCH_PEBS_THRESH_MULTI && old.wr > 0) {
 				/*
@@ -6255,8 +6255,7 @@ static void intel_update_pmu_caps(struct pmu *pmu)
 		update_pmu_cap_from_perfmonext(pmu);
 
 	if (is_hybrid() && this_cpu_has(X86_FEATURE_PDCM)) {
-		rdmsrq(MSR_IA32_PERF_CAPABILITIES,
-		       hybrid(pmu, intel_cap).capabilities);
+		hybrid(pmu, intel_cap).capabilities = rdmsrq(MSR_IA32_PERF_CAPABILITIES);
 
 		/*
 		 * Restore perf_metrics on platforms with broken
@@ -6412,7 +6411,7 @@ static void intel_pmu_cpu_starting(int cpu)
 	if (!is_hybrid() && x86_pmu.intel_cap.perf_metrics) {
 		union perf_capabilities perf_cap;
 
-		rdmsrq(MSR_IA32_PERF_CAPABILITIES, perf_cap.capabilities);
+		perf_cap.capabilities = rdmsrq(MSR_IA32_PERF_CAPABILITIES);
 		if (!perf_cap.perf_metrics) {
 			x86_pmu.intel_cap.perf_metrics = 0;
 			x86_pmu.intel_ctrl &= ~GLOBAL_CTRL_EN_PERF_METRICS;
@@ -7959,7 +7958,7 @@ __init int intel_pmu_init(void)
 	if (boot_cpu_has(X86_FEATURE_PDCM)) {
 		u64 capabilities;
 
-		rdmsrq(MSR_IA32_PERF_CAPABILITIES, capabilities);
+		capabilities = rdmsrq(MSR_IA32_PERF_CAPABILITIES);
 		x86_pmu.intel_cap.capabilities = capabilities;
 	}
 
diff --git a/arch/x86/events/intel/cstate.c b/arch/x86/events/intel/cstate.c
index f3d5ee07f8f2..69eb6cf51d3b 100644
--- a/arch/x86/events/intel/cstate.c
+++ b/arch/x86/events/intel/cstate.c
@@ -324,7 +324,7 @@ static inline u64 cstate_pmu_read_counter(struct perf_event *event)
 {
 	u64 val;
 
-	rdmsrq(event->hw.event_base, val);
+	val = rdmsrq(event->hw.event_base);
 	return val;
 }
 
diff --git a/arch/x86/events/intel/ds.c b/arch/x86/events/intel/ds.c
index e86e4ba91e1b..d332ae04e3cd 100644
--- a/arch/x86/events/intel/ds.c
+++ b/arch/x86/events/intel/ds.c
@@ -3231,7 +3231,7 @@ static void intel_pmu_drain_arch_pebs(struct pt_regs *iregs,
 	void *base, *at, *top;
 	u64 mask;
 
-	rdmsrq(MSR_IA32_PEBS_INDEX, index.whole);
+	index.whole = rdmsrq(MSR_IA32_PEBS_INDEX);
 
 	if (unlikely(!index.wr)) {
 		intel_pmu_pebs_event_update_no_drain(cpuc, X86_PMC_IDX_MAX);
diff --git a/arch/x86/events/intel/knc.c b/arch/x86/events/intel/knc.c
index e887adc108ac..c4f81215f758 100644
--- a/arch/x86/events/intel/knc.c
+++ b/arch/x86/events/intel/knc.c
@@ -160,7 +160,7 @@ static void knc_pmu_disable_all(void)
 {
 	u64 val;
 
-	rdmsrq(MSR_KNC_IA32_PERF_GLOBAL_CTRL, val);
+	val = rdmsrq(MSR_KNC_IA32_PERF_GLOBAL_CTRL);
 	val &= ~(KNC_ENABLE_COUNTER0|KNC_ENABLE_COUNTER1);
 	wrmsrq(MSR_KNC_IA32_PERF_GLOBAL_CTRL, val);
 }
@@ -169,7 +169,7 @@ static void knc_pmu_enable_all(int added)
 {
 	u64 val;
 
-	rdmsrq(MSR_KNC_IA32_PERF_GLOBAL_CTRL, val);
+	val = rdmsrq(MSR_KNC_IA32_PERF_GLOBAL_CTRL);
 	val |= (KNC_ENABLE_COUNTER0|KNC_ENABLE_COUNTER1);
 	wrmsrq(MSR_KNC_IA32_PERF_GLOBAL_CTRL, val);
 }
@@ -201,7 +201,7 @@ static inline u64 knc_pmu_get_status(void)
 {
 	u64 status;
 
-	rdmsrq(MSR_KNC_IA32_PERF_GLOBAL_STATUS, status);
+	status = rdmsrq(MSR_KNC_IA32_PERF_GLOBAL_STATUS);
 
 	return status;
 }
diff --git a/arch/x86/events/intel/lbr.c b/arch/x86/events/intel/lbr.c
index cbe5c762008d..fc05ec3b9a99 100644
--- a/arch/x86/events/intel/lbr.c
+++ b/arch/x86/events/intel/lbr.c
@@ -141,7 +141,7 @@ static void __intel_pmu_lbr_enable(bool pmi)
 	if (!cpu_feature_enabled(X86_FEATURE_ARCH_LBR) && !pmi && cpuc->lbr_sel)
 		wrmsrq(MSR_LBR_SELECT, lbr_select);
 
-	rdmsrq(MSR_IA32_DEBUGCTLMSR, debugctl);
+	debugctl = rdmsrq(MSR_IA32_DEBUGCTLMSR);
 	orig_debugctl = debugctl;
 
 	if (!cpu_feature_enabled(X86_FEATURE_ARCH_LBR))
@@ -211,7 +211,7 @@ static inline u64 intel_pmu_lbr_tos(void)
 {
 	u64 tos;
 
-	rdmsrq(x86_pmu.lbr_tos, tos);
+	tos = rdmsrq(x86_pmu.lbr_tos);
 	return tos;
 }
 
@@ -304,7 +304,7 @@ static __always_inline u64 rdlbr_from(unsigned int idx, struct lbr_entry *lbr)
 	if (lbr)
 		return lbr->from;
 
-	rdmsrq(x86_pmu.lbr_from + idx, val);
+	val = rdmsrq(x86_pmu.lbr_from + idx);
 
 	return lbr_from_signext_quirk_rd(val);
 }
@@ -316,7 +316,7 @@ static __always_inline u64 rdlbr_to(unsigned int idx, struct lbr_entry *lbr)
 	if (lbr)
 		return lbr->to;
 
-	rdmsrq(x86_pmu.lbr_to + idx, val);
+	val = rdmsrq(x86_pmu.lbr_to + idx);
 
 	return val;
 }
@@ -328,7 +328,7 @@ static __always_inline u64 rdlbr_info(unsigned int idx, struct lbr_entry *lbr)
 	if (lbr)
 		return lbr->info;
 
-	rdmsrq(x86_pmu.lbr_info + idx, val);
+	val = rdmsrq(x86_pmu.lbr_info + idx);
 
 	return val;
 }
@@ -477,7 +477,7 @@ void intel_pmu_lbr_save(void *ctx)
 	task_ctx->tos = tos;
 
 	if (cpuc->lbr_select)
-		rdmsrq(MSR_LBR_SELECT, task_ctx->lbr_sel);
+		task_ctx->lbr_sel = rdmsrq(MSR_LBR_SELECT);
 }
 
 static void intel_pmu_arch_lbr_save(void *ctx)
@@ -754,7 +754,7 @@ void intel_pmu_lbr_read_32(struct cpu_hw_events *cpuc)
 			u64     lbr;
 		} msr_lastbranch;
 
-		rdmsrq(x86_pmu.lbr_from + lbr_idx, msr_lastbranch.lbr);
+		msr_lastbranch.lbr = rdmsrq(x86_pmu.lbr_from + lbr_idx);
 
 		perf_clear_branch_entry_bitfields(br);
 
diff --git a/arch/x86/events/intel/p4.c b/arch/x86/events/intel/p4.c
index 5368dc31787c..e675e85682f1 100644
--- a/arch/x86/events/intel/p4.c
+++ b/arch/x86/events/intel/p4.c
@@ -860,7 +860,7 @@ static inline int p4_pmu_clear_cccr_ovf(struct hw_perf_event *hwc)
 	u64 v;
 
 	/* an official way for overflow indication */
-	rdmsrq(hwc->config_base, v);
+	v = rdmsrq(hwc->config_base);
 	if (v & P4_CCCR_OVF) {
 		wrmsrq(hwc->config_base, v & ~P4_CCCR_OVF);
 		return 1;
@@ -873,7 +873,7 @@ static inline int p4_pmu_clear_cccr_ovf(struct hw_perf_event *hwc)
 	 * the counter has reached zero value and continued counting before
 	 * real NMI signal was received:
 	 */
-	rdmsrq(hwc->event_base, v);
+	v = rdmsrq(hwc->event_base);
 	if (!(v & ARCH_P4_UNFLAGGED_BIT))
 		return 1;
 
@@ -1373,7 +1373,7 @@ __init int p4_pmu_init(void)
 	/* If we get stripped -- indexing fails */
 	BUILD_BUG_ON(ARCH_P4_MAX_CCCR > INTEL_PMC_MAX_GENERIC);
 
-	rdmsrq(MSR_IA32_MISC_ENABLE, misc);
+	misc = rdmsrq(MSR_IA32_MISC_ENABLE);
 	if (!(misc & MSR_IA32_MISC_ENABLE_EMON)) {
 		pr_cont("unsupported Netburst CPU model %d ",
 			boot_cpu_data.x86_model);
diff --git a/arch/x86/events/intel/p6.c b/arch/x86/events/intel/p6.c
index fb991e0ac614..4268b576b5d8 100644
--- a/arch/x86/events/intel/p6.c
+++ b/arch/x86/events/intel/p6.c
@@ -143,7 +143,7 @@ static void p6_pmu_disable_all(void)
 	u64 val;
 
 	/* p6 only has one enable register */
-	rdmsrq(MSR_P6_EVNTSEL0, val);
+	val = rdmsrq(MSR_P6_EVNTSEL0);
 	val &= ~ARCH_PERFMON_EVENTSEL_ENABLE;
 	wrmsrq(MSR_P6_EVNTSEL0, val);
 }
@@ -153,7 +153,7 @@ static void p6_pmu_enable_all(int added)
 	unsigned long val;
 
 	/* p6 only has one enable register */
-	rdmsrq(MSR_P6_EVNTSEL0, val);
+	val = rdmsrq(MSR_P6_EVNTSEL0);
 	val |= ARCH_PERFMON_EVENTSEL_ENABLE;
 	wrmsrq(MSR_P6_EVNTSEL0, val);
 }
diff --git a/arch/x86/events/intel/pt.c b/arch/x86/events/intel/pt.c
index 5754cd405562..d595d69b49a1 100644
--- a/arch/x86/events/intel/pt.c
+++ b/arch/x86/events/intel/pt.c
@@ -196,7 +196,7 @@ static int __init pt_pmu_hw_init(void)
 	int ret;
 	long i;
 
-	rdmsrq(MSR_PLATFORM_INFO, reg);
+	reg = rdmsrq(MSR_PLATFORM_INFO);
 	pt_pmu.max_nonturbo_ratio = (reg & 0xff00) >> 8;
 
 	/*
@@ -232,7 +232,7 @@ static int __init pt_pmu_hw_init(void)
 		 * "IA32_VMX_MISC[bit 14]" being 1 means PT can trace
 		 * post-VMXON.
 		 */
-		rdmsrq(MSR_IA32_VMX_MISC, reg);
+		reg = rdmsrq(MSR_IA32_VMX_MISC);
 		if (reg & BIT(14))
 			pt_pmu.vmx = true;
 	}
@@ -935,7 +935,7 @@ static void pt_handle_status(struct pt *pt)
 	int advance = 0;
 	u64 status;
 
-	rdmsrq(MSR_IA32_RTIT_STATUS, status);
+	status = rdmsrq(MSR_IA32_RTIT_STATUS);
 
 	if (status & RTIT_STATUS_ERROR) {
 		pr_err_ratelimited("ToPA ERROR encountered, trying to recover\n");
@@ -994,12 +994,12 @@ static void pt_read_offset(struct pt_buffer *buf)
 	struct topa_page *tp;
 
 	if (!buf->single) {
-		rdmsrq(MSR_IA32_RTIT_OUTPUT_BASE, pt->output_base);
+		pt->output_base = rdmsrq(MSR_IA32_RTIT_OUTPUT_BASE);
 		tp = phys_to_virt(pt->output_base);
 		buf->cur = &tp->topa;
 	}
 
-	rdmsrq(MSR_IA32_RTIT_OUTPUT_MASK, pt->output_mask);
+	pt->output_mask = rdmsrq(MSR_IA32_RTIT_OUTPUT_MASK);
 	/* offset within current output region */
 	buf->output_off = pt->output_mask >> 32;
 	/* index of current output region within this table */
@@ -1623,7 +1623,7 @@ static void pt_event_start(struct perf_event *event, int mode)
 			 * PMI might have just cleared these, so resume_allowed
 			 * must be checked again also.
 			 */
-			rdmsrq(MSR_IA32_RTIT_STATUS, status);
+			status = rdmsrq(MSR_IA32_RTIT_STATUS);
 			if (!(status & (RTIT_STATUS_TRIGGEREN |
 					RTIT_STATUS_ERROR |
 					RTIT_STATUS_STOPPED)) &&
diff --git a/arch/x86/events/intel/uncore.c b/arch/x86/events/intel/uncore.c
index 9f8a80c9dcb6..cd85a5d28e42 100644
--- a/arch/x86/events/intel/uncore.c
+++ b/arch/x86/events/intel/uncore.c
@@ -171,7 +171,7 @@ u64 uncore_msr_read_counter(struct intel_uncore_box *box, struct perf_event *eve
 {
 	u64 count;
 
-	rdmsrq(event->hw.event_base, count);
+	count = rdmsrq(event->hw.event_base);
 
 	return count;
 }
diff --git a/arch/x86/events/intel/uncore_nhmex.c b/arch/x86/events/intel/uncore_nhmex.c
index 7a6855281102..81e838e6f729 100644
--- a/arch/x86/events/intel/uncore_nhmex.c
+++ b/arch/x86/events/intel/uncore_nhmex.c
@@ -216,7 +216,7 @@ static void nhmex_uncore_msr_disable_box(struct intel_uncore_box *box)
 	u64 config;
 
 	if (msr) {
-		rdmsrq(msr, config);
+		config = rdmsrq(msr);
 		config &= ~((1ULL << uncore_num_counters(box)) - 1);
 		/* WBox has a fixed counter */
 		if (uncore_msr_fixed_ctl(box))
@@ -231,7 +231,7 @@ static void nhmex_uncore_msr_enable_box(struct intel_uncore_box *box)
 	u64 config;
 
 	if (msr) {
-		rdmsrq(msr, config);
+		config = rdmsrq(msr);
 		config |= (1ULL << uncore_num_counters(box)) - 1;
 		/* WBox has a fixed counter */
 		if (uncore_msr_fixed_ctl(box))
diff --git a/arch/x86/events/intel/uncore_snb.c b/arch/x86/events/intel/uncore_snb.c
index 055131c508ff..da864724ff20 100644
--- a/arch/x86/events/intel/uncore_snb.c
+++ b/arch/x86/events/intel/uncore_snb.c
@@ -533,7 +533,7 @@ static int icl_get_cbox_num(void)
 {
 	u64 num_boxes;
 
-	rdmsrq(ICL_UNC_CBO_CONFIG, num_boxes);
+	num_boxes = rdmsrq(ICL_UNC_CBO_CONFIG);
 
 	return num_boxes & ICL_UNC_NUM_CBO_MASK;
 }
diff --git a/arch/x86/events/intel/uncore_snbep.c b/arch/x86/events/intel/uncore_snbep.c
index a97cd029db36..490725c8fee1 100644
--- a/arch/x86/events/intel/uncore_snbep.c
+++ b/arch/x86/events/intel/uncore_snbep.c
@@ -642,7 +642,7 @@ static void snbep_uncore_msr_disable_box(struct intel_uncore_box *box)
 
 	msr = uncore_msr_box_ctl(box);
 	if (msr) {
-		rdmsrq(msr, config);
+		config = rdmsrq(msr);
 		config |= SNBEP_PMON_BOX_CTL_FRZ;
 		wrmsrq(msr, config);
 	}
@@ -655,7 +655,7 @@ static void snbep_uncore_msr_enable_box(struct intel_uncore_box *box)
 
 	msr = uncore_msr_box_ctl(box);
 	if (msr) {
-		rdmsrq(msr, config);
+		config = rdmsrq(msr);
 		config &= ~SNBEP_PMON_BOX_CTL_FRZ;
 		wrmsrq(msr, config);
 	}
@@ -6370,7 +6370,7 @@ void spr_uncore_cpu_init(void)
 		 * of UNCORE_SPR_CHA) is incorrect on some SPR variants because of a
 		 * firmware bug. Using the value from SPR_MSR_UNC_CBO_CONFIG to replace it.
 		 */
-		rdmsrq(SPR_MSR_UNC_CBO_CONFIG, num_cbo);
+		num_cbo = rdmsrq(SPR_MSR_UNC_CBO_CONFIG);
 		/*
 		 * The MSR doesn't work on the EMR XCC, but the firmware bug doesn't impact
 		 * the EMR XCC. Don't let the value from the MSR replace the existing value.
diff --git a/arch/x86/events/msr.c b/arch/x86/events/msr.c
index 76d6418c5055..0e59781549ca 100644
--- a/arch/x86/events/msr.c
+++ b/arch/x86/events/msr.c
@@ -158,7 +158,7 @@ static inline u64 msr_read_counter(struct perf_event *event)
 	u64 now;
 
 	if (event->hw.event_base)
-		rdmsrq(event->hw.event_base, now);
+		now = rdmsrq(event->hw.event_base);
 	else
 		now = rdtsc_ordered();
 
diff --git a/arch/x86/events/perf_event.h b/arch/x86/events/perf_event.h
index 71ed5b2acea2..4abdb9475e8f 100644
--- a/arch/x86/events/perf_event.h
+++ b/arch/x86/events/perf_event.h
@@ -1475,11 +1475,11 @@ static __always_inline void __amd_pmu_lbr_disable(void)
 {
 	u64 dbg_ctl, dbg_extn_cfg;
 
-	rdmsrq(MSR_AMD_DBG_EXTN_CFG, dbg_extn_cfg);
+	dbg_extn_cfg = rdmsrq(MSR_AMD_DBG_EXTN_CFG);
 	wrmsrq(MSR_AMD_DBG_EXTN_CFG, dbg_extn_cfg & ~DBG_EXTN_CFG_LBRV2EN);
 
 	if (cpu_feature_enabled(X86_FEATURE_AMD_LBR_PMC_FREEZE)) {
-		rdmsrq(MSR_IA32_DEBUGCTLMSR, dbg_ctl);
+		dbg_ctl = rdmsrq(MSR_IA32_DEBUGCTLMSR);
 		wrmsrq(MSR_IA32_DEBUGCTLMSR, dbg_ctl & ~DEBUGCTLMSR_FREEZE_LBRS_ON_PMI);
 	}
 }
@@ -1633,7 +1633,7 @@ static __always_inline void __intel_pmu_lbr_disable(void)
 {
 	u64 debugctl;
 
-	rdmsrq(MSR_IA32_DEBUGCTLMSR, debugctl);
+	debugctl = rdmsrq(MSR_IA32_DEBUGCTLMSR);
 	debugctl &= ~(DEBUGCTLMSR_LBR | DEBUGCTLMSR_FREEZE_LBRS_ON_PMI);
 	wrmsrq(MSR_IA32_DEBUGCTLMSR, debugctl);
 }
diff --git a/arch/x86/events/rapl.c b/arch/x86/events/rapl.c
index 8ed03c32f560..180cc18282ca 100644
--- a/arch/x86/events/rapl.c
+++ b/arch/x86/events/rapl.c
@@ -193,7 +193,7 @@ static inline unsigned int get_rapl_pmu_idx(int cpu, int scope)
 static inline u64 rapl_read_counter(struct perf_event *event)
 {
 	u64 raw;
-	rdmsrq(event->hw.event_base, raw);
+	raw = rdmsrq(event->hw.event_base);
 	return raw;
 }
 
@@ -222,7 +222,7 @@ static u64 rapl_event_update(struct perf_event *event)
 
 	prev_raw_count = local64_read(&hwc->prev_count);
 	do {
-		rdmsrq(event->hw.event_base, new_raw_count);
+		new_raw_count = rdmsrq(event->hw.event_base);
 	} while (!local64_try_cmpxchg(&hwc->prev_count,
 				      &prev_raw_count, new_raw_count));
 
diff --git a/arch/x86/events/zhaoxin/core.c b/arch/x86/events/zhaoxin/core.c
index e506f677db57..1980e5995e27 100644
--- a/arch/x86/events/zhaoxin/core.c
+++ b/arch/x86/events/zhaoxin/core.c
@@ -268,7 +268,7 @@ static inline u64 zhaoxin_pmu_get_status(void)
 {
 	u64 status;
 
-	rdmsrq(MSR_CORE_PERF_GLOBAL_STATUS, status);
+	status = rdmsrq(MSR_CORE_PERF_GLOBAL_STATUS);
 
 	return status;
 }
@@ -295,7 +295,7 @@ static void zhaoxin_pmu_disable_fixed(struct hw_perf_event *hwc)
 
 	mask = 0xfULL << (idx * 4);
 
-	rdmsrq(hwc->config_base, ctrl_val);
+	ctrl_val = rdmsrq(hwc->config_base);
 	ctrl_val &= ~mask;
 	wrmsrq(hwc->config_base, ctrl_val);
 }
@@ -331,7 +331,7 @@ static void zhaoxin_pmu_enable_fixed(struct hw_perf_event *hwc)
 	bits <<= (idx * 4);
 	mask = 0xfULL << (idx * 4);
 
-	rdmsrq(hwc->config_base, ctrl_val);
+	ctrl_val = rdmsrq(hwc->config_base);
 	ctrl_val &= ~mask;
 	ctrl_val |= bits;
 	wrmsrq(hwc->config_base, ctrl_val);
diff --git a/arch/x86/hyperv/hv_apic.c b/arch/x86/hyperv/hv_apic.c
index 95f1782d1e17..4e30f9a11bc4 100644
--- a/arch/x86/hyperv/hv_apic.c
+++ b/arch/x86/hyperv/hv_apic.c
@@ -38,7 +38,7 @@ static u64 hv_apic_icr_read(void)
 {
 	u64 reg_val;
 
-	rdmsrq(HV_X64_MSR_ICR, reg_val);
+	reg_val = rdmsrq(HV_X64_MSR_ICR);
 	return reg_val;
 }
 
@@ -64,10 +64,10 @@ static u32 hv_apic_read(u32 reg)
 
 	switch (reg) {
 	case APIC_EOI:
-		rdmsrq(HV_X64_MSR_EOI, reg_val.q);
+		reg_val.q = rdmsrq(HV_X64_MSR_EOI);
 		return reg_val.l;
 	case APIC_TASKPRI:
-		rdmsrq(HV_X64_MSR_TPR, reg_val.q);
+		reg_val.q = rdmsrq(HV_X64_MSR_TPR);
 		return reg_val.l;
 
 	default:
diff --git a/arch/x86/hyperv/hv_init.c b/arch/x86/hyperv/hv_init.c
index 55a8b6de2865..bc8f91114868 100644
--- a/arch/x86/hyperv/hv_init.c
+++ b/arch/x86/hyperv/hv_init.c
@@ -101,7 +101,7 @@ static int hyperv_init_ghcb(void)
 	 * returned by MSR_AMD64_SEV_ES_GHCB is above shared
 	 * memory boundary and map it here.
 	 */
-	rdmsrq(MSR_AMD64_SEV_ES_GHCB, ghcb_gpa);
+	ghcb_gpa = rdmsrq(MSR_AMD64_SEV_ES_GHCB);
 
 	/* Mask out vTOM bit and map as decrypted */
 	ghcb_gpa &= ~ms_hyperv.shared_gpa_boundary;
@@ -134,7 +134,7 @@ static int hv_cpu_init(unsigned int cpu)
 		 * For root partition we get the hypervisor provided VP assist
 		 * page, instead of allocating a new page.
 		 */
-		rdmsrq(HV_X64_MSR_VP_ASSIST_PAGE, msr.as_uint64);
+		msr.as_uint64 = rdmsrq(HV_X64_MSR_VP_ASSIST_PAGE);
 		*hvp = memremap(msr.pfn << HV_X64_MSR_VP_ASSIST_PAGE_ADDRESS_SHIFT,
 				PAGE_SIZE, MEMREMAP_WB);
 	} else {
@@ -183,7 +183,7 @@ static void hv_reenlightenment_notify(struct work_struct *dummy)
 {
 	struct hv_tsc_emulation_status emu_status;
 
-	rdmsrq(HV_X64_MSR_TSC_EMULATION_STATUS, *(u64 *)&emu_status);
+	*(u64 *)&emu_status = rdmsrq(HV_X64_MSR_TSC_EMULATION_STATUS);
 
 	/* Don't issue the callback if TSC accesses are not emulated */
 	if (hv_reenlightenment_cb && emu_status.inprogress)
@@ -196,11 +196,11 @@ void hyperv_stop_tsc_emulation(void)
 	u64 freq;
 	struct hv_tsc_emulation_status emu_status;
 
-	rdmsrq(HV_X64_MSR_TSC_EMULATION_STATUS, *(u64 *)&emu_status);
+	*(u64 *)&emu_status = rdmsrq(HV_X64_MSR_TSC_EMULATION_STATUS);
 	emu_status.inprogress = 0;
 	wrmsrq(HV_X64_MSR_TSC_EMULATION_STATUS, *(u64 *)&emu_status);
 
-	rdmsrq(HV_X64_MSR_TSC_FREQUENCY, freq);
+	freq = rdmsrq(HV_X64_MSR_TSC_FREQUENCY);
 	tsc_khz = div64_u64(freq, 1000);
 }
 EXPORT_SYMBOL_GPL(hyperv_stop_tsc_emulation);
@@ -260,7 +260,7 @@ void clear_hv_tscchange_cb(void)
 	if (!hv_reenlightenment_available())
 		return;
 
-	rdmsrq(HV_X64_MSR_REENLIGHTENMENT_CONTROL, *(u64 *)&re_ctrl);
+	*(u64 *)&re_ctrl = rdmsrq(HV_X64_MSR_REENLIGHTENMENT_CONTROL);
 	re_ctrl.enabled = 0;
 	wrmsrq(HV_X64_MSR_REENLIGHTENMENT_CONTROL, *(u64 *)&re_ctrl);
 
@@ -297,7 +297,7 @@ static int hv_cpu_die(unsigned int cpu)
 			 */
 			memunmap(hv_vp_assist_page[cpu]);
 			hv_vp_assist_page[cpu] = NULL;
-			rdmsrq(HV_X64_MSR_VP_ASSIST_PAGE, msr.as_uint64);
+			msr.as_uint64 = rdmsrq(HV_X64_MSR_VP_ASSIST_PAGE);
 			msr.enable = 0;
 		}
 		wrmsrq(HV_X64_MSR_VP_ASSIST_PAGE, msr.as_uint64);
@@ -306,7 +306,7 @@ static int hv_cpu_die(unsigned int cpu)
 	if (hv_reenlightenment_cb == NULL)
 		return 0;
 
-	rdmsrq(HV_X64_MSR_REENLIGHTENMENT_CONTROL, *((u64 *)&re_ctrl));
+	*((u64 *)&re_ctrl) = rdmsrq(HV_X64_MSR_REENLIGHTENMENT_CONTROL);
 	if (re_ctrl.target_vp == hv_vp_index[cpu]) {
 		/*
 		 * Reassign reenlightenment notifications to some other online
@@ -377,7 +377,7 @@ static int hv_suspend(void *data)
 	hv_set_hypercall_pg(NULL);
 
 	/* Disable the hypercall page in the hypervisor */
-	rdmsrq(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
+	hypercall_msr.as_uint64 = rdmsrq(HV_X64_MSR_HYPERCALL);
 	hypercall_msr.enable = 0;
 	wrmsrq(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
 
@@ -394,7 +394,7 @@ static void hv_resume(void *data)
 	WARN_ON(ret);
 
 	/* Re-enable the hypercall page */
-	rdmsrq(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
+	hypercall_msr.as_uint64 = rdmsrq(HV_X64_MSR_HYPERCALL);
 	hypercall_msr.enable = 1;
 	hypercall_msr.guest_physical_address =
 		vmalloc_to_pfn(hv_hypercall_pg_saved);
@@ -529,7 +529,7 @@ void __init hyperv_init(void)
 	if (hv_hypercall_pg == NULL)
 		goto clean_guest_os_id;
 
-	rdmsrq(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
+	hypercall_msr.as_uint64 = rdmsrq(HV_X64_MSR_HYPERCALL);
 	hypercall_msr.enable = 1;
 
 	if (hv_root_partition()) {
@@ -671,7 +671,7 @@ void hyperv_report_panic(struct pt_regs *regs, long err, bool in_die)
 		return;
 	panic_reported = true;
 
-	rdmsrq(HV_X64_MSR_GUEST_OS_ID, guest_id);
+	guest_id = rdmsrq(HV_X64_MSR_GUEST_OS_ID);
 
 	wrmsrq(HV_X64_MSR_CRASH_P0, err);
 	wrmsrq(HV_X64_MSR_CRASH_P1, guest_id);
@@ -705,7 +705,7 @@ bool hv_is_hyperv_initialized(void)
 	 * that the hypercall page is setup
 	 */
 	hypercall_msr.as_uint64 = 0;
-	rdmsrq(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
+	hypercall_msr.as_uint64 = rdmsrq(HV_X64_MSR_HYPERCALL);
 
 	return hypercall_msr.enable;
 }
diff --git a/arch/x86/hyperv/hv_spinlock.c b/arch/x86/hyperv/hv_spinlock.c
index 6b4bdea18218..7ef2d794e2e8 100644
--- a/arch/x86/hyperv/hv_spinlock.c
+++ b/arch/x86/hyperv/hv_spinlock.c
@@ -50,7 +50,7 @@ static void hv_qlock_wait(u8 *byte, u8 val)
 	if (READ_ONCE(*byte) == val) {
 		unsigned long msr_val;
 
-		rdmsrq(HV_X64_MSR_GUEST_IDLE, msr_val);
+		msr_val = rdmsrq(HV_X64_MSR_GUEST_IDLE);
 
 		(void)msr_val;
 	}
diff --git a/arch/x86/include/asm/apic.h b/arch/x86/include/asm/apic.h
index 9cd493d467d4..e028140fac49 100644
--- a/arch/x86/include/asm/apic.h
+++ b/arch/x86/include/asm/apic.h
@@ -220,7 +220,7 @@ static inline u32 native_apic_msr_read(u32 reg)
 	if (reg == APIC_DFR)
 		return -1;
 
-	rdmsrq(APIC_BASE_MSR + (reg >> 4), msr);
+	msr = rdmsrq(APIC_BASE_MSR + (reg >> 4));
 	return (u32)msr;
 }
 
@@ -233,7 +233,7 @@ static inline u64 native_x2apic_icr_read(void)
 {
 	unsigned long val;
 
-	rdmsrq(APIC_BASE_MSR + (APIC_ICR >> 4), val);
+	val = rdmsrq(APIC_BASE_MSR + (APIC_ICR >> 4));
 	return val;
 }
 
diff --git a/arch/x86/include/asm/debugreg.h b/arch/x86/include/asm/debugreg.h
index 854d82b88ff4..60a3df32a4d3 100644
--- a/arch/x86/include/asm/debugreg.h
+++ b/arch/x86/include/asm/debugreg.h
@@ -180,7 +180,7 @@ static inline unsigned long get_debugctlmsr(void)
 	if (boot_cpu_data.x86 < 6)
 		return 0;
 #endif
-	rdmsrq(MSR_IA32_DEBUGCTLMSR, debugctlmsr);
+	debugctlmsr = rdmsrq(MSR_IA32_DEBUGCTLMSR);
 
 	return debugctlmsr;
 }
diff --git a/arch/x86/include/asm/fsgsbase.h b/arch/x86/include/asm/fsgsbase.h
index 70ff4ef457b1..9de49d732e9e 100644
--- a/arch/x86/include/asm/fsgsbase.h
+++ b/arch/x86/include/asm/fsgsbase.h
@@ -60,7 +60,7 @@ static inline unsigned long x86_fsbase_read_cpu(void)
 	if (boot_cpu_has(X86_FEATURE_FSGSBASE))
 		fsbase = rdfsbase();
 	else
-		rdmsrq(MSR_FS_BASE, fsbase);
+		fsbase = rdmsrq(MSR_FS_BASE);
 
 	return fsbase;
 }
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 6db5b5f79df9..2097602a00a0 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -2417,7 +2417,7 @@ static inline unsigned long read_msr(unsigned long msr)
 {
 	u64 value;
 
-	rdmsrq(msr, value);
+	value = rdmsrq(msr);
 	return value;
 }
 #endif
diff --git a/arch/x86/include/asm/msr.h b/arch/x86/include/asm/msr.h
index 6d7bab33af71..95761803c2e6 100644
--- a/arch/x86/include/asm/msr.h
+++ b/arch/x86/include/asm/msr.h
@@ -173,14 +173,13 @@ static inline u64 native_read_pmc(int counter)
 #include <asm/paravirt.h>
 #else
 #include <linux/errno.h>
-/*
- * Access to machine-specific registers (available on 586 and better only)
- * Note: the rd* operations modify the parameters directly (without using
- * pointer indirection), this allows gcc to optimize better
- */
 
-#define rdmsrq(msr, val)			\
-	((val) = native_read_msr((msr)))
+/* Access to machine-specific registers (available on 586 and better only) */
+
+static __always_inline u64 rdmsrq(u32 msr)
+{
+	return native_read_msr(msr);
+}
 
 static inline void wrmsrq(u32 msr, u64 val)
 {
@@ -237,7 +236,7 @@ int wrmsr_safe_regs_on_cpu(unsigned int cpu, u32 regs[8]);
 #else  /*  CONFIG_SMP  */
 static inline int rdmsrq_on_cpu(unsigned int cpu, u32 msr_no, u64 *q)
 {
-	rdmsrq(msr_no, *q);
+	*q = rdmsrq(msr_no);
 	return 0;
 }
 static inline int wrmsrq_on_cpu(unsigned int cpu, u32 msr_no, u64 q)
diff --git a/arch/x86/include/asm/paravirt.h b/arch/x86/include/asm/paravirt.h
index 93754aa60d5e..19442bc3af37 100644
--- a/arch/x86/include/asm/paravirt.h
+++ b/arch/x86/include/asm/paravirt.h
@@ -150,10 +150,10 @@ static inline int paravirt_write_msr_safe(u32 msr, u64 val)
 	return PVOP_CALL2(int, pv_ops, cpu.write_msr_safe, msr, val);
 }
 
-#define rdmsrq(msr, val)			\
-do {						\
-	val = paravirt_read_msr(msr);		\
-} while (0)
+static __always_inline u64 rdmsrq(u32 msr)
+{
+	return paravirt_read_msr(msr);
+}
 
 static inline void wrmsrq(u32 msr, u64 val)
 {
diff --git a/arch/x86/kernel/apic/apic.c b/arch/x86/kernel/apic/apic.c
index 90025451ace2..7a6c77e6a44e 100644
--- a/arch/x86/kernel/apic/apic.c
+++ b/arch/x86/kernel/apic/apic.c
@@ -1193,7 +1193,7 @@ void disable_local_APIC(void)
 	if (enabled_via_apicbase) {
 		struct msr val;
 
-		rdmsrq(MSR_IA32_APICBASE, val.q);
+		val.q = rdmsrq(MSR_IA32_APICBASE);
 		val.l &= ~MSR_IA32_APICBASE_ENABLE;
 		wrmsrq(MSR_IA32_APICBASE, val.q);
 	}
@@ -1710,7 +1710,7 @@ static bool x2apic_hw_locked(void)
 
 	x86_arch_cap_msr = x86_read_arch_cap_msr();
 	if (x86_arch_cap_msr & ARCH_CAP_XAPIC_DISABLE) {
-		rdmsrq(MSR_IA32_XAPIC_DISABLE_STATUS, msr);
+		msr = rdmsrq(MSR_IA32_XAPIC_DISABLE_STATUS);
 		return (msr & LEGACY_XAPIC_DISABLED);
 	}
 	return false;
@@ -1723,7 +1723,7 @@ static void __x2apic_disable(void)
 	if (!boot_cpu_has(X86_FEATURE_APIC))
 		return;
 
-	rdmsrq(MSR_IA32_APICBASE, msr);
+	msr = rdmsrq(MSR_IA32_APICBASE);
 	if (!(msr & X2APIC_ENABLE))
 		return;
 	/* Disable xapic and x2apic first and then reenable xapic mode */
@@ -1736,7 +1736,7 @@ static void __x2apic_enable(void)
 {
 	u64 msr;
 
-	rdmsrq(MSR_IA32_APICBASE, msr);
+	msr = rdmsrq(MSR_IA32_APICBASE);
 	if (msr & X2APIC_ENABLE)
 		return;
 	wrmsrq(MSR_IA32_APICBASE, msr | X2APIC_ENABLE);
@@ -1976,7 +1976,7 @@ static bool __init apic_verify(unsigned long addr)
 
 	/* The BIOS may have set up the APIC at some other address */
 	if (boot_cpu_data.x86 >= 6) {
-		rdmsrq(MSR_IA32_APICBASE, val.q);
+		val.q = rdmsrq(MSR_IA32_APICBASE);
 		if (val.l & MSR_IA32_APICBASE_ENABLE)
 			addr = val.l & MSR_IA32_APICBASE_BASE;
 	}
@@ -1999,7 +1999,7 @@ bool __init apic_force_enable(unsigned long addr)
 	 * and AMD K7 (Model > 1) or later.
 	 */
 	if (boot_cpu_data.x86 >= 6) {
-		rdmsrq(MSR_IA32_APICBASE, val.q);
+		val.q = rdmsrq(MSR_IA32_APICBASE);
 		if (!(val.l & MSR_IA32_APICBASE_ENABLE)) {
 			pr_info("Local APIC disabled by BIOS -- reenabling.\n");
 			val.l &= ~MSR_IA32_APICBASE_BASE;
@@ -2476,7 +2476,7 @@ static void lapic_resume(void *data)
 		 * SMP! We'll need to do this as part of the CPU restore!
 		 */
 		if (boot_cpu_data.x86 >= 6) {
-			rdmsrq(MSR_IA32_APICBASE, val.q);
+			val.q = rdmsrq(MSR_IA32_APICBASE);
 			val.l &= ~MSR_IA32_APICBASE_BASE;
 			val.l |= MSR_IA32_APICBASE_ENABLE | mp_lapic_addr;
 			wrmsrq(MSR_IA32_APICBASE, val.q);
diff --git a/arch/x86/kernel/apic/apic_numachip.c b/arch/x86/kernel/apic/apic_numachip.c
index a60c8960bbfd..3af93e3f2179 100644
--- a/arch/x86/kernel/apic/apic_numachip.c
+++ b/arch/x86/kernel/apic/apic_numachip.c
@@ -32,7 +32,7 @@ static u32 numachip1_get_apic_id(u32 x)
 	unsigned int id = (x >> 24) & 0xff;
 
 	if (cpu_feature_enabled(X86_FEATURE_NODEID_MSR)) {
-		rdmsrq(MSR_FAM10H_NODE_ID, value);
+		value = rdmsrq(MSR_FAM10H_NODE_ID);
 		id |= (value << 2) & 0xff00;
 	}
 
@@ -43,7 +43,7 @@ static u32 numachip2_get_apic_id(u32 x)
 {
 	u64 mcfg;
 
-	rdmsrq(MSR_FAM10H_MMIO_CONF_BASE, mcfg);
+	mcfg = rdmsrq(MSR_FAM10H_MMIO_CONF_BASE);
 	return ((mcfg >> (28 - 8)) & 0xfff00) | (x >> 24);
 }
 
@@ -151,7 +151,7 @@ static void fixup_cpu_id(struct cpuinfo_x86 *c, int node)
 
 	/* Account for nodes per socket in multi-core-module processors */
 	if (boot_cpu_has(X86_FEATURE_NODEID_MSR)) {
-		rdmsrq(MSR_FAM10H_NODE_ID, val);
+		val = rdmsrq(MSR_FAM10H_NODE_ID);
 		nodes = ((val >> 3) & 7) + 1;
 	}
 
diff --git a/arch/x86/kernel/cet.c b/arch/x86/kernel/cet.c
index 99444409c026..3efceee443b7 100644
--- a/arch/x86/kernel/cet.c
+++ b/arch/x86/kernel/cet.c
@@ -56,7 +56,7 @@ static void do_user_cp_fault(struct pt_regs *regs, unsigned long error_code)
 	 * will be whatever is live in userspace. So read the SSP before enabling
 	 * interrupts so locking the fpregs to do it later is not required.
 	 */
-	rdmsrq(MSR_IA32_PL3_SSP, ssp);
+	ssp = rdmsrq(MSR_IA32_PL3_SSP);
 
 	cond_local_irq_enable(regs);
 
diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c
index 54e14ed276b5..4bd43e899c64 100644
--- a/arch/x86/kernel/cpu/amd.c
+++ b/arch/x86/kernel/cpu/amd.c
@@ -160,7 +160,7 @@ static void init_amd_k6(struct cpuinfo_x86 *c)
 		if (mbytes > 508)
 			mbytes = 508;
 
-		rdmsrq(MSR_K6_WHCR, val.q);
+		val.q = rdmsrq(MSR_K6_WHCR);
 		if ((val.l & 0x0000FFFF) == 0) {
 			unsigned long flags;
 			val.l = (1 << 0) | ((mbytes / 4) << 1);
@@ -181,7 +181,7 @@ static void init_amd_k6(struct cpuinfo_x86 *c)
 		if (mbytes > 4092)
 			mbytes = 4092;
 
-		rdmsrq(MSR_K6_WHCR, val.q);
+		val.q = rdmsrq(MSR_K6_WHCR);
 		if ((val.l & 0xFFFF0000) == 0) {
 			unsigned long flags;
 			val.l = ((mbytes >> 2) << 22) | (1 << 16);
@@ -228,7 +228,7 @@ static void init_amd_k7(struct cpuinfo_x86 *c)
 	 * As per AMD technical note 27212 0.2
 	 */
 	if ((c->x86_model == 8 && c->x86_stepping >= 1) || (c->x86_model > 8)) {
-		rdmsrq(MSR_K7_CLK_CTL, val.q);
+		val.q = rdmsrq(MSR_K7_CLK_CTL);
 		if ((val.l & 0xfff00000) != 0x20000000) {
 			pr_info("CPU: CLK_CTL MSR was %x. Reprogramming to %x\n",
 				val.l, ((val.l & 0x000fffff) | 0x20000000));
@@ -429,7 +429,7 @@ static void bsp_init_amd(struct cpuinfo_x86 *c)
 		    (c->x86 == 0x10 && c->x86_model >= 0x2)) {
 			u64 val;
 
-			rdmsrq(MSR_K7_HWCR, val);
+			val = rdmsrq(MSR_K7_HWCR);
 			if (!(val & BIT(24)))
 				pr_warn(FW_BUG "TSC doesn't count with P0 frequency!\n");
 		}
@@ -583,7 +583,7 @@ static void early_detect_mem_encrypt(struct cpuinfo_x86 *c)
 	 */
 	if (cpu_has(c, X86_FEATURE_SME) || cpu_has(c, X86_FEATURE_SEV)) {
 		/* Check if memory encryption is enabled */
-		rdmsrq(MSR_AMD64_SYSCFG, msr);
+		msr = rdmsrq(MSR_AMD64_SYSCFG);
 		if (!(msr & MSR_AMD64_SYSCFG_MEM_ENCRYPT))
 			goto clear_all;
 
@@ -600,7 +600,7 @@ static void early_detect_mem_encrypt(struct cpuinfo_x86 *c)
 		if (!sme_me_mask)
 			setup_clear_cpu_cap(X86_FEATURE_SME);
 
-		rdmsrq(MSR_K7_HWCR, msr);
+		msr = rdmsrq(MSR_K7_HWCR);
 		if (!(msr & MSR_K7_HWCR_SMMLOCK))
 			goto clear_sev;
 
@@ -1111,7 +1111,7 @@ static void init_amd(struct cpuinfo_x86 *c)
 	init_amd_cacheinfo(c);
 
 	if (cpu_has(c, X86_FEATURE_SVM)) {
-		rdmsrq(MSR_VM_CR, vm_cr);
+		vm_cr = rdmsrq(MSR_VM_CR);
 		if (vm_cr & SVM_VM_CR_SVM_DIS_MASK) {
 			pr_notice_once("SVM disabled (by BIOS) in MSR_VM_CR\n");
 			clear_cpu_cap(c, X86_FEATURE_SVM);
diff --git a/arch/x86/kernel/cpu/aperfmperf.c b/arch/x86/kernel/cpu/aperfmperf.c
index 7ffc78d5ebf2..492dc9a11d6b 100644
--- a/arch/x86/kernel/cpu/aperfmperf.c
+++ b/arch/x86/kernel/cpu/aperfmperf.c
@@ -41,8 +41,8 @@ static void init_counter_refs(void *data)
 {
 	u64 aperf, mperf;
 
-	rdmsrq(MSR_IA32_APERF, aperf);
-	rdmsrq(MSR_IA32_MPERF, mperf);
+	aperf = rdmsrq(MSR_IA32_APERF);
+	mperf = rdmsrq(MSR_IA32_MPERF);
 
 	this_cpu_write(cpu_samples.aperf, aperf);
 	this_cpu_write(cpu_samples.mperf, mperf);
@@ -479,8 +479,8 @@ void arch_scale_freq_tick(void)
 	if (!cpu_feature_enabled(X86_FEATURE_APERFMPERF))
 		return;
 
-	rdmsrq(MSR_IA32_APERF, aperf);
-	rdmsrq(MSR_IA32_MPERF, mperf);
+	aperf = rdmsrq(MSR_IA32_APERF);
+	mperf = rdmsrq(MSR_IA32_MPERF);
 	acnt = aperf - s->aperf;
 	mcnt = mperf - s->mperf;
 
diff --git a/arch/x86/kernel/cpu/bugs.c b/arch/x86/kernel/cpu/bugs.c
index 56eac5611c31..aeb4770ba73c 100644
--- a/arch/x86/kernel/cpu/bugs.c
+++ b/arch/x86/kernel/cpu/bugs.c
@@ -761,7 +761,7 @@ void update_srbds_msr(void)
 	if (!boot_cpu_has(X86_FEATURE_SRBDS_CTRL))
 		return;
 
-	rdmsrq(MSR_IA32_MCU_OPT_CTRL, mcu_ctrl);
+	mcu_ctrl = rdmsrq(MSR_IA32_MCU_OPT_CTRL);
 
 	switch (srbds_mitigation) {
 	case SRBDS_MITIGATION_OFF:
@@ -897,7 +897,7 @@ void update_gds_msr(void)
 
 	switch (gds_mitigation) {
 	case GDS_MITIGATION_OFF:
-		rdmsrq(MSR_IA32_MCU_OPT_CTRL, mcu_ctrl);
+		mcu_ctrl = rdmsrq(MSR_IA32_MCU_OPT_CTRL);
 		mcu_ctrl |= GDS_MITG_DIS;
 		break;
 	case GDS_MITIGATION_FULL_LOCKED:
@@ -907,7 +907,7 @@ void update_gds_msr(void)
 		 * CPUs.
 		 */
 	case GDS_MITIGATION_FULL:
-		rdmsrq(MSR_IA32_MCU_OPT_CTRL, mcu_ctrl);
+		mcu_ctrl = rdmsrq(MSR_IA32_MCU_OPT_CTRL);
 		mcu_ctrl &= ~GDS_MITG_DIS;
 		break;
 	case GDS_MITIGATION_FORCE:
@@ -924,7 +924,7 @@ void update_gds_msr(void)
 	 * GDS_MITG_DIS will be ignored if this processor is locked but the boot
 	 * processor was not.
 	 */
-	rdmsrq(MSR_IA32_MCU_OPT_CTRL, mcu_ctrl_after);
+	mcu_ctrl_after = rdmsrq(MSR_IA32_MCU_OPT_CTRL);
 	WARN_ON_ONCE(mcu_ctrl != mcu_ctrl_after);
 }
 
@@ -959,7 +959,7 @@ static void __init gds_select_mitigation(void)
 	if (gds_mitigation == GDS_MITIGATION_FORCE)
 		gds_mitigation = GDS_MITIGATION_FULL;
 
-	rdmsrq(MSR_IA32_MCU_OPT_CTRL, mcu_ctrl);
+	mcu_ctrl = rdmsrq(MSR_IA32_MCU_OPT_CTRL);
 	if (mcu_ctrl & GDS_MITG_LOCKED) {
 		if (gds_mitigation == GDS_MITIGATION_OFF)
 			pr_warn("Mitigation locked. Disable failed.\n");
@@ -3274,7 +3274,7 @@ void __init cpu_select_mitigations(void)
 	 * init code as it is not enumerated and depends on the family.
 	 */
 	if (cpu_feature_enabled(X86_FEATURE_MSR_SPEC_CTRL)) {
-		rdmsrq(MSR_IA32_SPEC_CTRL, x86_spec_ctrl_base);
+		x86_spec_ctrl_base = rdmsrq(MSR_IA32_SPEC_CTRL);
 
 		/*
 		 * Previously running kernel (kexec), may have some controls
diff --git a/arch/x86/kernel/cpu/bus_lock.c b/arch/x86/kernel/cpu/bus_lock.c
index bba28607a59a..c9074c368146 100644
--- a/arch/x86/kernel/cpu/bus_lock.c
+++ b/arch/x86/kernel/cpu/bus_lock.c
@@ -105,7 +105,7 @@ static bool split_lock_verify_msr(bool on)
 		ctrl &= ~MSR_TEST_CTRL_SPLIT_LOCK_DETECT;
 	if (wrmsrq_safe(MSR_TEST_CTRL, ctrl))
 		return false;
-	rdmsrq(MSR_TEST_CTRL, tmp);
+	tmp = rdmsrq(MSR_TEST_CTRL);
 	return ctrl == tmp;
 }
 
@@ -145,7 +145,7 @@ static void __init __split_lock_setup(void)
 		return;
 	}
 
-	rdmsrq(MSR_TEST_CTRL, msr_test_ctrl_cache);
+	msr_test_ctrl_cache = rdmsrq(MSR_TEST_CTRL);
 
 	if (!split_lock_verify_msr(true)) {
 		pr_info("MSR access failed: Disabled\n");
@@ -305,7 +305,7 @@ void bus_lock_init(void)
 	if (!boot_cpu_has(X86_FEATURE_BUS_LOCK_DETECT))
 		return;
 
-	rdmsrq(MSR_IA32_DEBUGCTLMSR, val);
+	val = rdmsrq(MSR_IA32_DEBUGCTLMSR);
 
 	if ((boot_cpu_has(X86_FEATURE_SPLIT_LOCK_DETECT) &&
 	    (sld_state == sld_warn || sld_state == sld_fatal)) ||
@@ -383,7 +383,7 @@ static void __init split_lock_setup(struct cpuinfo_x86 *c)
 	 * MSR_IA32_CORE_CAPS_SPLIT_LOCK_DETECT is.  All CPUs that set
 	 * it have split lock detection.
 	 */
-	rdmsrq(MSR_IA32_CORE_CAPS, ia32_core_caps);
+	ia32_core_caps = rdmsrq(MSR_IA32_CORE_CAPS);
 	if (ia32_core_caps & MSR_IA32_CORE_CAPS_SPLIT_LOCK_DETECT)
 		goto supported;
 
diff --git a/arch/x86/kernel/cpu/centaur.c b/arch/x86/kernel/cpu/centaur.c
index 513fa1f640f9..6cd89b5ac9de 100644
--- a/arch/x86/kernel/cpu/centaur.c
+++ b/arch/x86/kernel/cpu/centaur.c
@@ -30,7 +30,7 @@ static void init_c3(struct cpuinfo_x86 *c)
 
 		/* enable ACE unit, if present and disabled */
 		if ((tmp & (ACE_PRESENT | ACE_ENABLED)) == ACE_PRESENT) {
-			rdmsrq(MSR_VIA_FCR, msr);
+			msr = rdmsrq(MSR_VIA_FCR);
 			/* enable ACE unit */
 			wrmsrq(MSR_VIA_FCR, msr | ACE_FCR);
 			pr_info("CPU: Enabled ACE h/w crypto\n");
@@ -38,7 +38,7 @@ static void init_c3(struct cpuinfo_x86 *c)
 
 		/* enable RNG unit, if present and disabled */
 		if ((tmp & (RNG_PRESENT | RNG_ENABLED)) == RNG_PRESENT) {
-			rdmsrq(MSR_VIA_RNG, msr);
+			msr = rdmsrq(MSR_VIA_RNG);
 			/* enable RNG unit */
 			wrmsrq(MSR_VIA_RNG, msr | RNG_ENABLE);
 			pr_info("CPU: Enabled h/w RNG\n");
@@ -52,7 +52,7 @@ static void init_c3(struct cpuinfo_x86 *c)
 #ifdef CONFIG_X86_32
 	/* Cyrix III family needs CX8 & PGE explicitly enabled. */
 	if (c->x86_model >= 6 && c->x86_model <= 13) {
-		rdmsrq(MSR_VIA_FCR, msr);
+		msr = rdmsrq(MSR_VIA_FCR);
 		wrmsrq(MSR_VIA_FCR, msr | (1 << 1 | 1 << 7));
 		set_cpu_cap(c, X86_FEATURE_CX8);
 	}
@@ -169,7 +169,7 @@ static void init_centaur(struct cpuinfo_x86 *c)
 			name = "??";
 		}
 
-		rdmsrq(MSR_IDT_FCR1, val.q);
+		val.q = rdmsrq(MSR_IDT_FCR1);
 		newlo = (val.l | fcr_set) & (~fcr_clr);
 
 		if (newlo != val.l) {
diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c
index c7352827f491..b2d9f8b14909 100644
--- a/arch/x86/kernel/cpu/common.c
+++ b/arch/x86/kernel/cpu/common.c
@@ -346,7 +346,7 @@ static void squash_the_stupid_serial_number(struct cpuinfo_x86 *c)
 
 	/* Disable processor serial number: */
 
-	rdmsrq(MSR_IA32_BBL_CR_CTL, val.q);
+	val.q = rdmsrq(MSR_IA32_BBL_CR_CTL);
 	val.l |= 0x200000;
 	wrmsrq(MSR_IA32_BBL_CR_CTL, val.q);
 
@@ -610,7 +610,7 @@ __noendbr u64 ibt_save(bool disable)
 	u64 msr = 0;
 
 	if (cpu_feature_enabled(X86_FEATURE_IBT)) {
-		rdmsrq(MSR_IA32_S_CET, msr);
+		msr = rdmsrq(MSR_IA32_S_CET);
 		if (disable)
 			wrmsrq(MSR_IA32_S_CET, msr & ~CET_ENDBR_EN);
 	}
@@ -623,7 +623,7 @@ __noendbr void ibt_restore(u64 save)
 	u64 msr;
 
 	if (cpu_feature_enabled(X86_FEATURE_IBT)) {
-		rdmsrq(MSR_IA32_S_CET, msr);
+		msr = rdmsrq(MSR_IA32_S_CET);
 		msr &= ~CET_ENDBR_EN;
 		msr |= (save & CET_ENDBR_EN);
 		wrmsrq(MSR_IA32_S_CET, msr);
@@ -1357,7 +1357,7 @@ u64 x86_read_arch_cap_msr(void)
 	u64 x86_arch_cap_msr = 0;
 
 	if (boot_cpu_has(X86_FEATURE_ARCH_CAPABILITIES))
-		rdmsrq(MSR_IA32_ARCH_CAPABILITIES, x86_arch_cap_msr);
+		x86_arch_cap_msr = rdmsrq(MSR_IA32_ARCH_CAPABILITIES);
 
 	return x86_arch_cap_msr;
 }
@@ -1923,10 +1923,10 @@ static bool detect_null_seg_behavior(void)
 	 */
 
 	unsigned long old_base, tmp;
-	rdmsrq(MSR_FS_BASE, old_base);
+	old_base = rdmsrq(MSR_FS_BASE);
 	wrmsrq(MSR_FS_BASE, 1);
 	loadsegment(fs, 0);
-	rdmsrq(MSR_FS_BASE, tmp);
+	tmp = rdmsrq(MSR_FS_BASE);
 	wrmsrq(MSR_FS_BASE, old_base);
 	return tmp == 0;
 }
diff --git a/arch/x86/kernel/cpu/feat_ctl.c b/arch/x86/kernel/cpu/feat_ctl.c
index 10a92927a515..7228fc2bb43e 100644
--- a/arch/x86/kernel/cpu/feat_ctl.c
+++ b/arch/x86/kernel/cpu/feat_ctl.c
@@ -40,7 +40,7 @@ static void init_vmx_capabilities(struct cpuinfo_x86 *c)
 	 * as they exist on any CPU that supports VMX, i.e. we want the WARN if
 	 * the RDMSR faults.
 	 */
-	rdmsrq(MSR_IA32_VMX_PROCBASED_CTLS, val.q);
+	val.q = rdmsrq(MSR_IA32_VMX_PROCBASED_CTLS);
 	supported = val.h;
 	c->vmx_capability[PRIMARY_CTLS] = supported;
 
@@ -53,7 +53,7 @@ static void init_vmx_capabilities(struct cpuinfo_x86 *c)
 	c->vmx_capability[TERTIARY_CTLS_LOW] = val.l;
 	c->vmx_capability[TERTIARY_CTLS_HIGH] = val.h;
 
-	rdmsrq(MSR_IA32_VMX_PINBASED_CTLS, val.q);
+	val.q = rdmsrq(MSR_IA32_VMX_PINBASED_CTLS);
 	supported = val.h;
 	rdmsrq_safe(MSR_IA32_VMX_VMFUNC, &val.q);
 	funcs = val.h;
diff --git a/arch/x86/kernel/cpu/hygon.c b/arch/x86/kernel/cpu/hygon.c
index ec51c2b9a257..44b462799cf6 100644
--- a/arch/x86/kernel/cpu/hygon.c
+++ b/arch/x86/kernel/cpu/hygon.c
@@ -99,7 +99,7 @@ static void bsp_init_hygon(struct cpuinfo_x86 *c)
 	if (cpu_has(c, X86_FEATURE_CONSTANT_TSC)) {
 		u64 val;
 
-		rdmsrq(MSR_K7_HWCR, val);
+		val = rdmsrq(MSR_K7_HWCR);
 		if (!(val & BIT(24)))
 			pr_warn(FW_BUG "TSC doesn't count with P0 frequency!\n");
 	}
@@ -194,7 +194,7 @@ static void init_hygon(struct cpuinfo_x86 *c)
 	init_hygon_cacheinfo(c);
 
 	if (cpu_has(c, X86_FEATURE_SVM)) {
-		rdmsrq(MSR_VM_CR, vm_cr);
+		vm_cr = rdmsrq(MSR_VM_CR);
 		if (vm_cr & SVM_VM_CR_SVM_DIS_MASK) {
 			pr_notice_once("SVM disabled (by BIOS) in MSR_VM_CR\n");
 			clear_cpu_cap(c, X86_FEATURE_SVM);
diff --git a/arch/x86/kernel/cpu/intel.c b/arch/x86/kernel/cpu/intel.c
index 4297ceb2cb24..2aabe9affa0c 100644
--- a/arch/x86/kernel/cpu/intel.c
+++ b/arch/x86/kernel/cpu/intel.c
@@ -159,7 +159,7 @@ static void detect_tme_early(struct cpuinfo_x86 *c)
 	u64 tme_activate;
 	int keyid_bits;
 
-	rdmsrq(MSR_IA32_TME_ACTIVATE, tme_activate);
+	tme_activate = rdmsrq(MSR_IA32_TME_ACTIVATE);
 
 	if (!TME_ACTIVATE_LOCKED(tme_activate) || !TME_ACTIVATE_ENABLED(tme_activate)) {
 		pr_info_once("x86/tme: not enabled by BIOS\n");
@@ -335,7 +335,7 @@ static void early_init_intel(struct cpuinfo_x86 *c)
 	 * string flag and enhanced fast string capabilities accordingly.
 	 */
 	if (c->x86_vfm >= INTEL_PENTIUM_M_DOTHAN) {
-		rdmsrq(MSR_IA32_MISC_ENABLE, misc_enable);
+		misc_enable = rdmsrq(MSR_IA32_MISC_ENABLE);
 		if (misc_enable & MSR_IA32_MISC_ENABLE_FAST_STRING) {
 			/* X86_FEATURE_ERMS is set based on CPUID */
 			set_cpu_cap(c, X86_FEATURE_REP_GOOD);
@@ -579,7 +579,7 @@ static void init_intel(struct cpuinfo_x86 *c)
 	if (boot_cpu_has(X86_FEATURE_DS)) {
 		u64 l;
 
-		rdmsrq(MSR_IA32_MISC_ENABLE, l);
+		l = rdmsrq(MSR_IA32_MISC_ENABLE);
 		if (!(l & MSR_IA32_MISC_ENABLE_BTS_UNAVAIL))
 			set_cpu_cap(c, X86_FEATURE_BTS);
 		if (!(l & MSR_IA32_MISC_ENABLE_PEBS_UNAVAIL))
diff --git a/arch/x86/kernel/cpu/intel_epb.c b/arch/x86/kernel/cpu/intel_epb.c
index 2c56f8730f59..a02d562253d9 100644
--- a/arch/x86/kernel/cpu/intel_epb.c
+++ b/arch/x86/kernel/cpu/intel_epb.c
@@ -79,7 +79,7 @@ static int intel_epb_save(void *data)
 {
 	u64 epb;
 
-	rdmsrq(MSR_IA32_ENERGY_PERF_BIAS, epb);
+	epb = rdmsrq(MSR_IA32_ENERGY_PERF_BIAS);
 	/*
 	 * Ensure that saved_epb will always be nonzero after this write even if
 	 * the EPB value read from the MSR is 0.
@@ -94,7 +94,7 @@ static void intel_epb_restore(void *data)
 	u64 val = this_cpu_read(saved_epb);
 	u64 epb;
 
-	rdmsrq(MSR_IA32_ENERGY_PERF_BIAS, epb);
+	epb = rdmsrq(MSR_IA32_ENERGY_PERF_BIAS);
 	if (val) {
 		val &= EPB_MASK;
 	} else {
diff --git a/arch/x86/kernel/cpu/mce/amd.c b/arch/x86/kernel/cpu/mce/amd.c
index f916fb4c5d13..febdd4fe9b3b 100644
--- a/arch/x86/kernel/cpu/mce/amd.c
+++ b/arch/x86/kernel/cpu/mce/amd.c
@@ -438,7 +438,7 @@ static void threshold_restart_block(void *_tr)
 	if (!this_cpu_read(threshold_banks) && !tr->set_lvt_off)
 		return;
 
-	rdmsrq(tr->b->address, val.q);
+	val.q = rdmsrq(tr->b->address);
 
 	/*
 	 * Reset error count and overflow bit.
@@ -658,7 +658,7 @@ static void disable_err_thresholding(struct cpuinfo_x86 *c, unsigned int bank)
 		return;
 	}
 
-	rdmsrq(MSR_K7_HWCR, hwcr);
+	hwcr = rdmsrq(MSR_K7_HWCR);
 
 	/* McStatusWrEn has to be set */
 	need_toggle = !(hwcr & BIT(18));
diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c
index ab469605fc89..df56ca06275b 100644
--- a/arch/x86/kernel/cpu/mce/core.c
+++ b/arch/x86/kernel/cpu/mce/core.c
@@ -1845,7 +1845,7 @@ static void __mcheck_cpu_cap_init(void)
 	u64 cap;
 	u8 b;
 
-	rdmsrq(MSR_IA32_MCG_CAP, cap);
+	cap = rdmsrq(MSR_IA32_MCG_CAP);
 
 	b = cap & MCG_BANKCNT_MASK;
 
@@ -1864,7 +1864,7 @@ static void __mcheck_cpu_init_generic(void)
 {
 	u64 cap;
 
-	rdmsrq(MSR_IA32_MCG_CAP, cap);
+	cap = rdmsrq(MSR_IA32_MCG_CAP);
 	if (cap & MCG_CTL_P)
 		wrmsrq(MSR_IA32_MCG_CTL, ~0ULL);
 }
@@ -1896,7 +1896,7 @@ static void __mcheck_cpu_init_prepare_banks(void)
 		wrmsrq(mca_msr_reg(i, MCA_CTL), b->ctl);
 		wrmsrq(mca_msr_reg(i, MCA_STATUS), 0);
 
-		rdmsrq(mca_msr_reg(i, MCA_CTL), msrval);
+		msrval = rdmsrq(mca_msr_reg(i, MCA_CTL));
 		b->init = !!msrval;
 	}
 }
@@ -2214,7 +2214,7 @@ void mca_bsp_init(struct cpuinfo_x86 *c)
 	if (mce_flags.smca)
 		smca_bsp_init();
 
-	rdmsrq(MSR_IA32_MCG_CAP, cap);
+	cap = rdmsrq(MSR_IA32_MCG_CAP);
 
 	/* Use accurate RIP reporting if available. */
 	if ((cap & MCG_EXT_P) && MCG_EXT_CNT(cap) >= 9)
diff --git a/arch/x86/kernel/cpu/mce/inject.c b/arch/x86/kernel/cpu/mce/inject.c
index 6f8a49d8baeb..aa933864f32e 100644
--- a/arch/x86/kernel/cpu/mce/inject.c
+++ b/arch/x86/kernel/cpu/mce/inject.c
@@ -743,7 +743,7 @@ static void check_hw_inj_possible(void)
 		u64 status = MCI_STATUS_VAL, ipid;
 
 		/* Check whether bank is populated */
-		rdmsrq(MSR_AMD64_SMCA_MCx_IPID(bank), ipid);
+		ipid = rdmsrq(MSR_AMD64_SMCA_MCx_IPID(bank));
 		if (!ipid)
 			continue;
 
diff --git a/arch/x86/kernel/cpu/mce/intel.c b/arch/x86/kernel/cpu/mce/intel.c
index 4655223ba560..2edb05d5e2d7 100644
--- a/arch/x86/kernel/cpu/mce/intel.c
+++ b/arch/x86/kernel/cpu/mce/intel.c
@@ -94,7 +94,7 @@ static bool cmci_supported(int *banks)
 	if (!boot_cpu_has(X86_FEATURE_APIC) || lapic_get_maxlvt() < 6)
 		return false;
 
-	rdmsrq(MSR_IA32_MCG_CAP, cap);
+	cap = rdmsrq(MSR_IA32_MCG_CAP);
 	*banks = min_t(unsigned, MAX_NR_BANKS, cap & MCG_BANKCNT_MASK);
 	return !!(cap & MCG_CMCI_P);
 }
@@ -106,7 +106,7 @@ static bool lmce_supported(void)
 	if (mca_cfg.lmce_disabled)
 		return false;
 
-	rdmsrq(MSR_IA32_MCG_CAP, tmp);
+	tmp = rdmsrq(MSR_IA32_MCG_CAP);
 
 	/*
 	 * LMCE depends on recovery support in the processor. Hence both
@@ -123,7 +123,7 @@ static bool lmce_supported(void)
 	 * WARN if the MSR isn't locked as init_ia32_feat_ctl() unconditionally
 	 * locks the MSR in the event that it wasn't already locked by BIOS.
 	 */
-	rdmsrq(MSR_IA32_FEAT_CTL, tmp);
+	tmp = rdmsrq(MSR_IA32_FEAT_CTL);
 	if (WARN_ON_ONCE(!(tmp & FEAT_CTL_LOCKED)))
 		return false;
 
@@ -141,7 +141,7 @@ static void cmci_set_threshold(int bank, int thresh)
 	u64 val;
 
 	raw_spin_lock_irqsave(&cmci_discover_lock, flags);
-	rdmsrq(MSR_IA32_MCx_CTL2(bank), val);
+	val = rdmsrq(MSR_IA32_MCx_CTL2(bank));
 	val &= ~MCI_CTL2_CMCI_THRESHOLD_MASK;
 	wrmsrq(MSR_IA32_MCx_CTL2(bank), val | thresh);
 	raw_spin_unlock_irqrestore(&cmci_discover_lock, flags);
@@ -184,7 +184,7 @@ static bool cmci_skip_bank(int bank, u64 *val)
 	if (test_bit(bank, mce_banks_ce_disabled))
 		return true;
 
-	rdmsrq(MSR_IA32_MCx_CTL2(bank), *val);
+	*val = rdmsrq(MSR_IA32_MCx_CTL2(bank));
 
 	/* Already owned by someone else? */
 	if (*val & MCI_CTL2_CMCI_EN) {
@@ -233,7 +233,7 @@ static void cmci_claim_bank(int bank, u64 val, int bios_zero_thresh, int *bios_w
 
 	val |= MCI_CTL2_CMCI_EN;
 	wrmsrq(MSR_IA32_MCx_CTL2(bank), val);
-	rdmsrq(MSR_IA32_MCx_CTL2(bank), val);
+	val = rdmsrq(MSR_IA32_MCx_CTL2(bank));
 
 	/* If the enable bit did not stick, this bank should be polled. */
 	if (!(val & MCI_CTL2_CMCI_EN)) {
@@ -324,7 +324,7 @@ static void __cmci_disable_bank(int bank)
 
 	if (!test_bit(bank, this_cpu_ptr(mce_banks_owned)))
 		return;
-	rdmsrq(MSR_IA32_MCx_CTL2(bank), val);
+	val = rdmsrq(MSR_IA32_MCx_CTL2(bank));
 	val &= ~MCI_CTL2_CMCI_EN;
 	wrmsrq(MSR_IA32_MCx_CTL2(bank), val);
 	__clear_bit(bank, this_cpu_ptr(mce_banks_owned));
@@ -430,7 +430,7 @@ void intel_init_lmce(void)
 	if (!lmce_supported())
 		return;
 
-	rdmsrq(MSR_IA32_MCG_EXT_CTL, val);
+	val = rdmsrq(MSR_IA32_MCG_EXT_CTL);
 
 	if (!(val & MCG_EXT_CTL_LMCE_EN))
 		wrmsrq(MSR_IA32_MCG_EXT_CTL, val | MCG_EXT_CTL_LMCE_EN);
@@ -443,7 +443,7 @@ void intel_clear_lmce(void)
 	if (!lmce_supported())
 		return;
 
-	rdmsrq(MSR_IA32_MCG_EXT_CTL, val);
+	val = rdmsrq(MSR_IA32_MCG_EXT_CTL);
 	val &= ~MCG_EXT_CTL_LMCE_EN;
 	wrmsrq(MSR_IA32_MCG_EXT_CTL, val);
 }
diff --git a/arch/x86/kernel/cpu/mce/p5.c b/arch/x86/kernel/cpu/mce/p5.c
index 3c2b6cc918b1..b441c720f9b7 100644
--- a/arch/x86/kernel/cpu/mce/p5.c
+++ b/arch/x86/kernel/cpu/mce/p5.c
@@ -26,8 +26,8 @@ noinstr void pentium_machine_check(struct pt_regs *regs)
 	u64 addr, type;
 
 	instrumentation_begin();
-	rdmsrq(MSR_IA32_P5_MC_ADDR, addr);
-	rdmsrq(MSR_IA32_P5_MC_TYPE, type);
+	addr = rdmsrq(MSR_IA32_P5_MC_ADDR);
+	type = rdmsrq(MSR_IA32_P5_MC_TYPE);
 
 	pr_emerg("CPU#%d: Machine Check Exception:  0x%8X (type 0x%8X).\n",
 		 smp_processor_id(), (u32)addr, (u32)type);
@@ -55,8 +55,8 @@ void intel_p5_mcheck_init(struct cpuinfo_x86 *c)
 		return;
 
 	/* Read registers before enabling: */
-	rdmsrq(MSR_IA32_P5_MC_ADDR, q);
-	rdmsrq(MSR_IA32_P5_MC_TYPE, q);
+	q = rdmsrq(MSR_IA32_P5_MC_ADDR);
+	q = rdmsrq(MSR_IA32_P5_MC_TYPE);
 	pr_info("Intel old style machine check architecture supported.\n");
 
 	/* Enable MCE: */
diff --git a/arch/x86/kernel/cpu/mce/winchip.c b/arch/x86/kernel/cpu/mce/winchip.c
index 7040243533d9..7b967f2abd8f 100644
--- a/arch/x86/kernel/cpu/mce/winchip.c
+++ b/arch/x86/kernel/cpu/mce/winchip.c
@@ -30,7 +30,7 @@ void winchip_mcheck_init(struct cpuinfo_x86 *c)
 {
 	struct msr val;
 
-	rdmsrq(MSR_IDT_FCR1, val.q);
+	val.q = rdmsrq(MSR_IDT_FCR1);
 	val.l |= (1<<2);	/* Enable EIERRINT (int 18 MCE) */
 	val.l &= ~(1<<4);	/* Enable MCE */
 	wrmsrq(MSR_IDT_FCR1, val.q);
diff --git a/arch/x86/kernel/cpu/microcode/intel.c b/arch/x86/kernel/cpu/microcode/intel.c
index 1142183c950c..4d199abd8fd8 100644
--- a/arch/x86/kernel/cpu/microcode/intel.c
+++ b/arch/x86/kernel/cpu/microcode/intel.c
@@ -991,7 +991,7 @@ static __init bool staging_available(void)
 	if (!(val & ARCH_CAP_MCU_ENUM))
 		return false;
 
-	rdmsrq(MSR_IA32_MCU_ENUMERATION, val);
+	val = rdmsrq(MSR_IA32_MCU_ENUMERATION);
 	return !!(val & MCU_STAGING);
 }
 
diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c
index 185d4f677ec0..65ad235ef5c6 100644
--- a/arch/x86/kernel/cpu/mshyperv.c
+++ b/arch/x86/kernel/cpu/mshyperv.c
@@ -74,7 +74,7 @@ u64 hv_get_non_nested_msr(unsigned int reg)
 	if (hv_is_synic_msr(reg) && ms_hyperv.paravisor_present)
 		hv_ivm_msr_read(reg, &value);
 	else
-		rdmsrq(reg, value);
+		value = rdmsrq(reg);
 	return value;
 }
 EXPORT_SYMBOL_GPL(hv_get_non_nested_msr);
@@ -399,7 +399,7 @@ static unsigned long hv_get_tsc_khz(void)
 {
 	unsigned long freq;
 
-	rdmsrq(HV_X64_MSR_TSC_FREQUENCY, freq);
+	freq = rdmsrq(HV_X64_MSR_TSC_FREQUENCY);
 
 	return freq / 1000;
 }
@@ -645,7 +645,7 @@ static void __init ms_hyperv_init_platform(void)
 		 */
 		u64	hv_lapic_frequency;
 
-		rdmsrq(HV_X64_MSR_APIC_FREQUENCY, hv_lapic_frequency);
+		hv_lapic_frequency = rdmsrq(HV_X64_MSR_APIC_FREQUENCY);
 		hv_lapic_frequency = div_u64(hv_lapic_frequency, HZ);
 		lapic_timer_period = hv_lapic_frequency;
 		pr_info("Hyper-V: LAPIC Timer Frequency: %#x\n",
diff --git a/arch/x86/kernel/cpu/mtrr/amd.c b/arch/x86/kernel/cpu/mtrr/amd.c
index a73715d6f05c..652e4d64ff4a 100644
--- a/arch/x86/kernel/cpu/mtrr/amd.c
+++ b/arch/x86/kernel/cpu/mtrr/amd.c
@@ -13,7 +13,7 @@ amd_get_mtrr(unsigned int reg, unsigned long *base,
 	unsigned long val;
 	struct msr msr;
 
-	rdmsrq(MSR_K6_UWCCR, msr.q);
+	msr.q = rdmsrq(MSR_K6_UWCCR);
 	/* Upper dword is region 1, lower is region 0 */
 	if (reg == 1)
 		val = msr.h;
@@ -68,7 +68,7 @@ amd_set_mtrr(unsigned int reg, unsigned long base, unsigned long size, mtrr_type
 	/*
 	 * Low is MTRR0, High MTRR 1
 	 */
-	rdmsrq(MSR_K6_UWCCR, msr.q);
+	msr.q = rdmsrq(MSR_K6_UWCCR);
 	regs[0] = msr.l;
 	regs[1] = msr.h;
 
diff --git a/arch/x86/kernel/cpu/mtrr/cleanup.c b/arch/x86/kernel/cpu/mtrr/cleanup.c
index cd1a6dec4064..2b72c784db9e 100644
--- a/arch/x86/kernel/cpu/mtrr/cleanup.c
+++ b/arch/x86/kernel/cpu/mtrr/cleanup.c
@@ -670,7 +670,7 @@ int __init mtrr_cleanup(void)
 	if (!cpu_feature_enabled(X86_FEATURE_MTRR) || enable_mtrr_cleanup < 1)
 		return 0;
 
-	rdmsrq(MSR_MTRRdefType, def);
+	def = rdmsrq(MSR_MTRRdefType);
 	def &= 0xff;
 	if (def != MTRR_TYPE_UNCACHABLE)
 		return 0;
@@ -870,7 +870,7 @@ int __init mtrr_trim_uncached_memory(unsigned long end_pfn)
 	if (!cpu_feature_enabled(X86_FEATURE_MTRR) || disable_mtrr_trim)
 		return 0;
 
-	rdmsrq(MSR_MTRRdefType, def);
+	def = rdmsrq(MSR_MTRRdefType);
 	def &= MTRR_DEF_TYPE_TYPE;
 	if (def != MTRR_TYPE_UNCACHABLE)
 		return 0;
diff --git a/arch/x86/kernel/cpu/mtrr/generic.c b/arch/x86/kernel/cpu/mtrr/generic.c
index 67cf69f24b00..4d8034a7fdc8 100644
--- a/arch/x86/kernel/cpu/mtrr/generic.c
+++ b/arch/x86/kernel/cpu/mtrr/generic.c
@@ -112,7 +112,7 @@ static inline void k8_check_syscfg_dram_mod_en(void)
 	if (cc_platform_has(CC_ATTR_HOST_SEV_SNP))
 		return;
 
-	rdmsrq(MSR_AMD64_SYSCFG, val.q);
+	val.q = rdmsrq(MSR_AMD64_SYSCFG);
 	if (val.l & K8_MTRRFIXRANGE_DRAM_MODIFY) {
 		pr_err(FW_WARN "MTRR: CPU %u: SYSCFG[MtrrFixDramModEn]"
 		       " not cleared by BIOS, clearing this bit\n",
@@ -559,10 +559,10 @@ get_mtrr_var_range(unsigned int index, struct mtrr_var_range *vr)
 {
 	struct msr val;
 
-	rdmsrq(MTRRphysBase_MSR(index), val.q);
+	val.q = rdmsrq(MTRRphysBase_MSR(index));
 	vr->base_lo = val.l;
 	vr->base_hi = val.h;
-	rdmsrq(MTRRphysMask_MSR(index), val.q);
+	val.q = rdmsrq(MTRRphysMask_MSR(index));
 	vr->mask_lo = val.l;
 	vr->mask_hi = val.h;
 }
@@ -588,12 +588,12 @@ static void get_fixed_ranges(mtrr_type *frs)
 
 	k8_check_syscfg_dram_mod_en();
 
-	rdmsrq(MSR_MTRRfix64K_00000, p[0]);
+	p[0] = rdmsrq(MSR_MTRRfix64K_00000);
 
 	for (i = 0; i < 2; i++)
-		rdmsrq(MSR_MTRRfix16K_80000 + i, p[1 + i]);
+		p[1 + i] = rdmsrq(MSR_MTRRfix16K_80000 + i);
 	for (i = 0; i < 8; i++)
-		rdmsrq(MSR_MTRRfix4K_C0000 + i, p[3 + i]);
+		p[3 + i] = rdmsrq(MSR_MTRRfix4K_C0000 + i);
 }
 
 void mtrr_save_fixed_ranges(void *info)
@@ -700,7 +700,7 @@ bool __init get_mtrr_state(void)
 
 	vrs = mtrr_state.var_ranges;
 
-	rdmsrq(MSR_MTRRcap, q);
+	q = rdmsrq(MSR_MTRRcap);
 	mtrr_state.have_fixed = q & MTRR_CAP_FIX;
 
 	for (i = 0; i < num_var_ranges; i++)
@@ -708,13 +708,13 @@ bool __init get_mtrr_state(void)
 	if (mtrr_state.have_fixed)
 		get_fixed_ranges(mtrr_state.fixed_ranges);
 
-	rdmsrq(MSR_MTRRdefType, q);
+	q = rdmsrq(MSR_MTRRdefType);
 	mtrr_state.def_type = q & MTRR_DEF_TYPE_TYPE;
 	mtrr_state.enabled = (q & MTRR_DEF_TYPE_ENABLE) >> MTRR_STATE_SHIFT;
 
 	if (amd_special_default_mtrr()) {
 		/* TOP_MEM2 */
-		rdmsrq(MSR_K8_TOP_MEM2, mtrr_tom2);
+		mtrr_tom2 = rdmsrq(MSR_K8_TOP_MEM2);
 		mtrr_tom2 &= 0xffffff800000ULL;
 	}
 
@@ -770,7 +770,7 @@ static void set_fixed_range(int msr, bool *changed, unsigned int *msrwords)
 {
 	struct msr val;
 
-	rdmsrq(msr, val.q);
+	val.q = rdmsrq(msr);
 
 	if (val.l != msrwords[0] || val.h != msrwords[1]) {
 		mtrr_wrmsr(msr, msrwords[0], msrwords[1]);
@@ -818,7 +818,7 @@ static void generic_get_mtrr(unsigned int reg, unsigned long *base,
 	 */
 	get_cpu();
 
-	rdmsrq(MTRRphysMask_MSR(reg), mask);
+	mask = rdmsrq(MTRRphysMask_MSR(reg));
 
 	if (!(mask & MTRR_PHYSMASK_V)) {
 		/*  Invalid (i.e. free) range */
@@ -828,7 +828,7 @@ static void generic_get_mtrr(unsigned int reg, unsigned long *base,
 		goto out_put_cpu;
 	}
 
-	rdmsrq(MTRRphysBase_MSR(reg), base_msr);
+	base_msr = rdmsrq(MTRRphysBase_MSR(reg));
 
 	/* Work out the shifted address mask: */
 	tmp = mask & PAGE_MASK;
@@ -889,7 +889,7 @@ static bool set_mtrr_var_ranges(unsigned int index, struct mtrr_var_range *vr)
 	bool changed = false;
 	struct msr val;
 
-	rdmsrq(MTRRphysBase_MSR(index), val.q);
+	val.q = rdmsrq(MTRRphysBase_MSR(index));
 	if ((vr->base_lo & ~MTRR_PHYSBASE_RSVD) != (val.l & ~MTRR_PHYSBASE_RSVD)
 	    || (vr->base_hi & ~phys_hi_rsvd) != (val.h & ~phys_hi_rsvd)) {
 
@@ -897,7 +897,7 @@ static bool set_mtrr_var_ranges(unsigned int index, struct mtrr_var_range *vr)
 		changed = true;
 	}
 
-	rdmsrq(MTRRphysMask_MSR(index), val.q);
+	val.q = rdmsrq(MTRRphysMask_MSR(index));
 
 	if ((vr->mask_lo & ~MTRR_PHYSMASK_RSVD) != (val.l & ~MTRR_PHYSMASK_RSVD)
 	    || (vr->mask_hi & ~phys_hi_rsvd) != (val.h & ~phys_hi_rsvd)) {
@@ -952,7 +952,7 @@ void mtrr_disable(void)
 	struct msr val;
 
 	/* Save MTRR state */
-	rdmsrq(MSR_MTRRdefType, val.q);
+	val.q = rdmsrq(MSR_MTRRdefType);
 	deftype_lo = val.l;
 	deftype_hi = val.h;
 
@@ -1065,7 +1065,7 @@ static int generic_have_wrcomb(void)
 {
 	u64 config;
 
-	rdmsrq(MSR_MTRRcap, config);
+	config = rdmsrq(MSR_MTRRcap);
 	return config & MTRR_CAP_WC;
 }
 
diff --git a/arch/x86/kernel/cpu/mtrr/mtrr.c b/arch/x86/kernel/cpu/mtrr/mtrr.c
index 468c53b20acf..9b7abd58b677 100644
--- a/arch/x86/kernel/cpu/mtrr/mtrr.c
+++ b/arch/x86/kernel/cpu/mtrr/mtrr.c
@@ -571,7 +571,7 @@ void __init mtrr_bp_init(void)
 	if (mtrr_enabled()) {
 		/* Get the number of variable MTRR ranges. */
 		if (mtrr_if == &generic_mtrr_ops)
-			rdmsrq(MSR_MTRRcap, config);
+			config = rdmsrq(MSR_MTRRcap);
 		else
 			config = mtrr_if->var_regs;
 		num_var_ranges = config & MTRR_CAP_VCNT;
diff --git a/arch/x86/kernel/cpu/resctrl/core.c b/arch/x86/kernel/cpu/resctrl/core.c
index 55214d6fdc49..dd0e20179fce 100644
--- a/arch/x86/kernel/cpu/resctrl/core.c
+++ b/arch/x86/kernel/cpu/resctrl/core.c
@@ -165,7 +165,7 @@ static inline void cache_alloc_hsw_probe(void)
 	if (wrmsrq_safe(MSR_IA32_L3_CBM_BASE, max_cbm))
 		return;
 
-	rdmsrq(MSR_IA32_L3_CBM_BASE, l3_cbm_0);
+	l3_cbm_0 = rdmsrq(MSR_IA32_L3_CBM_BASE);
 
 	/* If all the bits were set in MSR, return success */
 	if (l3_cbm_0 != max_cbm)
diff --git a/arch/x86/kernel/cpu/resctrl/monitor.c b/arch/x86/kernel/cpu/resctrl/monitor.c
index 3838e0a13d36..530c425d7ee4 100644
--- a/arch/x86/kernel/cpu/resctrl/monitor.c
+++ b/arch/x86/kernel/cpu/resctrl/monitor.c
@@ -147,7 +147,7 @@ static int __rmid_read_phys(u32 prmid, enum resctrl_event_id eventid, u64 *val)
 	 * are error bits.
 	 */
 	wrmsrq(MSR_IA32_QM_EVTSEL, msr_val.q);
-	rdmsrq(MSR_IA32_QM_CTR, msr_val.q);
+	msr_val.q = rdmsrq(MSR_IA32_QM_CTR);
 
 	if (msr_val.q & RMID_VAL_ERROR)
 		return -EIO;
@@ -310,7 +310,7 @@ static int __cntr_id_read(u32 cntr_id, u64 *val)
 	 * is set if the counter data is unavailable.
 	 */
 	wrmsrq(MSR_IA32_QM_EVTSEL, msr_val.q);
-	rdmsrq(MSR_IA32_QM_CTR, msr_val.q);
+	msr_val.q = rdmsrq(MSR_IA32_QM_CTR);
 
 	if (msr_val.q & RMID_VAL_ERROR)
 		return -EIO;
diff --git a/arch/x86/kernel/cpu/resctrl/pseudo_lock.c b/arch/x86/kernel/cpu/resctrl/pseudo_lock.c
index d7caab0409b6..0408ac7f66fd 100644
--- a/arch/x86/kernel/cpu/resctrl/pseudo_lock.c
+++ b/arch/x86/kernel/cpu/resctrl/pseudo_lock.c
@@ -250,7 +250,7 @@ int resctrl_arch_measure_cycles_lat_fn(void *_plr)
 	/*
 	 * Disable hardware prefetchers.
 	 */
-	rdmsrq(MSR_MISC_FEATURE_CONTROL, saved);
+	saved = rdmsrq(MSR_MISC_FEATURE_CONTROL);
 	wrmsrq(MSR_MISC_FEATURE_CONTROL, prefetch_disable_bits);
 	mem_r = READ_ONCE(plr->kmem);
 	/*
@@ -346,7 +346,7 @@ static int measure_residency_fn(struct perf_event_attr *miss_attr,
 	/*
 	 * Disable hardware prefetchers.
 	 */
-	rdmsrq(MSR_MISC_FEATURE_CONTROL, saved);
+	saved = rdmsrq(MSR_MISC_FEATURE_CONTROL);
 	wrmsrq(MSR_MISC_FEATURE_CONTROL, prefetch_disable_bits);
 
 	/* Initialize rest of local variables */
diff --git a/arch/x86/kernel/cpu/resctrl/rdtgroup.c b/arch/x86/kernel/cpu/resctrl/rdtgroup.c
index 5ffa39fa86fa..37f0b1f7fe4f 100644
--- a/arch/x86/kernel/cpu/resctrl/rdtgroup.c
+++ b/arch/x86/kernel/cpu/resctrl/rdtgroup.c
@@ -96,7 +96,7 @@ void resctrl_arch_mon_event_config_read(void *_config_info)
 		pr_warn_once("Invalid event id %d\n", config_info->evtid);
 		return;
 	}
-	rdmsrq(MSR_IA32_EVT_CFG_BASE + index, msrval);
+	msrval = rdmsrq(MSR_IA32_EVT_CFG_BASE + index);
 
 	/* Report only the valid event configuration bits */
 	config_info->mon_config = msrval & MAX_EVT_CONFIG_BITS;
diff --git a/arch/x86/kernel/cpu/topology.c b/arch/x86/kernel/cpu/topology.c
index 4913b64ec592..68337da5aa09 100644
--- a/arch/x86/kernel/cpu/topology.c
+++ b/arch/x86/kernel/cpu/topology.c
@@ -151,7 +151,7 @@ static __init bool check_for_real_bsp(u32 apic_id)
 	 * kernel must rely on the firmware enumeration order.
 	 */
 	if (has_apic_base) {
-		rdmsrq(MSR_IA32_APICBASE, msr);
+		msr = rdmsrq(MSR_IA32_APICBASE);
 		is_bsp = !!(msr & MSR_IA32_APICBASE_BSP);
 	}
 
diff --git a/arch/x86/kernel/cpu/topology_amd.c b/arch/x86/kernel/cpu/topology_amd.c
index c5a6944df86a..5192f13f3686 100644
--- a/arch/x86/kernel/cpu/topology_amd.c
+++ b/arch/x86/kernel/cpu/topology_amd.c
@@ -140,7 +140,7 @@ static void parse_fam10h_node_id(struct topo_scan *tscan)
 	if (!boot_cpu_has(X86_FEATURE_NODEID_MSR))
 		return;
 
-	rdmsrq(MSR_FAM10H_NODE_ID, nid.msr);
+	nid.msr = rdmsrq(MSR_FAM10H_NODE_ID);
 	store_node(tscan, nid.nodes_per_pkg + 1, nid.node_id);
 	tscan->c->topo.llc_id = nid.node_id;
 }
@@ -168,7 +168,7 @@ static void topoext_fixup(struct topo_scan *tscan)
 			MSR_AMD64_CPUID_EXT_FEAT_TOPOEXT_BIT) <= 0)
 		return;
 
-	rdmsrq(MSR_AMD64_CPUID_EXT_FEAT, msrval);
+	msrval = rdmsrq(MSR_AMD64_CPUID_EXT_FEAT);
 	if (msrval & MSR_AMD64_CPUID_EXT_FEAT_TOPOEXT) {
 		set_cpu_cap(c, X86_FEATURE_TOPOEXT);
 		pr_info_once(FW_INFO "CPU: Re-enabling disabled Topology Extensions Support.\n");
diff --git a/arch/x86/kernel/cpu/transmeta.c b/arch/x86/kernel/cpu/transmeta.c
index 35d498f24e34..f8632becda18 100644
--- a/arch/x86/kernel/cpu/transmeta.c
+++ b/arch/x86/kernel/cpu/transmeta.c
@@ -87,7 +87,7 @@ static void init_transmeta(struct cpuinfo_x86 *c)
 	}
 
 	/* Unhide possibly hidden capability flags */
-	rdmsrq(0x80860004, msr);
+	msr = rdmsrq(0x80860004);
 	wrmsrq(0x80860004, msr | ~0U);
 	cpuid_refresh_leaf(c, 0x1);
 	c->x86_capability[CPUID_1_EDX] = cpuid_edx(0x00000001);
diff --git a/arch/x86/kernel/cpu/tsx.c b/arch/x86/kernel/cpu/tsx.c
index 209b5a22d880..b500845d2049 100644
--- a/arch/x86/kernel/cpu/tsx.c
+++ b/arch/x86/kernel/cpu/tsx.c
@@ -35,7 +35,7 @@ static void tsx_disable(void)
 {
 	u64 tsx;
 
-	rdmsrq(MSR_IA32_TSX_CTRL, tsx);
+	tsx = rdmsrq(MSR_IA32_TSX_CTRL);
 
 	/* Force all transactions to immediately abort */
 	tsx |= TSX_CTRL_RTM_DISABLE;
@@ -55,7 +55,7 @@ static void tsx_enable(void)
 {
 	u64 tsx;
 
-	rdmsrq(MSR_IA32_TSX_CTRL, tsx);
+	tsx = rdmsrq(MSR_IA32_TSX_CTRL);
 
 	/* Enable the RTM feature in the cpu */
 	tsx &= ~TSX_CTRL_RTM_DISABLE;
@@ -126,11 +126,11 @@ static void tsx_clear_cpuid(void)
 	 */
 	if (boot_cpu_has(X86_FEATURE_RTM_ALWAYS_ABORT) &&
 	    boot_cpu_has(X86_FEATURE_TSX_FORCE_ABORT)) {
-		rdmsrq(MSR_TSX_FORCE_ABORT, msr);
+		msr = rdmsrq(MSR_TSX_FORCE_ABORT);
 		msr |= MSR_TFA_TSX_CPUID_CLEAR;
 		wrmsrq(MSR_TSX_FORCE_ABORT, msr);
 	} else if (cpu_feature_enabled(X86_FEATURE_MSR_TSX_CTRL)) {
-		rdmsrq(MSR_IA32_TSX_CTRL, msr);
+		msr = rdmsrq(MSR_IA32_TSX_CTRL);
 		msr |= TSX_CTRL_CPUID_CLEAR;
 		wrmsrq(MSR_IA32_TSX_CTRL, msr);
 	}
@@ -157,7 +157,7 @@ static void tsx_dev_mode_disable(void)
 	    !cpu_feature_enabled(X86_FEATURE_SRBDS_CTRL))
 		return;
 
-	rdmsrq(MSR_IA32_MCU_OPT_CTRL, mcu_opt_ctrl);
+	mcu_opt_ctrl = rdmsrq(MSR_IA32_MCU_OPT_CTRL);
 
 	if (mcu_opt_ctrl & RTM_ALLOW) {
 		mcu_opt_ctrl &= ~RTM_ALLOW;
diff --git a/arch/x86/kernel/cpu/umwait.c b/arch/x86/kernel/cpu/umwait.c
index e4a31c536642..8c3cf0f95e9a 100644
--- a/arch/x86/kernel/cpu/umwait.c
+++ b/arch/x86/kernel/cpu/umwait.c
@@ -218,7 +218,7 @@ static int __init umwait_init(void)
 	 * changed. This is the only place where orig_umwait_control_cached
 	 * is modified.
 	 */
-	rdmsrq(MSR_IA32_UMWAIT_CONTROL, orig_umwait_control_cached);
+	orig_umwait_control_cached = rdmsrq(MSR_IA32_UMWAIT_CONTROL);
 
 	ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "umwait:online",
 				umwait_cpu_online, umwait_cpu_offline);
diff --git a/arch/x86/kernel/cpu/zhaoxin.c b/arch/x86/kernel/cpu/zhaoxin.c
index fe504fd43c77..f89aec38a349 100644
--- a/arch/x86/kernel/cpu/zhaoxin.c
+++ b/arch/x86/kernel/cpu/zhaoxin.c
@@ -29,7 +29,7 @@ static void init_zhaoxin_cap(struct cpuinfo_x86 *c)
 
 		/* Enable ACE unit, if present and disabled */
 		if ((tmp & (ACE_PRESENT | ACE_ENABLED)) == ACE_PRESENT) {
-			rdmsrq(MSR_ZHAOXIN_FCR57, msr);
+			msr = rdmsrq(MSR_ZHAOXIN_FCR57);
 			/* Enable ACE unit */
 			wrmsrq(MSR_ZHAOXIN_FCR57, msr | ACE_FCR);
 			pr_info("CPU: Enabled ACE h/w crypto\n");
@@ -37,7 +37,7 @@ static void init_zhaoxin_cap(struct cpuinfo_x86 *c)
 
 		/* Enable RNG unit, if present and disabled */
 		if ((tmp & (RNG_PRESENT | RNG_ENABLED)) == RNG_PRESENT) {
-			rdmsrq(MSR_ZHAOXIN_FCR57, msr);
+			msr = rdmsrq(MSR_ZHAOXIN_FCR57);
 			/* Enable RNG unit */
 			wrmsrq(MSR_ZHAOXIN_FCR57, msr | RNG_ENABLE);
 			pr_info("CPU: Enabled h/w RNG\n");
diff --git a/arch/x86/kernel/fpu/core.c b/arch/x86/kernel/fpu/core.c
index d1aeecd57f5e..7d2cff9633cf 100644
--- a/arch/x86/kernel/fpu/core.c
+++ b/arch/x86/kernel/fpu/core.c
@@ -364,7 +364,7 @@ void fpu_sync_guest_vmexit_xfd_state(void)
 
 	lockdep_assert_irqs_disabled();
 	if (fpu_state_size_dynamic()) {
-		rdmsrq(MSR_IA32_XFD, fpstate->xfd);
+		fpstate->xfd = rdmsrq(MSR_IA32_XFD);
 		__this_cpu_write(xfd_state, fpstate->xfd);
 	}
 }
diff --git a/arch/x86/kernel/hpet.c b/arch/x86/kernel/hpet.c
index 8dc7b710e125..8a27b9ae9fc7 100644
--- a/arch/x86/kernel/hpet.c
+++ b/arch/x86/kernel/hpet.c
@@ -971,7 +971,7 @@ static bool __init hpet_is_pc10_damaged(void)
 		return false;
 
 	/* Check whether PC10 is enabled in PKG C-state limit */
-	rdmsrq(MSR_PKG_CST_CONFIG_CONTROL, pcfg);
+	pcfg = rdmsrq(MSR_PKG_CST_CONFIG_CONTROL);
 	if ((pcfg & 0xF) < 8)
 		return false;
 
diff --git a/arch/x86/kernel/kvm.c b/arch/x86/kernel/kvm.c
index 6b0a5861ccb8..8710e3fab693 100644
--- a/arch/x86/kernel/kvm.c
+++ b/arch/x86/kernel/kvm.c
@@ -740,7 +740,7 @@ static int kvm_suspend(void *data)
 
 #ifdef CONFIG_ARCH_CPUIDLE_HALTPOLL
 	if (kvm_para_has_feature(KVM_FEATURE_POLL_CONTROL))
-		rdmsrq(MSR_KVM_POLL_CONTROL, val);
+		val = rdmsrq(MSR_KVM_POLL_CONTROL);
 	has_guest_poll = !(val & 1);
 #endif
 	return 0;
diff --git a/arch/x86/kernel/mmconf-fam10h_64.c b/arch/x86/kernel/mmconf-fam10h_64.c
index ef6104e7cc72..348ac8fce3ad 100644
--- a/arch/x86/kernel/mmconf-fam10h_64.c
+++ b/arch/x86/kernel/mmconf-fam10h_64.c
@@ -97,7 +97,7 @@ static void get_fam10h_pci_mmconf_base(void)
 
 	/* SYS_CFG */
 	address = MSR_AMD64_SYSCFG;
-	rdmsrq(address, val);
+	val = rdmsrq(address);
 
 	/* TOP_MEM2 is not enabled? */
 	if (!(val & (1<<21))) {
@@ -105,7 +105,7 @@ static void get_fam10h_pci_mmconf_base(void)
 	} else {
 		/* TOP_MEM2 */
 		address = MSR_K8_TOP_MEM2;
-		rdmsrq(address, val);
+		val = rdmsrq(address);
 		tom2 = max(val & 0xffffff800000ULL, 1ULL << 32);
 	}
 
@@ -177,7 +177,7 @@ void fam10h_check_enable_mmcfg(void)
 		return;
 
 	address = MSR_FAM10H_MMIO_CONF_BASE;
-	rdmsrq(address, val);
+	val = rdmsrq(address);
 
 	/* try to make sure that AP's setting is identical to BSP setting */
 	if (val & FAM10H_MMIO_CONF_ENABLE) {
diff --git a/arch/x86/kernel/process.c b/arch/x86/kernel/process.c
index 346c438ac880..85e6b076ef17 100644
--- a/arch/x86/kernel/process.c
+++ b/arch/x86/kernel/process.c
@@ -730,7 +730,7 @@ void __switch_to_xtra(struct task_struct *prev_p, struct task_struct *next_p)
 	    arch_has_block_step()) {
 		unsigned long debugctl, msk;
 
-		rdmsrq(MSR_IA32_DEBUGCTLMSR, debugctl);
+		debugctl = rdmsrq(MSR_IA32_DEBUGCTLMSR);
 		debugctl &= ~DEBUGCTLMSR_BTF;
 		msk = tifn & _TIF_BLOCKSTEP;
 		debugctl |= (msk >> TIF_BLOCKSTEP) << DEBUGCTLMSR_BTF_SHIFT;
@@ -980,7 +980,7 @@ void __init arch_post_acpi_subsys_init(void)
 	 * the machine is affected K8_INTP_C1E_ACTIVE_MASK bits are set in
 	 * MSR_K8_INT_PENDING_MSG.
 	 */
-	rdmsrq(MSR_K8_INT_PENDING_MSG, val);
+	val = rdmsrq(MSR_K8_INT_PENDING_MSG);
 	if (!(val & K8_INTP_C1E_ACTIVE_MASK))
 		return;
 
diff --git a/arch/x86/kernel/process_64.c b/arch/x86/kernel/process_64.c
index 2bce7b3f97ed..28dbe7e629fc 100644
--- a/arch/x86/kernel/process_64.c
+++ b/arch/x86/kernel/process_64.c
@@ -97,8 +97,8 @@ void __show_regs(struct pt_regs *regs, enum show_regs_mode mode,
 		return;
 
 	if (mode == SHOW_REGS_USER) {
-		rdmsrq(MSR_FS_BASE, fs);
-		rdmsrq(MSR_KERNEL_GS_BASE, shadowgs);
+		fs = rdmsrq(MSR_FS_BASE);
+		shadowgs = rdmsrq(MSR_KERNEL_GS_BASE);
 		printk("%sFS:  %016lx GS:  %016lx\n",
 		       log_lvl, fs, shadowgs);
 		return;
@@ -109,9 +109,9 @@ void __show_regs(struct pt_regs *regs, enum show_regs_mode mode,
 	savesegment(fs, fsindex);
 	savesegment(gs, gsindex);
 
-	rdmsrq(MSR_FS_BASE, fs);
-	rdmsrq(MSR_GS_BASE, gs);
-	rdmsrq(MSR_KERNEL_GS_BASE, shadowgs);
+	fs = rdmsrq(MSR_FS_BASE);
+	gs = rdmsrq(MSR_GS_BASE);
+	shadowgs = rdmsrq(MSR_KERNEL_GS_BASE);
 
 	cr0 = read_cr0();
 	cr2 = read_cr2();
@@ -197,7 +197,7 @@ static noinstr unsigned long __rdgsbase_inactive(void)
 		native_swapgs();
 	} else {
 		instrumentation_begin();
-		rdmsrq(MSR_KERNEL_GS_BASE, gsbase);
+		gsbase = rdmsrq(MSR_KERNEL_GS_BASE);
 		instrumentation_end();
 	}
 
@@ -463,7 +463,7 @@ unsigned long x86_gsbase_read_cpu_inactive(void)
 		gsbase = __rdgsbase_inactive();
 		local_irq_restore(flags);
 	} else {
-		rdmsrq(MSR_KERNEL_GS_BASE, gsbase);
+		gsbase = rdmsrq(MSR_KERNEL_GS_BASE);
 	}
 
 	return gsbase;
diff --git a/arch/x86/kernel/shstk.c b/arch/x86/kernel/shstk.c
index 0ca64900192f..f3d1f385c626 100644
--- a/arch/x86/kernel/shstk.c
+++ b/arch/x86/kernel/shstk.c
@@ -231,7 +231,7 @@ static unsigned long get_user_shstk_addr(void)
 
 	fpregs_lock_and_load();
 
-	rdmsrq(MSR_IA32_PL3_SSP, ssp);
+	ssp = rdmsrq(MSR_IA32_PL3_SSP);
 
 	fpregs_unlock();
 
@@ -248,7 +248,7 @@ int shstk_pop(u64 *val)
 
 	fpregs_lock_and_load();
 
-	rdmsrq(MSR_IA32_PL3_SSP, ssp);
+	ssp = rdmsrq(MSR_IA32_PL3_SSP);
 	if (val && get_user(*val, (__user u64 *)ssp))
 		ret = -EFAULT;
 	else
@@ -268,7 +268,7 @@ int shstk_push(u64 val)
 
 	fpregs_lock_and_load();
 
-	rdmsrq(MSR_IA32_PL3_SSP, ssp);
+	ssp = rdmsrq(MSR_IA32_PL3_SSP);
 	ssp -= SS_FRAME_SIZE;
 	ret = write_user_shstk_64((__user void *)ssp, val);
 	if (!ret)
@@ -497,7 +497,7 @@ static int wrss_control(bool enable)
 		return 0;
 
 	fpregs_lock_and_load();
-	rdmsrq(MSR_IA32_U_CET, msrval);
+	msrval = rdmsrq(MSR_IA32_U_CET);
 
 	if (enable) {
 		features_set(ARCH_SHSTK_WRSS);
diff --git a/arch/x86/kernel/traps.c b/arch/x86/kernel/traps.c
index 30aa8369957e..9cd11dc7bb17 100644
--- a/arch/x86/kernel/traps.c
+++ b/arch/x86/kernel/traps.c
@@ -1250,7 +1250,7 @@ static noinstr void exc_debug_kernel(struct pt_regs *regs, unsigned long dr6)
 		 */
 		unsigned long debugctl;
 
-		rdmsrq(MSR_IA32_DEBUGCTLMSR, debugctl);
+		debugctl = rdmsrq(MSR_IA32_DEBUGCTLMSR);
 		debugctl |= DEBUGCTLMSR_BTF;
 		wrmsrq(MSR_IA32_DEBUGCTLMSR, debugctl);
 	}
@@ -1509,7 +1509,7 @@ static bool handle_xfd_event(struct pt_regs *regs)
 	if (!IS_ENABLED(CONFIG_X86_64) || !cpu_feature_enabled(X86_FEATURE_XFD))
 		return false;
 
-	rdmsrq(MSR_IA32_XFD_ERR, xfd_err);
+	xfd_err = rdmsrq(MSR_IA32_XFD_ERR);
 	if (!xfd_err)
 		return false;
 
diff --git a/arch/x86/kernel/tsc.c b/arch/x86/kernel/tsc.c
index 723347e2cf7f..39baee2307c3 100644
--- a/arch/x86/kernel/tsc.c
+++ b/arch/x86/kernel/tsc.c
@@ -1088,7 +1088,7 @@ static void __init detect_art(void)
 	if (art_base_clk.denominator < ART_MIN_DENOMINATOR)
 		return;
 
-	rdmsrq(MSR_IA32_TSC_ADJUST, art_base_clk.offset);
+	art_base_clk.offset = rdmsrq(MSR_IA32_TSC_ADJUST);
 
 	/* Make this sticky over multiple CPU init calls */
 	setup_force_cpu_cap(X86_FEATURE_ART);
diff --git a/arch/x86/kernel/tsc_msr.c b/arch/x86/kernel/tsc_msr.c
index d74743c8d2a4..6a1d22e8b846 100644
--- a/arch/x86/kernel/tsc_msr.c
+++ b/arch/x86/kernel/tsc_msr.c
@@ -179,15 +179,15 @@ unsigned long cpu_khz_from_msr(void)
 
 	freq_desc = (struct freq_desc *)id->driver_data;
 	if (freq_desc->use_msr_plat) {
-		rdmsrq(MSR_PLATFORM_INFO, val.q);
+		val.q = rdmsrq(MSR_PLATFORM_INFO);
 		ratio = (val.l >> 8) & 0xff;
 	} else {
-		rdmsrq(MSR_IA32_PERF_STATUS, val.q);
+		val.q = rdmsrq(MSR_IA32_PERF_STATUS);
 		ratio = (val.h >> 8) & 0x1f;
 	}
 
 	/* Get FSB FREQ ID */
-	rdmsrq(MSR_FSB_FREQ, val.q);
+	val.q = rdmsrq(MSR_FSB_FREQ);
 	index = val.l & freq_desc->mask;
 	md = &freq_desc->muldiv[index];
 
diff --git a/arch/x86/kernel/tsc_sync.c b/arch/x86/kernel/tsc_sync.c
index ec3aa340d351..a21ea3c85f6f 100644
--- a/arch/x86/kernel/tsc_sync.c
+++ b/arch/x86/kernel/tsc_sync.c
@@ -66,7 +66,7 @@ void tsc_verify_tsc_adjust(bool resume)
 
 	adj->nextcheck = jiffies + HZ;
 
-	rdmsrq(MSR_IA32_TSC_ADJUST, curval);
+	curval = rdmsrq(MSR_IA32_TSC_ADJUST);
 	if (adj->adjusted == curval)
 		return;
 
@@ -166,7 +166,7 @@ bool __init tsc_store_and_check_tsc_adjust(bool bootcpu)
 	if (check_tsc_unstable())
 		return false;
 
-	rdmsrq(MSR_IA32_TSC_ADJUST, bootval);
+	bootval = rdmsrq(MSR_IA32_TSC_ADJUST);
 	cur->bootval = bootval;
 	cur->nextcheck = jiffies + HZ;
 	tsc_sanitize_first_cpu(cur, bootval, smp_processor_id(), bootcpu);
@@ -188,7 +188,7 @@ bool tsc_store_and_check_tsc_adjust(bool bootcpu)
 	if (!boot_cpu_has(X86_FEATURE_TSC_ADJUST))
 		return false;
 
-	rdmsrq(MSR_IA32_TSC_ADJUST, bootval);
+	bootval = rdmsrq(MSR_IA32_TSC_ADJUST);
 	cur->bootval = bootval;
 	cur->nextcheck = jiffies + HZ;
 	cur->warned = false;
diff --git a/arch/x86/kvm/svm/pmu.c b/arch/x86/kvm/svm/pmu.c
index c18286545a7a..5ebee82dba84 100644
--- a/arch/x86/kvm/svm/pmu.c
+++ b/arch/x86/kvm/svm/pmu.c
@@ -249,7 +249,7 @@ static void amd_mediated_pmu_load(struct kvm_vcpu *vcpu)
 	struct kvm_pmu *pmu = vcpu_to_pmu(vcpu);
 	u64 global_status;
 
-	rdmsrq(MSR_AMD64_PERF_CNTR_GLOBAL_STATUS, global_status);
+	global_status = rdmsrq(MSR_AMD64_PERF_CNTR_GLOBAL_STATUS);
 	/* Clear host global_status MSR if non-zero. */
 	if (global_status)
 		wrmsrq(MSR_AMD64_PERF_CNTR_GLOBAL_STATUS_CLR, global_status);
@@ -263,7 +263,7 @@ static void amd_mediated_pmu_put(struct kvm_vcpu *vcpu)
 	struct kvm_pmu *pmu = vcpu_to_pmu(vcpu);
 
 	wrmsrq(MSR_AMD64_PERF_CNTR_GLOBAL_CTL, 0);
-	rdmsrq(MSR_AMD64_PERF_CNTR_GLOBAL_STATUS, pmu->global_status);
+	pmu->global_status = rdmsrq(MSR_AMD64_PERF_CNTR_GLOBAL_STATUS);
 
 	/* Clear global status bits if non-zero */
 	if (pmu->global_status)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index 69a0d6a1cfc7..6410bd3eb2cd 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -4636,7 +4636,7 @@ static __no_kcsan fastpath_t svm_vcpu_run(struct kvm_vcpu *vcpu, u64 run_flags)
 	kvm_clear_available_registers(vcpu, SVM_REGS_LAZY_LOAD_SET);
 
 	if (!msr_write_intercepted(svm, MSR_AMD64_PERF_CNTR_GLOBAL_CTL))
-		rdmsrq(MSR_AMD64_PERF_CNTR_GLOBAL_CTL, vcpu_to_pmu(vcpu)->global_ctrl);
+		vcpu_to_pmu(vcpu)->global_ctrl = rdmsrq(MSR_AMD64_PERF_CNTR_GLOBAL_CTL);
 
 	trace_kvm_exit(vcpu, KVM_ISA_SVM);
 
@@ -5499,7 +5499,7 @@ static __init void svm_adjust_mmio_mask(void)
 		return;
 
 	/* If memory encryption is not enabled, use existing mask */
-	rdmsrq(MSR_AMD64_SYSCFG, msr);
+	msr = rdmsrq(MSR_AMD64_SYSCFG);
 	if (!(msr & MSR_AMD64_SYSCFG_MEM_ENCRYPT))
 		return;
 
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index ddf6df7bee93..d8f4e7706d39 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -7348,8 +7348,8 @@ static void nested_vmx_setup_cr_fixed(struct nested_vmx_msrs *msrs)
 	msrs->cr4_fixed0 = VMXON_CR4_ALWAYSON;
 
 	/* These MSRs specify bits which the guest must keep fixed off. */
-	rdmsrq(MSR_IA32_VMX_CR0_FIXED1, msrs->cr0_fixed1);
-	rdmsrq(MSR_IA32_VMX_CR4_FIXED1, msrs->cr4_fixed1);
+	msrs->cr0_fixed1 = rdmsrq(MSR_IA32_VMX_CR0_FIXED1);
+	msrs->cr4_fixed1 = rdmsrq(MSR_IA32_VMX_CR4_FIXED1);
 
 	if (vmx_umip_emulated())
 		msrs->cr4_fixed1 |= X86_CR4_UMIP;
diff --git a/arch/x86/kvm/vmx/pmu_intel.c b/arch/x86/kvm/vmx/pmu_intel.c
index 1944939c4139..5eb5636704bc 100644
--- a/arch/x86/kvm/vmx/pmu_intel.c
+++ b/arch/x86/kvm/vmx/pmu_intel.c
@@ -320,7 +320,7 @@ static bool intel_pmu_handle_lbr_msrs_access(struct kvm_vcpu *vcpu,
 		int err = 0;
 
 		if (read)
-			rdmsrq(index, msr_info->data);
+			msr_info->data = rdmsrq(index);
 		else
 			err = wrmsrq_safe(index, msr_info->data);
 		__set_bit(INTEL_PMC_IDX_FIXED_VLBR, vcpu_to_pmu(vcpu)->pmc_in_use);
@@ -772,7 +772,7 @@ static bool intel_pmu_is_mediated_pmu_supported(struct x86_pmu_capability *host_
 	u64 host_perf_cap = 0;
 
 	if (boot_cpu_has(X86_FEATURE_PDCM))
-		rdmsrq(MSR_IA32_PERF_CAPABILITIES, host_perf_cap);
+		host_perf_cap = rdmsrq(MSR_IA32_PERF_CAPABILITIES);
 
 	/*
 	 * Require v4+ for MSR_CORE_PERF_GLOBAL_STATUS_SET, and full-width
@@ -802,7 +802,7 @@ static void intel_mediated_pmu_load(struct kvm_vcpu *vcpu)
 	struct kvm_pmu *pmu = vcpu_to_pmu(vcpu);
 	u64 global_status, toggle;
 
-	rdmsrq(MSR_CORE_PERF_GLOBAL_STATUS, global_status);
+	global_status = rdmsrq(MSR_CORE_PERF_GLOBAL_STATUS);
 	toggle = pmu->global_status ^ global_status;
 	if (global_status & toggle)
 		wrmsrq(MSR_CORE_PERF_GLOBAL_OVF_CTRL, global_status & toggle);
@@ -817,7 +817,7 @@ static void intel_mediated_pmu_put(struct kvm_vcpu *vcpu)
 	struct kvm_pmu *pmu = vcpu_to_pmu(vcpu);
 
 	/* MSR_CORE_PERF_GLOBAL_CTRL is already saved at VM-exit. */
-	rdmsrq(MSR_CORE_PERF_GLOBAL_STATUS, pmu->global_status);
+	pmu->global_status = rdmsrq(MSR_CORE_PERF_GLOBAL_STATUS);
 
 	/* Clear hardware MSR_CORE_PERF_GLOBAL_STATUS MSR, if non-zero. */
 	if (pmu->global_status)
diff --git a/arch/x86/kvm/vmx/sgx.c b/arch/x86/kvm/vmx/sgx.c
index 771c75a58343..765cac151023 100644
--- a/arch/x86/kvm/vmx/sgx.c
+++ b/arch/x86/kvm/vmx/sgx.c
@@ -420,9 +420,9 @@ void setup_default_sgx_lepubkeyhash(void)
 		sgx_pubkey_hash[3] = 0xd4f8c05909f9bb3bULL;
 	} else {
 		/* MSR_IA32_SGXLEPUBKEYHASH0 is read above */
-		rdmsrq(MSR_IA32_SGXLEPUBKEYHASH1, sgx_pubkey_hash[1]);
-		rdmsrq(MSR_IA32_SGXLEPUBKEYHASH2, sgx_pubkey_hash[2]);
-		rdmsrq(MSR_IA32_SGXLEPUBKEYHASH3, sgx_pubkey_hash[3]);
+		sgx_pubkey_hash[1] = rdmsrq(MSR_IA32_SGXLEPUBKEYHASH1);
+		sgx_pubkey_hash[2] = rdmsrq(MSR_IA32_SGXLEPUBKEYHASH2);
+		sgx_pubkey_hash[3] = rdmsrq(MSR_IA32_SGXLEPUBKEYHASH3);
 	}
 }
 
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 2c1947e987f8..bea3c3da264b 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -1245,13 +1245,13 @@ static inline void pt_save_msr(struct pt_ctx *ctx, u32 addr_range)
 {
 	u32 i;
 
-	rdmsrq(MSR_IA32_RTIT_STATUS, ctx->status);
-	rdmsrq(MSR_IA32_RTIT_OUTPUT_BASE, ctx->output_base);
-	rdmsrq(MSR_IA32_RTIT_OUTPUT_MASK, ctx->output_mask);
-	rdmsrq(MSR_IA32_RTIT_CR3_MATCH, ctx->cr3_match);
+	ctx->status = rdmsrq(MSR_IA32_RTIT_STATUS);
+	ctx->output_base = rdmsrq(MSR_IA32_RTIT_OUTPUT_BASE);
+	ctx->output_mask = rdmsrq(MSR_IA32_RTIT_OUTPUT_MASK);
+	ctx->cr3_match = rdmsrq(MSR_IA32_RTIT_CR3_MATCH);
 	for (i = 0; i < addr_range; i++) {
-		rdmsrq(MSR_IA32_RTIT_ADDR0_A + i * 2, ctx->addr_a[i]);
-		rdmsrq(MSR_IA32_RTIT_ADDR0_B + i * 2, ctx->addr_b[i]);
+		ctx->addr_a[i] = rdmsrq(MSR_IA32_RTIT_ADDR0_A + i * 2);
+		ctx->addr_b[i] = rdmsrq(MSR_IA32_RTIT_ADDR0_B + i * 2);
 	}
 }
 
@@ -1264,7 +1264,7 @@ static void pt_guest_enter(struct vcpu_vmx *vmx)
 	 * GUEST_IA32_RTIT_CTL is already set in the VMCS.
 	 * Save host state before VM entry.
 	 */
-	rdmsrq(MSR_IA32_RTIT_CTL, vmx->pt_desc.host.ctl);
+	vmx->pt_desc.host.ctl = rdmsrq(MSR_IA32_RTIT_CTL);
 	if (vmx->pt_desc.guest.ctl & RTIT_CTL_TRACEEN) {
 		wrmsrq(MSR_IA32_RTIT_CTL, 0);
 		pt_save_msr(&vmx->pt_desc.host, vmx->pt_desc.num_address_ranges);
@@ -1402,7 +1402,7 @@ static void vmx_prepare_switch_to_host(struct vcpu_vmx *vmx)
 	++vmx->vcpu.stat.host_state_reload;
 
 #ifdef CONFIG_X86_64
-	rdmsrq(MSR_KERNEL_GS_BASE, vmx->msr_guest_kernel_gs_base);
+	vmx->msr_guest_kernel_gs_base = rdmsrq(MSR_KERNEL_GS_BASE);
 #endif
 	if (host_state->ldt_sel || (host_state->gs_sel & 7)) {
 		kvm_load_ldt(host_state->ldt_sel);
@@ -2678,7 +2678,7 @@ static int adjust_vmx_controls(u32 ctl_min, u32 ctl_opt, u32 msr, u32 *result)
 	struct msr vmx_msr;
 	u32 ctl = ctl_min | ctl_opt;
 
-	rdmsrq(msr, vmx_msr.q);
+	vmx_msr.q = rdmsrq(msr);
 
 	ctl &= vmx_msr.h;  /* bit == 0 in high word ==> must be zero */
 	ctl |= vmx_msr.l;  /* bit == 1 in low word  ==> must be one  */
@@ -2695,7 +2695,7 @@ static u64 adjust_vmx_controls64(u64 ctl_opt, u32 msr)
 {
 	u64 allowed;
 
-	rdmsrq(msr, allowed);
+	allowed = rdmsrq(msr);
 
 	return  ctl_opt & allowed;
 }
@@ -2881,7 +2881,7 @@ static int setup_vmcs_config(struct vmcs_config *vmcs_conf,
 		break;
 	}
 
-	rdmsrq(MSR_IA32_VMX_BASIC, basic_msr);
+	basic_msr = rdmsrq(MSR_IA32_VMX_BASIC);
 
 	/* IA-32 SDM Vol 3B: VMCS size is never greater than 4kB. */
 	if (vmx_basic_vmcs_size(basic_msr) > PAGE_SIZE)
@@ -2901,7 +2901,7 @@ static int setup_vmcs_config(struct vmcs_config *vmcs_conf,
 	if (vmx_basic_vmcs_mem_type(basic_msr) != X86_MEMTYPE_WB)
 		return -EIO;
 
-	rdmsrq(MSR_IA32_VMX_MISC, misc_msr);
+	misc_msr = rdmsrq(MSR_IA32_VMX_MISC);
 
 	vmcs_conf->basic = basic_msr;
 	vmcs_conf->pin_based_exec_ctrl = _pin_based_exec_control;
@@ -4477,7 +4477,7 @@ void vmx_set_constant_host_state(struct vcpu_vmx *vmx)
 
 	vmcs_writel(HOST_RIP, (unsigned long)vmx_vmexit); /* 22.2.5 */
 
-	rdmsrq(MSR_IA32_SYSENTER_CS, val.q);
+	val.q = rdmsrq(MSR_IA32_SYSENTER_CS);
 	vmcs_write32(HOST_IA32_SYSENTER_CS, val.l);
 
 	/*
@@ -4489,11 +4489,11 @@ void vmx_set_constant_host_state(struct vcpu_vmx *vmx)
 	if (!IS_ENABLED(CONFIG_IA32_EMULATION) && !IS_ENABLED(CONFIG_X86_32))
 		vmcs_writel(HOST_IA32_SYSENTER_ESP, 0);
 
-	rdmsrq(MSR_IA32_SYSENTER_EIP, tmpl);
+	tmpl = rdmsrq(MSR_IA32_SYSENTER_EIP);
 	vmcs_writel(HOST_IA32_SYSENTER_EIP, tmpl);   /* 22.2.3 */
 
 	if (vmcs_config.vmexit_ctrl & VM_EXIT_LOAD_IA32_PAT) {
-		rdmsrq(MSR_IA32_CR_PAT, val.q);
+		val.q = rdmsrq(MSR_IA32_CR_PAT);
 		vmcs_write64(HOST_IA32_PAT, val.q);
 	}
 
@@ -7151,7 +7151,7 @@ static void handle_nm_fault_irqoff(struct kvm_vcpu *vcpu)
 	 * the #NM exception.
 	 */
 	if (is_xfd_nm_fault(vcpu))
-		rdmsrq(MSR_IA32_XFD_ERR, vcpu->arch.guest_fpu.xfd_err);
+		vcpu->arch.guest_fpu.xfd_err = rdmsrq(MSR_IA32_XFD_ERR);
 }
 
 static void handle_exception_irqoff(struct kvm_vcpu *vcpu, u32 intr_info)
@@ -8042,7 +8042,7 @@ static __init u64 vmx_get_perf_capabilities(void)
 		return 0;
 
 	if (boot_cpu_has(X86_FEATURE_PDCM))
-		rdmsrq(MSR_IA32_PERF_CAPABILITIES, host_perf_cap);
+		host_perf_cap = rdmsrq(MSR_IA32_PERF_CAPABILITIES);
 
 	if (!cpu_feature_enabled(X86_FEATURE_ARCH_LBR) &&
 	    !enable_mediated_pmu) {
@@ -8599,7 +8599,7 @@ __init int vmx_hardware_setup(void)
 	vmx_setup_user_return_msrs();
 
 	if (boot_cpu_has(X86_FEATURE_MPX)) {
-		rdmsrq(MSR_IA32_BNDCFGS, host_bndcfgs);
+		host_bndcfgs = rdmsrq(MSR_IA32_BNDCFGS);
 		WARN_ONCE(host_bndcfgs, "BNDCFGS in host will be lost");
 	}
 
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 69469bbdc84a..44d870b51d85 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3859,7 +3859,7 @@ static __always_inline void kvm_access_xstate_msr(struct kvm_vcpu *vcpu,
 
 	kvm_fpu_get();
 	if (access == MSR_TYPE_R)
-		rdmsrq(msr_info->index, msr_info->data);
+		msr_info->data = rdmsrq(msr_info->index);
 	else
 		wrmsrq(msr_info->index, msr_info->data);
 	kvm_fpu_put();
@@ -10098,7 +10098,7 @@ int kvm_x86_vendor_init(struct kvm_x86_init_ops *ops)
 	}
 
 	if (boot_cpu_has(X86_FEATURE_SHSTK) || boot_cpu_has(X86_FEATURE_IBT)) {
-		rdmsrq(MSR_IA32_S_CET, kvm_host.s_cet);
+		kvm_host.s_cet = rdmsrq(MSR_IA32_S_CET);
 		/*
 		 * Linux doesn't yet support supervisor shadow stacks (SSS), so
 		 * KVM doesn't save/restore the associated MSRs, i.e. KVM may
@@ -10130,7 +10130,7 @@ int kvm_x86_vendor_init(struct kvm_x86_init_ops *ops)
 	}
 
 	if (boot_cpu_has(X86_FEATURE_XSAVES)) {
-		rdmsrq(MSR_IA32_XSS, kvm_host.xss);
+		kvm_host.xss = rdmsrq(MSR_IA32_XSS);
 		kvm_caps.supported_xss = kvm_host.xss & KVM_SUPPORTED_XSS;
 	}
 
@@ -10142,7 +10142,7 @@ int kvm_x86_vendor_init(struct kvm_x86_init_ops *ops)
 	kvm_init_pmu_capability(ops->pmu_ops);
 
 	if (boot_cpu_has(X86_FEATURE_ARCH_CAPABILITIES))
-		rdmsrq(MSR_IA32_ARCH_CAPABILITIES, kvm_host.arch_capabilities);
+		kvm_host.arch_capabilities = rdmsrq(MSR_IA32_ARCH_CAPABILITIES);
 
 	WARN_ON_ONCE(kvm_nr_uret_msrs);
 
diff --git a/arch/x86/lib/insn-eval.c b/arch/x86/lib/insn-eval.c
index e03eeec55cfe..b9db2012a75e 100644
--- a/arch/x86/lib/insn-eval.c
+++ b/arch/x86/lib/insn-eval.c
@@ -709,16 +709,16 @@ unsigned long insn_get_seg_base(struct pt_regs *regs, int seg_reg_idx)
 		unsigned long base;
 
 		if (seg_reg_idx == INAT_SEG_REG_FS) {
-			rdmsrq(MSR_FS_BASE, base);
+			base = rdmsrq(MSR_FS_BASE);
 		} else if (seg_reg_idx == INAT_SEG_REG_GS) {
 			/*
 			 * swapgs was called at the kernel entry point. Thus,
 			 * MSR_KERNEL_GS_BASE will have the user-space GS base.
 			 */
 			if (user_mode(regs))
-				rdmsrq(MSR_KERNEL_GS_BASE, base);
+				base = rdmsrq(MSR_KERNEL_GS_BASE);
 			else
-				rdmsrq(MSR_GS_BASE, base);
+				base = rdmsrq(MSR_GS_BASE);
 		} else {
 			base = 0;
 		}
diff --git a/arch/x86/lib/msr-smp.c b/arch/x86/lib/msr-smp.c
index 7b6cfc2c0970..ddff68050322 100644
--- a/arch/x86/lib/msr-smp.c
+++ b/arch/x86/lib/msr-smp.c
@@ -15,7 +15,7 @@ static void __rdmsr_on_cpu(void *info)
 	else
 		reg = &rv->reg;
 
-	rdmsrq(rv->msr_no, reg->q);
+	reg->q = rdmsrq(rv->msr_no);
 }
 
 static void __wrmsr_on_cpu(void *info)
diff --git a/arch/x86/mm/pat/memtype.c b/arch/x86/mm/pat/memtype.c
index cf94fb561310..114853c212ed 100644
--- a/arch/x86/mm/pat/memtype.c
+++ b/arch/x86/mm/pat/memtype.c
@@ -257,7 +257,7 @@ void __init pat_bp_init(void)
 	if (!cpu_feature_enabled(X86_FEATURE_PAT))
 		pat_disable("PAT not supported by the CPU.");
 	else
-		rdmsrq(MSR_IA32_CR_PAT, pat_msr_val);
+		pat_msr_val = rdmsrq(MSR_IA32_CR_PAT);
 
 	if (!pat_msr_val) {
 		pat_disable("PAT support disabled by the firmware.");
diff --git a/arch/x86/pci/amd_bus.c b/arch/x86/pci/amd_bus.c
index 99b1727136c1..342371081f60 100644
--- a/arch/x86/pci/amd_bus.c
+++ b/arch/x86/pci/amd_bus.c
@@ -202,7 +202,7 @@ static int __init early_root_info_init(void)
 
 	/* need to take out [0, TOM) for RAM*/
 	address = MSR_K8_TOP_MEM1;
-	rdmsrq(address, val);
+	val = rdmsrq(address);
 	end = (val & 0xffffff800000ULL);
 	printk(KERN_INFO "TOM: %016llx aka %lldM\n", end, end>>20);
 	if (end < (1ULL<<32))
@@ -293,12 +293,12 @@ static int __init early_root_info_init(void)
 	/* need to take out [4G, TOM2) for RAM*/
 	/* SYS_CFG */
 	address = MSR_AMD64_SYSCFG;
-	rdmsrq(address, val);
+	val = rdmsrq(address);
 	/* TOP_MEM2 is enabled? */
 	if (val & (1<<21)) {
 		/* TOP_MEM2 */
 		address = MSR_K8_TOP_MEM2;
-		rdmsrq(address, val);
+		val = rdmsrq(address);
 		end = (val & 0xffffff800000ULL);
 		printk(KERN_INFO "TOM2: %016llx aka %lldM\n", end, end>>20);
 		subtract_range(range, RANGE_NUM, 1ULL<<32, end);
@@ -341,7 +341,7 @@ static int amd_bus_cpu_online(unsigned int cpu)
 {
 	u64 reg;
 
-	rdmsrq(MSR_AMD64_NB_CFG, reg);
+	reg = rdmsrq(MSR_AMD64_NB_CFG);
 	if (!(reg & ENABLE_CF8_EXT_CFG)) {
 		reg |= ENABLE_CF8_EXT_CFG;
 		wrmsrq(MSR_AMD64_NB_CFG, reg);
diff --git a/arch/x86/platform/olpc/olpc-xo1-rtc.c b/arch/x86/platform/olpc/olpc-xo1-rtc.c
index ee77d57bcab7..95dfd90b1f05 100644
--- a/arch/x86/platform/olpc/olpc-xo1-rtc.c
+++ b/arch/x86/platform/olpc/olpc-xo1-rtc.c
@@ -64,9 +64,9 @@ static int __init xo1_rtc_init(void)
 	of_node_put(node);
 
 	pr_info("olpc-xo1-rtc: Initializing OLPC XO-1 RTC\n");
-	rdmsrq(MSR_RTC_DOMA_OFFSET, rtc_info.rtc_day_alarm);
-	rdmsrq(MSR_RTC_MONA_OFFSET, rtc_info.rtc_mon_alarm);
-	rdmsrq(MSR_RTC_CEN_OFFSET, rtc_info.rtc_century);
+	rtc_info.rtc_day_alarm = rdmsrq(MSR_RTC_DOMA_OFFSET);
+	rtc_info.rtc_mon_alarm = rdmsrq(MSR_RTC_MONA_OFFSET);
+	rtc_info.rtc_century = rdmsrq(MSR_RTC_CEN_OFFSET);
 
 	r = platform_device_register(&xo1_rtc_device);
 	if (r)
diff --git a/arch/x86/platform/olpc/olpc-xo1-sci.c b/arch/x86/platform/olpc/olpc-xo1-sci.c
index 97eb4738d602..b2e12d780e58 100644
--- a/arch/x86/platform/olpc/olpc-xo1-sci.c
+++ b/arch/x86/platform/olpc/olpc-xo1-sci.c
@@ -316,7 +316,7 @@ static int setup_sci_interrupt(struct platform_device *pdev)
 	u32 sts;
 	int r;
 
-	rdmsrq(0x51400020, msr);
+	msr = rdmsrq(0x51400020);
 	sci_irq = (msr >> 20) & 15;
 
 	if (sci_irq) {
diff --git a/arch/x86/power/cpu.c b/arch/x86/power/cpu.c
index 702f30eaf9c4..f44ebe2fe099 100644
--- a/arch/x86/power/cpu.c
+++ b/arch/x86/power/cpu.c
@@ -45,7 +45,7 @@ static void msr_save_context(struct saved_context *ctxt)
 
 	while (msr < end) {
 		if (msr->valid)
-			rdmsrq(msr->info.msr_no, msr->info.reg.q);
+			msr->info.reg.q = rdmsrq(msr->info.msr_no);
 		msr++;
 	}
 }
@@ -111,12 +111,12 @@ static void __save_processor_state(struct saved_context *ctxt)
 	savesegment(ds, ctxt->ds);
 	savesegment(es, ctxt->es);
 
-	rdmsrq(MSR_FS_BASE, ctxt->fs_base);
-	rdmsrq(MSR_GS_BASE, ctxt->kernelmode_gs_base);
-	rdmsrq(MSR_KERNEL_GS_BASE, ctxt->usermode_gs_base);
+	ctxt->fs_base = rdmsrq(MSR_FS_BASE);
+	ctxt->kernelmode_gs_base = rdmsrq(MSR_GS_BASE);
+	ctxt->usermode_gs_base = rdmsrq(MSR_KERNEL_GS_BASE);
 	mtrr_save_fixed_ranges(NULL);
 
-	rdmsrq(MSR_EFER, ctxt->efer);
+	ctxt->efer = rdmsrq(MSR_EFER);
 #endif
 
 	/*
diff --git a/arch/x86/realmode/init.c b/arch/x86/realmode/init.c
index 694d80a5c68e..469f2aa8e105 100644
--- a/arch/x86/realmode/init.c
+++ b/arch/x86/realmode/init.c
@@ -147,7 +147,7 @@ static void __init setup_real_mode(void)
 	 * Some AMD processors will #GP(0) if EFER.LMA is set in WRMSR
 	 * so we need to mask it out.
 	 */
-	rdmsrq(MSR_EFER, efer);
+	efer = rdmsrq(MSR_EFER);
 	trampoline_header->efer = efer & ~EFER_LMA;
 
 	trampoline_header->start = (u64) secondary_startup_64;
diff --git a/arch/x86/virt/hw.c b/arch/x86/virt/hw.c
index 7e9091c640be..a2e4b99d7276 100644
--- a/arch/x86/virt/hw.c
+++ b/arch/x86/virt/hw.c
@@ -178,7 +178,7 @@ static __init int __x86_vmx_init(void)
 	if (!cpu_feature_enabled(X86_FEATURE_VMX))
 		return -EOPNOTSUPP;
 
-	rdmsrq(MSR_IA32_VMX_BASIC, basic_msr);
+	basic_msr = rdmsrq(MSR_IA32_VMX_BASIC);
 
 	/* IA-32 SDM Vol 3B: VMCS size is never greater than 4kB. */
 	if (WARN_ON_ONCE(vmx_basic_vmcs_size(basic_msr) > PAGE_SIZE))
@@ -230,7 +230,7 @@ static int x86_svm_enable_virtualization_cpu(void)
 {
 	u64 efer;
 
-	rdmsrq(MSR_EFER, efer);
+	efer = rdmsrq(MSR_EFER);
 	if (efer & EFER_SVME)
 		return -EBUSY;
 
@@ -253,7 +253,7 @@ static int x86_svm_disable_virtualization_cpu(void)
 	r = 0;
 
 fault:
-	rdmsrq(MSR_EFER, efer);
+	efer = rdmsrq(MSR_EFER);
 	wrmsrq(MSR_EFER, efer & ~EFER_SVME);
 	return r;
 }
@@ -264,7 +264,7 @@ static void x86_svm_emergency_disable_virtualization_cpu(void)
 
 	virt_rebooting = true;
 
-	rdmsrq(MSR_EFER, efer);
+	efer = rdmsrq(MSR_EFER);
 	if (!(efer & EFER_SVME))
 		return;
 
diff --git a/arch/x86/virt/svm/sev.c b/arch/x86/virt/svm/sev.c
index cff285d8ad8e..4a1e75d66e6c 100644
--- a/arch/x86/virt/svm/sev.c
+++ b/arch/x86/virt/svm/sev.c
@@ -150,7 +150,7 @@ static void snp_enable(void *arg)
 	if (!cc_platform_has(CC_ATTR_HOST_SEV_SNP))
 		return;
 
-	rdmsrq(MSR_AMD64_SYSCFG, val);
+	val = rdmsrq(MSR_AMD64_SYSCFG);
 
 	val |= MSR_AMD64_SYSCFG_SNP_EN;
 	val |= MSR_AMD64_SYSCFG_SNP_VMPL_EN;
@@ -254,7 +254,7 @@ static void clear_rmp(void)
 		return;
 
 	/* Clearing the RMP while SNP is enabled will cause an exception */
-	rdmsrq(MSR_AMD64_SYSCFG, val);
+	val = rdmsrq(MSR_AMD64_SYSCFG);
 	if (WARN_ON_ONCE(val & MSR_AMD64_SYSCFG_SNP_EN))
 		return;
 
@@ -520,7 +520,7 @@ int snp_prepare(void)
 	 * Check if SEV-SNP is already enabled, this can happen in case of
 	 * kexec boot.
 	 */
-	rdmsrq(MSR_AMD64_SYSCFG, val);
+	val = rdmsrq(MSR_AMD64_SYSCFG);
 	if (val & MSR_AMD64_SYSCFG_SNP_EN)
 		return 0;
 
@@ -561,7 +561,7 @@ void snp_shutdown(void)
 {
 	u64 syscfg;
 
-	rdmsrq(MSR_AMD64_SYSCFG, syscfg);
+	syscfg = rdmsrq(MSR_AMD64_SYSCFG);
 	if (syscfg & MSR_AMD64_SYSCFG_SNP_EN)
 		return;
 
@@ -608,8 +608,8 @@ static bool probe_contiguous_rmptable_info(void)
 {
 	u64 rmp_sz, rmp_base, rmp_end;
 
-	rdmsrq(MSR_AMD64_RMP_BASE, rmp_base);
-	rdmsrq(MSR_AMD64_RMP_END, rmp_end);
+	rmp_base = rdmsrq(MSR_AMD64_RMP_BASE);
+	rmp_end = rdmsrq(MSR_AMD64_RMP_END);
 
 	if (!(rmp_base & RMP_ADDR_MASK) || !(rmp_end & RMP_ADDR_MASK)) {
 		pr_err("Memory for the RMP table has not been reserved by BIOS\n");
@@ -642,13 +642,13 @@ static bool probe_segmented_rmptable_info(void)
 	unsigned int eax, ebx, segment_shift, segment_shift_min, segment_shift_max;
 	u64 rmp_base, rmp_end;
 
-	rdmsrq(MSR_AMD64_RMP_BASE, rmp_base);
+	rmp_base = rdmsrq(MSR_AMD64_RMP_BASE);
 	if (!(rmp_base & RMP_ADDR_MASK)) {
 		pr_err("Memory for the RMP table has not been reserved by BIOS\n");
 		return false;
 	}
 
-	rdmsrq(MSR_AMD64_RMP_END, rmp_end);
+	rmp_end = rdmsrq(MSR_AMD64_RMP_END);
 	WARN_ONCE(rmp_end & RMP_ADDR_MASK,
 		  "Segmented RMP enabled but RMP_END MSR is non-zero\n");
 
@@ -684,7 +684,7 @@ static bool probe_segmented_rmptable_info(void)
 bool snp_probe_rmptable_info(void)
 {
 	if (cpu_feature_enabled(X86_FEATURE_SEGMENTED_RMP))
-		rdmsrq(MSR_AMD64_RMP_CFG, rmp_cfg);
+		rmp_cfg = rdmsrq(MSR_AMD64_RMP_CFG);
 
 	if (rmp_cfg & MSR_AMD64_SEG_RMP_ENABLED)
 		return probe_segmented_rmptable_info();
diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
index 1b9ff749dd8e..9839f5b8dcc3 100644
--- a/arch/x86/virt/vmx/tdx/tdx.c
+++ b/arch/x86/virt/vmx/tdx/tdx.c
@@ -1523,7 +1523,7 @@ static void __init check_tdx_erratum(void)
 	 * Some TDX-capable CPUs have an erratum where the current VMCS is
 	 * cleared after calling into P-SEAMLDR.
 	 */
-	rdmsrq(MSR_IA32_VMX_BASIC, basic_msr);
+	basic_msr = rdmsrq(MSR_IA32_VMX_BASIC);
 	if (!(basic_msr & VMX_BASIC_NO_SEAMRET_INVD_VMCS))
 		setup_force_cpu_bug(X86_BUG_SEAMRET_INVD_VMCS);
 }
diff --git a/arch/x86/xen/suspend.c b/arch/x86/xen/suspend.c
index ba2f17e64321..b0e1152aa80e 100644
--- a/arch/x86/xen/suspend.c
+++ b/arch/x86/xen/suspend.c
@@ -56,7 +56,7 @@ static void xen_vcpu_notify_suspend(void *data)
 	tick_suspend_local();
 
 	if (xen_pv_domain() && boot_cpu_has(X86_FEATURE_SPEC_CTRL)) {
-		rdmsrq(MSR_IA32_SPEC_CTRL, tmp);
+		tmp = rdmsrq(MSR_IA32_SPEC_CTRL);
 		this_cpu_write(spec_ctrl, tmp);
 		wrmsrq(MSR_IA32_SPEC_CTRL, 0);
 	}
diff --git a/drivers/acpi/processor_perflib.c b/drivers/acpi/processor_perflib.c
index 9e25d6124efd..535a0f9dd2cf 100644
--- a/drivers/acpi/processor_perflib.c
+++ b/drivers/acpi/processor_perflib.c
@@ -296,7 +296,7 @@ static void amd_fixup_frequency(struct acpi_processor_px *px, int i)
 
 	if ((boot_cpu_data.x86 == 0x10 && boot_cpu_data.x86_model < 10) ||
 	    boot_cpu_data.x86 == 0x11) {
-		rdmsrq(MSR_AMD_PSTATE_DEF_BASE + index, val.q);
+		val.q = rdmsrq(MSR_AMD_PSTATE_DEF_BASE + index);
 		/*
 		 * MSR C001_0064+:
 		 * Bit 63: PstateEn. Read-write. If set, the P-state is valid.
diff --git a/drivers/ata/pata_cs5535.c b/drivers/ata/pata_cs5535.c
index e16c7f4c7c27..63a2b05ed22a 100644
--- a/drivers/ata/pata_cs5535.c
+++ b/drivers/ata/pata_cs5535.c
@@ -110,7 +110,7 @@ static void cs5535_set_piomode(struct ata_port *ap, struct ata_device *adev)
 		(u32)pio_cmd_timings[cmdmode] << 16 | pio_timings[mode]);
 
 	/* Set the PIO "format 1" bit in the DMA timing register */
-	rdmsrq(ATAC_CH0D0_DMA + 2 * adev->devno, reg);
+	reg = rdmsrq(ATAC_CH0D0_DMA + 2 * adev->devno);
 	wrmsrq(ATAC_CH0D0_DMA + 2 * adev->devno, reg | 0x80000000UL);
 }
 
@@ -132,7 +132,7 @@ static void cs5535_set_dmamode(struct ata_port *ap, struct ata_device *adev)
 	u32 reg;
 	int mode = adev->dma_mode;
 
-	rdmsrq(ATAC_CH0D0_DMA + 2 * adev->devno, reg);
+	reg = rdmsrq(ATAC_CH0D0_DMA + 2 * adev->devno);
 	reg &= 0x80000000UL;
 	if (mode >= XFER_UDMA_0)
 		reg |= udma_timings[mode - XFER_UDMA_0];
diff --git a/drivers/ata/pata_cs5536.c b/drivers/ata/pata_cs5536.c
index 61d232a82b5e..eca97dce1e85 100644
--- a/drivers/ata/pata_cs5536.c
+++ b/drivers/ata/pata_cs5536.c
@@ -84,7 +84,7 @@ static int cs5536_read(struct pci_dev *pdev, int reg, u32 *val)
 {
 #ifdef MAYBE_USE_MSR
 	if (unlikely(use_msr)) {
-		rdmsrq(MSR_IDE_CFG + reg, *val);
+		*val = rdmsrq(MSR_IDE_CFG + reg);
 		return 0;
 	}
 #endif
diff --git a/drivers/char/agp/nvidia-agp.c b/drivers/char/agp/nvidia-agp.c
index 3e760bc00afa..f646f1854981 100644
--- a/drivers/char/agp/nvidia-agp.c
+++ b/drivers/char/agp/nvidia-agp.c
@@ -72,8 +72,8 @@ static int nvidia_init_iorr(u32 base, u32 size)
 	/* If not found, determine the uppermost available iorr */
 	free_iorr_addr = AMD_K7_NUM_IORR;
 	for (iorr_addr = 0; iorr_addr < AMD_K7_NUM_IORR; iorr_addr++) {
-		rdmsrq(IORR_BASE0 + 2 * iorr_addr, base_msr.q);
-		rdmsrq(IORR_MASK0 + 2 * iorr_addr, mask_msr.q);
+		base_msr.q = rdmsrq(IORR_BASE0 + 2 * iorr_addr);
+		mask_msr.q = rdmsrq(IORR_MASK0 + 2 * iorr_addr);
 
 		if ((base_msr.l & 0xfffff000) == (base & 0xfffff000))
 			break;
@@ -94,7 +94,7 @@ static int nvidia_init_iorr(u32 base, u32 size)
     wrmsrq(IORR_BASE0 + 2 * iorr_addr, base_msr.q);
     wrmsrq(IORR_MASK0 + 2 * iorr_addr, mask_msr.q);
 
-    rdmsrq(SYSCFG, sys_msr.q);
+    sys_msr.q = rdmsrq(SYSCFG);
     sys_msr.l |= 0x00100000;
     wrmsrq(SYSCFG, sys_msr.q);
 
diff --git a/drivers/char/hw_random/via-rng.c b/drivers/char/hw_random/via-rng.c
index b718e78d3c1c..a0a3488a6947 100644
--- a/drivers/char/hw_random/via-rng.c
+++ b/drivers/char/hw_random/via-rng.c
@@ -151,7 +151,7 @@ static int via_rng_init(struct hwrng *rng)
 	 * does not say to write them as zero, so I make a guess that
 	 * we restore the values we find in the register.
 	 */
-	rdmsrq(MSR_VIA_RNG, val.q);
+	val.q = rdmsrq(MSR_VIA_RNG);
 
 	old_lo = val.l;
 	val.l &= ~(0x7f << VIA_STRFILT_CNT_SHIFT);
@@ -175,7 +175,7 @@ static int via_rng_init(struct hwrng *rng)
 
 	/* perhaps-unnecessary sanity check; remove after testing if
 	   unneeded */
-	rdmsrq(MSR_VIA_RNG, val.q);
+	val.q = rdmsrq(MSR_VIA_RNG);
 	if ((val.l & VIA_RNG_ENABLE) == 0) {
 		pr_err(PFX "cannot enable VIA C3 RNG, aborting\n");
 		return -ENODEV;
diff --git a/drivers/cpufreq/acpi-cpufreq.c b/drivers/cpufreq/acpi-cpufreq.c
index 10ea6035f4ad..e1ee80ecd0bb 100644
--- a/drivers/cpufreq/acpi-cpufreq.c
+++ b/drivers/cpufreq/acpi-cpufreq.c
@@ -110,7 +110,7 @@ static int boost_set_msr(bool enable)
 		return -EINVAL;
 	}
 
-	rdmsrq(msr_addr, val);
+	val = rdmsrq(msr_addr);
 
 	if (enable)
 		val &= ~msr_mask;
@@ -248,7 +248,7 @@ static u32 cpu_freq_read_intel(struct acpi_pct_register *not_used)
 {
 	u64 val;
 
-	rdmsrq(MSR_IA32_PERF_CTL, val);
+	val = rdmsrq(MSR_IA32_PERF_CTL);
 	return (u32)val;
 }
 
@@ -256,7 +256,7 @@ static void cpu_freq_write_intel(struct acpi_pct_register *not_used, u32 val)
 {
 	u64 msrval;
 
-	rdmsrq(MSR_IA32_PERF_CTL, msrval);
+	msrval = rdmsrq(MSR_IA32_PERF_CTL);
 	msrval = (msrval & ~(u64)INTEL_MSR_RANGE) | (val & INTEL_MSR_RANGE);
 	wrmsrq(MSR_IA32_PERF_CTL, msrval);
 }
@@ -265,7 +265,7 @@ static u32 cpu_freq_read_amd(struct acpi_pct_register *not_used)
 {
 	u64 val;
 
-	rdmsrq(MSR_AMD_PERF_CTL, val);
+	val = rdmsrq(MSR_AMD_PERF_CTL);
 	return (u32)val;
 }
 
diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c
index d4ff8b228f86..34ba2bde0f1f 100644
--- a/drivers/cpufreq/amd-pstate.c
+++ b/drivers/cpufreq/amd-pstate.c
@@ -596,8 +596,8 @@ static inline bool amd_pstate_sample(struct amd_cpudata *cpudata)
 	unsigned long flags;
 
 	local_irq_save(flags);
-	rdmsrq(MSR_IA32_APERF, aperf);
-	rdmsrq(MSR_IA32_MPERF, mperf);
+	aperf = rdmsrq(MSR_IA32_APERF);
+	mperf = rdmsrq(MSR_IA32_MPERF);
 	tsc = rdtsc();
 
 	if (cpudata->prev.mperf == mperf || cpudata->prev.tsc == tsc) {
diff --git a/drivers/cpufreq/e_powersaver.c b/drivers/cpufreq/e_powersaver.c
index 54689ebadeb2..cc05494ac316 100644
--- a/drivers/cpufreq/e_powersaver.c
+++ b/drivers/cpufreq/e_powersaver.c
@@ -99,7 +99,7 @@ static unsigned int eps_get(unsigned int cpu)
 		return 0;
 
 	/* Return current frequency */
-	rdmsrq(MSR_IA32_PERF_STATUS, val);
+	val = rdmsrq(MSR_IA32_PERF_STATUS);
 	return centaur->fsb * ((val >> 8) & 0xff);
 }
 
@@ -111,11 +111,11 @@ static int eps_set_state(struct eps_cpu_data *centaur,
 	int i;
 
 	/* Wait while CPU is busy */
-	rdmsrq(MSR_IA32_PERF_STATUS, val);
+	val = rdmsrq(MSR_IA32_PERF_STATUS);
 	i = 0;
 	while (val & ((1 << 16) | (1 << 17))) {
 		udelay(16);
-		rdmsrq(MSR_IA32_PERF_STATUS, val);
+		val = rdmsrq(MSR_IA32_PERF_STATUS);
 		i++;
 		if (unlikely(i > 64)) {
 			return -ENODEV;
@@ -127,7 +127,7 @@ static int eps_set_state(struct eps_cpu_data *centaur,
 	i = 0;
 	do {
 		udelay(16);
-		rdmsrq(MSR_IA32_PERF_STATUS, val);
+		val = rdmsrq(MSR_IA32_PERF_STATUS);
 		i++;
 		if (unlikely(i > 64)) {
 			return -ENODEV;
@@ -139,7 +139,7 @@ static int eps_set_state(struct eps_cpu_data *centaur,
 	u8 current_multiplier, current_voltage;
 
 	/* Print voltage and multiplier */
-	rdmsrq(MSR_IA32_PERF_STATUS, val);
+	val = rdmsrq(MSR_IA32_PERF_STATUS);
 	current_voltage = val & 0xff;
 	pr_info("Current voltage = %dmV\n", current_voltage * 16 + 700);
 	current_multiplier = (val >> 8) & 0xff;
@@ -194,12 +194,12 @@ static int eps_cpu_init(struct cpufreq_policy *policy)
 
 	switch (c->x86_model) {
 	case 10:
-		rdmsrq(0x1153, val);
+		val = rdmsrq(0x1153);
 		brand = (((val >> 2) ^ val) >> 18) & 3;
 		pr_cont("Model A ");
 		break;
 	case 13:
-		rdmsrq(0x1154, val);
+		val = rdmsrq(0x1154);
 		brand = (((val >> 4) ^ (val >> 2))) & 0x000000ff;
 		pr_cont("Model D ");
 		break;
@@ -223,12 +223,12 @@ static int eps_cpu_init(struct cpufreq_policy *policy)
 		return -ENODEV;
 	}
 	/* Enable Enhanced PowerSaver */
-	rdmsrq(MSR_IA32_MISC_ENABLE, val);
+	val = rdmsrq(MSR_IA32_MISC_ENABLE);
 	if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
 		val |= MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP;
 		wrmsrq(MSR_IA32_MISC_ENABLE, val);
 		/* Can be locked at 0 */
-		rdmsrq(MSR_IA32_MISC_ENABLE, val);
+		val = rdmsrq(MSR_IA32_MISC_ENABLE);
 		if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
 			pr_info("Can't enable Enhanced PowerSaver\n");
 			return -ENODEV;
@@ -236,7 +236,7 @@ static int eps_cpu_init(struct cpufreq_policy *policy)
 	}
 
 	/* Print voltage and multiplier */
-	rdmsrq(MSR_IA32_PERF_STATUS, val);
+	val = rdmsrq(MSR_IA32_PERF_STATUS);
 	current_voltage = val & 0xff;
 	pr_info("Current voltage = %dmV\n", current_voltage * 16 + 700);
 	current_multiplier = (val >> 8) & 0xff;
diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c
index 7ce5981cbae5..2ea55a1f6cc2 100644
--- a/drivers/cpufreq/intel_pstate.c
+++ b/drivers/cpufreq/intel_pstate.c
@@ -600,7 +600,7 @@ static bool turbo_is_disabled(void)
 {
 	u64 misc_en;
 
-	rdmsrq(MSR_IA32_MISC_ENABLE, misc_en);
+	misc_en = rdmsrq(MSR_IA32_MISC_ENABLE);
 
 	return !!(misc_en & MSR_IA32_MISC_ENABLE_TURBO_DISABLE);
 }
@@ -1391,7 +1391,7 @@ static void set_power_ctl_ee_state(bool input)
 
 	guard(mutex)(&intel_pstate_driver_lock);
 
-	rdmsrq(MSR_IA32_POWER_CTL, power_ctl);
+	power_ctl = rdmsrq(MSR_IA32_POWER_CTL);
 	if (input) {
 		power_ctl &= ~BIT(MSR_IA32_POWER_CTL_BIT_EE);
 		power_ctl_ee_state = POWER_CTL_EE_ENABLE;
@@ -1768,7 +1768,7 @@ static ssize_t show_energy_efficiency(struct kobject *kobj, struct kobj_attribut
 	u64 power_ctl;
 	int enable;
 
-	rdmsrq(MSR_IA32_POWER_CTL, power_ctl);
+	power_ctl = rdmsrq(MSR_IA32_POWER_CTL);
 	enable = !!(power_ctl & BIT(MSR_IA32_POWER_CTL_BIT_EE));
 	return sprintf(buf, "%d\n", !enable);
 }
@@ -2062,7 +2062,7 @@ static int atom_get_min_pstate(int not_used)
 {
 	u64 value;
 
-	rdmsrq(MSR_ATOM_CORE_RATIOS, value);
+	value = rdmsrq(MSR_ATOM_CORE_RATIOS);
 	return (value >> 8) & 0x7F;
 }
 
@@ -2070,7 +2070,7 @@ static int atom_get_max_pstate(int not_used)
 {
 	u64 value;
 
-	rdmsrq(MSR_ATOM_CORE_RATIOS, value);
+	value = rdmsrq(MSR_ATOM_CORE_RATIOS);
 	return (value >> 16) & 0x7F;
 }
 
@@ -2078,7 +2078,7 @@ static int atom_get_turbo_pstate(int not_used)
 {
 	u64 value;
 
-	rdmsrq(MSR_ATOM_CORE_TURBO_RATIOS, value);
+	value = rdmsrq(MSR_ATOM_CORE_TURBO_RATIOS);
 	return value & 0x7F;
 }
 
@@ -2109,7 +2109,7 @@ static int silvermont_get_scaling(void)
 	static int silvermont_freq_table[] = {
 		83300, 100000, 133300, 116700, 80000};
 
-	rdmsrq(MSR_FSB_FREQ, value);
+	value = rdmsrq(MSR_FSB_FREQ);
 	i = value & 0x7;
 	WARN_ON(i > 4);
 
@@ -2125,7 +2125,7 @@ static int airmont_get_scaling(void)
 		83300, 100000, 133300, 116700, 80000,
 		93300, 90000, 88900, 87500};
 
-	rdmsrq(MSR_FSB_FREQ, value);
+	value = rdmsrq(MSR_FSB_FREQ);
 	i = value & 0xF;
 	WARN_ON(i > 8);
 
@@ -2136,7 +2136,7 @@ static void atom_get_vid(struct cpudata *cpudata)
 {
 	u64 value;
 
-	rdmsrq(MSR_ATOM_CORE_VIDS, value);
+	value = rdmsrq(MSR_ATOM_CORE_VIDS);
 	cpudata->vid.min = int_tofp((value >> 8) & 0x7f);
 	cpudata->vid.max = int_tofp((value >> 16) & 0x7f);
 	cpudata->vid.ratio = div_fp(
@@ -2144,7 +2144,7 @@ static void atom_get_vid(struct cpudata *cpudata)
 		int_tofp(cpudata->pstate.max_pstate -
 			cpudata->pstate.min_pstate));
 
-	rdmsrq(MSR_ATOM_CORE_TURBO_VIDS, value);
+	value = rdmsrq(MSR_ATOM_CORE_TURBO_VIDS);
 	cpudata->vid.turbo = value & 0x7f;
 }
 
@@ -2456,8 +2456,8 @@ static inline bool intel_pstate_sample(struct cpudata *cpu, u64 time)
 	u64 tsc;
 
 	local_irq_save(flags);
-	rdmsrq(MSR_IA32_APERF, aperf);
-	rdmsrq(MSR_IA32_MPERF, mperf);
+	aperf = rdmsrq(MSR_IA32_APERF);
+	mperf = rdmsrq(MSR_IA32_MPERF);
 	tsc = rdtsc();
 	if (cpu->prev_mperf == mperf || cpu->prev_tsc == tsc) {
 		local_irq_restore(flags);
@@ -3646,7 +3646,7 @@ static bool __init intel_pstate_platform_pwr_mgmt_exists(void)
 
 	id = x86_match_cpu(intel_pstate_cpu_oob_ids);
 	if (id) {
-		rdmsrq(MSR_MISC_PWR_MGMT, misc_pwr);
+		misc_pwr = rdmsrq(MSR_MISC_PWR_MGMT);
 		if (misc_pwr & BITMASK_OOB) {
 			pr_debug("Bit 8 or 18 in the MISC_PWR_MGMT MSR set\n");
 			pr_debug("P states are controlled in Out of Band mode by the firmware/hardware\n");
@@ -3702,7 +3702,7 @@ static bool intel_pstate_hwp_is_enabled(void)
 {
 	u64 value;
 
-	rdmsrq(MSR_PM_ENABLE, value);
+	value = rdmsrq(MSR_PM_ENABLE);
 	return !!(value & 0x1);
 }
 
@@ -3767,7 +3767,7 @@ static bool hwp_check_dec(void)
 {
 	u64 power_ctl;
 
-	rdmsrq(MSR_IA32_POWER_CTL, power_ctl);
+	power_ctl = rdmsrq(MSR_IA32_POWER_CTL);
 	return !!(power_ctl & BIT(POWER_CTL_DEC_ENABLE));
 }
 
diff --git a/drivers/cpufreq/longhaul.c b/drivers/cpufreq/longhaul.c
index 4c2599264333..9bc0acc0ea69 100644
--- a/drivers/cpufreq/longhaul.c
+++ b/drivers/cpufreq/longhaul.c
@@ -121,7 +121,7 @@ static int longhaul_get_cpu_mult(void)
 	unsigned long invalue = 0;
 	u64 val;
 
-	rdmsrq(MSR_IA32_EBL_CR_POWERON, val);
+	val = rdmsrq(MSR_IA32_EBL_CR_POWERON);
 	invalue = (val & (1<<22|1<<23|1<<24|1<<25))>>22;
 	if (longhaul_version == TYPE_LONGHAUL_V2 ||
 	    longhaul_version == TYPE_POWERSAVER) {
@@ -137,7 +137,7 @@ static void do_longhaul1(unsigned int mults_index)
 {
 	union msr_bcr2 bcr2;
 
-	rdmsrq(MSR_VIA_BCR2, bcr2.val);
+	bcr2.val = rdmsrq(MSR_VIA_BCR2);
 	/* Enable software clock multiplier */
 	bcr2.bits.ESOFTBF = 1;
 	bcr2.bits.CLOCKMUL = mults_index & 0xff;
@@ -152,7 +152,7 @@ static void do_longhaul1(unsigned int mults_index)
 
 	/* Disable software clock multiplier */
 	local_irq_disable();
-	rdmsrq(MSR_VIA_BCR2, bcr2.val);
+	bcr2.val = rdmsrq(MSR_VIA_BCR2);
 	bcr2.bits.ESOFTBF = 0;
 	wrmsrq(MSR_VIA_BCR2, bcr2.val);
 }
@@ -165,7 +165,7 @@ static void do_powersaver(int cx_address, unsigned int mults_index,
 	union msr_longhaul longhaul;
 	u32 t;
 
-	rdmsrq(MSR_VIA_LONGHAUL, longhaul.val);
+	longhaul.val = rdmsrq(MSR_VIA_LONGHAUL);
 	/* Setup new frequency */
 	if (!revid_errata)
 		longhaul.bits.RevisionKey = longhaul.bits.RevisionID;
@@ -534,7 +534,7 @@ static void longhaul_setup_voltagescaling(void)
 	unsigned int j, speed, pos, kHz_step, numvscales;
 	int min_vid_speed;
 
-	rdmsrq(MSR_VIA_LONGHAUL, longhaul.val);
+	longhaul.val = rdmsrq(MSR_VIA_LONGHAUL);
 	if (!(longhaul.bits.RevisionID & 1)) {
 		pr_info("Voltage scaling not supported by CPU\n");
 		return;
@@ -836,7 +836,7 @@ static int longhaul_cpu_init(struct cpufreq_policy *policy)
 	}
 	/* Check Longhaul ver. 2 */
 	if (longhaul_version == TYPE_LONGHAUL_V2) {
-		rdmsrq(MSR_VIA_LONGHAUL, val);
+		val = rdmsrq(MSR_VIA_LONGHAUL);
 		if (val == 0)
 			/* Looks like MSR isn't present */
 			longhaul_version = TYPE_LONGHAUL_V1;
diff --git a/drivers/cpufreq/longrun.c b/drivers/cpufreq/longrun.c
index 82a7bb69c401..4b7e624ae1e5 100644
--- a/drivers/cpufreq/longrun.c
+++ b/drivers/cpufreq/longrun.c
@@ -37,14 +37,14 @@ static void longrun_get_policy(struct cpufreq_policy *policy)
 {
 	struct msr msr;
 
-	rdmsrq(MSR_TMTA_LONGRUN_FLAGS, msr.q);
+	msr.q = rdmsrq(MSR_TMTA_LONGRUN_FLAGS);
 	pr_debug("longrun flags are %x - %x\n", msr.l, msr.h);
 	if (msr.l & 0x01)
 		policy->policy = CPUFREQ_POLICY_PERFORMANCE;
 	else
 		policy->policy = CPUFREQ_POLICY_POWERSAVE;
 
-	rdmsrq(MSR_TMTA_LONGRUN_CTRL, msr.q);
+	msr.q = rdmsrq(MSR_TMTA_LONGRUN_CTRL);
 	pr_debug("longrun ctrl is %x - %x\n", msr.l, msr.h);
 	msr.l &= 0x0000007F;
 	msr.h &= 0x0000007F;
@@ -93,7 +93,7 @@ static int longrun_set_policy(struct cpufreq_policy *policy)
 		pctg_lo = pctg_hi;
 
 	/* performance or economy mode */
-	rdmsrq(MSR_TMTA_LONGRUN_FLAGS, msr.q);
+	msr.q = rdmsrq(MSR_TMTA_LONGRUN_FLAGS);
 	msr.l &= 0xFFFFFFFE;
 	switch (policy->policy) {
 	case CPUFREQ_POLICY_PERFORMANCE:
@@ -105,7 +105,7 @@ static int longrun_set_policy(struct cpufreq_policy *policy)
 	wrmsrq(MSR_TMTA_LONGRUN_FLAGS, msr.q);
 
 	/* lower and upper boundary */
-	rdmsrq(MSR_TMTA_LONGRUN_CTRL, msr.q);
+	msr.q = rdmsrq(MSR_TMTA_LONGRUN_CTRL);
 	msr.l &= 0xFFFFFF80;
 	msr.h &= 0xFFFFFF80;
 	msr.l |= pctg_lo;
@@ -177,16 +177,16 @@ static int longrun_determine_freqs(unsigned int *low_freq,
 		 * For maximum frequency, read out level zero.
 		 */
 		/* minimum */
-		rdmsrq(MSR_TMTA_LRTI_READOUT, msr.q);
+		msr.q = rdmsrq(MSR_TMTA_LRTI_READOUT);
 		msr.l = msr.h;
 		wrmsrq(MSR_TMTA_LRTI_READOUT, msr.q);
-		rdmsrq(MSR_TMTA_LRTI_VOLT_MHZ, msr.q);
+		msr.q = rdmsrq(MSR_TMTA_LRTI_VOLT_MHZ);
 		*low_freq = msr.l * 1000; /* to kHz */
 
 		/* maximum */
 		msr.l = 0;
 		wrmsrq(MSR_TMTA_LRTI_READOUT, msr.q);
-		rdmsrq(MSR_TMTA_LRTI_VOLT_MHZ, msr.q);
+		msr.q = rdmsrq(MSR_TMTA_LRTI_VOLT_MHZ);
 		*high_freq = msr.l * 1000; /* to kHz */
 
 		pr_debug("longrun table interface told %u - %u kHz\n",
@@ -203,7 +203,7 @@ static int longrun_determine_freqs(unsigned int *low_freq,
 	pr_debug("high frequency is %u kHz\n", *high_freq);
 
 	/* get current borders */
-	rdmsrq(MSR_TMTA_LONGRUN_CTRL, msr.q);
+	msr.q = rdmsrq(MSR_TMTA_LONGRUN_CTRL);
 	save.l = msr.l & 0x0000007F;
 	save.h = msr.h & 0x0000007F;
 
diff --git a/drivers/cpufreq/powernow-k7.c b/drivers/cpufreq/powernow-k7.c
index 6a930d7e6a5c..1df2a8f365a3 100644
--- a/drivers/cpufreq/powernow-k7.c
+++ b/drivers/cpufreq/powernow-k7.c
@@ -220,7 +220,7 @@ static void change_FID(int fid)
 {
 	union msr_fidvidctl fidvidctl;
 
-	rdmsrq(MSR_K7_FID_VID_CTL, fidvidctl.val);
+	fidvidctl.val = rdmsrq(MSR_K7_FID_VID_CTL);
 	if (fidvidctl.bits.FID != fid) {
 		fidvidctl.bits.SGTC = latency;
 		fidvidctl.bits.FID = fid;
@@ -235,7 +235,7 @@ static void change_VID(int vid)
 {
 	union msr_fidvidctl fidvidctl;
 
-	rdmsrq(MSR_K7_FID_VID_CTL, fidvidctl.val);
+	fidvidctl.val = rdmsrq(MSR_K7_FID_VID_CTL);
 	if (fidvidctl.bits.VID != vid) {
 		fidvidctl.bits.SGTC = latency;
 		fidvidctl.bits.VID = vid;
@@ -261,7 +261,7 @@ static int powernow_target(struct cpufreq_policy *policy, unsigned int index)
 	fid = powernow_table[index].driver_data & 0xFF;
 	vid = (powernow_table[index].driver_data & 0xFF00) >> 8;
 
-	rdmsrq(MSR_K7_FID_VID_STATUS, fidvidstatus.val);
+	fidvidstatus.val = rdmsrq(MSR_K7_FID_VID_STATUS);
 	cfid = fidvidstatus.bits.CFID;
 	freqs.old = fsb * fid_codes[cfid] / 10;
 
@@ -558,7 +558,7 @@ static unsigned int powernow_get(unsigned int cpu)
 
 	if (cpu)
 		return 0;
-	rdmsrq(MSR_K7_FID_VID_STATUS, fidvidstatus.val);
+	fidvidstatus.val = rdmsrq(MSR_K7_FID_VID_STATUS);
 	cfid = fidvidstatus.bits.CFID;
 
 	return fsb * fid_codes[cfid] / 10;
@@ -599,7 +599,7 @@ static int powernow_cpu_init(struct cpufreq_policy *policy)
 	if (policy->cpu != 0)
 		return -ENODEV;
 
-	rdmsrq(MSR_K7_FID_VID_STATUS, fidvidstatus.val);
+	fidvidstatus.val = rdmsrq(MSR_K7_FID_VID_STATUS);
 
 	recalibrate_cpu_khz();
 
diff --git a/drivers/cpufreq/powernow-k8.c b/drivers/cpufreq/powernow-k8.c
index c9bcc6f0ab7b..6097dc3e5a89 100644
--- a/drivers/cpufreq/powernow-k8.c
+++ b/drivers/cpufreq/powernow-k8.c
@@ -89,7 +89,7 @@ static int pending_bit_stuck(void)
 {
 	u64 msr;
 
-	rdmsrq(MSR_FIDVID_STATUS, msr);
+	msr = rdmsrq(MSR_FIDVID_STATUS);
 	return msr & MSR_S_LO_CHANGE_PENDING ? 1 : 0;
 }
 
@@ -107,7 +107,7 @@ static int query_current_values_with_pending_wait(struct powernow_k8_data *data)
 			pr_debug("detected change pending stuck\n");
 			return 1;
 		}
-		rdmsrq(MSR_FIDVID_STATUS, msr.q);
+		msr.q = rdmsrq(MSR_FIDVID_STATUS);
 	} while (msr.l & MSR_S_LO_CHANGE_PENDING);
 
 	data->currvid = msr.h & MSR_S_HI_CURRENT_VID;
@@ -134,7 +134,7 @@ static void fidvid_msr_init(void)
 	struct msr msr;
 	u8 fid, vid;
 
-	rdmsrq(MSR_FIDVID_STATUS, msr.q);
+	msr.q = rdmsrq(MSR_FIDVID_STATUS);
 	vid = msr.h & MSR_S_HI_CURRENT_VID;
 	fid = msr.l & MSR_S_LO_CURRENT_FID;
 	msr.l = fid | (vid << MSR_C_LO_VID_SHIFT);
@@ -293,7 +293,7 @@ static int core_voltage_pre_transition(struct powernow_k8_data *data,
 	if ((savefid < LO_FID_TABLE_TOP) && (reqfid < LO_FID_TABLE_TOP))
 		rvomult = 2;
 	rvosteps *= rvomult;
-	rdmsrq(MSR_FIDVID_STATUS, msr.q);
+	msr.q = rdmsrq(MSR_FIDVID_STATUS);
 	maxvid = 0x1f & (msr.h >> 16);
 	pr_debug("ph1 maxvid=0x%x\n", maxvid);
 	if (reqvid < maxvid) /* lower numbers are higher voltages */
diff --git a/drivers/cpufreq/speedstep-centrino.c b/drivers/cpufreq/speedstep-centrino.c
index de50fb367c6b..2e90b658bfc2 100644
--- a/drivers/cpufreq/speedstep-centrino.c
+++ b/drivers/cpufreq/speedstep-centrino.c
@@ -378,7 +378,7 @@ static int centrino_cpu_init(struct cpufreq_policy *policy)
 
 	/* Check to see if Enhanced SpeedStep is enabled, and try to
 	   enable it if not. */
-	rdmsrq(MSR_IA32_MISC_ENABLE, q);
+	q = rdmsrq(MSR_IA32_MISC_ENABLE);
 
 	if (!(q & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
 		q |= MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP;
@@ -386,7 +386,7 @@ static int centrino_cpu_init(struct cpufreq_policy *policy)
 		wrmsrq(MSR_IA32_MISC_ENABLE, q);
 
 		/* check to see if it stuck */
-		rdmsrq(MSR_IA32_MISC_ENABLE, q);
+		q = rdmsrq(MSR_IA32_MISC_ENABLE);
 		if (!(q & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
 			pr_info("couldn't enable Enhanced SpeedStep\n");
 			return -ENODEV;
diff --git a/drivers/cpufreq/speedstep-lib.c b/drivers/cpufreq/speedstep-lib.c
index 2afc3f177a29..188d459c7e2a 100644
--- a/drivers/cpufreq/speedstep-lib.c
+++ b/drivers/cpufreq/speedstep-lib.c
@@ -74,7 +74,7 @@ static unsigned int pentium3_get_frequency(enum speedstep_processor processor)
 	int i = 0, j = 0;
 
 	/* read MSR 0x2a - we only need the low 32 bits */
-	rdmsrq(MSR_IA32_EBL_CR_POWERON, msr.q);
+	msr.q = rdmsrq(MSR_IA32_EBL_CR_POWERON);
 	pr_debug("P3 - MSR_IA32_EBL_CR_POWERON: 0x%x 0x%x\n", msr.l, msr.h);
 	msr_tmp = msr_lo = msr.l;
 
@@ -112,7 +112,7 @@ static unsigned int pentiumM_get_frequency(void)
 	struct msr msr;
 	u32 msr_tmp;
 
-	rdmsrq(MSR_IA32_EBL_CR_POWERON, msr.q);
+	msr.q = rdmsrq(MSR_IA32_EBL_CR_POWERON);
 	pr_debug("PM - MSR_IA32_EBL_CR_POWERON: 0x%x 0x%x\n", msr.l, msr.h);
 
 	/* see table B-2 of 24547212.pdf */
@@ -136,7 +136,7 @@ static unsigned int pentium_core_get_frequency(void)
 	u32 msr_tmp;
 	int ret;
 
-	rdmsrq(MSR_FSB_FREQ, msr.q);
+	msr.q = rdmsrq(MSR_FSB_FREQ);
 	/* see table B-2 of 25366920.pdf */
 	switch (msr.l & 0x07) {
 	case 5:
@@ -161,7 +161,7 @@ static unsigned int pentium_core_get_frequency(void)
 		pr_err("PCORE - MSR_FSB_FREQ undefined value\n");
 	}
 
-	rdmsrq(MSR_IA32_EBL_CR_POWERON, msr.q);
+	msr.q = rdmsrq(MSR_IA32_EBL_CR_POWERON);
 	pr_debug("PCORE - MSR_IA32_EBL_CR_POWERON: 0x%x 0x%x\n",
 			msr.l, msr.h);
 
@@ -191,7 +191,7 @@ static unsigned int pentium4_get_frequency(void)
 	if (c->x86_model < 2)
 		return cpu_khz;
 
-	rdmsrq(0x2c, msr.q);
+	msr.q = rdmsrq(0x2c);
 
 	pr_debug("P4 - MSR_EBC_FREQUENCY_ID: 0x%x 0x%x\n", msr.l, msr.h);
 
@@ -348,7 +348,7 @@ enum speedstep_processor speedstep_detect_processor(void)
 
 		/* all mobile PIII Coppermines have FSB 100 MHz
 		 * ==> sort out a few desktop PIIIs. */
-		rdmsrq(MSR_IA32_EBL_CR_POWERON, msr.q);
+		msr.q = rdmsrq(MSR_IA32_EBL_CR_POWERON);
 		pr_debug("Coppermine: MSR_IA32_EBL_CR_POWERON is 0x%x, 0x%x\n",
 				msr.l, msr.h);
 		msr.l &= 0x00c0000;
@@ -361,7 +361,7 @@ enum speedstep_processor speedstep_detect_processor(void)
 		 * it has SpeedStep technology if either
 		 * bit 56 or 57 is set
 		 */
-		rdmsrq(MSR_IA32_PLATFORM_ID, msr.q);
+		msr.q = rdmsrq(MSR_IA32_PLATFORM_ID);
 		pr_debug("Coppermine: MSR_IA32_PLATFORM ID is 0x%x, 0x%x\n",
 				msr.l, msr.h);
 		if ((msr.h & (1<<18)) &&
diff --git a/drivers/edac/amd64_edac.c b/drivers/edac/amd64_edac.c
index c6aa69dbd9fb..ef2fc2be954a 100644
--- a/drivers/edac/amd64_edac.c
+++ b/drivers/edac/amd64_edac.c
@@ -2956,13 +2956,13 @@ static void dct_read_mc_regs(struct amd64_pvt *pvt)
 	 * Retrieve TOP_MEM and TOP_MEM2; no masking off of reserved bits since
 	 * those are Read-As-Zero.
 	 */
-	rdmsrq(MSR_K8_TOP_MEM1, pvt->top_mem);
+	pvt->top_mem = rdmsrq(MSR_K8_TOP_MEM1);
 	edac_dbg(0, "  TOP_MEM:  0x%016llx\n", pvt->top_mem);
 
 	/* Check first whether TOP_MEM2 is enabled: */
-	rdmsrq(MSR_AMD64_SYSCFG, msr_val);
+	msr_val = rdmsrq(MSR_AMD64_SYSCFG);
 	if (msr_val & BIT(21)) {
-		rdmsrq(MSR_K8_TOP_MEM2, pvt->top_mem2);
+		pvt->top_mem2 = rdmsrq(MSR_K8_TOP_MEM2);
 		edac_dbg(0, "  TOP_MEM2: 0x%016llx\n", pvt->top_mem2);
 	} else {
 		edac_dbg(0, "  TOP_MEM2 disabled\n");
diff --git a/drivers/gpio/gpio-cs5535.c b/drivers/gpio/gpio-cs5535.c
index a97ec7561889..e9cc577e94b7 100644
--- a/drivers/gpio/gpio-cs5535.c
+++ b/drivers/gpio/gpio-cs5535.c
@@ -148,7 +148,7 @@ int cs5535_gpio_set_irq(unsigned group, unsigned irq)
 	if (group > 7 || irq > 15)
 		return -EINVAL;
 
-	rdmsrq(MSR_PIC_ZSEL_HIGH, val.q);
+	val.q = rdmsrq(MSR_PIC_ZSEL_HIGH);
 
 	val.l &= ~(0xF << (group * 4));
 	val.l |= (irq & 0xF) << (group * 4);
diff --git a/drivers/hv/mshv_vtl_main.c b/drivers/hv/mshv_vtl_main.c
index 6e3c11c68171..f678eda1641c 100644
--- a/drivers/hv/mshv_vtl_main.c
+++ b/drivers/hv/mshv_vtl_main.c
@@ -599,7 +599,7 @@ static int mshv_vtl_get_set_reg(struct hv_register_assoc *regs, bool set)
 			if (set)
 				wrmsrq(reg_table[i].msr_addr, *reg64);
 			else
-				rdmsrq(reg_table[i].msr_addr, *reg64);
+				*reg64 = rdmsrq(reg_table[i].msr_addr);
 		}
 		return 0;
 	}
diff --git a/drivers/hwmon/hwmon-vid.c b/drivers/hwmon/hwmon-vid.c
index dee42c163d92..cfb3a2975de4 100644
--- a/drivers/hwmon/hwmon-vid.c
+++ b/drivers/hwmon/hwmon-vid.c
@@ -243,10 +243,10 @@ static u8 get_via_model_d_vrm(void)
 		"C7-M", "C7", "Eden", "C7-D"
 	};
 
-	rdmsrq(0x198, msr);
+	msr = rdmsrq(0x198);
 	vid = (msr >> 32) & 0xff;
 
-	rdmsrq(0x1154, msr);
+	msr = rdmsrq(0x1154);
 	brand = ((msr >> 4) ^ (msr >> 2)) & 0x03;
 
 	if (vid > 0x3f) {
diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c
index 651408df9c24..82faaf771b2d 100644
--- a/drivers/idle/intel_idle.c
+++ b/drivers/idle/intel_idle.c
@@ -2125,35 +2125,35 @@ static void __init bxt_idle_state_table_update(void)
 	unsigned long long msr;
 	unsigned int usec;
 
-	rdmsrq(MSR_PKGC6_IRTL, msr);
+	msr = rdmsrq(MSR_PKGC6_IRTL);
 	usec = irtl_2_usec(msr);
 	if (usec) {
 		bxt_cstates[2].exit_latency = usec;
 		bxt_cstates[2].target_residency = usec;
 	}
 
-	rdmsrq(MSR_PKGC7_IRTL, msr);
+	msr = rdmsrq(MSR_PKGC7_IRTL);
 	usec = irtl_2_usec(msr);
 	if (usec) {
 		bxt_cstates[3].exit_latency = usec;
 		bxt_cstates[3].target_residency = usec;
 	}
 
-	rdmsrq(MSR_PKGC8_IRTL, msr);
+	msr = rdmsrq(MSR_PKGC8_IRTL);
 	usec = irtl_2_usec(msr);
 	if (usec) {
 		bxt_cstates[4].exit_latency = usec;
 		bxt_cstates[4].target_residency = usec;
 	}
 
-	rdmsrq(MSR_PKGC9_IRTL, msr);
+	msr = rdmsrq(MSR_PKGC9_IRTL);
 	usec = irtl_2_usec(msr);
 	if (usec) {
 		bxt_cstates[5].exit_latency = usec;
 		bxt_cstates[5].target_residency = usec;
 	}
 
-	rdmsrq(MSR_PKGC10_IRTL, msr);
+	msr = rdmsrq(MSR_PKGC10_IRTL);
 	usec = irtl_2_usec(msr);
 	if (usec) {
 		bxt_cstates[6].exit_latency = usec;
@@ -2181,7 +2181,7 @@ static void __init sklh_idle_state_table_update(void)
 	if ((mwait_substates & (0xF << 28)) == 0)
 		return;
 
-	rdmsrq(MSR_PKG_CST_CONFIG_CONTROL, msr);
+	msr = rdmsrq(MSR_PKG_CST_CONFIG_CONTROL);
 
 	/* PC10 is not enabled in PKG C-state limit */
 	if ((msr & 0xF) != 8)
@@ -2193,7 +2193,7 @@ static void __init sklh_idle_state_table_update(void)
 	/* if SGX is present */
 	if (ebx & (1 << 2)) {
 
-		rdmsrq(MSR_IA32_FEAT_CTL, msr);
+		msr = rdmsrq(MSR_IA32_FEAT_CTL);
 
 		/* if SGX is enabled */
 		if (msr & (1 << 18))
@@ -2213,7 +2213,7 @@ static bool __init skx_is_pc6_disabled(void)
 {
 	u64 msr;
 
-	rdmsrq(MSR_PKG_CST_CONFIG_CONTROL, msr);
+	msr = rdmsrq(MSR_PKG_CST_CONFIG_CONTROL);
 
 	/*
 	 * 000b: C0/C1 (no package C-state support)
@@ -2467,7 +2467,7 @@ static void auto_demotion_disable(void)
 {
 	unsigned long long msr_bits;
 
-	rdmsrq(MSR_PKG_CST_CONFIG_CONTROL, msr_bits);
+	msr_bits = rdmsrq(MSR_PKG_CST_CONFIG_CONTROL);
 	msr_bits &= ~auto_demotion_disable_flags;
 	wrmsrq(MSR_PKG_CST_CONFIG_CONTROL, msr_bits);
 }
@@ -2476,7 +2476,7 @@ static void c1e_promotion_enable(void)
 {
 	unsigned long long msr_bits;
 
-	rdmsrq(MSR_IA32_POWER_CTL, msr_bits);
+	msr_bits = rdmsrq(MSR_IA32_POWER_CTL);
 	msr_bits |= 0x2;
 	wrmsrq(MSR_IA32_POWER_CTL, msr_bits);
 }
@@ -2485,7 +2485,7 @@ static void c1e_promotion_disable(void)
 {
 	unsigned long long msr_bits;
 
-	rdmsrq(MSR_IA32_POWER_CTL, msr_bits);
+	msr_bits = rdmsrq(MSR_IA32_POWER_CTL);
 	msr_bits &= ~0x2;
 	wrmsrq(MSR_IA32_POWER_CTL, msr_bits);
 }
@@ -2554,7 +2554,7 @@ static void intel_c1_demotion_toggle(void *enable)
 {
 	unsigned long long msr_val;
 
-	rdmsrq(MSR_PKG_CST_CONFIG_CONTROL, msr_val);
+	msr_val = rdmsrq(MSR_PKG_CST_CONFIG_CONTROL);
 	/*
 	 * Enable/disable C1 undemotion along with C1 demotion, as this is the
 	 * most sensible configuration in general.
@@ -2594,7 +2594,7 @@ static ssize_t intel_c1_demotion_show(struct device *dev,
 	 * Read the MSR value for a CPU and assume it is the same for all CPUs. Any other
 	 * configuration would be a BIOS bug.
 	 */
-	rdmsrq(MSR_PKG_CST_CONFIG_CONTROL, msr_val);
+	msr_val = rdmsrq(MSR_PKG_CST_CONFIG_CONTROL);
 	return sysfs_emit(buf, "%d\n", !!(msr_val & NHM_C1_AUTO_DEMOTE));
 }
 static DEVICE_ATTR_RW(intel_c1_demotion);
diff --git a/drivers/misc/cs5535-mfgpt.c b/drivers/misc/cs5535-mfgpt.c
index 9abfc44f70f4..9006b59858ff 100644
--- a/drivers/misc/cs5535-mfgpt.c
+++ b/drivers/misc/cs5535-mfgpt.c
@@ -83,7 +83,7 @@ int cs5535_mfgpt_toggle_event(struct cs5535_mfgpt_timer *timer, int cmp,
 		return -EIO;
 	}
 
-	rdmsrq(msr, val.q);
+	val.q = rdmsrq(msr);
 
 	if (enable)
 		val.l |= mask;
@@ -114,7 +114,7 @@ int cs5535_mfgpt_set_irq(struct cs5535_mfgpt_timer *timer, int cmp, int *irq,
 	 * IRQ of the 1st. This can only happen if forcing an IRQ, calling this
 	 * with *irq==0 is safe. Currently there _are_ no 2 drivers.
 	 */
-	rdmsrq(MSR_PIC_ZSEL_LOW, zsel.q);
+	zsel.q = rdmsrq(MSR_PIC_ZSEL_LOW);
 	shift = ((cmp == MFGPT_CMP1 ? 0 : 4) + timer->nr % 4) * 4;
 	if (((zsel.l >> shift) & 0xF) == 2)
 		return -EIO;
@@ -128,7 +128,7 @@ int cs5535_mfgpt_set_irq(struct cs5535_mfgpt_timer *timer, int cmp, int *irq,
 	/* Can't use IRQ if it's 0 (=disabled), 2, or routed to LPC */
 	if (*irq < 1 || *irq == 2 || *irq > 15)
 		return -EIO;
-	rdmsrq(MSR_PIC_IRQM_LPC, lpc.q);
+	lpc.q = rdmsrq(MSR_PIC_IRQM_LPC);
 	if (lpc.l & (1 << *irq))
 		return -EIO;
 
diff --git a/drivers/mtd/nand/raw/cs553x_nand.c b/drivers/mtd/nand/raw/cs553x_nand.c
index 0b872b1e3b04..2ed13666b48f 100644
--- a/drivers/mtd/nand/raw/cs553x_nand.c
+++ b/drivers/mtd/nand/raw/cs553x_nand.c
@@ -351,20 +351,20 @@ static int __init cs553x_init(void)
 		return -ENXIO;
 
 	/* If it doesn't have the CS553[56], abort */
-	rdmsrq(MSR_DIVIL_GLD_CAP, val);
+	val = rdmsrq(MSR_DIVIL_GLD_CAP);
 	val &= ~0xFFULL;
 	if (val != CAP_CS5535 && val != CAP_CS5536)
 		return -ENXIO;
 
 	/* If it doesn't have the NAND controller enabled, abort */
-	rdmsrq(MSR_DIVIL_BALL_OPTS, val);
+	val = rdmsrq(MSR_DIVIL_BALL_OPTS);
 	if (val & PIN_OPT_IDE) {
 		pr_info("CS553x NAND controller: Flash I/O not enabled in MSR_DIVIL_BALL_OPTS.\n");
 		return -ENXIO;
 	}
 
 	for (i = 0; i < NR_CS553X_CONTROLLERS; i++) {
-		rdmsrq(MSR_DIVIL_LBAR_FLSH0 + i, val);
+		val = rdmsrq(MSR_DIVIL_LBAR_FLSH0 + i);
 
 		if ((val & (FLSH_LBAR_EN|FLSH_NOR_NAND)) == (FLSH_LBAR_EN|FLSH_NOR_NAND))
 			err = cs553x_init_one(i, !!(val & FLSH_MEM_IO), val & 0xFFFFFFFF);
diff --git a/drivers/platform/x86/intel/ifs/load.c b/drivers/platform/x86/intel/ifs/load.c
index 50f1fdf7dfed..6320a0e45d38 100644
--- a/drivers/platform/x86/intel/ifs/load.c
+++ b/drivers/platform/x86/intel/ifs/load.c
@@ -129,7 +129,7 @@ static void copy_hashes_authenticate_chunks(struct work_struct *work)
 	msrs = ifs_get_test_msrs(dev);
 	/* run scan hash copy */
 	wrmsrq(msrs->copy_hashes, ifs_hash_ptr);
-	rdmsrq(msrs->copy_hashes_status, hashes_status.data);
+	hashes_status.data = rdmsrq(msrs->copy_hashes_status);
 
 	/* enumerate the scan image information */
 	num_chunks = hashes_status.num_chunks;
@@ -151,7 +151,7 @@ static void copy_hashes_authenticate_chunks(struct work_struct *work)
 		linear_addr |= i;
 
 		wrmsrq(msrs->copy_chunks, linear_addr);
-		rdmsrq(msrs->copy_chunks_status, chunk_status.data);
+		chunk_status.data = rdmsrq(msrs->copy_chunks_status);
 
 		ifsd->valid_chunks = chunk_status.valid_chunks;
 		err_code = chunk_status.error_code;
@@ -197,7 +197,7 @@ static int copy_hashes_authenticate_chunks_gen2(struct device *dev)
 
 	if (need_copy_scan_hashes(ifsd)) {
 		wrmsrq(msrs->copy_hashes, ifs_hash_ptr);
-		rdmsrq(msrs->copy_hashes_status, hashes_status.data);
+		hashes_status.data = rdmsrq(msrs->copy_hashes_status);
 
 		/* enumerate the scan image information */
 		chunk_size = hashes_status.chunk_size * SZ_1K;
@@ -218,7 +218,7 @@ static int copy_hashes_authenticate_chunks_gen2(struct device *dev)
 
 	if (ifsd->generation >= IFS_GEN_STRIDE_AWARE) {
 		wrmsrq(msrs->test_ctrl, INVALIDATE_STRIDE);
-		rdmsrq(msrs->copy_chunks_status, chunk_status.data);
+		chunk_status.data = rdmsrq(msrs->copy_chunks_status);
 		if (chunk_status.valid_chunks != 0) {
 			dev_err(dev, "Couldn't invalidate installed stride - %d\n",
 				chunk_status.valid_chunks);
@@ -241,7 +241,7 @@ static int copy_hashes_authenticate_chunks_gen2(struct device *dev)
 			local_irq_disable();
 			wrmsrq(msrs->copy_chunks, (u64)chunk_table);
 			local_irq_enable();
-			rdmsrq(msrs->copy_chunks_status, chunk_status.data);
+			chunk_status.data = rdmsrq(msrs->copy_chunks_status);
 			err_code = chunk_status.error_code;
 		} while (err_code == AUTH_INTERRUPTED_ERROR && --retry_count);
 
diff --git a/drivers/platform/x86/intel/ifs/runtest.c b/drivers/platform/x86/intel/ifs/runtest.c
index dfc119d7354d..46e1ea944ce7 100644
--- a/drivers/platform/x86/intel/ifs/runtest.c
+++ b/drivers/platform/x86/intel/ifs/runtest.c
@@ -211,7 +211,7 @@ static int doscan(void *data)
 	 * are processed in a single pass) before it retires.
 	 */
 	wrmsrq(MSR_ACTIVATE_SCAN, params->activate->data);
-	rdmsrq(MSR_SCAN_STATUS, status.data);
+	status.data = rdmsrq(MSR_SCAN_STATUS);
 
 	trace_ifs_status(ifsd->cur_batch, start, stop, status.data);
 
@@ -324,7 +324,7 @@ static int do_array_test(void *data)
 	if (cpu == first) {
 		wrmsrq(MSR_ARRAY_BIST, command->data);
 		/* Pass back the result of the test */
-		rdmsrq(MSR_ARRAY_BIST, command->data);
+		command->data = rdmsrq(MSR_ARRAY_BIST);
 	}
 
 	return 0;
@@ -376,7 +376,7 @@ static int do_array_test_gen1(void *status)
 
 	if (cpu == first) {
 		wrmsrq(MSR_ARRAY_TRIGGER, ARRAY_GEN1_TEST_ALL_ARRAYS);
-		rdmsrq(MSR_ARRAY_STATUS, *((u64 *)status));
+		*((u64 *)status) = rdmsrq(MSR_ARRAY_STATUS);
 	}
 
 	return 0;
@@ -528,7 +528,7 @@ static int dosbaf(void *data)
 	 * during the "execution" of the WRMSR.
 	 */
 	wrmsrq(MSR_ACTIVATE_SBAF, run_params->activate->data);
-	rdmsrq(MSR_SBAF_STATUS, status.data);
+	status.data = rdmsrq(MSR_SBAF_STATUS);
 	trace_ifs_sbaf(ifsd->cur_batch, *run_params->activate, status);
 
 	/* Pass back the result of the test */
diff --git a/drivers/platform/x86/intel/pmc/cnp.c b/drivers/platform/x86/intel/pmc/cnp.c
index efea4e1ba52b..44979e680361 100644
--- a/drivers/platform/x86/intel/pmc/cnp.c
+++ b/drivers/platform/x86/intel/pmc/cnp.c
@@ -228,7 +228,7 @@ static void disable_c1_auto_demote(void *unused)
 	int cpunum = smp_processor_id();
 	u64 val;
 
-	rdmsrq(MSR_PKG_CST_CONFIG_CONTROL, val);
+	val = rdmsrq(MSR_PKG_CST_CONFIG_CONTROL);
 	per_cpu(pkg_cst_config, cpunum) = val;
 	val &= ~NHM_C1_AUTO_DEMOTE;
 	wrmsrq(MSR_PKG_CST_CONFIG_CONTROL, val);
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c
index 22745b217c6f..909c9e25112d 100644
--- a/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c
@@ -40,7 +40,7 @@ static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter,
 	/* Poll for rb bit == 0 */
 	retries = OS_MAILBOX_RETRY_COUNT;
 	do {
-		rdmsrq(MSR_OS_MAILBOX_INTERFACE, data);
+		data = rdmsrq(MSR_OS_MAILBOX_INTERFACE);
 		if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) {
 			ret = -EBUSY;
 			continue;
@@ -65,7 +65,7 @@ static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter,
 	/* Poll for rb bit == 0 */
 	retries = OS_MAILBOX_RETRY_COUNT;
 	do {
-		rdmsrq(MSR_OS_MAILBOX_INTERFACE, data);
+		data = rdmsrq(MSR_OS_MAILBOX_INTERFACE);
 		if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) {
 			ret = -EBUSY;
 			continue;
@@ -75,7 +75,7 @@ static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter,
 			return -ENXIO;
 
 		if (response_data) {
-			rdmsrq(MSR_OS_MAILBOX_DATA, data);
+			data = rdmsrq(MSR_OS_MAILBOX_DATA);
 			*response_data = data;
 		}
 		ret = 0;
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
index 5ded0e25b6c7..2b86e9969aef 100644
--- a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
+++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
@@ -561,7 +561,7 @@ static bool disable_dynamic_sst_features(void)
 	if (!cpu_feature_enabled(X86_FEATURE_HWP))
 		return true;
 
-	rdmsrq(MSR_PM_ENABLE, value);
+	value = rdmsrq(MSR_PM_ENABLE);
 	return !(value & 0x1);
 }
 
diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c
index b1b2d9caba7b..79c07f23ba0b 100644
--- a/drivers/platform/x86/intel_ips.c
+++ b/drivers/platform/x86/intel_ips.c
@@ -370,7 +370,7 @@ static void ips_cpu_raise(struct ips_driver *ips)
 	if (!ips->cpu_turbo_enabled)
 		return;
 
-	rdmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+	turbo_override = rdmsrq(TURBO_POWER_CURRENT_LIMIT);
 
 	cur_tdp_limit = turbo_override & TURBO_TDP_MASK;
 	new_tdp_limit = cur_tdp_limit + 8; /* 1W increase */
@@ -405,7 +405,7 @@ static void ips_cpu_lower(struct ips_driver *ips)
 	u64 turbo_override;
 	u16 cur_limit, new_limit;
 
-	rdmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+	turbo_override = rdmsrq(TURBO_POWER_CURRENT_LIMIT);
 
 	cur_limit = turbo_override & TURBO_TDP_MASK;
 	new_limit = cur_limit - 8; /* 1W decrease */
@@ -437,7 +437,7 @@ static void do_enable_cpu_turbo(void *data)
 {
 	u64 perf_ctl;
 
-	rdmsrq(IA32_PERF_CTL, perf_ctl);
+	perf_ctl = rdmsrq(IA32_PERF_CTL);
 	if (perf_ctl & IA32_PERF_TURBO_DIS) {
 		perf_ctl &= ~IA32_PERF_TURBO_DIS;
 		wrmsrq(IA32_PERF_CTL, perf_ctl);
@@ -475,7 +475,7 @@ static void do_disable_cpu_turbo(void *data)
 {
 	u64 perf_ctl;
 
-	rdmsrq(IA32_PERF_CTL, perf_ctl);
+	perf_ctl = rdmsrq(IA32_PERF_CTL);
 	if (!(perf_ctl & IA32_PERF_TURBO_DIS)) {
 		perf_ctl |= IA32_PERF_TURBO_DIS;
 		wrmsrq(IA32_PERF_CTL, perf_ctl);
@@ -1215,7 +1215,7 @@ static int cpu_clamp_show(struct seq_file *m, void *data)
 	u64 turbo_override;
 	int tdp, tdc;
 
-	rdmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+	turbo_override = rdmsrq(TURBO_POWER_CURRENT_LIMIT);
 
 	tdp = (int)(turbo_override & TURBO_TDP_MASK);
 	tdc = (int)((turbo_override & TURBO_TDC_MASK) >> TURBO_TDC_SHIFT);
@@ -1290,7 +1290,7 @@ static struct ips_mcp_limits *ips_detect_cpu(struct ips_driver *ips)
 		return NULL;
 	}
 
-	rdmsrq(IA32_MISC_ENABLE, misc_en);
+	misc_en = rdmsrq(IA32_MISC_ENABLE);
 	/*
 	 * If the turbo enable bit isn't set, we shouldn't try to enable/disable
 	 * turbo manually or we'll get an illegal MSR access, even though
@@ -1312,7 +1312,7 @@ static struct ips_mcp_limits *ips_detect_cpu(struct ips_driver *ips)
 		return NULL;
 	}
 
-	rdmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_power);
+	turbo_power = rdmsrq(TURBO_POWER_CURRENT_LIMIT);
 	tdp = turbo_power & TURBO_TDP_MASK;
 
 	/* Sanity check TDP against CPU */
@@ -1496,7 +1496,7 @@ static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	 * Check PLATFORM_INFO MSR to make sure this chip is
 	 * turbo capable.
 	 */
-	rdmsrq(PLATFORM_INFO, platform_info);
+	platform_info = rdmsrq(PLATFORM_INFO);
 	if (!(platform_info & PLATFORM_TDP)) {
 		dev_err(&dev->dev, "platform indicates TDP override unavailable, aborting\n");
 		return -ENODEV;
@@ -1529,7 +1529,7 @@ static int ips_probe(struct pci_dev *dev, const struct pci_device_id *id)
 	ips->mgta_val = thm_readw(THM_MGTA);
 
 	/* Save turbo limits & ratios */
-	rdmsrq(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit);
+	ips->orig_turbo_limit = rdmsrq(TURBO_POWER_CURRENT_LIMIT);
 
 	ips_disable_cpu_turbo(ips);
 	ips->cpu_turbo_enabled = false;
@@ -1596,7 +1596,7 @@ static void ips_remove(struct pci_dev *dev)
 	if (ips->gpu_turbo_disable)
 		symbol_put(i915_gpu_turbo_disable);
 
-	rdmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_override);
+	turbo_override = rdmsrq(TURBO_POWER_CURRENT_LIMIT);
 	turbo_override &= ~(TURBO_TDC_OVR_EN | TURBO_TDP_OVR_EN);
 	wrmsrq(TURBO_POWER_CURRENT_LIMIT, turbo_override);
 	wrmsrq(TURBO_POWER_CURRENT_LIMIT, ips->orig_turbo_limit);
diff --git a/drivers/powercap/intel_rapl_msr.c b/drivers/powercap/intel_rapl_msr.c
index a34543e66446..5d840de67c2b 100644
--- a/drivers/powercap/intel_rapl_msr.c
+++ b/drivers/powercap/intel_rapl_msr.c
@@ -176,7 +176,7 @@ static int rapl_msr_read_raw(int cpu, struct reg_action *ra, bool pmu_ctx)
 	 * from any CPU in the package.
 	 */
 	if (pmu_ctx) {
-		rdmsrq(ra->reg.msr, ra->value);
+		ra->value = rdmsrq(ra->reg.msr);
 		goto out;
 	}
 
diff --git a/drivers/thermal/intel/intel_hfi.c b/drivers/thermal/intel/intel_hfi.c
index 3273b8fe3d4d..6b2801aaa745 100644
--- a/drivers/thermal/intel/intel_hfi.c
+++ b/drivers/thermal/intel/intel_hfi.c
@@ -285,7 +285,7 @@ void intel_hfi_process_event(__u64 pkg_therm_status_msr_val)
 	if (!raw_spin_trylock(&hfi_instance->event_lock))
 		return;
 
-	rdmsrq(MSR_IA32_PACKAGE_THERM_STATUS, msr);
+	msr = rdmsrq(MSR_IA32_PACKAGE_THERM_STATUS);
 	hfi = msr & PACKAGE_THERM_STATUS_HFI_UPDATED;
 	if (!hfi) {
 		raw_spin_unlock(&hfi_instance->event_lock);
@@ -357,7 +357,7 @@ static void hfi_enable(void)
 {
 	u64 msr_val;
 
-	rdmsrq(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val);
+	msr_val = rdmsrq(MSR_IA32_HW_FEEDBACK_CONFIG);
 	msr_val |= HW_FEEDBACK_CONFIG_HFI_ENABLE_BIT;
 	wrmsrq(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val);
 }
@@ -378,7 +378,7 @@ static void hfi_disable(void)
 	u64 msr_val;
 	int i;
 
-	rdmsrq(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val);
+	msr_val = rdmsrq(MSR_IA32_HW_FEEDBACK_CONFIG);
 	msr_val &= ~HW_FEEDBACK_CONFIG_HFI_ENABLE_BIT;
 	wrmsrq(MSR_IA32_HW_FEEDBACK_CONFIG, msr_val);
 
@@ -389,7 +389,7 @@ static void hfi_disable(void)
 	 * memory.
 	 */
 	for (i = 0; i < 2000; i++) {
-		rdmsrq(MSR_IA32_PACKAGE_THERM_STATUS, msr_val);
+		msr_val = rdmsrq(MSR_IA32_PACKAGE_THERM_STATUS);
 		if (msr_val & PACKAGE_THERM_STATUS_HFI_UPDATED)
 			break;
 
diff --git a/drivers/thermal/intel/therm_throt.c b/drivers/thermal/intel/therm_throt.c
index 4d4e81601d17..3ead57f45510 100644
--- a/drivers/thermal/intel/therm_throt.c
+++ b/drivers/thermal/intel/therm_throt.c
@@ -297,7 +297,7 @@ static void get_therm_status(int level, bool *proc_hot, u8 *temp)
 	else
 		msr = MSR_IA32_PACKAGE_THERM_STATUS;
 
-	rdmsrq(msr, msr_val);
+	msr_val = rdmsrq(msr);
 	if (msr_val & THERM_STATUS_PROCHOT_LOG)
 		*proc_hot = true;
 	else
@@ -543,7 +543,7 @@ static int check_directed_thermal_pkg_intr_ack(void)
 	 * Wait 15ms to be safe.
 	 */
 	do {
-		rdmsrq(MSR_IA32_PACKAGE_THERM_STATUS, msr_val);
+		msr_val = rdmsrq(MSR_IA32_PACKAGE_THERM_STATUS);
 		udelay(1);
 	} while (!(msr_val & PACKAGE_THERM_STATUS_DPTI_ACK) && --count);
 
@@ -561,7 +561,7 @@ static void config_directed_thermal_pkg_intr(void *info)
 	bool enable = *((bool *)info);
 	u64 msr_val;
 
-	rdmsrq(MSR_IA32_THERM_INTERRUPT, msr_val);
+	msr_val = rdmsrq(MSR_IA32_THERM_INTERRUPT);
 
 	if (enable)
 		msr_val |= THERM_INT_DPTI_ENABLE;
@@ -931,7 +931,7 @@ void intel_thermal_interrupt(void)
 	if (cpu_feature_enabled(X86_FEATURE_HWP))
 		notify_hwp_interrupt();
 
-	rdmsrq(MSR_IA32_THERM_STATUS, msr_val);
+	msr_val = rdmsrq(MSR_IA32_THERM_STATUS);
 
 	/* Check for violation of core thermal thresholds*/
 	notify_thresholds(msr_val);
@@ -946,7 +946,7 @@ void intel_thermal_interrupt(void)
 					CORE_LEVEL);
 
 	if (this_cpu_has(X86_FEATURE_PTS)) {
-		rdmsrq(MSR_IA32_PACKAGE_THERM_STATUS, msr_val);
+		msr_val = rdmsrq(MSR_IA32_PACKAGE_THERM_STATUS);
 		/* check violations of package thermal thresholds */
 		notify_package_thresholds(msr_val);
 		therm_throt_process(msr_val & PACKAGE_THERM_STATUS_PROCHOT,
@@ -1004,7 +1004,7 @@ void intel_init_thermal(struct cpuinfo_x86 *c)
 	 * be some SMM goo which handles it, so we can't even put a handler
 	 * since it might be delivered via SMI already:
 	 */
-	rdmsrq(MSR_IA32_MISC_ENABLE, val.q);
+	val.q = rdmsrq(MSR_IA32_MISC_ENABLE);
 
 	val.h = lvtthmr_init;
 	/*
@@ -1030,7 +1030,7 @@ void intel_init_thermal(struct cpuinfo_x86 *c)
 	/* early Pentium M models use different method for enabling TM2 */
 	if (cpu_has(c, X86_FEATURE_TM2)) {
 		if (c->x86 == 6 && (c->x86_model == 9 || c->x86_model == 13)) {
-			rdmsrq(MSR_THERM2_CTL, val.q);
+			val.q = rdmsrq(MSR_THERM2_CTL);
 			if (val.l & MSR_THERM2_CTL_TM_SELECT)
 				tm2 = 1;
 		} else if (val.l & MSR_IA32_MISC_ENABLE_TM2)
@@ -1044,7 +1044,7 @@ void intel_init_thermal(struct cpuinfo_x86 *c)
 	thermal_intr_init_core_clear_mask();
 	thermal_intr_init_pkg_clear_mask();
 
-	rdmsrq(MSR_IA32_THERM_INTERRUPT, val.q);
+	val.q = rdmsrq(MSR_IA32_THERM_INTERRUPT);
 	if (cpu_has(c, X86_FEATURE_PLN) && !int_pln_enable) {
 		val.l |= THERM_INT_LOW_ENABLE | THERM_INT_HIGH_ENABLE;
 		val.l &= ~THERM_INT_PLN_ENABLE;
@@ -1056,7 +1056,7 @@ void intel_init_thermal(struct cpuinfo_x86 *c)
 	wrmsrq(MSR_IA32_THERM_INTERRUPT, val.q);
 
 	if (cpu_has(c, X86_FEATURE_PTS)) {
-		rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q);
+		val.q = rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT);
 		if (cpu_has(c, X86_FEATURE_PLN) && !int_pln_enable) {
 			val.l |= PACKAGE_THERM_INT_LOW_ENABLE |
 				 PACKAGE_THERM_INT_HIGH_ENABLE;
@@ -1071,13 +1071,13 @@ void intel_init_thermal(struct cpuinfo_x86 *c)
 		wrmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q);
 
 		if (cpu_has(c, X86_FEATURE_HFI)) {
-			rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q);
+			val.q = rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT);
 			wrmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT,
 			       val.q | PACKAGE_THERM_INT_HFI_ENABLE);
 		}
 	}
 
-	rdmsrq(MSR_IA32_MISC_ENABLE, val.q);
+	val.q = rdmsrq(MSR_IA32_MISC_ENABLE);
 	wrmsrq(MSR_IA32_MISC_ENABLE, val.q | MSR_IA32_MISC_ENABLE_TM1);
 
 	pr_info_once("CPU0: Thermal monitoring enabled (%s)\n",
diff --git a/drivers/thermal/intel/x86_pkg_temp_thermal.c b/drivers/thermal/intel/x86_pkg_temp_thermal.c
index 43fd5bdf1d8d..11ca647802dc 100644
--- a/drivers/thermal/intel/x86_pkg_temp_thermal.c
+++ b/drivers/thermal/intel/x86_pkg_temp_thermal.c
@@ -187,7 +187,7 @@ static inline void enable_pkg_thres_interrupt(void)
 	u8 thres_0, thres_1;
 	struct msr val;
 
-	rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q);
+	val.q = rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT);
 	/* only enable/disable if it had valid threshold value */
 	thres_0 = (val.l & THERM_MASK_THRESHOLD0) >> THERM_SHIFT_THRESHOLD0;
 	thres_1 = (val.l & THERM_MASK_THRESHOLD1) >> THERM_SHIFT_THRESHOLD1;
@@ -203,7 +203,7 @@ static inline void disable_pkg_thres_interrupt(void)
 {
 	struct msr val;
 
-	rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q);
+	val.q = rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT);
 
 	val.l &= ~(THERM_INT_THRESHOLD0_ENABLE | THERM_INT_THRESHOLD1_ENABLE);
 	wrmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, val.q);
@@ -356,7 +356,7 @@ static int pkg_temp_thermal_device_add(unsigned int cpu)
 		goto out_unregister_tz;
 
 	/* Store MSR value for package thermal interrupt, to restore at exit */
-	rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT, zonedev->msr_pkg_therm);
+	zonedev->msr_pkg_therm = rdmsrq(MSR_IA32_PACKAGE_THERM_INTERRUPT);
 
 	cpumask_set_cpu(cpu, &zonedev->cpumask);
 	raw_spin_lock_irq(&pkg_temp_lock);
diff --git a/drivers/video/fbdev/geode/display_gx.c b/drivers/video/fbdev/geode/display_gx.c
index b93aa21a1d2b..511cb7bf98e9 100644
--- a/drivers/video/fbdev/geode/display_gx.c
+++ b/drivers/video/fbdev/geode/display_gx.c
@@ -26,7 +26,7 @@ unsigned int gx_frame_buffer_size(void)
 		struct msr msr;
 
 		/* The number of pages is (PMAX - PMIN)+1 */
-		rdmsrq(MSR_GLIU_P2D_RO0, msr.q);
+		msr.q = rdmsrq(MSR_GLIU_P2D_RO0);
 
 		/* PMAX */
 		val = ((msr.h & 0xff) << 12) | ((msr.l & 0xfff00000) >> 20);
diff --git a/drivers/video/fbdev/geode/gxfb_core.c b/drivers/video/fbdev/geode/gxfb_core.c
index 8d69be7c9d31..e0a3447f2b1b 100644
--- a/drivers/video/fbdev/geode/gxfb_core.c
+++ b/drivers/video/fbdev/geode/gxfb_core.c
@@ -378,7 +378,7 @@ static int gxfb_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 
 	/* Figure out if this is a TFT or CRT part */
 
-	rdmsrq(MSR_GX_GLD_MSR_CONFIG, val);
+	val = rdmsrq(MSR_GX_GLD_MSR_CONFIG);
 
 	if ((val & MSR_GX_GLD_MSR_CONFIG_FP) == MSR_GX_GLD_MSR_CONFIG_FP)
 		par->enable_crt = 0;
diff --git a/drivers/video/fbdev/geode/lxfb_ops.c b/drivers/video/fbdev/geode/lxfb_ops.c
index f5f1134cae9a..980a782214a2 100644
--- a/drivers/video/fbdev/geode/lxfb_ops.c
+++ b/drivers/video/fbdev/geode/lxfb_ops.c
@@ -127,7 +127,7 @@ static void lx_set_dotpll(u32 pllval)
 	struct msr dotpll;
 	int i;
 
-	rdmsrq(MSR_GLCP_DOTPLL, dotpll.q);
+	dotpll.q = rdmsrq(MSR_GLCP_DOTPLL);
 
 	if ((dotpll.l & MSR_GLCP_DOTPLL_LOCK) && (dotpll.h == pllval))
 		return;
@@ -145,7 +145,7 @@ static void lx_set_dotpll(u32 pllval)
 	/* Now, loop for the lock bit */
 
 	for (i = 0; i < 1000; i++) {
-		rdmsrq(MSR_GLCP_DOTPLL, dotpll.q);
+		dotpll.q = rdmsrq(MSR_GLCP_DOTPLL);
 		if (dotpll.l & MSR_GLCP_DOTPLL_LOCK)
 			break;
 	}
@@ -316,7 +316,7 @@ unsigned int lx_framebuffer_size(void)
 		struct msr msr;
 
 		/* The number of pages is (PMAX - PMIN)+1 */
-		rdmsrq(MSR_GLIU_P2D_RO0, msr.q);
+		msr.q = rdmsrq(MSR_GLIU_P2D_RO0);
 
 		/* PMAX */
 		val = ((msr.h & 0xff) << 12) | ((msr.l & 0xfff00000) >> 20);
@@ -359,7 +359,7 @@ void lx_set_mode(struct fb_info *info)
 
 	/* Set output mode */
 
-	rdmsrq(MSR_LX_GLD_MSR_CONFIG, msrval);
+	msrval = rdmsrq(MSR_LX_GLD_MSR_CONFIG);
 	msrval &= ~MSR_LX_GLD_MSR_CONFIG_FMT;
 
 	if (par->output & OUTPUT_PANEL) {
@@ -420,7 +420,7 @@ void lx_set_mode(struct fb_info *info)
 
 	/* Set default watermark values */
 
-	rdmsrq(MSR_LX_SPARE_MSR, msrval);
+	msrval = rdmsrq(MSR_LX_SPARE_MSR);
 
 	msrval &= ~(MSR_LX_SPARE_MSR_DIS_CFIFO_HGO
 			| MSR_LX_SPARE_MSR_VFIFO_ARB_SEL
@@ -592,10 +592,10 @@ static void lx_save_regs(struct lxfb_par *par)
 	} while ((i & GP_BLT_STATUS_PB) || !(i & GP_BLT_STATUS_CE));
 
 	/* save MSRs */
-	rdmsrq(MSR_LX_MSR_PADSEL, par->msr.padsel);
-	rdmsrq(MSR_GLCP_DOTPLL, par->msr.dotpll);
-	rdmsrq(MSR_LX_GLD_MSR_CONFIG, par->msr.dfglcfg);
-	rdmsrq(MSR_LX_SPARE_MSR, par->msr.dcspare);
+	par->msr.padsel = rdmsrq(MSR_LX_MSR_PADSEL);
+	par->msr.dotpll = rdmsrq(MSR_GLCP_DOTPLL);
+	par->msr.dfglcfg = rdmsrq(MSR_LX_GLD_MSR_CONFIG);
+	par->msr.dcspare = rdmsrq(MSR_LX_SPARE_MSR);
 
 	write_dc(par, DC_UNLOCK, DC_UNLOCK_UNLOCK);
 
diff --git a/drivers/video/fbdev/geode/suspend_gx.c b/drivers/video/fbdev/geode/suspend_gx.c
index 6c0a526ee8a2..2fa20acba62e 100644
--- a/drivers/video/fbdev/geode/suspend_gx.c
+++ b/drivers/video/fbdev/geode/suspend_gx.c
@@ -21,8 +21,8 @@ static void gx_save_regs(struct gxfb_par *par)
 	} while (i & (GP_BLT_STATUS_BLT_PENDING | GP_BLT_STATUS_BLT_BUSY));
 
 	/* save MSRs */
-	rdmsrq(MSR_GX_MSR_PADSEL, par->msr.padsel);
-	rdmsrq(MSR_GLCP_DOTPLL, par->msr.dotpll);
+	par->msr.padsel = rdmsrq(MSR_GX_MSR_PADSEL);
+	par->msr.dotpll = rdmsrq(MSR_GLCP_DOTPLL);
 
 	write_dc(par, DC_UNLOCK, DC_UNLOCK_UNLOCK);
 
@@ -43,7 +43,7 @@ static void gx_set_dotpll(uint32_t dotpll_hi)
 	struct msr dotpll;
 	int i;
 
-	rdmsrq(MSR_GLCP_DOTPLL, dotpll.q);
+	dotpll.q = rdmsrq(MSR_GLCP_DOTPLL);
 	dotpll.l |= MSR_GLCP_DOTPLL_DOTRESET;
 	dotpll.l &= ~MSR_GLCP_DOTPLL_BYPASS;
 	dotpll.h = dotpll_hi;
@@ -51,7 +51,7 @@ static void gx_set_dotpll(uint32_t dotpll_hi)
 
 	/* wait for the PLL to lock */
 	for (i = 0; i < 200; i++) {
-		rdmsrq(MSR_GLCP_DOTPLL, dotpll.q);
+		dotpll.q = rdmsrq(MSR_GLCP_DOTPLL);
 		if (dotpll.l & MSR_GLCP_DOTPLL_LOCK)
 			break;
 		udelay(1);
diff --git a/drivers/video/fbdev/geode/video_gx.c b/drivers/video/fbdev/geode/video_gx.c
index 5717c3356949..b2cfadaf3ff8 100644
--- a/drivers/video/fbdev/geode/video_gx.c
+++ b/drivers/video/fbdev/geode/video_gx.c
@@ -142,8 +142,8 @@ void gx_set_dclk_frequency(struct fb_info *info)
 		}
 	}
 
-	rdmsrq(MSR_GLCP_SYS_RSTPLL, sys_rstpll);
-	rdmsrq(MSR_GLCP_DOTPLL, dotpll);
+	sys_rstpll = rdmsrq(MSR_GLCP_SYS_RSTPLL);
+	dotpll = rdmsrq(MSR_GLCP_DOTPLL);
 
 	/* Program new M, N and P. */
 	dotpll &= 0x00000000ffffffffull;
@@ -167,7 +167,7 @@ void gx_set_dclk_frequency(struct fb_info *info)
 
 	/* Wait for LOCK bit. */
 	do {
-		rdmsrq(MSR_GLCP_DOTPLL, dotpll);
+		dotpll = rdmsrq(MSR_GLCP_DOTPLL);
 	} while (timeout-- && !(dotpll & MSR_GLCP_DOTPLL_LOCK));
 }
 
@@ -180,7 +180,7 @@ gx_configure_tft(struct fb_info *info)
 
 	/* Set up the DF pad select MSR */
 
-	rdmsrq(MSR_GX_MSR_PADSEL, val);
+	val = rdmsrq(MSR_GX_MSR_PADSEL);
 	val &= ~MSR_GX_MSR_PADSEL_MASK;
 	val |= MSR_GX_MSR_PADSEL_TFT;
 	wrmsrq(MSR_GX_MSR_PADSEL, val);
diff --git a/include/linux/cs5535.h b/include/linux/cs5535.h
index 5ec2aca537bb..fef236cab7d8 100644
--- a/include/linux/cs5535.h
+++ b/include/linux/cs5535.h
@@ -51,7 +51,7 @@ static inline int cs5535_pic_unreqz_select_high(unsigned int group,
 {
 	struct msr val;
 
-	rdmsrq(MSR_PIC_ZSEL_HIGH, val.q);
+	val.q = rdmsrq(MSR_PIC_ZSEL_HIGH);
 	val.l &= ~(0xF << (group * 4));
 	val.l |= (irq & 0xF) << (group * 4);
 	wrmsrq(MSR_PIC_ZSEL_HIGH, val.q);
-- 
2.55.0



From richard at nod.at  Wed Aug 19 03:48:28 2026
From: richard at nod.at (Richard Weinberger)
Date: Wed, 19 Aug 2026 12:48:28 +0200 (CEST)
Subject: [PATCH 0/2] Backport fixes for UBI
In-Reply-To: <20260818-ubi-backports-v1-0-324d6816d2ae@atmark-techno.com>
References: <20260818-ubi-backports-v1-0-324d6816d2ae@atmark-techno.com>
Message-ID: <1200997805.161072.1787136508436.JavaMail.zimbra@nod.at>

----- Urspr?ngliche Mail -----
> Von: "Dominique Martinet" <dominique.martinet at atmark-techno.com>
> While looking at recent UBI patches I found these two commits that I
> believe are worth backporting.
> 
> We're not using fastmap so I don't care much about the leak there, but
> reducing wear is always good to take
> 
> Thanks!
> 
> Signed-off-by: Dominique Martinet <dominique.martinet at atmark-techno.com>
> ---
> Cheng Ming Lin (1):
>      mtd: ubi: skip programming unused bits in ubi headers
> 
> Liyuan Pang (1):
>      ubi: fastmap: fix ubi->fm memory leak

While porting these back is not wrong, I have a hard time to see how the
current stables rules apply here.

"mtd: ubi: skip programming unused bits in ubi headers" does not fix anything.
It's a pure optimization for future flashes, UBI worked since ever without this
change.

"ubi: fastmap: fix ubi->fm memory leak" fixes a memory leak, yes. But only in a failure
path at attach time. Userspace cannot trigger this.

Thanks,
//richard


From runyu.xiao at seu.edu.cn  Wed Aug 19 07:03:35 2026
From: runyu.xiao at seu.edu.cn (Runyu Xiao)
Date: Wed, 19 Aug 2026 22:03:35 +0800
Subject: [PATCH v4 0/2] mtd: spi-nor: core: Fix RWW wait locking
In-Reply-To: <0a15f6e8-f1ca-4945-9bd5-2b64a2802a58@linaro.org>
References: <0a15f6e8-f1ca-4945-9bd5-2b64a2802a58@linaro.org>
Message-ID: <20260819140337.36775-1-runyu.xiao@seu.edu.cn>

The RWW wait helpers must not block while evaluating the wait
condition. In the current code, spi_nor_rww_start_exclusive() can leave
nor->lock held on a false return. Also, if a killable RWW wait is
interrupted after spi_nor_prep(), the controller setup must be undone.

Patch 1 switches the four RWW start helpers to conditional scoped mutex
guards so the wait condition never sleeps and the mutex is always
released before returning.
Patch 2 unprepares the controller on interrupted waits in the three
spi_nor_prep_and_lock() helpers.

Runyu Xiao (2):
  mtd: spi-nor: core: Fix mutex leak in spi_nor_rww_start_exclusive()
  mtd: spi-nor: core: Unprepare after interrupted RWW wait

 drivers/mtd/spi-nor/core.c | 93 ++++++++++++++++++++++----------------
 1 file changed, 54 insertions(+), 39 deletions(-)

base-commit: 2b533e775aec580cf60074417f4ca00ac9cf3580
-- 
2.34.1


From runyu.xiao at seu.edu.cn  Wed Aug 19 07:03:37 2026
From: runyu.xiao at seu.edu.cn (Runyu Xiao)
Date: Wed, 19 Aug 2026 22:03:37 +0800
Subject: [PATCH v4 2/2] mtd: spi-nor: core: Unprepare after interrupted RWW wait
In-Reply-To: <20260819140337.36775-1-runyu.xiao@seu.edu.cn>
References: <0a15f6e8-f1ca-4945-9bd5-2b64a2802a58@linaro.org>
 <20260819140337.36775-1-runyu.xiao@seu.edu.cn>
Message-ID: <20260819140337.36775-3-runyu.xiao@seu.edu.cn>

spi_nor_prep_and_lock() calls spi_nor_prep() before waiting. If the wait
is interrupted, the controller setup is left active.

Call spi_nor_unprep() before returning an error from each helper.

Fixes: 74df43b3f626 ("mtd: spi-nor: Enhance locking to support reads while writes")
Cc: stable at vger.kernel.org
Signed-off-by: Runyu Xiao <runyu.xiao at seu.edu.cn>

Changes in v4:
- Add the cleanup for interrupted RWW waits as a separate patch.
- Use the original RWW locking commit in Fixes.
---
 drivers/mtd/spi-nor/core.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index d5c6a925862e..52264e24eb4b 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -1348,6 +1348,9 @@ int spi_nor_prep_and_lock(struct spi_nor *nor)
 		ret = wait_event_killable(nor->rww.wait,
 					  spi_nor_rww_start_exclusive(nor));
 
+	if (ret)
+		spi_nor_unprep(nor);
+
 	return ret;
 }
 
@@ -1421,6 +1424,9 @@ static int spi_nor_prep_and_lock_pe(struct spi_nor *nor, loff_t start, size_t le
 		ret = wait_event_killable(nor->rww.wait,
 					  spi_nor_rww_start_pe(nor, start, len));
 
+	if (ret)
+		spi_nor_unprep(nor);
+
 	return ret;
 }
 
@@ -1496,6 +1502,9 @@ static int spi_nor_prep_and_lock_rd(struct spi_nor *nor, loff_t start, size_t le
 		ret = wait_event_killable(nor->rww.wait,
 					  spi_nor_rww_start_rd(nor, start, len));
 
+	if (ret)
+		spi_nor_unprep(nor);
+
 	return ret;
 }
 
-- 
2.34.1



From runyu.xiao at seu.edu.cn  Wed Aug 19 07:03:36 2026
From: runyu.xiao at seu.edu.cn (Runyu Xiao)
Date: Wed, 19 Aug 2026 22:03:36 +0800
Subject: [PATCH v4 1/2] mtd: spi-nor: core: Fix mutex leak in spi_nor_rww_start_exclusive()
In-Reply-To: <20260819140337.36775-1-runyu.xiao@seu.edu.cn>
References: <0a15f6e8-f1ca-4945-9bd5-2b64a2802a58@linaro.org>
 <20260819140337.36775-1-runyu.xiao@seu.edu.cn>
Message-ID: <20260819140337.36775-2-runyu.xiao@seu.edu.cn>

The RWW wait helpers must not block while evaluating the condition.
spi_nor_rww_start_exclusive() used mutex_lock() directly and could return
with nor->lock still held.

Switch the four RWW start helpers to conditional scoped mutex guards so
the wait condition never sleeps and nor->lock is released before return.

Fixes: 74df43b3f626 ("mtd: spi-nor: Enhance locking to support reads while writes")
Cc: stable at vger.kernel.org
Signed-off-by: Runyu Xiao <runyu.xiao at seu.edu.cn>

Changes in v4:
- Use the original RWW locking commit in Fixes and update the subject.
- Apply the locking fix to all RWW start helpers used as wait conditions.
- Keep commit 03e7bb864d9a ("mtd: spi-nor: use scope-based mutex
  cleanup helpers") as a stable prerequisite because it adds cleanup.h.
---
 drivers/mtd/spi-nor/core.c | 84 ++++++++++++++++++++------------------
 1 file changed, 45 insertions(+), 39 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ccf4396cdcd0..d5c6a925862e 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -1273,14 +1273,15 @@ static bool spi_nor_rww_start_io(struct spi_nor *nor)
 {
 	struct spi_nor_rww *rww = &nor->rww;
 
-	guard(mutex)(&nor->lock);
-
-	if (rww->ongoing_io)
-		return false;
+	scoped_guard(mutex_try, &nor->lock) {
+		if (rww->ongoing_io)
+			return false;
 
-	rww->ongoing_io = true;
+		rww->ongoing_io = true;
+		return true;
+	}
 
-	return true;
+	return false;
 }
 
 static void spi_nor_rww_end_io(struct spi_nor *nor)
@@ -1310,16 +1311,17 @@ static bool spi_nor_rww_start_exclusive(struct spi_nor *nor)
 {
 	struct spi_nor_rww *rww = &nor->rww;
 
-	mutex_lock(&nor->lock);
-
-	if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe)
-		return false;
+	scoped_guard(mutex_try, &nor->lock) {
+		if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe)
+			return false;
 
-	rww->ongoing_io = true;
-	rww->ongoing_rd = true;
-	rww->ongoing_pe = true;
+		rww->ongoing_io = true;
+		rww->ongoing_rd = true;
+		rww->ongoing_pe = true;
+		return true;
+	}
 
-	return true;
+	return false;
 }
 
 static void spi_nor_rww_end_exclusive(struct spi_nor *nor)
@@ -1369,23 +1371,25 @@ static bool spi_nor_rww_start_pe(struct spi_nor *nor, loff_t start, size_t len)
 	u8 first, last;
 	int bank;
 
-	guard(mutex)(&nor->lock);
+	scoped_guard(mutex_try, &nor->lock) {
+		if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe)
+			return false;
 
-	if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe)
-		return false;
+		spi_nor_offset_to_banks(nor->params->bank_size, start, len,
+					&first, &last);
+		for (bank = first; bank <= last; bank++) {
+			if (rww->used_banks & BIT(bank))
+				return false;
 
-	spi_nor_offset_to_banks(nor->params->bank_size, start, len, &first, &last);
-	for (bank = first; bank <= last; bank++) {
-		if (rww->used_banks & BIT(bank))
-			return false;
+			used_banks |= BIT(bank);
+		}
 
-		used_banks |= BIT(bank);
+		rww->used_banks |= used_banks;
+		rww->ongoing_pe = true;
+		return true;
 	}
 
-	rww->used_banks |= used_banks;
-	rww->ongoing_pe = true;
-
-	return true;
+	return false;
 }
 
 static void spi_nor_rww_end_pe(struct spi_nor *nor, loff_t start, size_t len)
@@ -1440,24 +1444,26 @@ static bool spi_nor_rww_start_rd(struct spi_nor *nor, loff_t start, size_t len)
 	u8 first, last;
 	int bank;
 
-	guard(mutex)(&nor->lock);
+	scoped_guard(mutex_try, &nor->lock) {
+		if (rww->ongoing_io || rww->ongoing_rd)
+			return false;
 
-	if (rww->ongoing_io || rww->ongoing_rd)
-		return false;
+		spi_nor_offset_to_banks(nor->params->bank_size, start, len,
+					&first, &last);
+		for (bank = first; bank <= last; bank++) {
+			if (rww->used_banks & BIT(bank))
+				return false;
 
-	spi_nor_offset_to_banks(nor->params->bank_size, start, len, &first, &last);
-	for (bank = first; bank <= last; bank++) {
-		if (rww->used_banks & BIT(bank))
-			return false;
+			used_banks |= BIT(bank);
+		}
 
-		used_banks |= BIT(bank);
+		rww->used_banks |= used_banks;
+		rww->ongoing_io = true;
+		rww->ongoing_rd = true;
+		return true;
 	}
 
-	rww->used_banks |= used_banks;
-	rww->ongoing_io = true;
-	rww->ongoing_rd = true;
-
-	return true;
+	return false;
 }
 
 static void spi_nor_rww_end_rd(struct spi_nor *nor, loff_t start, size_t len)
-- 
2.34.1


From dave.hansen at intel.com  Wed Aug 19 07:40:09 2026
From: dave.hansen at intel.com (Dave Hansen)
Date: Wed, 19 Aug 2026 07:40:09 -0700
Subject: [PATCH v2 00/13] x86/msr: Drop 32-bit MSR interfaces
In-Reply-To: <20260819102314.1499258-1-jgross@suse.com>
References: <20260819102314.1499258-1-jgross@suse.com>
Message-ID: <bd9e5b6e-07f2-459d-bca6-e290585060aa@intel.com>

Hey Juergen,

These look great. Thanks for doing it!

The only wonky thing is how we're going to actually merge it. We
obviously can't do patches 10-11 until all the "stop using" patches have
been applied.

I obviously 100% realize that it's during the merge window, but any acks
that the maintainers of 5-9 provide in the next few weeks would be super
helpful. Or, if any of those maintainers want to cherry pick their
subsystem's stuff out of this series and apply it, that would be great too.

But, the last time we did some MSR function munging, the tip tree
carried most of the patches. I would expect we'll have a repeat of that
here too. I'll pencil in the idea of just merging the lot around -rc1 time.


From jgross at suse.com  Wed Aug 19 08:21:18 2026
From: jgross at suse.com (=?UTF-8?B?SsO8cmdlbiBHcm/Dnw==?=)
Date: Wed, 19 Aug 2026 17:21:18 +0200
Subject: [PATCH v2 00/13] x86/msr: Drop 32-bit MSR interfaces
In-Reply-To: <bd9e5b6e-07f2-459d-bca6-e290585060aa@intel.com>
References: <20260819102314.1499258-1-jgross@suse.com>
 <bd9e5b6e-07f2-459d-bca6-e290585060aa@intel.com>
Message-ID: <819fea2f-7df9-484a-9565-80578cfdfa3c@suse.com>

On 19.08.26 16:40, Dave Hansen wrote:
> Hey Juergen,
> 
> These look great. Thanks for doing it!
> 
> The only wonky thing is how we're going to actually merge it. We
> obviously can't do patches 10-11 until all the "stop using" patches have
> been applied.
> 
> I obviously 100% realize that it's during the merge window, but any acks
> that the maintainers of 5-9 provide in the next few weeks would be super
> helpful. Or, if any of those maintainers want to cherry pick their
> subsystem's stuff out of this series and apply it, that would be great too.
> 
> But, the last time we did some MSR function munging, the tip tree
> carried most of the patches. I would expect we'll have a repeat of that
> here too. I'll pencil in the idea of just merging the lot around -rc1 time.

Ingo requested this series to be carried through the tip tree.


Juergen

-------------- next part --------------
A non-text attachment was scrubbed...
Name: OpenPGP_0xB0DE9DD628BF132F.asc
Type: application/pgp-keys
Size: 3683 bytes
Desc: OpenPGP public key
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260819/d5ebe238/attachment.bin>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: OpenPGP_signature.asc
Type: application/pgp-signature
Size: 495 bytes
Desc: OpenPGP digital signature
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260819/d5ebe238/attachment.sig>

From seanjc at google.com  Wed Aug 19 08:33:45 2026
From: seanjc at google.com (Sean Christopherson)
Date: Wed, 19 Aug 2026 08:33:45 -0700
Subject: [PATCH v2 00/13] x86/msr: Drop 32-bit MSR interfaces
In-Reply-To: <bd9e5b6e-07f2-459d-bca6-e290585060aa@intel.com>
References: <20260819102314.1499258-1-jgross@suse.com> <bd9e5b6e-07f2-459d-bca6-e290585060aa@intel.com>
Message-ID: <aoXM2f1YTwgVj8KL@google.com>

On Wed, Aug 19, 2026, Dave Hansen wrote:
> Hey Juergen,
> 
> These look great. Thanks for doing it!
> 
> The only wonky thing is how we're going to actually merge it. We
> obviously can't do patches 10-11 until all the "stop using" patches have
> been applied.
> 
> I obviously 100% realize that it's during the merge window, but any acks
> that the maintainers of 5-9 provide in the next few weeks would be super
> helpful. Or, if any of those maintainers want to cherry pick their
> subsystem's stuff out of this series and apply it, that would be great too.
> 
> But, the last time we did some MSR function munging, the tip tree
> carried most of the patches. I would expect we'll have a repeat of that
> here too. I'll pencil in the idea of just merging the lot around -rc1 time.

Note, patch 12 "treewide: convert rdmsrq() from a macro to an inline function"
is going to conflict with the KVM changes for 7.3; kvm_access_xstate_msr() is
getting moved from x86.c to msrs.c.  Somewhat surprisingly, AFAICT that's the
only conflict with KVM's code movement, so it's probably not strictly required
to send a new version after the KVM pull request?


From maciej.andrzejewski at m-works.net  Wed Aug 19 08:55:10 2026
From: maciej.andrzejewski at m-works.net (Maciej Andrzejewski ICEYE)
Date: Wed, 19 Aug 2026 17:55:10 +0200
Subject: [PATCH v2] gpiolib: of: Only apply the SPI CS quirk to SPI buses
Message-ID: <20260819155502.3934837-1-maciej.andrzejewski@m-works.net>

The legacy SPI chip-select polarity quirk in of_gpio_flags_quirks() is
keyed on nothing but the property name "cs-gpios". That name is not
exclusive to SPI: nand-controller.yaml documents the very same property
for NAND controllers, and rawnand_dt_parse_gpio_cs() requests those
lines with gpiod_count(dev, "cs"), which gpiolib expands to "cs-gpios".
A NAND controller therefore has SPI chip-select semantics forced onto
its chip selects, and any chip node whose first reg cell matches a GPIO
index is silently flipped to active low. The NAND core requests the
descriptors GPIOD_OUT_HIGH and drivers assert with a logical 0, so the
inversion leaves the die permanently deselected.

The example in nand-controller.yaml is itself affected: it pairs a
native chip select with a GPIO one and gives the second chip a reg of 1,
which trips the quirk whenever CONFIG_SPI_MASTER is enabled. In-tree the
collision is real but latent. 36 board trees, all Atmel/Microchip at91,
give their nand at 3 controller a cs-gpios line, and they escape only
because the sole child of those nodes is a partitions container with no
reg for the quirk to match against.

Device tree carries no bus type marker, so identify the bus from two
hints. Properties of an SPI peripheral are namespaced with "spi-"
(spi-max-frequency, spi-cpol, spi-cs-high and the rest of
spi-peripheral-props.yaml), whereas a NAND chip node carries only reg,
nand-* and its partition table. That is a convention rather than a
guarantee, since compatible and reg are the only properties
spi-controller.yaml makes mandatory for a peripheral, so let the
controller settle the remaining cases: spi-controller.yaml constrains
the controller nodename to ^spi(@.*|-[0-9]+)?$, which makes the name the
one bus marker every conforming controller has to carry. A peripheral
that carries nothing but compatible and reg therefore keeps its active
low default through its parent. Skip the quirk only when neither test
matches.

Both tests were scored over every board device tree in the kernel,
expanded with the same cpp and scripts/dtc pipeline the build uses, at
v7.2-rc7. All 3620 trees under arch/*/boot/dts expand; 503 of them hold
at least one GPIO chip select that can reach the quirk, 765 such chip
selects in total. 761 are matched by both tests, and none are matched by
neither, so no in-tree board changes behaviour.

One needs the property scan on its own: psc at 11400 on ac14xx, a
fsl,mpc5121-psc-spi named after the hardware block rather than the bus,
whose m25p128 at 0 child carries spi-max-frequency.

Three need the nodename on its own, all peripherals with no "spi-"
property of any kind:

  - panel at 0 under the spi-gpio controller on rk3566-anbernic-rg503
  - can at 0, an mcp251xfd, under ecspi3 on imx8mn-vhip4-evalboard-v1
  - spi at 1 under ecspi1 on imx53-ppd

The two tests are complementary, so both are needed to keep every
in-tree board working. A tree that names its controller after the
hardware block and gives it a peripheral with no "spi-" property would
still lose the quirk. No in-tree board does, and such a controller is
already outside the nodename pattern spi-controller.yaml requires.

of_gpio_spi_cs_get_count() in this file identifies SPI controllers with
of_device_is_compatible() instead, but it only has to name three legacy
controllers whose bindings are closed. An allow-list here would have to
name every SPI controller binding in the tree, 71 distinct compatible
strings among the candidates alone, and grow with every new one.

Signed-off-by: Maciej Andrzejewski ICEYE <maciej.andrzejewski at m-works.net>
---
Changes in v2:
- Squashed the two patches into one. The property scan and the nodename
  test are complementary, so patch 1 alone stopped applying the quirk to
  the three peripherals that carry no "spi-" property, which would have
  broken those boards for anyone bisecting through the series. Reported
  by an automated review of v1.
- Folded the two helpers into a single of_gpio_is_spi_chipselect().
- Said outright that a peripheral with nothing but compatible and reg
  keeps its active low default through the controller nodename.
- Rebased; drivers/gpio/gpiolib-of.c is unchanged since v7.2-rc7, so the
  diff itself is the v1 pair verbatim.

v1: https://lore.kernel.org/r/20260810141629.81650-1-maciej.andrzejewski at m-works.net

 drivers/gpio/gpiolib-of.c | 31 +++++++++++++++++++++++++++++--
 1 file changed, 29 insertions(+), 2 deletions(-)

diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c
index 940b566946ce..d73114166f6b 100644
--- a/drivers/gpio/gpiolib-of.c
+++ b/drivers/gpio/gpiolib-of.c
@@ -340,6 +340,28 @@ static void of_gpio_set_polarity_by_property(const struct device_node *np,
 	}
 }
 
+/*
+ * The legacy SPI chip select binding below is keyed on a property name that
+ * other subsystems reuse for the same purpose, notably NAND controllers (see
+ * Documentation/devicetree/bindings/mtd/nand-controller.yaml), whose chip
+ * selects carry no SPI polarity semantics. Device tree has no bus type
+ * marker, so take two hints. Properties of an SPI peripheral are namespaced
+ * with "spi-", and a peripheral that declares none is covered by the
+ * controller, whose nodename spi-controller.yaml constrains to
+ * ^spi(@.*|-[0-9]+)?$.
+ */
+static bool of_gpio_is_spi_chipselect(const struct device_node *np,
+				      const struct device_node *child)
+{
+	struct property *pp;
+
+	for_each_property_of_node(child, pp)
+		if (str_has_prefix(pp->name, "spi-"))
+			return true;
+
+	return of_node_name_prefix(np, "spi");
+}
+
 static void of_gpio_flags_quirks(const struct device_node *np,
 				 const char *propname,
 				 enum of_gpio_flags *flags,
@@ -374,6 +396,11 @@ static void of_gpio_flags_quirks(const struct device_node *np,
 			if (ret)
 				continue;
 			if (cs == index) {
+				bool active_high;
+
+				if (!of_gpio_is_spi_chipselect(np, child))
+					break;
+
 				/*
 				 * SPI children have active low chip selects
 				 * by default. This can be specified negatively
@@ -386,8 +413,8 @@ static void of_gpio_flags_quirks(const struct device_node *np,
 				 * conflict and the "spi-cs-high" flag will
 				 * take precedence.
 				 */
-				bool active_high = of_property_read_bool(child,
-								"spi-cs-high");
+				active_high = of_property_read_bool(child,
+								    "spi-cs-high");
 				of_gpio_quirk_polarity(child, active_high,
 						       flags);
 				break;

base-commit: bd5f485f3f026225b86573e559af0b7254ef4184
-- 
2.43.0



From jjy600901 at snu.ac.kr  Wed Aug 19 11:28:04 2026
From: jjy600901 at snu.ac.kr (Jaeyoung Chung)
Date: Thu, 20 Aug 2026 03:28:04 +0900
Subject: [BUG] general protection fault in jffs2_xattr_delete_inode
Message-ID: <20260819182805.4020838-1-jjy600901@snu.ac.kr>

Hello,

We found a "general protection fault in jffs2_xattr_delete_inode" on Linux v7.2.
The issue was found by our own race fuzzer. We have not analyzed the root cause,
so we do not have a proposed fix to offer.

To reproduce the race reliably, we applied the delay patch below to the
kernel and ran the C reproducer as root inside an x86_64 QEMU guest. The
crash log we observed, the delay patch and the reproducer are all included
below.

The following kernel config options are required to reproduce the issue:
    CONFIG_MTD=y
    CONFIG_MTD_MTDRAM=y
    CONFIG_MTDRAM_TOTAL_SIZE=128
    CONFIG_MTDRAM_ERASE_SIZE=4
    CONFIG_MTD_BLOCK=y
    CONFIG_JFFS2_FS=y
    CONFIG_JFFS2_FS_XATTR=y
    CONFIG_DEBUG_FS=y
    CONFIG_FAULT_INJECTION=y
    CONFIG_FAILSLAB=y
    CONFIG_FAULT_INJECTION_DEBUG_FS=y
    CONFIG_KASAN=y

We hope this report is useful. Please let us know if any further
information would help.

Reported-by: Eulgyu Kim <eulgyukim at snu.ac.kr>
Reported-by: Jaeyoung Chung <jjy600901 at snu.ac.kr>

Kernel delay patch:
==================================================================
diff --git a/fs/jffs2/fs.c b/fs/jffs2/fs.c
index 6ada8369a762..32c2aaae80d5 100644
--- a/fs/jffs2/fs.c
+++ b/fs/jffs2/fs.c
@@ -25,6 +25,7 @@
 #include <linux/vmalloc.h>
 #include <linux/vfs.h>
 #include <linux/crc32.h>
+#include <linux/delay.h>
 #include "nodelist.h"
 
 static int jffs2_flash_setup(struct jffs2_sb_info *c);
@@ -433,6 +434,9 @@ struct inode *jffs2_new_inode (struct inode *dir_i, umode_t mode, struct jffs2_r
 
 	c = JFFS2_SB_INFO(sb);
 
+	if (strncmp(current->comm, "syzrepro1", 9) == 0) {
+		mdelay(10);
+	}
 	inode = new_inode(sb);
 
 	if (!inode)
diff --git a/fs/jffs2/write.c b/fs/jffs2/write.c
index cda9a361368e..9e7886c5b95a 100644
--- a/fs/jffs2/write.c
+++ b/fs/jffs2/write.c
@@ -12,6 +12,7 @@
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
 #include <linux/kernel.h>
+#include <linux/sched.h>
 #include <linux/fs.h>
 #include <linux/crc32.h>
 #include <linux/pagemap.h>

==================================================================

C reproducer:
==================================================================
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>

#define SYSCHK(x) ({ long __r = (long)(x); if (__r == -1L) { perror(#x); exit(1); } __r; })

#define JM   "/root/jm"
#define DIR0 JM "/d0"
#define DIR1 JM "/d1"
#define DBG  "/sys/kernel/debug"

static volatile int stop_all;

static void knob(const char *path, const char *val)
{
	int fd = SYSCHK(open(path, O_WRONLY));

	write(fd, val, strlen(val));
	close(fd);
}

static void *th_victim(void *arg)
{
	char p[128];
	unsigned i = 0;
	int fd;

	prctl(PR_SET_NAME, "syzrepro0", 0, 0, 0);
	while (!stop_all) {
		snprintf(p, sizeof(p), DIR0 "/v%u", i++ & 31u);
		fd = open(p, O_RDWR | O_CREAT | O_EXCL, 0600);
		if (fd >= 0)
			close(fd);
		else if (errno == ENOSPC || errno == EIO)
			sched_yield();
		unlink(p);
	}
	return NULL;
}

static void *th_inject(void *arg)
{
	char p[128], nb[16];
	unsigned nth = 1, i = 0;
	int ffd, len, fd;

	prctl(PR_SET_NAME, "syzrepro1", 0, 0, 0);
	ffd = SYSCHK(open("/proc/thread-self/fail-nth", O_RDWR));

	while (!stop_all) {
		snprintf(p, sizeof(p), DIR1 "/t%u", i++ & 15u);
		unlink(p);

		len = snprintf(nb, sizeof(nb), "%u", nth);
		if (write(ffd, nb, len) != len)
			break;

		fd = syscall(SYS_openat, AT_FDCWD, p, O_RDWR | O_CREAT | O_EXCL, 0777);
		write(ffd, "0", 1);

		if (fd >= 0) {
			close(fd);
			unlink(p);
		} else if (errno == ENOSPC || errno == EIO) {
			sched_yield();
		}
		if (++nth > 32)
			nth = 1;
	}
	close(ffd);
	return NULL;
}

static void *th_churn(void *arg)
{
	long idx = (long)arg;
	char nm[16], *p;
	int k, fd;

	snprintf(nm, sizeof(nm), "syzrepro%ld", idx);
	prctl(PR_SET_NAME, nm, 0, 0, 0);

	p = malloc(4008);
	p[0] = '/';
	memset(p + 1, 'a' + (int)(idx & 7), 3998);
	p[3999] = 0;

	while (!stop_all) {
		for (k = 0; k < 256; k++) {
			fd = syscall(SYS_openat, AT_FDCWD, p, O_RDONLY, 0);
			if (fd >= 0)
				close(fd);
		}
	}
	free(p);
	return NULL;
}

int main(void)
{
	pthread_t th[4];
	char p[128];
	int i, fd;

	mkdir(DBG, 0755);
	if (mount("none", DBG, "debugfs", 0, NULL) != 0 && errno != EBUSY)
		SYSCHK(-1);
	knob(DBG "/failslab/ignore-gfp-wait", "N");
	knob(DBG "/failslab/verbose", "1");
	knob(DBG "/failslab/probability", "0");
	knob(DBG "/failslab/cache-filter", "N");

	mkdir(JM, 0777);
	if (mount("mtd0", JM, "jffs2", 0, NULL) != 0 &&
	    mount("/dev/mtdblock0", JM, "jffs2", 0, NULL) != 0)
		SYSCHK(-1);
	mkdir(DIR0, 0777);
	mkdir(DIR1, 0777);

	for (i = 0; i < 4; i++) {
		snprintf(p, sizeof(p), DIR1 "/w%d", i);
		fd = open(p, O_RDWR | O_CREAT | O_EXCL, 0600);
		if (fd >= 0)
			close(fd);
		unlink(p);
	}

	pthread_create(&th[0], NULL, th_victim, NULL);
	pthread_create(&th[1], NULL, th_inject, NULL);
	pthread_create(&th[2], NULL, th_churn, (void *)2L);
	pthread_create(&th[3], NULL, th_churn, (void *)3L);

	sleep(165);
	stop_all = 1;
	for (i = 0; i < 4; i++)
		pthread_join(th[i], NULL);
	return 0;
}
==================================================================

Crash log:
==================================================================
name failslab, interval 1, probability 0, space 0, times 0
FAULT_INJECTION: forcing a failure.
name failslab, interval 1, probability 0, space 0, times 0
FAULT_INJECTION: forcing a failure.
name failslab, interval 1, probability 0, space 0, times 0
Oops: general protection fault, probably for non-canonical address 0xec8c888c8c8c8c91: 0000 [#1] SMP KASAN PTI
KASAN: maybe wild-memory-access in range [0x6464646464646488-0x646464646464648f]
CPU: 3 UID: 0 PID: 402 Comm: syzrepro1 Not tainted 7.2.0-dirty #2 PREEMPT 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.13.0-1ubuntu1.1 04/01/2014
RIP: 0010:jffs2_xattr_delete_inode+0x38/0x300 fs/jffs2/xattr.c:602
Code: 41 55 41 54 53 48 83 ec 50 48 89 3c 24 48 85 f6 74 2a 49 89 f6 49 bd 00 00 00 00 00 fc ff df 48 8d 5e 28 48 89 d8 48 c1 e8 03 <42> 0f b6 04 28 84 c0 0f 85 90 02 00 00 83 3b 00 74 14 48 83 c4 50
RSP: 0018:ffff888104ad7918 EFLAGS: 00010203
RAX: 0c8c8c8c8c8c8c91 RBX: 646464646464648c RCX: 0000000000000001
RDX: 0000000000000001 RSI: 6464646464646464 RDI: ffff888108260000
RBP: dffffc0000000000 R08: ffff888104ad7987 R09: 1ffff1102095af30
R10: dffffc0000000000 R11: ffffed102095af31 R12: 1ffff11022349cfc
R13: dffffc0000000000 R14: 6464646464646464 R15: 1ffff11022349ce8
FS:  00007863627186c0(0000) GS:ffff88815e8ac000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000786362717f78 CR3: 000000010795c000 CR4: 00000000000006f0
Call Trace:
 <TASK>
 jffs2_do_clear_inode+0x4d/0x2f0 fs/jffs2/readinode.c:1418
 evict+0x353/0x700 fs/inode.c:825
 jffs2_new_inode+0x443/0xce0 fs/jffs2/fs.c:-1
 jffs2_create+0x87/0x300 fs/jffs2/dir.c:182
 lookup_open fs/namei.c:4508 [inline]
 open_last_lookups fs/namei.c:4608 [inline]
 path_openat+0xe3c/0x29b0 fs/namei.c:4860
 do_file_open+0x19d/0x360 fs/namei.c:4892
 do_sys_openat2+0x9a/0x100 fs/open.c:1368
 do_sys_open fs/open.c:1374 [inline]
 __do_sys_openat fs/open.c:1390 [inline]
 __se_sys_openat fs/open.c:1385 [inline]
 __x64_sys_openat+0xf8/0x130 fs/open.c:1385
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xf7/0x370 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x76/0x7e
RIP: 0033:0x78636301e829
Code: 08 89 e8 5b 5d c3 66 2e 0f 1f 84 00 00 00 00 00 90 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 8b 0d a7 15 0d 00 f7 d8 64 89 01 48
RSP: 002b:0000786362717df8 EFLAGS: 00000246 ORIG_RAX: 0000000000000101
RAX: ffffffffffffffda RBX: 0000000000000007 RCX: 000078636301e829
RDX: 00000000000000c2 RSI: 0000786362717e10 RDI: 00000000ffffff9c
RBP: 0000000000000001 R08: 0000000000000075 R09: 0000000000000037
R10: 00000000000001ff R11: 0000000000000246 R12: 0000786362717e10
R13: 0000000000000027 R14: 0000000000000004 R15: 0000786362717e00
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:jffs2_xattr_delete_inode+0x38/0x300 fs/jffs2/xattr.c:602
Code: 41 55 41 54 53 48 83 ec 50 48 89 3c 24 48 85 f6 74 2a 49 89 f6 49 bd 00 00 00 00 00 fc ff df 48 8d 5e 28 48 89 d8 48 c1 e8 03 <42> 0f b6 04 28 84 c0 0f 85 90 02 00 00 83 3b 00 74 14 48 83 c4 50
RSP: 0018:ffff888104ad7918 EFLAGS: 00010203
RAX: 0c8c8c8c8c8c8c91 RBX: 646464646464648c RCX: 0000000000000001
RDX: 0000000000000001 RSI: 6464646464646464 RDI: ffff888108260000
RBP: dffffc0000000000 R08: ffff888104ad7987 R09: 1ffff1102095af30
R10: dffffc0000000000 R11: ffffed102095af31 R12: 1ffff11022349cfc
R13: dffffc0000000000 R14: 6464646464646464 R15: 1ffff11022349ce8
FS:  00007863627186c0(0000) GS:ffff88815e8ac000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000786362717f78 CR3: 000000010795c000 CR4: 00000000000006f0
----------------
Code disassembly (best guess):
   0:	41 55                	push   %r13
   2:	41 54                	push   %r12
   4:	53                   	push   %rbx
   5:	48 83 ec 50          	sub    $0x50,%rsp
   9:	48 89 3c 24          	mov    %rdi,(%rsp)
   d:	48 85 f6             	test   %rsi,%rsi
  10:	74 2a                	je     0x3c
  12:	49 89 f6             	mov    %rsi,%r14
  15:	49 bd 00 00 00 00 00 	movabs $0xdffffc0000000000,%r13
  1c:	fc ff df
  1f:	48 8d 5e 28          	lea    0x28(%rsi),%rbx
  23:	48 89 d8             	mov    %rbx,%rax
  26:	48 c1 e8 03          	shr    $0x3,%rax
* 2a:	42 0f b6 04 28       	movzbl (%rax,%r13,1),%eax <-- trapping instruction
  2f:	84 c0                	test   %al,%al
  31:	0f 85 90 02 00 00    	jne    0x2c7
  37:	83 3b 00             	cmpl   $0x0,(%rbx)
  3a:	74 14                	je     0x50
  3c:	48 83 c4 50          	add    $0x50,%rsp
==================================================================




From richard at nod.at  Wed Aug 19 14:03:33 2026
From: richard at nod.at (Richard Weinberger)
Date: Wed, 19 Aug 2026 23:03:33 +0200 (CEST)
Subject: [BUG] general protection fault in jffs2_xattr_delete_inode
In-Reply-To: <20260819182805.4020838-1-jjy600901@snu.ac.kr>
References: <20260819182805.4020838-1-jjy600901@snu.ac.kr>
Message-ID: <849183271.921.1787173413104.JavaMail.zimbra@nod.at>

----- Urspr?ngliche Mail -----
> Von: "Jaeyoung Chung" <jjy600901 at snu.ac.kr>

[...]

> RIP: 0010:jffs2_xattr_delete_inode+0x38/0x300 fs/jffs2/xattr.c:602
> Code: 41 55 41 54 53 48 83 ec 50 48 89 3c 24 48 85 f6 74 2a 49 89 f6 49 bd 00 00
> 00 00 00 fc ff df 48 8d 5e 28 48 89 d8 48 c1 e8 03 <42> 0f b6 04 28 84 c0 0f 85
> 90 02 00 00 83 3b 00 74 14 48 83 c4 50
> RSP: 0018:ffff888104ad7918 EFLAGS: 00010203
> RAX: 0c8c8c8c8c8c8c91 RBX: 646464646464648c RCX: 0000000000000001
> RDX: 0000000000000001 RSI: 6464646464646464 RDI: ffff888108260000
> RBP: dffffc0000000000 R08: ffff888104ad7987 R09: 1ffff1102095af30
> R10: dffffc0000000000 R11: ffffed102095af31 R12: 1ffff11022349cfc
> R13: dffffc0000000000 R14: 6464646464646464 R15: 1ffff11022349ce8
> FS:  00007863627186c0(0000) GS:ffff88815e8ac000(0000) knlGS:0000000000000000
> CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 0000786362717f78 CR3: 000000010795c000 CR4: 00000000000006f0
> Call Trace:
> <TASK>
> jffs2_do_clear_inode+0x4d/0x2f0 fs/jffs2/readinode.c:1418
> evict+0x353/0x700 fs/inode.c:825
> jffs2_new_inode+0x443/0xce0 fs/jffs2/fs.c:-1
> jffs2_create+0x87/0x300 fs/jffs2/dir.c:182
> lookup_open fs/namei.c:4508 [inline]
> open_last_lookups fs/namei.c:4608 [inline]
> path_openat+0xe3c/0x29b0 fs/namei.c:4860
> do_file_open+0x19d/0x360 fs/namei.c:4892
> do_sys_openat2+0x9a/0x100 fs/open.c:1368
> do_sys_open fs/open.c:1374 [inline]
> __do_sys_openat fs/open.c:1390 [inline]
> __se_sys_openat fs/open.c:1385 [inline]
> __x64_sys_openat+0xf8/0x130 fs/open.c:1385
> do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
> do_syscall_64+0xf7/0x370 arch/x86/entry/syscall_64.c:94
> entry_SYSCALL_64_after_hwframe+0x76/0x7e
> RIP: 0033:0x78636301e829

So, the partially created inode is evicted in the create path.
Does this change help?

diff --git a/fs/jffs2/os-linux.h b/fs/jffs2/os-linux.h
index 86ab014a349c..a9906f9e3638 100644
--- a/fs/jffs2/os-linux.h
+++ b/fs/jffs2/os-linux.h
@@ -57,6 +57,7 @@ static inline void jffs2_init_inode_info(struct jffs2_inode_info *f)
 	f->target = NULL;
 	f->flags = 0;
 	f->usercompr = 0;
+	f->inocache = NULL;
 }
 
Thanks,
//richard


From dominique.martinet at atmark-techno.com  Wed Aug 19 17:57:27 2026
From: dominique.martinet at atmark-techno.com (Dominique Martinet)
Date: Thu, 20 Aug 2026 09:57:27 +0900
Subject: [PATCH 0/2] Backport fixes for UBI
In-Reply-To: <1200997805.161072.1787136508436.JavaMail.zimbra@nod.at>
References: <20260818-ubi-backports-v1-0-324d6816d2ae@atmark-techno.com>
 <1200997805.161072.1787136508436.JavaMail.zimbra@nod.at>
Message-ID: <aoZQ90HBGlM-SL8K@atmark-techno.com>

Richard Weinberger wrote on Wed, Aug 19, 2026 at 12:48:28PM +0200:
> While porting these back is not wrong, I have a hard time to see how the
> current stables rules apply here.

Thank you for looking into this!

I'm honestly fuzzy on stable backport "rules", but in practice I see all
sort of things get in (admitely sometimes new features/refactor just
because it makes an actual fix easier to backport, but also quite a few
leaks on failures like the second patch and other general improvements),
so after noticing someone else submitted 6.12 contiguous read
improvements recently[1] so I assumed such backports would be welcome...
But ultimately I think it's up to you, so happy to see the patches
dropped if you prefer.

[1] https://lore.kernel.org/r/20260811161342.533280-1-frieder at fris.de

> "mtd: ubi: skip programming unused bits in ubi headers" does not fix anything.
> It's a pure optimization for future flashes, UBI worked since ever without this
> change.

I might have misunderstood something about this patch, but while UBI
works fine I believe this would increase the longevity of more than just
"future flashes"

I've burned out 2 times 1MB (4 erase blocks) from the NAND I have on
hand (winbond W25N04LW) using either random data + erase (nandtest) or a
patched version writing zeroes + erase, and writing many zeroes failed
the erase blocks about 40-50% faster (85 thousands cycles vs 132
thousands until the first erase failure, 121/175 until erase stopped
working with many retries)

This obviously is a tiny sample size and not concrete proof, and this
patch won't have such a big impact because the area is small, but I
believe this patch will still improve the endurance a tiny bit for at
least our model, which was the motivation for me to pick this up.

Of course, there's also a chance that something breaks from it so I
can perfectly understand if you prefer not to backport it; you're in a
much better place than me to draw the line.


Thanks,
-- 
Dominique Martinet | Asmadeus


From zhanxusheng1024 at gmail.com  Wed Aug 19 20:39:49 2026
From: zhanxusheng1024 at gmail.com (Zhan Xusheng)
Date: Thu, 20 Aug 2026 11:39:49 +0800
Subject: [BUG] general protection fault in jffs2_xattr_delete_inode
In-Reply-To: <20260819182805.4020838-1-jjy600901@snu.ac.kr>
References: <20260819182805.4020838-1-jjy600901@snu.ac.kr>
Message-ID: <20260820033950.1045382-1-zhanxusheng@xiaomi.com>

On Thu, 20 Aug 2026 03:28:04 +0900, Jaeyoung Chung wrote:
> We have not analyzed the root cause, so we do not have a proposed fix
> to offer.

f->inocache is never initialised, and jffs2_new_inode() has two error paths
that evict the inode before it gets assigned.

Your log says it outright.  RSI is the ic argument:

  RSI: 6464646464646464
  RBX: 646464646464648c
  1f: 48 8d 5e 28   lea 0x28(%rsi),%rbx

0x64 is 'd', and th_churn() does memset(p + 1, 'a' + (idx & 7), 3998) with
idx 3.  So ic is not a stale pointer to anything, it is the pathname bytes
that thread left lying in the page the inode was later carved from.  With
CONFIG_JFFS2_FS_XATTR the 0x28 is pino_nlink, so the faulting access is the
ic->pino_nlink of xattr.c:602.

Nothing initialises that field.  jffs2_inode_cachep has a constructor, and
jffs2_i_init_once() only does mutex_init(), target = NULL and
inode_init_once().  jffs2_alloc_inode() allocates with plain GFP_KERNEL, so
no zeroing.  jffs2_init_inode_info() sets seven fields and inocache is not
among them.  inocache also sits before vfs_inode in the struct, so
inode_init_always() does not reach it.

On this path f->inocache is assigned only by jffs2_do_new_inode(),
write.c:35.  Both of these return before that, and both then do
make_bad_inode() and iput():

  fs.c:459  jffs2_init_acl_pre() fails
  fs.c:466  jffs2_do_new_inode() fails in jffs2_alloc_inode_cache(),
            write.c:28

Either is an allocation failure, so fail-nth reaches both.  The inode is
unhashed, so inode_generic_drop() makes iput() evict it, which is the
evict() -> jffs2_do_clear_inode() -> jffs2_xattr_delete_inode() in your
trace.  The mdelay() before new_inode() gives the churn threads a window to
leave their bytes where the inode is then carved from.

mtd/next has the same jffs2_init_inode_info(), so:

--- a/fs/jffs2/os-linux.h
+++ b/fs/jffs2/os-linux.h
@@ -55,6 +55,7 @@ static inline void jffs2_init_inode_info(struct jffs2_inode_info *f)
 	f->metadata = NULL;
 	f->dents = NULL;
 	f->target = NULL;
+	f->inocache = NULL;
 	f->flags = 0;
 	f->usercompr = 0;
 }

Both callers assign inocache immediately afterwards, jffs2_do_read_inode()
at readinode.c:1335 as its first act and jffs2_do_new_inode() at write.c:35,
and jffs2_xattr_delete_inode() returns early on a NULL ic, so the read path
is unaffected.

I have not run your reproducer, so please confirm it on your setup before
this goes out as a patch.

Thanks,
Zhan Xusheng


From jgross at suse.com  Wed Aug 19 22:27:11 2026
From: jgross at suse.com (=?UTF-8?B?SsO8cmdlbiBHcm/Dnw==?=)
Date: Thu, 20 Aug 2026 07:27:11 +0200
Subject: [PATCH v2 00/13] x86/msr: Drop 32-bit MSR interfaces
In-Reply-To: <aoXM2f1YTwgVj8KL@google.com>
References: <20260819102314.1499258-1-jgross@suse.com>
 <bd9e5b6e-07f2-459d-bca6-e290585060aa@intel.com>
 <aoXM2f1YTwgVj8KL@google.com>
Message-ID: <7c46f230-7eff-4420-aafc-47fdc27abd24@suse.com>

On 19.08.26 17:33, Sean Christopherson wrote:
> On Wed, Aug 19, 2026, Dave Hansen wrote:
>> Hey Juergen,
>>
>> These look great. Thanks for doing it!
>>
>> The only wonky thing is how we're going to actually merge it. We
>> obviously can't do patches 10-11 until all the "stop using" patches have
>> been applied.
>>
>> I obviously 100% realize that it's during the merge window, but any acks
>> that the maintainers of 5-9 provide in the next few weeks would be super
>> helpful. Or, if any of those maintainers want to cherry pick their
>> subsystem's stuff out of this series and apply it, that would be great too.
>>
>> But, the last time we did some MSR function munging, the tip tree
>> carried most of the patches. I would expect we'll have a repeat of that
>> here too. I'll pencil in the idea of just merging the lot around -rc1 time.
> 
> Note, patch 12 "treewide: convert rdmsrq() from a macro to an inline function"
> is going to conflict with the KVM changes for 7.3; kvm_access_xstate_msr() is
> getting moved from x86.c to msrs.c.  Somewhat surprisingly, AFAICT that's the
> only conflict with KVM's code movement, so it's probably not strictly required
> to send a new version after the KVM pull request?

I'm expecting some more conflicts especially for patches 10-12 before they are
merged. I'm more or less constantly rebasing locally. In case a conflict is
detected, I'll send a V3 after rc1.


Juergen
-------------- next part --------------
A non-text attachment was scrubbed...
Name: OpenPGP_0xB0DE9DD628BF132F.asc
Type: application/pgp-keys
Size: 3683 bytes
Desc: OpenPGP public key
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260820/25569e0d/attachment-0001.bin>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: OpenPGP_signature.asc
Type: application/pgp-signature
Size: 495 bytes
Desc: OpenPGP digital signature
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260820/25569e0d/attachment-0001.sig>

From Takahiro.Kuwano at infineon.com  Wed Aug 19 22:50:55 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 05:50:55 +0000
Subject: [PATCH v3 01/23] mtd: spi-nor: Rename
 BFPT_DWORD15_QER_SR2_BIT1_BUGGY
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-1-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-1-b637cf120d5c@bootlin.com>
Message-ID: <c7e7f557f17647b1b7134facaa1cefc1@infineon.com>

> The _BUGGY suffix is not super accurate. There is a behavior that indeed
> seems strange, but it is somewhat documented. The fact is, with this QER
> field the status register is considered 16-bit wide and if one writes
> the first 8 bits (SR1) without the next 8 bits (SR2) then the content of
> SR2 will be assumed to be 0x00 and will be cleared.
> 
> Rename it BFPT_DWORD15_QER_SR2_BIT1_NO_1B_WR to align with the other
> namings (_NO_RD) and clarify the meaning of this field.
> 
> No functional change.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 22:52:16 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 05:52:16 +0000
Subject: [PATCH v3 02/23] mtd: spi-nor: Create a structure containing the
 flash opcodes
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-2-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-2-b637cf120d5c@bootlin.com>
Message-ID: <e1c58d4707274cde88e81c02fd1e1549@infineon.com>

> Create a structure named opcodes in the flash parameters structures to
> store all the flash specific opcodes. They are typically discovered
> dynamically through SFDP parsing. Move the die_erase opcode
> inside. Later on we will add new fields there, like the supported
> opcodes for accessing the status registers, which are almost vendor
> specific.
> 
> No fonctional change.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 22:53:16 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 05:53:16 +0000
Subject: [PATCH v3 04/23] mtd: spi-nor: Add support for the new JESD216
 rev F QER field
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-4-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-4-b637cf120d5c@bootlin.com>
Message-ID: <c209ecaf857b40519b921e4d73ae29b6@infineon.com>

> Revision F of the JEDEC JESD216 standard includes yet another
> supported setting in the QER 3-bit bitfield: 110b.
> 
> This setting implies that the QE bit is bit 1 of SR2 (which is an
> already supported case) but this time it makes it very clear that both
> reading and writing to SR2 is supported, but not with the usual
> opcodes. The write operation uses an alternate WRSR2 opcode:
> 0x31 (instead of 0x3E) and both the reads and writes manipulate a single
> register at a time.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 22:56:28 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 05:56:28 +0000
Subject: [PATCH v3 05/23] mtd: spi-nor: Create the concept of fixup table
 with match function
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-5-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-5-b637cf120d5c@bootlin.com>
Message-ID: <8309659bd3a24f37aeef01a8d75844d5@infineon.com>

> Manufacturer ID tables increase and fixup() hooks proliferate. Having
> one possible structure per chip was fine until the fixups started being
> more and more common and needed, to some extend due to ID reuses. Mixing
> fixups and chips becomes hard and requires extra helpers to sort which
> ones are needed for a given chip, which every time this happens requires
> a lot of rework.
> 
> Replace the two-level fixup association (a manufacturer wide hook and a
> per flash_info hook) with a per-manufacturer list of fixups that can be
> looked up by flash ID and/or match function.
> 
> The match logic works as follows:
> - If there is an ID, it must match
> - If there is a match function, it must match (cumulative)
> - If there is no identifier (no ID nor any match function), it's a
>   catch-all entry typically used for flagging manufacturer fixups.
> 
> Suggested-by: Michael Walle <mwalle at kernel.org>
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

[...]

> diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c
> index ca2703a5a338..f3862eb1a23a 100644
> --- a/drivers/mtd/spi-nor/spansion.c
> +++ b/drivers/mtd/spi-nor/spansion.c
> @@ -905,7 +905,6 @@ static const struct flash_info spansion_nor_parts[] = {
>                 .sector_size = SZ_256K,
>                 .no_sfdp_flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
>                 .mfr_flags = USE_CLSR,
> -               .fixups = &s25fs_s_nor_fixups,
>         }, {
>                 .id = SNOR_ID(0x01, 0x20, 0x18, 0x03, 0x00),
>                 .name = "s25sl12800",
> @@ -941,7 +940,6 @@ static const struct flash_info spansion_nor_parts[] = {
>                 .size = SZ_16M,
>                 .no_sfdp_flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
>                 .mfr_flags = USE_CLSR,
> -               .fixups = &s25fs_s_nor_fixups,
>         }, {
>                 .id = SNOR_ID(0x01, 0x20, 0x18, 0x4d, 0x01),
>                 .name = "s25fl129p1",
> @@ -1001,76 +999,61 @@ static const struct flash_info spansion_nor_parts[] = {
>                 .id = SNOR_ID(0x34, 0x2a, 0x1a, 0x0f, 0x03, 0x90),
>                 .name = "s25hl512t",
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s25hx_t_fixups
>         }, {
>                 .id = SNOR_ID(0x34, 0x2a, 0x1b, 0x0f, 0x03, 0x90),
>                 .name = "s25hl01gt",
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s25hx_t_fixups
>         }, {
>                 .id = SNOR_ID(0x34, 0x2a, 0x1c, 0x0f, 0x00, 0x90),
>                 .name = "s25hl02gt",
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s25hx_t_fixups
>         }, {
>                 .id = SNOR_ID(0x34, 0x2b, 0x19, 0x0f, 0x08, 0x90),
>                 .name = "s25fs256t",
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s25fs256t_fixups
>         }, {
>                 .id = SNOR_ID(0x34, 0x2b, 0x1a, 0x0f, 0x03, 0x90),
>                 .name = "s25hs512t",
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s25hx_t_fixups
>         }, {
>                 .id = SNOR_ID(0x34, 0x2b, 0x1b, 0x0f, 0x03, 0x90),
>                 .name = "s25hs01gt",
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s25hx_t_fixups
>         }, {
>                 .id = SNOR_ID(0x34, 0x2b, 0x1c, 0x0f, 0x00, 0x90),
>                 .name = "s25hs02gt",
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s25hx_t_fixups
>         }, {
>                 /* S28HL256T */
>                 .id = SNOR_ID(0x34, 0x5a, 0x19),
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s28hx_t_fixups,
>         }, {
>                 .id = SNOR_ID(0x34, 0x5a, 0x1a),
>                 .name = "s28hl512t",
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s28hx_t_fixups,
>         }, {
>                 .id = SNOR_ID(0x34, 0x5a, 0x1b),
>                 .name = "s28hl01gt",
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s28hx_t_fixups,
>         }, {
>                 /* S28HL02GT */
>                 .id = SNOR_ID(0x34, 0x5a, 0x1c),
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s28hx_t_fixups,
>         }, {
>                 .id = SNOR_ID(0x34, 0x5b, 0x19),
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s28hx_t_fixups,
>         }, {
>                 .id = SNOR_ID(0x34, 0x5b, 0x1a),
>                 .name = "s28hs512t",
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s28hx_t_fixups,
>         }, {
>                 .id = SNOR_ID(0x34, 0x5b, 0x1b),
>                 .name = "s28hs01gt",
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s28hx_t_fixups,
>         }, {
>                 .id = SNOR_ID(0x34, 0x5b, 0x1c),
>                 .name = "s28hs02gt",
>                 .mfr_flags = USE_CLPEF,
> -               .fixups = &s28hx_t_fixups,
>         }, {
>                 .id = SNOR_ID(0xef, 0x40, 0x13),
>                 .name = "s25fl004k",
> @@ -1170,9 +1153,31 @@ static const struct spi_nor_fixups spansion_nor_fixups = {
>         .late_init = spansion_nor_late_init,
>  };
> 
> +static const struct spi_nor_fixup spansion_fixups[] = {
> +       { .fixups = &spansion_nor_fixups },
> +       { .id = SNOR_ID(0x01, 0x02, 0x20, 0x4d, 0x00, 0x81), .fixups = &s25fs_s_nor_fixups },
> +       { .id = SNOR_ID(0x01, 0x20, 0x18, 0x4d, 0x01, 0x81), .fixups = &s25fs_s_nor_fixups },
> +       { .id = SNOR_ID(0x34, 0x2a, 0x1a, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups },
> +       { .id = SNOR_ID(0x34, 0x2a, 0x1b, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups },
> +       { .id = SNOR_ID(0x34, 0x2a, 0x1c, 0x0f, 0x00, 0x90), .fixups = &s25hx_t_fixups },
> +       { .id = SNOR_ID(0x34, 0x2b, 0x19, 0x0f, 0x08, 0x90), .fixups = &s25fs256t_fixups },
> +       { .id = SNOR_ID(0x34, 0x2b, 0x1a, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups },
> +       { .id = SNOR_ID(0x34, 0x2b, 0x1b, 0x0f, 0x03, 0x90), .fixups = &s25hx_t_fixups },
> +       { .id = SNOR_ID(0x34, 0x2b, 0x1c, 0x0f, 0x00, 0x90), .fixups = &s25hx_t_fixups },
> +       { .id = SNOR_ID(0x34, 0x5a, 0x19), .fixups = &s28hx_t_fixups },
> +       { .id = SNOR_ID(0x34, 0x5a, 0x1a), .fixups = &s28hx_t_fixups },
> +       { .id = SNOR_ID(0x34, 0x5a, 0x1b), .fixups = &s28hx_t_fixups },
> +       { .id = SNOR_ID(0x34, 0x5a, 0x1c), .fixups = &s28hx_t_fixups },
> +       { .id = SNOR_ID(0x34, 0x5b, 0x19), .fixups = &s28hx_t_fixups },
> +       { .id = SNOR_ID(0x34, 0x5b, 0x1a), .fixups = &s28hx_t_fixups },
> +       { .id = SNOR_ID(0x34, 0x5b, 0x1b), .fixups = &s28hx_t_fixups },
> +       { .id = SNOR_ID(0x34, 0x5b, 0x1c), .fixups = &s28hx_t_fixups },
> +};
> +
>  const struct spi_nor_manufacturer spi_nor_spansion = {
>         .name = "spansion",
>         .parts = spansion_nor_parts,
>         .nparts = ARRAY_SIZE(spansion_nor_parts),
> -       .fixups = &spansion_nor_fixups,
> +       .fixups = spansion_fixups,
> +       .nfixups = ARRAY_SIZE(spansion_fixups),
>  };

With S25FS512S, S25HL512T, and S28HS512T,
Tested-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 22:57:18 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 05:57:18 +0000
Subject: [PATCH v3 06/23] mtd: spi-nor: Create an indirection on the part
 name
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-6-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-6-b637cf120d5c@bootlin.com>
Message-ID: <b8197d8e5f7d435397536b0187a4297b@infineon.com>

> We currently print through sysfs and debugfs the name of the part if the
> .name field, which is legacy, has been filled in the ID table (otherwise
> "(null)" is printed). These IDs had an interest until manufacturers
> started re-using more and more extensively the so called unique JEDEC
> IDs. At this stage, a name that was tailored for a chip sold in 2016 may
> no longer be relevant for a chip sold in 2026. Still showing this name
> through the various filesystems may confuse people, removing these names
> cannot be done since they are now part of the user ABI (because of
> sysfs).
> 
> In order to allow overwriting the .name field (which is part of a
> read-only structure), let's create a spi-nor pointer which will by
> default point to that .name, but which can be cleared in fixup hooks to
> make sure the name is hidden on newer parts.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 22:57:51 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 05:57:51 +0000
Subject: [PATCH v3 07/23] mtd: spi-nor: Move the SFDP header structure to
 a C header
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-7-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-7-b637cf120d5c@bootlin.com>
Message-ID: <c21838d45aed4ed99364646db69958f3@infineon.com>

> The sfdp_header structure is currently defined in sfdp.c, whereas the
> sfdp_parameter_header structure is defined in sfdp.h. In order to ease
> the reuse of this structure outside of the sfdp.c file, move this
> structure to the sfdp C header (.h).
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 22:58:58 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 05:58:58 +0000
Subject: [PATCH v3 08/23] mtd: spi-nor: winbond: Prepare introduction of
 W25QxxRV-Q/N parts
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-8-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-8-b637cf120d5c@bootlin.com>
Message-ID: <fca9748398d1496e9e1dbdac19cab2dc@infineon.com>

> There is an ID collision between the JV and RV families. Both chips are
> very similar in practice, it is mostly a matter of electrical
> differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2 opcode
> (0x31). They also do not require the multi-die fixups which must remain
> assigned to the JV chips.
> 
> Finally, since they share the IDs but not the names, we must hide the
> names using a fixup.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 23:03:01 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 06:03:01 +0000
Subject: [PATCH v3 09/23] mtd: spi-nor: winbond: Add support for
 W25Q32RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-9-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-9-b637cf120d5c@bootlin.com>
Message-ID: <bbd8a9be848a40a380c32e0361d56b82@infineon.com>

> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 23:04:25 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 06:04:25 +0000
Subject: [PATCH v3 11/23] mtd: spi-nor: winbond: Add support for
 W25Q12RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-11-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-11-b637cf120d5c@bootlin.com>
Message-ID: <4679ea5e945a4f6c8e743d3543e000b5@infineon.com>

> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 23:04:58 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 06:04:58 +0000
Subject: [PATCH v3 12/23] mtd: spi-nor: winbond: Add support for
 W25Q51RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-12-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-12-b637cf120d5c@bootlin.com>
Message-ID: <6ed3856fa9dd45ba845e9bde3fbd12b5@infineon.com>

> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 23:03:51 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 06:03:51 +0000
Subject: [PATCH v3 10/23] mtd: spi-nor: winbond: Add support for
 W25Q64RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-10-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-10-b637cf120d5c@bootlin.com>
Message-ID: <5d1cbf4b2a414b15be60832fc4be6025@infineon.com>

> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From dlemoal at kernel.org  Wed Aug 19 23:06:10 2026
From: dlemoal at kernel.org (Damien Le Moal)
Date: Thu, 20 Aug 2026 15:06:10 +0900
Subject: [PATCH v2 12/13] treewide: convert rdmsrq() from a macro to an
 inline function
In-Reply-To: <20260819102314.1499258-13-jgross@suse.com>
References: <20260819102314.1499258-1-jgross@suse.com>
 <20260819102314.1499258-13-jgross@suse.com>
Message-ID: <21d31639-4d16-4ee2-8ff3-18877636aa8b@kernel.org>

On 8/19/26 19:23, Juergen Gross wrote:
> Today rdmsrq() is a macro using its second parameter as the target for
> storing the read MSR value.
> 
> Convert rdmsrq() to an inline function returning the MSR value.
> 
> The users have been converted using the following semantic patch:
> 
>   // Options: --include-headers
> 
>   virtual patch
>   virtual report
> 
>   @@
>   expression msr, val;
>   @@
>   (
>   - rdmsrq(msr,val)
>   + val = rdmsrq(msr)
>   )
> 
> Signed-off-by: Juergen Gross <jgross at suse.com>

For the ata bits,

Acked-by: Damien Le Moal <dlemoal at kernel.org>

-- 
Damien Le Moal
Western Digital Research


From Takahiro.Kuwano at infineon.com  Wed Aug 19 23:15:08 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 06:15:08 +0000
Subject: [PATCH v3 13/23] mtd: spi-nor: winbond: Add support for
 W25Q01RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-13-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-13-b637cf120d5c@bootlin.com>
Message-ID: <4d7597839fc542ea846fe0ddb3eb6683@infineon.com>

Hi Miquel,

> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
> ---
> + cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
> ef4020

ef4021?
I think you copied wrong log (this looks same as patch #12).

> + cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
> winbond
> + xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
> 534644500a0101ff00080117800000ff84010102e00000ffffffffffffff
> ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
> ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
> ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
> ffffffffffffffffe520f3ffffffff1f44eb086b083b42bbfeffffffffff
> 0000ffff44eb0c200f5210d800001602a60081e214dde96376337a757a75
> f7a4d55c39f66dffe970f9a50000000000002c0c0000000066ffffff0000
> 00000000000000000000ffffffffff0a00fe21ffdcff
> + sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
> 74d21fecc7faa297b3f77f1136b1dc53f78b2a432629e226675b6384153fd9f9  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
> + cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
> Supported read modes by the flash
>  1S-1S-1S
>   opcode        0x13
>   mode cycles   0
>   dummy cycles  0
>  1S-1S-2S
>   opcode        0x3c
>   mode cycles   0
>   dummy cycles  8
>  1S-2S-2S
>   opcode        0xbc
>   mode cycles   2
>   dummy cycles  2
>  1S-1S-4S
>   opcode        0x6c
>   mode cycles   0
>   dummy cycles  8
>  1S-4S-4S
>   opcode        0xec
>   mode cycles   2
>   dummy cycles  4
>  4S-4S-4S
>   opcode        0xec
>   mode cycles   2
>   dummy cycles  4
> 
> Supported page program modes by the flash
>  1S-1S-1S
>   opcode        0x12
>  1S-1S-4S
>   opcode        0x34
> + cat /sys/kernel/debug/spi-nor/spi0.0/params
> name            (null)
> id              ef 40 20 00 00 00
> size            64.0 MiB
> write size      1
> page size       256
> address nbytes  4
> flags           HAS_SR_TB | 4B_OPCODES | HAS_4BAIT | HAS_LOCK | HAS_SR_TB_BIT6 | HAS_4BIT_BP | SOFT_RESET | NO_WP |
> HAS_SR2_CMP_BIT6
> 
> opcodes
>  read           0xec
>   dummy cycles  6
>  erase          0xdc
>  program        0x34
>  8D extension   repeat
> 
> protocols
>  read           1S-4S-4S
>  write          1S-1S-4S
>  register       1S-1S-1S
> 
> erase commands
>  21 (4.00 KiB) [1]
>  dc (64.0 KiB) [3]
>  c7 (64.0 MiB)
> 
> sector map
>  region (in hex)   | erase mask | overlaid
>  ------------------+------------+---------
>  00000000-03ffffff |     [   3] | no
> 
> locked sectors
>  region (in hex)   | status   | #sectors
>  ------------------+----------+---------
>  00000000-03ffffff | unlocked | 1024
> + dd 'if=/dev/urandom' 'of=./spi_test' 'bs=1M' 'count=2'
> 2+0 records in
> 2+0 records out
> + mtd_debug erase /dev/mtd0 0 2097152
> Erased 2097152 bytes from address 0x00000000 in flash
> + mtd_debug read /dev/mtd0 0 2097152 spi_read
> Copied 2097152 bytes from address 0x00000000 in flash to spi_read
> + hexdump spi_read
> 0000000 ffff ffff ffff ffff ffff ffff ffff ffff
> *
> 0200000
> + sha256sum spi_read
> 4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
> + mtd_debug write /dev/mtd0 0 2097152 spi_test
> Copied 2097152 bytes from spi_test to address 0x00000000 in flash
> + mtd_debug read /dev/mtd0 0 2097152 spi_read
> Copied 2097152 bytes from address 0x00000000 in flash to spi_read
> + sha256sum spi_read spi_test
> 950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read
> 950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
> + mtd_debug erase /dev/mtd0 0 2097152
> Erased 2097152 bytes from address 0x00000000 in flash
> + mtd_debug read /dev/mtd0 0 2097152 spi_read
> Copied 2097152 bytes from address 0x00000000 in flash to spi_read
> + sha256sum spi_read spi_test
> 4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5  spi_read
> 950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
> + mtd_debug info /dev/mtd0
> mtd.type = MTD_NORFLASH
> mtd.flags = MTD_CAP_NORFLASH
> mtd.size = 67108864 (64M)
> mtd.erasesize = 65536 (64K)
> mtd.writesize = 1
> mtd.oobsize = 0
> regions = 0
> 
> + alias 'show_sectors=grep -A4 "locked sectors" /sys/kernel/debug/spi-nor/spi0.0/params'
> + flash_lock -u /dev/mtd0
> + flash_lock -i /dev/mtd0
> Device: /dev/mtd0
> Start: 0
> Len: 0x4000000
> Lock status: unlocked
> Return code: 0
> + mtd_debug erase /dev/mtd0 0 2097152
> Erased 2097152 bytes from address 0x00000000 in flash
> + mtd_debug write /dev/mtd0 0 2097152 spi_test
> Copied 2097152 bytes from spi_test to address 0x00000000 in flash
> + mtd_debug read /dev/mtd0 0 2097152 spi_read
> Copied 2097152 bytes from address 0x00000000 in flash to spi_read
> + sha256sum spi_read spi_test
> 950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read
> 950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
> + grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
> locked sectors
>  region (in hex)   | status   | #sectors
>  ------------------+----------+---------
>  00000000-03ffffff | unlocked | 1024
> + flash_lock -l /dev/mtd0
> + flash_lock -i /dev/mtd0
> Device: /dev/mtd0
> Start: 0
> Len: 0x4000000
> Lock status: locked
> Return code: 1
> + mtd_debug erase /dev/mtd0 0 2097152
> Erased 2097152 bytes from address 0x00000000 in flash
> + mtd_debug read /dev/mtd0 0 2097152 spi_read
> Copied 2097152 bytes from address 0x00000000 in flash to spi_read
> + sha256sum spi_read spi_test
> 950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read
> 950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
> + dd 'if=/dev/urandom' 'of=./spi_test2' 'bs=1M' 'count=2'
> 2+0 records in
> 2+0 records out
> + mtd_debug write /dev/mtd0 0 2097152 spi_test2
> Copied 2097152 bytes from spi_test2 to address 0x00000000 in flash
> + mtd_debug read /dev/mtd0 0 2097152 spi_read2
> Copied 2097152 bytes from address 0x00000000 in flash to spi_read2
> + sha256sum spi_read spi_read2 spi_test spi_test2
> 950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read
> 950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_read2
> 950cbc701e7f6ed69837b30bbf4145fe776e6c07a7bc5b8f346d42242bdd7a63  spi_test
> 643412dbe0f46c2780ec8d4f609d369923829945f4bc3d7842138b2fbbc86046  spi_test2
> + grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
> locked sectors
>  region (in hex)   | status   | #sectors
>  ------------------+----------+---------
>  00000000-03ffffff |   locked | 1024
> + flash_lock -u /dev/mtd0
> + cat /sys/class/mtd/mtd0/size
> + size=67108864
> + cat /sys/class/mtd/mtd0/erasesize
> + bs=65536
> + grep unlocked /sys/kernel/debug/spi-nor/spi0.0/params
> + sed -e 's/.*unlocked | //'
> + nsectors=1024
> + ss=65536
> + bps=1
> + flash_lock -u /dev/mtd0
> + flash_lock -l /dev/mtd0 66977792 2
> + grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
> locked sectors
>  region (in hex)   | status   | #sectors
>  ------------------+----------+---------
>  00000000-03fdffff | unlocked | 1022
>  03fe0000-03ffffff |   locked | 2
> + flash_lock -u /dev/mtd0 66977792 1
> + grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
> locked sectors
>  region (in hex)   | status   | #sectors
>  ------------------+----------+---------
>  00000000-03feffff | unlocked | 1023
>  03ff0000-03ffffff |   locked | 1
> + flash_lock -u /dev/mtd0
> + flash_lock -l /dev/mtd0 58720256 128
> + grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
> locked sectors
>  region (in hex)   | status   | #sectors
>  ------------------+----------+---------
>  00000000-037fffff | unlocked | 896
>  03800000-03ffffff |   locked | 128
> + flash_lock -u /dev/mtd0
> + flash_lock -l /dev/mtd0 0 2
> + grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
> locked sectors
>  region (in hex)   | status   | #sectors
>  ------------------+----------+---------
>  00000000-0001ffff |   locked | 2
>  00020000-03ffffff | unlocked | 1022
> + flash_lock -u /dev/mtd0 65536 1
> + grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
> locked sectors
>  region (in hex)   | status   | #sectors
>  ------------------+----------+---------
>  00000000-0000ffff |   locked | 1
>  00010000-03ffffff | unlocked | 1023
> + all_but_one=1023
> + flash_lock -u /dev/mtd0
> + flash_lock -l /dev/mtd0 65536 1023
> + grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
> locked sectors
>  region (in hex)   | status   | #sectors
>  ------------------+----------+---------
>  00000000-0000ffff | unlocked | 1
>  00010000-03ffffff |   locked | 1023
> + flash_lock -u /dev/mtd0 65536 1
> + grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
> locked sectors
>  region (in hex)   | status   | #sectors
>  ------------------+----------+---------
>  00000000-0001ffff | unlocked | 2
>  00020000-03ffffff |   locked | 1022
> + flash_lock -u /dev/mtd0
> + flash_lock -l /dev/mtd0 0 1023
> + grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
> locked sectors
>  region (in hex)   | status   | #sectors
>  ------------------+----------+---------
>  00000000-03feffff |   locked | 1023
>  03ff0000-03ffffff | unlocked | 1
> + flash_lock -u /dev/mtd0 66977792 1
> + grep -A4 'locked sectors' /sys/kernel/debug/spi-nor/spi0.0/params
> locked sectors
>  region (in hex)   | status   | #sectors
>  ------------------+----------+---------
>  00000000-03fdffff |   locked | 1022
>  03fe0000-03ffffff | unlocked | 2
> ---
>  drivers/mtd/spi-nor/winbond.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
> index 47a5c2cf71de..1c67a652ddcd 100644
> --- a/drivers/mtd/spi-nor/winbond.c
> +++ b/drivers/mtd/spi-nor/winbond.c
> @@ -281,7 +281,7 @@ static const struct flash_info winbond_nor_parts[] = {
>                 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
>                          SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
>         }, {
> -               /* W25Q01JV-Q/N */
> +               /* W25Q01JV-Q/N, W25Q01RV-Q/N */
>                 .id = SNOR_ID(0xef, 0x40, 0x21),
>                 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
>                          SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
> 
> --
> 2.54.0


From Takahiro.Kuwano at infineon.com  Wed Aug 19 23:17:43 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 06:17:43 +0000
Subject: [PATCH v3 14/23] mtd: spi-nor: winbond: Add support for
 W25Q02RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-14-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-14-b637cf120d5c@bootlin.com>
Message-ID: <8a200e9d111847a7a8a6cdd7d25de28d@infineon.com>

> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 23:18:22 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 06:18:22 +0000
Subject: [PATCH v3 15/23] mtd: spi-nor: winbond: Prepare introduction of
 W25QxxRV-M parts
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-15-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-15-b637cf120d5c@bootlin.com>
Message-ID: <2556eaef1ad346518e169552d60474ed@infineon.com>

> There is an ID collision between the JV and RV families. Both chips are
> very similar in practice, it is mostly a matter of electrical
> differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> The new SFDP (rev F) field which forces an alternate write SR2 opcode
> (0x31).
> 
> Since they share the IDs but not the names, we must hide the names using
> a fixup.
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 23:19:25 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 06:19:25 +0000
Subject: [PATCH v3 16/23] mtd: spi-nor: winbond: Add support for
 W25Q32RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-16-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-16-b637cf120d5c@bootlin.com>
Message-ID: <1054daad5c394b63b429c153434a9896@infineon.com>

> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 23:21:17 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 06:21:17 +0000
Subject: [PATCH v3 17/23] mtd: spi-nor: winbond: Add support for
 W25Q64RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-17-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-17-b637cf120d5c@bootlin.com>
Message-ID: <1607509688694e31872d1b6cc373bc7a@infineon.com>

> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>



From Takahiro.Kuwano at infineon.com  Wed Aug 19 23:44:31 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 06:44:31 +0000
Subject: [PATCH v3 18/23] mtd: spi-nor: winbond: Add support for
 W25Q12RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-18-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-18-b637cf120d5c@bootlin.com>
Message-ID: <889b2fa10c5e4c6ea22ede16baa22b44@infineon.com>

Hi Miquel,

> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
> ---
> + cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
> ef7018
> + cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
> winbond
> + xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
> 534644500a0100ff00080117800000ffffffffffffffffffffffffffffff
> ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
> ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
> ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
> ffffffffffffffffe520f9ffffffff0344eb086b083b42bbfeffffffffff
> 0000ffff44eb0c200f5210d80000142a010186e114c1e96376337a757a75
> f7a2d55c39f66dffe930f880000000000000ac0000000000f7f5ffff0f00
> 0000060d44bd27ed080d
> + sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
> 94b52d6e903a2046e144eab0760384b6932c6fdbfec17eb7336f1778c3898daf  /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
> + cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
> Supported read modes by the flash
>  1S-1S-1S
>   opcode        0x03
>   mode cycles   0
>   dummy cycles  0
>  1S-1S-2S
>   opcode        0x3b
>   mode cycles   0
>   dummy cycles  8
>  1S-2S-2S
>   opcode        0xbb
>   mode cycles   2
>   dummy cycles  2
>  1S-1S-4S
>   opcode        0x6b
>   mode cycles   0
>   dummy cycles  8
>  1S-4S-4S
>   opcode        0xeb
>   mode cycles   2
>   dummy cycles  4
>  4S-4S-4S
>   opcode        0xeb
>   mode cycles   2
>   dummy cycles  4
> 
> Supported page program modes by the flash
>  1S-1S-1S
>   opcode        0x02
>  1S-1S-4S
>   opcode        0x32
> + cat /sys/kernel/debug/spi-nor/spi0.0/params
> name            (null)
> id              ef 70 18 00 00 00
> size            8.00 MiB

Is this correct?
This part should be 16MB(128Mb).

Thanks,
Takahiro


From Takahiro.Kuwano at infineon.com  Wed Aug 19 23:48:20 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 06:48:20 +0000
Subject: [PATCH v3 19/23] mtd: spi-nor: winbond: Add support for
 W25Q51RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-19-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-19-b637cf120d5c@bootlin.com>
Message-ID: <96751e6445544105be6ec8cd93d498b5@infineon.com>

> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 23:50:59 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 06:50:59 +0000
Subject: [PATCH v3 20/23] mtd: spi-nor: winbond: Add support for
 W25Q01RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-20-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-20-b637cf120d5c@bootlin.com>
Message-ID: <7cd3079bee9d4149a8ed633fde00469e@infineon.com>

> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Wed Aug 19 23:52:16 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 06:52:16 +0000
Subject: [PATCH v3 21/23] mtd: spi-nor: winbond: Add support for
 W25Q02RV-M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-21-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-21-b637cf120d5c@bootlin.com>
Message-ID: <80a0af05fc444ebba4ab95e4cd1d5c8c@infineon.com>

> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

Reviewed-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>


From Takahiro.Kuwano at infineon.com  Thu Aug 20 00:03:44 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 07:03:44 +0000
Subject: [PATCH v3 23/23] mtd: spi-nor: debugfs: Expose SR opcodes and QE
 mask
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-23-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-23-b637cf120d5c@bootlin.com>
Message-ID: <2bf142b04caf4c578cfbc57740490632@infineon.com>

Hi Miquel,

> Expose Status Register opcodes based on the flash capabilities
> discovered while parsing the SFDP QER field.
> 
> Value '0' indicates an absence of opcode (non supported), we do not
> print anything in that case.
> 
> The QE bit is also displayed.
> 
> Example on a W25Q02JV-M:
> 
> $ grep opcodes -A10 /sys/kernel/debug/spi-nor/spi0.0/params
> opcodes
>  read           0xec
>   dummy cycles  6
>  erase          0xdc
>  program        0x34
>  SR1 read       0x05
>  SR2 read       0x35
>  SR1+SR2 write  0x01
>  QE             SR2 bit 1
>  8D extension   none
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>

S25HL512T:
opcodes
 read           0xec
  dummy cycles  10
 erase          0xd8
 program        0x12
 SR1 read       0x05
 SR2 read       0x35
 SR1+SR2 write  0x01
 QE             SR2 bit 1
 8D extension   repeat

S25FS512S:
opcodes
 read           0xec
  dummy cycles  10
 erase          0xdc
 program        0x12
 SR1 read       0x05
 SR2 read       0x35
 SR1+SR2 write  0x01
 QE             SR2 bit 1
 8D extension   none

S25FL256S (no SFDP):
opcodes
 read           0x6c
  dummy cycles  8
 erase          0xdc
 program        0x12
 SR1 read       0x05
 SR2 read       0x35
 SR1+SR2 write  0x01
 QE             SR2 bit 1
 8D extension   none

S28HS512T (Octal, no QE):
opcodes
 read           0xee
  dummy cycles  24
 erase          0xd8
 program        0x12
 SR1 read       0x05
 SR2 read       0x35
 SR1 write      0x01
 SR1+SR2 write  0x01
 8D extension   repeat

Tested-by: Takahiro Kuwano <takahiro.kuwano at infineon.com>




From Takahiro.Kuwano at infineon.com  Thu Aug 20 00:32:19 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 07:32:19 +0000
Subject: [PATCH v3 11/23] mtd: spi-nor: winbond: Add support for
 W25Q12RV-Q/N
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-11-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-11-b637cf120d5c@bootlin.com>
Message-ID: <cc933d88b77b4fb5ad8f22720f08bb19@infineon.com>

Hi Miquel,

> There is an ID collision with the chip of same density from the JV
> family. Both chips are very similar in practice, it is mostly a matter
> of electrical differences (mostly power consumption being lower).
> 
> As a significant difference, RV chips identify themselves as supporting
> the new SFDP (rev F) field which forces an alternate write SR2
> opcode (0x31).
> 
> Signed-off-by: Miquel Raynal <miquel.raynal at bootlin.com>
> ---
> + cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
> ef4018
> + cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
> winbond
> + xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
> 534644500a0100ff00080117800000ffffffffffffffffffffffffffffff
> ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
> ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
> ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
> ffffffffffffffffe520f9ffffffff0344eb086b083b42bbfeffffffffff
                          ^^^^^^^^
Just noticed size is wrong like patch #18.
BFPT DWORD2 advertises memory density in bits (0-based).
In 128Mb parts, this should be 0x07FFFFFF.
Need post_bfpt_fixup.

Thanks,
Takahiro


From Takahiro.Kuwano at infineon.com  Thu Aug 20 00:42:31 2026
From: Takahiro.Kuwano at infineon.com (Takahiro.Kuwano at infineon.com)
Date: Thu, 20 Aug 2026 07:42:31 +0000
Subject: [PATCH v3 22/23] mtd: spi-nor: winbond: Add support for
 W25Q51RV-Q/N/M
In-Reply-To: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-22-b637cf120d5c@bootlin.com>
References: <20260813-winbond-v7-1-spi-nor-rv-addition-v3-0-b637cf120d5c@bootlin.com>
 <20260813-winbond-v7-1-spi-nor-rv-addition-v3-22-b637cf120d5c@bootlin.com>
Message-ID: <516e6eb78d3741e7bf5127e928bbe597@infineon.com>

Hi Miquel,

> I currently do not have access to any chips from the W25Q25RV
> density. Yet, I successfully tested the entire JV family and all other
> chips from the RV family. They all behaved strictly identically, so I
> propose to nevertheless add the missing comments for the sake of clarity
> and completeness.

Subject says W25Q51RV that inconsistent with what you changed.
And it would be nice to have a W25Q25RV chip and check SFDP.

Thanks,
Takahiro


From zhouminqiang2 at huawei.com  Thu Aug 20 03:49:59 2026
From: zhouminqiang2 at huawei.com (zhouminqiang)
Date: Thu, 20 Aug 2026 18:49:59 +0800
Subject: [PATCH 3/6] jffs2: add write verification to direct page writes in flash_writev
In-Reply-To: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
References: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
Message-ID: <20260820105003.2525647-4-zhouminqiang2@huawei.com>

jffs2_flash_writev() performs writes in three phases: phase 1 fills
the write buffer and flushes it when full, phase 2 writes full pages
directly via mtd_write() bypassing the buffer, and phase 3 fills the
remaining data into the write buffer.

Phases 1 and 3 both invoke __jffs2_flush_wbuf() when the buffer is
full, which includes write-back verification when
CONFIG_JFFS2_FS_WBUF_VERIFY is enabled. However phase 2, which
handles the bulk of the write data, calls mtd_write() directly
without any verification.

Phase 2 direct writes may span multiple wbuf_pagesize pages. Extend
jffs2_verify_write() with a len parameter to specify the data length
to verify, and add the verification call after the phase 2
mtd_write() to cover this gap.

Signed-off-by: zhouminqiang <zhouminqiang2 at huawei.com>
---
 fs/jffs2/wbuf.c | 33 ++++++++++++++++++++-------------
 1 file changed, 20 insertions(+), 13 deletions(-)

diff --git a/fs/jffs2/wbuf.c b/fs/jffs2/wbuf.c
index da6da813a4ef..3e99587f40e4 100644
--- a/fs/jffs2/wbuf.c
+++ b/fs/jffs2/wbuf.c
@@ -229,33 +229,33 @@ static struct jffs2_raw_node_ref **jffs2_incore_replace_raw(struct jffs2_sb_info
 
 #ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
 static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
-			      uint32_t ofs)
+			      uint32_t ofs, size_t len)
 {
 	int ret = 0;
 	size_t retlen, i;
 	char *eccstr;
 	void *verify_buf;
 
-	verify_buf = __vmalloc(c->wbuf_pagesize, GFP_NOFS);
+	verify_buf = __vmalloc(len, GFP_NOFS);
 	if (!verify_buf) {
 		pr_warn("%s(): verify buffer allocation failed, skipping verification\n",
 			__func__);
 		return 0;
 	}
 
-	ret = mtd_read(c->mtd, ofs, c->wbuf_pagesize, &retlen, verify_buf);
+	ret = mtd_read(c->mtd, ofs, len, &retlen, verify_buf);
 	if (ret && ret != -EUCLEAN && ret != -EBADMSG) {
 		pr_warn("%s(): Read back of page at %08x failed: %d\n",
 			__func__, ofs, ret);
 		goto out_free;
-	} else if (retlen != c->wbuf_pagesize) {
-		pr_warn("%s(): Read back of page at %08x gave short read: %zu not %d\n",
-			__func__, ofs, retlen, c->wbuf_pagesize);
+	} else if (retlen != len) {
+		pr_warn("%s(): Read back of page at %08x gave short read: %zu not %zu\n",
+			__func__, ofs, retlen, len);
 		ret = -EIO;
 		goto out_free;
 	}
 
-	for (i = 0; i < c->wbuf_pagesize; i++) {
+	for (i = 0; i < len; i++) {
 		uint8_t c1 = ((uint8_t *)buf)[i];
 		uint8_t c2 = ((uint8_t *)verify_buf)[i];
 		int dump_len;
@@ -270,9 +270,9 @@ static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
 		else
 			eccstr = "OK or unused";
 
-		dump_len = min_t(int, 128, c->wbuf_pagesize - i);
-		pr_warn("Write verify error (ECC %s) at %08x (+%zu/%d). Wrote:\n",
-			eccstr, ofs, i, c->wbuf_pagesize);
+		dump_len = min_t(int, 128, len - i);
+		pr_warn("Write verify error (ECC %s) at %08x (+%zu/%zu). Wrote:\n",
+			eccstr, ofs, i, len);
 		print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
 			       buf + i, dump_len, 0);
 
@@ -292,7 +292,7 @@ static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
 	return ret;
 }
 #else
-#define jffs2_verify_write(c,b,o) (0)
+#define jffs2_verify_write(c,b,o,l) (0)
 #endif
 
 /* Recover from failure to write wbuf. Recover the nodes up to the
@@ -455,7 +455,7 @@ static void jffs2_wbuf_recover(struct jffs2_sb_info *c)
 			ret = mtd_write(c->mtd, ofs, towrite, &retlen,
 					rewrite_buf);
 
-		if (ret || retlen != towrite || jffs2_verify_write(c, rewrite_buf, ofs)) {
+		if (ret || retlen != towrite || jffs2_verify_write(c, rewrite_buf, ofs, towrite)) {
 			/* Argh. We tried. Really we did. */
 			pr_crit("Recovery of wbuf failed due to a second write error\n");
 			kfree(buf);
@@ -672,7 +672,10 @@ static int __jffs2_flush_wbuf(struct jffs2_sb_info *c, int pad)
 			retlen, c->wbuf_pagesize);
 		ret = -EIO;
 		goto wfail;
-	} else if ((ret = jffs2_verify_write(c, c->wbuf, c->wbuf_ofs))) {
+	}
+
+	ret = jffs2_verify_write(c, c->wbuf, c->wbuf_ofs, c->wbuf_pagesize);
+	if (ret) {
 	wfail:
 		jffs2_wbuf_recover(c);
 
@@ -904,6 +907,10 @@ int jffs2_flash_writev(struct jffs2_sb_info *c, const struct kvec *invecs,
 			if (ret < 0 || wbuf_retlen != PAGE_DIV(vlen))
 				goto outfile;
 
+			ret = jffs2_verify_write(c, v, outvec_to, PAGE_DIV(vlen));
+			if (ret)
+				goto outfile;
+
 			vlen -= wbuf_retlen;
 			outvec_to += wbuf_retlen;
 			c->wbuf_ofs = outvec_to;
-- 
2.52.0



From zhouminqiang2 at huawei.com  Thu Aug 20 03:49:56 2026
From: zhouminqiang2 at huawei.com (zhouminqiang)
Date: Thu, 20 Aug 2026 18:49:56 +0800
Subject: [PATCH 0/6] jffs2: extend write verification to all write paths
Message-ID: <20260820105003.2525647-1-zhouminqiang2@huawei.com>

When JFFS2 writes data to flash, it first calculates a node CRC, and
the NAND controller calculates ECC as the data is programmed. If
corruption occurs between these two points, the possible causes are
RAM failures or bus transfer errors before the data reaches the flash,
or bit flips on the flash medium itself. To distinguish whether the
corruption happened during the write transfer or after commit to the
medium, commit a6bc432e296d ("[JFFS2] Add support for write-buffer
verification") introduced CONFIG_JFFS2_FS_WBUF_VERIFY: reading the
data back immediately after a successful write and comparing it with
the in-memory source buffer provides the missing observation point
for that diagnosis.

However, the current implementation only performs read-back verification
on write-buffer flush paths. Two scenarios remain uncovered:

  1. When the write data length exceeds wbuf_pagesize, the excess data
  bypasses the write buffer and is written directly to flash via
  mtd_write() in jffs2_flash_writev(), with no verification.
  2. NOR Flash and other non-writebuffered devices write directly through
  jffs2_flash_direct_write() and jffs2_flash_direct_writev(), with no
  equivalent check.

In both cases, if MTD reports a successful write but the readable medium
differs from JFFS2's source buffer, a later node CRC failure cannot
distinguish transport/program-time corruption from post-commit media
damage, defeating the original diagnostic intent.

This series covers both scenarios with the following changes.

Replace the pre-allocated per-superblock wbuf_verify buffer with
on-demand allocation inside jffs2_verify_write(). This allows
concurrent verification calls to proceed independently without
contending for a shared buffer. Additionally, memcmp() is replaced
with a byte-by-byte comparison that pinpoints the exact mismatch
offset, rather than merely reporting equal or not-equal.

Add verification calls in jffs2_flash_writev() for direct page
writes, and in jffs2_flash_direct_write() and
jffs2_flash_direct_writev() for NOR and other non-writebuffered
devices.

Since verification now covers all write paths rather than only the
write-buffer path, rename CONFIG_JFFS2_FS_WBUF_VERIFY to
CONFIG_JFFS2_FS_WRITE_VERIFY and remove the Kconfig dependency on
CONFIG_JFFS2_FS_WRITEBUFFER.

Add a module parameter write_verify (default off, 0644) to allow
dynamic enable/disable of write verification at runtime, so kernels
can carry the diagnostic facility without read-back overhead until
fault isolation is required. The parameter can be accessed through:

    /sys/module/jffs2/parameters/write_verify

This remains an optional diagnostic aid; node CRCs continue to provide
JFFS2's normal on-media integrity check.

zhouminqiang (6):
  jffs2: replace per-superblock verify buffer with per-write buffer
  jffs2: write verify: replace memcmp with byte-by-byte comparison
  jffs2: add write verification to direct page writes in flash_writev
  jffs2: add write verification to NOR direct write paths
  jffs2: rename CONFIG_JFFS2_FS_WBUF_VERIFY to
    CONFIG_JFFS2_FS_WRITE_VERIFY
  jffs2: add runtime toggle for write verification

 fs/jffs2/Kconfig       |  31 +++++++--
 fs/jffs2/jffs2_fs_sb.h |   3 -
 fs/jffs2/os-linux.h    |  11 ++++
 fs/jffs2/wbuf.c        |  86 +++---------------------
 fs/jffs2/writev.c      | 145 ++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 188 insertions(+), 88 deletions(-)

-- 
2.52.0



From zhouminqiang2 at huawei.com  Thu Aug 20 03:49:57 2026
From: zhouminqiang2 at huawei.com (zhouminqiang)
Date: Thu, 20 Aug 2026 18:49:57 +0800
Subject: [PATCH 1/6] jffs2: replace per-superblock verify buffer with per-write buffer
In-Reply-To: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
References: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
Message-ID: <20260820105003.2525647-2-zhouminqiang2@huawei.com>

Subsequent patches will extend write verification to additional write
paths that may execute concurrently. A shared per-superblock buffer
would require a coarse lock to serialize all verification, hurting
concurrency.

To avoid this contention, remove the wbuf_verify field from
jffs2_sb_info, along with the scattered kmalloc/kfree of wbuf_verify
in jffs2_nand_flash_setup, jffs2_dataflash_setup,
jffs2_nor_wbuf_flash_setup and their corresponding cleanup functions.

Instead, allocate a temporary buffer inside jffs2_verify_write(),
giving each invocation its own buffer and eliminating the shared state.

Signed-off-by: zhouminqiang <zhouminqiang2 at huawei.com>
---
 fs/jffs2/jffs2_fs_sb.h |  3 ---
 fs/jffs2/wbuf.c        | 61 ++++++++++++++++--------------------------
 2 files changed, 23 insertions(+), 41 deletions(-)

diff --git a/fs/jffs2/jffs2_fs_sb.h b/fs/jffs2/jffs2_fs_sb.h
index 5a7091746f68..8a75870d3fc8 100644
--- a/fs/jffs2/jffs2_fs_sb.h
+++ b/fs/jffs2/jffs2_fs_sb.h
@@ -124,9 +124,6 @@ struct jffs2_sb_info {
 
 	uint32_t wbuf_pagesize; /* 0 for NOR and other flashes with no wbuf */
 
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
-	unsigned char *wbuf_verify; /* read-back buffer for verification */
-#endif
 #ifdef CONFIG_JFFS2_FS_WRITEBUFFER
 	unsigned char *wbuf; /* Write-behind buffer for NAND flash */
 	uint32_t wbuf_ofs;
diff --git a/fs/jffs2/wbuf.c b/fs/jffs2/wbuf.c
index 3b7803c75d58..7e4608b43a4e 100644
--- a/fs/jffs2/wbuf.c
+++ b/fs/jffs2/wbuf.c
@@ -15,6 +15,7 @@
 
 #include <linux/kernel.h>
 #include <linux/slab.h>
+#include <linux/vmalloc.h>
 #include <linux/mtd/mtd.h>
 #include <linux/crc32.h>
 #include <linux/mtd/rawnand.h>
@@ -233,19 +234,30 @@ static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
 	int ret;
 	size_t retlen;
 	char *eccstr;
+	void *verify_buf;
 
-	ret = mtd_read(c->mtd, ofs, c->wbuf_pagesize, &retlen, c->wbuf_verify);
+	verify_buf = __vmalloc(c->wbuf_pagesize, GFP_NOFS);
+	if (!verify_buf) {
+		pr_warn("%s(): verify buffer allocation failed, skipping verification\n",
+			__func__);
+		return 0;
+	}
+
+	ret = mtd_read(c->mtd, ofs, c->wbuf_pagesize, &retlen, verify_buf);
 	if (ret && ret != -EUCLEAN && ret != -EBADMSG) {
 		pr_warn("%s(): Read back of page at %08x failed: %d\n",
 			__func__, c->wbuf_ofs, ret);
-		return ret;
+		goto out_free;
 	} else if (retlen != c->wbuf_pagesize) {
 		pr_warn("%s(): Read back of page at %08x gave short read: %zd not %d\n",
 			__func__, ofs, retlen, c->wbuf_pagesize);
-		return -EIO;
+		ret = -EIO;
+		goto out_free;
+	}
+	if (!memcmp(buf, verify_buf, c->wbuf_pagesize)) {
+		ret = 0;
+		goto out_free;
 	}
-	if (!memcmp(buf, c->wbuf_verify, c->wbuf_pagesize))
-		return 0;
 
 	if (ret == -EUCLEAN)
 		eccstr = "corrected";
@@ -261,9 +273,14 @@ static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
 
 	pr_warn("Read back:\n");
 	print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
-		       c->wbuf_verify, c->wbuf_pagesize, 0);
+		       verify_buf, c->wbuf_pagesize, 0);
 
+	vfree(verify_buf);
 	return -EIO;
+
+out_free:
+	vfree(verify_buf);
+	return ret;
 }
 #else
 #define jffs2_verify_write(c,b,o) (0)
@@ -1214,22 +1231,11 @@ int jffs2_nand_flash_setup(struct jffs2_sb_info *c)
 		return -ENOMEM;
 	}
 
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
-	c->wbuf_verify = kmalloc(c->wbuf_pagesize, GFP_KERNEL);
-	if (!c->wbuf_verify) {
-		kfree(c->oobbuf);
-		kfree(c->wbuf);
-		return -ENOMEM;
-	}
-#endif
 	return 0;
 }
 
 void jffs2_nand_flash_cleanup(struct jffs2_sb_info *c)
 {
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
-	kfree(c->wbuf_verify);
-#endif
 	kfree(c->wbuf);
 	kfree(c->oobbuf);
 }
@@ -1269,14 +1275,6 @@ int jffs2_dataflash_setup(struct jffs2_sb_info *c) {
 	if (!c->wbuf)
 		return -ENOMEM;
 
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
-	c->wbuf_verify = kmalloc(c->wbuf_pagesize, GFP_KERNEL);
-	if (!c->wbuf_verify) {
-		kfree(c->wbuf);
-		return -ENOMEM;
-	}
-#endif
-
 	pr_info("write-buffering enabled buffer (%d) erasesize (%d)\n",
 		c->wbuf_pagesize, c->sector_size);
 
@@ -1284,9 +1282,6 @@ int jffs2_dataflash_setup(struct jffs2_sb_info *c) {
 }
 
 void jffs2_dataflash_cleanup(struct jffs2_sb_info *c) {
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
-	kfree(c->wbuf_verify);
-#endif
 	kfree(c->wbuf);
 }
 
@@ -1306,20 +1301,10 @@ int jffs2_nor_wbuf_flash_setup(struct jffs2_sb_info *c) {
 	if (!c->wbuf)
 		return -ENOMEM;
 
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
-	c->wbuf_verify = kmalloc(c->wbuf_pagesize, GFP_KERNEL);
-	if (!c->wbuf_verify) {
-		kfree(c->wbuf);
-		return -ENOMEM;
-	}
-#endif
 	return 0;
 }
 
 void jffs2_nor_wbuf_flash_cleanup(struct jffs2_sb_info *c) {
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
-	kfree(c->wbuf_verify);
-#endif
 	kfree(c->wbuf);
 }
 
-- 
2.52.0



From zhouminqiang2 at huawei.com  Thu Aug 20 03:50:02 2026
From: zhouminqiang2 at huawei.com (zhouminqiang)
Date: Thu, 20 Aug 2026 18:50:02 +0800
Subject: [PATCH 6/6] jffs2: add runtime toggle for write verification
In-Reply-To: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
References: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
Message-ID: <20260820105003.2525647-7-zhouminqiang2@huawei.com>

Write verification is a diagnostic facility that adds read-back
overhead to every write. In production, this overhead is undesirable
unless fault isolation is required.

Add a module parameter jffs2.write_verify (bool, 0644) that defaults
to off when CONFIG_JFFS2_FS_WRITE_VERIFY is enabled. The verification
entry checks READ_ONCE(jffs2_write_verify) and returns immediately
when disabled, avoiding any overhead. The parameter can be toggled at
runtime via /sys/module/jffs2/parameters/write_verify or set at
boot/modprobe time.

Signed-off-by: zhouminqiang <zhouminqiang2 at huawei.com>
---
 fs/jffs2/Kconfig  |  8 ++++++++
 fs/jffs2/writev.c | 23 +++++++++++++++++++++++
 2 files changed, 31 insertions(+)

diff --git a/fs/jffs2/Kconfig b/fs/jffs2/Kconfig
index 556025a5d438..03dadbd003cd 100644
--- a/fs/jffs2/Kconfig
+++ b/fs/jffs2/Kconfig
@@ -61,6 +61,14 @@ config JFFS2_FS_WRITE_VERIFY
 	  tell transport/program-time corruption from post-commit media
 	  damage.
 
+	  Verification defaults to off when this option is selected and can
+	  be enabled at runtime via sysfs:
+
+	    /sys/module/jffs2/parameters/write_verify
+
+	  Write 0 to disable, 1 to enable.  Boot/modprobe parameter
+	  jffs2.write_verify=0|1 is also supported.
+
 	  If unsure, say 'N'.
 
 config JFFS2_SUMMARY
diff --git a/fs/jffs2/writev.c b/fs/jffs2/writev.c
index fc1eae60c476..b32eb07747f4 100644
--- a/fs/jffs2/writev.c
+++ b/fs/jffs2/writev.c
@@ -9,12 +9,32 @@
  *
  */
 
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
 #include <linux/kernel.h>
+#include <linux/module.h>
 #include <linux/vmalloc.h>
 #include <linux/mtd/mtd.h>
 #include "nodelist.h"
 
 #ifdef CONFIG_JFFS2_FS_WRITE_VERIFY
+/*
+ * Optional read-back after writes.
+ *
+ * Catch cases where data is corrupted after node CRCs are calculated but
+ * before it is correctly programmed -- e.g. in RAM or during DMA/bus
+ * transfer to the flash controller -- so mtd_write() succeeds while the
+ * medium does not match the in-memory image.
+ *
+ * Runtime toggle: /sys/module/jffs2/parameters/write_verify
+ * (also boot/modprobe: jffs2.write_verify=0|1)
+ */
+static bool jffs2_write_verify;
+module_param_named(write_verify, jffs2_write_verify, bool, 0644);
+MODULE_PARM_DESC(write_verify,
+		 "Verify flash writes by reading back (default: N)");
+
+
 int jffs2_verify_write(struct jffs2_sb_info *c, const unsigned char *buf,
 			      uint32_t ofs, size_t len)
 {
@@ -23,6 +43,9 @@ int jffs2_verify_write(struct jffs2_sb_info *c, const unsigned char *buf,
 	char *eccstr;
 	void *verify_buf;
 
+	if (!READ_ONCE(jffs2_write_verify))
+		return 0;
+
 	verify_buf = __vmalloc(len, GFP_NOFS);
 	if (!verify_buf) {
 		pr_warn("%s(): verify buffer allocation failed, skipping verification\n",
-- 
2.52.0



From zhouminqiang2 at huawei.com  Thu Aug 20 03:49:58 2026
From: zhouminqiang2 at huawei.com (zhouminqiang)
Date: Thu, 20 Aug 2026 18:49:58 +0800
Subject: [PATCH 2/6] jffs2: write verify: replace memcmp with byte-by-byte comparison
In-Reply-To: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
References: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
Message-ID: <20260820105003.2525647-3-zhouminqiang2@huawei.com>

jffs2_verify_write() uses memcmp() to compare the write buffer
against data read back from flash. While memcmp() compares byte
by byte internally, it only reports equal or not-equal without
identifying the mismatch offset.

To pinpoint the exact mismatch offset, replace this with the
approach used by UBI's self_check_write(): compare byte by byte
and, on mismatch, report the first differing offset and dump up
to 128 bytes of both the source and read-back data.

Signed-off-by: zhouminqiang <zhouminqiang2 at huawei.com>
---
 fs/jffs2/wbuf.c | 53 +++++++++++++++++++++++++++++--------------------
 1 file changed, 31 insertions(+), 22 deletions(-)

diff --git a/fs/jffs2/wbuf.c b/fs/jffs2/wbuf.c
index 7e4608b43a4e..da6da813a4ef 100644
--- a/fs/jffs2/wbuf.c
+++ b/fs/jffs2/wbuf.c
@@ -231,8 +231,8 @@ static struct jffs2_raw_node_ref **jffs2_incore_replace_raw(struct jffs2_sb_info
 static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
 			      uint32_t ofs)
 {
-	int ret;
-	size_t retlen;
+	int ret = 0;
+	size_t retlen, i;
 	char *eccstr;
 	void *verify_buf;
 
@@ -246,37 +246,46 @@ static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
 	ret = mtd_read(c->mtd, ofs, c->wbuf_pagesize, &retlen, verify_buf);
 	if (ret && ret != -EUCLEAN && ret != -EBADMSG) {
 		pr_warn("%s(): Read back of page at %08x failed: %d\n",
-			__func__, c->wbuf_ofs, ret);
+			__func__, ofs, ret);
 		goto out_free;
 	} else if (retlen != c->wbuf_pagesize) {
-		pr_warn("%s(): Read back of page at %08x gave short read: %zd not %d\n",
+		pr_warn("%s(): Read back of page at %08x gave short read: %zu not %d\n",
 			__func__, ofs, retlen, c->wbuf_pagesize);
 		ret = -EIO;
 		goto out_free;
 	}
-	if (!memcmp(buf, verify_buf, c->wbuf_pagesize)) {
-		ret = 0;
-		goto out_free;
-	}
 
-	if (ret == -EUCLEAN)
-		eccstr = "corrected";
-	else if (ret == -EBADMSG)
-		eccstr = "correction failed";
-	else
-		eccstr = "OK or unused";
+	for (i = 0; i < c->wbuf_pagesize; i++) {
+		uint8_t c1 = ((uint8_t *)buf)[i];
+		uint8_t c2 = ((uint8_t *)verify_buf)[i];
+		int dump_len;
 
-	pr_warn("Write verify error (ECC %s) at %08x. Wrote:\n",
-		eccstr, c->wbuf_ofs);
-	print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
-		       c->wbuf, c->wbuf_pagesize, 0);
+		if (c1 == c2)
+			continue;
 
-	pr_warn("Read back:\n");
-	print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
-		       verify_buf, c->wbuf_pagesize, 0);
+		if (ret == -EUCLEAN)
+			eccstr = "corrected";
+		else if (ret == -EBADMSG)
+			eccstr = "correction failed";
+		else
+			eccstr = "OK or unused";
+
+		dump_len = min_t(int, 128, c->wbuf_pagesize - i);
+		pr_warn("Write verify error (ECC %s) at %08x (+%zu/%d). Wrote:\n",
+			eccstr, ofs, i, c->wbuf_pagesize);
+		print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
+			       buf + i, dump_len, 0);
+
+		pr_warn("Read back:\n");
+		print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
+			       verify_buf + i, dump_len, 0);
+
+		ret = -EIO;
+		goto out_free;
+	}
 
 	vfree(verify_buf);
-	return -EIO;
+	return 0;
 
 out_free:
 	vfree(verify_buf);
-- 
2.52.0



From zhouminqiang2 at huawei.com  Thu Aug 20 03:50:01 2026
From: zhouminqiang2 at huawei.com (zhouminqiang)
Date: Thu, 20 Aug 2026 18:50:01 +0800
Subject: [PATCH 5/6] jffs2: rename CONFIG_JFFS2_FS_WBUF_VERIFY to CONFIG_JFFS2_FS_WRITE_VERIFY
In-Reply-To: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
References: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
Message-ID: <20260820105003.2525647-6-zhouminqiang2@huawei.com>

Write verification now covers all write paths including NOR direct
writes and write-buffer direct page writes, not just the write-buffer
flush path. The Kconfig option also no longer depends on
CONFIG_JFFS2_FS_WRITEBUFFER.

Rename the option from CONFIG_JFFS2_FS_WBUF_VERIFY to
CONFIG_JFFS2_FS_WRITE_VERIFY to reflect its broader scope and
independence from the write-buffer configuration.

Signed-off-by: zhouminqiang <zhouminqiang2 at huawei.com>
---
 fs/jffs2/Kconfig    | 23 ++++++++++++++++++-----
 fs/jffs2/os-linux.h |  2 +-
 fs/jffs2/writev.c   |  4 ++--
 3 files changed, 21 insertions(+), 8 deletions(-)

diff --git a/fs/jffs2/Kconfig b/fs/jffs2/Kconfig
index 560187d61562..556025a5d438 100644
--- a/fs/jffs2/Kconfig
+++ b/fs/jffs2/Kconfig
@@ -42,13 +42,26 @@ config JFFS2_FS_WRITEBUFFER
 	    - NOR flash with transparent ECC
 	    - DataFlash
 
-config JFFS2_FS_WBUF_VERIFY
-	bool "Verify JFFS2 write-buffer reads"
-	depends on JFFS2_FS_WRITEBUFFER
+config JFFS2_FS_WRITE_VERIFY
+	bool "Verify JFFS2 writes"
+	depends on JFFS2_FS
 	default n
 	help
-	  This causes JFFS2 to read back every page written through the
-	  write-buffer, and check for errors.
+	  Read back data immediately after flash writes and compare it
+	  with the in-memory image that was written. This covers both
+	  write-buffer flushes and direct writes to non-writebuffered
+	  devices.
+
+	  This may catch corruption introduced after node CRCs are
+	  calculated but before/while data is transferred to the flash
+	  controller (e.g. RAM or DMA), where mtd_write() may succeed
+	  while the medium does not match what JFFS2 intended.
+
+	  Without an immediate read-back, a later node CRC failure cannot
+	  tell transport/program-time corruption from post-commit media
+	  damage.
+
+	  If unsure, say 'N'.
 
 config JFFS2_SUMMARY
 	bool "JFFS2 summary support"
diff --git a/fs/jffs2/os-linux.h b/fs/jffs2/os-linux.h
index e73ef643fd97..65604a6f8148 100644
--- a/fs/jffs2/os-linux.h
+++ b/fs/jffs2/os-linux.h
@@ -192,7 +192,7 @@ int jffs2_flash_direct_writev(struct jffs2_sb_info *c, const struct kvec *vecs,
 int jffs2_flash_direct_write(struct jffs2_sb_info *c, loff_t ofs, size_t len,
 			size_t *retlen, const u_char *buf);
 
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
+#ifdef CONFIG_JFFS2_FS_WRITE_VERIFY
 int jffs2_verify_write(struct jffs2_sb_info *c, const unsigned char *buf,
 		       uint32_t ofs, size_t len);
 int jffs2_verify_writev(struct jffs2_sb_info *c,
diff --git a/fs/jffs2/writev.c b/fs/jffs2/writev.c
index 39adc15ffd69..fc1eae60c476 100644
--- a/fs/jffs2/writev.c
+++ b/fs/jffs2/writev.c
@@ -14,7 +14,7 @@
 #include <linux/mtd/mtd.h>
 #include "nodelist.h"
 
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
+#ifdef CONFIG_JFFS2_FS_WRITE_VERIFY
 int jffs2_verify_write(struct jffs2_sb_info *c, const unsigned char *buf,
 			      uint32_t ofs, size_t len)
 {
@@ -98,7 +98,7 @@ int jffs2_verify_writev(struct jffs2_sb_info *c,
 	}
 	return 0;
 }
-#endif /* CONFIG_JFFS2_FS_WBUF_VERIFY */
+#endif /* CONFIG_JFFS2_FS_WRITE_VERIFY */
 
 int jffs2_flash_direct_writev(struct jffs2_sb_info *c, const struct kvec *vecs,
 			      unsigned long count, loff_t to, size_t *retlen)
-- 
2.52.0



From zhouminqiang2 at huawei.com  Thu Aug 20 03:50:00 2026
From: zhouminqiang2 at huawei.com (zhouminqiang)
Date: Thu, 20 Aug 2026 18:50:00 +0800
Subject: [PATCH 4/6] jffs2: add write verification to NOR direct write paths
In-Reply-To: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
References: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
Message-ID: <20260820105003.2525647-5-zhouminqiang2@huawei.com>

NOR Flash and other non-writebuffered devices write directly through
jffs2_flash_direct_writev() and jffs2_flash_direct_write() without
any write-back verification. If mtd_write() succeeds but the readable
medium differs from JFFS2's source buffer, a later node CRC failure
cannot distinguish transport/program-time corruption from post-commit
media damage.

Move jffs2_verify_write() from wbuf.c to writev.c so it can be
shared by both writebuffered and direct write paths. Add
jffs2_verify_writev() to iterate over kvec entries and verify each
one individually. In both direct write functions, add mtd_write()
return value and retlen checks, and invoke verification after
a successful complete write.

Signed-off-by: zhouminqiang <zhouminqiang2 at huawei.com>
---
 fs/jffs2/os-linux.h |  11 ++++
 fs/jffs2/wbuf.c     |  69 -------------------------
 fs/jffs2/writev.c   | 122 ++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 130 insertions(+), 72 deletions(-)

diff --git a/fs/jffs2/os-linux.h b/fs/jffs2/os-linux.h
index 86ab014a349c..e73ef643fd97 100644
--- a/fs/jffs2/os-linux.h
+++ b/fs/jffs2/os-linux.h
@@ -192,6 +192,17 @@ int jffs2_flash_direct_writev(struct jffs2_sb_info *c, const struct kvec *vecs,
 int jffs2_flash_direct_write(struct jffs2_sb_info *c, loff_t ofs, size_t len,
 			size_t *retlen, const u_char *buf);
 
+#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
+int jffs2_verify_write(struct jffs2_sb_info *c, const unsigned char *buf,
+		       uint32_t ofs, size_t len);
+int jffs2_verify_writev(struct jffs2_sb_info *c,
+		       const struct kvec *vecs,
+		       unsigned long count, loff_t to);
+#else
+#define jffs2_verify_write(c, b, o, l) (0)
+#define jffs2_verify_writev(c, v, cnt, t) (0)
+#endif
+
 #endif /* __JFFS2_OS_LINUX_H__ */
 
 
diff --git a/fs/jffs2/wbuf.c b/fs/jffs2/wbuf.c
index 3e99587f40e4..493be5765c47 100644
--- a/fs/jffs2/wbuf.c
+++ b/fs/jffs2/wbuf.c
@@ -15,7 +15,6 @@
 
 #include <linux/kernel.h>
 #include <linux/slab.h>
-#include <linux/vmalloc.h>
 #include <linux/mtd/mtd.h>
 #include <linux/crc32.h>
 #include <linux/mtd/rawnand.h>
@@ -227,74 +226,6 @@ static struct jffs2_raw_node_ref **jffs2_incore_replace_raw(struct jffs2_sb_info
 	return NULL;
 }
 
-#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
-static int jffs2_verify_write(struct jffs2_sb_info *c, unsigned char *buf,
-			      uint32_t ofs, size_t len)
-{
-	int ret = 0;
-	size_t retlen, i;
-	char *eccstr;
-	void *verify_buf;
-
-	verify_buf = __vmalloc(len, GFP_NOFS);
-	if (!verify_buf) {
-		pr_warn("%s(): verify buffer allocation failed, skipping verification\n",
-			__func__);
-		return 0;
-	}
-
-	ret = mtd_read(c->mtd, ofs, len, &retlen, verify_buf);
-	if (ret && ret != -EUCLEAN && ret != -EBADMSG) {
-		pr_warn("%s(): Read back of page at %08x failed: %d\n",
-			__func__, ofs, ret);
-		goto out_free;
-	} else if (retlen != len) {
-		pr_warn("%s(): Read back of page at %08x gave short read: %zu not %zu\n",
-			__func__, ofs, retlen, len);
-		ret = -EIO;
-		goto out_free;
-	}
-
-	for (i = 0; i < len; i++) {
-		uint8_t c1 = ((uint8_t *)buf)[i];
-		uint8_t c2 = ((uint8_t *)verify_buf)[i];
-		int dump_len;
-
-		if (c1 == c2)
-			continue;
-
-		if (ret == -EUCLEAN)
-			eccstr = "corrected";
-		else if (ret == -EBADMSG)
-			eccstr = "correction failed";
-		else
-			eccstr = "OK or unused";
-
-		dump_len = min_t(int, 128, len - i);
-		pr_warn("Write verify error (ECC %s) at %08x (+%zu/%zu). Wrote:\n",
-			eccstr, ofs, i, len);
-		print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
-			       buf + i, dump_len, 0);
-
-		pr_warn("Read back:\n");
-		print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
-			       verify_buf + i, dump_len, 0);
-
-		ret = -EIO;
-		goto out_free;
-	}
-
-	vfree(verify_buf);
-	return 0;
-
-out_free:
-	vfree(verify_buf);
-	return ret;
-}
-#else
-#define jffs2_verify_write(c,b,o,l) (0)
-#endif
-
 /* Recover from failure to write wbuf. Recover the nodes up to the
  * wbuf, not the one which we were starting to try to write. */
 
diff --git a/fs/jffs2/writev.c b/fs/jffs2/writev.c
index a1bda9dab3f8..39adc15ffd69 100644
--- a/fs/jffs2/writev.c
+++ b/fs/jffs2/writev.c
@@ -10,12 +10,100 @@
  */
 
 #include <linux/kernel.h>
+#include <linux/vmalloc.h>
 #include <linux/mtd/mtd.h>
 #include "nodelist.h"
 
+#ifdef CONFIG_JFFS2_FS_WBUF_VERIFY
+int jffs2_verify_write(struct jffs2_sb_info *c, const unsigned char *buf,
+			      uint32_t ofs, size_t len)
+{
+	int ret = 0;
+	size_t retlen, i;
+	char *eccstr;
+	void *verify_buf;
+
+	verify_buf = __vmalloc(len, GFP_NOFS);
+	if (!verify_buf) {
+		pr_warn("%s(): verify buffer allocation failed, skipping verification\n",
+			__func__);
+		return 0;
+	}
+
+	ret = mtd_read(c->mtd, ofs, len, &retlen, verify_buf);
+	if (ret && ret != -EUCLEAN && ret != -EBADMSG) {
+		pr_warn("%s(): Read back of page at %08x failed: %d\n",
+			__func__, ofs, ret);
+		goto out_free;
+	} else if (retlen != len) {
+		pr_warn("%s(): Read back of page at %08x gave short read: %zu not %zu\n",
+			__func__, ofs, retlen, len);
+		ret = -EIO;
+		goto out_free;
+	}
+
+	for (i = 0; i < len; i++) {
+		uint8_t c1 = ((uint8_t *)buf)[i];
+		uint8_t c2 = ((uint8_t *)verify_buf)[i];
+		int dump_len;
+
+		if (c1 == c2)
+			continue;
+
+		if (ret == -EUCLEAN)
+			eccstr = "corrected";
+		else if (ret == -EBADMSG)
+			eccstr = "correction failed";
+		else
+			eccstr = "OK or unused";
+
+		dump_len = min_t(int, 128, len - i);
+		pr_warn("Write verify error (ECC %s) at %08x (+%zu/%zu). Wrote:\n",
+			eccstr, ofs, i, len);
+		print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
+			       buf + i, dump_len, 0);
+
+		pr_warn("Read back:\n");
+		print_hex_dump(KERN_WARNING, "", DUMP_PREFIX_OFFSET, 16, 1,
+			       verify_buf + i, dump_len, 0);
+
+		ret = -EIO;
+		goto out_free;
+	}
+
+	vfree(verify_buf);
+	return 0;
+
+out_free:
+	vfree(verify_buf);
+	return ret;
+}
+
+int jffs2_verify_writev(struct jffs2_sb_info *c,
+			const struct kvec *vecs,
+			unsigned long count, loff_t to)
+{
+	loff_t ofs = to;
+	unsigned long i;
+	int ret;
+
+	for (i = 0; i < count; i++) {
+		if (!vecs[i].iov_len)
+			continue;
+		ret = jffs2_verify_write(c, vecs[i].iov_base, ofs,
+					 vecs[i].iov_len);
+		if (ret)
+			return ret;
+		ofs += vecs[i].iov_len;
+	}
+	return 0;
+}
+#endif /* CONFIG_JFFS2_FS_WBUF_VERIFY */
+
 int jffs2_flash_direct_writev(struct jffs2_sb_info *c, const struct kvec *vecs,
 			      unsigned long count, loff_t to, size_t *retlen)
 {
+	int ret;
 	if (!jffs2_is_writebuffered(c)) {
 		if (jffs2_sum_active()) {
 			int res;
@@ -26,15 +114,31 @@ int jffs2_flash_direct_writev(struct jffs2_sb_info *c, const struct kvec *vecs,
 		}
 	}
 
-	return mtd_writev(c->mtd, vecs, count, to, retlen);
+	ret = mtd_writev(c->mtd, vecs, count, to, retlen);
+
+	if (ret) {
+		pr_warn("%s(): Write failed with %d\n", __func__, ret);
+	} else {
+		size_t totlen = 0;
+		unsigned long i;
+
+		for (i = 0; i < count; i++)
+			totlen += vecs[i].iov_len;
+		if (*retlen != totlen) {
+			pr_warn("%s(): Write was short: %zu instead of %zu\n",
+				__func__, *retlen, totlen);
+			ret = -EIO;
+		} else
+			ret = jffs2_verify_writev(c, vecs, count, to);
+	}
+
+	return ret;
 }
 
 int jffs2_flash_direct_write(struct jffs2_sb_info *c, loff_t ofs, size_t len,
 			size_t *retlen, const u_char *buf)
 {
 	int ret;
-	ret = mtd_write(c->mtd, ofs, len, retlen, buf);
-
 	if (jffs2_sum_active()) {
 		struct kvec vecs[1];
 		int res;
@@ -47,5 +151,17 @@ int jffs2_flash_direct_write(struct jffs2_sb_info *c, loff_t ofs, size_t len,
 			return res;
 		}
 	}
+
+	ret = mtd_write(c->mtd, ofs, len, retlen, buf);
+
+	if (ret) {
+		pr_warn("%s(): Write failed with %d\n", __func__, ret);
+	} else if (*retlen != len) {
+		pr_warn("%s(): Write was short: %zu instead of %zu\n",
+			__func__, *retlen, len);
+		ret = -EIO;
+	} else
+		ret = jffs2_verify_write(c, buf, ofs, len);
+
 	return ret;
 }
-- 
2.52.0



From richard at nod.at  Thu Aug 20 11:58:21 2026
From: richard at nod.at (Richard Weinberger)
Date: Thu, 20 Aug 2026 20:58:21 +0200 (CEST)
Subject: [PATCH 0/6] jffs2: extend write verification to all write paths
In-Reply-To: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
References: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
Message-ID: <852434244.6574.1787252301788.JavaMail.zimbra@nod.at>

----- Urspr?ngliche Mail -----
> Von: "zhouminqiang" <zhouminqiang2 at huawei.com>
> An: "David Woodhouse" <dwmw2 at infradead.org>, "richard" <richard at nod.at>
> CC: "linux-mtd" <linux-mtd at lists.infradead.org>, "linux-kernel" <linux-kernel at vger.kernel.org>, "chengzhihao1"
> <chengzhihao1 at huawei.com>, "yangerkun" <yangerkun at huawei.com>, "yi zhang" <yi.zhang at huawei.com>
> Gesendet: Donnerstag, 20. August 2026 12:49:56
> Betreff: [PATCH 0/6] jffs2: extend write verification to all write paths

> When JFFS2 writes data to flash, it first calculates a node CRC, and
> the NAND controller calculates ECC as the data is programmed. If

Why would one use JFFS2 in 2026 on NAND flash?

> corruption occurs between these two points, the possible causes are
> RAM failures or bus transfer errors before the data reaches the flash,
> or bit flips on the flash medium itself. To distinguish whether the
> corruption happened during the write transfer or after commit to the
> medium, commit a6bc432e296d ("[JFFS2] Add support for write-buffer
> verification") introduced CONFIG_JFFS2_FS_WBUF_VERIFY: reading the
> data back immediately after a successful write and comparing it with
> the in-memory source buffer provides the missing observation point
> for that diagnosis.
> 
> However, the current implementation only performs read-back verification
> on write-buffer flush paths. Two scenarios remain uncovered:
> 
>  1. When the write data length exceeds wbuf_pagesize, the excess data
>  bypasses the write buffer and is written directly to flash via
>  mtd_write() in jffs2_flash_writev(), with no verification.
>  2. NOR Flash and other non-writebuffered devices write directly through
>  jffs2_flash_direct_write() and jffs2_flash_direct_writev(), with no
>  equivalent check.
> 
> In both cases, if MTD reports a successful write but the readable medium
> differs from JFFS2's source buffer, a later node CRC failure cannot
> distinguish transport/program-time corruption from post-commit media
> damage, defeating the original diagnostic intent.
> 
> This series covers both scenarios with the following changes.
> 
> Replace the pre-allocated per-superblock wbuf_verify buffer with
> on-demand allocation inside jffs2_verify_write(). This allows
> concurrent verification calls to proceed independently without
> contending for a shared buffer. Additionally, memcmp() is replaced
> with a byte-by-byte comparison that pinpoints the exact mismatch
> offset, rather than merely reporting equal or not-equal.
> 
> Add verification calls in jffs2_flash_writev() for direct page
> writes, and in jffs2_flash_direct_write() and
> jffs2_flash_direct_writev() for NOR and other non-writebuffered
> devices.
> 
> Since verification now covers all write paths rather than only the
> write-buffer path, rename CONFIG_JFFS2_FS_WBUF_VERIFY to
> CONFIG_JFFS2_FS_WRITE_VERIFY and remove the Kconfig dependency on
> CONFIG_JFFS2_FS_WRITEBUFFER.
> 
> Add a module parameter write_verify (default off, 0644) to allow
> dynamic enable/disable of write verification at runtime, so kernels
> can carry the diagnostic facility without read-back overhead until
> fault isolation is required. The parameter can be accessed through:
> 
>    /sys/module/jffs2/parameters/write_verify
> 
> This remains an optional diagnostic aid; node CRCs continue to provide
> JFFS2's normal on-media integrity check.

Please explain why this is needed.
JFFS2 is in "Odd fixes" maintenance mode.
It never worked well for NAND flash, that's why UBIFS (JFFS3) was born.

Thanks,
//richard


From dwmw2 at infradead.org  Thu Aug 20 13:46:45 2026
From: dwmw2 at infradead.org (David Woodhouse)
Date: Thu, 20 Aug 2026 21:46:45 +0100
Subject: [PATCH 0/6] jffs2: extend write verification to all write paths
In-Reply-To: <852434244.6574.1787252301788.JavaMail.zimbra@nod.at>
References: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
	 <852434244.6574.1787252301788.JavaMail.zimbra@nod.at>
Message-ID: <71cde1ebfc9fd3c00dc5a352e2cb1fb7fb5af5ad.camel@infradead.org>

On Thu, 2026-08-20 at 20:58 +0200, Richard Weinberger wrote:
> 
> Please explain why this is needed.
> JFFS2 is in "Odd fixes" maintenance mode.
> It never worked well for NAND flash, that's why UBIFS (JFFS3) was
> born.

JFFS2 works OK for NAND flash. We got it running with up to 1GiB of
NAND for OLPC.

It doesn't scale well past that, with both memory usage and mount time,
but it's still in active use on NAND in a bunch of embedded platforms
(including OpenWRT routers).
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/pkcs7-signature
Size: 6179 bytes
Desc: not available
URL: <http://lists.infradead.org/pipermail/linux-mtd/attachments/20260820/15c1533e/attachment.p7s>

From zhouminqiang2 at huawei.com  Thu Aug 20 20:37:13 2026
From: zhouminqiang2 at huawei.com (zhouminqiang)
Date: Fri, 21 Aug 2026 11:37:13 +0800
Subject: [PATCH 0/6] jffs2: extend write verification to all write paths
In-Reply-To: <852434244.6574.1787252301788.JavaMail.zimbra@nod.at>
References: <20260820105003.2525647-1-zhouminqiang2@huawei.com>
 <852434244.6574.1787252301788.JavaMail.zimbra@nod.at>
Message-ID: <2ca192b7-f903-41ba-8ffe-e8bfe9f79125@huawei.com>

Thank you for your review and feedback.

Sorry for the confusion caused by my cover letter. We are not using
JFFS2 on NAND flash devices. Our primary goal is to extend write
verification to the NOR flash path because we have encountered data
corruption incidents in production environment and currently lack a
quick diagnostic tool to isolate the cause. While reviewing the
code flow, we noticed that the NAND buffered write path also lacked
verification, so we took the opportunity to extend it as well.

Although JFFS2 is in "Odd fixes" maintenance mode, there are still
a significant number of devices in production that rely on JFFS2,
and we are encountering data corruption issues on live nodes.
Extending write verification to the NOR flash path would provide a
diagnostic tool for fault isolation. We notice that WBUF_VERIFY has
existed since 2007 (a6bc432e296d), so we want to extend the same
diagnostic facility to cover the write paths it missed, so that it
can serve ongoing product issue diagnosis. IMHO, this is a
debugging/maintenance feature, not introducing new functionality.

We respect your judgment and will leave the final decision to you.

? 2026/8/21 2:58, Richard Weinberger ??:
> ----- Urspr?ngliche Mail -----
>> Von: "zhouminqiang" <zhouminqiang2 at huawei.com>
>> An: "David Woodhouse" <dwmw2 at infradead.org>, "richard" <richard at nod.at>
>> CC: "linux-mtd" <linux-mtd at lists.infradead.org>, "linux-kernel" <linux-kernel at vger.kernel.org>, "chengzhihao1"
>> <chengzhihao1 at huawei.com>, "yangerkun" <yangerkun at huawei.com>, "yi zhang" <yi.zhang at huawei.com>
>> Gesendet: Donnerstag, 20. August 2026 12:49:56
>> Betreff: [PATCH 0/6] jffs2: extend write verification to all write paths
> 
>> When JFFS2 writes data to flash, it first calculates a node CRC, and
>> the NAND controller calculates ECC as the data is programmed. If
> 
> Why would one use JFFS2 in 2026 on NAND flash?
> 
>> corruption occurs between these two points, the possible causes are
>> RAM failures or bus transfer errors before the data reaches the flash,
>> or bit flips on the flash medium itself. To distinguish whether the
>> corruption happened during the write transfer or after commit to the
>> medium, commit a6bc432e296d ("[JFFS2] Add support for write-buffer
>> verification") introduced CONFIG_JFFS2_FS_WBUF_VERIFY: reading the
>> data back immediately after a successful write and comparing it with
>> the in-memory source buffer provides the missing observation point
>> for that diagnosis.
>>
>> However, the current implementation only performs read-back verification
>> on write-buffer flush paths. Two scenarios remain uncovered:
>>
>>   1. When the write data length exceeds wbuf_pagesize, the excess data
>>   bypasses the write buffer and is written directly to flash via
>>   mtd_write() in jffs2_flash_writev(), with no verification.
>>   2. NOR Flash and other non-writebuffered devices write directly through
>>   jffs2_flash_direct_write() and jffs2_flash_direct_writev(), with no
>>   equivalent check.
>>
>> In both cases, if MTD reports a successful write but the readable medium
>> differs from JFFS2's source buffer, a later node CRC failure cannot
>> distinguish transport/program-time corruption from post-commit media
>> damage, defeating the original diagnostic intent.
>>
>> This series covers both scenarios with the following changes.
>>
>> Replace the pre-allocated per-superblock wbuf_verify buffer with
>> on-demand allocation inside jffs2_verify_write(). This allows
>> concurrent verification calls to proceed independently without
>> contending for a shared buffer. Additionally, memcmp() is replaced
>> with a byte-by-byte comparison that pinpoints the exact mismatch
>> offset, rather than merely reporting equal or not-equal.
>>
>> Add verification calls in jffs2_flash_writev() for direct page
>> writes, and in jffs2_flash_direct_write() and
>> jffs2_flash_direct_writev() for NOR and other non-writebuffered
>> devices.
>>
>> Since verification now covers all write paths rather than only the
>> write-buffer path, rename CONFIG_JFFS2_FS_WBUF_VERIFY to
>> CONFIG_JFFS2_FS_WRITE_VERIFY and remove the Kconfig dependency on
>> CONFIG_JFFS2_FS_WRITEBUFFER.
>>
>> Add a module parameter write_verify (default off, 0644) to allow
>> dynamic enable/disable of write verification at runtime, so kernels
>> can carry the diagnostic facility without read-back overhead until
>> fault isolation is required. The parameter can be accessed through:
>>
>>     /sys/module/jffs2/parameters/write_verify
>>
>> This remains an optional diagnostic aid; node CRCs continue to provide
>> JFFS2's normal on-media integrity check.
> 
> Please explain why this is needed.
> JFFS2 is in "Odd fixes" maintenance mode.
> It never worked well for NAND flash, that's why UBIFS (JFFS3) was born.
> 
> Thanks,
> //richard



From mhklinux at outlook.com  Thu Aug 20 21:13:05 2026
From: mhklinux at outlook.com (Michael Kelley)
Date: Fri, 21 Aug 2026 04:13:05 +0000
Subject: [PATCH v2 12/13] treewide: convert rdmsrq() from a macro to an
 inline function
In-Reply-To: <20260819102314.1499258-13-jgross@suse.com>
References: <20260819102314.1499258-1-jgross@suse.com>
 <20260819102314.1499258-13-jgross@suse.com>
Message-ID: <SN6PR02MB4157769F2FAEB7D78259164ED4A32@SN6PR02MB4157.namprd02.prod.outlook.com>

From: Juergen Gross <jgross at suse.com> Sent: Wednesday, August 19, 2026 3:23 AM
> 
> Today rdmsrq() is a macro using its second parameter as the target for
> storing the read MSR value.
> 
> Convert rdmsrq() to an inline function returning the MSR value.
> 
> The users have been converted using the following semantic patch:
> 
>   // Options: --include-headers
> 
>   virtual patch
>   virtual report
> 
>   @@
>   expression msr, val;
>   @@
>   (
>   - rdmsrq(msr,val)
>   + val = rdmsrq(msr)
>   )
> 
> Signed-off-by: Juergen Gross <jgross at suse.com>
> ---
>  arch/x86/coco/sev/core.c                      |  2 +-
>  arch/x86/events/amd/brs.c                     |  4 +--
>  arch/x86/events/amd/core.c                    |  4 +--
>  arch/x86/events/amd/ibs.c                     | 18 +++++-----
>  arch/x86/events/amd/lbr.c                     |  8 ++---
>  arch/x86/events/amd/power.c                   |  8 ++---
>  arch/x86/events/amd/uncore.c                  |  4 +--
>  arch/x86/events/core.c                        | 20 +++++------
>  arch/x86/events/intel/core.c                  | 11 +++---
>  arch/x86/events/intel/cstate.c                |  2 +-
>  arch/x86/events/intel/ds.c                    |  2 +-
>  arch/x86/events/intel/knc.c                   |  6 ++--
>  arch/x86/events/intel/lbr.c                   | 14 ++++----
>  arch/x86/events/intel/p4.c                    |  6 ++--
>  arch/x86/events/intel/p6.c                    |  4 +--
>  arch/x86/events/intel/pt.c                    | 12 +++----
>  arch/x86/events/intel/uncore.c                |  2 +-
>  arch/x86/events/intel/uncore_nhmex.c          |  4 +--
>  arch/x86/events/intel/uncore_snb.c            |  2 +-
>  arch/x86/events/intel/uncore_snbep.c          |  6 ++--
>  arch/x86/events/msr.c                         |  2 +-
>  arch/x86/events/perf_event.h                  |  6 ++--
>  arch/x86/events/rapl.c                        |  4 +--
>  arch/x86/events/zhaoxin/core.c                |  6 ++--
>  arch/x86/hyperv/hv_apic.c                     |  6 ++--
>  arch/x86/hyperv/hv_init.c                     | 26 +++++++-------
>  arch/x86/hyperv/hv_spinlock.c                 |  2 +-
>  arch/x86/include/asm/apic.h                   |  4 +--
>  arch/x86/include/asm/debugreg.h               |  2 +-
>  arch/x86/include/asm/fsgsbase.h               |  2 +-
>  arch/x86/include/asm/kvm_host.h               |  2 +-
>  arch/x86/include/asm/msr.h                    | 15 ++++----
>  arch/x86/include/asm/paravirt.h               |  8 ++---
>  arch/x86/kernel/apic/apic.c                   | 14 ++++----
>  arch/x86/kernel/apic/apic_numachip.c          |  6 ++--
>  arch/x86/kernel/cet.c                         |  2 +-
>  arch/x86/kernel/cpu/amd.c                     | 14 ++++----
>  arch/x86/kernel/cpu/aperfmperf.c              |  8 ++---
>  arch/x86/kernel/cpu/bugs.c                    | 12 +++----
>  arch/x86/kernel/cpu/bus_lock.c                |  8 ++---
>  arch/x86/kernel/cpu/centaur.c                 |  8 ++---
>  arch/x86/kernel/cpu/common.c                  | 12 +++----
>  arch/x86/kernel/cpu/feat_ctl.c                |  4 +--
>  arch/x86/kernel/cpu/hygon.c                   |  4 +--
>  arch/x86/kernel/cpu/intel.c                   |  6 ++--
>  arch/x86/kernel/cpu/intel_epb.c               |  4 +--
>  arch/x86/kernel/cpu/mce/amd.c                 |  4 +--
>  arch/x86/kernel/cpu/mce/core.c                |  8 ++---
>  arch/x86/kernel/cpu/mce/inject.c              |  2 +-
>  arch/x86/kernel/cpu/mce/intel.c               | 18 +++++-----
>  arch/x86/kernel/cpu/mce/p5.c                  |  8 ++---
>  arch/x86/kernel/cpu/mce/winchip.c             |  2 +-
>  arch/x86/kernel/cpu/microcode/intel.c         |  2 +-
>  arch/x86/kernel/cpu/mshyperv.c                |  6 ++--
>  arch/x86/kernel/cpu/mtrr/amd.c                |  4 +--
>  arch/x86/kernel/cpu/mtrr/cleanup.c            |  4 +--
>  arch/x86/kernel/cpu/mtrr/generic.c            | 32 ++++++++---------
>  arch/x86/kernel/cpu/mtrr/mtrr.c               |  2 +-
>  arch/x86/kernel/cpu/resctrl/core.c            |  2 +-
>  arch/x86/kernel/cpu/resctrl/monitor.c         |  4 +--
>  arch/x86/kernel/cpu/resctrl/pseudo_lock.c     |  4 +--
>  arch/x86/kernel/cpu/resctrl/rdtgroup.c        |  2 +-
>  arch/x86/kernel/cpu/topology.c                |  2 +-
>  arch/x86/kernel/cpu/topology_amd.c            |  4 +--
>  arch/x86/kernel/cpu/transmeta.c               |  2 +-
>  arch/x86/kernel/cpu/tsx.c                     | 10 +++---
>  arch/x86/kernel/cpu/umwait.c                  |  2 +-
>  arch/x86/kernel/cpu/zhaoxin.c                 |  4 +--
>  arch/x86/kernel/fpu/core.c                    |  2 +-
>  arch/x86/kernel/hpet.c                        |  2 +-
>  arch/x86/kernel/kvm.c                         |  2 +-
>  arch/x86/kernel/mmconf-fam10h_64.c            |  6 ++--
>  arch/x86/kernel/process.c                     |  4 +--
>  arch/x86/kernel/process_64.c                  | 14 ++++----
>  arch/x86/kernel/shstk.c                       |  8 ++---
>  arch/x86/kernel/traps.c                       |  4 +--
>  arch/x86/kernel/tsc.c                         |  2 +-
>  arch/x86/kernel/tsc_msr.c                     |  6 ++--
>  arch/x86/kernel/tsc_sync.c                    |  6 ++--
>  arch/x86/kvm/svm/pmu.c                        |  4 +--
>  arch/x86/kvm/svm/svm.c                        |  4 +--
>  arch/x86/kvm/vmx/nested.c                     |  4 +--
>  arch/x86/kvm/vmx/pmu_intel.c                  |  8 ++---
>  arch/x86/kvm/vmx/sgx.c                        |  6 ++--
>  arch/x86/kvm/vmx/vmx.c                        | 36 +++++++++----------
>  arch/x86/kvm/x86.c                            |  8 ++---
>  arch/x86/lib/insn-eval.c                      |  6 ++--
>  arch/x86/lib/msr-smp.c                        |  2 +-
>  arch/x86/mm/pat/memtype.c                     |  2 +-
>  arch/x86/pci/amd_bus.c                        |  8 ++---
>  arch/x86/platform/olpc/olpc-xo1-rtc.c         |  6 ++--
>  arch/x86/platform/olpc/olpc-xo1-sci.c         |  2 +-
>  arch/x86/power/cpu.c                          | 10 +++---
>  arch/x86/realmode/init.c                      |  2 +-
>  arch/x86/virt/hw.c                            |  8 ++---
>  arch/x86/virt/svm/sev.c                       | 18 +++++-----
>  arch/x86/virt/vmx/tdx/tdx.c                   |  2 +-
>  arch/x86/xen/suspend.c                        |  2 +-
>  drivers/acpi/processor_perflib.c              |  2 +-
>  drivers/ata/pata_cs5535.c                     |  4 +--
>  drivers/ata/pata_cs5536.c                     |  2 +-
>  drivers/char/agp/nvidia-agp.c                 |  6 ++--
>  drivers/char/hw_random/via-rng.c              |  4 +--
>  drivers/cpufreq/acpi-cpufreq.c                |  8 ++---
>  drivers/cpufreq/amd-pstate.c                  |  4 +--
>  drivers/cpufreq/e_powersaver.c                | 20 +++++------
>  drivers/cpufreq/intel_pstate.c                | 30 ++++++++--------
>  drivers/cpufreq/longhaul.c                    | 12 +++----
>  drivers/cpufreq/longrun.c                     | 16 ++++-----
>  drivers/cpufreq/powernow-k7.c                 | 10 +++---
>  drivers/cpufreq/powernow-k8.c                 |  8 ++---
>  drivers/cpufreq/speedstep-centrino.c          |  4 +--
>  drivers/cpufreq/speedstep-lib.c               | 14 ++++----
>  drivers/edac/amd64_edac.c                     |  6 ++--
>  drivers/gpio/gpio-cs5535.c                    |  2 +-
>  drivers/hv/mshv_vtl_main.c                    |  2 +-
>  drivers/hwmon/hwmon-vid.c                     |  4 +--
>  drivers/idle/intel_idle.c                     | 26 +++++++-------
>  drivers/misc/cs5535-mfgpt.c                   |  6 ++--
>  drivers/mtd/nand/raw/cs553x_nand.c            |  6 ++--
>  drivers/platform/x86/intel/ifs/load.c         | 10 +++---
>  drivers/platform/x86/intel/ifs/runtest.c      |  8 ++---
>  drivers/platform/x86/intel/pmc/cnp.c          |  2 +-
>  .../intel/speed_select_if/isst_if_mbox_msr.c  |  6 ++--
>  .../intel/speed_select_if/isst_tpmi_core.c    |  2 +-
>  drivers/platform/x86/intel_ips.c              | 20 +++++------
>  drivers/powercap/intel_rapl_msr.c             |  2 +-
>  drivers/thermal/intel/intel_hfi.c             |  8 ++---
>  drivers/thermal/intel/therm_throt.c           | 22 ++++++------
>  drivers/thermal/intel/x86_pkg_temp_thermal.c  |  6 ++--
>  drivers/video/fbdev/geode/display_gx.c        |  2 +-
>  drivers/video/fbdev/geode/gxfb_core.c         |  2 +-
>  drivers/video/fbdev/geode/lxfb_ops.c          | 18 +++++-----
>  drivers/video/fbdev/geode/suspend_gx.c        |  8 ++---
>  drivers/video/fbdev/geode/video_gx.c          |  8 ++---
>  include/linux/cs5535.h                        |  2 +-
>  136 files changed, 484 insertions(+), 486 deletions(-)
> 

[snip]

> diff --git a/arch/x86/hyperv/hv_apic.c b/arch/x86/hyperv/hv_apic.c
> index 95f1782d1e17..4e30f9a11bc4 100644
> --- a/arch/x86/hyperv/hv_apic.c
> +++ b/arch/x86/hyperv/hv_apic.c
> @@ -38,7 +38,7 @@ static u64 hv_apic_icr_read(void)
>  {
>  	u64 reg_val;
> 
> -	rdmsrq(HV_X64_MSR_ICR, reg_val);
> +	reg_val = rdmsrq(HV_X64_MSR_ICR);
>  	return reg_val;
>  }
> 
> @@ -64,10 +64,10 @@ static u32 hv_apic_read(u32 reg)
> 
>  	switch (reg) {
>  	case APIC_EOI:
> -		rdmsrq(HV_X64_MSR_EOI, reg_val.q);
> +		reg_val.q = rdmsrq(HV_X64_MSR_EOI);
>  		return reg_val.l;
>  	case APIC_TASKPRI:
> -		rdmsrq(HV_X64_MSR_TPR, reg_val.q);
> +		reg_val.q = rdmsrq(HV_X64_MSR_TPR);
>  		return reg_val.l;
> 
>  	default:
> diff --git a/arch/x86/hyperv/hv_init.c b/arch/x86/hyperv/hv_init.c
> index 55a8b6de2865..bc8f91114868 100644
> --- a/arch/x86/hyperv/hv_init.c
> +++ b/arch/x86/hyperv/hv_init.c
> @@ -101,7 +101,7 @@ static int hyperv_init_ghcb(void)
>  	 * returned by MSR_AMD64_SEV_ES_GHCB is above shared
>  	 * memory boundary and map it here.
>  	 */
> -	rdmsrq(MSR_AMD64_SEV_ES_GHCB, ghcb_gpa);
> +	ghcb_gpa = rdmsrq(MSR_AMD64_SEV_ES_GHCB);
> 
>  	/* Mask out vTOM bit and map as decrypted */
>  	ghcb_gpa &= ~ms_hyperv.shared_gpa_boundary;
> @@ -134,7 +134,7 @@ static int hv_cpu_init(unsigned int cpu)
>  		 * For root partition we get the hypervisor provided VP assist
>  		 * page, instead of allocating a new page.
>  		 */
> -		rdmsrq(HV_X64_MSR_VP_ASSIST_PAGE, msr.as_uint64);
> +		msr.as_uint64 = rdmsrq(HV_X64_MSR_VP_ASSIST_PAGE);
>  		*hvp = memremap(msr.pfn << HV_X64_MSR_VP_ASSIST_PAGE_ADDRESS_SHIFT,
>  				PAGE_SIZE, MEMREMAP_WB);
>  	} else {
> @@ -183,7 +183,7 @@ static void hv_reenlightenment_notify(struct work_struct *dummy)
>  {
>  	struct hv_tsc_emulation_status emu_status;
> 
> -	rdmsrq(HV_X64_MSR_TSC_EMULATION_STATUS, *(u64 *)&emu_status);
> +	*(u64 *)&emu_status = rdmsrq(HV_X64_MSR_TSC_EMULATION_STATUS);
> 
>  	/* Don't issue the callback if TSC accesses are not emulated */
>  	if (hv_reenlightenment_cb && emu_status.inprogress)
> @@ -196,11 +196,11 @@ void hyperv_stop_tsc_emulation(void)
>  	u64 freq;
>  	struct hv_tsc_emulation_status emu_status;
> 
> -	rdmsrq(HV_X64_MSR_TSC_EMULATION_STATUS, *(u64 *)&emu_status);
> +	*(u64 *)&emu_status = rdmsrq(HV_X64_MSR_TSC_EMULATION_STATUS);
>  	emu_status.inprogress = 0;
>  	wrmsrq(HV_X64_MSR_TSC_EMULATION_STATUS, *(u64 *)&emu_status);
> 
> -	rdmsrq(HV_X64_MSR_TSC_FREQUENCY, freq);
> +	freq = rdmsrq(HV_X64_MSR_TSC_FREQUENCY);
>  	tsc_khz = div64_u64(freq, 1000);
>  }
>  EXPORT_SYMBOL_GPL(hyperv_stop_tsc_emulation);
> @@ -260,7 +260,7 @@ void clear_hv_tscchange_cb(void)
>  	if (!hv_reenlightenment_available())
>  		return;
> 
> -	rdmsrq(HV_X64_MSR_REENLIGHTENMENT_CONTROL, *(u64 *)&re_ctrl);
> +	*(u64 *)&re_ctrl = rdmsrq(HV_X64_MSR_REENLIGHTENMENT_CONTROL);
>  	re_ctrl.enabled = 0;
>  	wrmsrq(HV_X64_MSR_REENLIGHTENMENT_CONTROL, *(u64 *)&re_ctrl);
> 
> @@ -297,7 +297,7 @@ static int hv_cpu_die(unsigned int cpu)
>  			 */
>  			memunmap(hv_vp_assist_page[cpu]);
>  			hv_vp_assist_page[cpu] = NULL;
> -			rdmsrq(HV_X64_MSR_VP_ASSIST_PAGE, msr.as_uint64);
> +			msr.as_uint64 = rdmsrq(HV_X64_MSR_VP_ASSIST_PAGE);
>  			msr.enable = 0;
>  		}
>  		wrmsrq(HV_X64_MSR_VP_ASSIST_PAGE, msr.as_uint64);
> @@ -306,7 +306,7 @@ static int hv_cpu_die(unsigned int cpu)
>  	if (hv_reenlightenment_cb == NULL)
>  		return 0;
> 
> -	rdmsrq(HV_X64_MSR_REENLIGHTENMENT_CONTROL, *((u64 *)&re_ctrl));
> +	*((u64 *)&re_ctrl) = rdmsrq(HV_X64_MSR_REENLIGHTENMENT_CONTROL);
>  	if (re_ctrl.target_vp == hv_vp_index[cpu]) {
>  		/*
>  		 * Reassign reenlightenment notifications to some other online
> @@ -377,7 +377,7 @@ static int hv_suspend(void *data)
>  	hv_set_hypercall_pg(NULL);
> 
>  	/* Disable the hypercall page in the hypervisor */
> -	rdmsrq(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
> +	hypercall_msr.as_uint64 = rdmsrq(HV_X64_MSR_HYPERCALL);
>  	hypercall_msr.enable = 0;
>  	wrmsrq(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
> 
> @@ -394,7 +394,7 @@ static void hv_resume(void *data)
>  	WARN_ON(ret);
> 
>  	/* Re-enable the hypercall page */
> -	rdmsrq(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
> +	hypercall_msr.as_uint64 = rdmsrq(HV_X64_MSR_HYPERCALL);
>  	hypercall_msr.enable = 1;
>  	hypercall_msr.guest_physical_address =
>  		vmalloc_to_pfn(hv_hypercall_pg_saved);
> @@ -529,7 +529,7 @@ void __init hyperv_init(void)
>  	if (hv_hypercall_pg == NULL)
>  		goto clean_guest_os_id;
> 
> -	rdmsrq(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
> +	hypercall_msr.as_uint64 = rdmsrq(HV_X64_MSR_HYPERCALL);
>  	hypercall_msr.enable = 1;
> 
>  	if (hv_root_partition()) {
> @@ -671,7 +671,7 @@ void hyperv_report_panic(struct pt_regs *regs, long err, bool in_die)
>  		return;
>  	panic_reported = true;
> 
> -	rdmsrq(HV_X64_MSR_GUEST_OS_ID, guest_id);
> +	guest_id = rdmsrq(HV_X64_MSR_GUEST_OS_ID);
> 
>  	wrmsrq(HV_X64_MSR_CRASH_P0, err);
>  	wrmsrq(HV_X64_MSR_CRASH_P1, guest_id);
> @@ -705,7 +705,7 @@ bool hv_is_hyperv_initialized(void)
>  	 * that the hypercall page is setup
>  	 */
>  	hypercall_msr.as_uint64 = 0;
> -	rdmsrq(HV_X64_MSR_HYPERCALL, hypercall_msr.as_uint64);
> +	hypercall_msr.as_uint64 = rdmsrq(HV_X64_MSR_HYPERCALL);
> 
>  	return hypercall_msr.enable;
>  }
> diff --git a/arch/x86/hyperv/hv_spinlock.c b/arch/x86/hyperv/hv_spinlock.c
> index 6b4bdea18218..7ef2d794e2e8 100644
> --- a/arch/x86/hyperv/hv_spinlock.c
> +++ b/arch/x86/hyperv/hv_spinlock.c
> @@ -50,7 +50,7 @@ static void hv_qlock_wait(u8 *byte, u8 val)
>  	if (READ_ONCE(*byte) == val) {
>  		unsigned long msr_val;
> 
> -		rdmsrq(HV_X64_MSR_GUEST_IDLE, msr_val);
> +		msr_val = rdmsrq(HV_X64_MSR_GUEST_IDLE);
> 
>  		(void)msr_val;
>  	}

[snip]

> diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c
> index 185d4f677ec0..65ad235ef5c6 100644
> --- a/arch/x86/kernel/cpu/mshyperv.c
> +++ b/arch/x86/kernel/cpu/mshyperv.c
> @@ -74,7 +74,7 @@ u64 hv_get_non_nested_msr(unsigned int reg)
>  	if (hv_is_synic_msr(reg) && ms_hyperv.paravisor_present)
>  		hv_ivm_msr_read(reg, &value);
>  	else
> -		rdmsrq(reg, value);
> +		value = rdmsrq(reg);
>  	return value;
>  }
>  EXPORT_SYMBOL_GPL(hv_get_non_nested_msr);
> @@ -399,7 +399,7 @@ static unsigned long hv_get_tsc_khz(void)
>  {
>  	unsigned long freq;
> 
> -	rdmsrq(HV_X64_MSR_TSC_FREQUENCY, freq);
> +	freq = rdmsrq(HV_X64_MSR_TSC_FREQUENCY);
> 
>  	return freq / 1000;
>  }
> @@ -645,7 +645,7 @@ static void __init ms_hyperv_init_platform(void)
>  		 */
>  		u64	hv_lapic_frequency;
> 
> -		rdmsrq(HV_X64_MSR_APIC_FREQUENCY, hv_lapic_frequency);
> +		hv_lapic_frequency = rdmsrq(HV_X64_MSR_APIC_FREQUENCY);
>  		hv_lapic_frequency = div_u64(hv_lapic_frequency, HZ);
>  		lapic_timer_period = hv_lapic_frequency;
>  		pr_info("Hyper-V: LAPIC Timer Frequency: %#x\n",

For this Hyper-V specific code,

Reviewed-by: Michael Kelley <mhklinux at outlook.com>




From richard at nod.at  Thu Aug 20 23:31:30 2026
From: richard at nod.at (Richard Weinberger)
Date: Fri, 21 Aug 2026 08:31:30 +0200 (CEST)
Subject: [PATCH 0/6] jffs2: extend write verification to all write paths
In-Reply-To: <2ca192b7-f903-41ba-8ffe-e8bfe9f79125@huawei.com>
References: <20260820105003.2525647-1-zhouminqiang2@huawei.com> <852434244.6574.1787252301788.JavaMail.zimbra@nod.at> <2ca192b7-f903-41ba-8ffe-e8bfe9f79125@huawei.com>
Message-ID: <1421509427.8290.1787293890858.JavaMail.zimbra@nod.at>

----- Urspr?ngliche Mail -----
> Von: "zhouminqiang" <zhouminqiang2 at huawei.com>
> Thank you for your review and feedback.
> 
> Sorry for the confusion caused by my cover letter. We are not using
> JFFS2 on NAND flash devices. Our primary goal is to extend write
> verification to the NOR flash path because we have encountered data
> corruption incidents in production environment and currently lack a
> quick diagnostic tool to isolate the cause. While reviewing the
> code flow, we noticed that the NAND buffered write path also lacked
> verification, so we took the opportunity to extend it as well.
> 
> Although JFFS2 is in "Odd fixes" maintenance mode, there are still
> a significant number of devices in production that rely on JFFS2,

Okay, this makes more sense.

> and we are encountering data corruption issues on live nodes.
> Extending write verification to the NOR flash path would provide a
> diagnostic tool for fault isolation. We notice that WBUF_VERIFY has
> existed since 2007 (a6bc432e296d), so we want to extend the same
> diagnostic facility to cover the write paths it missed, so that it
> can serve ongoing product issue diagnosis. IMHO, this is a
> debugging/maintenance feature, not introducing new functionality.
> 
> We respect your judgment and will leave the final decision to you.

These days many patches are created by an LLM where people change software
for the sake of changing.
But if it solves a real problem you're facing in production things are different,
of course.

Please have a look at Sashiko review, maybe it's useful (maybe not...)
and resend with an updated cover letter.

Thanks,
//richard


From s-k6 at ti.com  Fri Aug 21 04:48:04 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:04 +0530
Subject: [PATCH v8 00/18] spi: cadence-quadspi: add PHY tuning support
Message-ID: <20260821114822.4040338-1-s-k6@ti.com>

This series implements PHY tuning support for the Cadence QSPI controller
to enable reliable high-speed operations. Without PHY tuning, controllers
use conservative timing that limits performance. PHY tuning calibrates
RX/TX delay lines to find optimal data capture timing windows, enabling
operation up to the controller's maximum frequency.

Background:
High-speed SPI memory controllers require precise timing calibration for
reliable operation. At higher frequencies, board-to-board variations make
fixed timing parameters inadequate. The Cadence QSPI controller includes
a PHY interface with programmable delay lines (0-127 taps) for RX and TX
paths, but these require runtime calibration to find the valid timing
window.

Approach:
Add SDR/DDR PHY tuning algorithms for the Cadence controller:

SDR Mode Tuning (1D search):
 - Searches for two consecutive valid RX delay windows
 - Selects the larger window and uses its midpoint for maximum margin
 - TX delay fixed at maximum (127) as it's less critical in SDR

DDR Mode Tuning (2D search):
 - Finds RX boundaries (rxlow/rxhigh) using TX window sweeps
 - Finds TX boundaries (txlow/txhigh) at fixed RX positions
 - Defines valid region corners and detects gaps via binary search
 - Applies temperature compensation for optimal point selection
 - Handles single or dual passing regions with different strategies

Patch description:
Infrastructure (1-5):
 - Patch 1:   Add spi-max-post-config-frequency-hz to describe maximum
              frequency achievable post controller configuration
 - Patch 2:   Add spi-phy-pattern-partition phandle for
              NOR flash PHY tuning pattern location
 - Patch 3:   Parse spi-max-post-config-frequency-hz in spi.c; adds
              spi_device.post_config_max_speed_hz (0 when not set
              keeping all existing DT fully compatible)
 - Patch 4:   Extend spi_mem_adjust_op_freq() with a bypass: if
              op->max_freq equals post_config_max_speed_hz, return
              immediately leaving op->max_freq unchanged. All other
              ops are capped to max_speed_hz. Also require per_op_freq
              controller capability for post-config ops in
              spi_mem_default_supports_op()
 - Patch 5:   Add execute_tuning callback to spi_controller_mem_ops and
              spi_mem_execute_tuning() wrapper in SPI-MEM core;
              serialises access via spi_mem_access_start/end and
              rejects GPIO chip selects

Cadence QSPI Implementation (6-14):
 - Patch 6:   Move cqspi_readdata_capture() earlier (preparatory)
 - Patch 7:   Add DQS bit to cqspi_readdata_capture() (preparatory)
 - Patch 8:   Add complete PHY tuning support: DLL management, pattern
              verification (NOR via spi-phy-pattern-partition phandle,
              NAND via write-to-cache), SDR 1D and DDR 2D search
              algorithms with temperature compensation, AM654-specific
              execute_tuning entry point
 - Patch 9:   Reject 2-byte-address DDR operations via a new
              CQSPI_NO_PHY_TUNING_WITH_ODDR_2B_ADDR quirk flag to work
              around AM654 OSPI erratum i2383
 - Patch 10:  Refactor direct read path for PHY support (preparatory)
 - Patch 11:  Enable PHY for direct reads at the calibrated frequency
              when both transfer boundaries are 16-byte aligned,
              falling back to non-PHY DMA otherwise; also reprograms
              DLL on chip-select switch when two devices have
              different calibrated settings
 - Patch 12:  Enable PHY for indirect writes of at least
              CQSPI_PHY_MIN_INDIRECT_WRITE_LEN bytes
 - Patch 13:  Reprogram per-device CS timing on any chip-select switch
 - Patch 14:  Reprogram the PHY DLL (master config and RX/TX taps) on
              runtime resume, since the DLL loses its lock when the
              OSPI controller is power-gated

MTD core (15-18):
 - Patch 15:  Extract spinand_select_op_variant() into a shared helper
              spinand_op_find_best_variant() with a skip_mask
 - Patch 16:  Negotiate optimal controller operating point before dirmap
              creation; iterate all read variants in performance order
              across ODTR and SSDR interfaces; re-pair cont_read_cache after
              PHY tuning via protocol-shape matching
 - Patch 17:  Extract spi_nor_spimem_get_read_op() helper (preparatory)
 - Patch 18:  Execute controller optimization in spi_nor_probe() before
              creating dirmaps

Testing:
This series was tested on TI's
AM62Ax SK with OSPI NAND flash and
AM62Px SK with OSPI NOR flash:

Read throughput:
|----------------------------------------|
|                | non-PHY   | PHY       |
|----------------------------------------|
| OSPI NOR (8D)  | 37.5 MB/s | 218 MB/s  |
|----------------------------------------|
| OSPI NAND (8S) | 9.2 MB/s  | 35.6 MB/s |
|----------------------------------------|

Write throughput:
|----------------------------------------|
|                | non-PHY   | PHY       |
|----------------------------------------|
| OSPI NAND (8S) | 6 MB/s    | 9.2 MB/s  |
|----------------------------------------|

Test log: https://gist.github.com/santhosh21/6ba537a984acc2f98abfca0576459118
Repo: https://github.com/santhosh21/linux/commits/phy_tuning_v8

Changes in v8:
 - Re-pair cont_read_cache with the selected read_cache variant via
   protocol-shape matching after PHY tuning instead of unconditionally
   diabling continuous read
 - Rebase on v7.2
 - Collect tag from Miquel
 - Link to v7: https://lore.kernel.org/linux-spi/20260811183313.1550425-1-s-k6 at ti.com/

Changes in v7:
 - Serialise spi_mem_execute_tuning() with spi_mem_access_start/end and
   reject GPIO chip-select configurations
 - Fix cqspi_phy_apply_setting() to also write the calibrated
   read_delay to CQSPI_REG_READCAPTURE, previously software-only
 - Use local op copies for pattern write/verify instead of mutating
   the caller's op templates in place
 - Fix the READCAPTURE bypass bit in cqspi_tune_phy() to match the
   value validated during calibration instead of being hardcoded
 - Enable PHY before starting the indirect-write state machine instead
   of after; the old order was a guaranteed deadlock
 - Add a new patch to reprogram the PHY DLL on runtime resume
 - Fix continuous-read and ODTR/SSDR template handling on ranked
   fallback in spinand_try_ranked_variant(), preventing data
   corruption after a fallback
 - Fix other, smaller correctness issues raised by sashiko-bot
 - Rebase on v7.2-rc7
 - Link to v6: https://lore.kernel.org/linux-spi/20260723110219.3180747-1-s-k6 at ti.com/

Changes in v6:
 - Add maxItems to spi-max-post-config-frequency-hz property
 - Rebase on v7.2-rc4
 - Collect tag from Krzysztof
 - Link to v5: https://lore.kernel.org/linux-spi/20260717185116.2065505-1-s-k6 at ti.com/

Changes in v5:
 - Rename spi-max-post-config-frequency to spi-max-post-config-frequency-hz
   per DT schema unit suffix convention
 - Rename CQSPI_NO_2BYTE_ADDR_PHY_DDR quirk to
   CQSPI_NO_PHY_TUNING_WITH_ODDR_2B_ADDR for clarity
 - Rename SPI NAND identifiers to remove PHY-specific terminology:
     spinand_configure_phy()     -> spinand_optimize_controller()
     spinand_try_phy_ranked()    -> spinand_try_ranked_variant()
     spinand_reset_max_ops()     -> spinand_reset_max_freq_ops()
     spinand_op_find_best()      -> spinand_op_find_best_variant()
     phy_read_variants (field)   -> all_read_variants
 - Replace bool odtr parameter with enum spinand_bus_interface iface in
   spinand_op_find_best_variant() to preserve the existing enumeration
 - Replace cqspi_get_phy_pattern_offset() with per-flash probe-time
   phandle lookup in cqspi_of_get_flash_pdata(); fixes incorrect pattern
   offset selection when multiple flash devices share a controller
 - In cqspi_tune_phy(), reprogramme and resync the DLL when the active
   device's calibrated settings differ from hardware
 - Edit cqspi_phy_apply_setting() to update phy_setting.rx and
   phy_setting.tx alongside phy_setting.read_delay
 - Reprogram per-device CS timing (CQSPI_REG_DELAY and read-capture register)
   on any chip-select switch, not only on clock changes
 - Move post_config_max_speed_hz early-return to the top of
   spinand_optimize_controller(), before any state is modified
 - Remove dead NULL guards on SSDR op templates in Pass 2
 - Downgrade tuning-failure log messages from dev_warn to dev_dbg
 - Rebase on v7.2-rc3
 - Collect tags
 - Link to v4: https://lore.kernel.org/linux-spi/20260618073725.84733-1-s-k6 at ti.com/

Changes in v4:
 - Add spi-max-post-config-frequency instead of extending spi-max-frequency
   to accept an optional second value
 - Replace spi_mem_apply_base_freq_cap() with spi_mem_adjust_op_freq() extension
 - For SPI NOR/NAND, execute PHY tuning before the dirmap creation
 - For SPI NAND, execute PHY tuning across all operation variants available,
   perform duration comparison, and select the best resulting variant
   by taking controller-specific restrictions into account
 - Move i2383 check from cqspi_supports_mem_op() to cqspi_am654_ospi_execute_tuning()
 - Rename cdns,phy-pattern-partition to spi-phy-pattern-partition,
   cqspi_phy_enable to cqspi_tune_phy and f_pdata->use_phy to use_tuned_phy
 - Remove redundant spi-max-frequency parsing in driver cqspi_of_get_flash_pdata()
 - Extract DMA refactoring into a preparatory patch
 - Rebase on v7.1
 - Collect tags from Miquel
 - Link to v3: https://lore.kernel.org/linux-spi/20260527175527.2247679-1-s-k6 at ti.com/

Changes in v3:
 - Drop spi-has-dqs DT property; DQS is now enabled automatically when
   the selected read operation uses DDR signalling (dtr flags in the op)
 - Extend spi-max-frequency to accept an optional second value forming a
   [base-freq, max-freq] pair; the presence of two values signals PHY
   tuning intent and encodes both the conservative base speed and the
   calibration target in one property
 - Add base_speed_hz to struct spi_device (spi.c/spi.h) and parse the
   two-element array there; single-value DT is fully backward-compatible
 - Move frequency enforcement from the cadence driver to core: new
   spi_mem_apply_base_freq_cap() called from spi_mem_exec_op() replaces
   the per-driver cqspi_op_matches_tuned() and non_phy_clk_rate field
 - Propagate the tuned max_freq to dirmap op templates after
   execute_tuning() succeeds; store persistent op templates in
   spi_nor.max_read_op and spinand.{max_read,max_write}_op so the
   frequency writeback survives across the probe call
 - Replace NOR pattern partition lookup by name with a
   cdns,phy-pattern-partition DT phandle pointing directly to the
   partition node
 - Add CQSPI_NO_2BYTE_ADDR_PHY_DDR quirk and reject 2-byte-address DDR
   ops in cqspi_supports_mem_op() to work around AM654 erratum i2383
 - Remove RFC tag
 - Rebase on v7.1-rc5
 - Collect tags from Miquel
 - Link to v2: https://lore.kernel.org/linux-spi/20260113141617.1905039-1-s-k6 at ti.com/

Changes in v2:
 - Restructure the .execute_tuning() call from spi-mem clients instead
   of mtdcore with best read_op and write_op (optional) passed
 - Add compatible-specific .execute_tuning() call which can be called by
   spi_mem_execute_tuning() if exists
 - Handle tuning requirement check by controller instead of spi-mem
   clients
 - Add support to write the phy_pattern to cache if relevant write_op
   is passed or get the partition offset which contains the phy_pattern
 - Add tuning algorithm for DDR mode
 - Add support for DQS
 - Restrict PHY frequency to tuned operations
 - Link to v1: https://lore.kernel.org/linux-spi/20250811193219.731851-1-s-k6 at ti.com/

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>

Pratyush Yadav (1):
  mtd: spi-nor: extract read op template construction into helper

Santhosh Kumar K (17):
  spi: dt-bindings: add spi-max-post-config-frequency-hz property
  spi: dt-bindings: add spi-phy-pattern-partition property
  spi: parse spi-max-post-config-frequency-hz into
    post_config_max_speed_hz
  spi: spi-mem: teach spi_mem_adjust_op_freq() about post-config ops
  spi: spi-mem: add execute_tuning callback and spi_mem_execute_tuning()
  spi: cadence-quadspi: move cqspi_readdata_capture earlier
  spi: cadence-quadspi: add DQS support to read data capture
  spi: cadence-quadspi: add PHY tuning support
  spi: cadence-quadspi: skip DDR PHY tuning for 2-byte-address ops
    (i2383)
  spi: cadence-quadspi: refactor direct read path for PHY support
  spi: cadence-quadspi: enable PHY for direct reads
  spi: cadence-quadspi: enable PHY for indirect writes
  spi: cadence-quadspi: reprogram CS timing on every chip-select switch
  spi: cadence-quadspi: reprogram PHY DLL on runtime resume
  mtd: spinand: extract variant ranking logic into
    spinand_op_find_best_variant()
  mtd: spinand: negotiate optimal controller operating point before
    dirmap creation
  mtd: spi-nor: run controller optimization before dirmap creation

 .../bindings/spi/cdns,qspi-nor.yaml           |   17 +
 .../bindings/spi/spi-peripheral-props.yaml    |   13 +
 drivers/mtd/nand/spi/core.c                   |  327 ++-
 drivers/mtd/spi-nor/core.c                    |   80 +-
 drivers/spi/spi-cadence-quadspi.c             | 2280 +++++++++++++++--
 drivers/spi/spi-mem.c                         |   55 +-
 drivers/spi/spi.c                             |    2 +
 include/linux/mtd/spi-nor.h                   |    3 +
 include/linux/mtd/spinand.h                   |   14 +
 include/linux/spi/spi-mem.h                   |   14 +
 include/linux/spi/spi.h                       |    3 +
 11 files changed, 2618 insertions(+), 190 deletions(-)

-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:07 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:07 +0530
Subject: [PATCH v8 03/18] spi: parse spi-max-post-config-frequency-hz into post_config_max_speed_hz
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-4-s-k6@ti.com>

Add post_config_max_speed_hz to struct spi_device and parse it from
the spi-max-post-config-frequency-hz DT property in of_spi_parse_dt().

This supports SPI devices that operate at two distinct clock rates: a
conservative rate always reachable without controller configuration,
and a higher rate achievable only after controller-side configuration
such as PHY tuning. With both properties set, spi-max-frequency gives
the conservative pre-configuration rate and post_config_max_speed_hz
carries the post-configuration target for the SPI-MEM layer.

Zero when not set, preserving existing behaviour.

Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi.c       | 2 ++
 include/linux/spi/spi.h | 3 +++
 2 files changed, 5 insertions(+)

diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 5b5b3bc5f0d8..6b01ddb7cc32 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -2800,6 +2800,8 @@ static int of_spi_parse_dt(struct spi_controller *ctlr, struct spi_device *spi,
 	/* Device speed */
 	if (!of_property_read_u32(nc, "spi-max-frequency", &value))
 		spi->max_speed_hz = value;
+	if (!of_property_read_u32(nc, "spi-max-post-config-frequency-hz", &value))
+		spi->post_config_max_speed_hz = value;
 
 	/* Device CS delays */
 	of_spi_parse_dt_cs_delay(nc, &spi->cs_setup, "spi-cs-setup-delay-ns");
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index 88d17fce02dc..02fd602f29e8 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -141,6 +141,8 @@ extern void spi_transfer_cs_change_delay_exec(struct spi_message *msg,
  * @max_speed_hz: Maximum clock rate to be used with this chip
  *	(on this board); may be changed by the device's driver.
  *	The spi_transfer.speed_hz can override this for each transfer.
+ * @post_config_max_speed_hz: Maximum clock rate achievable after controller
+ *	configuration (e.g. PHY tuning); zero when not assigned.
  * @bits_per_word: Data transfers involve one or more words; word sizes
  *	like eight or 12 bits are common.  In-memory wordsizes are
  *	powers of two bytes (e.g. 20 bit samples use 32 bits).
@@ -195,6 +197,7 @@ struct spi_device {
 	struct device		dev;
 	struct spi_controller	*controller;
 	u32			max_speed_hz;
+	u32			post_config_max_speed_hz;
 	u8			bits_per_word;
 	bool			rt;
 #define SPI_NO_TX		BIT(31)		/* No transmit wire */
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:11 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:11 +0530
Subject: [PATCH v8 07/18] spi: cadence-quadspi: add DQS support to read data capture
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-8-s-k6@ti.com>

Add DQS (Data Strobe) parameter to cqspi_readdata_capture() to control
data capture timing. DQS mode uses a dedicated strobe signal for
improved timing margins in high-speed SPI modes.

Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index f8e266372367..046fa5d69b0f 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -192,6 +192,7 @@ struct cqspi_driver_platdata {
 #define CQSPI_REG_READCAPTURE_BYPASS_LSB	0
 #define CQSPI_REG_READCAPTURE_DELAY_LSB		1
 #define CQSPI_REG_READCAPTURE_DELAY_MASK	0xF
+#define CQSPI_REG_READCAPTURE_DQS_LSB		8
 
 #define CQSPI_REG_SIZE				0x14
 #define CQSPI_REG_SIZE_ADDRESS_LSB		0
@@ -458,7 +459,7 @@ static int cqspi_wait_idle(struct cqspi_st *cqspi)
 }
 
 static void cqspi_readdata_capture(struct cqspi_st *cqspi, const bool bypass,
-				   const unsigned int delay)
+				   const bool dqs, const unsigned int delay)
 {
 	void __iomem *reg_base = cqspi->iobase;
 	unsigned int reg;
@@ -476,6 +477,11 @@ static void cqspi_readdata_capture(struct cqspi_st *cqspi, const bool bypass,
 	reg |= (delay & CQSPI_REG_READCAPTURE_DELAY_MASK)
 	       << CQSPI_REG_READCAPTURE_DELAY_LSB;
 
+	if (dqs)
+		reg |= BIT(CQSPI_REG_READCAPTURE_DQS_LSB);
+	else
+		reg &= ~BIT(CQSPI_REG_READCAPTURE_DQS_LSB);
+
 	writel(reg, reg_base + CQSPI_REG_READCAPTURE);
 }
 
@@ -1317,7 +1323,7 @@ static void cqspi_configure(struct cqspi_flash_pdata *f_pdata,
 		cqspi->sclk = sclk;
 		cqspi_config_baudrate_div(cqspi);
 		cqspi_delay(f_pdata);
-		cqspi_readdata_capture(cqspi, !cqspi->rclk_en,
+		cqspi_readdata_capture(cqspi, !cqspi->rclk_en, false,
 				       f_pdata->read_delay);
 	}
 
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:05 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:05 +0530
Subject: [PATCH v8 01/18] spi: dt-bindings: add spi-max-post-config-frequency-hz property
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-2-s-k6@ti.com>

Add spi-max-post-config-frequency-hz, a generic property for SPI
peripherals that operate at two distinct clock rates: a conservative
rate always reachable without controller configuration, and a higher
rate achievable only after controller-side configuration.

When both properties are present, spi-max-frequency gives the
conservative pre-configuration rate and spi-max-post-config-frequency-hz
gives the higher post-configuration target.

Acked-by: Conor Dooley <conor.dooley at microchip.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 .../devicetree/bindings/spi/spi-peripheral-props.yaml       | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
index 880a9f624566..84976fd3ee1b 100644
--- a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
+++ b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
@@ -45,6 +45,12 @@ properties:
     description:
       Maximum SPI clocking speed of the device in Hz.
 
+  spi-max-post-config-frequency-hz:
+    maxItems: 1
+    description:
+      Maximum SPI clock frequency in Hz achievable after controller-side
+      configuration.
+
   spi-cs-setup-delay-ns:
     description:
       Delay in nanoseconds to be introduced by the controller after CS is
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:06 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:06 +0530
Subject: [PATCH v8 02/18] spi: dt-bindings: add spi-phy-pattern-partition property
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-3-s-k6@ti.com>

Add spi-phy-pattern-partition, a per-device phandle property on the
flash sub-node that allows the DT author to directly reference the
partition holding the SPI PHY tuning pattern. Used to locate the
pattern data during PHY tuning when the device cannot load the
pattern dynamically.

PHY tuning works by reading a known data pattern from the device
repeatedly while sweeping controller delay parameters until the
capture window is stable. For SPI NAND, the driver loads the pattern
into the page cache once using write-to-cache opcodes, then reads it
during the sweep. SPI NOR devices have no equivalent opcode, so the
pattern must be pre-programmed in a dedicated flash partition. One
partition per device is required to keep the procedure unambiguous
when multiple devices share a bus.

Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski at oss.qualcomm.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 .../devicetree/bindings/spi/cdns,qspi-nor.yaml  | 17 +++++++++++++++++
 .../bindings/spi/spi-peripheral-props.yaml      |  7 +++++++
 2 files changed, 24 insertions(+)

diff --git a/Documentation/devicetree/bindings/spi/cdns,qspi-nor.yaml b/Documentation/devicetree/bindings/spi/cdns,qspi-nor.yaml
index 891f578b5ac4..277dedcd4246 100644
--- a/Documentation/devicetree/bindings/spi/cdns,qspi-nor.yaml
+++ b/Documentation/devicetree/bindings/spi/cdns,qspi-nor.yaml
@@ -209,5 +209,22 @@ examples:
             cdns,tsd2d-ns = <60>;
             cdns,tchsh-ns = <60>;
             cdns,tslch-ns = <60>;
+            spi-phy-pattern-partition = <&phy_pattern>;
+
+            partitions {
+                compatible = "fixed-partitions";
+                #address-cells = <1>;
+                #size-cells = <1>;
+
+                partition at 0 {
+                    label = "data";
+                    reg = <0x0 0x3fc0000>;
+                };
+
+                phy_pattern: partition at 3fc0000 {
+                    label = "phy-pattern";
+                    reg = <0x3fc0000 0x40000>;
+                };
+            };
         };
     };
diff --git a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
index 84976fd3ee1b..cc01b643fe9b 100644
--- a/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
+++ b/Documentation/devicetree/bindings/spi/spi-peripheral-props.yaml
@@ -123,6 +123,13 @@ properties:
     description:
       Delay, in microseconds, after a write transfer.
 
+  spi-phy-pattern-partition:
+    $ref: /schemas/types.yaml#/definitions/phandle
+    description:
+      Phandle to the flash partition holding the pre-programmed SPI PHY tuning
+      pattern. Used when the device cannot load the pattern dynamically during
+      PHY tuning.
+
   stacked-memories:
     description: Several SPI memories can be wired in stacked mode.
       This basically means that either a device features several chip
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:09 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:09 +0530
Subject: [PATCH v8 05/18] spi: spi-mem: add execute_tuning callback and spi_mem_execute_tuning()
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-6-s-k6@ti.com>

Add an execute_tuning callback to struct spi_controller_mem_ops. The
callback receives a mandatory read op template and an optional write op
template. On success the controller sets op->max_freq in each provided
template to the validated clock rate. A callback return of 0 with
max_freq left at zero signals that this specific op variant cannot be
PHY-tuned (e.g. a hardware erratum blocks it), allowing the caller to
try a different variant without treating it as a hard error.

Add the corresponding spi_mem_execute_tuning() wrapper. Like other
mem_ops paths it serialises access with spi_mem_access_start/end for
serialisation and runtime PM, and rejects GPIO CS configurations which
cannot support hardware-level tuning.

Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-mem.c       | 43 +++++++++++++++++++++++++++++++++++++
 include/linux/spi/spi-mem.h | 14 ++++++++++++
 2 files changed, 57 insertions(+)

diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index cd4bc4c914e7..79e89de1ce8d 100644
--- a/drivers/spi/spi-mem.c
+++ b/drivers/spi/spi-mem.c
@@ -693,6 +693,49 @@ u64 spi_mem_calc_op_duration(struct spi_mem *mem, struct spi_mem_op *op)
 }
 EXPORT_SYMBOL_GPL(spi_mem_calc_op_duration);
 
+/**
+ * spi_mem_execute_tuning() - Execute controller tuning procedure
+ * @mem: the SPI memory device
+ * @read_op: read operation template (mandatory)
+ * @write_op: write operation template (optional, may be NULL)
+ *
+ * Requests the controller to perform tuning for high-speed operation
+ * using the provided op templates. On success the controller callback
+ * sets @read_op->max_freq (and @write_op->max_freq when non-NULL) to
+ * the validated clock rate.
+ *
+ * Return: 0 on success, -EINVAL if @mem or @read_op is NULL,
+ *         -EOPNOTSUPP if the controller doesn't support tuning,
+ *         or a negative error code on failure.
+ */
+int spi_mem_execute_tuning(struct spi_mem *mem, struct spi_mem_op *read_op,
+			   struct spi_mem_op *write_op)
+{
+	struct spi_controller *ctlr;
+	int ret;
+
+	if (!mem || !read_op)
+		return -EINVAL;
+
+	ctlr = mem->spi->controller;
+	if (!ctlr->mem_ops || !ctlr->mem_ops->execute_tuning)
+		return -EOPNOTSUPP;
+
+	if (spi_get_csgpiod(mem->spi, 0))
+		return -EOPNOTSUPP;
+
+	ret = spi_mem_access_start(mem);
+	if (ret)
+		return ret;
+
+	ret = ctlr->mem_ops->execute_tuning(mem, read_op, write_op);
+
+	spi_mem_access_end(mem);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(spi_mem_execute_tuning);
+
 static ssize_t spi_mem_no_dirmap_read(struct spi_mem_dirmap_desc *desc,
 				      u64 offs, size_t len, void *buf)
 {
diff --git a/include/linux/spi/spi-mem.h b/include/linux/spi/spi-mem.h
index 0cce6b57242a..b69f671c40e7 100644
--- a/include/linux/spi/spi-mem.h
+++ b/include/linux/spi/spi-mem.h
@@ -348,6 +348,15 @@ static inline void *spi_mem_get_drvdata(struct spi_mem *mem)
  * @poll_status: poll memory device status until (status & mask) == match or
  *               when the timeout has expired. It fills the data buffer with
  *               the last status value.
+ * @execute_tuning: run the controller tuning procedure using the provided
+ *		    read and optional write op templates. On success, set
+ *		    @read_op->max_freq (and @write_op->max_freq when non-NULL)
+ *		    to the validated clock rate. Return a negative errno on
+ *		    error. Return -EOPNOTSUPP if the controller has no tuning
+ *		    capability at all. Return 0 with @read_op->max_freq left at
+ *		    zero to signal that this specific op cannot be PHY-tuned
+ *		    (e.g. a hardware erratum blocks it) but another variant may
+ *		    succeed; the caller will iterate remaining op variants.
  *
  * This interface should be implemented by SPI controllers providing an
  * high-level interface to execute SPI memory operation, which is usually the
@@ -378,6 +387,8 @@ struct spi_controller_mem_ops {
 			   unsigned long initial_delay_us,
 			   unsigned long polling_rate_us,
 			   unsigned long timeout_ms);
+	int (*execute_tuning)(struct spi_mem *mem, struct spi_mem_op *read_op,
+			      struct spi_mem_op *write_op);
 };
 
 /**
@@ -469,6 +480,9 @@ int spi_mem_adjust_op_size(struct spi_mem *mem, struct spi_mem_op *op);
 void spi_mem_adjust_op_freq(struct spi_mem *mem, struct spi_mem_op *op);
 u64 spi_mem_calc_op_duration(struct spi_mem *mem, struct spi_mem_op *op);
 
+int spi_mem_execute_tuning(struct spi_mem *mem, struct spi_mem_op *read_op,
+			   struct spi_mem_op *write_op);
+
 bool spi_mem_supports_op(struct spi_mem *mem,
 			 const struct spi_mem_op *op);
 
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:13 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:13 +0530
Subject: [PATCH v8 09/18] spi: cadence-quadspi: skip DDR PHY tuning for 2-byte-address ops (i2383)
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-10-s-k6@ti.com>

Erratum i2383 on AM654 locks the address phase in PHY DDR mode when a
2-byte column address is used. DDR PHY tuning must not be attempted for
such operations; non-PHY DDR usage is unaffected. [0]

Add CQSPI_NO_PHY_TUNING_WITH_ODDR_2B_ADDR quirk and check it in
cqspi_am654_ospi_execute_tuning(). When the erratum applies, return 0
with read_op->max_freq cleared ? the op-skip signal that tells the
caller to try another op variant.

[0] https://www.ti.com/lit/er/sprz544c/sprz544c.pdf

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 43 +++++++++++++++++++++----------
 1 file changed, 29 insertions(+), 14 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 5651320426b1..a1aaba7c1a7e 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -37,19 +37,20 @@
 static_assert(CQSPI_MAX_CHIPSELECT <= SPI_DEVICE_CS_CNT_MAX);
 
 /* Quirks */
-#define CQSPI_NEEDS_WR_DELAY		BIT(0)
-#define CQSPI_DISABLE_DAC_MODE		BIT(1)
-#define CQSPI_SUPPORT_EXTERNAL_DMA	BIT(2)
-#define CQSPI_NO_SUPPORT_WR_COMPLETION	BIT(3)
-#define CQSPI_SLOW_SRAM			BIT(4)
-#define CQSPI_NEEDS_APB_AHB_HAZARD_WAR	BIT(5)
-#define CQSPI_RD_NO_IRQ			BIT(6)
-#define CQSPI_DMA_SET_MASK		BIT(7)
-#define CQSPI_SUPPORT_DEVICE_RESET	BIT(8)
-#define CQSPI_DISABLE_STIG_MODE		BIT(9)
-#define CQSPI_DISABLE_RUNTIME_PM	BIT(10)
-#define CQSPI_NO_INDIRECT_MODE		BIT(11)
-#define CQSPI_HAS_WR_PROTECT		BIT(12)
+#define CQSPI_NEEDS_WR_DELAY			BIT(0)
+#define CQSPI_DISABLE_DAC_MODE			BIT(1)
+#define CQSPI_SUPPORT_EXTERNAL_DMA		BIT(2)
+#define CQSPI_NO_SUPPORT_WR_COMPLETION		BIT(3)
+#define CQSPI_SLOW_SRAM				BIT(4)
+#define CQSPI_NEEDS_APB_AHB_HAZARD_WAR		BIT(5)
+#define CQSPI_RD_NO_IRQ				BIT(6)
+#define CQSPI_DMA_SET_MASK			BIT(7)
+#define CQSPI_SUPPORT_DEVICE_RESET		BIT(8)
+#define CQSPI_DISABLE_STIG_MODE			BIT(9)
+#define CQSPI_DISABLE_RUNTIME_PM		BIT(10)
+#define CQSPI_NO_INDIRECT_MODE			BIT(11)
+#define CQSPI_HAS_WR_PROTECT			BIT(12)
+#define CQSPI_NO_PHY_TUNING_WITH_ODDR_2B_ADDR	BIT(13)
 
 /* Capabilities */
 #define CQSPI_SUPPORTS_OCTAL		BIT(0)
@@ -3211,6 +3212,20 @@ static int cqspi_am654_ospi_execute_tuning(struct spi_mem *mem,
 		return -EOPNOTSUPP;
 	}
 
+	/*
+	 * Erratum i2383: in PHY DDR mode, a 2-byte column address locks up
+	 * the address phase. Skip DDR PHY tuning for such operations.
+	 */
+	if ((cqspi->ddata->quirks & CQSPI_NO_PHY_TUNING_WITH_ODDR_2B_ADDR) &&
+	    read_op->addr.nbytes == 2 &&
+	    (read_op->cmd.dtr || read_op->addr.dtr || read_op->dummy.dtr ||
+	     read_op->data.dtr)) {
+		dev_dbg(dev,
+			"i2383: skipping DDR PHY tuning (2-byte address)\n");
+		read_op->max_freq = 0;
+		return 0;
+	}
+
 	if (write_op) {
 		/*
 		 * For NAND: write the calibration pattern to the page cache.
@@ -3849,7 +3864,7 @@ static const struct cqspi_driver_platdata k2g_qspi = {
 
 static const struct cqspi_driver_platdata am654_ospi = {
 	.hwcaps_mask = CQSPI_SUPPORTS_OCTAL | CQSPI_SUPPORTS_QUAD,
-	.quirks = CQSPI_NEEDS_WR_DELAY,
+	.quirks = CQSPI_NEEDS_WR_DELAY | CQSPI_NO_PHY_TUNING_WITH_ODDR_2B_ADDR,
 	.execute_tuning = cqspi_am654_ospi_execute_tuning,
 };
 
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:10 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:10 +0530
Subject: [PATCH v8 06/18] spi: cadence-quadspi: move cqspi_readdata_capture earlier
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-7-s-k6@ti.com>

Move cqspi_readdata_capture() function earlier in the file. This is
preparatory refactoring for upcoming PHY tuning support that needs to
call it without a forward declaration.

No functional changes.

Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 45 +++++++++++++++----------------
 1 file changed, 22 insertions(+), 23 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index ecb0be394696..f8e266372367 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -457,6 +457,28 @@ static int cqspi_wait_idle(struct cqspi_st *cqspi)
 	}
 }
 
+static void cqspi_readdata_capture(struct cqspi_st *cqspi, const bool bypass,
+				   const unsigned int delay)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+
+	reg = readl(reg_base + CQSPI_REG_READCAPTURE);
+
+	if (bypass)
+		reg |= BIT(CQSPI_REG_READCAPTURE_BYPASS_LSB);
+	else
+		reg &= ~BIT(CQSPI_REG_READCAPTURE_BYPASS_LSB);
+
+	reg &= ~(CQSPI_REG_READCAPTURE_DELAY_MASK
+		 << CQSPI_REG_READCAPTURE_DELAY_LSB);
+
+	reg |= (delay & CQSPI_REG_READCAPTURE_DELAY_MASK)
+	       << CQSPI_REG_READCAPTURE_DELAY_LSB;
+
+	writel(reg, reg_base + CQSPI_REG_READCAPTURE);
+}
+
 static int cqspi_exec_flash_cmd(struct cqspi_st *cqspi, unsigned int reg)
 {
 	void __iomem *reg_base = cqspi->iobase;
@@ -1274,29 +1296,6 @@ static void cqspi_config_baudrate_div(struct cqspi_st *cqspi)
 	writel(reg, reg_base + CQSPI_REG_CONFIG);
 }
 
-static void cqspi_readdata_capture(struct cqspi_st *cqspi,
-				   const bool bypass,
-				   const unsigned int delay)
-{
-	void __iomem *reg_base = cqspi->iobase;
-	unsigned int reg;
-
-	reg = readl(reg_base + CQSPI_REG_READCAPTURE);
-
-	if (bypass)
-		reg |= BIT(CQSPI_REG_READCAPTURE_BYPASS_LSB);
-	else
-		reg &= ~BIT(CQSPI_REG_READCAPTURE_BYPASS_LSB);
-
-	reg &= ~(CQSPI_REG_READCAPTURE_DELAY_MASK
-		 << CQSPI_REG_READCAPTURE_DELAY_LSB);
-
-	reg |= (delay & CQSPI_REG_READCAPTURE_DELAY_MASK)
-		<< CQSPI_REG_READCAPTURE_DELAY_LSB;
-
-	writel(reg, reg_base + CQSPI_REG_READCAPTURE);
-}
-
 static void cqspi_configure(struct cqspi_flash_pdata *f_pdata,
 			    unsigned long sclk)
 {
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:15 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:15 +0530
Subject: [PATCH v8 11/18] spi: cadence-quadspi: enable PHY for direct reads
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-12-s-k6@ti.com>

Add cqspi_tune_phy() to toggle PHY mode around a DMA transfer. On
enable: reprogramme the DLL from the per-CS calibration result when
the hardware values differ (handles alternating PHY reads across two
chip selects with different calibration), configure read-capture for
the tuned delay, assert PHY_EN and PHY_PIPELINE in CONFIG, and
decrement the dummy cycle count by one since the PHY data-capture
pipeline absorbs that latency. The original count is saved in
cqspi->phy_saved_dummy so the disable path restores it exactly,
handling the case where the original count was zero. On disable,
all settings are reversed using the saved count.

Add cqspi_use_tuned_phy() predicate: PHY is activated only when
calibration completed successfully and the op frequency equals the
post-config rate.

For direct reads, PHY is used only when both the start address and
end address are 16-byte aligned. Unaligned transfers fall back to
non-PHY DMA.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 119 +++++++++++++++++++++++++++++-
 1 file changed, 118 insertions(+), 1 deletion(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 99a0ec0e769c..8903b4cf4f6f 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -1074,6 +1074,102 @@ static int cqspi_resync_dll(struct cqspi_st *cqspi)
 	return ret;
 }
 
+static int cqspi_tune_phy(struct cqspi_flash_pdata *f_pdata, bool enable)
+{
+	struct cqspi_st *cqspi = f_pdata->cqspi;
+	void __iomem *reg_base = cqspi->iobase;
+	u32 reg;
+	u8 dummy;
+	int ret;
+
+	ret = cqspi_wait_idle(cqspi);
+	if (ret)
+		return ret;
+
+	if (enable) {
+		/*
+		 * CQSPI_REG_PHY_CONFIG holds the RX/TX delay line tap counts
+		 * and is a single global register with no per-CS equivalent.
+		 * Reprogramme and resync the DLL when the active device's
+		 * calibrated values differ from what is currently in hardware,
+		 * which happens when alternating PHY reads between two chip
+		 * selects with different calibration results.
+		 */
+		reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+		if (((reg >> CQSPI_REG_PHY_CONFIG_RX_DEL_LSB) &
+		     CQSPI_REG_PHY_CONFIG_RX_DEL_MASK) != f_pdata->phy_setting.rx ||
+		    ((reg >> CQSPI_REG_PHY_CONFIG_TX_DEL_LSB) &
+		     CQSPI_REG_PHY_CONFIG_TX_DEL_MASK) != f_pdata->phy_setting.tx) {
+			cqspi_set_dll(reg_base, f_pdata->phy_setting.rx,
+				      f_pdata->phy_setting.tx);
+			ret = cqspi_resync_dll(cqspi);
+			if (ret)
+				return ret;
+		}
+
+		cqspi_readdata_capture(cqspi, !f_pdata->use_dqs, f_pdata->use_dqs,
+				       f_pdata->phy_setting.read_delay);
+
+		reg = readl(reg_base + CQSPI_REG_CONFIG);
+		reg |= CQSPI_REG_CONFIG_PHY_EN | CQSPI_REG_CONFIG_PHY_PIPELINE;
+		writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+		/*
+		 * The PHY data-capture pipeline absorbs one dummy cycle's
+		 * worth of latency; reduce the count to avoid over-compensation.
+		 * Save the original count so the disable path can restore it
+		 * exactly, regardless of whether the original was zero.
+		 */
+		reg = readl(reg_base + CQSPI_REG_RD_INSTR);
+		dummy = FIELD_GET(CQSPI_REG_RD_INSTR_DUMMY_MASK
+					  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+				  reg);
+		cqspi->phy_saved_dummy = dummy;
+		if (dummy)
+			dummy--;
+		reg &= ~(CQSPI_REG_RD_INSTR_DUMMY_MASK
+			 << CQSPI_REG_RD_INSTR_DUMMY_LSB);
+		reg |= FIELD_PREP(CQSPI_REG_RD_INSTR_DUMMY_MASK
+					  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+				  dummy);
+		writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+
+		ret = cqspi_wait_idle(cqspi);
+		if (ret) {
+			reg = readl(reg_base + CQSPI_REG_CONFIG);
+			reg &= ~(CQSPI_REG_CONFIG_PHY_EN |
+				 CQSPI_REG_CONFIG_PHY_PIPELINE);
+			writel(reg, reg_base + CQSPI_REG_CONFIG);
+			reg = readl(reg_base + CQSPI_REG_RD_INSTR);
+			reg &= ~(CQSPI_REG_RD_INSTR_DUMMY_MASK
+				 << CQSPI_REG_RD_INSTR_DUMMY_LSB);
+			reg |= FIELD_PREP(CQSPI_REG_RD_INSTR_DUMMY_MASK
+						  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+					  cqspi->phy_saved_dummy);
+			writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+		}
+		return ret;
+	}
+
+	cqspi_readdata_capture(cqspi, !cqspi->rclk_en, false,
+			       f_pdata->read_delay);
+
+	reg = readl(reg_base + CQSPI_REG_CONFIG);
+	reg &= ~(CQSPI_REG_CONFIG_PHY_EN |
+		 CQSPI_REG_CONFIG_PHY_PIPELINE);
+	writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+	reg = readl(reg_base + CQSPI_REG_RD_INSTR);
+	reg &= ~(CQSPI_REG_RD_INSTR_DUMMY_MASK
+		 << CQSPI_REG_RD_INSTR_DUMMY_LSB);
+	reg |= FIELD_PREP(CQSPI_REG_RD_INSTR_DUMMY_MASK
+				  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+			  cqspi->phy_saved_dummy);
+	writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+
+	return cqspi_wait_idle(cqspi);
+}
+
 static int cqspi_versal_indirect_read_dma(struct cqspi_flash_pdata *f_pdata,
 					  u_char *rxbuf, loff_t from_addr,
 					  size_t n_rx)
@@ -1524,6 +1620,14 @@ static ssize_t cqspi_write(struct cqspi_flash_pdata *f_pdata,
 	return cqspi_indirect_write_execute(f_pdata, to, buf, len);
 }
 
+static bool cqspi_use_tuned_phy(struct cqspi_flash_pdata *f_pdata,
+				const struct spi_mem_op *op,
+				u32 post_config_max_speed_hz)
+{
+	return f_pdata->use_tuned_phy &&
+	       op->max_freq == post_config_max_speed_hz;
+}
+
 static void cqspi_rx_dma_callback(void *param)
 {
 	struct cqspi_st *cqspi = param;
@@ -1629,6 +1733,7 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
 	loff_t from = op->addr.val;
 	size_t len = op->data.nbytes;
 	u_char *buf = op->data.buf.in;
+	int ret;
 
 	if (!cqspi->rx_chan || !virt_addr_valid(buf) ||
 	    len < CQSPI_PHY_MIN_DIRECT_READ_LEN) {
@@ -1636,7 +1741,19 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
 		return 0;
 	}
 
-	return cqspi_direct_read_dma(f_pdata, buf, from, len);
+	if (!cqspi_use_tuned_phy(f_pdata, op, post_config_max_speed_hz))
+		return cqspi_direct_read_dma(f_pdata, buf, from, len);
+
+	if (!IS_ALIGNED(from, 16) || !IS_ALIGNED(from + len, 16))
+		return cqspi_direct_read_dma(f_pdata, buf, from, len);
+
+	ret = cqspi_tune_phy(f_pdata, true);
+	if (ret)
+		return ret;
+	ret = cqspi_direct_read_dma(f_pdata, buf, from, len);
+	cqspi_tune_phy(f_pdata, false);
+
+	return ret;
 }
 
 static ssize_t cqspi_read(struct cqspi_flash_pdata *f_pdata,
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:08 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:08 +0530
Subject: [PATCH v8 04/18] spi: spi-mem: teach spi_mem_adjust_op_freq() about post-config ops
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-5-s-k6@ti.com>

Extend spi_mem_adjust_op_freq() with a bypass for post-config ops:
when op->max_freq equals post_config_max_speed_hz (the value written by
execute_tuning on success), return immediately leaving op->max_freq
unchanged. All other ops are capped to max_speed_hz, the
always-reachable base rate. This places the policy at the single
existing frequency-adjustment point so exec_op(), supports_op(), and
calc_op_duration() all see consistent frequencies.

Also extend spi_mem_default_supports_op() to require per_op_freq
controller capability for post-config ops. A post-config op runs at a
different clock rate than the pre-config base; a controller that cannot
switch frequencies per-op cannot execute it correctly.

Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-mem.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/drivers/spi/spi-mem.c b/drivers/spi/spi-mem.c
index 5f973ebfb8b6..cd4bc4c914e7 100644
--- a/drivers/spi/spi-mem.c
+++ b/drivers/spi/spi-mem.c
@@ -206,7 +206,8 @@ bool spi_mem_default_supports_op(struct spi_mem *mem,
 		return false;
 
 	if (op->max_freq &&
-	    op->max_freq < mem->spi->max_speed_hz) {
+	    (op->max_freq < mem->spi->max_speed_hz ||
+	     op->max_freq == mem->spi->post_config_max_speed_hz)) {
 		if (!spi_mem_controller_is_capable(ctlr, per_op_freq))
 			return false;
 	}
@@ -623,9 +624,18 @@ EXPORT_SYMBOL_GPL(spi_mem_adjust_op_size);
  * Some chips have per-op frequency limitations and must adapt the maximum
  * speed. This function allows SPI mem drivers to set @op->max_freq to the
  * maximum supported value.
+ *
+ * When @mem->spi->post_config_max_speed_hz is set, ops with @op->max_freq
+ * equal to that value are treated as post-configuration ops (e.g. PHY-tuned)
+ * and are allowed to run at the full post-config rate. All other ops are
+ * capped to @mem->spi->max_speed_hz, the always-reachable base rate.
  */
 void spi_mem_adjust_op_freq(struct spi_mem *mem, struct spi_mem_op *op)
 {
+	if (mem->spi->post_config_max_speed_hz &&
+	    op->max_freq == mem->spi->post_config_max_speed_hz)
+		return;
+
 	if (!op->max_freq || op->max_freq > mem->spi->max_speed_hz)
 		op->max_freq = mem->spi->max_speed_hz;
 }
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:16 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:16 +0530
Subject: [PATCH v8 12/18] spi: cadence-quadspi: enable PHY for indirect writes
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-13-s-k6@ti.com>

Enable PHY for indirect writes of at least CQSPI_PHY_MIN_INDIRECT_WRITE_LEN
bytes. PHY is activated only when tuning completed successfully and the
write op was calibrated at the post-config frequency (phy_write_op.max_freq
matches), which restricts PHY writes to NAND devices where a write op
template was provided to execute_tuning. NOR flash writes are unaffected.

The indirect write is cancelled before disabling PHY in the error path
so that cqspi_wait_idle() inside cqspi_tune_phy() does not time out
while the controller is still waiting for AHB data.

Thread post_config_max_speed_hz from cqspi_mem_process() through
cqspi_write() into cqspi_indirect_write_execute() for the frequency check.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 39 +++++++++++++++++++++++++++----
 1 file changed, 34 insertions(+), 5 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 8903b4cf4f6f..940cfb72b946 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -157,6 +157,8 @@ struct cqspi_driver_platdata {
 #define CQSPI_DLL_TIMEOUT_US			300
 /* Minimum transfer length to use DMA for direct reads */
 #define CQSPI_PHY_MIN_DIRECT_READ_LEN		17
+/* Minimum indirect write length to amortize PHY enable/disable overhead */
+#define CQSPI_PHY_MIN_INDIRECT_WRITE_LEN	SZ_1K
 
 /* Runtime_pm autosuspend delay */
 #define CQSPI_AUTOSUSPEND_TIMEOUT		2000
@@ -1362,18 +1364,32 @@ static int cqspi_write_setup(struct cqspi_flash_pdata *f_pdata,
 
 static int cqspi_indirect_write_execute(struct cqspi_flash_pdata *f_pdata,
 					loff_t to_addr, const u8 *txbuf,
-					const size_t n_tx)
+					const size_t n_tx,
+					u32 post_config_max_speed_hz)
 {
 	struct cqspi_st *cqspi = f_pdata->cqspi;
 	struct device *dev = &cqspi->pdev->dev;
 	void __iomem *reg_base = cqspi->iobase;
 	unsigned int remaining = n_tx;
 	unsigned int write_bytes;
+	bool use_tuned_phy_write;
 	int ret;
 
 	if (!refcount_read(&cqspi->refcount))
 		return -ENODEV;
 
+	/* Use PHY only for large writes at the calibrated rate */
+	use_tuned_phy_write = n_tx >= CQSPI_PHY_MIN_INDIRECT_WRITE_LEN &&
+			      f_pdata->use_tuned_phy &&
+			      f_pdata->phy_write_op.max_freq ==
+				      post_config_max_speed_hz;
+
+	if (use_tuned_phy_write) {
+		ret = cqspi_tune_phy(f_pdata, true);
+		if (ret)
+			return ret;
+	}
+
 	writel(to_addr, reg_base + CQSPI_REG_INDIRECTWRSTARTADDR);
 	writel(remaining, reg_base + CQSPI_REG_INDIRECTWRBYTES);
 
@@ -1452,15 +1468,26 @@ static int cqspi_indirect_write_execute(struct cqspi_flash_pdata *f_pdata,
 
 	cqspi_wait_idle(cqspi);
 
+	if (use_tuned_phy_write)
+		cqspi_tune_phy(f_pdata, false);
+
 	return 0;
 
 failwr:
 	/* Disable interrupt. */
 	writel(0, reg_base + CQSPI_REG_IRQMASK);
 
-	/* Cancel the indirect write */
+	/*
+	 * Cancel the indirect write before disabling PHY so that
+	 * cqspi_wait_idle() inside cqspi_tune_phy() does not time out
+	 * while the controller is still waiting for AHB data.
+	 */
 	writel(CQSPI_REG_INDIRECTWR_CANCEL_MASK,
 	       reg_base + CQSPI_REG_INDIRECTWR);
+
+	if (use_tuned_phy_write)
+		cqspi_tune_phy(f_pdata, false);
+
 	return ret;
 }
 
@@ -1590,7 +1617,8 @@ static void cqspi_configure(struct cqspi_flash_pdata *f_pdata,
 }
 
 static ssize_t cqspi_write(struct cqspi_flash_pdata *f_pdata,
-			   const struct spi_mem_op *op)
+			   const struct spi_mem_op *op,
+			   u32 post_config_max_speed_hz)
 {
 	struct cqspi_st *cqspi = f_pdata->cqspi;
 	loff_t to = op->addr.val;
@@ -1617,7 +1645,8 @@ static ssize_t cqspi_write(struct cqspi_flash_pdata *f_pdata,
 		return cqspi_wait_idle(cqspi);
 	}
 
-	return cqspi_indirect_write_execute(f_pdata, to, buf, len);
+	return cqspi_indirect_write_execute(f_pdata, to, buf, len,
+					    post_config_max_speed_hz);
 }
 
 static bool cqspi_use_tuned_phy(struct cqspi_flash_pdata *f_pdata,
@@ -1810,7 +1839,7 @@ static int cqspi_mem_process(struct spi_mem *mem, const struct spi_mem_op *op)
 	if (!op->addr.nbytes || !op->data.buf.out)
 		return cqspi_command_write(f_pdata, op);
 
-	return cqspi_write(f_pdata, op);
+	return cqspi_write(f_pdata, op, mem->spi->post_config_max_speed_hz);
 }
 
 static int cqspi_exec_mem_op(struct spi_mem *mem, const struct spi_mem_op *op)
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:17 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:17 +0530
Subject: [PATCH v8 13/18] spi: cadence-quadspi: reprogram CS timing on every chip-select switch
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-14-s-k6@ti.com>

cqspi_configure() only reprogrammed per-device CS timing (CQSPI_REG_DELAY)
and the non-PHY read-capture register when the clock frequency changed, not
when the chip-select changed. In a multi-device setup where both devices
operate at the same frequency, a CS switch leaves the previous device's
timing parameters in hardware.

Split the update condition: baud rate divisor is updated on clock change
only (it is frequency-derived), but the per-device delay and read-capture
registers are now updated on any CS or clock switch.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 940cfb72b946..d4181b501d8d 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -1602,10 +1602,19 @@ static void cqspi_configure(struct cqspi_flash_pdata *f_pdata,
 		cqspi_chipselect(f_pdata);
 	}
 
-	/* Setup baudrate divisor and delays */
+	/* Update baudrate only on clock change. */
 	if (switch_ck) {
 		cqspi->sclk = sclk;
 		cqspi_config_baudrate_div(cqspi);
+	}
+
+	/*
+	 * Reprogram per-device CS timing and non-PHY read capture on any
+	 * chip-select or clock switch. Without this, two devices at the same
+	 * frequency would each see the other's timing parameters after a CS
+	 * switch, since the lazy clock-only check would never trigger.
+	 */
+	if (switch_cs || switch_ck) {
 		cqspi_delay(f_pdata);
 		if (!cqspi->phy_tuning_active)
 			cqspi_readdata_capture(cqspi, !cqspi->rclk_en, false,
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:18 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:18 +0530
Subject: [PATCH v8 14/18] spi: cadence-quadspi: reprogram PHY DLL on runtime resume
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-15-s-k6@ti.com>

The PHY DLL loses its programmed delay values and lock when the OSPI
controller is power-gated during runtime suspend. On resume,
cqspi_controller_init() reinitialises the controller but does not
touch the PHY DLL registers, leaving the DLL in an unlocked state.
Any subsequent PHY read or write would then fail at the cqspi_resync_dll()
call inside cqspi_tune_phy().

After the controller re-initialisation, iterate over all chip selects
and reprogram the DLL with each CS's stored calibrated RX/TX tap counts,
then resync to relock it. One invocation per calibrated CS allows each
calibration to be independently validated after resume. DLL resync
failure is non-fatal: a warning is emitted and resume continues, so the
system degrades to non-PHY speed rather than blocking the resume path.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index d4181b501d8d..c78d4ce1c930 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -4017,7 +4017,7 @@ static int cqspi_runtime_suspend(struct device *dev)
 static int cqspi_runtime_resume(struct device *dev)
 {
 	struct cqspi_st *cqspi = dev_get_drvdata(dev);
-	int ret;
+	int cs, ret;
 
 	ret = clk_bulk_prepare_enable(CLK_QSPI_NUM, cqspi->clks);
 	if (ret)
@@ -4030,6 +4030,23 @@ static int cqspi_runtime_resume(struct device *dev)
 
 	cqspi->current_cs = -1;
 	cqspi->sclk = 0;
+
+	for (cs = 0; cs < CQSPI_MAX_CHIPSELECT; cs++) {
+		struct cqspi_flash_pdata *f_pdata = &cqspi->f_pdata[cs];
+
+		if (!f_pdata->use_tuned_phy)
+			continue;
+
+		cqspi_phy_set_dll_master(cqspi);
+
+		cqspi_set_dll(cqspi->iobase, f_pdata->phy_setting.rx,
+			      f_pdata->phy_setting.tx);
+		ret = cqspi_resync_dll(cqspi);
+		if (ret)
+			dev_warn(dev, "cs%d: PHY DLL resync failed on resume: %d\n",
+				 cs, ret);
+	}
+
 	return 0;
 }
 
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:12 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:12 +0530
Subject: [PATCH v8 08/18] spi: cadence-quadspi: add PHY tuning support
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-9-s-k6@ti.com>

The Cadence QSPI controller supports a delay-line PHY for high-speed
operation. Without calibration the PHY is unused and read capture relies
on a fixed delay, limiting throughput at frequencies above the base
operating speed.

Add an execute_tuning callback that performs delay-line calibration using
a known data pattern written to a dedicated flash region. The pattern is
either read from a NOR partition identified by the DT property
spi-phy-pattern-partition, or written to the NAND page cache before
each calibration read.

For DDR protocols (8D-8D-8D) a 2D sweep of (rx_delay, tx_delay) pairs
is performed to find the widest passing region in the combined RX/TX
space. Binary search locates the gap boundary between passing regions
when two separate windows exist; the final operating point is placed at
the centre of the larger region with a small temperature-dependent
offset.

For SDR protocols a 1D sweep of the RX delay is sufficient. Two windows
at adjacent read_delay values are measured; the wider one's midpoint is
selected.

The tuning infrastructure is platform-specific: only am654-based OSPI
controllers populate the execute_tuning hook. All other platform data
entries return -EOPNOTSUPP and are unaffected.

The calibration target is sourced from spi->post_config_max_speed_hz,
populated by the SPI core from the spi-max-post-config-frequency-hz DT
property. Tuning is skipped when post_config_max_speed_hz is zero.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 1769 ++++++++++++++++++++++++++++-
 1 file changed, 1756 insertions(+), 13 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index 046fa5d69b0f..5651320426b1 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -6,6 +6,7 @@
 // Copyright Intel Corporation (C) 2019-2020. All rights reserved.
 // Copyright (C) 2020 Texas Instruments Incorporated - http://www.ti.com
 
+#include <linux/bitfield.h>
 #include <linux/clk.h>
 #include <linux/completion.h>
 #include <linux/delay.h>
@@ -65,15 +66,27 @@ enum {
 
 struct cqspi_st;
 
+struct phy_setting {
+	u8			rx;
+	u8			tx;
+	u8			read_delay;
+};
+
 struct cqspi_flash_pdata {
-	struct cqspi_st	*cqspi;
-	u32		clk_rate;
-	u32		read_delay;
-	u32		tshsl_ns;
-	u32		tsd2d_ns;
-	u32		tchsh_ns;
-	u32		tslch_ns;
-	u8		cs;
+	struct cqspi_st		*cqspi;
+	u32			read_delay;
+	u32			tshsl_ns;
+	u32			tsd2d_ns;
+	u32			tchsh_ns;
+	u32			tslch_ns;
+	bool			use_dqs;
+	bool			use_tuned_phy;
+	u8			cs;
+	struct phy_setting	phy_setting;
+	struct spi_mem_op	phy_read_op;
+	struct spi_mem_op	phy_write_op;
+	u32			phy_pattern_partition_offset;
+	bool			phy_pattern_partition_valid;
 };
 
 static const struct clk_bulk_data cqspi_clks[CLK_QSPI_NUM] = {
@@ -117,6 +130,8 @@ struct cqspi_st {
 	bool			is_jh7110; /* Flag for StarFive JH7110 SoC */
 	bool			is_rzn1; /* Flag for Renesas RZ/N1 SoC */
 	bool			disable_stig_mode;
+	bool			phy_tuning_active;
+	u8			phy_saved_dummy;
 	refcount_t		refcount;
 	refcount_t		inflight_ops;
 
@@ -129,12 +144,15 @@ struct cqspi_driver_platdata {
 	int (*indirect_read_dma)(struct cqspi_flash_pdata *f_pdata,
 				 u_char *rxbuf, loff_t from_addr, size_t n_rx);
 	u32 (*get_dma_status)(struct cqspi_st *cqspi);
+	int (*execute_tuning)(struct spi_mem *mem, struct spi_mem_op *read_op,
+			      struct spi_mem_op *write_op);
 };
 
 /* Operation timeout value */
 #define CQSPI_TIMEOUT_MS			500
 #define CQSPI_READ_TIMEOUT_MS			10
 #define CQSPI_BUSYWAIT_TIMEOUT_US		500
+#define CQSPI_DLL_TIMEOUT_US			300
 
 /* Runtime_pm autosuspend delay */
 #define CQSPI_AUTOSUSPEND_TIMEOUT		2000
@@ -148,12 +166,14 @@ struct cqspi_driver_platdata {
 /* Register map */
 #define CQSPI_REG_CONFIG			0x00
 #define CQSPI_REG_CONFIG_ENABLE_MASK		BIT(0)
+#define CQSPI_REG_CONFIG_PHY_EN			BIT(3)
 #define CQSPI_REG_CONFIG_ENB_DIR_ACC_CTRL	BIT(7)
 #define CQSPI_REG_CONFIG_DECODE_MASK		BIT(9)
 #define CQSPI_REG_CONFIG_CHIPSELECT_LSB		10
 #define CQSPI_REG_CONFIG_DMA_MASK		BIT(15)
 #define CQSPI_REG_CONFIG_BAUD_LSB		19
 #define CQSPI_REG_CONFIG_DTR_PROTO		BIT(24)
+#define CQSPI_REG_CONFIG_PHY_PIPELINE		BIT(25)
 #define CQSPI_REG_CONFIG_DUAL_OPCODE		BIT(30)
 #define CQSPI_REG_CONFIG_IDLE_LSB		31
 #define CQSPI_REG_CONFIG_CHIPSELECT_MASK	0xF
@@ -192,6 +212,7 @@ struct cqspi_driver_platdata {
 #define CQSPI_REG_READCAPTURE_BYPASS_LSB	0
 #define CQSPI_REG_READCAPTURE_DELAY_LSB		1
 #define CQSPI_REG_READCAPTURE_DELAY_MASK	0xF
+#define CQSPI_REG_READCAPTURE_EDGE_LSB		5
 #define CQSPI_REG_READCAPTURE_DQS_LSB		8
 
 #define CQSPI_REG_SIZE				0x14
@@ -273,6 +294,28 @@ struct cqspi_driver_platdata {
 #define CQSPI_REG_POLLING_STATUS		0xB0
 #define CQSPI_REG_POLLING_STATUS_DUMMY_LSB	16
 
+#define CQSPI_REG_PHY_CONFIG			0xB4
+#define CQSPI_REG_PHY_CONFIG_RX_DEL_LSB		0
+#define CQSPI_REG_PHY_CONFIG_RX_DEL_MASK	0x7F
+#define CQSPI_REG_PHY_CONFIG_TX_DEL_LSB		16
+#define CQSPI_REG_PHY_CONFIG_TX_DEL_MASK	0x7F
+#define CQSPI_REG_PHY_CONFIG_DLL_RESET		BIT(30)
+#define CQSPI_REG_PHY_CONFIG_RESYNC		BIT(31)
+
+#define CQSPI_REG_PHY_DLL_MASTER		0xB8
+#define CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_LSB	0
+#define CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_MASK	0x7f
+#define CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_VAL	16
+#define CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LEN	0x7
+#define CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LSB	20
+#define CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_3	0x2
+#define CQSPI_REG_PHY_DLL_MASTER_BYPASS		BIT(23)
+#define CQSPI_REG_PHY_DLL_MASTER_CYCLE		BIT(24)
+
+#define CQSPI_REG_DLL_OBS_LOW			0xBC
+#define CQSPI_REG_DLL_OBS_LOW_DLL_LOCK		BIT(0)
+#define CQSPI_REG_DLL_OBS_LOW_LOOPBACK_LOCK	BIT(15)
+
 #define CQSPI_REG_OP_EXT_LOWER			0xE0
 #define CQSPI_REG_OP_EXT_READ_LSB		24
 #define CQSPI_REG_OP_EXT_WRITE_LSB		16
@@ -321,6 +364,50 @@ struct cqspi_driver_platdata {
 
 #define CQSPI_REG_VERSAL_DMA_VAL		0x602
 
+#define CQSPI_PHY_INIT_RD			1
+#define CQSPI_PHY_MAX_RD			4
+#define CQSPI_PHY_MAX_DELAY			127
+#define CQSPI_PHY_DDR_SEARCH_STEP		4
+#define CQSPI_PHY_TX_LOOKUP_LOW_START		28
+#define CQSPI_PHY_TX_LOOKUP_LOW_END		48
+#define CQSPI_PHY_TX_LOOKUP_HIGH_START		60
+#define CQSPI_PHY_TX_LOOKUP_HIGH_END		96
+#define CQSPI_PHY_RX_LOW_SEARCH_START		0
+#define CQSPI_PHY_RX_LOW_SEARCH_END		40
+#define CQSPI_PHY_RX_HIGH_SEARCH_START		24
+#define CQSPI_PHY_RX_HIGH_SEARCH_END		127
+#define CQSPI_PHY_TX_LOW_SEARCH_START		0
+#define CQSPI_PHY_TX_LOW_SEARCH_END		64
+#define CQSPI_PHY_TX_HIGH_SEARCH_START		78
+#define CQSPI_PHY_TX_HIGH_SEARCH_END		127
+#define CQSPI_PHY_SEARCH_OFFSET			8
+
+#define CQSPI_PHY_DEFAULT_TEMP			45
+#define CQSPI_PHY_MIN_TEMP			-45
+#define CQSPI_PHY_MAX_TEMP			130
+#define CQSPI_PHY_MID_TEMP			(CQSPI_PHY_MIN_TEMP +	\
+						 ((CQSPI_PHY_MAX_TEMP -	\
+						   CQSPI_PHY_MIN_TEMP) / 2))
+
+/*
+ * PHY tuning pattern for calibrating read data capture delay. This 128-byte
+ * pattern provides sufficient bit transitions across all byte lanes to
+ * reliably detect timing windows at high frequencies.
+ */
+static const u8 phy_tuning_pattern[] __aligned(64) = {
+	0xFE, 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0xFE, 0xFE, 0x01,
+	0x01, 0x01, 0x01, 0x00, 0x00, 0xFE, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
+	0xFF, 0x00, 0x00, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFE,
+	0xFE, 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFE, 0x00, 0xFE, 0xFE, 0x01,
+	0x01, 0x01, 0x01, 0xFE, 0x00, 0xFE, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
+	0xFF, 0xFE, 0x00, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFE,
+	0xFE, 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0xFE, 0xFE, 0xFE, 0x01,
+	0x01, 0x01, 0x01, 0x00, 0xFE, 0xFE, 0xFE, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
+	0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFE, 0xFE,
+	0xFE, 0xFF, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFE, 0xFE, 0xFE, 0xFE, 0x01,
+	0x01, 0x01, 0x01, 0xFE, 0xFE, 0xFE, 0xFE, 0x01,
+};
+
 static int cqspi_wait_for_bit(const struct cqspi_driver_platdata *ddata,
 			      void __iomem *reg, const u32 mask, bool clr,
 			      bool busywait)
@@ -913,6 +1000,76 @@ static void cqspi_controller_enable(struct cqspi_st *cqspi, bool enable)
 	writel(reg, reg_base + CQSPI_REG_CONFIG);
 }
 
+static void cqspi_set_dll(void __iomem *reg_base, u8 rx_dll, u8 tx_dll)
+{
+	unsigned int reg;
+
+	reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+	reg &= ~((CQSPI_REG_PHY_CONFIG_RX_DEL_MASK
+		  << CQSPI_REG_PHY_CONFIG_RX_DEL_LSB) |
+		 (CQSPI_REG_PHY_CONFIG_TX_DEL_MASK
+		  << CQSPI_REG_PHY_CONFIG_TX_DEL_LSB));
+	reg |= ((rx_dll & CQSPI_REG_PHY_CONFIG_RX_DEL_MASK)
+		<< CQSPI_REG_PHY_CONFIG_RX_DEL_LSB) |
+	       ((tx_dll & CQSPI_REG_PHY_CONFIG_TX_DEL_MASK)
+		<< CQSPI_REG_PHY_CONFIG_TX_DEL_LSB) |
+	       CQSPI_REG_PHY_CONFIG_RESYNC;
+	writel(reg, reg_base + CQSPI_REG_PHY_CONFIG);
+}
+
+static int cqspi_resync_dll(struct cqspi_st *cqspi)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+	int ret;
+
+	ret = cqspi_wait_idle(cqspi);
+	if (ret)
+		return ret;
+
+	reg = readl(reg_base + CQSPI_REG_CONFIG);
+	reg &= ~CQSPI_REG_CONFIG_ENABLE_MASK;
+	writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+	reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+	reg &= ~(CQSPI_REG_PHY_CONFIG_DLL_RESET | CQSPI_REG_PHY_CONFIG_RESYNC);
+	writel(reg, reg_base + CQSPI_REG_PHY_CONFIG);
+
+	reg = readl(reg_base + CQSPI_REG_PHY_DLL_MASTER);
+	reg &= ~(CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_MASK
+		 << CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_LSB);
+	reg |= (CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_VAL
+		<< CQSPI_REG_PHY_DLL_MASTER_INIT_DELAY_LSB);
+	writel(reg, reg_base + CQSPI_REG_PHY_DLL_MASTER);
+
+	reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+	reg |= CQSPI_REG_PHY_CONFIG_DLL_RESET;
+	writel(reg, reg_base + CQSPI_REG_PHY_CONFIG);
+
+	ret = readl_poll_timeout(reg_base + CQSPI_REG_DLL_OBS_LOW, reg,
+				 (reg & CQSPI_REG_DLL_OBS_LOW_DLL_LOCK), 0,
+				 CQSPI_DLL_TIMEOUT_US);
+	if (ret)
+		goto re_enable;
+
+	ret = readl_poll_timeout(reg_base + CQSPI_REG_DLL_OBS_LOW, reg,
+				 (reg & CQSPI_REG_DLL_OBS_LOW_LOOPBACK_LOCK), 0,
+				 CQSPI_DLL_TIMEOUT_US);
+	if (ret)
+		goto re_enable;
+
+	reg = readl(reg_base + CQSPI_REG_PHY_CONFIG);
+	reg |= CQSPI_REG_PHY_CONFIG_RESYNC;
+	writel(reg, reg_base + CQSPI_REG_PHY_CONFIG);
+
+re_enable:
+	reg = readl(reg_base + CQSPI_REG_CONFIG);
+	reg |= CQSPI_REG_CONFIG_ENABLE_MASK;
+	writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+	return ret;
+}
+
 static int cqspi_versal_indirect_read_dma(struct cqspi_flash_pdata *f_pdata,
 					  u_char *rxbuf, loff_t from_addr,
 					  size_t n_rx)
@@ -1323,8 +1480,9 @@ static void cqspi_configure(struct cqspi_flash_pdata *f_pdata,
 		cqspi->sclk = sclk;
 		cqspi_config_baudrate_div(cqspi);
 		cqspi_delay(f_pdata);
-		cqspi_readdata_capture(cqspi, !cqspi->rclk_en, false,
-				       f_pdata->read_delay);
+		if (!cqspi->phy_tuning_active)
+			cqspi_readdata_capture(cqspi, !cqspi->rclk_en, false,
+					       f_pdata->read_delay);
 	}
 
 	if (switch_cs || switch_ck)
@@ -1559,10 +1717,1590 @@ static bool cqspi_supports_mem_op(struct spi_mem *mem,
 	return spi_mem_default_supports_op(mem, op);
 }
 
+static int cqspi_write_pattern_to_cache(struct cqspi_flash_pdata *f_pdata,
+					struct spi_mem *mem,
+					const struct spi_mem_op *write_op)
+{
+	struct spi_controller *ctlr = mem->spi->controller;
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	struct spi_mem_op op = *write_op;
+	int ret;
+
+	op.max_freq = mem->spi->max_speed_hz;
+	op.data.nbytes = sizeof(phy_tuning_pattern);
+	op.data.buf.out = phy_tuning_pattern;
+
+	ret = ctlr->mem_ops->exec_op(mem, &op);
+	if (ret) {
+		dev_err(dev, "Failed to write PHY pattern to cache: %d\n", ret);
+		return ret;
+	}
+	dev_dbg(dev, "PHY pattern (%zu bytes) written to cache\n",
+		sizeof(phy_tuning_pattern));
+
+	return 0;
+}
+
+static int cqspi_phy_check_pattern(struct cqspi_flash_pdata *f_pdata,
+				   struct spi_mem *mem)
+{
+	struct spi_controller *ctlr = mem->spi->controller;
+	struct spi_mem_op op;
+	u8 *read_data;
+	int ret;
+
+	read_data = kmalloc_array(ARRAY_SIZE(phy_tuning_pattern),
+				  sizeof(phy_tuning_pattern[0]), GFP_KERNEL);
+	if (!read_data)
+		return -ENOMEM;
+
+	op = f_pdata->phy_read_op;
+	op.data.buf.in = read_data;
+	op.data.nbytes = sizeof(phy_tuning_pattern);
+
+	ret = ctlr->mem_ops->exec_op(mem, &op);
+	if (ret)
+		goto out;
+
+	if (memcmp(read_data, phy_tuning_pattern, sizeof(phy_tuning_pattern)))
+		ret = -EAGAIN;
+
+out:
+	kfree(read_data);
+	return ret;
+}
+
+static void cqspi_phy_set_dll_master(struct cqspi_st *cqspi)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+
+	reg = readl(reg_base + CQSPI_REG_PHY_DLL_MASTER);
+	reg &= ~((CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LEN
+		  << CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LSB) |
+		 CQSPI_REG_PHY_DLL_MASTER_BYPASS |
+		 CQSPI_REG_PHY_DLL_MASTER_CYCLE);
+	reg |= ((CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_3
+		 << CQSPI_REG_PHY_DLL_MASTER_DLY_ELMTS_LSB) |
+		CQSPI_REG_PHY_DLL_MASTER_CYCLE);
+
+	writel(reg, reg_base + CQSPI_REG_PHY_DLL_MASTER);
+}
+
+static void cqspi_phy_pre_config(struct cqspi_st *cqspi,
+				 struct cqspi_flash_pdata *f_pdata,
+				 const bool bypass)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+	u8 dummy;
+
+	cqspi->phy_tuning_active = true;
+
+	cqspi_readdata_capture(cqspi, bypass, f_pdata->use_dqs,
+			       f_pdata->phy_setting.read_delay);
+
+	reg = readl(reg_base + CQSPI_REG_CONFIG);
+	reg &= ~(CQSPI_REG_CONFIG_PHY_EN | CQSPI_REG_CONFIG_PHY_PIPELINE);
+	reg |= CQSPI_REG_CONFIG_PHY_EN;
+	writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+	reg = readl(reg_base + CQSPI_REG_RD_INSTR);
+	dummy = FIELD_GET(CQSPI_REG_RD_INSTR_DUMMY_MASK
+				  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+			  reg);
+	cqspi->phy_saved_dummy = dummy;
+	if (dummy)
+		dummy--;
+	reg &= ~(CQSPI_REG_RD_INSTR_DUMMY_MASK << CQSPI_REG_RD_INSTR_DUMMY_LSB);
+	reg |= FIELD_PREP(CQSPI_REG_RD_INSTR_DUMMY_MASK
+				  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+			  dummy);
+	writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+
+	cqspi_phy_set_dll_master(cqspi);
+}
+
+static void cqspi_phy_post_config(struct cqspi_st *cqspi,
+				  const unsigned int delay)
+{
+	void __iomem *reg_base = cqspi->iobase;
+	unsigned int reg;
+
+	cqspi->phy_tuning_active = false;
+
+	/*
+	 * Clear the EDGE bit set by cqspi_phy_apply_setting, then restore the
+	 * READCAPTURE register to its non-PHY state (bypass, DQS, and delay).
+	 */
+	reg = readl(reg_base + CQSPI_REG_READCAPTURE);
+	reg &= ~BIT(CQSPI_REG_READCAPTURE_EDGE_LSB);
+	writel(reg, reg_base + CQSPI_REG_READCAPTURE);
+
+	cqspi_readdata_capture(cqspi, !cqspi->rclk_en, false, delay);
+
+	reg = readl(reg_base + CQSPI_REG_CONFIG);
+	reg &= ~(CQSPI_REG_CONFIG_PHY_EN | CQSPI_REG_CONFIG_PHY_PIPELINE);
+	writel(reg, reg_base + CQSPI_REG_CONFIG);
+
+	/* Restore the original dummy cycle count saved by cqspi_phy_pre_config. */
+	reg = readl(reg_base + CQSPI_REG_RD_INSTR);
+	reg &= ~(CQSPI_REG_RD_INSTR_DUMMY_MASK << CQSPI_REG_RD_INSTR_DUMMY_LSB);
+	reg |= FIELD_PREP(CQSPI_REG_RD_INSTR_DUMMY_MASK
+				  << CQSPI_REG_RD_INSTR_DUMMY_LSB,
+			  cqspi->phy_saved_dummy);
+	writel(reg, reg_base + CQSPI_REG_RD_INSTR);
+}
+
+static int cqspi_phy_apply_setting(struct cqspi_flash_pdata *f_pdata,
+				   struct phy_setting *phy)
+{
+	struct cqspi_st *cqspi = f_pdata->cqspi;
+	unsigned int reg;
+	int ret;
+
+	reg = readl(cqspi->iobase + CQSPI_REG_READCAPTURE);
+	reg |= BIT(CQSPI_REG_READCAPTURE_EDGE_LSB);
+	writel(reg, cqspi->iobase + CQSPI_REG_READCAPTURE);
+
+	cqspi_set_dll(cqspi->iobase, phy->rx, phy->tx);
+
+	ret = cqspi_resync_dll(cqspi);
+	if (ret)
+		return ret;
+
+	f_pdata->phy_setting.rx = phy->rx;
+	f_pdata->phy_setting.tx = phy->tx;
+	f_pdata->phy_setting.read_delay = phy->read_delay;
+
+	cqspi_readdata_capture(cqspi, !f_pdata->use_dqs, f_pdata->use_dqs,
+			       phy->read_delay);
+
+	return 0;
+}
+
+static int cqspi_find_rx_low_ddr(struct cqspi_flash_pdata *f_pdata,
+				 struct spi_mem *mem, struct phy_setting *phy)
+{
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	int ret;
+
+	do {
+		phy->rx = CQSPI_PHY_RX_LOW_SEARCH_START;
+		do {
+			ret = cqspi_phy_apply_setting(f_pdata, phy);
+			if (!ret) {
+				ret = cqspi_phy_check_pattern(f_pdata, mem);
+				if (!ret)
+					return 0;
+			}
+
+			phy->rx += CQSPI_PHY_DDR_SEARCH_STEP;
+		} while (phy->rx <= CQSPI_PHY_RX_LOW_SEARCH_END);
+
+		phy->read_delay++;
+	} while (phy->read_delay <= CQSPI_PHY_MAX_RD);
+
+	dev_dbg(dev, "Unable to find RX low\n");
+	return -ENOENT;
+}
+
+static int cqspi_find_rx_low_sdr(struct cqspi_flash_pdata *f_pdata,
+				 struct spi_mem *mem, struct phy_setting *phy)
+{
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	int ret;
+
+	phy->rx = 0;
+	do {
+		ret = cqspi_phy_apply_setting(f_pdata, phy);
+		if (!ret) {
+			ret = cqspi_phy_check_pattern(f_pdata, mem);
+			if (!ret)
+				return 0;
+		}
+		phy->rx++;
+	} while (phy->rx <= CQSPI_PHY_MAX_DELAY);
+
+	dev_dbg(dev, "Unable to find RX low\n");
+	return -ENOENT;
+}
+
+static int cqspi_find_rx_high_ddr(struct cqspi_flash_pdata *f_pdata,
+				  struct spi_mem *mem, struct phy_setting *phy)
+{
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	int ret;
+
+	do {
+		phy->rx = CQSPI_PHY_RX_HIGH_SEARCH_END;
+		do {
+			ret = cqspi_phy_apply_setting(f_pdata, phy);
+			if (!ret) {
+				ret = cqspi_phy_check_pattern(f_pdata, mem);
+				if (!ret)
+					return 0;
+			}
+
+			phy->rx -= CQSPI_PHY_DDR_SEARCH_STEP;
+		} while (phy->rx >= CQSPI_PHY_RX_HIGH_SEARCH_START);
+
+		phy->read_delay--;
+	} while (phy->read_delay >= CQSPI_PHY_INIT_RD);
+
+	dev_dbg(dev, "Unable to find RX high\n");
+	return -ENOENT;
+}
+
+static int cqspi_find_rx_high_sdr(struct cqspi_flash_pdata *f_pdata,
+				  struct spi_mem *mem, struct phy_setting *phy,
+				  u8 lowerbound)
+{
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	int ret;
+
+	phy->rx = CQSPI_PHY_MAX_DELAY;
+	do {
+		ret = cqspi_phy_apply_setting(f_pdata, phy);
+		if (!ret) {
+			ret = cqspi_phy_check_pattern(f_pdata, mem);
+			if (!ret)
+				return 0;
+		}
+		phy->rx--;
+	} while (phy->rx > lowerbound);
+
+	dev_dbg(dev, "Unable to find RX high\n");
+	return -ENOENT;
+}
+
+static int cqspi_find_tx_low_ddr(struct cqspi_flash_pdata *f_pdata,
+				 struct spi_mem *mem, struct phy_setting *phy)
+{
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	int ret;
+
+	do {
+		phy->tx = CQSPI_PHY_TX_LOW_SEARCH_START;
+		do {
+			ret = cqspi_phy_apply_setting(f_pdata, phy);
+			if (!ret) {
+				ret = cqspi_phy_check_pattern(f_pdata, mem);
+				if (!ret)
+					return 0;
+			}
+
+			phy->tx += CQSPI_PHY_DDR_SEARCH_STEP;
+		} while (phy->tx <= CQSPI_PHY_TX_LOW_SEARCH_END);
+
+		phy->read_delay++;
+	} while (phy->read_delay <= CQSPI_PHY_MAX_RD);
+
+	dev_dbg(dev, "Unable to find TX low\n");
+	return -ENOENT;
+}
+
+static int cqspi_find_tx_high_ddr(struct cqspi_flash_pdata *f_pdata,
+				  struct spi_mem *mem, struct phy_setting *phy)
+{
+	struct device *dev = &f_pdata->cqspi->pdev->dev;
+	int ret;
+
+	do {
+		phy->tx = CQSPI_PHY_TX_HIGH_SEARCH_END;
+		do {
+			ret = cqspi_phy_apply_setting(f_pdata, phy);
+			if (!ret) {
+				ret = cqspi_phy_check_pattern(f_pdata, mem);
+				if (!ret)
+					return 0;
+			}
+
+			phy->tx -= CQSPI_PHY_DDR_SEARCH_STEP;
+		} while (phy->tx >= CQSPI_PHY_TX_HIGH_SEARCH_START);
+
+		phy->read_delay--;
+	} while (phy->read_delay >= CQSPI_PHY_INIT_RD);
+
+	dev_dbg(dev, "Unable to find TX high\n");
+	return -ENOENT;
+}
+
+static void cqspi_phy_find_gaplow_ddr(struct cqspi_flash_pdata *f_pdata,
+				      struct spi_mem *mem,
+				      struct phy_setting *bottomleft,
+				      struct phy_setting *topright,
+				      struct phy_setting *gaplow)
+{
+	struct phy_setting left, right, mid;
+	int ret;
+
+	left = *bottomleft;
+	right = *topright;
+
+	mid.tx = left.tx + ((right.tx - left.tx) / 2);
+	mid.rx = left.rx + ((right.rx - left.rx) / 2);
+	mid.read_delay = left.read_delay;
+
+	do {
+		ret = cqspi_phy_apply_setting(f_pdata, &mid);
+		if (!ret)
+			ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+		if (ret) {
+			/* The pattern was not found. Go to the lower half. */
+			right.tx = mid.tx;
+			right.rx = mid.rx;
+
+			mid.tx = left.tx + ((mid.tx - left.tx) / 2);
+			mid.rx = left.rx + ((mid.rx - left.rx) / 2);
+		} else {
+			/* The pattern was found. Go to the upper half. */
+			left.tx = mid.tx;
+			left.rx = mid.rx;
+
+			mid.tx = mid.tx + ((right.tx - mid.tx) / 2);
+			mid.rx = mid.rx + ((right.rx - mid.rx) / 2);
+		}
+
+		/* Break the loop if the window has closed. */
+	} while ((right.tx - left.tx >= 2) && (right.rx - left.rx >= 2));
+
+	*gaplow = mid;
+}
+
+static void cqspi_phy_find_gaphigh_ddr(struct cqspi_flash_pdata *f_pdata,
+				       struct spi_mem *mem,
+				       struct phy_setting *bottomleft,
+				       struct phy_setting *topright,
+				       struct phy_setting *gaphigh)
+{
+	struct phy_setting left, right, mid;
+	int ret;
+
+	left = *bottomleft;
+	right = *topright;
+
+	mid.tx = left.tx + ((right.tx - left.tx) / 2);
+	mid.rx = left.rx + ((right.rx - left.rx) / 2);
+	mid.read_delay = right.read_delay;
+
+	do {
+		ret = cqspi_phy_apply_setting(f_pdata, &mid);
+		if (!ret)
+			ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+		if (ret) {
+			/* The pattern was not found. Go to the upper half. */
+			left.tx = mid.tx;
+			left.rx = mid.rx;
+
+			mid.tx = mid.tx + ((right.tx - mid.tx) / 2);
+			mid.rx = mid.rx + ((right.rx - mid.rx) / 2);
+		} else {
+			/* The pattern was found. Go to the lower half. */
+			right.tx = mid.tx;
+			right.rx = mid.rx;
+
+			mid.tx = left.tx + ((mid.tx - left.tx) / 2);
+			mid.rx = left.rx + ((mid.rx - left.rx) / 2);
+		}
+
+		/* Break the loop if the window has closed. */
+	} while ((right.tx - left.tx >= 2) && (right.rx - left.rx >= 2));
+
+	*gaphigh = mid;
+}
+
+static int cqspi_get_temp(int *temp)
+{
+	/* TODO: read SoC thermal sensor; caller falls back to room temperature */
+	return -EOPNOTSUPP;
+}
+
+static inline void cqspi_phy_reset_setting(struct phy_setting *phy)
+{
+	*phy = (struct phy_setting){ .rx = 0, .tx = 127, .read_delay = 0 };
+}
+
+static int cqspi_phy_tuning_ddr(struct cqspi_flash_pdata *f_pdata,
+				struct spi_mem *mem)
+{
+	struct cqspi_st *cqspi = f_pdata->cqspi;
+	struct device *dev = &cqspi->pdev->dev;
+	struct phy_setting rxlow, rxhigh, txlow, txhigh;
+	struct phy_setting srxlow, srxhigh;
+	struct phy_setting bottomleft, topright, searchpoint;
+	struct phy_setting gaplow, gaphigh;
+	struct phy_setting backuppoint, backupcornerpoint;
+	int ret, rx_window, temp;
+	bool primary = true, secondary = true;
+
+	/*
+	 * DDR tuning: 2D search across RX and TX delays for optimal timing.
+	 *
+	 * Algorithm: Find RX boundaries (rxlow/rxhigh) using TX window search,
+	 * find TX boundaries (txlow/txhigh) at fixed RX, define valid region,
+	 * locate gaps via binary search, select final point with temperature
+	 * compensation.
+	 *
+	 *              rx
+	 *          127 ^
+	 *              |                                 topright
+	 *              |                                    *
+	 *              |       xxxxx     ++++++++++++++++++++
+	 *              |       xxxxxx     +++++++++++++++++++
+	 *              |       xxxxxxx     ++++++++++++++++++
+	 *              |       xxxxxxxx     +++++++++++++++++
+	 *              |       xxxxxxxxx     ++++++++++++++++
+	 *              |       xxxxxxxxxx     +++++++++++++++
+	 *              |       xxxxxxxxxxx     ++++++++++++++
+	 *              |       xxxxxxxxxxxx     +++++++++++++
+	 *              |       xxxxxxxxxxxxx     ++++++++++++
+	 *              |       xxxxxxxxxxxxxx     +++++++++++
+	 *              |       xxxxxxxxxxxxxxx     ++++++++++
+	 *              |       xxxxxxxxxxxxxxxx     +++++++++
+	 *              |       xxxxxxxxxxxxxxxxx     ++++++++
+	 *              |       xxxxxxxxxxxxxxxxxx     +++++++
+	 *              |       *
+	 *              |  bottomleft
+	 *              -----------------------------------------> tx
+	 *              0                                      127
+	 */
+
+	f_pdata->use_tuned_phy = true;
+
+	/* Golden rxlow search: Find lower RX boundary using TX window sweep */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |       xxxxx     ++++++++++++++++++++
+	 *              |       xxxxxx     +++++++++++++++++++
+	 *              |       xxxxxxx     ++++++++++++++++++
+	 *              |       xxxxxxxx     +++++++++++++++++
+	 *              |       xxxxxxxxx     ++++++++++++++++
+	 *              |       xxxxxxxxxx     +++++++++++++++
+	 *              |       xxxxxxxxxxx     ++++++++++++++
+	 *              |       |xxxxx|xxxxx     +++++++++++++
+	 *              |       |xxxxx|xxxxxx     ++++++++++++
+	 *      search  |       |xxxxx|xxxxxxx     +++++++++++
+	 *      rxlow --------->|xxxxx|xxxxxxxx     ++++++++++
+	 *              |       |xxxxx|xxxxxxxxx     +++++++++
+	 *              |       |xxxxx|xxxxxxxxxx     ++++++++
+	 *              |       |xxxxx|xxxxxxxxxxx     +++++++
+	 *              |       |     |
+	 *              --------|-----|----------------------------> tx
+	 *              0       |     |                          127
+	 *                  txlow     txlow
+	 *                  start     end
+	 *
+	 *      |----------------------------------------------------------|
+	 *      | Primary | Secondary | Final                              |
+	 *      | Search  | Search    | Point                              |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Fail    | Fail      | Return Fail                        |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Fail    | Pass      | Return Fail                        |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Pass    | Fail      | Return Fail                        |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Pass    | Pass      | rx = min(primary.rx, secondary.rx) |
+	 *      |         |           | tx = primary.tx                    |
+	 *      |         |           | read_delay =                       |
+	 *      |         |           |         min(primary.read_delay,    |
+	 *      |         |           |             secondary.read_delay)  |
+	 *      |----------------------------------------------------------|
+	 */
+
+	/* Primary rxlow: Sweep TX window to find valid RX lower bound */
+
+	rxlow.tx = CQSPI_PHY_TX_LOOKUP_LOW_START;
+	do {
+		dev_dbg(dev, "Searching for Golden Primary rxlow on TX = %d\n",
+			rxlow.tx);
+		rxlow.read_delay = CQSPI_PHY_INIT_RD;
+		ret = cqspi_find_rx_low_ddr(f_pdata, mem, &rxlow);
+		rxlow.tx += CQSPI_PHY_DDR_SEARCH_STEP;
+	} while (ret && rxlow.tx <= CQSPI_PHY_TX_LOOKUP_LOW_END);
+	if (ret)
+		goto out;
+	dev_dbg(dev, "Golden Primary rxlow: RX: %d TX: %d RD: %d\n", rxlow.rx,
+		rxlow.tx, rxlow.read_delay);
+
+	/* Secondary rxlow: Verify at offset TX for robustness */
+
+	if (rxlow.tx <= (CQSPI_PHY_TX_LOOKUP_LOW_END - CQSPI_PHY_SEARCH_OFFSET))
+		srxlow.tx = rxlow.tx + CQSPI_PHY_SEARCH_OFFSET;
+	else
+		srxlow.tx = CQSPI_PHY_TX_LOOKUP_LOW_END;
+	dev_dbg(dev, "Searching for Golden Secondary rxlow on TX = %d\n",
+		srxlow.tx);
+	srxlow.read_delay = CQSPI_PHY_INIT_RD;
+	ret = cqspi_find_rx_low_ddr(f_pdata, mem, &srxlow);
+	if (ret)
+		goto out;
+	dev_dbg(dev, "Golden Secondary rxlow: RX: %d TX: %d RD: %d\n",
+		srxlow.rx, srxlow.tx, srxlow.read_delay);
+
+	rxlow.rx = min(rxlow.rx, srxlow.rx);
+	rxlow.read_delay = min(rxlow.read_delay, srxlow.read_delay);
+	dev_dbg(dev, "Golden Final rxlow: RX: %d TX: %d RD: %d\n", rxlow.rx,
+		rxlow.tx, rxlow.read_delay);
+
+	/* Golden rxhigh search: Find upper RX boundary at fixed TX */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |       |xxxx     ++++++++++++++++++++
+	 *              |       |xxxxx     +++++++++++++++++++
+	 *    search    |       |xxxxxx     ++++++++++++++++++
+	 *    rxhigh  --------->|xxxxxxx     +++++++++++++++++
+	 *    on fixed  |       |xxxxxxxx     ++++++++++++++++
+	 *    tx        |       |xxxxxxxxx     +++++++++++++++
+	 *              |       |xxxxxxxxxx     ++++++++++++++
+	 *              |       xxxxxxxxxxxx     +++++++++++++
+	 *              |       xxxxxxxxxxxxx     ++++++++++++
+	 *              |       xxxxxxxxxxxxxx     +++++++++++
+	 *              |       xxxxxxxxxxxxxxx     ++++++++++
+	 *              |       xxxxxxxxxxxxxxxx     +++++++++
+	 *              |       xxxxxxxxxxxxxxxxx     ++++++++
+	 *              |       xxxxxxxxxxxxxxxxxx     +++++++
+	 *              |
+	 *              -------------------------------------------> tx
+	 *              0                                        127
+	 *
+	 *      |----------------------------------------------------------|
+	 *      | Primary | Secondary | Final                              |
+	 *      | Search  | Search    | Point                              |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Fail    | Fail      | Return Fail                        |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Fail    | Pass      | Choose Secondary                   |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Pass    | Fail      | Choose Primary                     |
+	 *      |---------|-----------|------------------------------------|
+	 *      | Pass    | Pass      | if (secondary.rx > primary.rx)     |
+	 *      |         |           |         Choose Secondary           |
+	 *      |         |           | else                               |
+	 *      |         |           |         Choose Primary             |
+	 *      |----------------------------------------------------------|
+	 */
+
+	/* Primary rxhigh: Search at rxlow's TX, decrement from max read_delay */
+
+	rxhigh.tx = rxlow.tx;
+	dev_dbg(dev, "Searching for Golden Primary rxhigh on TX = %d\n",
+		rxhigh.tx);
+	rxhigh.read_delay = CQSPI_PHY_MAX_RD;
+	ret = cqspi_find_rx_high_ddr(f_pdata, mem, &rxhigh);
+	if (ret)
+		primary = false;
+	dev_dbg(dev, "Golden Primary rxhigh: RX: %d TX: %d RD: %d\n", rxhigh.rx,
+		rxhigh.tx, rxhigh.read_delay);
+
+	/* Secondary rxhigh: Verify at offset TX */
+
+	if (rxhigh.tx <=
+	    (CQSPI_PHY_TX_LOOKUP_LOW_END - CQSPI_PHY_SEARCH_OFFSET))
+		srxhigh.tx = rxhigh.tx + CQSPI_PHY_SEARCH_OFFSET;
+	else
+		srxhigh.tx = CQSPI_PHY_TX_LOOKUP_LOW_END;
+	dev_dbg(dev, "Searching for Golden Secondary rxhigh on TX = %d\n",
+		srxhigh.tx);
+	srxhigh.read_delay = CQSPI_PHY_MAX_RD;
+	ret = cqspi_find_rx_high_ddr(f_pdata, mem, &srxhigh);
+	if (ret)
+		secondary = false;
+	dev_dbg(dev, "Golden Secondary rxhigh: RX: %d TX: %d RD: %d\n",
+		srxhigh.rx, srxhigh.tx, srxhigh.read_delay);
+
+	if (!primary && !secondary)
+		goto out;
+	else if (!primary)
+		rxhigh = srxhigh;
+	else if (secondary && srxhigh.rx > rxhigh.rx)
+		rxhigh = srxhigh;
+	dev_dbg(dev, "Golden Final rxhigh: RX: %d TX: %d RD: %d\n", rxhigh.rx,
+		rxhigh.tx, rxhigh.read_delay);
+
+	primary = true;
+	secondary = true;
+
+	/* If rxlow/rxhigh at same read_delay, search backup at upper TX range */
+
+	if (rxlow.read_delay == rxhigh.read_delay) {
+		dev_dbg(dev, "rxlow and rxhigh at the same read delay.\n");
+
+		/* Backup rxlow: Search at high TX window */
+
+		/*
+		 *              rx
+		 *          127 ^
+		 *              |       xxxxx     ++++++++++++++++++++
+		 *              |       xxxxxx     +++++++++++++++++++
+		 *              |       xxxxxxx     ++++++++++++++++++
+		 *              |       xxxxxxxx     +++++++++++++++++
+		 *              |       xxxxxxxxx     ++++++++++++++++
+		 *              |       xxxxxxxxxx     +++++++++++++++
+		 *              |       xxxxxxxxxxx     ++++++++++++++
+		 *              |       xxxxxxxxxxxx     +++++++|++++|
+		 *              |       xxxxxxxxxxxxx     ++++++|++++|
+		 *      search  |       xxxxxxxxxxxxxx     +++++|++++|
+		 *      rxlow --------------------------------->|++++|
+		 *              |       xxxxxxxxxxxxxxxx     +++|++++|
+		 *              |       xxxxxxxxxxxxxxxxx     ++|++++|
+		 *              |       xxxxxxxxxxxxxxxxxx     +|++++|
+		 *              |                               |    |
+		 *              --------------------------------|----|-----> tx
+		 *              0                               |    |   127
+		 *                                         txhigh    txhigh
+		 *                                          start    end
+		 *
+		 *      |-----------------------------------------------------|
+		 *      | Primary | Secondary | Final                         |
+		 *      | Search  | Search    | Point                         |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Fail    | Fail      | Return Fail                   |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Fail    | Pass      | Return Fail                   |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Pass    | Fail      | Return Fail                   |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Pass    | Pass      | rx =                          |
+		 *      |         |           |  min(primary.rx, secondary.rx)|
+		 *      |         |           | tx = primary.tx               |
+		 *      |         |           | read_delay =                  |
+		 *      |         |           |  min(primary.read_delay,      |
+		 *      |         |           |      secondary.read_delay)    |
+		 *      |-----------------------------------------------------|
+		 */
+
+		/* Primary backup: Decrement TX from high window end */
+
+		backuppoint.tx = CQSPI_PHY_TX_LOOKUP_HIGH_END;
+		do {
+			dev_dbg(dev,
+				"Searching for Backup Primary rxlow on TX = %d\n",
+				backuppoint.tx);
+			backuppoint.read_delay = CQSPI_PHY_INIT_RD;
+			ret = cqspi_find_rx_low_ddr(f_pdata, mem, &backuppoint);
+			backuppoint.tx -= CQSPI_PHY_DDR_SEARCH_STEP;
+		} while (ret &&
+			 backuppoint.tx >= CQSPI_PHY_TX_LOOKUP_HIGH_START);
+		if (ret)
+			goto out;
+		dev_dbg(dev, "Backup Primary rxlow: RX: %d TX: %d RD: %d\n",
+			backuppoint.rx, backuppoint.tx, backuppoint.read_delay);
+
+		/* Secondary backup: Verify at offset TX */
+
+		if (backuppoint.tx >=
+		    (CQSPI_PHY_TX_LOOKUP_HIGH_START + CQSPI_PHY_SEARCH_OFFSET))
+			srxlow.tx = backuppoint.tx - CQSPI_PHY_SEARCH_OFFSET;
+		else
+			srxlow.tx = CQSPI_PHY_TX_LOOKUP_HIGH_START;
+		dev_dbg(dev,
+			"Searching for Backup Secondary rxlow on TX = %d\n",
+			srxlow.tx);
+		srxlow.read_delay = CQSPI_PHY_INIT_RD;
+		ret = cqspi_find_rx_low_ddr(f_pdata, mem, &srxlow);
+		if (ret)
+			goto out;
+		dev_dbg(dev, "Backup Secondary rxlow: RX: %d TX: %d RD: %d\n",
+			srxlow.rx, srxlow.tx, srxlow.read_delay);
+
+		backuppoint.rx = min(backuppoint.rx, srxlow.rx);
+		backuppoint.read_delay =
+			min(backuppoint.read_delay, srxlow.read_delay);
+		dev_dbg(dev, "Backup Final rxlow: RX: %d TX: %d RD: %d\n",
+			backuppoint.rx, backuppoint.tx, backuppoint.read_delay);
+
+		if (backuppoint.rx < rxlow.rx) {
+			rxlow = backuppoint;
+			dev_dbg(dev, "Updating rxlow to the one at TX = %d\n",
+				backuppoint.tx);
+		}
+		dev_dbg(dev, "Final rxlow: RX: %d TX: %d RD: %d\n", rxlow.rx,
+			rxlow.tx, rxlow.read_delay);
+
+		/* Backup rxhigh: Search at fixed backup TX */
+
+		/*
+		 *              rx
+		 *          127 ^
+		 *              |       xxxxx     +++++++++++++++++++|
+		 *              |       xxxxxx     ++++++++++++++++++|
+		 *    search    |       xxxxxxx     +++++++++++++++++|
+		 *    rxhigh  -------------------------------------->|
+		 *    on fixed  |       xxxxxxxxx     +++++++++++++++|
+		 *    tx        |       xxxxxxxxxx     ++++++++++++++|
+		 *              |       xxxxxxxxxxx     +++++++++++++|
+		 *              |       xxxxxxxxxxxx     +++++++++++++
+		 *              |       xxxxxxxxxxxxx     ++++++++++++
+		 *              |       xxxxxxxxxxxxxx     +++++++++++
+		 *              |       xxxxxxxxxxxxxxx     ++++++++++
+		 *              |       xxxxxxxxxxxxxxxx     +++++++++
+		 *              |       xxxxxxxxxxxxxxxxx     ++++++++
+		 *              |       xxxxxxxxxxxxxxxxxx     +++++++
+		 *              |
+		 *              -------------------------------------------> tx
+		 *               0                                        127
+		 *
+		 *      |-----------------------------------------------------|
+		 *      | Primary | Secondary | Final                         |
+		 *      | Search  | Search    | Point                         |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Fail    | Fail      | Return Fail                   |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Fail    | Pass      | Choose Secondary              |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Pass    | Fail      | Choose Primary                |
+		 *      |---------|-----------|-------------------------------|
+		 *      | Pass    | Pass      | if (secondary.rx > primary.rx)|
+		 *      |         |           |         Choose Secondary      |
+		 *      |         |           | else                          |
+		 *      |         |           |         Choose Primary        |
+		 *      |-----------------------------------------------------|
+		 */
+
+		/* Primary backup rxhigh: Use backup TX, decrement from max read_delay */
+
+		dev_dbg(dev, "Searching for Backup Primary rxhigh on TX = %d\n",
+			backuppoint.tx);
+		backuppoint.read_delay = CQSPI_PHY_MAX_RD;
+		ret = cqspi_find_rx_high_ddr(f_pdata, mem, &backuppoint);
+		if (ret)
+			primary = false;
+		dev_dbg(dev, "Backup Primary rxhigh: RX: %d TX: %d RD: %d\n",
+			backuppoint.rx, backuppoint.tx, backuppoint.read_delay);
+
+		/* Secondary backup rxhigh: Verify at offset TX */
+
+		if (backuppoint.tx >=
+		    (CQSPI_PHY_TX_LOOKUP_HIGH_START + CQSPI_PHY_SEARCH_OFFSET))
+			srxhigh.tx = backuppoint.tx - CQSPI_PHY_SEARCH_OFFSET;
+		else
+			srxhigh.tx = CQSPI_PHY_TX_LOOKUP_HIGH_START;
+		dev_dbg(dev,
+			"Searching for Backup Secondary rxhigh on TX = %d\n",
+			srxhigh.tx);
+		srxhigh.read_delay = CQSPI_PHY_MAX_RD;
+		ret = cqspi_find_rx_high_ddr(f_pdata, mem, &srxhigh);
+		if (ret)
+			secondary = false;
+		dev_dbg(dev, "Backup Secondary rxhigh: RX: %d TX: %d RD: %d\n",
+			srxhigh.rx, srxhigh.tx, srxhigh.read_delay);
+
+		if (!primary && !secondary)
+			goto out;
+		else if (!primary)
+			backuppoint = srxhigh;
+		else if (secondary && srxhigh.rx > backuppoint.rx)
+			backuppoint = srxhigh;
+		dev_dbg(dev, "Backup Final rxhigh: RX: %d TX: %d RD: %d\n",
+			backuppoint.rx, backuppoint.tx, backuppoint.read_delay);
+
+		if (backuppoint.rx > rxhigh.rx) {
+			rxhigh = backuppoint;
+			dev_dbg(dev, "Updating rxhigh to the one at TX = %d\n",
+				backuppoint.tx);
+		}
+		dev_dbg(dev, "Final rxhigh: RX: %d TX: %d RD: %d\n", rxhigh.rx,
+			rxhigh.tx, rxhigh.read_delay);
+	}
+
+	/* Golden txlow: Fix RX at 1/4 of RX window, search TX lower bound */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |
+	 *     rxhigh --------->xxxxx     ++++++++++++++++++++
+	 *              |       xxxxxx     +++++++++++++++++++
+	 *              |       xxxxxxx     ++++++++++++++++++
+	 *              |       xxxxxxxx     +++++++++++++++++
+	 *              |       xxxxxxxxx     ++++++++++++++++
+	 *              |       xxxxxxxxxx     +++++++++++++++
+	 *              |       xxxxxxxxxxx     ++++++++++++++
+	 *              |       xxxxxxxxxxxx     +++++++++++++
+	 *    fix rx    |       xxxxxxxxxxxxx     ++++++++++++
+	 *    1/4 b/w ---------><------->xxxxx     +++++++++++
+	 *    rxlow and |       xxxx|xxxxxxxxxx     ++++++++++
+	 *    rxhigh    |       xxxx|xxxxxxxxxxx     +++++++++
+	 *              |       xxxx|xxxxxxxxxxxx     ++++++++
+	 *      rxlow --------->xxxx|xxxxxxxxxxxxx     +++++++
+	 *              |           |
+	 *              ------------|------------------------------> tx
+	 *              0           |                            127
+	 *                     search
+	 *                      txlow
+	 */
+
+	rx_window = rxhigh.rx - rxlow.rx;
+	txlow.rx = rxlow.rx + (rx_window / 4);
+	dev_dbg(dev, "Searching for Golden txlow on RX = %d\n", txlow.rx);
+	txlow.read_delay = CQSPI_PHY_INIT_RD;
+	ret = cqspi_find_tx_low_ddr(f_pdata, mem, &txlow);
+	if (ret)
+		goto out;
+	dev_dbg(dev, "Golden txlow: RX: %d TX: %d RD: %d\n", txlow.rx, txlow.tx,
+		txlow.read_delay);
+
+	/* Golden txhigh: Same RX as txlow, decrement from max read_delay */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |
+	 *     rxhigh --------->xxxxx     ++++++++++++++++++++
+	 *              |       xxxxxx     +++++++++++++++++++
+	 *              |       xxxxxxx     ++++++++++++++++++
+	 *              |       xxxxxxxx     +++++++++++++++++
+	 *              |       xxxxxxxxx     ++++++++++++++++
+	 *              |       xxxxxxxxxx     +++++++++++++++
+	 *              |       xxxxxxxxxxx     ++++++++++++++
+	 *              |       xxxxxxxxxxxx     +++++++++++++
+	 *    fix rx    |       xxxxxxxxxxxxx     ++++++++++++
+	 *    1/4 b/w --------------------------------><----->
+	 *    rxlow and |       xxxxxxxxxxxxxxx     ++++++|+++
+	 *    rxhigh    |       xxxxxxxxxxxxxxxx     +++++|+++
+	 *              |       xxxxxxxxxxxxxxxxx     ++++|+++
+	 *      rxlow --------->xxxxxxxxxxxxxxxxxx     +++|+++
+	 *              |                                 |
+	 *              ----------------------------------|--------> tx
+	 *              0                                 |      127
+	 *                                           search
+	 *                                           txhigh
+	 */
+
+	txhigh.rx = txlow.rx;
+	dev_dbg(dev, "Searching for Golden txhigh on RX = %d\n", txhigh.rx);
+	txhigh.read_delay = CQSPI_PHY_MAX_RD;
+	ret = cqspi_find_tx_high_ddr(f_pdata, mem, &txhigh);
+	if (ret)
+		goto out;
+	dev_dbg(dev, "Golden txhigh: RX: %d TX: %d RD: %d\n", txhigh.rx,
+		txhigh.tx, txhigh.read_delay);
+
+	/* If txlow/txhigh at same read_delay, search backup at 3/4 RX window */
+
+	if (txlow.read_delay == txhigh.read_delay) {
+		/* Backup txlow: Fix RX at 3/4 of RX window */
+
+		/*
+		 *              rx
+		 *          127 ^
+		 *              |
+		 *     rxhigh --------->xxxxx     ++++++++++++++++++++
+		 *              |       xxxxxx     +++++++++++++++++++
+		 *    fix rx    |       xxxxxxx     ++++++++++++++++++
+		 *    3/4 b/w ---------><----->x     +++++++++++++++++
+		 *    rxlow and |       xxxx|xxxx     ++++++++++++++++
+		 *    rxhigh    |       xxxx|xxxxx     +++++++++++++++
+		 *              |       xxxx|xxxxxx     ++++++++++++++
+		 *              |       xxxx|xxxxxxx     +++++++++++++
+		 *              |       xxxx|xxxxxxxx     ++++++++++++
+		 *              |       xxxx|xxxxxxxxx     +++++++++++
+		 *              |       xxxx|xxxxxxxxxx     ++++++++++
+		 *              |       xxxx|xxxxxxxxxxx     +++++++++
+		 *              |       xxxx|xxxxxxxxxxxx     ++++++++
+		 *      rxlow --------->xxxx|xxxxxxxxxxxxx     +++++++
+		 *              |           |
+		 *              ------------|------------------------------> tx
+		 *              0           |                            127
+		 *                     search
+		 *                      txlow
+		 */
+
+		dev_dbg(dev, "txlow and txhigh at the same read delay.\n");
+		backuppoint.rx = rxlow.rx + ((rx_window * 3) / 4);
+		dev_dbg(dev, "Searching for Backup txlow on RX = %d\n",
+			backuppoint.rx);
+		backuppoint.read_delay = CQSPI_PHY_INIT_RD;
+		ret = cqspi_find_tx_low_ddr(f_pdata, mem, &backuppoint);
+		if (ret)
+			goto out;
+		dev_dbg(dev, "Backup txlow: RX: %d TX: %d RD: %d\n",
+			backuppoint.rx, backuppoint.tx, backuppoint.read_delay);
+
+		if (backuppoint.tx < txlow.tx) {
+			txlow = backuppoint;
+			dev_dbg(dev, "Updating txlow with the one at RX = %d\n",
+				backuppoint.rx);
+		}
+		dev_dbg(dev, "Final txlow: RX: %d TX: %d RD: %d\n", txlow.rx,
+			txlow.tx, txlow.read_delay);
+
+		/* Backup txhigh: Same RX as backup txlow, decrement from max */
+
+		/*
+		 *              rx
+		 *          127 ^
+		 *              |
+		 *     rxhigh --------->xxxxx     ++++++++++++++++++++
+		 *              |       xxxxxx     +++++++++++++++++++
+		 *    fix rx    |       xxxxxxx     ++++++++++++++++++
+		 *    3/4 b/w ------------------------------><------->
+		 *    rxlow and |       xxxxxxxxx     +++++++++++|++++
+		 *    rxhigh    |       xxxxxxxxxx     ++++++++++|++++
+		 *              |       xxxxxxxxxxx     +++++++++|++++
+		 *              |       xxxxxxxxxxxx     ++++++++|++++
+		 *              |       xxxxxxxxxxxxx     +++++++|++++
+		 *              |       xxxxxxxxxxxxxx     ++++++|++++
+		 *              |       xxxxxxxxxxxxxxx     +++++|++++
+		 *              |       xxxxxxxxxxxxxxxx     ++++|++++
+		 *              |       xxxxxxxxxxxxxxxxx     +++|++++
+		 *      rxlow --------->xxxxxxxxxxxxxxxxxx     ++|++++
+		 *              |                                |
+		 *              ---------------------------------|---------> tx
+		 *              0                                |       127
+		 *                                               search
+		 *                                               txhigh
+		 */
+
+		dev_dbg(dev, "Searching for Backup txhigh on RX = %d\n",
+			backuppoint.rx);
+		backuppoint.read_delay = CQSPI_PHY_MAX_RD;
+		ret = cqspi_find_tx_high_ddr(f_pdata, mem, &backuppoint);
+		if (ret)
+			goto out;
+		dev_dbg(dev, "Backup txhigh: RX: %d TX: %d RD: %d\n",
+			backuppoint.rx, backuppoint.tx, backuppoint.read_delay);
+
+		if (backuppoint.tx > txhigh.tx) {
+			txhigh = backuppoint;
+			dev_dbg(dev,
+				"Updating txhigh with the one at RX = %d\n",
+				backuppoint.rx);
+		}
+		dev_dbg(dev, "Final txhigh: RX: %d TX: %d RD: %d\n", txhigh.rx,
+			txhigh.tx, txhigh.read_delay);
+	}
+
+	/* Corner points: Define and verify bottomleft and topright boundaries */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |                                 topright
+	 *              |                                    *
+	 *    rxhigh -----------xxxxx     ++++++++++++++++++++
+	 *              |       xxxxxx     +++++++++++++++++++
+	 *              |       xxxxxxx     ++++++++++++++++++
+	 *              |       xxxxxxxx     +++++++++++++++++
+	 *              |       xxxxxxxxx     ++++++++++++++++
+	 *              |       xxxxxxxxxx     +++++++++++++++
+	 *              |       xxxxxxxxxxx     ++++++++++++++
+	 *              |       xxxxxxxxxxxx     +++++++++++++
+	 *              |       xxxxxxxxxxxxx     ++++++++++++
+	 *              |       xxxxxxxxxxxxxx     +++++++++++
+	 *              |       xxxxxxxxxxxxxxx     ++++++++++
+	 *              |       xxxxxxxxxxxxxxxx     +++++++++
+	 *              |       xxxxxxxxxxxxxxxxx     ++++++++
+	 *     rxlow -----------xxxxxxxxxxxxxxxxxx     +++++++
+	 *              |       *                            |
+	 *              | bottom|left                        |
+	 *              --------|----------------------------|---> tx
+	 *              0       |                            | 127
+	 *                      |                            |
+	 *                      txlow                   txhigh
+	 *
+	 * Verification: Test point 4 taps inside each corner, adjust
+	 * read_delay ?1 if needed to ensure valid corners for gap search.
+	 */
+
+	bottomleft.tx = txlow.tx;
+	bottomleft.rx = rxlow.rx;
+	if (txlow.read_delay <= rxlow.read_delay)
+		bottomleft.read_delay = txlow.read_delay;
+	else
+		bottomleft.read_delay = rxlow.read_delay;
+
+	/* Verify bottomleft: Test 4 taps inside, adjust read_delay if needed */
+	backupcornerpoint = bottomleft;
+	backupcornerpoint.tx += 4;
+	backupcornerpoint.rx += 4;
+	ret = cqspi_phy_apply_setting(f_pdata, &backupcornerpoint);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret) {
+		backupcornerpoint.read_delay--;
+		ret = cqspi_phy_apply_setting(f_pdata, &backupcornerpoint);
+		if (!ret)
+			ret = cqspi_phy_check_pattern(f_pdata, mem);
+	}
+
+	if (ret)
+		goto out;
+
+	bottomleft.read_delay = backupcornerpoint.read_delay;
+
+	topright.tx = txhigh.tx;
+	topright.rx = rxhigh.rx;
+	if (txhigh.read_delay >= rxhigh.read_delay)
+		topright.read_delay = txhigh.read_delay;
+	else
+		topright.read_delay = rxhigh.read_delay;
+
+	/* Verify topright: Test 4 taps inside, adjust read_delay if needed */
+	backupcornerpoint = topright;
+	backupcornerpoint.tx -= 4;
+	backupcornerpoint.rx -= 4;
+	ret = cqspi_phy_apply_setting(f_pdata, &backupcornerpoint);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret) {
+		backupcornerpoint.read_delay++;
+		ret = cqspi_phy_apply_setting(f_pdata, &backupcornerpoint);
+		if (!ret)
+			ret = cqspi_phy_check_pattern(f_pdata, mem);
+	}
+
+	if (ret)
+		goto out;
+
+	topright.read_delay = backupcornerpoint.read_delay;
+
+	dev_dbg(dev, "topright: RX: %d TX: %d RD: %d\n", topright.rx,
+		topright.tx, topright.read_delay);
+	dev_dbg(dev, "bottomleft: RX: %d TX: %d RD: %d\n", bottomleft.rx,
+		bottomleft.tx, bottomleft.read_delay);
+	cqspi_phy_find_gaplow_ddr(f_pdata, mem, &bottomleft, &topright, &gaplow);
+	dev_dbg(dev, "gaplow: RX: %d TX: %d RD: %d\n", gaplow.rx, gaplow.tx,
+		gaplow.read_delay);
+
+	/* Final point selection: Handle single vs dual passing regions */
+
+	if (bottomleft.read_delay == topright.read_delay) {
+		/*
+		 * Single region: Use midpoint with temperature compensation.
+		 * Gaplow approximates upper boundary of valid region.
+		 *
+		 *              rx
+		 *          127 ^
+		 *              |              gaplow (approx. topright)
+		 *              |              |
+		 *    rxhigh -----------xxxxxxx|          failing
+		 *              |       xxxxxxx|           region
+		 *              |       xxxxxxx|     <--------------->
+		 *              |       xxxxxxx|     +++++++++++++++++
+		 *              |       xxxxxxxxx     ++++++++++++++++
+		 *              |       xxxxxxxxxx     +++++++++++++++
+		 *              |       xxxxxxxxxxx     ++++++++++++++
+		 *              |       xxxxxxxxxxxx     +++++++++++++
+		 *              |       xxxxxxxxxxxxx     ++++++++++++
+		 *              |       xxxxxxxxxxxxxx     +++++++++++
+		 *              |       xxxxxxxxxxxxxxx     ++++++++++
+		 *              |       xxxxxxxxxxxxxxxx     +++++++++
+		 *              |       xxxxxxxxxxxxxxxxx     ++++++++
+		 *     rxlow -----------xxxxxxxxxxxxxxxxxx     +++++++
+		 *              |       *                            |
+		 *              | bottom|left                        |
+		 *              --------|----------------------------|---> tx
+		 *              0       |                            | 127
+		 *                      |                            |
+		 *                      txlow                   txhigh
+		 *                             (same read_delay)
+		 *
+		 * Temperature compensation: Valid region shifts with temp.
+		 * Offset = region_size / (330 / (temp - 42?C))
+		 * Factor 330 is empirically determined for this hardware.
+		 */
+
+		dev_dbg(dev,
+			"bottomleft and topright at the same read delay.\n");
+
+		topright = gaplow;
+		searchpoint.read_delay = bottomleft.read_delay;
+		searchpoint.tx =
+			bottomleft.tx + ((topright.tx - bottomleft.tx) / 2);
+		searchpoint.rx =
+			bottomleft.rx + ((topright.rx - bottomleft.rx) / 2);
+
+		ret = cqspi_get_temp(&temp);
+		if (ret) {
+			/* Assume room temperature if sensor unavailable */
+			dev_dbg(dev,
+				"Unable to get temperature. Assuming room temperature\n");
+			temp = CQSPI_PHY_DEFAULT_TEMP;
+		}
+
+		if (temp < CQSPI_PHY_MIN_TEMP || temp > CQSPI_PHY_MAX_TEMP) {
+			dev_err(dev,
+				"Temperature outside operating range: %dC\n",
+				temp);
+			ret = -EINVAL;
+			goto out;
+		}
+
+		if (temp == CQSPI_PHY_MID_TEMP)
+			temp++; /* Avoid divide-by-zero */
+		dev_dbg(dev, "Temperature: %dC\n", temp);
+
+		/*
+		 * Apply temperature offset: positive at high temp, negative at low.
+		 * Compute the divisor once and apply to both TX and RX. Use int
+		 * arithmetic throughout to avoid u8 wrapping on negative offsets.
+		 */
+		temp = 330 / (temp - CQSPI_PHY_MID_TEMP);
+		searchpoint.tx = clamp((int)searchpoint.tx +
+				       (topright.tx - bottomleft.tx) / temp,
+				       0, CQSPI_PHY_MAX_DELAY);
+		searchpoint.rx = clamp((int)searchpoint.rx +
+				       (topright.rx - bottomleft.rx) / temp,
+				       0, CQSPI_PHY_MAX_DELAY);
+	} else {
+		/*
+		 * Dual regions: Gap separates two valid regions, choose larger.
+		 *
+		 *              rx
+		 *          127 ^
+		 *              |                                 topright
+		 *              |                                    *
+		 *    rxhigh -----------xxxxx     +++++++++++++++++++|
+		 *              |       xxxxxx    <region 2> ++++++++|
+		 *              |       xxxxxxx     +++++++++++++++++|
+		 *              |       xxxxxxxx     ++++++++++++++++|
+		 *              |       xxxxxxxxx     +++++++++++++++|
+		 *              |       xxxxxxxxxx     ++++++++++++++|
+		 *              |                  failing           |
+		 *              |                  region            |
+		 *              |       xxxxxxxxxxxxx     +++++++++++|
+		 *              |       xxxxxxxxxxxxxx     ++++++++++|
+		 *              |       xxxxxxxxxxxxxxx     +++++++++|
+		 *              |       xxxxxxxxxxxxxxxx     ++++++++|
+		 *              |       xxxxxxxxx <region 1>  +++++++|
+		 *     rxlow -----------xxxxxxxxxxxxxxxxxx     ++++++|
+		 *              |       *                            |
+		 *              | bottom|left                        |
+		 *              --------|----------------------------|---> tx
+		 *              0       |                            | 127
+		 *                      |                            |
+		 *                      txlow                   txhigh
+		 *
+		 * Strategy: Compare Manhattan distances from gap boundaries to
+		 * corners. Choose corner furthest from gap (larger region).
+		 * Apply 16-tap margin inward, scale RX proportionally.
+		 */
+
+		cqspi_phy_find_gaphigh_ddr(f_pdata, mem, &bottomleft,
+					   &topright, &gaphigh);
+		dev_dbg(dev, "gaphigh: RX: %d TX: %d RD: %d\n", gaphigh.rx,
+			gaphigh.tx, gaphigh.read_delay);
+
+		if (topright.tx == bottomleft.tx) {
+			dev_err(dev, "zero TX span in dual-region: cannot compute search point\n");
+			ret = -EINVAL;
+			goto out;
+		}
+
+		/* Compare Manhattan distances: choose corner furthest from gap */
+		if ((abs(gaplow.tx - bottomleft.tx) +
+		     abs(gaplow.rx - bottomleft.rx)) <
+		    (abs(gaphigh.tx - topright.tx) +
+		     abs(gaphigh.rx - topright.rx))) {
+			/* Topright further: Use Region 2, 16 taps inward */
+			searchpoint = topright;
+			searchpoint.tx = clamp((int)topright.tx - 16,
+					       0, CQSPI_PHY_MAX_DELAY);
+			searchpoint.rx = clamp((int)topright.rx -
+					       (16 * (topright.rx - bottomleft.rx)) /
+					       (topright.tx - bottomleft.tx),
+					       0, CQSPI_PHY_MAX_DELAY);
+		} else {
+			/* Bottomleft further: Use Region 1, 16 taps inward */
+			searchpoint = bottomleft;
+			searchpoint.tx = clamp((int)bottomleft.tx + 16,
+					       0, CQSPI_PHY_MAX_DELAY);
+			searchpoint.rx = clamp((int)bottomleft.rx +
+					       (16 * (topright.rx - bottomleft.rx)) /
+					       (topright.tx - bottomleft.tx),
+					       0, CQSPI_PHY_MAX_DELAY);
+		}
+	}
+
+	/* Apply and verify final tuning point */
+	dev_dbg(dev, "Final tuning point: RX: %d TX: %d RD: %d\n",
+		searchpoint.rx, searchpoint.tx, searchpoint.read_delay);
+	ret = cqspi_phy_apply_setting(f_pdata, &searchpoint);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret) {
+		dev_err(dev,
+			"Failed to find pattern at final calibration point\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	f_pdata->phy_setting.read_delay = searchpoint.read_delay;
+	f_pdata->phy_setting.rx = searchpoint.rx;
+	f_pdata->phy_setting.tx = searchpoint.tx;
+out:
+	if (ret)
+		f_pdata->use_tuned_phy = false;
+
+	return ret;
+}
+
+static int cqspi_phy_tuning_sdr(struct cqspi_flash_pdata *f_pdata,
+				struct spi_mem *mem)
+{
+	struct cqspi_st *cqspi = f_pdata->cqspi;
+	struct device *dev = &cqspi->pdev->dev;
+	struct phy_setting rxlow, rxhigh, first, second, final;
+	u8 window1 = 0;
+	u8 window2 = 0;
+	int ret;
+
+	/*
+	 * SDR tuning: 1D search for optimal RX delay (TX less critical).
+	 * Find two consecutive windows, choose larger, use midpoint.
+	 *
+	 *              rx
+	 *          127 ^
+	 *              |        |-----window at----------|
+	 *              |        |-----read_delay = n+1---|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        rxlow(n+1)   midpoint    rxhigh(n+1)
+	 *              |
+	 *              |   |---window at--------|
+	 *              |   |---read_delay = n---|
+	 *              |   |xxxxxxxxxxxxxxxxxxxx|
+	 *              |   |xxxxxxxxxxxxxxxxxxxx|
+	 *              |   |xxxxxxxxxxxxxxxxxxxx|
+	 *              |   |xxxxxxxxxxxxxxxxxxxx|
+	 *              |   |xxxxxxxxxxxxxxxxxxxx|
+	 *              |   rxlow(n)   midpoint  rxhigh(n)
+	 *              |
+	 *              -----------------------------------------> tx
+	 *              0                                      127
+	 *                  read_delay=n                  read_delay=n+1
+	 */
+
+	f_pdata->use_tuned_phy = true;
+	cqspi_phy_reset_setting(&rxlow);
+	cqspi_phy_reset_setting(&rxhigh);
+	cqspi_phy_reset_setting(&first);
+
+	/* First window: Find rxlow by incrementing read_delay from 0 */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |   |xxxxxxxxxxxxxxxxxxxx|
+	 *  search      |   |xxxxxxxxxxxxxxxxxxxx|
+	 *  rxlow       |   |xxxxxxxxxxxxxxxxxxxx|
+	 *  increasing  |   |xxxxxxxxxxxxxxxxxxxx|
+	 *           --------->|xxxxxxxxxxxxxxxxxxxx|
+	 *  read_delay  |        |xxxxxxxxxxxxxxxxxxx|
+	 *  until found |        |xxxxxxxxxxxxxxxxxxx|
+	 *              |        rxlow
+	 *              -----------------------------------------> tx
+	 *              0                            tx fixed at 127
+	 */
+
+	do {
+		ret = cqspi_find_rx_low_sdr(f_pdata, mem, &rxlow);
+
+		if (ret)
+			rxlow.read_delay++;
+	} while (ret && rxlow.read_delay <= CQSPI_PHY_MAX_RD);
+
+	if (ret)
+		goto out;
+
+	/* Find rxhigh: Decrement from RX=127 at same read_delay */
+
+	/*
+	 *              rx
+	 *          127 ^        search rxhigh
+	 *              |        (decrement from
+	 *              |        127 until found)
+	 *              |          |
+	 *              |          |
+	 *              |          v
+	 *              |        |------------------------|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        rxlow                    rxhigh
+	 *              -----------------------------------------> tx
+	 *              0                            tx fixed at 127
+	 */
+
+	rxhigh.read_delay = rxlow.read_delay;
+	ret = cqspi_find_rx_high_sdr(f_pdata, mem, &rxhigh, rxlow.rx);
+	if (ret)
+		goto out;
+
+	/* Calculate first window midpoint for max margin */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |        |--------window1---------|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxx * xxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        rxlow        ^           rxhigh
+	 *              ----------------------|------------------> tx
+	 *              0                     |      tx fixed at 127
+	 *                            window1/2
+	 */
+
+	first.read_delay = rxlow.read_delay;
+	window1 = rxhigh.rx - rxlow.rx;
+	first.rx = rxlow.rx + (window1 / 2);
+
+	dev_dbg(dev, "First tuning point: RX: %d TX: %d RD: %d\n", first.rx,
+		first.tx, first.read_delay);
+	ret = cqspi_phy_apply_setting(f_pdata, &first);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret || first.read_delay > CQSPI_PHY_MAX_RD)
+		goto out;
+
+	/* Second window: Search at read_delay+1, may differ in size */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |        |-------|
+	 *              |        |xxxxxxx|
+	 *              |        |xxxxxxx|
+	 *              |        |xxxxxxx|
+	 *              |        |xxxxxxx|
+	 *              |        |xxxxxxx|
+	 *              |        rxlow   rxhigh
+	 *              -----------------------------------------> tx
+	 *              0
+	 *              read_delay = n (smaller window)
+	 *
+	 *              rx
+	 *          127 ^
+	 *              |        |-----------------|
+	 *              |        |xxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxx|
+	 *              |        rxlow             rxhigh
+	 *              -----------------------------------------> tx
+	 *              0
+	 *              read_delay = n+1 (larger window - better)
+	 */
+
+	cqspi_phy_reset_setting(&rxlow);
+	cqspi_phy_reset_setting(&rxhigh);
+	cqspi_phy_reset_setting(&second);
+
+	rxlow.read_delay = first.read_delay + 1;
+	if (rxlow.read_delay > CQSPI_PHY_MAX_RD)
+		goto compare;
+
+	ret = cqspi_find_rx_low_sdr(f_pdata, mem, &rxlow);
+	if (ret)
+		goto compare;
+
+	rxhigh.read_delay = rxlow.read_delay;
+	ret = cqspi_find_rx_high_sdr(f_pdata, mem, &rxhigh, rxlow.rx);
+	if (ret)
+		goto compare;
+
+	/* Calculate second window midpoint */
+
+	/*
+	 *              rx
+	 *          127 ^
+	 *              |        |--------window2---------|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxx * xxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        |xxxxxxxxxxxxxxxxxxxxxxxx|
+	 *              |        rxlow        ^           rxhigh
+	 *              ----------------------|------------------> tx
+	 *              0                     |      tx fixed at 127
+	 *                            window2/2
+	 *              read_delay = n+1
+	 */
+
+	window2 = rxhigh.rx - rxlow.rx;
+	second.rx = rxlow.rx + (window2 / 2);
+	second.read_delay = rxlow.read_delay;
+
+	dev_dbg(dev, "Second tuning point: RX: %d TX: %d RD: %d\n", second.rx,
+		second.tx, second.read_delay);
+	ret = cqspi_phy_apply_setting(f_pdata, &second);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret || second.read_delay > CQSPI_PHY_MAX_RD)
+		window2 = 0;
+
+	/* Window comparison: Choose larger window for better margin */
+
+compare:
+	cqspi_phy_reset_setting(&final);
+	if (window2 > window1) {
+		final.rx = second.rx;
+		final.read_delay = second.read_delay;
+	} else {
+		final.rx = first.rx;
+		final.read_delay = first.read_delay;
+	}
+
+	/* Apply and verify final tuning point */
+
+	dev_dbg(dev, "Final tuning point: RX: %d TX: %d RD: %d\n", final.rx,
+		final.tx, final.read_delay);
+	ret = cqspi_phy_apply_setting(f_pdata, &final);
+	if (!ret)
+		ret = cqspi_phy_check_pattern(f_pdata, mem);
+
+	if (ret) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	f_pdata->phy_setting.read_delay = final.read_delay;
+	f_pdata->phy_setting.rx = final.rx;
+	f_pdata->phy_setting.tx = final.tx;
+
+out:
+	if (ret)
+		f_pdata->use_tuned_phy = false;
+
+	return ret;
+}
+
+static int cqspi_am654_ospi_execute_tuning(struct spi_mem *mem,
+					   struct spi_mem_op *read_op,
+					   struct spi_mem_op *write_op)
+{
+	struct cqspi_st *cqspi =
+		spi_controller_get_devdata(mem->spi->controller);
+	struct cqspi_flash_pdata *f_pdata;
+	struct device *dev = &cqspi->pdev->dev;
+	int ret = 0;
+
+	f_pdata = &cqspi->f_pdata[spi_get_chipselect(mem->spi, 0)];
+
+	/*
+	 * spi-max-post-config-frequency-hz must be present for PHY tuning.
+	 * If absent, post_config_max_speed_hz is zero and there is no
+	 * calibration target, so skip tuning gracefully.
+	 */
+	if (!mem->spi->post_config_max_speed_hz) {
+		dev_dbg(dev,
+			"No post-config frequency configured, skipping tuning\n");
+		return -EOPNOTSUPP;
+	}
+
+	if (write_op) {
+		/*
+		 * For NAND: write the calibration pattern to the page cache.
+		 * The write op runs at the conservative base speed (spi->max_speed_hz)
+		 * so the write itself is reliable before PHY calibration.
+		 */
+		ret = cqspi_write_pattern_to_cache(f_pdata, mem, write_op);
+		if (ret) {
+			dev_warn(dev,
+				 "failed to write pattern to cache: %d, skipping tuning\n",
+				 ret);
+			goto out;
+		}
+
+		f_pdata->phy_write_op = *write_op;
+	} else {
+		if (!f_pdata->phy_pattern_partition_valid) {
+			dev_warn(dev, "pattern partition not found, skipping tuning\n");
+			return -EOPNOTSUPP;
+		}
+	}
+
+	/*
+	 * Build the read op template for pattern verification.  For NOR, point
+	 * at the pre-programmed pattern partition rather than the caller's
+	 * address without altering the caller's template.
+	 */
+	f_pdata->phy_read_op = *read_op;
+	if (!write_op)
+		f_pdata->phy_read_op.addr.val =
+			f_pdata->phy_pattern_partition_offset;
+
+	f_pdata->phy_read_op.max_freq = mem->spi->max_speed_hz;
+
+	ret = cqspi_phy_check_pattern(f_pdata, mem);
+	if (ret) {
+		dev_err(dev, "pattern not found: %d, skipping tuning\n", ret);
+		goto out;
+	}
+
+	f_pdata->phy_read_op.max_freq = mem->spi->post_config_max_speed_hz;
+
+	if (read_op->cmd.dtr || read_op->addr.dtr || read_op->dummy.dtr ||
+	    read_op->data.dtr) {
+		f_pdata->use_dqs = true;
+		cqspi_phy_pre_config(cqspi, f_pdata, false);
+		ret = cqspi_phy_tuning_ddr(f_pdata, mem);
+	} else {
+		f_pdata->use_dqs = false;
+		cqspi_phy_pre_config(cqspi, f_pdata, true);
+		ret = cqspi_phy_tuning_sdr(f_pdata, mem);
+	}
+
+	if (ret)
+		dev_warn(dev, "tuning failed: %d\n", ret);
+
+	cqspi_phy_post_config(cqspi, f_pdata->read_delay);
+
+out:
+	if (!ret) {
+		read_op->max_freq = mem->spi->post_config_max_speed_hz;
+		if (write_op) {
+			write_op->max_freq = mem->spi->post_config_max_speed_hz;
+			f_pdata->phy_write_op.max_freq =
+				mem->spi->post_config_max_speed_hz;
+		}
+	}
+
+	return ret;
+}
+
+static int cqspi_mem_op_execute_tuning(struct spi_mem *mem,
+				       struct spi_mem_op *read_op,
+				       struct spi_mem_op *write_op)
+{
+	struct cqspi_st *cqspi =
+		spi_controller_get_devdata(mem->spi->controller);
+
+	if (!cqspi->ddata || !cqspi->ddata->execute_tuning)
+		return -EOPNOTSUPP;
+
+	return cqspi->ddata->execute_tuning(mem, read_op, write_op);
+}
+
 static int cqspi_of_get_flash_pdata(struct platform_device *pdev,
 				    struct cqspi_flash_pdata *f_pdata,
 				    struct device_node *np)
 {
+	struct device_node *part_np;
+
 	if (of_property_read_u32(np, "cdns,read-delay", &f_pdata->read_delay)) {
 		dev_err(&pdev->dev, "couldn't determine read-delay\n");
 		return -ENXIO;
@@ -1588,9 +3326,12 @@ static int cqspi_of_get_flash_pdata(struct platform_device *pdev,
 		return -ENXIO;
 	}
 
-	if (of_property_read_u32(np, "spi-max-frequency", &f_pdata->clk_rate)) {
-		dev_err(&pdev->dev, "couldn't determine spi-max-frequency\n");
-		return -ENXIO;
+	part_np = of_parse_phandle(np, "spi-phy-pattern-partition", 0);
+	if (part_np) {
+		f_pdata->phy_pattern_partition_valid =
+			!of_property_read_u32_index(part_np, "reg", 0,
+						    &f_pdata->phy_pattern_partition_offset);
+		of_node_put(part_np);
 	}
 
 	return 0;
@@ -1740,6 +3481,7 @@ static const struct spi_controller_mem_ops cqspi_mem_ops = {
 	.exec_op = cqspi_exec_mem_op,
 	.get_name = cqspi_get_name,
 	.supports_op = cqspi_supports_mem_op,
+	.execute_tuning = cqspi_mem_op_execute_tuning,
 };
 
 static const struct spi_controller_mem_caps cqspi_mem_caps = {
@@ -2108,6 +3850,7 @@ static const struct cqspi_driver_platdata k2g_qspi = {
 static const struct cqspi_driver_platdata am654_ospi = {
 	.hwcaps_mask = CQSPI_SUPPORTS_OCTAL | CQSPI_SUPPORTS_QUAD,
 	.quirks = CQSPI_NEEDS_WR_DELAY,
+	.execute_tuning = cqspi_am654_ospi_execute_tuning,
 };
 
 static const struct cqspi_driver_platdata intel_lgm_qspi = {
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:19 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:19 +0530
Subject: [PATCH v8 15/18] mtd: spinand: extract variant ranking logic into spinand_op_find_best_variant()
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-16-s-k6@ti.com>

Refactor spinand_select_op_variant() to extract the inner loop into a
shared helper spinand_op_find_best_variant(). The helper takes a
skip_mask bitmask of already-tried variant indices, enabling callers to
iterate variants in ranked performance order while skipping those
already attempted. spinand_select_op_variant() becomes a one-liner
wrapper. No functional change.

Reviewed-by: Miquel Raynal <miquel.raynal at bootlin.com>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/mtd/nand/spi/core.c | 32 +++++++++++++++++++++++++++-----
 1 file changed, 27 insertions(+), 5 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 8bf9301f25e7..7c3341f1fca0 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -1542,9 +1542,22 @@ static int spinand_init_odtr_instruction_set(struct spinand_device *spinand)
 	return 0;
 }
 
+/*
+ * spinand_op_find_best_variant() - Find the fastest eligible op variant.
+ * @spinand:    SPI NAND device
+ * @variants:   full variant list to search
+ * @iface:      bus interface to consider (ODTR or SSDR)
+ * @skip_mask:  bitmask of variant indices to skip (already tried)
+ *
+ * Iterates @variants, evaluates transfer duration for each eligible op, and
+ * returns a pointer to the fastest one not in @skip_mask.  Returns NULL when
+ * no eligible variant remains.  Used by both variant selection at init time
+ * (skip_mask == 0) and ranked PHY tuning iteration.
+ */
 static const struct spi_mem_op *
-spinand_select_op_variant(struct spinand_device *spinand, enum spinand_bus_interface iface,
-			  const struct spinand_op_variants *variants)
+spinand_op_find_best_variant(struct spinand_device *spinand,
+			     const struct spinand_op_variants *variants,
+			     enum spinand_bus_interface iface, u32 skip_mask)
 {
 	struct nand_device *nand = spinand_to_nand(spinand);
 	const struct spi_mem_op *best_variant = NULL;
@@ -1552,15 +1565,16 @@ spinand_select_op_variant(struct spinand_device *spinand, enum spinand_bus_inter
 	unsigned int i;
 
 	for (i = 0; i < variants->nops; i++) {
-		struct spi_mem_op op = variants->ops[i];
+		struct spi_mem_op op;
 		u64 op_duration_ns = 0;
 		unsigned int nbytes;
 		int ret;
 
-		if ((iface == SSDR && spinand_op_is_odtr(&op)) ||
-		    (iface == ODTR && !spinand_op_is_odtr(&op)))
+		if ((skip_mask & BIT(i)) ||
+		    spinand_op_is_odtr(&variants->ops[i]) != (iface == ODTR))
 			continue;
 
+		op = variants->ops[i];
 		nbytes = nanddev_per_page_oobsize(nand) +
 			 nanddev_page_size(nand);
 
@@ -1589,6 +1603,14 @@ spinand_select_op_variant(struct spinand_device *spinand, enum spinand_bus_inter
 	return best_variant;
 }
 
+static const struct spi_mem_op *
+spinand_select_op_variant(struct spinand_device *spinand,
+			  enum spinand_bus_interface iface,
+			  const struct spinand_op_variants *variants)
+{
+	return spinand_op_find_best_variant(spinand, variants, iface, 0);
+}
+
 /**
  * spinand_match_and_init() - Try to find a match between a device ID and an
  *			      entry in a spinand_info table
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:20 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:20 +0530
Subject: [PATCH v8 16/18] mtd: spinand: negotiate optimal controller operating point before dirmap creation
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-17-s-k6@ti.com>

PHY calibration must complete before dirmap creation so the validated
max_freq is embedded into the dirmap op templates from the start; a
dirmap built at the base rate cannot be updated later.

Add spinand_optimize_controller() which calls spi_mem_execute_tuning()
on the pre-selected op variant. If the controller returns a per-op skip
signal (ret == 0, max_freq still 0), spinand_try_ranked_variant()
iterates all remaining variants in descending performance order, trying
ODTR first and falling back to SSDR if all ODTR attempts fail.
Optimisation failure is never fatal.

When SSDR is permanently selected, clear odtr_op_templates to prevent
spinand_configure_chip() on resume from re-entering ODTR mode with
dirmaps built for SSDR, which would corrupt data.

For devices with a distinct cont_read op table,
spinand_try_ranked_variant() re-pairs cont_read_cache with the newly
selected read_cache variant rather than disabling continuous reads
outright. Re-pairing uses protocol-shape matching (cmd/addr buswidth,
addr.nbytes, DTR-ness); addr.nbytes is included because different
address byte counts represent different controller configurations
that cannot share calibrated settings. Mode-toggle devices are
unaffected.

Move spinand_create_dirmaps() from spinand_init() to spinand_probe() so
spinand_optimize_controller() can run first.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/mtd/nand/spi/core.c | 311 ++++++++++++++++++++++++++++++++++--
 include/linux/mtd/spinand.h |  14 ++
 2 files changed, 308 insertions(+), 17 deletions(-)

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index 7c3341f1fca0..6e58e0893736 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -1284,6 +1284,7 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
 	info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
 	info.primary_op_tmpl = *spinand->op_templates->update_cache;
 	info.primary_op_tmpl.data.ecc = enable_ecc;
+	info.primary_op_tmpl.max_freq = spinand->max_write_op.max_freq;
 	desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
 					  spinand->spimem, &info);
 	if (IS_ERR(desc))
@@ -1294,9 +1295,11 @@ static int spinand_create_dirmap(struct spinand_device *spinand,
 	/* Read descriptor */
 	info.primary_op_tmpl = *spinand->op_templates->read_cache;
 	info.primary_op_tmpl.data.ecc = enable_ecc;
+	info.primary_op_tmpl.max_freq = spinand->max_read_op.max_freq;
 	if (secondary_op) {
 		info.secondary_op_tmpl = *spinand->op_templates->cont_read_cache;
 		info.secondary_op_tmpl.data.ecc = enable_ecc;
+		info.secondary_op_tmpl.max_freq = spinand->max_read_op.max_freq;
 	}
 	desc = spinand_create_rdesc(spinand, &info);
 	if (IS_ERR(desc))
@@ -1542,22 +1545,51 @@ static int spinand_init_odtr_instruction_set(struct spinand_device *spinand)
 	return 0;
 }
 
+/*
+ * spinand_op_shape_matches() - Check whether two ops share the same protocol
+ *				 shape.
+ * @a: first op
+ * @b: second op
+ *
+ * Compares cmd/addr/data buswidth, addr.nbytes, and DTR-ness, ignoring
+ * dummy cycle count and address value. A continuous-read op and its
+ * corresponding read_cache op are expected to differ only in those ignored
+ * fields: continuous read typically needs fewer dummy cycles, and always
+ * targets an internally tracked address rather than the caller's.
+ */
+static bool spinand_op_shape_matches(const struct spi_mem_op *a,
+				     const struct spi_mem_op *b)
+{
+	return a->cmd.buswidth == b->cmd.buswidth &&
+	       a->cmd.dtr == b->cmd.dtr &&
+	       a->addr.nbytes == b->addr.nbytes &&
+	       a->addr.buswidth == b->addr.buswidth &&
+	       a->addr.dtr == b->addr.dtr &&
+	       a->data.buswidth == b->data.buswidth &&
+	       a->data.dtr == b->data.dtr;
+}
+
 /*
  * spinand_op_find_best_variant() - Find the fastest eligible op variant.
- * @spinand:    SPI NAND device
- * @variants:   full variant list to search
- * @iface:      bus interface to consider (ODTR or SSDR)
- * @skip_mask:  bitmask of variant indices to skip (already tried)
+ * @spinand:     SPI NAND device
+ * @variants:    full variant list to search
+ * @iface:       bus interface to consider (ODTR or SSDR)
+ * @skip_mask:   bitmask of variant indices to skip (already tried)
+ * @match_shape: if non-NULL, only consider variants whose protocol shape
+ *		 (see spinand_op_shape_matches()) matches this op
  *
  * Iterates @variants, evaluates transfer duration for each eligible op, and
  * returns a pointer to the fastest one not in @skip_mask.  Returns NULL when
- * no eligible variant remains.  Used by both variant selection at init time
- * (skip_mask == 0) and ranked PHY tuning iteration.
+ * no eligible variant remains.  Used by variant selection at init time
+ * (skip_mask == 0, match_shape == NULL), ranked PHY tuning iteration
+ * (match_shape == NULL), and continuous-read counterpart lookup
+ * (match_shape == the read_cache op just selected).
  */
 static const struct spi_mem_op *
 spinand_op_find_best_variant(struct spinand_device *spinand,
 			     const struct spinand_op_variants *variants,
-			     enum spinand_bus_interface iface, u32 skip_mask)
+			     enum spinand_bus_interface iface, u32 skip_mask,
+			     const struct spi_mem_op *match_shape)
 {
 	struct nand_device *nand = spinand_to_nand(spinand);
 	const struct spi_mem_op *best_variant = NULL;
@@ -1574,6 +1606,10 @@ spinand_op_find_best_variant(struct spinand_device *spinand,
 		    spinand_op_is_odtr(&variants->ops[i]) != (iface == ODTR))
 			continue;
 
+		if (match_shape &&
+		    !spinand_op_shape_matches(&variants->ops[i], match_shape))
+			continue;
+
 		op = variants->ops[i];
 		nbytes = nanddev_per_page_oobsize(nand) +
 			 nanddev_page_size(nand);
@@ -1608,7 +1644,37 @@ spinand_select_op_variant(struct spinand_device *spinand,
 			  enum spinand_bus_interface iface,
 			  const struct spinand_op_variants *variants)
 {
-	return spinand_op_find_best_variant(spinand, variants, iface, 0);
+	return spinand_op_find_best_variant(spinand, variants, iface, 0, NULL);
+}
+
+/*
+ * spinand_find_cont_read_variant() - Find the continuous-read counterpart of
+ *				       a chosen read_cache variant.
+ * @spinand: SPI NAND device
+ * @read_op: the read_cache op that was just selected
+ * @iface:   bus interface @read_op belongs to (ODTR or SSDR)
+ *
+ * Devices that implement continuous read via a distinct op template (rather
+ * than a mode toggle around the existing read_cache op) need that template
+ * re-paired whenever a different read_cache variant is selected, since the
+ * two are not guaranteed to sit at the same index in their respective
+ * variant tables (some devices omit continuous-read counterparts for
+ * specific dummy-cycle sub-variants, shifting every later index out of
+ * alignment). Matching by protocol shape instead of index is unaffected by
+ * such gaps. Returns NULL if this device has no continuous-read variant
+ * table, or no matching variant is eligible on this controller.
+ */
+static const struct spi_mem_op *
+spinand_find_cont_read_variant(struct spinand_device *spinand,
+			       const struct spi_mem_op *read_op,
+			       enum spinand_bus_interface iface)
+{
+	if (!spinand->all_cont_read_variants)
+		return NULL;
+
+	return spinand_op_find_best_variant(spinand,
+					    spinand->all_cont_read_variants,
+					    iface, 0, read_op);
 }
 
 /**
@@ -1745,6 +1811,20 @@ int spinand_match_and_init(struct spinand_device *spinand,
 				spinand->cont_read_possible = false;
 		}
 
+		/*
+		 * Save the full read variant list (ODTR and SSDR ops) for
+		 * ranked controller optimization. Only saved when all ODTR
+		 * templates are valid; spinand_optimize_controller() uses this
+		 * to fall back to the next-best variant when needed.
+		 */
+		if (spinand->odtr_op_templates.read_cache &&
+		    spinand->odtr_op_templates.write_cache &&
+		    spinand->odtr_op_templates.update_cache) {
+			spinand->all_read_variants = info->op_variants.read_cache;
+			spinand->all_cont_read_variants =
+				info->op_variants.cont_read_cache;
+		}
+
 		return 0;
 	}
 
@@ -1923,7 +2003,6 @@ static int spinand_mtd_suspend(struct mtd_info *mtd)
 
 static int spinand_init(struct spinand_device *spinand)
 {
-	struct device *dev = &spinand->spimem->spi->dev;
 	struct mtd_info *mtd = spinand_to_mtd(spinand);
 	struct nand_device *nand = mtd_to_nanddev(mtd);
 	int ret;
@@ -2015,14 +2094,6 @@ static int spinand_init(struct spinand_device *spinand)
 	mtd->ecc_step_size = nanddev_get_ecc_conf(nand)->step_size;
 	mtd->bitflip_threshold = DIV_ROUND_UP(mtd->ecc_strength * 3, 4);
 
-	ret = spinand_create_dirmaps(spinand);
-	if (ret) {
-		dev_err(dev,
-			"Failed to create direct mappings for read/write operations (err = %d)\n",
-			ret);
-		goto err_cleanup_ecc_engine;
-	}
-
 	return 0;
 
 err_cleanup_ecc_engine:
@@ -2051,6 +2122,198 @@ static void spinand_cleanup(struct spinand_device *spinand)
 	kfree(spinand->scratchbuf);
 }
 
+/*
+ * spinand_try_ranked_variant() - Try controller optimization on variants in
+ *				  performance order.
+ * @spinand:    SPI NAND device
+ * @mem:        SPI memory device
+ * @iface:      bus interface to iterate (ODTR or SSDR)
+ * @tried_mask: bitmask of already-tried variant indices; updated on each try
+ *
+ * Iterates the full read variant list in descending performance order,
+ * skipping variants in @tried_mask, and calls execute_tuning on each until
+ * one succeeds. Ranked iteration finds the best available variant without
+ * re-trying already-attempted ones.
+ *
+ * On success, sets spinand->max_read_op and updates the matching
+ * odtr_op_templates.read_cache or ssdr_op_templates.read_cache. For devices
+ * with a distinct cont_read_cache variant table (non-NULL
+ * all_cont_read_variants), re-pairs cont_read_cache via protocol-shape
+ * matching and updates cont_read_possible. Mode-toggle devices
+ * (all_cont_read_variants == NULL) are unaffected.
+ */
+static bool spinand_try_ranked_variant(struct spinand_device *spinand,
+				       struct spi_mem *mem,
+				       enum spinand_bus_interface iface,
+				       u32 *tried_mask)
+{
+	const struct spinand_op_variants *variants = spinand->all_read_variants;
+	struct spinand_mem_ops *templates = iface == ODTR ?
+		&spinand->odtr_op_templates : &spinand->ssdr_op_templates;
+	const struct spi_mem_op *best;
+	int ret;
+
+	if (!variants)
+		return false;
+
+	while ((best = spinand_op_find_best_variant(spinand, variants, iface,
+						    *tried_mask, NULL))) {
+		*tried_mask |= BIT(best - variants->ops);
+		spinand->max_read_op = *best;
+		spinand->max_read_op.max_freq = 0;
+		spinand->max_write_op.max_freq = 0;
+		ret = spi_mem_execute_tuning(mem, &spinand->max_read_op,
+					     &spinand->max_write_op);
+		if (ret && ret != -EOPNOTSUPP)
+			dev_dbg(&mem->spi->dev, "%s optimization failed: %d\n",
+				iface == ODTR ? "ODTR" : "SSDR", ret);
+		if (!ret && spinand->max_read_op.max_freq) {
+			templates->read_cache = best;
+			if (spinand->all_cont_read_variants) {
+				templates->cont_read_cache =
+					spinand_find_cont_read_variant(spinand, best, iface);
+				spinand->cont_read_possible =
+					!!templates->cont_read_cache;
+			}
+			return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * spinand_reset_max_freq_ops() - Copy op templates and zero max_freq on both.
+ * @spinand:    SPI NAND device
+ * @templates:  op template set to copy from
+ *
+ * Called before execute_tuning so max_freq starts at zero; execute_tuning sets
+ * it to the validated clock rate only on success. A non-zero max_freq means
+ * controller-optimized; zero means the base rate applies.
+ */
+static void spinand_reset_max_freq_ops(struct spinand_device *spinand,
+				       struct spinand_mem_ops *templates)
+{
+	spinand->max_read_op = *templates->read_cache;
+	spinand->max_read_op.max_freq = 0;
+	spinand->max_write_op = *templates->write_cache;
+	spinand->max_write_op.max_freq = 0;
+}
+
+/*
+ * spinand_optimize_controller() - Negotiate the optimal controller operating
+ *				   point for the SPI NAND device.
+ * @spinand:    SPI NAND device
+ * @mem:        SPI memory device
+ *
+ * Tries the pre-selected variant first.  If the controller signals that
+ * optimization is not applicable for that specific op, iterates all remaining
+ * variants in performance order.  For devices that support both DTR and SDR
+ * interfaces, DTR variants are tried first; if all fail the device is
+ * switched to SDR mode and SDR variants are tried.  On full failure the
+ * device falls back to the best available non-optimized mode.  Devices that
+ * support only SDR skip the DTR ranked pass entirely.
+ *
+ * Optimization failure is never fatal.
+ *
+ * Note: tried_mask is u32, supporting up to 32 variants total across both
+ * ODTR and SSDR. Flash devices with more than 32 read variants are not
+ * supported.
+ */
+static void spinand_optimize_controller(struct spinand_device *spinand,
+					struct spi_mem *mem)
+{
+	u32 tried_mask;
+	int ret;
+
+	/* Skip entirely when no post-config target is configured. */
+	if (!mem->spi->post_config_max_speed_hz)
+		return;
+
+	spinand_reset_max_freq_ops(spinand, spinand->op_templates);
+
+	ret = spi_mem_execute_tuning(mem, &spinand->max_read_op,
+				     &spinand->max_write_op);
+	if (ret && ret != -EOPNOTSUPP)
+		dev_dbg(&mem->spi->dev, "Controller optimization failed: %d\n",
+			ret);
+
+	/*
+	 * Any non-zero return or a set max_freq means we are done (error,
+	 * unsupported, or success). Fallback only for the op-specific "skip"
+	 * signal: ret == 0 with max_freq still 0.
+	 */
+	if (ret || spinand->max_read_op.max_freq)
+		return;
+
+	/* SSDR-only devices have no ranked ODTR fallback available. */
+	if (spinand->bus_iface == SSDR || !spinand->all_read_variants)
+		return;
+
+	if (WARN_ON(spinand->all_read_variants->nops > 32))
+		return;
+
+	/* Mark the pre-selected ODTR variant as already tried. */
+	tried_mask = BIT(spinand->odtr_op_templates.read_cache -
+			 spinand->all_read_variants->ops);
+
+	dev_dbg(&mem->spi->dev,
+		"Optimization skipped for current op; searching for best variant\n");
+
+	/* Pass 1: try all remaining ODTR variants in performance order. */
+	if (spinand_try_ranked_variant(spinand, mem, ODTR, &tried_mask))
+		return;
+
+	/*
+	 * Pass 2: switch to SSDR and try all SSDR variants in performance
+	 * order. configure_chip is guaranteed non-NULL here: reaching ODTR
+	 * mode requires it.
+	 */
+	if (WARN_ON(!spinand->configure_chip))
+		goto use_odtr_fallback;
+
+	if (spinand->configure_chip(spinand, SSDR))
+		goto use_odtr_fallback;
+
+	spinand->op_templates = &spinand->ssdr_op_templates;
+	spinand->bus_iface = SSDR;
+	spinand->max_write_op = *spinand->ssdr_op_templates.write_cache;
+	spinand->max_write_op.max_freq = 0;
+
+	/*
+	 * Only ODTR variants were candidates in Pass 1; SSDR bits are clear.
+	 * Clear ODTR templates on success so spinand_configure_chip() on
+	 * resume does not re-enter ODTR with mismatched SSDR dirmaps.
+	 */
+	if (spinand_try_ranked_variant(spinand, mem, SSDR, &tried_mask)) {
+		spinand->odtr_op_templates.read_cache = NULL;
+		spinand->odtr_op_templates.write_cache = NULL;
+		spinand->odtr_op_templates.update_cache = NULL;
+		return;
+	}
+
+	/*
+	 * All attempts exhausted.  Revert to ODTR for non-optimized DTR
+	 * operation.  If revert fails, stay in SSDR ? a mode mismatch
+	 * (ODTR op templates on SSDR-mode device) would corrupt data.
+	 * Clear ODTR templates in either case to prevent resume from
+	 * re-entering ODTR with mismatched SSDR dirmaps.
+	 */
+	if (spinand->configure_chip(spinand, ODTR)) {
+		dev_warn(&mem->spi->dev,
+			 "Failed to revert to ODTR, staying in SSDR\n");
+		spinand->odtr_op_templates.read_cache = NULL;
+		spinand->odtr_op_templates.write_cache = NULL;
+		spinand->odtr_op_templates.update_cache = NULL;
+		spinand_reset_max_freq_ops(spinand, &spinand->ssdr_op_templates);
+		return;
+	}
+
+use_odtr_fallback:
+	spinand->op_templates = &spinand->odtr_op_templates;
+	spinand->bus_iface = ODTR;
+	spinand_reset_max_freq_ops(spinand, &spinand->odtr_op_templates);
+}
+
 static int spinand_probe(struct spi_mem *mem)
 {
 	struct spinand_device *spinand;
@@ -2073,6 +2336,20 @@ static int spinand_probe(struct spi_mem *mem)
 	if (ret)
 		return ret;
 
+	/*
+	 * Negotiate the best controller operating point before creating dirmaps
+	 * so the validated frequency is available at dirmap construction time.
+	 */
+	spinand_optimize_controller(spinand, mem);
+
+	ret = spinand_create_dirmaps(spinand);
+	if (ret) {
+		dev_err(&mem->spi->dev,
+			"Failed to create direct mappings for read/write operations (err = %d)\n",
+			ret);
+		goto err_spinand_cleanup;
+	}
+
 	ret = mtd_device_register(mtd, NULL, 0);
 	if (ret)
 		goto err_spinand_cleanup;
diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h
index 5f4c00ae72a7..31e3a0dfe8a4 100644
--- a/include/linux/mtd/spinand.h
+++ b/include/linux/mtd/spinand.h
@@ -792,8 +792,22 @@ struct spinand_device {
 	struct spinand_mem_ops *op_templates;
 	enum spinand_bus_interface bus_iface;
 
+	/*
+	 * Full read variant list (ODTR and SSDR ops together), saved when ODTR
+	 * templates are valid. Used by spinand_optimize_controller() for ranked
+	 * fallback when the pre-selected variant cannot be controller-optimized.
+	 */
+	const struct spinand_op_variants *all_read_variants;
+
+	/* Continuous-read counterpart of all_read_variants, if any. */
+	const struct spinand_op_variants *all_cont_read_variants;
+
 	struct spinand_dirmap *dirmaps;
 
+	/* Persistent op templates updated by execute_tuning with validated speed. */
+	struct spi_mem_op max_read_op;
+	struct spi_mem_op max_write_op;
+
 	int (*select_target)(struct spinand_device *spinand,
 			     unsigned int target);
 	unsigned int cur_target;
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:14 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:14 +0530
Subject: [PATCH v8 10/18] spi: cadence-quadspi: refactor direct read path for PHY support
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-11-s-k6@ti.com>

Extract the DMA transfer code from cqspi_direct_read_execute() into a
new cqspi_direct_read_dma() helper. Add cqspi_memcpy_fromio() to handle
non-DMA transfers, using 2-byte-aligned I/O reads for 8D-8D-8D mode to
satisfy the minimum 16-bit AHB transaction width required by the Cadence
OSPI controller in that mode.

Change cqspi_direct_read_execute() to take the full spi_mem_op and
post_config_max_speed_hz instead of separate buf/from/len parameters,
matching the interface needed by the PHY-aware version in the following
patch. Thread post_config_max_speed_hz from cqspi_mem_process() through
cqspi_read().

Transfers shorter than CQSPI_PHY_MIN_DIRECT_READ_LEN bytes always use
the memcpy path; longer transfers use DMA when available.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/spi/spi-cadence-quadspi.c | 75 ++++++++++++++++++++++++++-----
 1 file changed, 65 insertions(+), 10 deletions(-)

diff --git a/drivers/spi/spi-cadence-quadspi.c b/drivers/spi/spi-cadence-quadspi.c
index a1aaba7c1a7e..99a0ec0e769c 100644
--- a/drivers/spi/spi-cadence-quadspi.c
+++ b/drivers/spi/spi-cadence-quadspi.c
@@ -30,6 +30,7 @@
 #include <linux/spi/spi.h>
 #include <linux/spi/spi-mem.h>
 #include <linux/timer.h>
+#include <linux/unaligned.h>
 
 #define CQSPI_NAME			"cadence-qspi"
 #define CQSPI_MAX_CHIPSELECT		4
@@ -154,6 +155,8 @@ struct cqspi_driver_platdata {
 #define CQSPI_READ_TIMEOUT_MS			10
 #define CQSPI_BUSYWAIT_TIMEOUT_US		500
 #define CQSPI_DLL_TIMEOUT_US			300
+/* Minimum transfer length to use DMA for direct reads */
+#define CQSPI_PHY_MIN_DIRECT_READ_LEN		17
 
 /* Runtime_pm autosuspend delay */
 #define CQSPI_AUTOSUSPEND_TIMEOUT		2000
@@ -1528,8 +1531,8 @@ static void cqspi_rx_dma_callback(void *param)
 	complete(&cqspi->rx_dma_complete);
 }
 
-static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
-				     u_char *buf, loff_t from, size_t len)
+static int cqspi_direct_read_dma(struct cqspi_flash_pdata *f_pdata, u_char *buf,
+				 loff_t from, size_t len)
 {
 	struct cqspi_st *cqspi = f_pdata->cqspi;
 	struct device *dev = &cqspi->pdev->dev;
@@ -1541,11 +1544,6 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
 	dma_addr_t dma_dst;
 	struct device *ddev;
 
-	if (!cqspi->rx_chan || !virt_addr_valid(buf)) {
-		memcpy_fromio(buf, cqspi->ahb_base + from, len);
-		return 0;
-	}
-
 	ddev = cqspi->rx_chan->device->dev;
 	dma_dst = dma_map_single(ddev, buf, len, DMA_FROM_DEVICE);
 	if (dma_mapping_error(ddev, dma_dst)) {
@@ -1587,8 +1585,63 @@ static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
 	return ret;
 }
 
+static void cqspi_memcpy_fromio(const struct spi_mem_op *op, void *to,
+				const void __iomem *from, size_t count)
+{
+	if (op->data.buswidth == 8 && op->data.dtr) {
+		unsigned long from_addr = (unsigned long)from;
+
+		/* Handle unaligned start with 2-byte read */
+		if (count && !IS_ALIGNED(from_addr, 4)) {
+			put_unaligned_le16(__raw_readw(from), to);
+			from += 2;
+			to += 2;
+			count -= 2;
+		}
+
+		/*
+		 * Use an explicit 32-bit read loop for the bulk; this
+		 * guarantees the minimum 16-bit AHB transaction width required
+		 * in 8D-8D-8D mode on all architectures.
+		 */
+		while (count >= 4) {
+			put_unaligned_le32(__raw_readl(from), to);
+			from += 4;
+			to += 4;
+			count -= 4;
+		}
+
+		/* Handle remaining 2 bytes */
+		if (count)
+			put_unaligned_le16(__raw_readw(from), to);
+
+		return;
+	}
+
+	memcpy_fromio(to, from, count);
+}
+
+static int cqspi_direct_read_execute(struct cqspi_flash_pdata *f_pdata,
+				     const struct spi_mem_op *op,
+				     u32 post_config_max_speed_hz)
+{
+	struct cqspi_st *cqspi = f_pdata->cqspi;
+	loff_t from = op->addr.val;
+	size_t len = op->data.nbytes;
+	u_char *buf = op->data.buf.in;
+
+	if (!cqspi->rx_chan || !virt_addr_valid(buf) ||
+	    len < CQSPI_PHY_MIN_DIRECT_READ_LEN) {
+		cqspi_memcpy_fromio(op, buf, cqspi->ahb_base + from, len);
+		return 0;
+	}
+
+	return cqspi_direct_read_dma(f_pdata, buf, from, len);
+}
+
 static ssize_t cqspi_read(struct cqspi_flash_pdata *f_pdata,
-			  const struct spi_mem_op *op)
+			  const struct spi_mem_op *op,
+			  u32 post_config_max_speed_hz)
 {
 	struct cqspi_st *cqspi = f_pdata->cqspi;
 	const struct cqspi_driver_platdata *ddata = cqspi->ddata;
@@ -1604,7 +1657,8 @@ static ssize_t cqspi_read(struct cqspi_flash_pdata *f_pdata,
 
 	if ((cqspi->use_direct_mode && ((from + len) <= cqspi->ahb_size)) ||
 	    (cqspi->ddata && cqspi->ddata->quirks & CQSPI_NO_INDIRECT_MODE))
-		return cqspi_direct_read_execute(f_pdata, buf, from, len);
+		return cqspi_direct_read_execute(f_pdata, op,
+						 post_config_max_speed_hz);
 
 	if (cqspi->use_dma_read && ddata && ddata->indirect_read_dma &&
 	    virt_addr_valid(buf) && ((dma_align & CQSPI_DMA_UNALIGN) == 0))
@@ -1632,7 +1686,8 @@ static int cqspi_mem_process(struct spi_mem *mem, const struct spi_mem_op *op)
 		     !cqspi->disable_stig_mode))
 			return cqspi_command_read(f_pdata, op);
 
-		return cqspi_read(f_pdata, op);
+		return cqspi_read(f_pdata, op,
+				  mem->spi->post_config_max_speed_hz);
 	}
 
 	if (!op->addr.nbytes || !op->data.buf.out)
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:21 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:21 +0530
Subject: [PATCH v8 17/18] mtd: spi-nor: extract read op template construction into helper
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-18-s-k6@ti.com>

From: Pratyush Yadav <pratyush at kernel.org>

Extract spi_nor_spimem_get_read_op() from the duplicated op-template
construction in spi_nor_spimem_read_data() and
spi_nor_create_read_dirmap().

Using data.nbytes = 2 as a non-zero placeholder lets
spi_nor_spimem_setup_op() configure the data buswidth without a separate
override; callers replace data.nbytes with the actual transfer length
before use. No functional change.

Signed-off-by: Pratyush Yadav <pratyush at kernel.org>
Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/mtd/spi-nor/core.c | 66 +++++++++++++++++++++-----------------
 1 file changed, 36 insertions(+), 30 deletions(-)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index ccf4396cdcd0..b683c077a233 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -188,6 +188,37 @@ static int spi_nor_controller_ops_erase(struct spi_nor *nor, loff_t offs)
 	return nor->controller_ops->erase(nor, offs);
 }
 
+/**
+ * spi_nor_spimem_get_read_op() - build a configured read op template
+ * @nor:	the spi-nor device
+ *
+ * Returns a spi_mem_op with the command, address format, dummy cycles,
+ * and data buswidth configured for @nor. For direct reads, the caller
+ * must fill in addr.val, data.nbytes, and data.buf.in before use.
+ */
+static struct spi_mem_op spi_nor_spimem_get_read_op(struct spi_nor *nor)
+{
+	/*
+	 * data.nbytes must be non-zero so spi_nor_spimem_setup_op()
+	 * configures the data buswidth; callers replace it with the
+	 * actual transfer length.
+	 */
+	struct spi_mem_op op =
+		SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0),
+			   SPI_MEM_OP_ADDR(nor->addr_nbytes, 0, 0),
+			   SPI_MEM_OP_DUMMY(nor->read_dummy, 0),
+			   SPI_MEM_OP_DATA_IN(2, NULL, 0));
+
+	spi_nor_spimem_setup_op(nor, &op, nor->read_proto);
+
+	/* convert the dummy cycles to the number of bytes */
+	op.dummy.nbytes = (nor->read_dummy * op.dummy.buswidth) / 8;
+	if (spi_nor_protocol_is_dtr(nor->read_proto))
+		op.dummy.nbytes *= 2;
+
+	return op;
+}
+
 /**
  * spi_nor_spimem_read_data() - read data from flash's memory region via
  *                              spi-mem
@@ -201,21 +232,14 @@ static int spi_nor_controller_ops_erase(struct spi_nor *nor, loff_t offs)
 static ssize_t spi_nor_spimem_read_data(struct spi_nor *nor, loff_t from,
 					size_t len, u8 *buf)
 {
-	struct spi_mem_op op =
-		SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0),
-			   SPI_MEM_OP_ADDR(nor->addr_nbytes, from, 0),
-			   SPI_MEM_OP_DUMMY(nor->read_dummy, 0),
-			   SPI_MEM_OP_DATA_IN(len, buf, 0));
+	struct spi_mem_op op = spi_nor_spimem_get_read_op(nor);
 	bool usebouncebuf;
 	ssize_t nbytes;
 	int error;
 
-	spi_nor_spimem_setup_op(nor, &op, nor->read_proto);
-
-	/* convert the dummy cycles to the number of bytes */
-	op.dummy.nbytes = (nor->read_dummy * op.dummy.buswidth) / 8;
-	if (spi_nor_protocol_is_dtr(nor->read_proto))
-		op.dummy.nbytes *= 2;
+	op.addr.val = from;
+	op.data.nbytes = len;
+	op.data.buf.in = buf;
 
 	usebouncebuf = spi_nor_spimem_bounce(nor, &op);
 
@@ -3712,28 +3736,10 @@ static int spi_nor_create_read_dirmap(struct spi_nor *nor)
 {
 	struct spi_mem_dirmap_info info = {
 		.op_tmpl = &info.primary_op_tmpl,
-		.primary_op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0),
-					      SPI_MEM_OP_ADDR(nor->addr_nbytes, 0, 0),
-					      SPI_MEM_OP_DUMMY(nor->read_dummy, 0),
-					      SPI_MEM_OP_DATA_IN(0, NULL, 0)),
+		.primary_op_tmpl = spi_nor_spimem_get_read_op(nor),
 		.offset = 0,
 		.length = nor->params->size,
 	};
-	struct spi_mem_op *op = info.op_tmpl;
-
-	spi_nor_spimem_setup_op(nor, op, nor->read_proto);
-
-	/* convert the dummy cycles to the number of bytes */
-	op->dummy.nbytes = (nor->read_dummy * op->dummy.buswidth) / 8;
-	if (spi_nor_protocol_is_dtr(nor->read_proto))
-		op->dummy.nbytes *= 2;
-
-	/*
-	 * Since spi_nor_spimem_setup_op() only sets buswidth when the number
-	 * of data bytes is non-zero, the data buswidth won't be set here. So,
-	 * do it explicitly.
-	 */
-	op->data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto);
 
 	nor->dirmap.rdesc = devm_spi_mem_dirmap_create(nor->dev, nor->spimem,
 						       &info);
-- 
2.34.1



From s-k6 at ti.com  Fri Aug 21 04:48:22 2026
From: s-k6 at ti.com (Santhosh Kumar K)
Date: Fri, 21 Aug 2026 17:18:22 +0530
Subject: [PATCH v8 18/18] mtd: spi-nor: run controller optimization before dirmap creation
In-Reply-To: <20260821114822.4040338-1-s-k6@ti.com>
References: <20260821114822.4040338-1-s-k6@ti.com>
Message-ID: <20260821114822.4040338-19-s-k6@ti.com>

Run controller optimization in spi_nor_probe() before creating dirmaps
so the validated frequency is available at dirmap construction time.

Store the configured read op template in nor->max_read_op and pass it
to spi_mem_execute_tuning(). On success the controller sets
max_read_op.max_freq to the calibrated rate.
spi_nor_spimem_get_read_op() propagates nor->max_read_op.max_freq into
every op it returns, so the validated frequency flows automatically into
the dirmap template and into regular read ops.

Optimization failure is non-fatal; the device operates at the
conservative base rate.

Signed-off-by: Santhosh Kumar K <s-k6 at ti.com>
---
 drivers/mtd/spi-nor/core.c  | 14 ++++++++++++++
 include/linux/mtd/spi-nor.h |  3 +++
 2 files changed, 17 insertions(+)

diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index b683c077a233..2efcdc7f637f 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -216,6 +216,9 @@ static struct spi_mem_op spi_nor_spimem_get_read_op(struct spi_nor *nor)
 	if (spi_nor_protocol_is_dtr(nor->read_proto))
 		op.dummy.nbytes *= 2;
 
+	/* Propagate the validated frequency; zero before tuning. */
+	op.max_freq = nor->max_read_op.max_freq;
+
 	return op;
 }
 
@@ -3843,6 +3846,17 @@ static int spi_nor_probe(struct spi_mem *spimem)
 			return -ENOMEM;
 	}
 
+	/*
+	 * Populate the persistent template and run PHY tuning before dirmap
+	 * creation so the validated frequency feeds into the dirmap op.
+	 * Tuning failure is non-fatal; the device operates at base speed.
+	 */
+	nor->max_read_op = spi_nor_spimem_get_read_op(nor);
+
+	ret = spi_mem_execute_tuning(spimem, &nor->max_read_op, NULL);
+	if (ret && ret != -EOPNOTSUPP)
+		dev_dbg(dev, "Controller optimization failed: %d\n", ret);
+
 	ret = spi_nor_create_read_dirmap(nor);
 	if (ret)
 		return ret;
diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h
index 4b92494827b1..ab498a50f15f 100644
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -422,6 +422,9 @@ struct spi_nor {
 		struct spi_mem_dirmap_desc *wdesc;
 	} dirmap;
 
+	/* Persistent op template updated by execute_tuning with validated speed. */
+	struct spi_mem_op max_read_op;
+
 	void *priv;
 };
 
-- 
2.34.1



From triet.hoang.dev at gmail.com  Fri Aug 21 21:59:56 2026
From: triet.hoang.dev at gmail.com (Triet Hoang)
Date: Sat, 22 Aug 2026 04:59:56 +0000
Subject: [PATCH] mtd: rawnand: stm32_fmc2: Convert to DEFINE_SIMPLE_DEV_PM_OPS()
Message-ID: <20260822045957.12511-1-triet.hoang.dev@gmail.com>

Convert the deprecated SIMPLE_DEV_PM_OPS to DEFINE_SIMPLE_DEV_PM_OPS
and pm_sleep_ptr().

This lets us drop the __maybe_unused annotations from its suspend 
and resume callbacks, also reduces kernel size in case CONFIG_PM 
or CONFIG_PM_SLEEP is disabled.

Signed-off-by: Triet Hoang <triet.hoang.dev at gmail.com>
---
 drivers/mtd/nand/raw/stm32_fmc2_nand.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/mtd/nand/raw/stm32_fmc2_nand.c b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
index c08d6b176372..9f42f8ab3212 100644
--- a/drivers/mtd/nand/raw/stm32_fmc2_nand.c
+++ b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
@@ -2076,7 +2076,7 @@ static void stm32_fmc2_nfc_remove(struct platform_device *pdev)
 	stm32_fmc2_nfc_wp_enable(nand);
 }
 
-static int __maybe_unused stm32_fmc2_nfc_suspend(struct device *dev)
+static int stm32_fmc2_nfc_suspend(struct device *dev)
 {
 	struct stm32_fmc2_nfc *nfc = dev_get_drvdata(dev);
 	struct stm32_fmc2_nand *nand = &nfc->nand;
@@ -2090,7 +2090,7 @@ static int __maybe_unused stm32_fmc2_nfc_suspend(struct device *dev)
 	return 0;
 }
 
-static int __maybe_unused stm32_fmc2_nfc_resume(struct device *dev)
+static int stm32_fmc2_nfc_resume(struct device *dev)
 {
 	struct stm32_fmc2_nfc *nfc = dev_get_drvdata(dev);
 	struct stm32_fmc2_nand *nand = &nfc->nand;
@@ -2118,7 +2118,7 @@ static int __maybe_unused stm32_fmc2_nfc_resume(struct device *dev)
 	return 0;
 }
 
-static SIMPLE_DEV_PM_OPS(stm32_fmc2_nfc_pm_ops, stm32_fmc2_nfc_suspend,
+static DEFINE_SIMPLE_DEV_PM_OPS(stm32_fmc2_nfc_pm_ops, stm32_fmc2_nfc_suspend,
 			 stm32_fmc2_nfc_resume);
 
 static const struct stm32_fmc2_nfc_data stm32_fmc2_nfc_mp1_data = {
@@ -2153,7 +2153,7 @@ static struct platform_driver stm32_fmc2_nfc_driver = {
 	.driver	= {
 		.name = "stm32_fmc2_nfc",
 		.of_match_table = stm32_fmc2_nfc_match,
-		.pm = &stm32_fmc2_nfc_pm_ops,
+		.pm = pm_sleep_ptr(&stm32_fmc2_nfc_pm_ops),
 	},
 };
 module_platform_driver(stm32_fmc2_nfc_driver);
-- 
2.53.0



