362 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (C) 2005, Intec Automation Inc.4 * Copyright (C) 2014, Freescale Semiconductor, Inc.5 */6 7#include <linux/mtd/spi-nor.h>8 9#include "core.h"10 11#define WINBOND_NOR_OP_RDEAR 0xc8 /* Read Extended Address Register */12#define WINBOND_NOR_OP_WREAR 0xc5 /* Write Extended Address Register */13 14#define WINBOND_NOR_WREAR_OP(buf) \15 SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_WREAR, 0), \16 SPI_MEM_OP_NO_ADDR, \17 SPI_MEM_OP_NO_DUMMY, \18 SPI_MEM_OP_DATA_OUT(1, buf, 0))19 20static int21w25q128_post_bfpt_fixups(struct spi_nor *nor,22 const struct sfdp_parameter_header *bfpt_header,23 const struct sfdp_bfpt *bfpt)24{25 /*26 * Zetta ZD25Q128C is a clone of the Winbond device. But the encoded27 * size is really wrong. It seems that they confused Mbit with MiB.28 * Thus the flash is discovered as a 2MiB device.29 */30 if (bfpt_header->major == SFDP_JESD216_MAJOR &&31 bfpt_header->minor == SFDP_JESD216_MINOR &&32 nor->params->size == SZ_2M &&33 nor->params->erase_map.regions[0].size == SZ_2M) {34 nor->params->size = SZ_16M;35 nor->params->erase_map.regions[0].size = SZ_16M;36 }37 38 return 0;39}40 41static const struct spi_nor_fixups w25q128_fixups = {42 .post_bfpt = w25q128_post_bfpt_fixups,43};44 45static int46w25q256_post_bfpt_fixups(struct spi_nor *nor,47 const struct sfdp_parameter_header *bfpt_header,48 const struct sfdp_bfpt *bfpt)49{50 /*51 * W25Q256JV supports 4B opcodes but W25Q256FV does not.52 * Unfortunately, Winbond has re-used the same JEDEC ID for both53 * variants which prevents us from defining a new entry in the parts54 * table.55 * To differentiate between W25Q256JV and W25Q256FV check SFDP header56 * version: only JV has JESD216A compliant structure (version 5).57 */58 if (bfpt_header->major == SFDP_JESD216_MAJOR &&59 bfpt_header->minor == SFDP_JESD216A_MINOR)60 nor->flags |= SNOR_F_4B_OPCODES;61 62 return 0;63}64 65static const struct spi_nor_fixups w25q256_fixups = {66 .post_bfpt = w25q256_post_bfpt_fixups,67};68 69static const struct flash_info winbond_nor_parts[] = {70 {71 .id = SNOR_ID(0xef, 0x30, 0x10),72 .name = "w25x05",73 .size = SZ_64K,74 .no_sfdp_flags = SECT_4K,75 }, {76 .id = SNOR_ID(0xef, 0x30, 0x11),77 .name = "w25x10",78 .size = SZ_128K,79 .no_sfdp_flags = SECT_4K,80 }, {81 .id = SNOR_ID(0xef, 0x30, 0x12),82 .name = "w25x20",83 .size = SZ_256K,84 .no_sfdp_flags = SECT_4K,85 }, {86 .id = SNOR_ID(0xef, 0x30, 0x13),87 .name = "w25x40",88 .size = SZ_512K,89 .no_sfdp_flags = SECT_4K,90 }, {91 .id = SNOR_ID(0xef, 0x30, 0x14),92 .name = "w25x80",93 .size = SZ_1M,94 .no_sfdp_flags = SECT_4K,95 }, {96 .id = SNOR_ID(0xef, 0x30, 0x15),97 .name = "w25x16",98 .size = SZ_2M,99 .no_sfdp_flags = SECT_4K,100 }, {101 .id = SNOR_ID(0xef, 0x30, 0x16),102 .name = "w25x32",103 .size = SZ_4M,104 .no_sfdp_flags = SECT_4K,105 }, {106 .id = SNOR_ID(0xef, 0x30, 0x17),107 .name = "w25x64",108 .size = SZ_8M,109 .no_sfdp_flags = SECT_4K,110 }, {111 .id = SNOR_ID(0xef, 0x40, 0x12),112 .name = "w25q20cl",113 .size = SZ_256K,114 .no_sfdp_flags = SECT_4K,115 }, {116 .id = SNOR_ID(0xef, 0x40, 0x14),117 .name = "w25q80bl",118 .size = SZ_1M,119 .no_sfdp_flags = SECT_4K,120 }, {121 .id = SNOR_ID(0xef, 0x40, 0x16),122 .name = "w25q32",123 .size = SZ_4M,124 .no_sfdp_flags = SECT_4K,125 }, {126 .id = SNOR_ID(0xef, 0x40, 0x17),127 .name = "w25q64",128 .size = SZ_8M,129 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,130 }, {131 .id = SNOR_ID(0xef, 0x40, 0x18),132 .name = "w25q128",133 .size = SZ_16M,134 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,135 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,136 .fixups = &w25q128_fixups,137 }, {138 .id = SNOR_ID(0xef, 0x40, 0x19),139 .name = "w25q256",140 .size = SZ_32M,141 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,142 .fixups = &w25q256_fixups,143 }, {144 .id = SNOR_ID(0xef, 0x40, 0x20),145 .name = "w25q512jvq",146 .size = SZ_64M,147 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,148 }, {149 .id = SNOR_ID(0xef, 0x50, 0x12),150 .name = "w25q20bw",151 .size = SZ_256K,152 .no_sfdp_flags = SECT_4K,153 }, {154 .id = SNOR_ID(0xef, 0x50, 0x14),155 .name = "w25q80",156 .size = SZ_1M,157 .no_sfdp_flags = SECT_4K,158 }, {159 .id = SNOR_ID(0xef, 0x60, 0x12),160 .name = "w25q20ew",161 .size = SZ_256K,162 .no_sfdp_flags = SECT_4K,163 }, {164 .id = SNOR_ID(0xef, 0x60, 0x15),165 .name = "w25q16dw",166 .size = SZ_2M,167 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,168 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,169 }, {170 .id = SNOR_ID(0xef, 0x60, 0x16),171 .name = "w25q32dw",172 .size = SZ_4M,173 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,174 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,175 .otp = SNOR_OTP(256, 3, 0x1000, 0x1000),176 }, {177 .id = SNOR_ID(0xef, 0x60, 0x17),178 .name = "w25q64dw",179 .size = SZ_8M,180 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,181 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,182 }, {183 .id = SNOR_ID(0xef, 0x60, 0x18),184 .name = "w25q128fw",185 .size = SZ_16M,186 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,187 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,188 }, {189 .id = SNOR_ID(0xef, 0x60, 0x19),190 .name = "w25q256jw",191 .size = SZ_32M,192 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,193 }, {194 .id = SNOR_ID(0xef, 0x60, 0x20),195 .name = "w25q512nwq",196 .otp = SNOR_OTP(256, 3, 0x1000, 0x1000),197 }, {198 .id = SNOR_ID(0xef, 0x70, 0x15),199 .name = "w25q16jv-im/jm",200 .size = SZ_2M,201 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,202 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,203 }, {204 .id = SNOR_ID(0xef, 0x70, 0x16),205 .name = "w25q32jv",206 .size = SZ_4M,207 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,208 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,209 }, {210 .id = SNOR_ID(0xef, 0x70, 0x17),211 .name = "w25q64jvm",212 .size = SZ_8M,213 .no_sfdp_flags = SECT_4K,214 }, {215 .id = SNOR_ID(0xef, 0x70, 0x18),216 .name = "w25q128jv",217 .size = SZ_16M,218 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,219 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,220 }, {221 .id = SNOR_ID(0xef, 0x70, 0x19),222 .name = "w25q256jvm",223 }, {224 .id = SNOR_ID(0xef, 0x71, 0x19),225 .name = "w25m512jv",226 .size = SZ_64M,227 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,228 }, {229 .id = SNOR_ID(0xef, 0x80, 0x16),230 .name = "w25q32jwm",231 .size = SZ_4M,232 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,233 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,234 .otp = SNOR_OTP(256, 3, 0x1000, 0x1000),235 }, {236 .id = SNOR_ID(0xef, 0x80, 0x17),237 .name = "w25q64jwm",238 .size = SZ_8M,239 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,240 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,241 }, {242 .id = SNOR_ID(0xef, 0x80, 0x18),243 .name = "w25q128jwm",244 .size = SZ_16M,245 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,246 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,247 }, {248 .id = SNOR_ID(0xef, 0x80, 0x19),249 .name = "w25q256jwm",250 .size = SZ_32M,251 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,252 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,253 }, {254 .id = SNOR_ID(0xef, 0x80, 0x20),255 .name = "w25q512nwm",256 .otp = SNOR_OTP(256, 3, 0x1000, 0x1000),257 },258};259 260/**261 * winbond_nor_write_ear() - Write Extended Address Register.262 * @nor: pointer to 'struct spi_nor'.263 * @ear: value to write to the Extended Address Register.264 *265 * Return: 0 on success, -errno otherwise.266 */267static int winbond_nor_write_ear(struct spi_nor *nor, u8 ear)268{269 int ret;270 271 nor->bouncebuf[0] = ear;272 273 if (nor->spimem) {274 struct spi_mem_op op = WINBOND_NOR_WREAR_OP(nor->bouncebuf);275 276 spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);277 278 ret = spi_mem_exec_op(nor->spimem, &op);279 } else {280 ret = spi_nor_controller_ops_write_reg(nor,281 WINBOND_NOR_OP_WREAR,282 nor->bouncebuf, 1);283 }284 285 if (ret)286 dev_dbg(nor->dev, "error %d writing EAR\n", ret);287 288 return ret;289}290 291/**292 * winbond_nor_set_4byte_addr_mode() - Set 4-byte address mode for Winbond293 * flashes.294 * @nor: pointer to 'struct spi_nor'.295 * @enable: true to enter the 4-byte address mode, false to exit the 4-byte296 * address mode.297 *298 * Return: 0 on success, -errno otherwise.299 */300static int winbond_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable)301{302 int ret;303 304 ret = spi_nor_set_4byte_addr_mode_en4b_ex4b(nor, enable);305 if (ret || enable)306 return ret;307 308 /*309 * On Winbond W25Q256FV, leaving 4byte mode causes the Extended Address310 * Register to be set to 1, so all 3-byte-address reads come from the311 * second 16M. We must clear the register to enable normal behavior.312 */313 ret = spi_nor_write_enable(nor);314 if (ret)315 return ret;316 317 ret = winbond_nor_write_ear(nor, 0);318 if (ret)319 return ret;320 321 return spi_nor_write_disable(nor);322}323 324static const struct spi_nor_otp_ops winbond_nor_otp_ops = {325 .read = spi_nor_otp_read_secr,326 .write = spi_nor_otp_write_secr,327 .erase = spi_nor_otp_erase_secr,328 .lock = spi_nor_otp_lock_sr2,329 .is_locked = spi_nor_otp_is_locked_sr2,330};331 332static int winbond_nor_late_init(struct spi_nor *nor)333{334 struct spi_nor_flash_parameter *params = nor->params;335 336 if (params->otp.org)337 params->otp.ops = &winbond_nor_otp_ops;338 339 /*340 * Winbond seems to require that the Extended Address Register to be set341 * to zero when exiting the 4-Byte Address Mode, at least for W25Q256FV.342 * This requirement is not described in the JESD216 SFDP standard, thus343 * it is Winbond specific. Since we do not know if other Winbond flashes344 * have the same requirement, play safe and overwrite the method parsed345 * from BFPT, if any.346 */347 params->set_4byte_addr_mode = winbond_nor_set_4byte_addr_mode;348 349 return 0;350}351 352static const struct spi_nor_fixups winbond_nor_fixups = {353 .late_init = winbond_nor_late_init,354};355 356const struct spi_nor_manufacturer spi_nor_winbond = {357 .name = "winbond",358 .parts = winbond_nor_parts,359 .nparts = ARRAY_SIZE(winbond_nor_parts),360 .fixups = &winbond_nor_fixups,361};362