xref: /linux/drivers/mtd/spi-nor/atmel.c (revision 06d07429858317ded2db7986113a9e0129cd599b)
1f7242bfcSBoris Brezillon // SPDX-License-Identifier: GPL-2.0
2f7242bfcSBoris Brezillon /*
3f7242bfcSBoris Brezillon  * Copyright (C) 2005, Intec Automation Inc.
4f7242bfcSBoris Brezillon  * Copyright (C) 2014, Freescale Semiconductor, Inc.
5f7242bfcSBoris Brezillon  */
6f7242bfcSBoris Brezillon 
7f7242bfcSBoris Brezillon #include <linux/mtd/spi-nor.h>
8f7242bfcSBoris Brezillon 
9f7242bfcSBoris Brezillon #include "core.h"
10f7242bfcSBoris Brezillon 
1131ad3effSMichael Walle #define ATMEL_SR_GLOBAL_PROTECT_MASK GENMASK(5, 2)
1231ad3effSMichael Walle 
138c174d15SMichael Walle /*
148c174d15SMichael Walle  * The Atmel AT25FS010/AT25FS040 parts have some weird configuration for the
158c174d15SMichael Walle  * block protection bits. We don't support them. But legacy behavior in linux
168c174d15SMichael Walle  * is to unlock the whole flash array on startup. Therefore, we have to support
178c174d15SMichael Walle  * exactly this operation.
188c174d15SMichael Walle  */
at25fs_nor_lock(struct spi_nor * nor,loff_t ofs,u64 len)19*075ede8dSTudor Ambarus static int at25fs_nor_lock(struct spi_nor *nor, loff_t ofs, u64 len)
208c174d15SMichael Walle {
218c174d15SMichael Walle 	return -EOPNOTSUPP;
228c174d15SMichael Walle }
238c174d15SMichael Walle 
at25fs_nor_unlock(struct spi_nor * nor,loff_t ofs,u64 len)24*075ede8dSTudor Ambarus static int at25fs_nor_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
258c174d15SMichael Walle {
268c174d15SMichael Walle 	int ret;
278c174d15SMichael Walle 
288c174d15SMichael Walle 	/* We only support unlocking the whole flash array */
298c174d15SMichael Walle 	if (ofs || len != nor->params->size)
308c174d15SMichael Walle 		return -EINVAL;
318c174d15SMichael Walle 
328c174d15SMichael Walle 	/* Write 0x00 to the status register to disable write protection */
338c174d15SMichael Walle 	ret = spi_nor_write_sr_and_check(nor, 0);
348c174d15SMichael Walle 	if (ret)
358c174d15SMichael Walle 		dev_dbg(nor->dev, "unable to clear BP bits, WP# asserted?\n");
368c174d15SMichael Walle 
378c174d15SMichael Walle 	return ret;
388c174d15SMichael Walle }
398c174d15SMichael Walle 
at25fs_nor_is_locked(struct spi_nor * nor,loff_t ofs,u64 len)40*075ede8dSTudor Ambarus static int at25fs_nor_is_locked(struct spi_nor *nor, loff_t ofs, u64 len)
418c174d15SMichael Walle {
428c174d15SMichael Walle 	return -EOPNOTSUPP;
438c174d15SMichael Walle }
448c174d15SMichael Walle 
452394770bSMichael Walle static const struct spi_nor_locking_ops at25fs_nor_locking_ops = {
462394770bSMichael Walle 	.lock = at25fs_nor_lock,
472394770bSMichael Walle 	.unlock = at25fs_nor_unlock,
482394770bSMichael Walle 	.is_locked = at25fs_nor_is_locked,
498c174d15SMichael Walle };
508c174d15SMichael Walle 
at25fs_nor_late_init(struct spi_nor * nor)51d534fd97STakahiro Kuwano static int at25fs_nor_late_init(struct spi_nor *nor)
528c174d15SMichael Walle {
532394770bSMichael Walle 	nor->params->locking_ops = &at25fs_nor_locking_ops;
54d534fd97STakahiro Kuwano 
55d534fd97STakahiro Kuwano 	return 0;
568c174d15SMichael Walle }
578c174d15SMichael Walle 
582394770bSMichael Walle static const struct spi_nor_fixups at25fs_nor_fixups = {
592394770bSMichael Walle 	.late_init = at25fs_nor_late_init,
608c174d15SMichael Walle };
618c174d15SMichael Walle 
6231ad3effSMichael Walle /**
632394770bSMichael Walle  * atmel_nor_set_global_protection - Do a Global Protect or Unprotect command
6431ad3effSMichael Walle  * @nor:	pointer to 'struct spi_nor'
6531ad3effSMichael Walle  * @ofs:	offset in bytes
6631ad3effSMichael Walle  * @len:	len in bytes
6731ad3effSMichael Walle  * @is_protect:	if true do a Global Protect otherwise it is a Global Unprotect
6831ad3effSMichael Walle  *
6931ad3effSMichael Walle  * Return: 0 on success, -error otherwise.
7031ad3effSMichael Walle  */
atmel_nor_set_global_protection(struct spi_nor * nor,loff_t ofs,u64 len,bool is_protect)712394770bSMichael Walle static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs,
72*075ede8dSTudor Ambarus 					   u64 len, bool is_protect)
7331ad3effSMichael Walle {
7431ad3effSMichael Walle 	int ret;
7531ad3effSMichael Walle 	u8 sr;
7631ad3effSMichael Walle 
7731ad3effSMichael Walle 	/* We only support locking the whole flash array */
7831ad3effSMichael Walle 	if (ofs || len != nor->params->size)
7931ad3effSMichael Walle 		return -EINVAL;
8031ad3effSMichael Walle 
8131ad3effSMichael Walle 	ret = spi_nor_read_sr(nor, nor->bouncebuf);
8231ad3effSMichael Walle 	if (ret)
8331ad3effSMichael Walle 		return ret;
8431ad3effSMichael Walle 
8531ad3effSMichael Walle 	sr = nor->bouncebuf[0];
8631ad3effSMichael Walle 
8731ad3effSMichael Walle 	/* SRWD bit needs to be cleared, otherwise the protection doesn't change */
8831ad3effSMichael Walle 	if (sr & SR_SRWD) {
8931ad3effSMichael Walle 		sr &= ~SR_SRWD;
9031ad3effSMichael Walle 		ret = spi_nor_write_sr_and_check(nor, sr);
9131ad3effSMichael Walle 		if (ret) {
9231ad3effSMichael Walle 			dev_dbg(nor->dev, "unable to clear SRWD bit, WP# asserted?\n");
9331ad3effSMichael Walle 			return ret;
9431ad3effSMichael Walle 		}
9531ad3effSMichael Walle 	}
9631ad3effSMichael Walle 
9731ad3effSMichael Walle 	if (is_protect) {
9831ad3effSMichael Walle 		sr |= ATMEL_SR_GLOBAL_PROTECT_MASK;
9931ad3effSMichael Walle 		/*
10031ad3effSMichael Walle 		 * Set the SRWD bit again as soon as we are protecting
10131ad3effSMichael Walle 		 * anything. This will ensure that the WP# pin is working
10231ad3effSMichael Walle 		 * correctly. By doing this we also behave the same as
10331ad3effSMichael Walle 		 * spi_nor_sr_lock(), which sets SRWD if any block protection
10431ad3effSMichael Walle 		 * is active.
10531ad3effSMichael Walle 		 */
10631ad3effSMichael Walle 		sr |= SR_SRWD;
10731ad3effSMichael Walle 	} else {
10831ad3effSMichael Walle 		sr &= ~ATMEL_SR_GLOBAL_PROTECT_MASK;
10931ad3effSMichael Walle 	}
11031ad3effSMichael Walle 
11131ad3effSMichael Walle 	nor->bouncebuf[0] = sr;
11231ad3effSMichael Walle 
11331ad3effSMichael Walle 	/*
11431ad3effSMichael Walle 	 * We cannot use the spi_nor_write_sr_and_check() because this command
11531ad3effSMichael Walle 	 * isn't really setting any bits, instead it is an pseudo command for
11631ad3effSMichael Walle 	 * "Global Unprotect" or "Global Protect"
11731ad3effSMichael Walle 	 */
11831ad3effSMichael Walle 	return spi_nor_write_sr(nor, nor->bouncebuf, 1);
11931ad3effSMichael Walle }
12031ad3effSMichael Walle 
atmel_nor_global_protect(struct spi_nor * nor,loff_t ofs,u64 len)121*075ede8dSTudor Ambarus static int atmel_nor_global_protect(struct spi_nor *nor, loff_t ofs, u64 len)
12231ad3effSMichael Walle {
1232394770bSMichael Walle 	return atmel_nor_set_global_protection(nor, ofs, len, true);
12431ad3effSMichael Walle }
12531ad3effSMichael Walle 
atmel_nor_global_unprotect(struct spi_nor * nor,loff_t ofs,u64 len)126*075ede8dSTudor Ambarus static int atmel_nor_global_unprotect(struct spi_nor *nor, loff_t ofs, u64 len)
12731ad3effSMichael Walle {
1282394770bSMichael Walle 	return atmel_nor_set_global_protection(nor, ofs, len, false);
12931ad3effSMichael Walle }
13031ad3effSMichael Walle 
atmel_nor_is_global_protected(struct spi_nor * nor,loff_t ofs,u64 len)1312394770bSMichael Walle static int atmel_nor_is_global_protected(struct spi_nor *nor, loff_t ofs,
132*075ede8dSTudor Ambarus 					 u64 len)
13331ad3effSMichael Walle {
13431ad3effSMichael Walle 	int ret;
13531ad3effSMichael Walle 
13631ad3effSMichael Walle 	if (ofs >= nor->params->size || (ofs + len) > nor->params->size)
13731ad3effSMichael Walle 		return -EINVAL;
13831ad3effSMichael Walle 
13931ad3effSMichael Walle 	ret = spi_nor_read_sr(nor, nor->bouncebuf);
14031ad3effSMichael Walle 	if (ret)
14131ad3effSMichael Walle 		return ret;
14231ad3effSMichael Walle 
14331ad3effSMichael Walle 	return ((nor->bouncebuf[0] & ATMEL_SR_GLOBAL_PROTECT_MASK) == ATMEL_SR_GLOBAL_PROTECT_MASK);
14431ad3effSMichael Walle }
14531ad3effSMichael Walle 
1462394770bSMichael Walle static const struct spi_nor_locking_ops atmel_nor_global_protection_ops = {
1472394770bSMichael Walle 	.lock = atmel_nor_global_protect,
1482394770bSMichael Walle 	.unlock = atmel_nor_global_unprotect,
1492394770bSMichael Walle 	.is_locked = atmel_nor_is_global_protected,
15031ad3effSMichael Walle };
15131ad3effSMichael Walle 
atmel_nor_global_protection_late_init(struct spi_nor * nor)152d534fd97STakahiro Kuwano static int atmel_nor_global_protection_late_init(struct spi_nor *nor)
15331ad3effSMichael Walle {
1542394770bSMichael Walle 	nor->params->locking_ops = &atmel_nor_global_protection_ops;
155d534fd97STakahiro Kuwano 
156d534fd97STakahiro Kuwano 	return 0;
15731ad3effSMichael Walle }
15831ad3effSMichael Walle 
1592394770bSMichael Walle static const struct spi_nor_fixups atmel_nor_global_protection_fixups = {
1602394770bSMichael Walle 	.late_init = atmel_nor_global_protection_late_init,
16131ad3effSMichael Walle };
16231ad3effSMichael Walle 
1632394770bSMichael Walle static const struct flash_info atmel_nor_parts[] = {
164f9d52efbSMichael Walle 	{
165a16ae250SMichael Walle 		.id = SNOR_ID(0x1f, 0x04, 0x00),
166a16ae250SMichael Walle 		.name = "at26f004",
167f9d52efbSMichael Walle 		.size = SZ_512K,
168f9d52efbSMichael Walle 		.no_sfdp_flags = SECT_4K,
169a16ae250SMichael Walle 	}, {
170a16ae250SMichael Walle 		.id = SNOR_ID(0x1f, 0x25, 0x00),
171a16ae250SMichael Walle 		.name = "at45db081d",
172a16ae250SMichael Walle 		.size = SZ_1M,
173a16ae250SMichael Walle 		.no_sfdp_flags = SECT_4K,
174a16ae250SMichael Walle 	}, {
175a16ae250SMichael Walle 		.id = SNOR_ID(0x1f, 0x42, 0x16),
176a16ae250SMichael Walle 		.name = "at25sl321",
177a16ae250SMichael Walle 		.size = SZ_4M,
178a16ae250SMichael Walle 		.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
179f9d52efbSMichael Walle 	}, {
180f9d52efbSMichael Walle 		.id = SNOR_ID(0x1f, 0x44, 0x01),
181f9d52efbSMichael Walle 		.name = "at25df041a",
182f9d52efbSMichael Walle 		.size = SZ_512K,
183f9d52efbSMichael Walle 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
184f9d52efbSMichael Walle 		.no_sfdp_flags = SECT_4K,
185f9d52efbSMichael Walle 		.fixups = &atmel_nor_global_protection_fixups,
186f9d52efbSMichael Walle 	}, {
187a16ae250SMichael Walle 		.id = SNOR_ID(0x1f, 0x45, 0x01),
188a16ae250SMichael Walle 		.name = "at26df081a",
189a16ae250SMichael Walle 		.size = SZ_1M,
190a16ae250SMichael Walle 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
191a16ae250SMichael Walle 		.no_sfdp_flags = SECT_4K,
192a16ae250SMichael Walle 		.fixups = &atmel_nor_global_protection_fixups
193a16ae250SMichael Walle 	}, {
194a16ae250SMichael Walle 		.id = SNOR_ID(0x1f, 0x46, 0x01),
195a16ae250SMichael Walle 		.name = "at26df161a",
196a16ae250SMichael Walle 		.size = SZ_2M,
197a16ae250SMichael Walle 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
198a16ae250SMichael Walle 		.no_sfdp_flags = SECT_4K,
199a16ae250SMichael Walle 		.fixups = &atmel_nor_global_protection_fixups
200a16ae250SMichael Walle 	}, {
201f9d52efbSMichael Walle 		.id = SNOR_ID(0x1f, 0x47, 0x00),
202f9d52efbSMichael Walle 		.name = "at25df321",
203f9d52efbSMichael Walle 		.size = SZ_4M,
204f9d52efbSMichael Walle 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
205f9d52efbSMichael Walle 		.no_sfdp_flags = SECT_4K,
206f9d52efbSMichael Walle 		.fixups = &atmel_nor_global_protection_fixups
207f9d52efbSMichael Walle 	}, {
208f9d52efbSMichael Walle 		.id = SNOR_ID(0x1f, 0x47, 0x01),
209f9d52efbSMichael Walle 		.name = "at25df321a",
210f9d52efbSMichael Walle 		.size = SZ_4M,
211f9d52efbSMichael Walle 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
212f9d52efbSMichael Walle 		.no_sfdp_flags = SECT_4K,
213f9d52efbSMichael Walle 		.fixups = &atmel_nor_global_protection_fixups
214f9d52efbSMichael Walle 	}, {
2158f407edaSNicolas Ferre 		.id = SNOR_ID(0x1f, 0x47, 0x08),
2168f407edaSNicolas Ferre 		.name = "at25ff321a",
2178f407edaSNicolas Ferre 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
2188f407edaSNicolas Ferre 		.fixups = &atmel_nor_global_protection_fixups
2198f407edaSNicolas Ferre 	}, {
220f9d52efbSMichael Walle 		.id = SNOR_ID(0x1f, 0x48, 0x00),
221f9d52efbSMichael Walle 		.name = "at25df641",
222f9d52efbSMichael Walle 		.size = SZ_8M,
223f9d52efbSMichael Walle 		.flags = SPI_NOR_HAS_LOCK | SPI_NOR_SWP_IS_VOLATILE,
224f9d52efbSMichael Walle 		.no_sfdp_flags = SECT_4K,
225f9d52efbSMichael Walle 		.fixups = &atmel_nor_global_protection_fixups
226f9d52efbSMichael Walle 	}, {
227a16ae250SMichael Walle 		.id = SNOR_ID(0x1f, 0x66, 0x01),
228a16ae250SMichael Walle 		.name = "at25fs010",
229a16ae250SMichael Walle 		.sector_size = SZ_32K,
230a16ae250SMichael Walle 		.size = SZ_128K,
231a16ae250SMichael Walle 		.flags = SPI_NOR_HAS_LOCK,
232a16ae250SMichael Walle 		.no_sfdp_flags = SECT_4K,
233a16ae250SMichael Walle 		.fixups = &at25fs_nor_fixups
234f9d52efbSMichael Walle 	}, {
235a16ae250SMichael Walle 		.id = SNOR_ID(0x1f, 0x66, 0x04),
236a16ae250SMichael Walle 		.name = "at25fs040",
237f9d52efbSMichael Walle 		.size = SZ_512K,
238a16ae250SMichael Walle 		.flags = SPI_NOR_HAS_LOCK,
239f9d52efbSMichael Walle 		.no_sfdp_flags = SECT_4K,
240a16ae250SMichael Walle 		.fixups = &at25fs_nor_fixups
241f9d52efbSMichael Walle 	},
242f7242bfcSBoris Brezillon };
243f7242bfcSBoris Brezillon 
244f7242bfcSBoris Brezillon const struct spi_nor_manufacturer spi_nor_atmel = {
245f7242bfcSBoris Brezillon 	.name = "atmel",
2462394770bSMichael Walle 	.parts = atmel_nor_parts,
2472394770bSMichael Walle 	.nparts = ARRAY_SIZE(atmel_nor_parts),
248f7242bfcSBoris Brezillon };
249