1 // SPDX-License-Identifier: GPL-2.0 2 /* 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 the 15 * block protection bits. We don't support them. But legacy behavior in linux 16 * is to unlock the whole flash array on startup. Therefore, we have to support 17 * exactly this operation. 18 */ 19 static int at25fs_nor_lock(struct spi_nor *nor, loff_t ofs, uint64_t len) 20 { 21 return -EOPNOTSUPP; 22 } 23 24 static int at25fs_nor_unlock(struct spi_nor *nor, loff_t ofs, uint64_t 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 40 static int at25fs_nor_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len) 41 { 42 return -EOPNOTSUPP; 43 } 44 45 static 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 51 static 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 58 static 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 command 64 * @nor: pointer to 'struct spi_nor' 65 * @ofs: offset in bytes 66 * @len: len in bytes 67 * @is_protect: if true do a Global Protect otherwise it is a Global Unprotect 68 * 69 * Return: 0 on success, -error otherwise. 70 */ 71 static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs, 72 uint64_t 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 protecting 101 * anything. This will ensure that the WP# pin is working 102 * correctly. By doing this we also behave the same as 103 * spi_nor_sr_lock(), which sets SRWD if any block protection 104 * 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 command 115 * isn't really setting any bits, instead it is an pseudo command for 116 * "Global Unprotect" or "Global Protect" 117 */ 118 return spi_nor_write_sr(nor, nor->bouncebuf, 1); 119 } 120 121 static int atmel_nor_global_protect(struct spi_nor *nor, loff_t ofs, 122 uint64_t len) 123 { 124 return atmel_nor_set_global_protection(nor, ofs, len, true); 125 } 126 127 static int atmel_nor_global_unprotect(struct spi_nor *nor, loff_t ofs, 128 uint64_t len) 129 { 130 return atmel_nor_set_global_protection(nor, ofs, len, false); 131 } 132 133 static int atmel_nor_is_global_protected(struct spi_nor *nor, loff_t ofs, 134 uint64_t len) 135 { 136 int ret; 137 138 if (ofs >= nor->params->size || (ofs + len) > nor->params->size) 139 return -EINVAL; 140 141 ret = spi_nor_read_sr(nor, nor->bouncebuf); 142 if (ret) 143 return ret; 144 145 return ((nor->bouncebuf[0] & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK); 146 } 147 148 static const struct spi_nor_locking_ops atmel_nor_global_protection_ops = { 149 .lock = atmel_nor_global_protect, 150 .unlock = atmel_nor_global_unprotect, 151 .is_locked = atmel_nor_is_global_protected, 152 }; 153 154 static int atmel_nor_global_protection_late_init(struct spi_nor *nor) 155 { 156 nor->params->locking_ops = &atmel_nor_global_protection_ops; 157 158 return 0; 159 } 160 161 static const struct spi_nor_fixups atmel_nor_global_protection_fixups = { 162 .late_init = atmel_nor_global_protection_late_init, 163 }; 164 165 static const struct flash_info atmel_nor_parts[] = { 166 { 167 .id = SNOR_ID(0x1f, 0x04, 0x00), 168 .name = "at26f004", 169 .size = SZ_512K, 170 .no_sfdp_flags = SECT_4K, 171 }, { 172 .id = SNOR_ID(0x1f, 0x25, 0x00), 173 .name = "at45db081d", 174 .size = SZ_1M, 175 .no_sfdp_flags = SECT_4K, 176 }, { 177 .id = SNOR_ID(0x1f, 0x42, 0x16), 178 .name = "at25sl321", 179 .size = SZ_4M, 180 .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ, 181 }, { 182 .id = SNOR_ID(0x1f, 0x44, 0x01), 183 .name = "at25df041a", 184 .size = SZ_512K, 185 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, 186 .no_sfdp_flags = SECT_4K, 187 .fixups = &atmel_nor_global_protection_fixups, 188 }, { 189 .id = SNOR_ID(0x1f, 0x45, 0x01), 190 .name = "at26df081a", 191 .size = SZ_1M, 192 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, 193 .no_sfdp_flags = SECT_4K, 194 .fixups = &atmel_nor_global_protection_fixups 195 }, { 196 .id = SNOR_ID(0x1f, 0x46, 0x01), 197 .name = "at26df161a", 198 .size = SZ_2M, 199 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, 200 .no_sfdp_flags = SECT_4K, 201 .fixups = &atmel_nor_global_protection_fixups 202 }, { 203 .id = SNOR_ID(0x1f, 0x47, 0x00), 204 .name = "at25df321", 205 .size = SZ_4M, 206 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, 207 .no_sfdp_flags = SECT_4K, 208 .fixups = &atmel_nor_global_protection_fixups 209 }, { 210 .id = SNOR_ID(0x1f, 0x47, 0x01), 211 .name = "at25df321a", 212 .size = SZ_4M, 213 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, 214 .no_sfdp_flags = SECT_4K, 215 .fixups = &atmel_nor_global_protection_fixups 216 }, { 217 .id = SNOR_ID(0x1f, 0x47, 0x08), 218 .name = "at25ff321a", 219 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, 220 .fixups = &atmel_nor_global_protection_fixups 221 }, { 222 .id = SNOR_ID(0x1f, 0x48, 0x00), 223 .name = "at25df641", 224 .size = SZ_8M, 225 .flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE, 226 .no_sfdp_flags = SECT_4K, 227 .fixups = &atmel_nor_global_protection_fixups 228 }, { 229 .id = SNOR_ID(0x1f, 0x66, 0x01), 230 .name = "at25fs010", 231 .sector_size = SZ_32K, 232 .size = SZ_128K, 233 .flags = SPI_NOR_HAS_LOCK, 234 .no_sfdp_flags = SECT_4K, 235 .fixups = &at25fs_nor_fixups 236 }, { 237 .id = SNOR_ID(0x1f, 0x66, 0x04), 238 .name = "at25fs040", 239 .size = SZ_512K, 240 .flags = SPI_NOR_HAS_LOCK, 241 .no_sfdp_flags = SECT_4K, 242 .fixups = &at25fs_nor_fixups 243 }, 244 }; 245 246 const struct spi_nor_manufacturer spi_nor_atmel = { 247 .name = "atmel", 248 .parts = atmel_nor_parts, 249 .nparts = ARRAY_SIZE(atmel_nor_parts), 250 }; 251