From patchwork Tue Aug 14 10:58:31 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Terry Lv X-Patchwork-Id: 177226 X-Patchwork-Delegate: sbabic@denx.de 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 903FE2C0083 for ; Tue, 14 Aug 2012 21:17:44 +1000 (EST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 3256C280E4; Tue, 14 Aug 2012 13:17:42 +0200 (CEST) X-Virus-Scanned: Debian amavisd-new at theia.denx.de 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 OY3F78MeT8hT; Tue, 14 Aug 2012 13:17:41 +0200 (CEST) Received: from theia.denx.de (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 5D209280DD; Tue, 14 Aug 2012 13:17:36 +0200 (CEST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 08CFF280DD for ; Tue, 14 Aug 2012 13:17:34 +0200 (CEST) X-Virus-Scanned: Debian amavisd-new at theia.denx.de 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 bTebwQdJ9LcG for ; Tue, 14 Aug 2012 13:17:32 +0200 (CEST) X-Greylist: delayed 906 seconds by postgrey-1.27 at theia; Tue, 14 Aug 2012 13:17:31 CEST Received: from VA3EHSNDR001.bigfish.com (va3outboundsmtppool2.messaging.microsoft.com [157.55.133.164]) by theia.denx.de (Postfix) with ESMTPS id 8C3D3280DC for ; Tue, 14 Aug 2012 13:17:31 +0200 (CEST) Received: from va3outboundpool.messaging.microsoft.com (10.7.14.252) by VA3EHSNDR001.bigfish.com (10.7.40.50) with Microsoft SMTP Server (TLS) id 14.1.225.23; Tue, 14 Aug 2012 11:02:22 +0000 Received: from mail260-va3-R.bigfish.com (10.7.14.248) by VA3EHSOBE006.bigfish.com (10.7.40.26) with Microsoft SMTP Server id 14.1.225.23; Tue, 14 Aug 2012 11:02:22 +0000 Received: from mail260-va3 (localhost [127.0.0.1]) by mail260-va3-R.bigfish.com (Postfix) with ESMTP id 56E89401BE for ; Tue, 14 Aug 2012 11:02:22 +0000 (UTC) X-Forefront-Antispam-Report: CIP:70.37.183.190; KIP:(null); UIP:(null); IPV:NLI; H:mail.freescale.net; RD:none; EFVD:NLI X-FB-OUTBOUND-SPAM: yes X-SpamScore: 5 X-BigFish: VS5(zzc8kzz1202h10c0jzz8275bh84d07hz2dh87h2a8h668h839hd24he5bhe96hf0ah41h42h) X-FB-DOMAIN-IP-MATCH: fail Received: from mail260-va3 (localhost.localdomain [127.0.0.1]) by mail260-va3 (MessageSwitch) id 134494213920378_7868; Tue, 14 Aug 2012 11:02:19 +0000 (UTC) Received: from VA3EHSMHS013.bigfish.com (unknown [10.7.14.253]) by mail260-va3.bigfish.com (Postfix) with ESMTP id ED1755C0045 for ; Tue, 14 Aug 2012 11:02:18 +0000 (UTC) Received: from mail.freescale.net (70.37.183.190) by VA3EHSMHS013.bigfish.com (10.7.99.23) with Microsoft SMTP Server (TLS) id 14.1.225.23; Tue, 14 Aug 2012 11:02:18 +0000 Received: from az84smr01.freescale.net (10.64.34.197) by 039-SN1MMR1-001.039d.mgd.msft.net (10.84.1.13) with Microsoft SMTP Server (TLS) id 14.2.298.5; Tue, 14 Aug 2012 06:02:17 -0500 Received: from shlinux3.ap.freescale.net ([10.213.130.145]) by az84smr01.freescale.net (8.14.3/8.14.0) with ESMTP id q7EB2F4G004690 for ; Tue, 14 Aug 2012 04:02:16 -0700 Received: by shlinux3.ap.freescale.net (Postfix, from userid 1001) id EE1E9232954; Tue, 14 Aug 2012 18:58:34 +0800 (CST) From: Terry Lv To: Date: Tue, 14 Aug 2012 18:58:31 +0800 Message-ID: <1344941911-4096-1-git-send-email-r65388@freescale.com> X-Mailer: git-send-email 1.7.0.4 MIME-Version: 1.0 X-OriginatorOrg: sigmatel.com Subject: [U-Boot] [PATCH 1/4] spi_nor: add m25p32 spi nor driver X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.11 Precedence: list Reply-To: r65388@shlinux3.net List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: u-boot-bounces@lists.denx.de Errors-To: u-boot-bounces@lists.denx.de This driver is ported from kernel and can handle a series of spi nor flash including m25pxx, m45pxx, w25xxx, cat25xxx, sst25vf0xxx, sst25wfxxx, s25xxx, mx25xxx, at25xxx, at26xxx, en25xxx, xxxs33b. Signed-off-by: Terry Lv --- drivers/mtd/spi/Makefile | 1 + drivers/mtd/spi/m25p80.c | 896 ++++++++++++++++++++++++++++++++++ drivers/mtd/spi/spi_flash.c | 10 + drivers/mtd/spi/spi_flash_internal.h | 1 + 4 files changed, 908 insertions(+), 0 deletions(-) create mode 100644 drivers/mtd/spi/m25p80.c diff --git a/drivers/mtd/spi/Makefile b/drivers/mtd/spi/Makefile index 57112af..1621a61 100644 --- a/drivers/mtd/spi/Makefile +++ b/drivers/mtd/spi/Makefile @@ -28,6 +28,7 @@ LIB := $(obj)libspi_flash.o COBJS-$(CONFIG_SPI_FLASH) += spi_flash.o COBJS-$(CONFIG_SPI_FLASH_ATMEL) += atmel.o COBJS-$(CONFIG_SPI_FLASH_EON) += eon.o +COBJS-$(CONFIG_SPI_FLASH_M25P80) += m25p80.o COBJS-$(CONFIG_SPI_FLASH_MACRONIX) += macronix.o COBJS-$(CONFIG_SPI_FLASH_SPANSION) += spansion.o COBJS-$(CONFIG_SPI_FLASH_SST) += sst.o diff --git a/drivers/mtd/spi/m25p80.c b/drivers/mtd/spi/m25p80.c new file mode 100644 index 0000000..1d87793 --- /dev/null +++ b/drivers/mtd/spi/m25p80.c @@ -0,0 +1,896 @@ +/* + * Copyright (C) 2012 Freescale Semiconductor, Inc. + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ +/* + * MTD SPI driver for ST M25Pxx (and similar) serial flash chips + * + * Author: Mike Lavender, mike@steroidmicros.com + * + * Copyright (c) 2005, Intec Automation Inc. + * + * Some parts are based on lart.c by Abraham Van Der Merwe + * + * Cleaned up and generalized based on mtd_dataflash.c + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "spi_flash_internal.h" + +/* CFI */ +#define CFI_MFR_ANY 0xFFFF +#define CFI_ID_ANY 0xFFFF +#define CFI_MFR_CONTINUATION 0x007F + +#define CFI_MFR_AMD 0x0001 +#define CFI_MFR_AMIC 0x0037 +#define CFI_MFR_ATMEL 0x001F +#define CFI_MFR_EON 0x001C +#define CFI_MFR_FUJITSU 0x0004 +#define CFI_MFR_HYUNDAI 0x00AD +#define CFI_MFR_INTEL 0x0089 +#define CFI_MFR_MACRONIX 0x00C2 +#define CFI_MFR_NEC 0x0010 +#define CFI_MFR_PMC 0x009D +#define CFI_MFR_SAMSUNG 0x00EC +#define CFI_MFR_SHARP 0x00B0 +#define CFI_MFR_SST 0x00BF +#define CFI_MFR_ST 0x0020 /* STMicroelectronics */ +#define CFI_MFR_TOSHIBA 0x0098 +#define CFI_MFR_WINBOND 0x00DA + +/* Flash opcodes. */ +#define OPCODE_WREN 0x06 /* Write enable */ +#define OPCODE_RDSR 0x05 /* Read status register */ +#define OPCODE_WRSR 0x01 /* Write status register 1 byte */ +#define OPCODE_NORM_READ 0x03 /* Read data bytes (low frequency) */ +#define OPCODE_FAST_READ 0x0b /* Read data bytes (high frequency) */ +#define OPCODE_PP 0x02 /* Page program (up to 256 bytes) */ +#define OPCODE_BE_4K 0x20 /* Erase 4KiB block */ +#define OPCODE_BE_32K 0x52 /* Erase 32KiB block */ +#define OPCODE_CHIP_ERASE 0xc7 /* Erase whole flash chip */ +#define OPCODE_SE 0xd8 /* Sector erase (usually 64KiB) */ +#define OPCODE_RDID 0x9f /* Read JEDEC ID */ + +/* Used for SST flashes only. */ +#define OPCODE_BP 0x02 /* Byte program */ +#define OPCODE_WRDI 0x04 /* Write disable */ +#define OPCODE_AAI_WP 0xad /* Auto address increment word program */ + +/* Used for Macronix flashes only. */ +#define OPCODE_EN4B 0xb7 /* Enter 4-byte mode */ +#define OPCODE_EX4B 0xe9 /* Exit 4-byte mode */ + +/* Used for Spansion flashes only. */ +#define OPCODE_BRWR 0x17 /* Bank register write */ + +/* Status Register bits. */ +#define SR_WIP 1 /* Write in progress */ +#define SR_WEL 2 /* Write enable latch */ +/* meaning of other SR_* bits may differ between vendors */ +#define SR_BP0 4 /* Block protect 0 */ +#define SR_BP1 8 /* Block protect 1 */ +#define SR_BP2 0x10 /* Block protect 2 */ +#define SR_SRWD 0x80 /* SR write protect */ + +/* Define max times to check status register before we give up. */ +#define MAX_READY_WAIT_JIFFIES (40 * HZ) /* M25P16 specs 40s max chip erase */ +#define MAX_CMD_SIZE 5 + +#ifdef CONFIG_M25PXX_USE_FAST_READ +#define OPCODE_READ OPCODE_FAST_READ +#define FAST_READ_DUMMY_BYTE 1 +#else +#define OPCODE_READ OPCODE_NORM_READ +#define FAST_READ_DUMMY_BYTE 0 +#endif + +#define JEDEC_MFR(_jedec_id) ((_jedec_id) >> 16) + +/****************************************************************************/ + +struct m25p_spi_flash { + struct spi_flash flash; + u16 page_size; + u16 addr_width; + u32 erase_size; + u8 erase_opcode; +}; + +static inline struct m25p_spi_flash *to_m25p_spi_flash(struct spi_flash *flash) +{ + return container_of(flash, struct m25p_spi_flash, flash); +} + +/****************************************************************************/ + +/* + * Internal helper functions + */ + +/* + * Read the status register, returning its value in the location + * Return the status register value. + * Returns negative if error occurred. + */ +static int read_sr(struct spi_flash *flash) +{ + int ret; + u8 code = OPCODE_RDSR; + u8 stat = 0; + + ret = spi_flash_cmd(flash->spi, code, &stat, 1); + if (ret) + return ret; + + debug("flash status: 0x%x\n", stat); + + return (int)stat; +} + +/* + * Write status register 1 byte + * Returns negative if error occurred. + */ +static int write_sr(struct spi_flash *flash, u8 val) +{ + u8 cmd[MAX_CMD_SIZE + FAST_READ_DUMMY_BYTE] = { 0 }; + + cmd[0] = OPCODE_WRSR; + cmd[1] = val; + + return spi_flash_cmd_write(flash->spi, cmd, 2, NULL, 0); +} + +/* + * Set write enable latch with Write Enable command. + * Returns negative if error occurred. + */ +static inline int write_enable(struct spi_flash *flash) +{ + u8 code = OPCODE_WREN; + + return spi_flash_cmd(flash->spi, code, NULL, 0); +} + +/* + * Send write disble instruction to the chip. + */ +static inline int write_disable(struct spi_flash *flash) +{ + u8 code = OPCODE_WRDI; + + return spi_flash_cmd(flash->spi, code, NULL, 0); +} + +/* + * Enable/disable 4-byte addressing mode. + */ +static inline int set_4byte(struct spi_flash *flash, u32 jedec_id, int enable) +{ + u8 cmd[MAX_CMD_SIZE + FAST_READ_DUMMY_BYTE] = { 0 }; + + switch (JEDEC_MFR(jedec_id)) { + case CFI_MFR_MACRONIX: + cmd[0] = enable ? OPCODE_EN4B : OPCODE_EX4B; + return spi_flash_cmd_write(flash->spi, cmd, 1, NULL, 0); + default: + /* Spansion style */ + cmd[0] = OPCODE_BRWR; + cmd[1] = enable << 7; + return spi_flash_cmd_write(flash->spi, cmd, 2, NULL, 0); + } +} + +/* + * Service routine to read status register until ready, or timeout occurs. + * Returns non-zero if error. + */ +static int wait_till_ready(struct spi_flash *flash) +{ + int sr; + int times = 10000; + + do { + sr = read_sr(flash); + if (sr < 0) + break; + else if (!(sr & SR_WIP)) + return 0; + + udelay(100); + } while (times--); + + return 1; +} + +/* + * Erase the whole flash memory + * + * Returns 0 if successful, non-zero otherwise. + */ +static int erase_chip(struct spi_flash *flash) +{ + s32 ret = 0; + u8 cmd[MAX_CMD_SIZE + FAST_READ_DUMMY_BYTE] = { 0 }; + + /* Wait until finished previous write command. */ + if (wait_till_ready(flash)) { + printf("SF: timeout for ready!\n"); + return 1; + } + + /* Send write enable, then erase commands. */ + ret = write_enable(flash); + if (ret < 0) { + printf("SF: Write enable failed\n"); + return ret; + } + + /* Set up command buffer. */ + cmd[0] = OPCODE_CHIP_ERASE; + + ret = spi_flash_cmd_write(flash->spi, cmd, 1, NULL, 0); + if (ret) + printf("SF: whole chip erase failed\n"); + + return 0; +} + +static void m25p_addr2cmd(struct m25p_spi_flash *flash, + unsigned int addr, u8 *cmd) +{ + /* opcode is in cmd[0] */ + cmd[1] = addr >> (flash->addr_width * 8 - 8); + cmd[2] = addr >> (flash->addr_width * 8 - 16); + cmd[3] = addr >> (flash->addr_width * 8 - 24); + cmd[4] = addr >> (flash->addr_width * 8 - 32); +} + +static int m25p_cmdsz(struct m25p_spi_flash *flash) +{ + return 1 + flash->addr_width; +} + +/* + * Erase one sector of flash memory at offset ``offset'' which is any + * address within the sector which should be erased. + * + * Returns 0 if successful, non-zero otherwise. + */ +static int erase_sector(struct spi_flash *flash, u32 offset) +{ + s32 ret = 0; + u8 cmd[MAX_CMD_SIZE + FAST_READ_DUMMY_BYTE] = { 0 }; + struct m25p_spi_flash *m25p = to_m25p_spi_flash(flash); + + /* Wait until finished previous write command. */ + if (wait_till_ready(flash)) { + printf("SF: timeout for ready: 0x%x!\n", offset); + return 1; + } + + /* Send write enable, then erase commands. */ + ret = write_enable(flash); + if (ret < 0) { + printf("SF: Write enable failed\n"); + return ret; + } + + /* Set up command buffer. */ + cmd[0] = m25p->erase_opcode; + m25p_addr2cmd(m25p, offset, cmd); + + ret = spi_flash_cmd_write(flash->spi, cmd, m25p_cmdsz(m25p), + NULL, 0); + if (ret) + printf("SF: page erase failed\n"); + + return 0; +} + +/* + * Erase an address range on the flash chip. The address range may extend + * one or more erase sectors. Return an error is there is a problem erasing. + */ +static int m25p80_erase(struct spi_flash *flash, u32 offset, size_t len) +{ + struct m25p_spi_flash *m25p = to_m25p_spi_flash(flash); + int ret = 0; + + /* sanity checks */ + if (offset + len > flash->size) { + printf("Size out of range!\n"); + return -EINVAL; + } + + if (len % m25p->erase_size) { + printf("Erase len not 0x%x aligned!", m25p->erase_size); + return -EINVAL; + } + + ret = spi_claim_bus(flash->spi); + if (ret) { + printf("SF: Unable to claim SPI bus\n"); + return ret; + } + + /* whole-chip erase? */ + if (len == flash->size) { + ret = erase_chip(flash); + } else { + while (len) { + ret = erase_sector(flash, offset); + if (ret) + break; + + offset += m25p->erase_size; + len -= m25p->erase_size; + } + } + + spi_release_bus(flash->spi); + return ret; +} + +/* + * Read an address range from the flash chip. The address range + * may be any size provided it is within the physical boundaries. + */ +static int m25p80_read(struct spi_flash *flash, u32 offset, size_t len, + void *buf) +{ + struct m25p_spi_flash *m25p = to_m25p_spi_flash(flash); + u8 cmd[MAX_CMD_SIZE + FAST_READ_DUMMY_BYTE] = { 0 }; + int cmd_sz; + + /* sanity checks */ + if (!len) + return 0; + + if (offset + len > flash->size) { + printf("Size out of range!\n"); + return -EINVAL; + } + + /* NOTE: + * OPCODE_FAST_READ (if available) is faster. + * Should add 1 byte DUMMY_BYTE. + */ + cmd_sz = m25p_cmdsz(m25p) + FAST_READ_DUMMY_BYTE; + + /* Wait till previous write/erase is done. */ + if (wait_till_ready(flash)) { + printf("SF: timeout for ready!\n"); + /* REVISIT status return?? */ + return 1; + } + + /* FIXME switch to OPCODE_FAST_READ. It's required for higher + * clocks; and at this writing, every chip this driver handles + * supports that opcode. + */ + + /* Set up the write data buffer. */ + cmd[0] = OPCODE_READ; + m25p_addr2cmd(m25p, offset, cmd); + + return spi_flash_read_common(flash, cmd, cmd_sz, buf, len); +} + +/* + * Write an address range to the flash chip. Data must be written in + * FLASH_PAGESIZE chunks. The address range may be any size provided + * it is within the physical boundaries. + */ +static int m25p80_write(struct spi_flash *flash, u32 offset, size_t len, + const void *buf) +{ + struct m25p_spi_flash *m25p = to_m25p_spi_flash(flash); + u32 page_offset, page_size; + u8 cmd[MAX_CMD_SIZE + FAST_READ_DUMMY_BYTE] = { 0 }; + int cmd_sz, ret; + + /* sanity checks */ + if (!len) + return 0; + + if (offset + len > flash->size) { + printf("Size out of range!\n"); + return -EINVAL; + } + + cmd_sz = m25p_cmdsz(m25p); + + ret = spi_claim_bus(flash->spi); + if (ret) { + printf("SF: Unable to claim SPI bus\n"); + return ret; + } + /* Wait until finished previous write command. */ + if (wait_till_ready(flash)) { + printf("SF: timeout for ready!\n"); + goto done; + } + + ret = write_enable(flash); + if (ret < 0) { + printf("SF: Write enable failed\n"); + goto done; + } + + /* Set up the opcode in the write buffer. */ + cmd[0] = OPCODE_PP; + m25p_addr2cmd(m25p, offset, cmd); + + page_offset = offset & (m25p->page_size - 1); + + /* do all the bytes fit onto one page? */ + if (page_offset + len <= m25p->page_size) { + ret = spi_flash_cmd_write(flash->spi, cmd, cmd_sz, + buf, len); + if (ret) + printf("SF: m25p program failed\n"); + } else { + u32 i; + + /* the size of data remaining on the first page */ + page_size = m25p->page_size - page_offset; + + ret = spi_flash_cmd_write(flash->spi, cmd, cmd_sz, + buf, page_size); + if (ret) + printf("SF: m25p program failed\n"); + /* write everything in flash->page_size chunks */ + for (i = page_size; i < len; i += page_size) { + page_size = len - i; + if (page_size > m25p->page_size) + page_size = m25p->page_size; + + /* write the next page to flash */ + m25p_addr2cmd(m25p, offset + i, cmd); + + if (wait_till_ready(flash)) { + printf("SF: timeout for ready: 0x%x!\n", + (offset + i)); + goto done; + } + + ret = write_enable(flash); + if (ret < 0) { + printf("SF: Write enable failed\n"); + goto done; + } + + ret = spi_flash_cmd_write(flash->spi, cmd, cmd_sz, + buf + i, page_size); + if (ret) + printf("SF: m25p program failed\n"); + } + } + +done: + spi_release_bus(flash->spi); + return ret; +} + +static int sst_write(struct spi_flash *flash, u32 offset, size_t len, + const void *buf) +{ + struct m25p_spi_flash *m25p = to_m25p_spi_flash(flash); + size_t actual; + int cmd_sz, ret; + u8 cmd[MAX_CMD_SIZE + FAST_READ_DUMMY_BYTE] = { 0 }; + + /* sanity checks */ + if (!len) + return 0; + + if (offset + len > flash->size) + return -EINVAL; + + ret = spi_claim_bus(flash->spi); + if (ret) { + printf("SF: Unable to claim SPI bus\n"); + return ret; + } + /* Wait until finished previous write command. */ + ret = wait_till_ready(flash); + if (ret) { + printf("SF: timeout for ready!\n"); + goto time_out; + } + + ret = write_enable(flash); + if (ret < 0) { + printf("SF: Write enable failed\n"); + goto time_out; + } + + actual = offset % 2; + /* Start write from odd address. */ + if (actual) { + cmd[0] = OPCODE_BP; + m25p_addr2cmd(m25p, offset, cmd); + cmd_sz = m25p_cmdsz(m25p); + + /* write one byte. */ + ret = spi_flash_cmd_write(flash->spi, cmd, cmd_sz, + buf, 1); + ret = wait_till_ready(flash); + if (ret) { + printf("SF: timeout for ready!\n"); + goto time_out; + } + } + offset += actual; + + cmd[0] = OPCODE_AAI_WP; + m25p_addr2cmd(m25p, offset, cmd); + + /* Write out most of the data here. */ + cmd_sz = m25p_cmdsz(m25p); + for (; actual < len - 1; actual += 2) { + ret = spi_flash_cmd_write(flash->spi, cmd, cmd_sz, + buf + actual, 2); + if (ret) { + printf("SF: sst word program failed\n"); + break; + } + ret = wait_till_ready(flash); + if (ret) { + printf("SF: timeout for ready!\n"); + goto time_out; + } + cmd_sz = 1; + offset += 2; + } + write_disable(flash); + ret = wait_till_ready(flash); + if (ret) { + printf("SF: timeout for ready!\n"); + goto time_out; + } + + /* Write out trailing byte if it exists. */ + if (actual != len) { + write_enable(flash); + cmd[0] = OPCODE_BP; + m25p_addr2cmd(m25p, offset, cmd); + cmd_sz = m25p_cmdsz(m25p); + + ret = spi_flash_cmd_write(flash->spi, cmd, cmd_sz, + buf + actual, 1); + if (ret) { + printf("SF: m25p trailing byte program failed\n"); + goto time_out; + } + ret = wait_till_ready(flash); + if (ret) { + printf("SF: timeout for ready!\n"); + goto time_out; + } + write_disable(flash); + } + +time_out: + spi_release_bus(flash->spi); + return ret; +} + +/****************************************************************************/ + +/* + * SPI device driver setup and teardown + */ + +struct flash_info { + /* JEDEC id zero means "no ID" (most older chips); otherwise it has + * a high byte of zero plus three data bytes: the manufacturer id, + * then a two byte device id. + */ + u32 jedec_id; + u16 ext_id; + + /* The size listed here is what works with OPCODE_SE, which isn't + * necessarily called a "sector" by the vendor. + */ + unsigned sector_size; + u16 n_sectors; + + u16 page_size; + u16 addr_width; + + u16 flags; +#define SECT_4K 0x01 /* OPCODE_BE_4K works uniformly */ +#define M25P_NO_ERASE 0x02 /* No erase command needed */ +}; + +#define SPI_NAME_SIZE 32 +#define SPI_MODULE_PREFIX "spi:" + +struct spi_device_id { + s8 name[SPI_NAME_SIZE]; + u32 driver_data; +}; + +#define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \ + ((u32)&(struct flash_info) { \ + .jedec_id = (_jedec_id), \ + .ext_id = (_ext_id), \ + .sector_size = (_sector_size), \ + .n_sectors = (_n_sectors), \ + .page_size = 256, \ + .flags = (_flags), \ + }) + +#define CAT25_INFO(_sector_size, _n_sectors, _page_size, _addr_width) \ + ((u32)&(struct flash_info) { \ + .sector_size = (_sector_size), \ + .n_sectors = (_n_sectors), \ + .page_size = (_page_size), \ + .addr_width = (_addr_width), \ + .flags = M25P_NO_ERASE, \ + }) + +/* NOTE: double check command sets and memory organization when you add + * more flash chips. This current list focusses on newer chips, which + * have been converging on command sets which including JEDEC ID. + */ +static const struct spi_device_id m25p_ids[] = { + /* Atmel -- some are (confusingly) marketed as "DataFlash" */ + { "at25fs010", INFO(0x1f6601, 0, 32 * 1024, 4, SECT_4K) }, + { "at25fs040", INFO(0x1f6604, 0, 64 * 1024, 8, SECT_4K) }, + + { "at25df041a", INFO(0x1f4401, 0, 64 * 1024, 8, SECT_4K) }, + { "at25df641", INFO(0x1f4800, 0, 64 * 1024, 128, SECT_4K) }, + + { "at26f004", INFO(0x1f0400, 0, 64 * 1024, 8, SECT_4K) }, + { "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16, SECT_4K) }, + { "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32, SECT_4K) }, + { "at26df321", INFO(0x1f4700, 0, 64 * 1024, 64, SECT_4K) }, + + /* EON -- en25xxx */ + { "en25f32", INFO(0x1c3116, 0, 64 * 1024, 64, SECT_4K) }, + { "en25p32", INFO(0x1c2016, 0, 64 * 1024, 64, 0) }, + { "en25p64", INFO(0x1c2017, 0, 64 * 1024, 128, 0) }, + + /* Intel/Numonyx -- xxxs33b */ + { "160s33b", INFO(0x898911, 0, 64 * 1024, 32, 0) }, + { "320s33b", INFO(0x898912, 0, 64 * 1024, 64, 0) }, + { "640s33b", INFO(0x898913, 0, 64 * 1024, 128, 0) }, + + /* Macronix */ + { "mx25l4005a", INFO(0xc22013, 0, 64 * 1024, 8, SECT_4K) }, + { "mx25l8005", INFO(0xc22014, 0, 64 * 1024, 16, 0) }, + { "mx25l1606e", INFO(0xc22015, 0, 64 * 1024, 32, SECT_4K) }, + { "mx25l3205d", INFO(0xc22016, 0, 64 * 1024, 64, 0) }, + { "mx25l6405d", INFO(0xc22017, 0, 64 * 1024, 128, 0) }, + { "mx25l12805d", INFO(0xc22018, 0, 64 * 1024, 256, 0) }, + { "mx25l12855e", INFO(0xc22618, 0, 64 * 1024, 256, 0) }, + { "mx25l25635e", INFO(0xc22019, 0, 64 * 1024, 512, 0) }, + { "mx25l25655e", INFO(0xc22619, 0, 64 * 1024, 512, 0) }, + + /* Spansion -- single (large) sector size only, at least + * for the chips listed here (without boot sectors). + */ + { "s25sl004a", INFO(0x010212, 0, 64 * 1024, 8, 0) }, + { "s25sl008a", INFO(0x010213, 0, 64 * 1024, 16, 0) }, + { "s25sl016a", INFO(0x010214, 0, 64 * 1024, 32, 0) }, + { "s25sl032a", INFO(0x010215, 0, 64 * 1024, 64, 0) }, + { "s25sl032p", INFO(0x010215, 0x4d00, 64 * 1024, 64, SECT_4K) }, + { "s25sl064a", INFO(0x010216, 0, 64 * 1024, 128, 0) }, + { "s25fl256s0", INFO(0x010219, 0x4d00, 256 * 1024, 128, 0) }, + { "s25fl256s1", INFO(0x010219, 0x4d01, 64 * 1024, 512, 0) }, + { "s25fl512s", INFO(0x010220, 0x4d00, 256 * 1024, 256, 0) }, + { "s70fl01gs", INFO(0x010221, 0x4d00, 256 * 1024, 256, 0) }, + { "s25sl12800", INFO(0x012018, 0x0300, 256 * 1024, 64, 0) }, + { "s25sl12801", INFO(0x012018, 0x0301, 64 * 1024, 256, 0) }, + { "s25fl129p0", INFO(0x012018, 0x4d00, 256 * 1024, 64, 0) }, + { "s25fl129p1", INFO(0x012018, 0x4d01, 64 * 1024, 256, 0) }, + { "s25fl016k", INFO(0xef4015, 0, 64 * 1024, 32, SECT_4K) }, + { "s25fl064k", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) }, + + /* SST -- large erase sizes are "overlays", "sectors" are 4K */ + { "sst25vf040b", INFO(0xbf258d, 0, 64 * 1024, 8, SECT_4K) }, + { "sst25vf080b", INFO(0xbf258e, 0, 64 * 1024, 16, SECT_4K) }, + { "sst25vf016b", INFO(0xbf2541, 0, 64 * 1024, 32, SECT_4K) }, + { "sst25vf032b", INFO(0xbf254a, 0, 64 * 1024, 64, SECT_4K) }, + { "sst25wf512", INFO(0xbf2501, 0, 64 * 1024, 1, SECT_4K) }, + { "sst25wf010", INFO(0xbf2502, 0, 64 * 1024, 2, SECT_4K) }, + { "sst25wf020", INFO(0xbf2503, 0, 64 * 1024, 4, SECT_4K) }, + { "sst25wf040", INFO(0xbf2504, 0, 64 * 1024, 8, SECT_4K) }, + + /* ST Microelectronics -- newer production may have feature updates */ + { "m25p05", INFO(0x202010, 0, 32 * 1024, 2, 0) }, + { "m25p10", INFO(0x202011, 0, 32 * 1024, 4, 0) }, + { "m25p20", INFO(0x202012, 0, 64 * 1024, 4, 0) }, + { "m25p40", INFO(0x202013, 0, 64 * 1024, 8, 0) }, + { "m25p80", INFO(0x202014, 0, 64 * 1024, 16, 0) }, + { "m25p16", INFO(0x202015, 0, 64 * 1024, 32, 0) }, + { "m25p32", INFO(0x202016, 0, 64 * 1024, 64, 0) }, + { "m25p64", INFO(0x202017, 0, 64 * 1024, 128, 0) }, + { "m25p128", INFO(0x202018, 0, 256 * 1024, 64, 0) }, + + { "m25p05-nonjedec", INFO(0, 0, 32 * 1024, 2, 0) }, + { "m25p10-nonjedec", INFO(0, 0, 32 * 1024, 4, 0) }, + { "m25p20-nonjedec", INFO(0, 0, 64 * 1024, 4, 0) }, + { "m25p40-nonjedec", INFO(0, 0, 64 * 1024, 8, 0) }, + { "m25p80-nonjedec", INFO(0, 0, 64 * 1024, 16, 0) }, + { "m25p16-nonjedec", INFO(0, 0, 64 * 1024, 32, 0) }, + { "m25p32-nonjedec", INFO(0, 0, 64 * 1024, 64, 0) }, + { "m25p64-nonjedec", INFO(0, 0, 64 * 1024, 128, 0) }, + { "m25p128-nonjedec", INFO(0, 0, 256 * 1024, 64, 0) }, + + { "m45pe10", INFO(0x204011, 0, 64 * 1024, 2, 0) }, + { "m45pe80", INFO(0x204014, 0, 64 * 1024, 16, 0) }, + { "m45pe16", INFO(0x204015, 0, 64 * 1024, 32, 0) }, + + { "m25pe80", INFO(0x208014, 0, 64 * 1024, 16, 0) }, + { "m25pe16", INFO(0x208015, 0, 64 * 1024, 32, SECT_4K) }, + + { "m25px32", INFO(0x207116, 0, 64 * 1024, 64, SECT_4K) }, + { "m25px32-s0", INFO(0x207316, 0, 64 * 1024, 64, SECT_4K) }, + { "m25px32-s1", INFO(0x206316, 0, 64 * 1024, 64, SECT_4K) }, + { "m25px64", INFO(0x207117, 0, 64 * 1024, 128, 0) }, + + /* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */ + { "w25x10", INFO(0xef3011, 0, 64 * 1024, 2, SECT_4K) }, + { "w25x20", INFO(0xef3012, 0, 64 * 1024, 4, SECT_4K) }, + { "w25x40", INFO(0xef3013, 0, 64 * 1024, 8, SECT_4K) }, + { "w25x80", INFO(0xef3014, 0, 64 * 1024, 16, SECT_4K) }, + { "w25x16", INFO(0xef3015, 0, 64 * 1024, 32, SECT_4K) }, + { "w25x32", INFO(0xef3016, 0, 64 * 1024, 64, SECT_4K) }, + { "w25q32", INFO(0xef4016, 0, 64 * 1024, 64, SECT_4K) }, + { "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, SECT_4K) }, + { "w25q64", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) }, + + /* Catalyst / On Semiconductor -- non-JEDEC */ + { "cat25c11", CAT25_INFO(16, 8, 16, 1) }, + { "cat25c03", CAT25_INFO(32, 8, 16, 2) }, + { "cat25c09", CAT25_INFO(128, 8, 32, 2) }, + { "cat25c17", CAT25_INFO(256, 8, 32, 2) }, + { "cat25128", CAT25_INFO(2048, 8, 64, 2) }, + { }, +}; + +static const struct spi_device_id *jedec_probe(u8 *idcode) +{ + int tmp; + u32 jedec; + u16 ext_jedec; + struct flash_info *info; + + jedec = idcode[0]; + jedec = jedec << 8; + jedec |= idcode[1]; + jedec = jedec << 8; + jedec |= idcode[2]; + + ext_jedec = idcode[3] << 8 | idcode[4]; + + for (tmp = 0; tmp < ARRAY_SIZE(m25p_ids) - 1; tmp++) { + info = (void *)m25p_ids[tmp].driver_data; + if (info->jedec_id == jedec) { + if (info->ext_id != 0 && info->ext_id != ext_jedec) + continue; + printf("JEDEC ID: 0x%x\n", jedec); + + return &m25p_ids[tmp]; + } + } + printf("unrecognized JEDEC id %06x\n", jedec); + return NULL; +} + + +/* + * board specific setup should have ensured the SPI clock used here + * matches what the READ command supports, at least until this driver + * understands FAST_READ (for clocks over 25 MHz). + */ +struct spi_flash * +spi_flash_probe_m25p(struct spi_slave *spi, u8 *idcode) +{ + struct m25p_spi_flash *m25p; + struct flash_info *info; + const struct spi_device_id *jid; + + jid = jedec_probe(idcode); + if (!jid) + return NULL; + + info = (void *)jid->driver_data; + + m25p = malloc(sizeof(*m25p)); + if (!m25p) + return NULL; + + /* + * Atmel, SST and Intel/Numonyx serial flash tend to power + * up with the software protection bits set + */ + + if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ATMEL || + JEDEC_MFR(info->jedec_id) == CFI_MFR_INTEL || + JEDEC_MFR(info->jedec_id) == CFI_MFR_SST) { + if (write_enable(&m25p->flash) < 0) { + printf("SF: Write enable failed\n"); + goto err_rtn; + } + if (write_sr(&m25p->flash, 0) < 0) { + printf("SF: Write sr failed\n"); + goto err_rtn; + } + } + + m25p->flash.spi = spi; + m25p->flash.name = (const char *)jid->name; + m25p->flash.erase = m25p80_erase; + m25p->flash.read = m25p80_read; + m25p->flash.size = info->sector_size * info->n_sectors; + + /* sst flash chips use AAI word program */ + if (JEDEC_MFR(info->jedec_id) == CFI_MFR_SST) + m25p->flash.write = sst_write; + else + m25p->flash.write = m25p80_write; + + /* prefer "small sector" erase if possible */ + if (info->flags & SECT_4K) { + m25p->erase_opcode = OPCODE_BE_4K; + m25p->erase_size = 4096; + } else { + m25p->erase_opcode = OPCODE_SE; + m25p->erase_size = info->sector_size; + } + + m25p->page_size = info->page_size; + + if (info->addr_width) + m25p->addr_width = info->addr_width; + else { + /* enable 4-byte addressing if the device exceeds 16MiB */ + if (m25p->flash.size > 0x1000000) { + m25p->addr_width = 4; + set_4byte(&m25p->flash, info->jedec_id, 1); + } else + m25p->addr_width = 3; + } + + printf("%s (%lld Kbytes)\n", jid->name, + (long long)m25p->flash.size >> 10); + + return &m25p->flash; +err_rtn: + free(m25p); + return NULL; +} diff --git a/drivers/mtd/spi/spi_flash.c b/drivers/mtd/spi/spi_flash.c index ced4c94..a7db8d2 100644 --- a/drivers/mtd/spi/spi_flash.c +++ b/drivers/mtd/spi/spi_flash.c @@ -277,6 +277,16 @@ static const struct { #ifdef CONFIG_SPI_FLASH_EON { 0, 0x1c, spi_flash_probe_eon, }, #endif +#ifdef CONFIG_SPI_FLASH_M25P80 + { 0, 0x01, spi_flash_probe_m25p, }, + { 0, 0x1c, spi_flash_probe_m25p, }, + { 0, 0x1f, spi_flash_probe_m25p, }, + { 0, 0x20, spi_flash_probe_m25p, }, + { 0, 0x89, spi_flash_probe_m25p, }, + { 0, 0xbf, spi_flash_probe_m25p, }, + { 0, 0xc2, spi_flash_probe_m25p, }, + { 0, 0xef, spi_flash_probe_m25p, }, +#endif #ifdef CONFIG_SPI_FLASH_MACRONIX { 0, 0xc2, spi_flash_probe_macronix, }, #endif diff --git a/drivers/mtd/spi/spi_flash_internal.h b/drivers/mtd/spi/spi_flash_internal.h index 91e036a..5d75025 100644 --- a/drivers/mtd/spi/spi_flash_internal.h +++ b/drivers/mtd/spi/spi_flash_internal.h @@ -95,6 +95,7 @@ int spi_flash_cmd_erase(struct spi_flash *flash, u8 erase_cmd, struct spi_flash *spi_flash_probe_spansion(struct spi_slave *spi, u8 *idcode); struct spi_flash *spi_flash_probe_atmel(struct spi_slave *spi, u8 *idcode); struct spi_flash *spi_flash_probe_eon(struct spi_slave *spi, u8 *idcode); +struct spi_flash *spi_flash_probe_m25p(struct spi_slave *spi, u8 *idcode); struct spi_flash *spi_flash_probe_macronix(struct spi_slave *spi, u8 *idcode); struct spi_flash *spi_flash_probe_sst(struct spi_slave *spi, u8 *idcode); struct spi_flash *spi_flash_probe_stmicro(struct spi_slave *spi, u8 *idcode);