249 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 ATMEL_SR_GLOBAL_PROTECT_MASK GENMASK(5, 2)12 13/*14 * The Atmel AT25FS010/AT25FS040 parts have some weird configuration for the15 * block protection bits. We don't support them. But legacy behavior in linux16 * is to unlock the whole flash array on startup. Therefore, we have to support17 * exactly this operation.18 */19static int at25fs_nor_lock(struct spi_nor *nor, loff_t ofs, u64 len)20{21 return -EOPNOTSUPP;22}23 24static int at25fs_nor_unlock(struct spi_nor *nor, loff_t ofs, u64 len)25{26 int ret;27 28 /* We only support unlocking the whole flash array */29 if (ofs || len != nor->params->size)30 return -EINVAL;31 32 /* Write 0x00 to the status register to disable write protection */33 ret = spi_nor_write_sr_and_check(nor, 0);34 if (ret)35 dev_dbg(nor->dev, "unable to clear BP bits, WP# asserted?\n");36 37 return ret;38}39 40static int at25fs_nor_is_locked(struct spi_nor *nor, loff_t ofs, u64 len)41{42 return -EOPNOTSUPP;43}44 45static const struct spi_nor_locking_ops at25fs_nor_locking_ops = {46 .lock = at25fs_nor_lock,47 .unlock = at25fs_nor_unlock,48 .is_locked = at25fs_nor_is_locked,49};50 51static int at25fs_nor_late_init(struct spi_nor *nor)52{53 nor->params->locking_ops = &at25fs_nor_locking_ops;54 55 return 0;56}57 58static const struct spi_nor_fixups at25fs_nor_fixups = {59 .late_init = at25fs_nor_late_init,60};61 62/**63 * atmel_nor_set_global_protection - Do a Global Protect or Unprotect command64 * @nor: pointer to 'struct spi_nor'65 * @ofs: offset in bytes66 * @len: len in bytes67 * @is_protect: if true do a Global Protect otherwise it is a Global Unprotect68 *69 * Return: 0 on success, -error otherwise.70 */71static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs,72 u64 len, bool is_protect)73{74 int ret;75 u8 sr;76 77 /* We only support locking the whole flash array */78 if (ofs || len != nor->params->size)79 return -EINVAL;80 81 ret = spi_nor_read_sr(nor, nor->bouncebuf);82 if (ret)83 return ret;84 85 sr = nor->bouncebuf[0];86 87 /* SRWD bit needs to be cleared, otherwise the protection doesn't change */88 if (sr & SR_SRWD) {89 sr &= ~SR_SRWD;90 ret = spi_nor_write_sr_and_check(nor, sr);91 if (ret) {92 dev_dbg(nor->dev, "unable to clear SRWD bit, WP# asserted?\n");93 return ret;94 }95 }96 97 if (is_protect) {98 sr |= ATMEL_SR_GLOBAL_PROTECT_MASK;99 /*100 * Set the SRWD bit again as soon as we are protecting101 * anything. This will ensure that the WP# pin is working102 * correctly. By doing this we also behave the same as103 * spi_nor_sr_lock(), which sets SRWD if any block protection104 * is active.105 */106 sr |= SR_SRWD;107 } else {108 sr &= ~ATMEL_SR_GLOBAL_PROTECT_MASK;109 }110 111 nor->bouncebuf[0] = sr;112 113 /*114 * We cannot use the spi_nor_write_sr_and_check() because this command115 * isn't really setting any bits, instead it is an pseudo command for116 * "Global Unprotect" or "Global Protect"117 */118 return spi_nor_write_sr(nor, nor->bouncebuf, 1);119}120 121static int atmel_nor_global_protect(struct spi_nor *nor, loff_t ofs, u64 len)122{123 return atmel_nor_set_global_protection(nor, ofs, len, true);124}125 126static int atmel_nor_global_unprotect(struct spi_nor *nor, loff_t ofs, u64 len)127{128 return atmel_nor_set_global_protection(nor, ofs, len, false);129}130 131static int atmel_nor_is_global_protected(struct spi_nor *nor, loff_t ofs,132 u64 len)133{134 int ret;135 136 if (ofs >= nor->params->size || (ofs + len) > nor->params->size)137 return -EINVAL;138 139 ret = spi_nor_read_sr(nor, nor->bouncebuf);140 if (ret)141 return ret;142 143 return ((nor->bouncebuf[0] & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK);144}145 146static const struct spi_nor_locking_ops atmel_nor_global_protection_ops = {147 .lock = atmel_nor_global_protect,148 .unlock = atmel_nor_global_unprotect,149 .is_locked = atmel_nor_is_global_protected,150};151 152static int atmel_nor_global_protection_late_init(struct spi_nor *nor)153{154 nor->params->locking_ops = &atmel_nor_global_protection_ops;155 156 return 0;157}158 159static const struct spi_nor_fixups atmel_nor_global_protection_fixups = {160 .late_init = atmel_nor_global_protection_late_init,161};162 163static const struct flash_info atmel_nor_parts[] = {164 {165 .id = SNOR_ID(0x1f, 0x04, 0x00),166 .name = "at26f004",167 .size = SZ_512K,168 .no_sfdp_flags = SECT_4K,169 }, {170 .id = SNOR_ID(0x1f, 0x25, 0x00),171 .name = "at45db081d",172 .size = SZ_1M,173 .no_sfdp_flags = SECT_4K,174 }, {175 .id = SNOR_ID(0x1f, 0x42, 0x16),176 .name = "at25sl321",177 .size = SZ_4M,178 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,179 }, {180 .id = SNOR_ID(0x1f, 0x44, 0x01),181 .name = "at25df041a",182 .size = SZ_512K,183 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,184 .no_sfdp_flags = SECT_4K,185 .fixups = &atmel_nor_global_protection_fixups,186 }, {187 .id = SNOR_ID(0x1f, 0x45, 0x01),188 .name = "at26df081a",189 .size = SZ_1M,190 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,191 .no_sfdp_flags = SECT_4K,192 .fixups = &atmel_nor_global_protection_fixups193 }, {194 .id = SNOR_ID(0x1f, 0x46, 0x01),195 .name = "at26df161a",196 .size = SZ_2M,197 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,198 .no_sfdp_flags = SECT_4K,199 .fixups = &atmel_nor_global_protection_fixups200 }, {201 .id = SNOR_ID(0x1f, 0x47, 0x00),202 .name = "at25df321",203 .size = SZ_4M,204 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,205 .no_sfdp_flags = SECT_4K,206 .fixups = &atmel_nor_global_protection_fixups207 }, {208 .id = SNOR_ID(0x1f, 0x47, 0x01),209 .name = "at25df321a",210 .size = SZ_4M,211 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,212 .no_sfdp_flags = SECT_4K,213 .fixups = &atmel_nor_global_protection_fixups214 }, {215 .id = SNOR_ID(0x1f, 0x47, 0x08),216 .name = "at25ff321a",217 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,218 .fixups = &atmel_nor_global_protection_fixups219 }, {220 .id = SNOR_ID(0x1f, 0x48, 0x00),221 .name = "at25df641",222 .size = SZ_8M,223 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,224 .no_sfdp_flags = SECT_4K,225 .fixups = &atmel_nor_global_protection_fixups226 }, {227 .id = SNOR_ID(0x1f, 0x66, 0x01),228 .name = "at25fs010",229 .sector_size = SZ_32K,230 .size = SZ_128K,231 .flags = SPI_NOR_HAS_LOCK,232 .no_sfdp_flags = SECT_4K,233 .fixups = &at25fs_nor_fixups234 }, {235 .id = SNOR_ID(0x1f, 0x66, 0x04),236 .name = "at25fs040",237 .size = SZ_512K,238 .flags = SPI_NOR_HAS_LOCK,239 .no_sfdp_flags = SECT_4K,240 .fixups = &at25fs_nor_fixups241 },242};243 244const struct spi_nor_manufacturer spi_nor_atmel = {245 .name = "atmel",246 .parts = atmel_nor_parts,247 .nparts = ARRAY_SIZE(atmel_nor_parts),248};249