From patchwork Sun Oct 30 18:26:15 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jagan Teki X-Patchwork-Id: 689029 X-Patchwork-Delegate: jagannadh.teki@gmail.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from theia.denx.de (theia.denx.de [85.214.87.163]) by ozlabs.org (Postfix) with ESMTP id 3t6QxL6DSwz9s8x for ; Mon, 31 Oct 2016 05:28:54 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id E70E6A75DC; Sun, 30 Oct 2016 19:27:15 +0100 (CET) Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id GGJLH2GRQgSQ; Sun, 30 Oct 2016 19:27:15 +0100 (CET) Received: from theia.denx.de (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 4D164A75A7; Sun, 30 Oct 2016 19:27:15 +0100 (CET) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 5C71EA765B for ; Sun, 30 Oct 2016 19:27:10 +0100 (CET) Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id YZ0pt1D2t-2S for ; Sun, 30 Oct 2016 19:27:10 +0100 (CET) X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_SPAMCOP=-1.5 NOT_IN_BL_NJABL=-1.5 (only DNSBL check requested) Received: from mail-pf0-f193.google.com (mail-pf0-f193.google.com [209.85.192.193]) by theia.denx.de (Postfix) with ESMTPS id C7650A761F for ; Sun, 30 Oct 2016 19:26:53 +0100 (CET) Received: by mail-pf0-f193.google.com with SMTP id a136so3261759pfa.0 for ; Sun, 30 Oct 2016 11:26:53 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=kGP0q7axRaifYNks1mmHziHMMR7gOL9fgtx5YCXHelY=; b=fmNp6k3zk6OLv23tHqF6JLduGL86F4N+51RrqKrhbFUHpbVa5Lunnm4dNeNsH/+eMg Pb78Yxz0HXbH9jN73s3kf21luCKY4IcVH79/M6ctuWGZfX1pi/Bz2J4rpKvyarPaivdR MuyKBe+knmKPQPPb0FVaFKoLdCZhBroi3Y7ovHWmXqoroIdB7jayiOfXsQSP97t/g3Zs PcTKsKVBaKqWOYCvHuhUrWmut3jOwqh+ld75PEcp2bUqQmIi6Em+Ib9g3Fd/NSzaA/9+ VVEoNCB91USjDKXKFGglYwTxmkowsb50Kn9KMrrcgh80erv/k8mY7TIRpm1G6g5kLcx5 A76w== X-Gm-Message-State: ABUngveAdDvEIJvsNEFQNRmEiD7OjkgBlCV6UKKni/BIQ29igO1107kTFKv4GM8IQGCBJg== X-Received: by 10.98.10.141 with SMTP id 13mr16201384pfk.105.1477852011602; Sun, 30 Oct 2016 11:26:51 -0700 (PDT) Received: from Mr.J ([49.204.230.134]) by smtp.gmail.com with ESMTPSA id fi6sm31324067pac.20.2016.10.30.11.26.46 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Sun, 30 Oct 2016 11:26:50 -0700 (PDT) From: Jagan Teki To: u-boot@lists.denx.de Date: Sun, 30 Oct 2016 23:56:15 +0530 Message-Id: <1477851975-24149-1-git-send-email-jagan@openedev.com> X-Mailer: git-send-email 2.7.4 Cc: Tom Rini , Stefan Roese , Jagan Teki Subject: [U-Boot] [PATCH v9 02/21] mtd: Add SPI-NOR core support X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.15 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" Some of the SPI device drivers at drivers/spi not a real spi controllers, Unlike normal/generic SPI controllers they operates only with SPI-NOR flash devices. these were technically termed as SPI-NOR controllers, Ex: drivers/spi/fsl_qspi.c The problem with these were resides at drivers/spi is entire SPI layer becomes SPI-NOR flash oriented which is absolutely a wrong indication where SPI layer getting effected more with flash operations - So this SPI-NOR core will resolve this issue by separating all SPI-NOR flash operations from spi layer and creats a generic layer called SPI-NOR core which can be used to interact SPI-NOR to SPI driver interface layer and the SPI-NOR controller driver. The idea is taken from Linux spi-nor framework. ------------------------------ mtd.c ------------------------------ mtd-uclass ------------------------------- SPI-NOR Core ------------------------------- m25p80.c zynq_qspi ------------------------------- spi-uclass SPI NOR chip ------------------------------- spi drivers ------------------------------- SPI NOR chip ------------------------------- Signed-off-by: Jagan Teki --- Makefile | 1 + drivers/mtd/spi-nor/Makefile | 9 + drivers/mtd/spi-nor/spi-nor-ids.c | 176 +++++++++++ drivers/mtd/spi-nor/spi-nor.c | 648 ++++++++++++++++++++++++++++++++++++++ include/linux/err.h | 5 + include/linux/mtd/spi-nor.h | 207 ++++++++++++ 6 files changed, 1046 insertions(+) create mode 100644 drivers/mtd/spi-nor/Makefile create mode 100644 drivers/mtd/spi-nor/spi-nor-ids.c create mode 100644 drivers/mtd/spi-nor/spi-nor.c create mode 100644 include/linux/mtd/spi-nor.h diff --git a/Makefile b/Makefile index c67cc99..6404b12 100644 --- a/Makefile +++ b/Makefile @@ -642,6 +642,7 @@ libs-$(CONFIG_CMD_NAND) += drivers/mtd/nand/ libs-y += drivers/mtd/onenand/ libs-$(CONFIG_CMD_UBI) += drivers/mtd/ubi/ libs-y += drivers/mtd/spi/ +libs-y += drivers/mtd/spi-nor/ libs-y += drivers/net/ libs-y += drivers/net/phy/ libs-y += drivers/pci/ diff --git a/drivers/mtd/spi-nor/Makefile b/drivers/mtd/spi-nor/Makefile new file mode 100644 index 0000000..15e43ea --- /dev/null +++ b/drivers/mtd/spi-nor/Makefile @@ -0,0 +1,9 @@ +# +# Copyright (C) 2016 Jagan Teki +# +# SPDX-License-Identifier: GPL-2.0+ + +## spi-nor core +ifdef CONFIG_MTD_SPI_NOR +obj-y += spi-nor.o spi-nor-ids.o +endif diff --git a/drivers/mtd/spi-nor/spi-nor-ids.c b/drivers/mtd/spi-nor/spi-nor-ids.c new file mode 100644 index 0000000..bde8513 --- /dev/null +++ b/drivers/mtd/spi-nor/spi-nor-ids.c @@ -0,0 +1,176 @@ +/* + * SPI NOR IDs. + * + * Copyright (C) 2016 Jagan Teki + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include + +/* Used when the "_ext_id" is two bytes at most */ +#define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \ + .id = { \ + ((_jedec_id) >> 16) & 0xff, \ + ((_jedec_id) >> 8) & 0xff, \ + (_jedec_id) & 0xff, \ + ((_ext_id) >> 8) & 0xff, \ + (_ext_id) & 0xff, \ + }, \ + .id_len = (!(_jedec_id) ? 0 : (3 + ((_ext_id) ? 2 : 0))), \ + .sector_size = (_sector_size), \ + .n_sectors = (_n_sectors), \ + .page_size = 256, \ + .flags = (_flags), + +#define INFO6(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \ + .id = { \ + ((_jedec_id) >> 16) & 0xff, \ + ((_jedec_id) >> 8) & 0xff, \ + (_jedec_id) & 0xff, \ + ((_ext_id) >> 16) & 0xff, \ + ((_ext_id) >> 8) & 0xff, \ + (_ext_id) & 0xff, \ + }, \ + .id_len = 6, \ + .sector_size = (_sector_size), \ + .n_sectors = (_n_sectors), \ + .page_size = 256, \ + .flags = (_flags), + +const struct spi_nor_info spi_nor_ids[] = { +#ifdef CONFIG_SPI_NOR_MACRONIX /* MACRONIX */ + {"mx25l2006e", INFO(0xc22012, 0x0, 64 * 1024, 4, 0) }, + {"mx25l4005", INFO(0xc22013, 0x0, 64 * 1024, 8, 0) }, + {"mx25l8005", INFO(0xc22014, 0x0, 64 * 1024, 16, 0) }, + {"mx25l1605d", INFO(0xc22015, 0x0, 64 * 1024, 32, 0) }, + {"mx25l3205d", INFO(0xc22016, 0x0, 64 * 1024, 64, 0) }, + {"mx25l6405d", INFO(0xc22017, 0x0, 64 * 1024, 128, 0) }, + {"mx25l12805", INFO(0xc22018, 0x0, 64 * 1024, 256, RD_FULL | WR_QPP) }, + {"mx25l25635f", INFO(0xc22019, 0x0, 64 * 1024, 512, RD_FULL | WR_QPP) }, + {"mx25l51235f", INFO(0xc2201a, 0x0, 64 * 1024, 1024, RD_FULL | WR_QPP) }, + {"mx25l12855e", INFO(0xc22618, 0x0, 64 * 1024, 256, RD_FULL | WR_QPP) }, +#endif +#ifdef CONFIG_SPI_NOR_SPANSION /* SPANSION */ + {"s25fl008a", INFO(0x010213, 0x0, 64 * 1024, 16, 0) }, + {"s25fl016a", INFO(0x010214, 0x0, 64 * 1024, 32, 0) }, + {"s25fl032a", INFO(0x010215, 0x0, 64 * 1024, 64, 0) }, + {"s25fl064a", INFO(0x010216, 0x0, 64 * 1024, 128, 0) }, + {"s25fl116k", INFO(0x014015, 0x0, 64 * 1024, 128, 0) }, + {"s25fl164k", INFO(0x014017, 0x0140, 64 * 1024, 128, 0) }, + {"s25fl128p_256k", INFO(0x012018, 0x0300, 256 * 1024, 64, RD_FULL | WR_QPP) }, + {"s25fl128p_64k", INFO(0x012018, 0x0301, 64 * 1024, 256, RD_FULL | WR_QPP) }, + {"s25fl032p", INFO(0x010215, 0x4d00, 64 * 1024, 64, RD_FULL | WR_QPP) }, + {"s25fl064p", INFO(0x010216, 0x4d00, 64 * 1024, 128, RD_FULL | WR_QPP) }, + {"s25fl128s_256k", INFO(0x012018, 0x4d00, 256 * 1024, 64, RD_FULL | WR_QPP) }, + {"s25fl128s_64k", INFO(0x012018, 0x4d01, 64 * 1024, 256, RD_FULL | WR_QPP) }, + {"s25fl256s_256k", INFO(0x010219, 0x4d00, 256 * 1024, 128, RD_FULL | WR_QPP) }, + {"s25fl256s_64k", INFO(0x010219, 0x4d01, 64 * 1024, 512, RD_FULL | WR_QPP) }, + {"s25s256s_64k", INFO6(0x010219, 0x4d0181, 64 * 1024, 512, RD_FULL | WR_QPP | SECT_4K) }, + {"s25s512s", INFO(0x010220, 0x4d00, 128 * 1024, 512, RD_FULL | WR_QPP) }, + {"s25fl512s_256k", INFO(0x010220, 0x4d00, 256 * 1024, 256, RD_FULL | WR_QPP) }, + {"s25fl512s_64k", INFO(0x010220, 0x4d01, 64 * 1024, 1024, RD_FULL | WR_QPP) }, + {"s25fl512s_512k", INFO(0x010220, 0x4f00, 256 * 1024, 256, RD_FULL | WR_QPP) }, +#endif +#ifdef CONFIG_SPI_NOR_STMICRO /* STMICRO */ + {"m25p10", INFO(0x202011, 0x0, 32 * 1024, 4, 0) }, + {"m25p20", INFO(0x202012, 0x0, 64 * 1024, 4, 0) }, + {"m25p40", INFO(0x202013, 0x0, 64 * 1024, 8, 0) }, + {"m25p80", INFO(0x202014, 0x0, 64 * 1024, 16, 0) }, + {"m25p16", INFO(0x202015, 0x0, 64 * 1024, 32, 0) }, + {"m25pE16", INFO(0x208015, 0x1000, 64 * 1024, 32, 0) }, + {"m25pX16", INFO(0x207115, 0x1000, 64 * 1024, 32, RD_QUAD | RD_DUAL) }, + {"m25p32", INFO(0x202016, 0x0, 64 * 1024, 64, 0) }, + {"m25p64", INFO(0x202017, 0x0, 64 * 1024, 128, 0) }, + {"m25p128", INFO(0x202018, 0x0, 256 * 1024, 64, 0) }, + {"m25pX64", INFO(0x207117, 0x0, 64 * 1024, 128, SECT_4K) }, + {"n25q016a", INFO(0x20bb15, 0x0, 64 * 1024, 32, SECT_4K) }, + {"n25q32", INFO(0x20ba16, 0x0, 64 * 1024, 64, RD_FULL | WR_QPP | SECT_4K) }, + {"n25q32a", INFO(0x20bb16, 0x0, 64 * 1024, 64, RD_FULL | WR_QPP | SECT_4K) }, + {"n25q64", INFO(0x20ba17, 0x0, 64 * 1024, 128, RD_FULL | WR_QPP | SECT_4K) }, + {"n25q64a", INFO(0x20bb17, 0x0, 64 * 1024, 128, RD_FULL | WR_QPP | SECT_4K) }, + {"n25q128", INFO(0x20ba18, 0x0, 64 * 1024, 256, RD_FULL | WR_QPP) }, + {"n25q128a", INFO(0x20bb18, 0x0, 64 * 1024, 256, RD_FULL | WR_QPP) }, + {"n25q256", INFO(0x20ba19, 0x0, 64 * 1024, 512, RD_FULL | WR_QPP | SECT_4K) }, + {"n25q256a", INFO(0x20bb19, 0x0, 64 * 1024, 512, RD_FULL | WR_QPP | SECT_4K) }, + {"n25q512", INFO(0x20ba20, 0x0, 64 * 1024, 1024, RD_FULL | WR_QPP | E_FSR | SECT_4K) }, + {"n25q512a", INFO(0x20bb20, 0x0, 64 * 1024, 1024, RD_FULL | WR_QPP | E_FSR | SECT_4K) }, + {"n25q1024", INFO(0x20ba21, 0x0, 64 * 1024, 2048, RD_FULL | WR_QPP | E_FSR | SECT_4K) }, + {"n25q1024a", INFO(0x20bb21, 0x0, 64 * 1024, 2048, RD_FULL | WR_QPP | E_FSR | SECT_4K) }, +#endif +#ifdef CONFIG_SPI_NOR_SST /* SST */ + {"sst25vf040b", INFO(0xbf258d, 0x0, 64 * 1024, 8, SECT_4K | SST_WR) }, + {"sst25vf080b", INFO(0xbf258e, 0x0, 64 * 1024, 16, SECT_4K | SST_WR) }, + {"sst25vf016b", INFO(0xbf2541, 0x0, 64 * 1024, 32, SECT_4K | SST_WR) }, + {"sst25vf032b", INFO(0xbf254a, 0x0, 64 * 1024, 64, SECT_4K | SST_WR) }, + {"sst25vf064c", INFO(0xbf254b, 0x0, 64 * 1024, 128, SECT_4K) }, + {"sst25wf512", INFO(0xbf2501, 0x0, 64 * 1024, 1, SECT_4K | SST_WR) }, + {"sst25wf010", INFO(0xbf2502, 0x0, 64 * 1024, 2, SECT_4K | SST_WR) }, + {"sst25wf020", INFO(0xbf2503, 0x0, 64 * 1024, 4, SECT_4K | SST_WR) }, + {"sst25wf040", INFO(0xbf2504, 0x0, 64 * 1024, 8, SECT_4K | SST_WR) }, + {"sst25wf040b", INFO(0x621613, 0x0, 64 * 1024, 8, SECT_4K) }, + {"sst25wf080", INFO(0xbf2505, 0x0, 64 * 1024, 16, SECT_4K | SST_WR) }, +#endif +#ifdef CONFIG_SPI_NOR_WINBOND /* WINBOND */ + {"w25p80", INFO(0xef2014, 0x0, 64 * 1024, 16, 0) }, + {"w25p16", INFO(0xef2015, 0x0, 64 * 1024, 32, 0) }, + {"w25p32", INFO(0xef2016, 0x0, 64 * 1024, 64, 0) }, + {"w25x40", INFO(0xef3013, 0x0, 64 * 1024, 8, SECT_4K) }, + {"w25x16", INFO(0xef3015, 0x0, 64 * 1024, 32, SECT_4K) }, + {"w25x32", INFO(0xef3016, 0x0, 64 * 1024, 64, SECT_4K) }, + {"w25x64", INFO(0xef3017, 0x0, 64 * 1024, 128, SECT_4K) }, + {"w25q80bl", INFO(0xef4014, 0x0, 64 * 1024, 16, RD_FULL | WR_QPP | SECT_4K) }, + {"w25q16cl", INFO(0xef4015, 0x0, 64 * 1024, 32, RD_FULL | WR_QPP | SECT_4K) }, + {"w25q32bv", INFO(0xef4016, 0x0, 64 * 1024, 64, RD_FULL | WR_QPP | SECT_4K) }, + {"w25q64cv", INFO(0xef4017, 0x0, 64 * 1024, 128, RD_FULL | WR_QPP | SECT_4K) }, + {"w25q128bv", INFO(0xef4018, 0x0, 64 * 1024, 256, RD_FULL | WR_QPP | SECT_4K) }, + {"w25q256", INFO(0xef4019, 0x0, 64 * 1024, 512, RD_FULL | WR_QPP | SECT_4K) }, + {"w25q80bw", INFO(0xef5014, 0x0, 64 * 1024, 16, RD_FULL | WR_QPP | SECT_4K) }, + {"w25q16dw", INFO(0xef6015, 0x0, 64 * 1024, 32, RD_FULL | WR_QPP | SECT_4K) }, + {"w25q32dw", INFO(0xef6016, 0x0, 64 * 1024, 64, RD_FULL | WR_QPP | SECT_4K) }, + {"w25q64dw", INFO(0xef6017, 0x0, 64 * 1024, 128, RD_FULL | WR_QPP | SECT_4K) }, + {"w25q128fw", INFO(0xef6018, 0x0, 64 * 1024, 256, RD_FULL | WR_QPP | SECT_4K) }, +#endif +#ifdef CONFIG_SPI_NOR_MISC + /* ATMEL */ + {"at45db011d", INFO(0x1f2200, 0x0, 64 * 1024, 4, SECT_4K) }, + {"at45db021d", INFO(0x1f2300, 0x0, 64 * 1024, 8, SECT_4K) }, + {"at45db041d", INFO(0x1f2400, 0x0, 64 * 1024, 8, SECT_4K) }, + {"at45db081d", INFO(0x1f2500, 0x0, 64 * 1024, 16, SECT_4K) }, + {"at45db161d", INFO(0x1f2600, 0x0, 64 * 1024, 32, SECT_4K) }, + {"at45db321d", INFO(0x1f2700, 0x0, 64 * 1024, 64, SECT_4K) }, + {"at45db641d", INFO(0x1f2800, 0x0, 64 * 1024, 128, SECT_4K) }, + {"at25df321a", INFO(0x1f4701, 0x0, 64 * 1024, 64, SECT_4K) }, + {"at25df321", INFO(0x1f4700, 0x0, 64 * 1024, 64, SECT_4K) }, + {"at26df081a", INFO(0x1f4501, 0x0, 64 * 1024, 16, SECT_4K) }, + + /* EON */ + {"en25q32b", INFO(0x1c3016, 0x0, 64 * 1024, 64, 0) }, + {"en25q64", INFO(0x1c3017, 0x0, 64 * 1024, 128, SECT_4K) }, + {"en25q128b", INFO(0x1c3018, 0x0, 64 * 1024, 256, 0) }, + {"en25s64", INFO(0x1c3817, 0x0, 64 * 1024, 128, 0) }, + + /* GIGADEVICE */ + {"gd25q64b", INFO(0xc84017, 0x0, 64 * 1024, 128, SECT_4K) }, + {"gd25lq32", INFO(0xc86016, 0x0, 64 * 1024, 64, SECT_4K) }, + + /* ISSI */ + {"is25lp032", INFO(0x9d6016, 0x0, 64 * 1024, 64, 0) }, + {"is25lp064", INFO(0x9d6017, 0x0, 64 * 1024, 128, 0) }, + {"is25lp128", INFO(0x9d6018, 0x0, 64 * 1024, 256, 0) }, +#endif + {}, /* Empty entry to terminate the list */ + /* + * Note: + * Below paired flash devices has similar spi_nor params. + * (s25fl129p_64k, s25fl128s_64k) + * (w25q80bl, w25q80bv) + * (w25q16cl, w25q16dv) + * (w25q32bv, w25q32fv_spi) + * (w25q64cv, w25q64fv_spi) + * (w25q128bv, w25q128fv_spi) + * (w25q32dw, w25q32fv_qpi) + * (w25q64dw, w25q64fv_qpi) + * (w25q128fw, w25q128fv_qpi) + */ +}; diff --git a/drivers/mtd/spi-nor/spi-nor.c b/drivers/mtd/spi-nor/spi-nor.c new file mode 100644 index 0000000..12e7cfe --- /dev/null +++ b/drivers/mtd/spi-nor/spi-nor.c @@ -0,0 +1,648 @@ +/* + * SPI NOR Core framework. + * + * Copyright (C) 2016 Jagan Teki + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +/* Set write enable latch with Write Enable command */ +static inline int write_enable(struct spi_nor *nor) +{ + return nor->write_reg(nor, SNOR_OP_WREN, NULL, 0); +} + +/* Re-set write enable latch with Write Disable command */ +static inline int write_disable(struct spi_nor *nor) +{ + return nor->write_reg(nor, SNOR_OP_WRDI, NULL, 0); +} + +static int read_sr(struct spi_nor *nor) +{ + u8 sr; + int ret; + + ret = nor->read_reg(nor, SNOR_OP_RDSR, &sr, 1); + if (ret < 0) { + debug("spi-nor: fail to read status register\n"); + return ret; + } + + return sr; +} + +static int read_fsr(struct spi_nor *nor) +{ + u8 fsr; + int ret; + + ret = nor->read_reg(nor, SNOR_OP_RDFSR, &fsr, 1); + if (ret < 0) { + debug("spi-nor: fail to read flag status register\n"); + return ret; + } + + return fsr; +} + +static int write_sr(struct spi_nor *nor, u8 ws) +{ + nor->cmd_buf[0] = ws; + return nor->write_reg(nor, SNOR_OP_WRSR, nor->cmd_buf, 1); +} + +#if defined(CONFIG_SPI_NOR_SPANSION) || defined(CONFIG_SPI_NOR_WINBOND) +static int read_cr(struct spi_nor *nor) +{ + u8 cr; + int ret; + + ret = nor->read_reg(nor, SNOR_OP_RDCR, &cr, 1); + if (ret < 0) { + debug("spi-nor: fail to read config register\n"); + return ret; + } + + return cr; +} + +/* + * Write status Register and configuration register with 2 bytes + * - First byte will be written to the status register. + * - Second byte will be written to the configuration register. + * Return negative if error occured. + */ +static int write_sr_cr(struct spi_nor *nor, u16 val) +{ + nor->cmd_buf[0] = val & 0xff; + nor->cmd_buf[1] = (val >> 8); + + return nor->write_reg(nor, SNOR_OP_WRSR, nor->cmd_buf, 2); +} +#endif + +static int spi_nor_sr_ready(struct spi_nor *nor) +{ + int sr = read_sr(nor); + if (sr < 0) + return sr; + else + return !(sr & SR_WIP); +} + +static int spi_nor_fsr_ready(struct spi_nor *nor) +{ + int fsr = read_fsr(nor); + if (fsr < 0) + return fsr; + else + return fsr & FSR_READY; +} + +static int spi_nor_ready(struct spi_nor *nor) +{ + int sr, fsr; + + sr = spi_nor_sr_ready(nor); + if (sr < 0) + return sr; + + fsr = 1; + if (nor->flags & SNOR_F_USE_FSR) { + fsr = spi_nor_fsr_ready(nor); + if (fsr < 0) + return fsr; + } + + return sr && fsr; +} + +static int spi_nor_wait_till_ready(struct spi_nor *nor, unsigned long timeout) +{ + int timebase, ret; + + timebase = get_timer(0); + + while (get_timer(timebase) < timeout) { + ret = spi_nor_ready(nor); + if (ret < 0) + return ret; + if (ret) + return 0; + } + + printf("spi-nor: Timeout!\n"); + + return -ETIMEDOUT; +} + +static const struct spi_nor_info *spi_nor_id(struct spi_nor *nor) +{ + int tmp; + u8 id[SPI_NOR_MAX_ID_LEN]; + const struct spi_nor_info *info; + + tmp = nor->read_reg(nor, SNOR_OP_RDID, id, SPI_NOR_MAX_ID_LEN); + if (tmp < 0) { + printf("spi-nor: error %d reading JEDEC ID\n", tmp); + return ERR_PTR(tmp); + } + + info = spi_nor_ids; + for (; info->name != NULL; info++) { + if (info->id_len) { + if (!memcmp(info->id, id, info->id_len)) + return info; + } + } + + printf("spi-nor: unrecognized JEDEC id bytes: %02x, %2x, %2x\n", + id[0], id[1], id[2]); + return ERR_PTR(-ENODEV); +} + +static int spi_nor_erase(struct udevice *dev, struct erase_info *instr) +{ + struct mtd_info *mtd = mtd_get_info(dev); + struct spi_nor *nor = mtd->priv; + u32 addr, len, erase_addr; + uint32_t rem; + int ret = -1; + + div_u64_rem(instr->len, mtd->erasesize, &rem); + if (rem) + return -EINVAL; + + addr = instr->addr; + len = instr->len; + + while (len) { + erase_addr = addr; + + write_enable(nor); + + ret = nor->write(nor, erase_addr, 0, NULL); + if (ret < 0) + goto erase_err; + + ret = spi_nor_wait_till_ready(nor, SNOR_READY_WAIT_ERASE); + if (ret < 0) + goto erase_err; + + addr += mtd->erasesize; + len -= mtd->erasesize; + } + + write_disable(nor); + + instr->state = MTD_ERASE_DONE; + mtd_erase_callback(instr); + + return ret; + +erase_err: + instr->state = MTD_ERASE_FAILED; + return ret; +} + +static int spi_nor_write(struct udevice *dev, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct mtd_info *mtd = mtd_get_info(dev); + struct spi_nor *nor = mtd->priv; + size_t addr, byte_addr; + size_t chunk_len, actual; + uint32_t page_size; + int ret = -1; + + page_size = mtd->writebufsize; + + for (actual = 0; actual < len; actual += chunk_len) { + addr = to; + + byte_addr = addr % page_size; + chunk_len = min(len - actual, (size_t)(page_size - byte_addr)); + + if (nor->max_write_size) + chunk_len = min(chunk_len, + (size_t)nor->max_write_size); + + write_enable(nor); + + ret = nor->write(nor, addr, chunk_len, buf + actual); + if (ret < 0) + break; + + ret = spi_nor_wait_till_ready(nor, SNOR_READY_WAIT_PROG); + if (ret < 0) + return ret; + + to += chunk_len; + *retlen += chunk_len; + } + + return ret; +} + +static int spi_nor_read(struct udevice *dev, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct mtd_info *mtd = mtd_get_info(dev); + struct spi_nor *nor = mtd->priv; + int ret; + + /* Handle memory-mapped SPI */ + if (nor->memory_map) { + ret = nor->read(nor, from, len, buf); + if (ret) { + debug("spi-nor: mmap read failed\n"); + return ret; + } + + return ret; + } + + ret = nor->read(nor, from, len, buf); + if (ret < 0) + return ret; + + *retlen += len; + + return ret; +} + +#ifdef CONFIG_SPI_NOR_SST +static int sst_byte_write(struct spi_nor *nor, u32 addr, const void *buf, + size_t *retlen) +{ + int ret; + + ret = write_enable(nor); + if (ret) + return ret; + + nor->program_opcode = SNOR_OP_BP; + + ret = nor->write(nor, addr, 1, buf); + if (ret) + return ret; + + *retlen += 1; + + return spi_nor_wait_till_ready(nor, SNOR_READY_WAIT_PROG); +} + +static int sst_write_wp(struct udevice *dev, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct mtd_info *mtd = mtd_get_info(dev); + struct spi_nor *nor = mtd->priv; + size_t actual; + int ret; + + /* If the data is not word aligned, write out leading single byte */ + actual = to % 2; + if (actual) { + ret = sst_byte_write(nor, to, buf, retlen); + if (ret) + goto done; + } + to += actual; + + ret = write_enable(nor); + if (ret) + goto done; + + for (; actual < len - 1; actual += 2) { + nor->program_opcode = SNOR_OP_AAI_WP; + + ret = nor->write(nor, to, 2, buf + actual); + if (ret) { + debug("spi-nor: sst word program failed\n"); + break; + } + + ret = spi_nor_wait_till_ready(nor, SNOR_READY_WAIT_PROG); + if (ret) + break; + + to += 2; + *retlen += 2; + } + + if (!ret) + ret = write_disable(nor); + + /* If there is a single trailing byte, write it out */ + if (!ret && actual != len) + ret = sst_byte_write(nor, to, buf + actual, retlen); + + done: + return ret; +} + +static int sst_write_bp(struct udevice *dev, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct mtd_info *mtd = mtd_get_info(dev); + struct spi_nor *nor = mtd->priv; + size_t actual; + int ret; + + for (actual = 0; actual < len; actual++) { + ret = sst_byte_write(nor, to, buf + actual, retlen); + if (ret) { + debug("spi-nor: sst byte program failed\n"); + break; + } + to++; + } + + if (!ret) + ret = write_disable(nor); + + return ret; +} +#endif + +#ifdef CONFIG_SPI_NOR_MACRONIX +static int macronix_quad_enable(struct spi_nor *nor) +{ + int ret, val; + + val = read_sr(nor); + if (val < 0) + return val; + + if (val & SR_QUAD_EN_MX) + return 0; + + write_enable(nor); + + ret = write_sr(nor, val | SR_QUAD_EN_MX); + if (ret < 0) + return ret; + + if (spi_nor_wait_till_ready(nor, SNOR_READY_WAIT_PROG)) + return 1; + + ret = read_sr(nor); + if (!(ret > 0 && (ret & SR_QUAD_EN_MX))) { + printf("spi-nor: Macronix Quad bit not set\n"); + return -EINVAL; + } + + return 0; +} +#endif + +#if defined(CONFIG_SPI_NOR_SPANSION) || defined(CONFIG_SPI_NOR_WINBOND) +static int spansion_quad_enable(struct spi_nor *nor) +{ + int ret, val; + + val = read_cr(nor); + if (val < 0) + return val; + + if (val & CR_QUAD_EN_SPAN) + return 0; + + write_enable(nor); + + ret = write_sr_cr(nor, val | CR_QUAD_EN_SPAN); + if (ret < 0) + return ret; + + if (spi_nor_wait_till_ready(nor, SNOR_READY_WAIT_PROG)) + return 1; + + /* read back and check it */ + ret = read_cr(nor); + if (!(ret > 0 && (ret & CR_QUAD_EN_SPAN))) { + printf("spi-nor: Spansion Quad bit not set\n"); + return -EINVAL; + } + + return 0; +} +#endif + +static int set_quad_mode(struct spi_nor *nor, const struct spi_nor_info *info) +{ + switch (JEDEC_MFR(info)) { +#ifdef CONFIG_SPI_NOR_MACRONIX + case SNOR_MFR_MACRONIX: + return macronix_quad_enable(nor); +#endif +#if defined(CONFIG_SPI_NOR_SPANSION) || defined(CONFIG_SPI_NOR_WINBOND) + case SNOR_MFR_SPANSION: + case SNOR_MFR_WINBOND: + return spansion_quad_enable(nor); +#endif +#ifdef CONFIG_SPI_NOR_STMICRO + case SNOR_MFR_MICRON: + return 0; +#endif + default: + printf("spi-nor: Need set QEB func for %02x flash\n", + JEDEC_MFR(info)); + return -1; + } +} + +#if CONFIG_IS_ENABLED(OF_CONTROL) +int spi_nor_decode_fdt(const void *blob, struct spi_nor *nor) +{ + struct udevice *dev = nor->dev; + struct mtd_info *mtd = mtd_get_info(dev); + fdt_addr_t addr; + fdt_size_t size; + int node; + + /* If there is no node, do nothing */ + node = fdtdec_next_compatible(blob, 0, COMPAT_GENERIC_SPI_FLASH); + if (node < 0) + return 0; + + addr = fdtdec_get_addr_size(blob, node, "memory-map", &size); + if (addr == FDT_ADDR_T_NONE) { + debug("%s: Cannot decode address\n", __func__); + return 0; + } + + if (mtd->size != size) { + debug("%s: Memory map must cover entire device\n", __func__); + return -1; + } + nor->memory_map = map_sysmem(addr, size); + + return 0; +} +#endif /* CONFIG_IS_ENABLED(OF_CONTROL) */ + +static int spi_nor_check(struct spi_nor *nor) +{ + if (!nor->read || !nor->write || + !nor->read_reg || !nor->write_reg) { + pr_err("spi-nor: please fill all the necessary fields!\n"); + return -EINVAL; + } + + return 0; +} + +int spi_nor_scan(struct udevice *dev) +{ + struct mtd_info *mtd = mtd_get_info(dev); + struct spi_nor *nor = mtd->priv; + struct dm_mtd_ops *ops = mtd_get_ops(dev); + const struct spi_nor_info *info = NULL; + int ret; + + ret = spi_nor_check(nor); + if (ret) + return ret; + + info = spi_nor_id(nor); + if (IS_ERR_OR_NULL(info)) + return -ENOENT; + + /* + * Atmel, SST, Macronix, and others serial NOR tend to power up + * with the software protection bits set + */ + if (JEDEC_MFR(info) == SNOR_MFR_ATMEL || + JEDEC_MFR(info) == SNOR_MFR_MACRONIX || + JEDEC_MFR(info) == SNOR_MFR_SST) { + write_enable(nor); + write_sr(nor, 0); + } + + mtd->name = info->name; + mtd->priv = nor; + mtd->type = MTD_NORFLASH; + mtd->writesize = 1; + mtd->flags = MTD_CAP_NORFLASH; + ops->_erase = spi_nor_erase; + ops->_read = spi_nor_read; + + if (info->flags & E_FSR) + nor->flags |= SNOR_F_USE_FSR; + + if (info->flags & SST_WR) + nor->flags |= SNOR_F_SST_WRITE; + + ops->_write = spi_nor_write; +#if defined(CONFIG_SPI_NOR_SST) + if (nor->flags & SNOR_F_SST_WRITE) { + if (nor->mode & SNOR_WRITE_1_1_BYTE) + ops->_write = sst_write_bp; + else + ops->_write = sst_write_wp; + } +#endif + + /* Compute the flash size */ + nor->page_size = info->page_size; + /* + * The Spansion S25FL032P and S25FL064P have 256b pages, yet use the + * 0x4d00 Extended JEDEC code. The rest of the Spansion flashes with + * the 0x4d00 Extended JEDEC code have 512b pages. All of the others + * have 256b pages. + */ + if (JEDEC_EXT(info) == 0x4d00) { + if ((JEDEC_ID(info) != 0x0215) && + (JEDEC_ID(info) != 0x0216)) + nor->page_size = 512; + } + mtd->writebufsize = nor->page_size; + mtd->size = info->sector_size * info->n_sectors; + +#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS + /* prefer "small sector" erase if possible */ + if (info->flags & SECT_4K) { + nor->erase_opcode = SNOR_OP_BE_4K; + mtd->erasesize = 4096; + } else +#endif + { + nor->erase_opcode = SNOR_OP_SE; + mtd->erasesize = info->sector_size; + } + + /* Look for read opcode */ + nor->read_opcode = SNOR_OP_READ_FAST; + if (nor->mode & SNOR_READ) + nor->read_opcode = SNOR_OP_READ; + else if (nor->mode & SNOR_READ_1_1_4 && info->flags & RD_QUAD) + nor->read_opcode = SNOR_OP_READ_1_1_4; + else if (nor->mode & SNOR_READ_1_1_2 && info->flags & RD_DUAL) + nor->read_opcode = SNOR_OP_READ_1_1_2; + + /* Look for program opcode */ + if (info->flags & WR_QPP && nor->mode & SNOR_WRITE_1_1_4) + nor->program_opcode = SNOR_OP_QPP; + else + /* Go for default supported write cmd */ + nor->program_opcode = SNOR_OP_PP; + + /* Set the quad enable bit - only for quad commands */ + if ((nor->read_opcode == SNOR_OP_READ_1_1_4) || + (nor->read_opcode == SNOR_OP_READ_1_1_4_IO) || + (nor->program_opcode == SNOR_OP_QPP)) { + ret = set_quad_mode(nor, info); + if (ret) { + debug("spi-nor: quad mode not supported for %02x\n", + JEDEC_MFR(info)); + return ret; + } + } + + nor->addr_width = 3; + + /* Dummy cycles for read */ + switch (nor->read_opcode) { + case SNOR_OP_READ_1_1_4_IO: + nor->read_dummy = 16; + break; + case SNOR_OP_READ: + nor->read_dummy = 0; + break; + default: + nor->read_dummy = 8; + } + +#if CONFIG_IS_ENABLED(OF_CONTROL) + ret = spi_nor_decode_fdt(gd->fdt_blob, nor); + if (ret) { + debug("spi-nor: FDT decode error\n"); + return -EINVAL; + } +#endif + +#ifndef CONFIG_SPL_BUILD + printf("SPI-NOR: detected %s with page size ", mtd->name); + print_size(mtd->writebufsize, ", erase size "); + print_size(mtd->erasesize, ", total "); + print_size(mtd->size, ""); + if (nor->memory_map) + printf(", mapped at %p", nor->memory_map); + puts("\n"); +#endif + + return ret; +} diff --git a/include/linux/err.h b/include/linux/err.h index e4d22d5..22e5756 100644 --- a/include/linux/err.h +++ b/include/linux/err.h @@ -36,6 +36,11 @@ static inline long IS_ERR(const void *ptr) return IS_ERR_VALUE((unsigned long)ptr); } +static inline bool IS_ERR_OR_NULL(const void *ptr) +{ + return !ptr || IS_ERR_VALUE((unsigned long)ptr); +} + /** * ERR_CAST - Explicitly cast an error-valued pointer to another pointer type * @ptr: The pointer to cast. diff --git a/include/linux/mtd/spi-nor.h b/include/linux/mtd/spi-nor.h new file mode 100644 index 0000000..4e5b3ba --- /dev/null +++ b/include/linux/mtd/spi-nor.h @@ -0,0 +1,207 @@ +/* + * SPI NOR Core header file. + * + * Copyright (C) 2016 Jagan Teki + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#ifndef __MTD_SPI_NOR_H +#define __MTD_SPI_NOR_H + +#include + +/* + * Manufacturer IDs + * + * The first byte returned from the flash after sending opcode SPINOR_OP_RDID. + * Sometimes these are the same as CFI IDs, but sometimes they aren't. + */ +#define SNOR_MFR_ATMEL 0x1f +#define SNOR_MFR_MACRONIX 0xc2 +#define SNOR_MFR_MICRON 0x20 /* ST Micro <--> Micron */ +#define SNOR_MFR_SPANSION 0x01 +#define SNOR_MFR_SST 0xbf +#define SNOR_MFR_WINBOND 0xef + +/** + * SPI NOR opcodes. + * + * Note on opcode nomenclature: some opcodes have a format like + * SNOR_OP_FUNCTION{4,}_x_y_z. The numbers x, y, and z stand for the number + * of I/O lines used for the opcode, address, and data (respectively). The + * FUNCTION has an optional suffix of '4', to represent an opcode which + * requires a 4-byte (32-bit) address. + */ +#define SNOR_OP_WRDI 0x04 /* Write disable */ +#define SNOR_OP_WREN 0x06 /* Write enable */ +#define SNOR_OP_RDSR 0x05 /* Read status register */ +#define SNOR_OP_WRSR 0x01 /* Write status register 1 byte */ +#define SNOR_OP_READ 0x03 /* Read data bytes (low frequency) */ +#define SNOR_OP_READ_FAST 0x0b /* Read data bytes (high frequency) */ +#define SNOR_OP_READ_1_1_2 0x3b /* Read data bytes (Dual SPI) */ +#define SNOR_OP_READ_1_1_2_IO 0xbb /* Read data bytes (Dual IO SPI) */ +#define SNOR_OP_READ_1_1_4 0x6b /* Read data bytes (Quad SPI) */ +#define SNOR_OP_READ_1_1_4_IO 0xeb /* Read data bytes (Quad IO SPI) */ +#define SNOR_OP_BRWR 0x17 /* Bank register write */ +#define SNOR_OP_BRRD 0x16 /* Bank register read */ +#define SNOR_OP_WREAR 0xC5 /* Write extended address register */ +#define SNOR_OP_RDEAR 0xC8 /* Read extended address register */ +#define SNOR_OP_PP 0x02 /* Page program (up to 256 bytes) */ +#define SNOR_OP_QPP 0x32 /* Quad Page program */ +#define SNOR_OP_BE_4K 0x20 /* Erase 4KiB block */ +#define SNOR_OP_BE_4K_PMC 0xd7 /* Erase 4KiB block on PMC chips */ +#define SNOR_OP_BE_32K 0x52 /* Erase 32KiB block */ +#define SPINOR_OP_CHIP_ERASE 0xc7 /* Erase whole flash chip */ +#define SNOR_OP_SE 0xd8 /* Sector erase (usually 64KiB) */ +#define SNOR_OP_RDID 0x9f /* Read JEDEC ID */ +#define SNOR_OP_RDCR 0x35 /* Read configuration register */ +#define SNOR_OP_RDFSR 0x70 /* Read flag status register */ + +/* Used for SST flashes only. */ +#define SNOR_OP_BP 0x02 /* Byte program */ +#define SNOR_OP_AAI_WP 0xad /* Auto addr increment word program */ + +/* Status Register bits. */ +#define SR_WIP BIT(0) /* Write in progress */ +#define SR_WEL BIT(1) /* Write enable latch */ + +/* meaning of other SR_* bits may differ between vendors */ +#define SR_BP0 BIT(2) /* Block protect 0 */ +#define SR_BP1 BIT(3) /* Block protect 1 */ +#define SR_BP2 BIT(4) /* Block protect 2 */ +#define SR_SRWD BIT(7) /* SR write protect */ + +#define SR_QUAD_EN_MX BIT(6) /* Macronix Quad I/O */ + +/* Flag Status Register bits */ +#define FSR_READY BIT(7) + +/* Configuration Register bits. */ +#define CR_QUAD_EN_SPAN BIT(1) /* Spansion/Winbond Quad I/O */ + +/* Flash timeout values */ +#define SNOR_READY_WAIT_PROG (2 * CONFIG_SYS_HZ) +#define SNOR_READY_WAIT_ERASE (5 * CONFIG_SYS_HZ) +#define SNOR_MAX_CMD_SIZE 4 +#define SNOR_16MB_BOUN 0x1000000 + +enum snor_option_flags { + SNOR_F_SST_WRITE = BIT(0), + SNOR_F_USE_FSR = BIT(1), + SNOR_F_U_PAGE = BIT(1), +}; + +enum mode { + SNOR_READ = BIT(0), + SNOR_READ_1_1_2 = BIT(1), + SNOR_READ_1_1_4 = BIT(2), + SNOR_READ_1_1_2_IO = BIT(3), + SNOR_READ_1_1_4_IO = BIT(4), + SNOR_WRITE_1_1_BYTE = BIT(5), + SNOR_WRITE_1_1_4 = BIT(6), +}; + +#define JEDEC_MFR(info) ((info)->id[0]) +#define JEDEC_ID(info) (((info)->id[1]) << 8 | ((info)->id[2])) +#define JEDEC_EXT(info) (((info)->id[3]) << 8 | ((info)->id[4])) +#define SPI_NOR_MAX_ID_LEN 6 + +struct spi_nor_info { + char *name; + + /* + * This array stores the ID bytes. + * The first three bytes are the JEDIC ID. + * JEDEC ID zero means "no ID" (mostly older chips). + */ + u8 id[SPI_NOR_MAX_ID_LEN]; + u8 id_len; + + /* The size listed here is what works with SNOR_OP_SE, which isn't + * necessarily called a "sector" by the vendor. + */ + unsigned sector_size; + u16 n_sectors; + + u16 page_size; + + u16 flags; +#define SECT_4K BIT(0) +#define E_FSR BIT(1) +#define SST_WR BIT(2) +#define WR_QPP BIT(3) +#define RD_QUAD BIT(4) +#define RD_DUAL BIT(5) +#define RD_QUADIO BIT(6) +#define RD_DUALIO BIT(7) +#define RD_FULL (RD_QUAD | RD_DUAL | RD_QUADIO | RD_DUALIO) +}; + +extern const struct spi_nor_info spi_nor_ids[]; + +/** + * struct spi_nor - Structure for defining a the SPI NOR layer + * + * @dev: SPI NOR device + * @name: name of the SPI NOR device + * @page_size: the page size of the SPI NOR + * @addr_width: number of address bytes + * @erase_opcode: the opcode for erasing a sector + * @read_opcode: the read opcode + * @read_dummy: the dummy bytes needed by the read operation + * @program_opcode: the program opcode + * @max_write_size: If non-zero, the maximum number of bytes which can + * be written at once, excluding command bytes. + * @flags: flag options for the current SPI-NOR (SNOR_F_*) + * @mode: read, write mode or any other mode bits. + * @read_mode: read mode. + * @cmd_buf: used by the write_reg + * @read_reg: [DRIVER-SPECIFIC] read out the register + * @write_reg: [DRIVER-SPECIFIC] write data to the register + * @read: [DRIVER-SPECIFIC] read data from the SPI NOR + * @write: [DRIVER-SPECIFIC] write data to the SPI NOR + * @memory_map: address of read-only SPI NOR access + * @priv: the private data + */ +struct spi_nor { + struct udevice *dev; + const char *name; + u32 page_size; + u8 addr_width; + u8 erase_opcode; + u8 read_opcode; + u8 read_dummy; + u8 program_opcode; + u32 max_write_size; + u32 flags; + u8 mode; + u8 read_mode; + u8 cmd_buf[SNOR_MAX_CMD_SIZE]; + + int (*read_reg)(struct spi_nor *nor, u8 cmd, u8 *val, int len); + int (*write_reg)(struct spi_nor *nor, u8 cmd, u8 *data, int len); + + int (*read)(struct spi_nor *nor, loff_t from, size_t len, + u_char *read_buf); + int (*write)(struct spi_nor *nor, loff_t to, size_t len, + const u_char *write_buf); + + void *memory_map; + void *priv; +}; + +/** + * spi_nor_scan() - scan the SPI NOR + * + * @dev: SPI NOR device + * + * The drivers can use this fuction to scan the SPI NOR. + * In the scanning, it will try to get all the necessary information to + * fill the mtd_info{} and the spi_nor{}. + * + * @return 0 if OK, -ve on error + */ +int spi_nor_scan(struct udevice *dev); + +#endif /* __MTD_SPI_NOR_H */