GK SDK 源码库: XMIPCLinuxV100R005C00SPC030 (kernel/tools/open_source excluded)
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
menu "SPI Flash Support"
|
||||
|
||||
config DM_SPI_FLASH
|
||||
bool "Enable Driver Model for SPI flash"
|
||||
depends on DM && DM_SPI
|
||||
imply SPI_FLASH
|
||||
help
|
||||
Enable driver model for SPI flash. This SPI flash interface
|
||||
(spi_flash_probe(), spi_flash_write(), etc.) is then
|
||||
implemented by the SPI flash uclass. There is one standard
|
||||
SPI flash driver which knows how to probe most chips
|
||||
supported by U-Boot. The uclass interface is defined in
|
||||
include/spi_flash.h, but is currently fully compatible
|
||||
with the old interface to avoid confusion and duplication
|
||||
during the transition parent. SPI and SPI flash must be
|
||||
enabled together (it is not possible to use driver model
|
||||
for one and not the other).
|
||||
|
||||
config SPI_FLASH_SANDBOX
|
||||
bool "Support sandbox SPI flash device"
|
||||
depends on SANDBOX && DM_SPI_FLASH
|
||||
help
|
||||
Since sandbox cannot access real devices, an emulation mechanism is
|
||||
provided instead. Drivers can be connected up to the sandbox SPI
|
||||
bus (see CONFIG_SANDBOX_SPI) and SPI traffic will be routed to this
|
||||
device. Typically the contents of the emulated SPI flash device is
|
||||
stored in a file on the host filesystem.
|
||||
|
||||
config SPI_FLASH
|
||||
bool "SPI Flash Core Interface support"
|
||||
select SPI_MEM
|
||||
help
|
||||
Enable the SPI flash Core support. This will include basic
|
||||
standard support for things like probing, read / write, and
|
||||
erasing through cmd_sf interface.
|
||||
|
||||
If unsure, say N
|
||||
|
||||
config SF_DEFAULT_BUS
|
||||
int "SPI Flash default bus identifier"
|
||||
depends on SPI_FLASH || DM_SPI_FLASH
|
||||
default 0
|
||||
help
|
||||
The default bus may be provided by the platform
|
||||
to handle the common case when only a single serial
|
||||
flash is present on the system.
|
||||
|
||||
config SF_DEFAULT_CS
|
||||
int "SPI Flash default Chip-select"
|
||||
depends on SPI_FLASH || DM_SPI_FLASH
|
||||
default 0
|
||||
help
|
||||
The default chip select may be provided by the platform
|
||||
to handle the common case when only a single serial
|
||||
flash is present on the system.
|
||||
|
||||
config SF_DEFAULT_MODE
|
||||
hex "SPI Flash default mode (see include/spi.h)"
|
||||
depends on SPI_FLASH || DM_SPI_FLASH
|
||||
default 3
|
||||
help
|
||||
The default mode may be provided by the platform
|
||||
to handle the common case when only a single serial
|
||||
flash is present on the system.
|
||||
Not used for boot with device tree; the SPI driver reads
|
||||
speed and mode from platdata values computed from
|
||||
available node.
|
||||
|
||||
config SF_DEFAULT_SPEED
|
||||
int "SPI Flash default speed in Hz"
|
||||
depends on SPI_FLASH || DM_SPI_FLASH
|
||||
default 1000000
|
||||
help
|
||||
The default speed may be provided by the platform
|
||||
to handle the common case when only a single serial
|
||||
flash is present on the system.
|
||||
Not used for boot with device tree; the SPI driver reads
|
||||
speed and mode from platdata values computed from
|
||||
available node.
|
||||
|
||||
if SPI_FLASH
|
||||
|
||||
config SPI_FLASH_SFDP_SUPPORT
|
||||
bool "SFDP table parsing support for SPI NOR flashes"
|
||||
depends on !SPI_FLASH_BAR
|
||||
help
|
||||
Enable support for parsing and auto discovery of parameters for
|
||||
SPI NOR flashes using Serial Flash Discoverable Parameters (SFDP)
|
||||
tables as per JESD216 standard.
|
||||
|
||||
config SPI_FLASH_BAR
|
||||
bool "SPI flash Bank/Extended address register support"
|
||||
help
|
||||
Enable the SPI flash Bank/Extended address register support.
|
||||
Bank/Extended address registers are used to access the flash
|
||||
which has size > 16MiB in 3-byte addressing.
|
||||
|
||||
config SF_DUAL_FLASH
|
||||
bool "SPI DUAL flash memory support"
|
||||
help
|
||||
Enable this option to support two flash memories connected to a single
|
||||
controller. Currently Xilinx Zynq qspi supports this.
|
||||
|
||||
config SPI_FLASH_ATMEL
|
||||
bool "Atmel SPI flash support"
|
||||
help
|
||||
Add support for various Atmel SPI flash chips (AT45xxx and AT25xxx)
|
||||
|
||||
config SPI_FLASH_EON
|
||||
bool "EON SPI flash support"
|
||||
help
|
||||
Add support for various EON SPI flash chips (EN25xxx)
|
||||
|
||||
config SPI_FLASH_GIGADEVICE
|
||||
bool "GigaDevice SPI flash support"
|
||||
help
|
||||
Add support for various GigaDevice SPI flash chips (GD25xxx)
|
||||
|
||||
config SPI_FLASH_ISSI
|
||||
bool "ISSI SPI flash support"
|
||||
help
|
||||
Add support for various ISSI SPI flash chips (ISxxx)
|
||||
|
||||
config SPI_FLASH_MACRONIX
|
||||
bool "Macronix SPI flash support"
|
||||
help
|
||||
Add support for various Macronix SPI flash chips (MX25Lxxx)
|
||||
|
||||
config SPI_FLASH_SPANSION
|
||||
bool "Spansion SPI flash support"
|
||||
help
|
||||
Add support for various Spansion SPI flash chips (S25FLxxx)
|
||||
|
||||
config SPI_FLASH_STMICRO
|
||||
bool "STMicro SPI flash support"
|
||||
help
|
||||
Add support for various STMicro SPI flash chips (M25Pxxx and N25Qxxx)
|
||||
|
||||
config SPI_FLASH_SST
|
||||
bool "SST SPI flash support"
|
||||
help
|
||||
Add support for various SST SPI flash chips (SST25xxx)
|
||||
|
||||
config SPI_FLASH_WINBOND
|
||||
bool "Winbond SPI flash support"
|
||||
help
|
||||
Add support for various Winbond SPI flash chips (W25xxx)
|
||||
|
||||
config SPI_FLASH_XMC
|
||||
bool "XMC SPI flash support"
|
||||
help
|
||||
Add support for various XMC (Wuhan Xinxin Semiconductor
|
||||
Manufacturing Corp.) SPI flash chips (XM25xxx)
|
||||
|
||||
endif
|
||||
|
||||
config SPI_FLASH_USE_4K_SECTORS
|
||||
bool "Use small 4096 B erase sectors"
|
||||
depends on SPI_FLASH
|
||||
default y
|
||||
help
|
||||
Many flash memories support erasing small (4096 B) sectors. Depending
|
||||
on the usage this feature may provide performance gain in comparison
|
||||
to erasing whole blocks (32/64 KiB).
|
||||
Changing a small part of the flash's contents is usually faster with
|
||||
small sectors. On the other hand erasing should be faster when using
|
||||
64 KiB block instead of 16 × 4 KiB sectors.
|
||||
|
||||
Please note that some tools/drivers/filesystems may not work with
|
||||
4096 B erase size (e.g. UBIFS requires 15 KiB as a minimum).
|
||||
|
||||
config SPI_FLASH_DATAFLASH
|
||||
bool "AT45xxx DataFlash support"
|
||||
depends on SPI_FLASH && DM_SPI_FLASH
|
||||
help
|
||||
Enable the access for SPI-flash-based AT45xxx DataFlash chips.
|
||||
DataFlash is a kind of SPI flash. Most AT45 chips have two buffers
|
||||
in each chip, which may be used for double buffered I/O; but this
|
||||
driver doesn't (yet) use these for any kind of i/o overlap or prefetching.
|
||||
|
||||
Sometimes DataFlash is packaged in MMC-format cards, although the
|
||||
MMC stack can't (yet?) distinguish between MMC and DataFlash
|
||||
protocols during enumeration.
|
||||
|
||||
If unsure, say N
|
||||
|
||||
config SPI_FLASH_MTD
|
||||
bool "SPI Flash MTD support"
|
||||
depends on SPI_FLASH && MTD
|
||||
help
|
||||
Enable the MTD support for spi flash layer, this adapter is for
|
||||
translating mtd_read/mtd_write commands into spi_flash_read/write
|
||||
commands. It is not intended to use it within sf_cmd or the SPI
|
||||
flash subsystem. Such an adapter is needed for subsystems like
|
||||
UBI which can only operate on top of the MTD layer.
|
||||
|
||||
If unsure, say N
|
||||
|
||||
config SPL_SPI_FLASH_MTD
|
||||
bool "SPI flash MTD support for SPL"
|
||||
depends on SPI_FLASH
|
||||
help
|
||||
Enable the MTD support for the SPI flash layer in SPL.
|
||||
|
||||
If unsure, say N
|
||||
|
||||
endmenu # menu "SPI Flash Support"
|
||||
@@ -0,0 +1,23 @@
|
||||
# SPDX-License-Identifier: GPL-2.0+
|
||||
#
|
||||
# (C) Copyright 2006
|
||||
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
|
||||
|
||||
obj-$(CONFIG_DM_SPI_FLASH) += sf-uclass.o
|
||||
spi-nor-y := sf_probe.o spi-nor-ids.o
|
||||
|
||||
ifdef CONFIG_SPL_BUILD
|
||||
obj-$(CONFIG_SPL_SPI_BOOT) += fsl_espi_spl.o
|
||||
ifeq ($(CONFIG_SPL_SPI_FLASH_TINY),y)
|
||||
spi-nor-y += spi-nor-tiny.o
|
||||
else
|
||||
spi-nor-y += spi-nor-core.o
|
||||
endif
|
||||
else
|
||||
spi-nor-y += spi-nor-core.o
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_SPI_FLASH) += spi-nor.o
|
||||
obj-$(CONFIG_SPI_FLASH_DATAFLASH) += sf_dataflash.o
|
||||
obj-$(CONFIG_$(SPL_)SPI_FLASH_MTD) += sf_mtd.o
|
||||
obj-$(CONFIG_SPI_FLASH_SANDBOX) += sandbox.o
|
||||
@@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright 2013 Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <cpu_func.h>
|
||||
#include <spi_flash.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#define ESPI_BOOT_IMAGE_SIZE 0x48
|
||||
#define ESPI_BOOT_IMAGE_ADDR 0x50
|
||||
#define CONFIG_CFG_DATA_SECTOR 0
|
||||
|
||||
void fsl_spi_spl_load_image(uint32_t offs, unsigned int size, void *vdst)
|
||||
{
|
||||
struct spi_flash *flash;
|
||||
|
||||
flash = spi_flash_probe(CONFIG_ENV_SPI_BUS, CONFIG_ENV_SPI_CS,
|
||||
CONFIG_ENV_SPI_MAX_HZ, CONFIG_ENV_SPI_MODE);
|
||||
if (flash == NULL) {
|
||||
puts("\nspi_flash_probe failed");
|
||||
hang();
|
||||
}
|
||||
|
||||
spi_flash_read(flash, offs, size, vdst);
|
||||
}
|
||||
|
||||
/*
|
||||
* The main entry for SPI booting. It's necessary that SDRAM is already
|
||||
* configured and available since this code loads the main U-Boot image
|
||||
* from SPI into SDRAM and starts it from there.
|
||||
*/
|
||||
void fsl_spi_boot(void)
|
||||
{
|
||||
void (*uboot)(void) __noreturn;
|
||||
u32 offset, code_len, copy_len = 0;
|
||||
#ifndef CONFIG_FSL_CORENET
|
||||
unsigned char *buf = NULL;
|
||||
#endif
|
||||
struct spi_flash *flash;
|
||||
|
||||
flash = spi_flash_probe(CONFIG_ENV_SPI_BUS, CONFIG_ENV_SPI_CS,
|
||||
CONFIG_ENV_SPI_MAX_HZ, CONFIG_ENV_SPI_MODE);
|
||||
if (flash == NULL) {
|
||||
puts("\nspi_flash_probe failed");
|
||||
hang();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_FSL_CORENET
|
||||
offset = CONFIG_SYS_SPI_FLASH_U_BOOT_OFFS;
|
||||
code_len = CONFIG_SYS_SPI_FLASH_U_BOOT_SIZE;
|
||||
#else
|
||||
/*
|
||||
* Load U-Boot image from SPI flash into RAM
|
||||
*/
|
||||
buf = malloc(flash->page_size);
|
||||
if (buf == NULL) {
|
||||
puts("\nmalloc failed");
|
||||
hang();
|
||||
}
|
||||
memset(buf, 0, flash->page_size);
|
||||
|
||||
spi_flash_read(flash, CONFIG_CFG_DATA_SECTOR,
|
||||
flash->page_size, (void *)buf);
|
||||
offset = *(u32 *)(buf + ESPI_BOOT_IMAGE_ADDR);
|
||||
/* Skip spl code */
|
||||
offset += CONFIG_SYS_SPI_FLASH_U_BOOT_OFFS;
|
||||
/* Get the code size from offset 0x48 */
|
||||
code_len = *(u32 *)(buf + ESPI_BOOT_IMAGE_SIZE);
|
||||
/* Skip spl code */
|
||||
code_len = code_len - CONFIG_SPL_MAX_SIZE;
|
||||
#endif
|
||||
/* copy code to DDR */
|
||||
printf("Loading second stage boot loader ");
|
||||
while (copy_len <= code_len) {
|
||||
spi_flash_read(flash, offset + copy_len, 0x2000,
|
||||
(void *)(CONFIG_SYS_SPI_FLASH_U_BOOT_DST
|
||||
+ copy_len));
|
||||
copy_len = copy_len + 0x2000;
|
||||
putc('.');
|
||||
}
|
||||
|
||||
/*
|
||||
* Jump to U-Boot image
|
||||
*/
|
||||
flush_cache(CONFIG_SYS_SPI_FLASH_U_BOOT_DST, code_len);
|
||||
uboot = (void *)CONFIG_SYS_SPI_FLASH_U_BOOT_START;
|
||||
(*uboot)();
|
||||
}
|
||||
@@ -0,0 +1,607 @@
|
||||
/*
|
||||
* Simulate a SPI flash
|
||||
*
|
||||
* Copyright (c) 2011-2013 The Chromium OS Authors.
|
||||
* See file CREDITS for list of people who contributed to this
|
||||
* project.
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#define LOG_CATEGORY UCLASS_SPI_FLASH
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <malloc.h>
|
||||
#include <spi.h>
|
||||
#include <os.h>
|
||||
|
||||
#include <spi_flash.h>
|
||||
#include "sf_internal.h"
|
||||
|
||||
#include <asm/getopt.h>
|
||||
#include <asm/spi.h>
|
||||
#include <asm/state.h>
|
||||
#include <dm/device-internal.h>
|
||||
#include <dm/lists.h>
|
||||
#include <dm/uclass-internal.h>
|
||||
|
||||
/*
|
||||
* The different states that our SPI flash transitions between.
|
||||
* We need to keep track of this across multiple xfer calls since
|
||||
* the SPI bus could possibly call down into us multiple times.
|
||||
*/
|
||||
enum sandbox_sf_state {
|
||||
SF_CMD, /* default state -- we're awaiting a command */
|
||||
SF_ID, /* read the flash's (jedec) ID code */
|
||||
SF_ADDR, /* processing the offset in the flash to read/etc... */
|
||||
SF_READ, /* reading data from the flash */
|
||||
SF_WRITE, /* writing data to the flash, i.e. page programming */
|
||||
SF_ERASE, /* erase the flash */
|
||||
SF_READ_STATUS, /* read the flash's status register */
|
||||
SF_READ_STATUS1, /* read the flash's status register upper 8 bits*/
|
||||
SF_WRITE_STATUS, /* write the flash's status register */
|
||||
};
|
||||
|
||||
#if CONFIG_IS_ENABLED(LOG)
|
||||
static const char *sandbox_sf_state_name(enum sandbox_sf_state state)
|
||||
{
|
||||
static const char * const states[] = {
|
||||
"CMD", "ID", "ADDR", "READ", "WRITE", "ERASE", "READ_STATUS",
|
||||
"READ_STATUS1", "WRITE_STATUS",
|
||||
};
|
||||
return states[state];
|
||||
}
|
||||
#endif /* LOG */
|
||||
|
||||
/* Bits for the status register */
|
||||
#define STAT_WIP (1 << 0)
|
||||
#define STAT_WEL (1 << 1)
|
||||
#define STAT_BP_SHIFT 2
|
||||
#define STAT_BP_MASK (7 << STAT_BP_SHIFT)
|
||||
|
||||
/* Assume all SPI flashes have 3 byte addresses since they do atm */
|
||||
#define SF_ADDR_LEN 3
|
||||
|
||||
#define IDCODE_LEN 3
|
||||
|
||||
/* Used to quickly bulk erase backing store */
|
||||
static u8 sandbox_sf_0xff[0x1000];
|
||||
|
||||
/* Internal state data for each SPI flash */
|
||||
struct sandbox_spi_flash {
|
||||
unsigned int cs; /* Chip select we are attached to */
|
||||
/*
|
||||
* As we receive data over the SPI bus, our flash transitions
|
||||
* between states. For example, we start off in the SF_CMD
|
||||
* state where the first byte tells us what operation to perform
|
||||
* (such as read or write the flash). But the operation itself
|
||||
* can go through a few states such as first reading in the
|
||||
* offset in the flash to perform the requested operation.
|
||||
* Thus "state" stores the exact state that our machine is in
|
||||
* while "cmd" stores the overall command we're processing.
|
||||
*/
|
||||
enum sandbox_sf_state state;
|
||||
uint cmd;
|
||||
/* Erase size of current erase command */
|
||||
uint erase_size;
|
||||
/* Current position in the flash; used when reading/writing/etc... */
|
||||
uint off;
|
||||
/* How many address bytes we've consumed */
|
||||
uint addr_bytes, pad_addr_bytes;
|
||||
/* The current flash status (see STAT_XXX defines above) */
|
||||
u16 status;
|
||||
/* Data describing the flash we're emulating */
|
||||
const struct flash_info *data;
|
||||
/* The file on disk to serv up data from */
|
||||
int fd;
|
||||
};
|
||||
|
||||
struct sandbox_spi_flash_plat_data {
|
||||
const char *filename;
|
||||
const char *device_name;
|
||||
int bus;
|
||||
int cs;
|
||||
};
|
||||
|
||||
void sandbox_sf_set_block_protect(struct udevice *dev, int bp_mask)
|
||||
{
|
||||
struct sandbox_spi_flash *sbsf = dev_get_priv(dev);
|
||||
|
||||
sbsf->status &= ~STAT_BP_MASK;
|
||||
sbsf->status |= bp_mask << STAT_BP_SHIFT;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a very strange probe function. If it has platform data (which may
|
||||
* have come from the device tree) then this function gets the filename and
|
||||
* device type from there.
|
||||
*/
|
||||
static int sandbox_sf_probe(struct udevice *dev)
|
||||
{
|
||||
/* spec = idcode:file */
|
||||
struct sandbox_spi_flash *sbsf = dev_get_priv(dev);
|
||||
size_t len, idname_len;
|
||||
const struct flash_info *data;
|
||||
struct sandbox_spi_flash_plat_data *pdata = dev_get_platdata(dev);
|
||||
struct sandbox_state *state = state_get_current();
|
||||
struct dm_spi_slave_platdata *slave_plat;
|
||||
struct udevice *bus = dev->parent;
|
||||
const char *spec = NULL;
|
||||
struct udevice *emul;
|
||||
int ret = 0;
|
||||
int cs = -1;
|
||||
|
||||
debug("%s: bus %d, looking for emul=%p: ", __func__, bus->seq, dev);
|
||||
ret = sandbox_spi_get_emul(state, bus, dev, &emul);
|
||||
if (ret) {
|
||||
printf("Error: Unknown chip select for device '%s'\n",
|
||||
dev->name);
|
||||
return ret;
|
||||
}
|
||||
slave_plat = dev_get_parent_platdata(dev);
|
||||
cs = slave_plat->cs;
|
||||
debug("found at cs %d\n", cs);
|
||||
|
||||
if (!pdata->filename) {
|
||||
printf("Error: No filename available\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
spec = strchr(pdata->device_name, ',');
|
||||
if (spec)
|
||||
spec++;
|
||||
else
|
||||
spec = pdata->device_name;
|
||||
idname_len = strlen(spec);
|
||||
debug("%s: device='%s'\n", __func__, spec);
|
||||
|
||||
for (data = spi_nor_ids; data->name; data++) {
|
||||
len = strlen(data->name);
|
||||
if (idname_len != len)
|
||||
continue;
|
||||
if (!strncasecmp(spec, data->name, len))
|
||||
break;
|
||||
}
|
||||
if (!data->name) {
|
||||
printf("%s: unknown flash '%*s'\n", __func__, (int)idname_len,
|
||||
spec);
|
||||
ret = -EINVAL;
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (sandbox_sf_0xff[0] == 0x00)
|
||||
memset(sandbox_sf_0xff, 0xff, sizeof(sandbox_sf_0xff));
|
||||
|
||||
sbsf->fd = os_open(pdata->filename, 02);
|
||||
if (sbsf->fd == -1) {
|
||||
printf("%s: unable to open file '%s'\n", __func__,
|
||||
pdata->filename);
|
||||
ret = -EIO;
|
||||
goto error;
|
||||
}
|
||||
|
||||
sbsf->data = data;
|
||||
sbsf->cs = cs;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
debug("%s: Got error %d\n", __func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sandbox_sf_remove(struct udevice *dev)
|
||||
{
|
||||
struct sandbox_spi_flash *sbsf = dev_get_priv(dev);
|
||||
|
||||
os_close(sbsf->fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sandbox_sf_cs_activate(struct udevice *dev)
|
||||
{
|
||||
struct sandbox_spi_flash *sbsf = dev_get_priv(dev);
|
||||
|
||||
log_content("sandbox_sf: CS activated; state is fresh!\n");
|
||||
|
||||
/* CS is asserted, so reset state */
|
||||
sbsf->off = 0;
|
||||
sbsf->addr_bytes = 0;
|
||||
sbsf->pad_addr_bytes = 0;
|
||||
sbsf->state = SF_CMD;
|
||||
sbsf->cmd = SF_CMD;
|
||||
}
|
||||
|
||||
static void sandbox_sf_cs_deactivate(struct udevice *dev)
|
||||
{
|
||||
log_content("sandbox_sf: CS deactivated; cmd done processing!\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* There are times when the data lines are allowed to tristate. What
|
||||
* is actually sensed on the line depends on the hardware. It could
|
||||
* always be 0xFF/0x00 (if there are pull ups/downs), or things could
|
||||
* float and so we'd get garbage back. This func encapsulates that
|
||||
* scenario so we can worry about the details here.
|
||||
*/
|
||||
static void sandbox_spi_tristate(u8 *buf, uint len)
|
||||
{
|
||||
/* XXX: make this into a user config option ? */
|
||||
memset(buf, 0xff, len);
|
||||
}
|
||||
|
||||
/* Figure out what command this stream is telling us to do */
|
||||
static int sandbox_sf_process_cmd(struct sandbox_spi_flash *sbsf, const u8 *rx,
|
||||
u8 *tx)
|
||||
{
|
||||
enum sandbox_sf_state oldstate = sbsf->state;
|
||||
|
||||
/* We need to output a byte for the cmd byte we just ate */
|
||||
if (tx)
|
||||
sandbox_spi_tristate(tx, 1);
|
||||
|
||||
sbsf->cmd = rx[0];
|
||||
switch (sbsf->cmd) {
|
||||
case SPINOR_OP_RDID:
|
||||
sbsf->state = SF_ID;
|
||||
sbsf->cmd = SF_ID;
|
||||
break;
|
||||
case SPINOR_OP_READ_FAST:
|
||||
sbsf->pad_addr_bytes = 1;
|
||||
case SPINOR_OP_READ:
|
||||
case SPINOR_OP_PP:
|
||||
sbsf->state = SF_ADDR;
|
||||
break;
|
||||
case SPINOR_OP_WRDI:
|
||||
debug(" write disabled\n");
|
||||
sbsf->status &= ~STAT_WEL;
|
||||
break;
|
||||
case SPINOR_OP_RDSR:
|
||||
sbsf->state = SF_READ_STATUS;
|
||||
break;
|
||||
case SPINOR_OP_RDSR2:
|
||||
sbsf->state = SF_READ_STATUS1;
|
||||
break;
|
||||
case SPINOR_OP_WREN:
|
||||
debug(" write enabled\n");
|
||||
sbsf->status |= STAT_WEL;
|
||||
break;
|
||||
case SPINOR_OP_WRSR:
|
||||
sbsf->state = SF_WRITE_STATUS;
|
||||
break;
|
||||
default: {
|
||||
int flags = sbsf->data->flags;
|
||||
|
||||
/* we only support erase here */
|
||||
if (sbsf->cmd == SPINOR_OP_CHIP_ERASE) {
|
||||
sbsf->erase_size = sbsf->data->sector_size *
|
||||
sbsf->data->n_sectors;
|
||||
} else if (sbsf->cmd == SPINOR_OP_BE_4K && (flags & SECT_4K)) {
|
||||
sbsf->erase_size = 4 << 10;
|
||||
} else if (sbsf->cmd == SPINOR_OP_SE && !(flags & SECT_4K)) {
|
||||
sbsf->erase_size = 64 << 10;
|
||||
} else {
|
||||
debug(" cmd unknown: %#x\n", sbsf->cmd);
|
||||
return -EIO;
|
||||
}
|
||||
sbsf->state = SF_ADDR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldstate != sbsf->state)
|
||||
log_content(" cmd: transition to %s state\n",
|
||||
sandbox_sf_state_name(sbsf->state));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sandbox_erase_part(struct sandbox_spi_flash *sbsf, int size)
|
||||
{
|
||||
int todo;
|
||||
int ret;
|
||||
|
||||
while (size > 0) {
|
||||
todo = min(size, (int)sizeof(sandbox_sf_0xff));
|
||||
ret = os_write(sbsf->fd, sandbox_sf_0xff, todo);
|
||||
if (ret != todo)
|
||||
return ret;
|
||||
size -= todo;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sandbox_sf_xfer(struct udevice *dev, unsigned int bitlen,
|
||||
const void *rxp, void *txp, unsigned long flags)
|
||||
{
|
||||
struct sandbox_spi_flash *sbsf = dev_get_priv(dev);
|
||||
const uint8_t *rx = rxp;
|
||||
uint8_t *tx = txp;
|
||||
uint cnt, pos = 0;
|
||||
int bytes = bitlen / 8;
|
||||
int ret;
|
||||
|
||||
log_content("sandbox_sf: state:%x(%s) bytes:%u\n", sbsf->state,
|
||||
sandbox_sf_state_name(sbsf->state), bytes);
|
||||
|
||||
if ((flags & SPI_XFER_BEGIN))
|
||||
sandbox_sf_cs_activate(dev);
|
||||
|
||||
if (sbsf->state == SF_CMD) {
|
||||
/* Figure out the initial state */
|
||||
ret = sandbox_sf_process_cmd(sbsf, rx, tx);
|
||||
if (ret)
|
||||
return ret;
|
||||
++pos;
|
||||
}
|
||||
|
||||
/* Process the remaining data */
|
||||
while (pos < bytes) {
|
||||
switch (sbsf->state) {
|
||||
case SF_ID: {
|
||||
u8 id;
|
||||
|
||||
log_content(" id: off:%u tx:", sbsf->off);
|
||||
if (sbsf->off < IDCODE_LEN) {
|
||||
/* Extract correct byte from ID 0x00aabbcc */
|
||||
id = ((JEDEC_MFR(sbsf->data) << 16) |
|
||||
JEDEC_ID(sbsf->data)) >>
|
||||
(8 * (IDCODE_LEN - 1 - sbsf->off));
|
||||
} else {
|
||||
id = 0;
|
||||
}
|
||||
log_content("%d %02x\n", sbsf->off, id);
|
||||
tx[pos++] = id;
|
||||
++sbsf->off;
|
||||
break;
|
||||
}
|
||||
case SF_ADDR:
|
||||
log_content(" addr: bytes:%u rx:%02x ",
|
||||
sbsf->addr_bytes, rx[pos]);
|
||||
|
||||
if (sbsf->addr_bytes++ < SF_ADDR_LEN)
|
||||
sbsf->off = (sbsf->off << 8) | rx[pos];
|
||||
log_content("addr:%06x\n", sbsf->off);
|
||||
|
||||
if (tx)
|
||||
sandbox_spi_tristate(&tx[pos], 1);
|
||||
pos++;
|
||||
|
||||
/* See if we're done processing */
|
||||
if (sbsf->addr_bytes <
|
||||
SF_ADDR_LEN + sbsf->pad_addr_bytes)
|
||||
break;
|
||||
|
||||
/* Next state! */
|
||||
if (os_lseek(sbsf->fd, sbsf->off, OS_SEEK_SET) < 0) {
|
||||
puts("sandbox_sf: os_lseek() failed");
|
||||
return -EIO;
|
||||
}
|
||||
switch (sbsf->cmd) {
|
||||
case SPINOR_OP_READ_FAST:
|
||||
case SPINOR_OP_READ:
|
||||
sbsf->state = SF_READ;
|
||||
break;
|
||||
case SPINOR_OP_PP:
|
||||
sbsf->state = SF_WRITE;
|
||||
break;
|
||||
default:
|
||||
/* assume erase state ... */
|
||||
sbsf->state = SF_ERASE;
|
||||
goto case_sf_erase;
|
||||
}
|
||||
log_content(" cmd: transition to %s state\n",
|
||||
sandbox_sf_state_name(sbsf->state));
|
||||
break;
|
||||
case SF_READ:
|
||||
/*
|
||||
* XXX: need to handle exotic behavior:
|
||||
* - reading past end of device
|
||||
*/
|
||||
|
||||
cnt = bytes - pos;
|
||||
log_content(" tx: read(%u)\n", cnt);
|
||||
assert(tx);
|
||||
ret = os_read(sbsf->fd, tx + pos, cnt);
|
||||
if (ret < 0) {
|
||||
puts("sandbox_sf: os_read() failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
pos += ret;
|
||||
break;
|
||||
case SF_READ_STATUS:
|
||||
log_content(" read status: %#x\n", sbsf->status);
|
||||
cnt = bytes - pos;
|
||||
memset(tx + pos, sbsf->status, cnt);
|
||||
pos += cnt;
|
||||
break;
|
||||
case SF_READ_STATUS1:
|
||||
log_content(" read status: %#x\n", sbsf->status);
|
||||
cnt = bytes - pos;
|
||||
memset(tx + pos, sbsf->status >> 8, cnt);
|
||||
pos += cnt;
|
||||
break;
|
||||
case SF_WRITE_STATUS:
|
||||
log_content(" write status: %#x (ignored)\n", rx[pos]);
|
||||
pos = bytes;
|
||||
break;
|
||||
case SF_WRITE:
|
||||
/*
|
||||
* XXX: need to handle exotic behavior:
|
||||
* - unaligned addresses
|
||||
* - more than a page (256) worth of data
|
||||
* - reading past end of device
|
||||
*/
|
||||
if (!(sbsf->status & STAT_WEL)) {
|
||||
puts("sandbox_sf: write enable not set before write\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
cnt = bytes - pos;
|
||||
log_content(" rx: write(%u)\n", cnt);
|
||||
if (tx)
|
||||
sandbox_spi_tristate(&tx[pos], cnt);
|
||||
ret = os_write(sbsf->fd, rx + pos, cnt);
|
||||
if (ret < 0) {
|
||||
puts("sandbox_spi: os_write() failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
pos += ret;
|
||||
sbsf->status &= ~STAT_WEL;
|
||||
break;
|
||||
case SF_ERASE:
|
||||
case_sf_erase: {
|
||||
if (!(sbsf->status & STAT_WEL)) {
|
||||
puts("sandbox_sf: write enable not set before erase\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* verify address is aligned */
|
||||
if (sbsf->off & (sbsf->erase_size - 1)) {
|
||||
log_content(" sector erase: cmd:%#x needs align:%#x, but we got %#x\n",
|
||||
sbsf->cmd, sbsf->erase_size,
|
||||
sbsf->off);
|
||||
sbsf->status &= ~STAT_WEL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
log_content(" sector erase addr: %u, size: %u\n",
|
||||
sbsf->off, sbsf->erase_size);
|
||||
|
||||
cnt = bytes - pos;
|
||||
if (tx)
|
||||
sandbox_spi_tristate(&tx[pos], cnt);
|
||||
pos += cnt;
|
||||
|
||||
/*
|
||||
* TODO(vapier@gentoo.org): latch WIP in status, and
|
||||
* delay before clearing it ?
|
||||
*/
|
||||
ret = sandbox_erase_part(sbsf, sbsf->erase_size);
|
||||
sbsf->status &= ~STAT_WEL;
|
||||
if (ret) {
|
||||
log_content("sandbox_sf: Erase failed\n");
|
||||
goto done;
|
||||
}
|
||||
goto done;
|
||||
}
|
||||
default:
|
||||
log_content(" ??? no idea what to do ???\n");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (flags & SPI_XFER_END)
|
||||
sandbox_sf_cs_deactivate(dev);
|
||||
return pos == bytes ? 0 : -EIO;
|
||||
}
|
||||
|
||||
int sandbox_sf_ofdata_to_platdata(struct udevice *dev)
|
||||
{
|
||||
struct sandbox_spi_flash_plat_data *pdata = dev_get_platdata(dev);
|
||||
|
||||
pdata->filename = dev_read_string(dev, "sandbox,filename");
|
||||
pdata->device_name = dev_read_string(dev, "compatible");
|
||||
if (!pdata->filename || !pdata->device_name) {
|
||||
debug("%s: Missing properties, filename=%s, device_name=%s\n",
|
||||
__func__, pdata->filename, pdata->device_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dm_spi_emul_ops sandbox_sf_emul_ops = {
|
||||
.xfer = sandbox_sf_xfer,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_SPI_FLASH
|
||||
int sandbox_sf_bind_emul(struct sandbox_state *state, int busnum, int cs,
|
||||
struct udevice *bus, ofnode node, const char *spec)
|
||||
{
|
||||
struct udevice *emul;
|
||||
char name[20], *str;
|
||||
struct driver *drv;
|
||||
int ret;
|
||||
|
||||
/* now the emulator */
|
||||
strncpy(name, spec, sizeof(name) - 6);
|
||||
name[sizeof(name) - 6] = '\0';
|
||||
strcat(name, "-emul");
|
||||
drv = lists_driver_lookup_name("sandbox_sf_emul");
|
||||
if (!drv) {
|
||||
puts("Cannot find sandbox_sf_emul driver\n");
|
||||
return -ENOENT;
|
||||
}
|
||||
str = strdup(name);
|
||||
if (!str)
|
||||
return -ENOMEM;
|
||||
ret = device_bind_ofnode(bus, drv, str, NULL, node, &emul);
|
||||
if (ret) {
|
||||
free(str);
|
||||
printf("Cannot create emul device for spec '%s' (err=%d)\n",
|
||||
spec, ret);
|
||||
return ret;
|
||||
}
|
||||
state->spi[busnum][cs].emul = emul;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sandbox_sf_unbind_emul(struct sandbox_state *state, int busnum, int cs)
|
||||
{
|
||||
struct udevice *dev;
|
||||
|
||||
dev = state->spi[busnum][cs].emul;
|
||||
device_remove(dev, DM_REMOVE_NORMAL);
|
||||
device_unbind(dev);
|
||||
state->spi[busnum][cs].emul = NULL;
|
||||
}
|
||||
|
||||
int sandbox_spi_get_emul(struct sandbox_state *state,
|
||||
struct udevice *bus, struct udevice *slave,
|
||||
struct udevice **emulp)
|
||||
{
|
||||
struct sandbox_spi_info *info;
|
||||
int busnum = bus->seq;
|
||||
int cs = spi_chip_select(slave);
|
||||
int ret;
|
||||
|
||||
info = &state->spi[busnum][cs];
|
||||
if (!info->emul) {
|
||||
/* Use the same device tree node as the SPI flash device */
|
||||
debug("%s: busnum=%u, cs=%u: binding SPI flash emulation: ",
|
||||
__func__, busnum, cs);
|
||||
ret = sandbox_sf_bind_emul(state, busnum, cs, bus,
|
||||
dev_ofnode(slave), slave->name);
|
||||
if (ret) {
|
||||
debug("failed (err=%d)\n", ret);
|
||||
return ret;
|
||||
}
|
||||
debug("OK\n");
|
||||
}
|
||||
*emulp = info->emul;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct udevice_id sandbox_sf_ids[] = {
|
||||
{ .compatible = "sandbox,spi-flash" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(sandbox_sf_emul) = {
|
||||
.name = "sandbox_sf_emul",
|
||||
.id = UCLASS_SPI_EMUL,
|
||||
.of_match = sandbox_sf_ids,
|
||||
.ofdata_to_platdata = sandbox_sf_ofdata_to_platdata,
|
||||
.probe = sandbox_sf_probe,
|
||||
.remove = sandbox_sf_remove,
|
||||
.priv_auto_alloc_size = sizeof(struct sandbox_spi_flash),
|
||||
.platdata_auto_alloc_size = sizeof(struct sandbox_spi_flash_plat_data),
|
||||
.ops = &sandbox_sf_emul_ops,
|
||||
};
|
||||
@@ -0,0 +1,111 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (c) 2014 Google, Inc
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <spi.h>
|
||||
#include <spi_flash.h>
|
||||
#include <dm/device-internal.h>
|
||||
#include "sf_internal.h"
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
int spi_flash_read_dm(struct udevice *dev, u32 offset, size_t len, void *buf)
|
||||
{
|
||||
return log_ret(sf_get_ops(dev)->read(dev, offset, len, buf));
|
||||
}
|
||||
|
||||
int spi_flash_write_dm(struct udevice *dev, u32 offset, size_t len,
|
||||
const void *buf)
|
||||
{
|
||||
return log_ret(sf_get_ops(dev)->write(dev, offset, len, buf));
|
||||
}
|
||||
|
||||
int spi_flash_erase_dm(struct udevice *dev, u32 offset, size_t len)
|
||||
{
|
||||
return log_ret(sf_get_ops(dev)->erase(dev, offset, len));
|
||||
}
|
||||
|
||||
int spl_flash_get_sw_write_prot(struct udevice *dev)
|
||||
{
|
||||
struct dm_spi_flash_ops *ops = sf_get_ops(dev);
|
||||
|
||||
if (!ops->get_sw_write_prot)
|
||||
return -ENOSYS;
|
||||
return log_ret(ops->get_sw_write_prot(dev));
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO(sjg@chromium.org): This is an old-style function. We should remove
|
||||
* it when all SPI flash drivers use dm
|
||||
*/
|
||||
struct spi_flash *spi_flash_probe(unsigned int bus, unsigned int cs,
|
||||
unsigned int max_hz, unsigned int spi_mode)
|
||||
{
|
||||
struct udevice *dev;
|
||||
|
||||
if (spi_flash_probe_bus_cs(bus, cs, max_hz, spi_mode, &dev))
|
||||
return NULL;
|
||||
|
||||
return dev_get_uclass_priv(dev);
|
||||
}
|
||||
|
||||
void spi_flash_free(struct spi_flash *flash)
|
||||
{
|
||||
device_remove(flash->spi->dev, DM_REMOVE_NORMAL);
|
||||
}
|
||||
|
||||
int spi_flash_probe_bus_cs(unsigned int busnum, unsigned int cs,
|
||||
unsigned int max_hz, unsigned int spi_mode,
|
||||
struct udevice **devp)
|
||||
{
|
||||
struct spi_slave *slave;
|
||||
struct udevice *bus;
|
||||
char *str;
|
||||
int ret;
|
||||
|
||||
#if defined(CONFIG_SPL_BUILD) && CONFIG_IS_ENABLED(USE_TINY_PRINTF)
|
||||
str = "spi_flash";
|
||||
#else
|
||||
char name[30];
|
||||
|
||||
snprintf(name, sizeof(name), "spi_flash@%d:%d", busnum, cs);
|
||||
str = strdup(name);
|
||||
#endif
|
||||
ret = spi_get_bus_and_cs(busnum, cs, max_hz, spi_mode,
|
||||
"spi_flash_std", str, &bus, &slave);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*devp = slave->dev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_flash_post_bind(struct udevice *dev)
|
||||
{
|
||||
#if defined(CONFIG_NEEDS_MANUAL_RELOC)
|
||||
struct dm_spi_flash_ops *ops = sf_get_ops(dev);
|
||||
static int reloc_done;
|
||||
|
||||
if (!reloc_done) {
|
||||
if (ops->read)
|
||||
ops->read += gd->reloc_off;
|
||||
if (ops->write)
|
||||
ops->write += gd->reloc_off;
|
||||
if (ops->erase)
|
||||
ops->erase += gd->reloc_off;
|
||||
|
||||
reloc_done++;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
UCLASS_DRIVER(spi_flash) = {
|
||||
.id = UCLASS_SPI_FLASH,
|
||||
.name = "spi_flash",
|
||||
.post_bind = spi_flash_post_bind,
|
||||
.per_device_auto_alloc_size = sizeof(struct spi_flash),
|
||||
};
|
||||
@@ -0,0 +1,695 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Atmel DataFlash probing
|
||||
*
|
||||
* Copyright (C) 2004-2009, 2015 Freescale Semiconductor, Inc.
|
||||
* Haikun Wang (haikun.wang@freescale.com)
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <errno.h>
|
||||
#include <fdtdec.h>
|
||||
#include <spi.h>
|
||||
#include <spi_flash.h>
|
||||
#include <div64.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/math64.h>
|
||||
|
||||
#include "sf_internal.h"
|
||||
|
||||
#define CMD_READ_ID 0x9f
|
||||
/* reads can bypass the buffers */
|
||||
#define OP_READ_CONTINUOUS 0xE8
|
||||
#define OP_READ_PAGE 0xD2
|
||||
|
||||
/* group B requests can run even while status reports "busy" */
|
||||
#define OP_READ_STATUS 0xD7 /* group B */
|
||||
|
||||
/* move data between host and buffer */
|
||||
#define OP_READ_BUFFER1 0xD4 /* group B */
|
||||
#define OP_READ_BUFFER2 0xD6 /* group B */
|
||||
#define OP_WRITE_BUFFER1 0x84 /* group B */
|
||||
#define OP_WRITE_BUFFER2 0x87 /* group B */
|
||||
|
||||
/* erasing flash */
|
||||
#define OP_ERASE_PAGE 0x81
|
||||
#define OP_ERASE_BLOCK 0x50
|
||||
|
||||
/* move data between buffer and flash */
|
||||
#define OP_TRANSFER_BUF1 0x53
|
||||
#define OP_TRANSFER_BUF2 0x55
|
||||
#define OP_MREAD_BUFFER1 0xD4
|
||||
#define OP_MREAD_BUFFER2 0xD6
|
||||
#define OP_MWERASE_BUFFER1 0x83
|
||||
#define OP_MWERASE_BUFFER2 0x86
|
||||
#define OP_MWRITE_BUFFER1 0x88 /* sector must be pre-erased */
|
||||
#define OP_MWRITE_BUFFER2 0x89 /* sector must be pre-erased */
|
||||
|
||||
/* write to buffer, then write-erase to flash */
|
||||
#define OP_PROGRAM_VIA_BUF1 0x82
|
||||
#define OP_PROGRAM_VIA_BUF2 0x85
|
||||
|
||||
/* compare buffer to flash */
|
||||
#define OP_COMPARE_BUF1 0x60
|
||||
#define OP_COMPARE_BUF2 0x61
|
||||
|
||||
/* read flash to buffer, then write-erase to flash */
|
||||
#define OP_REWRITE_VIA_BUF1 0x58
|
||||
#define OP_REWRITE_VIA_BUF2 0x59
|
||||
|
||||
/*
|
||||
* newer chips report JEDEC manufacturer and device IDs; chip
|
||||
* serial number and OTP bits; and per-sector writeprotect.
|
||||
*/
|
||||
#define OP_READ_ID 0x9F
|
||||
#define OP_READ_SECURITY 0x77
|
||||
#define OP_WRITE_SECURITY_REVC 0x9A
|
||||
#define OP_WRITE_SECURITY 0x9B /* revision D */
|
||||
|
||||
struct dataflash {
|
||||
uint8_t command[16];
|
||||
unsigned short page_offset; /* offset in flash address */
|
||||
};
|
||||
|
||||
/* Return the status of the DataFlash device */
|
||||
static inline int dataflash_status(struct spi_slave *spi)
|
||||
{
|
||||
int ret;
|
||||
u8 opcode = OP_READ_STATUS;
|
||||
u8 status;
|
||||
|
||||
/*
|
||||
* NOTE: at45db321c over 25 MHz wants to write
|
||||
* a dummy byte after the opcode...
|
||||
*/
|
||||
ret = spi_write_then_read(spi, &opcode, 1, NULL, &status, 1);
|
||||
return ret ? -EIO : status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Poll the DataFlash device until it is READY.
|
||||
* This usually takes 5-20 msec or so; more for sector erase.
|
||||
* ready: return > 0
|
||||
*/
|
||||
static int dataflash_waitready(struct spi_slave *spi)
|
||||
{
|
||||
int status;
|
||||
int timeout = 2 * CONFIG_SYS_HZ;
|
||||
int timebase;
|
||||
|
||||
timebase = get_timer(0);
|
||||
do {
|
||||
status = dataflash_status(spi);
|
||||
if (status < 0)
|
||||
status = 0;
|
||||
|
||||
if (status & (1 << 7)) /* RDY/nBSY */
|
||||
return status;
|
||||
|
||||
mdelay(3);
|
||||
} while (get_timer(timebase) < timeout);
|
||||
|
||||
return -ETIME;
|
||||
}
|
||||
|
||||
/* Erase pages of flash */
|
||||
static int spi_dataflash_erase(struct udevice *dev, u32 offset, size_t len)
|
||||
{
|
||||
struct dataflash *dataflash;
|
||||
struct spi_flash *spi_flash;
|
||||
struct spi_slave *spi;
|
||||
unsigned blocksize;
|
||||
uint8_t *command;
|
||||
uint32_t rem;
|
||||
int status;
|
||||
|
||||
dataflash = dev_get_priv(dev);
|
||||
spi_flash = dev_get_uclass_priv(dev);
|
||||
spi = spi_flash->spi;
|
||||
|
||||
blocksize = spi_flash->page_size << 3;
|
||||
|
||||
memset(dataflash->command, 0 , sizeof(dataflash->command));
|
||||
command = dataflash->command;
|
||||
|
||||
debug("%s: erase addr=0x%x len 0x%x\n", dev->name, offset, len);
|
||||
|
||||
div_u64_rem(len, spi_flash->page_size, &rem);
|
||||
if (rem) {
|
||||
printf("%s: len(0x%x) isn't the multiple of page size(0x%x)\n",
|
||||
dev->name, len, spi_flash->page_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
div_u64_rem(offset, spi_flash->page_size, &rem);
|
||||
if (rem) {
|
||||
printf("%s: offset(0x%x) isn't the multiple of page size(0x%x)\n",
|
||||
dev->name, offset, spi_flash->page_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
status = spi_claim_bus(spi);
|
||||
if (status) {
|
||||
debug("dataflash: unable to claim SPI bus\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
while (len > 0) {
|
||||
unsigned int pageaddr;
|
||||
int do_block;
|
||||
/*
|
||||
* Calculate flash page address; use block erase (for speed) if
|
||||
* we're at a block boundary and need to erase the whole block.
|
||||
*/
|
||||
pageaddr = div_u64(offset, spi_flash->page_size);
|
||||
do_block = (pageaddr & 0x7) == 0 && len >= blocksize;
|
||||
pageaddr = pageaddr << dataflash->page_offset;
|
||||
|
||||
command[0] = do_block ? OP_ERASE_BLOCK : OP_ERASE_PAGE;
|
||||
command[1] = (uint8_t)(pageaddr >> 16);
|
||||
command[2] = (uint8_t)(pageaddr >> 8);
|
||||
command[3] = 0;
|
||||
|
||||
debug("%s ERASE %s: (%x) %x %x %x [%d]\n",
|
||||
dev->name, do_block ? "block" : "page",
|
||||
command[0], command[1], command[2], command[3],
|
||||
pageaddr);
|
||||
|
||||
status = spi_write_then_read(spi, command, 4, NULL, NULL, 0);
|
||||
if (status < 0) {
|
||||
debug("%s: erase send command error!\n", dev->name);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
status = dataflash_waitready(spi);
|
||||
if (status < 0) {
|
||||
debug("%s: erase waitready error!\n", dev->name);
|
||||
return status;
|
||||
}
|
||||
|
||||
if (do_block) {
|
||||
offset += blocksize;
|
||||
len -= blocksize;
|
||||
} else {
|
||||
offset += spi_flash->page_size;
|
||||
len -= spi_flash->page_size;
|
||||
}
|
||||
}
|
||||
|
||||
spi_release_bus(spi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read from the DataFlash device.
|
||||
* offset : Start offset in flash device
|
||||
* len : Amount to read
|
||||
* buf : Buffer containing the data
|
||||
*/
|
||||
static int spi_dataflash_read(struct udevice *dev, u32 offset, size_t len,
|
||||
void *buf)
|
||||
{
|
||||
struct dataflash *dataflash;
|
||||
struct spi_flash *spi_flash;
|
||||
struct spi_slave *spi;
|
||||
unsigned int addr;
|
||||
uint8_t *command;
|
||||
int status;
|
||||
|
||||
dataflash = dev_get_priv(dev);
|
||||
spi_flash = dev_get_uclass_priv(dev);
|
||||
spi = spi_flash->spi;
|
||||
|
||||
memset(dataflash->command, 0 , sizeof(dataflash->command));
|
||||
command = dataflash->command;
|
||||
|
||||
debug("%s: erase addr=0x%x len 0x%x\n", dev->name, offset, len);
|
||||
debug("READ: (%x) %x %x %x\n",
|
||||
command[0], command[1], command[2], command[3]);
|
||||
|
||||
/* Calculate flash page/byte address */
|
||||
addr = (((unsigned)offset / spi_flash->page_size)
|
||||
<< dataflash->page_offset)
|
||||
+ ((unsigned)offset % spi_flash->page_size);
|
||||
|
||||
status = spi_claim_bus(spi);
|
||||
if (status) {
|
||||
debug("dataflash: unable to claim SPI bus\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Continuous read, max clock = f(car) which may be less than
|
||||
* the peak rate available. Some chips support commands with
|
||||
* fewer "don't care" bytes. Both buffers stay unchanged.
|
||||
*/
|
||||
command[0] = OP_READ_CONTINUOUS;
|
||||
command[1] = (uint8_t)(addr >> 16);
|
||||
command[2] = (uint8_t)(addr >> 8);
|
||||
command[3] = (uint8_t)(addr >> 0);
|
||||
|
||||
/* plus 4 "don't care" bytes, command len: 4 + 4 "don't care" bytes */
|
||||
status = spi_write_then_read(spi, command, 8, NULL, buf, len);
|
||||
|
||||
spi_release_bus(spi);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write to the DataFlash device.
|
||||
* offset : Start offset in flash device
|
||||
* len : Amount to write
|
||||
* buf : Buffer containing the data
|
||||
*/
|
||||
int spi_dataflash_write(struct udevice *dev, u32 offset, size_t len,
|
||||
const void *buf)
|
||||
{
|
||||
struct dataflash *dataflash;
|
||||
struct spi_flash *spi_flash;
|
||||
struct spi_slave *spi;
|
||||
uint8_t *command;
|
||||
unsigned int pageaddr, addr, to, writelen;
|
||||
size_t remaining = len;
|
||||
u_char *writebuf = (u_char *)buf;
|
||||
int status = -EINVAL;
|
||||
|
||||
dataflash = dev_get_priv(dev);
|
||||
spi_flash = dev_get_uclass_priv(dev);
|
||||
spi = spi_flash->spi;
|
||||
|
||||
memset(dataflash->command, 0 , sizeof(dataflash->command));
|
||||
command = dataflash->command;
|
||||
|
||||
debug("%s: write 0x%x..0x%x\n", dev->name, offset, (offset + len));
|
||||
|
||||
pageaddr = ((unsigned)offset / spi_flash->page_size);
|
||||
to = ((unsigned)offset % spi_flash->page_size);
|
||||
if (to + len > spi_flash->page_size)
|
||||
writelen = spi_flash->page_size - to;
|
||||
else
|
||||
writelen = len;
|
||||
|
||||
status = spi_claim_bus(spi);
|
||||
if (status) {
|
||||
debug("dataflash: unable to claim SPI bus\n");
|
||||
return status;
|
||||
}
|
||||
|
||||
while (remaining > 0) {
|
||||
debug("write @ %d:%d len=%d\n", pageaddr, to, writelen);
|
||||
|
||||
/*
|
||||
* REVISIT:
|
||||
* (a) each page in a sector must be rewritten at least
|
||||
* once every 10K sibling erase/program operations.
|
||||
* (b) for pages that are already erased, we could
|
||||
* use WRITE+MWRITE not PROGRAM for ~30% speedup.
|
||||
* (c) WRITE to buffer could be done while waiting for
|
||||
* a previous MWRITE/MWERASE to complete ...
|
||||
* (d) error handling here seems to be mostly missing.
|
||||
*
|
||||
* Two persistent bits per page, plus a per-sector counter,
|
||||
* could support (a) and (b) ... we might consider using
|
||||
* the second half of sector zero, which is just one block,
|
||||
* to track that state. (On AT91, that sector should also
|
||||
* support boot-from-DataFlash.)
|
||||
*/
|
||||
|
||||
addr = pageaddr << dataflash->page_offset;
|
||||
|
||||
/* (1) Maybe transfer partial page to Buffer1 */
|
||||
if (writelen != spi_flash->page_size) {
|
||||
command[0] = OP_TRANSFER_BUF1;
|
||||
command[1] = (addr & 0x00FF0000) >> 16;
|
||||
command[2] = (addr & 0x0000FF00) >> 8;
|
||||
command[3] = 0;
|
||||
|
||||
debug("TRANSFER: (%x) %x %x %x\n",
|
||||
command[0], command[1], command[2], command[3]);
|
||||
|
||||
status = spi_write_then_read(spi, command, 4,
|
||||
NULL, NULL, 0);
|
||||
if (status < 0) {
|
||||
debug("%s: write(<pagesize) command error!\n",
|
||||
dev->name);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
status = dataflash_waitready(spi);
|
||||
if (status < 0) {
|
||||
debug("%s: write(<pagesize) waitready error!\n",
|
||||
dev->name);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
/* (2) Program full page via Buffer1 */
|
||||
addr += to;
|
||||
command[0] = OP_PROGRAM_VIA_BUF1;
|
||||
command[1] = (addr & 0x00FF0000) >> 16;
|
||||
command[2] = (addr & 0x0000FF00) >> 8;
|
||||
command[3] = (addr & 0x000000FF);
|
||||
|
||||
debug("PROGRAM: (%x) %x %x %x\n",
|
||||
command[0], command[1], command[2], command[3]);
|
||||
|
||||
status = spi_write_then_read(spi, command, 4,
|
||||
writebuf, NULL, writelen);
|
||||
if (status < 0) {
|
||||
debug("%s: write send command error!\n", dev->name);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
status = dataflash_waitready(spi);
|
||||
if (status < 0) {
|
||||
debug("%s: write waitready error!\n", dev->name);
|
||||
return status;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SPI_DATAFLASH_WRITE_VERIFY
|
||||
/* (3) Compare to Buffer1 */
|
||||
addr = pageaddr << dataflash->page_offset;
|
||||
command[0] = OP_COMPARE_BUF1;
|
||||
command[1] = (addr & 0x00FF0000) >> 16;
|
||||
command[2] = (addr & 0x0000FF00) >> 8;
|
||||
command[3] = 0;
|
||||
|
||||
debug("COMPARE: (%x) %x %x %x\n",
|
||||
command[0], command[1], command[2], command[3]);
|
||||
|
||||
status = spi_write_then_read(spi, command, 4,
|
||||
writebuf, NULL, writelen);
|
||||
if (status < 0) {
|
||||
debug("%s: write(compare) send command error!\n",
|
||||
dev->name);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
status = dataflash_waitready(spi);
|
||||
|
||||
/* Check result of the compare operation */
|
||||
if (status & (1 << 6)) {
|
||||
printf("dataflash: write compare page %u, err %d\n",
|
||||
pageaddr, status);
|
||||
remaining = 0;
|
||||
status = -EIO;
|
||||
break;
|
||||
} else {
|
||||
status = 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SPI_DATAFLASH_WRITE_VERIFY */
|
||||
remaining = remaining - writelen;
|
||||
pageaddr++;
|
||||
to = 0;
|
||||
writebuf += writelen;
|
||||
|
||||
if (remaining > spi_flash->page_size)
|
||||
writelen = spi_flash->page_size;
|
||||
else
|
||||
writelen = remaining;
|
||||
}
|
||||
|
||||
spi_release_bus(spi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int add_dataflash(struct udevice *dev, char *name, int nr_pages,
|
||||
int pagesize, int pageoffset, char revision)
|
||||
{
|
||||
struct spi_flash *spi_flash;
|
||||
struct dataflash *dataflash;
|
||||
|
||||
dataflash = dev_get_priv(dev);
|
||||
spi_flash = dev_get_uclass_priv(dev);
|
||||
|
||||
dataflash->page_offset = pageoffset;
|
||||
|
||||
spi_flash->name = name;
|
||||
spi_flash->page_size = pagesize;
|
||||
spi_flash->size = nr_pages * pagesize;
|
||||
spi_flash->erase_size = pagesize;
|
||||
|
||||
#ifndef CONFIG_SPL_BUILD
|
||||
printf("SPI DataFlash: Detected %s with page size ", spi_flash->name);
|
||||
print_size(spi_flash->page_size, ", erase size ");
|
||||
print_size(spi_flash->erase_size, ", total ");
|
||||
print_size(spi_flash->size, "");
|
||||
printf(", revision %c", revision);
|
||||
puts("\n");
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct data_flash_info {
|
||||
char *name;
|
||||
|
||||
/*
|
||||
* JEDEC id has a high byte of zero plus three data bytes:
|
||||
* the manufacturer id, then a two byte device id.
|
||||
*/
|
||||
uint32_t jedec_id;
|
||||
|
||||
/* The size listed here is what works with OP_ERASE_PAGE. */
|
||||
unsigned nr_pages;
|
||||
uint16_t pagesize;
|
||||
uint16_t pageoffset;
|
||||
|
||||
uint16_t flags;
|
||||
#define SUP_POW2PS 0x0002 /* supports 2^N byte pages */
|
||||
#define IS_POW2PS 0x0001 /* uses 2^N byte pages */
|
||||
};
|
||||
|
||||
static struct data_flash_info dataflash_data[] = {
|
||||
/*
|
||||
* NOTE: chips with SUP_POW2PS (rev D and up) need two entries,
|
||||
* one with IS_POW2PS and the other without. The entry with the
|
||||
* non-2^N byte page size can't name exact chip revisions without
|
||||
* losing backwards compatibility for cmdlinepart.
|
||||
*
|
||||
* Those two entries have different name spelling format in order to
|
||||
* show their difference obviously.
|
||||
* The upper case refer to the chip isn't in normal 2^N bytes page-size
|
||||
* mode.
|
||||
* The lower case refer to the chip is in normal 2^N bytes page-size
|
||||
* mode.
|
||||
*
|
||||
* These newer chips also support 128-byte security registers (with
|
||||
* 64 bytes one-time-programmable) and software write-protection.
|
||||
*/
|
||||
{ "AT45DB011B", 0x1f2200, 512, 264, 9, SUP_POW2PS},
|
||||
{ "at45db011d", 0x1f2200, 512, 256, 8, SUP_POW2PS | IS_POW2PS},
|
||||
|
||||
{ "AT45DB021B", 0x1f2300, 1024, 264, 9, SUP_POW2PS},
|
||||
{ "at45db021d", 0x1f2300, 1024, 256, 8, SUP_POW2PS | IS_POW2PS},
|
||||
|
||||
{ "AT45DB041x", 0x1f2400, 2048, 264, 9, SUP_POW2PS},
|
||||
{ "at45db041d", 0x1f2400, 2048, 256, 8, SUP_POW2PS | IS_POW2PS},
|
||||
|
||||
{ "AT45DB081B", 0x1f2500, 4096, 264, 9, SUP_POW2PS},
|
||||
{ "at45db081d", 0x1f2500, 4096, 256, 8, SUP_POW2PS | IS_POW2PS},
|
||||
|
||||
{ "AT45DB161x", 0x1f2600, 4096, 528, 10, SUP_POW2PS},
|
||||
{ "at45db161d", 0x1f2600, 4096, 512, 9, SUP_POW2PS | IS_POW2PS},
|
||||
|
||||
{ "AT45DB321x", 0x1f2700, 8192, 528, 10, 0}, /* rev C */
|
||||
|
||||
{ "AT45DB321x", 0x1f2701, 8192, 528, 10, SUP_POW2PS},
|
||||
{ "at45db321d", 0x1f2701, 8192, 512, 9, SUP_POW2PS | IS_POW2PS},
|
||||
|
||||
{ "AT45DB642x", 0x1f2800, 8192, 1056, 11, SUP_POW2PS},
|
||||
{ "at45db642d", 0x1f2800, 8192, 1024, 10, SUP_POW2PS | IS_POW2PS},
|
||||
};
|
||||
|
||||
static struct data_flash_info *jedec_probe(struct spi_slave *spi)
|
||||
{
|
||||
int tmp;
|
||||
uint8_t id[5];
|
||||
uint32_t jedec;
|
||||
struct data_flash_info *info;
|
||||
u8 opcode = CMD_READ_ID;
|
||||
int status;
|
||||
|
||||
/*
|
||||
* JEDEC also defines an optional "extended device information"
|
||||
* string for after vendor-specific data, after the three bytes
|
||||
* we use here. Supporting some chips might require using it.
|
||||
*
|
||||
* If the vendor ID isn't Atmel's (0x1f), assume this call failed.
|
||||
* That's not an error; only rev C and newer chips handle it, and
|
||||
* only Atmel sells these chips.
|
||||
*/
|
||||
tmp = spi_write_then_read(spi, &opcode, 1, NULL, id, sizeof(id));
|
||||
if (tmp < 0) {
|
||||
printf("dataflash: error %d reading JEDEC ID\n", tmp);
|
||||
return ERR_PTR(tmp);
|
||||
}
|
||||
if (id[0] != 0x1f)
|
||||
return NULL;
|
||||
|
||||
jedec = id[0];
|
||||
jedec = jedec << 8;
|
||||
jedec |= id[1];
|
||||
jedec = jedec << 8;
|
||||
jedec |= id[2];
|
||||
|
||||
for (tmp = 0, info = dataflash_data;
|
||||
tmp < ARRAY_SIZE(dataflash_data);
|
||||
tmp++, info++) {
|
||||
if (info->jedec_id == jedec) {
|
||||
if (info->flags & SUP_POW2PS) {
|
||||
status = dataflash_status(spi);
|
||||
if (status < 0) {
|
||||
debug("dataflash: status error %d\n",
|
||||
status);
|
||||
return NULL;
|
||||
}
|
||||
if (status & 0x1) {
|
||||
if (info->flags & IS_POW2PS)
|
||||
return info;
|
||||
} else {
|
||||
if (!(info->flags & IS_POW2PS))
|
||||
return info;
|
||||
}
|
||||
} else {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Treat other chips as errors ... we won't know the right page
|
||||
* size (it might be binary) even when we can tell which density
|
||||
* class is involved (legacy chip id scheme).
|
||||
*/
|
||||
printf("dataflash: JEDEC id %06x not handled\n", jedec);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
/*
|
||||
* Detect and initialize DataFlash device, using JEDEC IDs on newer chips
|
||||
* or else the ID code embedded in the status bits:
|
||||
*
|
||||
* Device Density ID code #Pages PageSize Offset
|
||||
* AT45DB011B 1Mbit (128K) xx0011xx (0x0c) 512 264 9
|
||||
* AT45DB021B 2Mbit (256K) xx0101xx (0x14) 1024 264 9
|
||||
* AT45DB041B 4Mbit (512K) xx0111xx (0x1c) 2048 264 9
|
||||
* AT45DB081B 8Mbit (1M) xx1001xx (0x24) 4096 264 9
|
||||
* AT45DB0161B 16Mbit (2M) xx1011xx (0x2c) 4096 528 10
|
||||
* AT45DB0321B 32Mbit (4M) xx1101xx (0x34) 8192 528 10
|
||||
* AT45DB0642 64Mbit (8M) xx111xxx (0x3c) 8192 1056 11
|
||||
* AT45DB1282 128Mbit (16M) xx0100xx (0x10) 16384 1056 11
|
||||
*/
|
||||
static int spi_dataflash_probe(struct udevice *dev)
|
||||
{
|
||||
struct spi_slave *spi = dev_get_parent_priv(dev);
|
||||
struct spi_flash *spi_flash;
|
||||
struct data_flash_info *info;
|
||||
int status;
|
||||
|
||||
spi_flash = dev_get_uclass_priv(dev);
|
||||
spi_flash->spi = spi;
|
||||
spi_flash->dev = dev;
|
||||
|
||||
status = spi_claim_bus(spi);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
/*
|
||||
* Try to detect dataflash by JEDEC ID.
|
||||
* If it succeeds we know we have either a C or D part.
|
||||
* D will support power of 2 pagesize option.
|
||||
* Both support the security register, though with different
|
||||
* write procedures.
|
||||
*/
|
||||
info = jedec_probe(spi);
|
||||
if (IS_ERR(info))
|
||||
goto err_jedec_probe;
|
||||
if (info != NULL) {
|
||||
status = add_dataflash(dev, info->name, info->nr_pages,
|
||||
info->pagesize, info->pageoffset,
|
||||
(info->flags & SUP_POW2PS) ? 'd' : 'c');
|
||||
if (status < 0)
|
||||
goto err_status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Older chips support only legacy commands, identifing
|
||||
* capacity using bits in the status byte.
|
||||
*/
|
||||
status = dataflash_status(spi);
|
||||
if (status <= 0 || status == 0xff) {
|
||||
printf("dataflash: read status error %d\n", status);
|
||||
if (status == 0 || status == 0xff)
|
||||
status = -ENODEV;
|
||||
goto err_jedec_probe;
|
||||
}
|
||||
|
||||
/*
|
||||
* if there's a device there, assume it's dataflash.
|
||||
* board setup should have set spi->max_speed_max to
|
||||
* match f(car) for continuous reads, mode 0 or 3.
|
||||
*/
|
||||
switch (status & 0x3c) {
|
||||
case 0x0c: /* 0 0 1 1 x x */
|
||||
status = add_dataflash(dev, "AT45DB011B", 512, 264, 9, 0);
|
||||
break;
|
||||
case 0x14: /* 0 1 0 1 x x */
|
||||
status = add_dataflash(dev, "AT45DB021B", 1024, 264, 9, 0);
|
||||
break;
|
||||
case 0x1c: /* 0 1 1 1 x x */
|
||||
status = add_dataflash(dev, "AT45DB041x", 2048, 264, 9, 0);
|
||||
break;
|
||||
case 0x24: /* 1 0 0 1 x x */
|
||||
status = add_dataflash(dev, "AT45DB081B", 4096, 264, 9, 0);
|
||||
break;
|
||||
case 0x2c: /* 1 0 1 1 x x */
|
||||
status = add_dataflash(dev, "AT45DB161x", 4096, 528, 10, 0);
|
||||
break;
|
||||
case 0x34: /* 1 1 0 1 x x */
|
||||
status = add_dataflash(dev, "AT45DB321x", 8192, 528, 10, 0);
|
||||
break;
|
||||
case 0x38: /* 1 1 1 x x x */
|
||||
case 0x3c:
|
||||
status = add_dataflash(dev, "AT45DB642x", 8192, 1056, 11, 0);
|
||||
break;
|
||||
/* obsolete AT45DB1282 not (yet?) supported */
|
||||
default:
|
||||
printf("dataflash: unsupported device (%x)\n", status & 0x3c);
|
||||
status = -ENODEV;
|
||||
goto err_status;
|
||||
}
|
||||
|
||||
return status;
|
||||
|
||||
err_status:
|
||||
spi_free_slave(spi);
|
||||
err_jedec_probe:
|
||||
spi_release_bus(spi);
|
||||
return status;
|
||||
}
|
||||
|
||||
static const struct dm_spi_flash_ops spi_dataflash_ops = {
|
||||
.read = spi_dataflash_read,
|
||||
.write = spi_dataflash_write,
|
||||
.erase = spi_dataflash_erase,
|
||||
};
|
||||
|
||||
static const struct udevice_id spi_dataflash_ids[] = {
|
||||
{ .compatible = "atmel,at45", },
|
||||
{ .compatible = "atmel,dataflash", },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(spi_dataflash) = {
|
||||
.name = "spi_dataflash",
|
||||
.id = UCLASS_SPI_FLASH,
|
||||
.of_match = spi_dataflash_ids,
|
||||
.probe = spi_dataflash_probe,
|
||||
.priv_auto_alloc_size = sizeof(struct dataflash),
|
||||
.ops = &spi_dataflash_ops,
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* SPI flash internal definitions
|
||||
*
|
||||
* Copyright (C) 2008 Atmel Corporation
|
||||
* Copyright (C) 2013 Jagannadha Sutradharudu Teki, Xilinx Inc.
|
||||
*/
|
||||
|
||||
#ifndef _SF_INTERNAL_H_
|
||||
#define _SF_INTERNAL_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/compiler.h>
|
||||
|
||||
#define SPI_NOR_MAX_ID_LEN 6
|
||||
#define SPI_NOR_MAX_ADDR_WIDTH 4
|
||||
|
||||
struct flash_info {
|
||||
#if !CONFIG_IS_ENABLED(SPI_FLASH_TINY)
|
||||
char *name;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 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 SPINOR_OP_SE, which isn't
|
||||
* necessarily called a "sector" by the vendor.
|
||||
*/
|
||||
unsigned int sector_size;
|
||||
u16 n_sectors;
|
||||
|
||||
u16 page_size;
|
||||
u16 addr_width;
|
||||
|
||||
u16 flags;
|
||||
#define SECT_4K BIT(0) /* SPINOR_OP_BE_4K works uniformly */
|
||||
#define SPI_NOR_NO_ERASE BIT(1) /* No erase command needed */
|
||||
#define SST_WRITE BIT(2) /* use SST byte programming */
|
||||
#define SPI_NOR_NO_FR BIT(3) /* Can't do fastread */
|
||||
#define SECT_4K_PMC BIT(4) /* SPINOR_OP_BE_4K_PMC works uniformly */
|
||||
#define SPI_NOR_DUAL_READ BIT(5) /* Flash supports Dual Read */
|
||||
#define SPI_NOR_QUAD_READ BIT(6) /* Flash supports Quad Read */
|
||||
#define USE_FSR BIT(7) /* use flag status register */
|
||||
#define SPI_NOR_HAS_LOCK BIT(8) /* Flash supports lock/unlock via SR */
|
||||
#define SPI_NOR_HAS_TB BIT(9) /*
|
||||
* Flash SR has Top/Bottom (TB) protect
|
||||
* bit. Must be used with
|
||||
* SPI_NOR_HAS_LOCK.
|
||||
*/
|
||||
#define SPI_S3AN BIT(10) /*
|
||||
* Xilinx Spartan 3AN In-System Flash
|
||||
* (MFR cannot be used for probing
|
||||
* because it has the same value as
|
||||
* ATMEL flashes)
|
||||
*/
|
||||
#define SPI_NOR_4B_OPCODES BIT(11) /*
|
||||
* Use dedicated 4byte address op codes
|
||||
* to support memory size above 128Mib.
|
||||
*/
|
||||
#define NO_CHIP_ERASE BIT(12) /* Chip does not support chip erase */
|
||||
#define SPI_NOR_SKIP_SFDP BIT(13) /* Skip parsing of SFDP tables */
|
||||
#define USE_CLSR BIT(14) /* use CLSR command */
|
||||
#define SPI_NOR_HAS_SST26LOCK BIT(15) /* Flash supports lock/unlock via BPR */
|
||||
};
|
||||
|
||||
extern const struct flash_info spi_nor_ids[];
|
||||
|
||||
#define JEDEC_MFR(info) ((info)->id[0])
|
||||
#define JEDEC_ID(info) (((info)->id[1]) << 8 | ((info)->id[2]))
|
||||
|
||||
/* Get software write-protect value (BP bits) */
|
||||
int spi_flash_cmd_get_sw_write_prot(struct spi_flash *flash);
|
||||
|
||||
|
||||
#if CONFIG_IS_ENABLED(SPI_FLASH_MTD)
|
||||
int spi_flash_mtd_register(struct spi_flash *flash);
|
||||
void spi_flash_mtd_unregister(void);
|
||||
#endif
|
||||
#endif /* _SF_INTERNAL_H_ */
|
||||
@@ -0,0 +1,147 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (C) 2012-2014 Daniel Schwierzeck, daniel.schwierzeck@gmail.com
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <malloc.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <spi_flash.h>
|
||||
|
||||
static struct mtd_info sf_mtd_info;
|
||||
static bool sf_mtd_registered;
|
||||
static char sf_mtd_name[8];
|
||||
|
||||
static int spi_flash_mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
struct spi_flash *flash = mtd->priv;
|
||||
int err;
|
||||
|
||||
if (!flash)
|
||||
return -ENODEV;
|
||||
|
||||
instr->state = MTD_ERASING;
|
||||
|
||||
err = spi_flash_erase(flash, instr->addr, instr->len);
|
||||
if (err) {
|
||||
instr->state = MTD_ERASE_FAILED;
|
||||
instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
mtd_erase_callback(instr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_flash_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct spi_flash *flash = mtd->priv;
|
||||
int err;
|
||||
|
||||
if (!flash)
|
||||
return -ENODEV;
|
||||
|
||||
err = spi_flash_read(flash, from, len, buf);
|
||||
if (!err)
|
||||
*retlen = len;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int spi_flash_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct spi_flash *flash = mtd->priv;
|
||||
int err;
|
||||
|
||||
if (!flash)
|
||||
return -ENODEV;
|
||||
|
||||
err = spi_flash_write(flash, to, len, buf);
|
||||
if (!err)
|
||||
*retlen = len;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void spi_flash_mtd_sync(struct mtd_info *mtd)
|
||||
{
|
||||
}
|
||||
|
||||
static int spi_flash_mtd_number(void)
|
||||
{
|
||||
#ifdef CONFIG_SYS_MAX_FLASH_BANKS
|
||||
return CONFIG_SYS_MAX_FLASH_BANKS;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int spi_flash_mtd_register(struct spi_flash *flash)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (sf_mtd_registered) {
|
||||
ret = del_mtd_device(&sf_mtd_info);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sf_mtd_registered = false;
|
||||
}
|
||||
|
||||
sf_mtd_registered = false;
|
||||
memset(&sf_mtd_info, 0, sizeof(sf_mtd_info));
|
||||
sprintf(sf_mtd_name, "nor%d", spi_flash_mtd_number());
|
||||
|
||||
sf_mtd_info.name = sf_mtd_name;
|
||||
sf_mtd_info.type = MTD_NORFLASH;
|
||||
sf_mtd_info.flags = MTD_CAP_NORFLASH;
|
||||
sf_mtd_info.writesize = 1;
|
||||
sf_mtd_info.writebufsize = flash->page_size;
|
||||
|
||||
sf_mtd_info._erase = spi_flash_mtd_erase;
|
||||
sf_mtd_info._read = spi_flash_mtd_read;
|
||||
sf_mtd_info._write = spi_flash_mtd_write;
|
||||
sf_mtd_info._sync = spi_flash_mtd_sync;
|
||||
|
||||
sf_mtd_info.size = flash->size;
|
||||
sf_mtd_info.priv = flash;
|
||||
|
||||
/* Only uniform flash devices for now */
|
||||
sf_mtd_info.numeraseregions = 0;
|
||||
sf_mtd_info.erasesize = flash->sector_size;
|
||||
|
||||
ret = add_mtd_device(&sf_mtd_info);
|
||||
if (!ret)
|
||||
sf_mtd_registered = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void spi_flash_mtd_unregister(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!sf_mtd_registered)
|
||||
return;
|
||||
|
||||
ret = del_mtd_device(&sf_mtd_info);
|
||||
if (!ret) {
|
||||
sf_mtd_registered = false;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Setting mtd->priv to NULL is the best we can do. Thanks to that,
|
||||
* the MTD layer can still call mtd hooks without risking a
|
||||
* use-after-free bug. Still, things should be fixed to prevent the
|
||||
* spi_flash object from being destroyed when del_mtd_device() fails.
|
||||
*/
|
||||
sf_mtd_info.priv = NULL;
|
||||
printf("Failed to unregister MTD %s and the spi_flash object is going away: you're in deep trouble!",
|
||||
sf_mtd_info.name);
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* SPI flash probing
|
||||
*
|
||||
* Copyright (C) 2008 Atmel Corporation
|
||||
* Copyright (C) 2010 Reinhard Meyer, EMK Elektronik
|
||||
* Copyright (C) 2013 Jagannadha Sutradharudu Teki, Xilinx Inc.
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <dm.h>
|
||||
#include <errno.h>
|
||||
#include <malloc.h>
|
||||
#include <spi.h>
|
||||
#include <spi_flash.h>
|
||||
|
||||
#include "sf_internal.h"
|
||||
|
||||
/**
|
||||
* spi_flash_probe_slave() - Probe for a SPI flash device on a bus
|
||||
*
|
||||
* @flashp: Pointer to place to put flash info, which may be NULL if the
|
||||
* space should be allocated
|
||||
*/
|
||||
static int spi_flash_probe_slave(struct spi_flash *flash)
|
||||
{
|
||||
struct spi_slave *spi = flash->spi;
|
||||
int ret;
|
||||
|
||||
/* Setup spi_slave */
|
||||
if (!spi) {
|
||||
printf("SF: Failed to set up slave\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Claim spi bus */
|
||||
ret = spi_claim_bus(spi);
|
||||
if (ret) {
|
||||
debug("SF: Failed to claim SPI bus: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = spi_nor_scan(flash);
|
||||
if (ret)
|
||||
goto err_read_id;
|
||||
|
||||
#if CONFIG_IS_ENABLED(SPI_FLASH_MTD)
|
||||
ret = spi_flash_mtd_register(flash);
|
||||
#endif
|
||||
|
||||
err_read_id:
|
||||
spi_release_bus(spi);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_DM_SPI_FLASH
|
||||
struct spi_flash *spi_flash_probe(unsigned int busnum, unsigned int cs,
|
||||
unsigned int max_hz, unsigned int spi_mode)
|
||||
{
|
||||
struct spi_slave *bus;
|
||||
struct spi_flash *flash;
|
||||
|
||||
bus = spi_setup_slave(busnum, cs, max_hz, spi_mode);
|
||||
if (!bus)
|
||||
return NULL;
|
||||
|
||||
/* Allocate space if needed (not used by sf-uclass */
|
||||
flash = calloc(1, sizeof(*flash));
|
||||
if (!flash) {
|
||||
debug("SF: Failed to allocate spi_flash\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
flash->spi = bus;
|
||||
if (spi_flash_probe_slave(flash)) {
|
||||
spi_free_slave(bus);
|
||||
free(flash);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return flash;
|
||||
}
|
||||
|
||||
void spi_flash_free(struct spi_flash *flash)
|
||||
{
|
||||
#if CONFIG_IS_ENABLED(SPI_FLASH_MTD)
|
||||
spi_flash_mtd_unregister();
|
||||
#endif
|
||||
spi_free_slave(flash->spi);
|
||||
free(flash);
|
||||
}
|
||||
|
||||
#else /* defined CONFIG_DM_SPI_FLASH */
|
||||
|
||||
static int spi_flash_std_read(struct udevice *dev, u32 offset, size_t len,
|
||||
void *buf)
|
||||
{
|
||||
struct spi_flash *flash = dev_get_uclass_priv(dev);
|
||||
struct mtd_info *mtd = &flash->mtd;
|
||||
size_t retlen;
|
||||
|
||||
return log_ret(mtd->_read(mtd, offset, len, &retlen, buf));
|
||||
}
|
||||
|
||||
static int spi_flash_std_write(struct udevice *dev, u32 offset, size_t len,
|
||||
const void *buf)
|
||||
{
|
||||
struct spi_flash *flash = dev_get_uclass_priv(dev);
|
||||
struct mtd_info *mtd = &flash->mtd;
|
||||
size_t retlen;
|
||||
|
||||
return mtd->_write(mtd, offset, len, &retlen, buf);
|
||||
}
|
||||
|
||||
static int spi_flash_std_erase(struct udevice *dev, u32 offset, size_t len)
|
||||
{
|
||||
struct spi_flash *flash = dev_get_uclass_priv(dev);
|
||||
struct mtd_info *mtd = &flash->mtd;
|
||||
struct erase_info instr;
|
||||
|
||||
if (offset % mtd->erasesize || len % mtd->erasesize) {
|
||||
printf("SF: Erase offset/length not multiple of erase size\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memset(&instr, 0, sizeof(instr));
|
||||
instr.addr = offset;
|
||||
instr.len = len;
|
||||
|
||||
return mtd->_erase(mtd, &instr);
|
||||
}
|
||||
|
||||
static int spi_flash_std_get_sw_write_prot(struct udevice *dev)
|
||||
{
|
||||
struct spi_flash *flash = dev_get_uclass_priv(dev);
|
||||
|
||||
return spi_flash_cmd_get_sw_write_prot(flash);
|
||||
}
|
||||
|
||||
static int spi_flash_std_probe(struct udevice *dev)
|
||||
{
|
||||
struct spi_slave *slave = dev_get_parent_priv(dev);
|
||||
struct dm_spi_slave_platdata *plat = dev_get_parent_platdata(dev);
|
||||
struct spi_flash *flash;
|
||||
|
||||
flash = dev_get_uclass_priv(dev);
|
||||
flash->dev = dev;
|
||||
flash->spi = slave;
|
||||
debug("%s: slave=%p, cs=%d\n", __func__, slave, plat->cs);
|
||||
return spi_flash_probe_slave(flash);
|
||||
}
|
||||
|
||||
static int spi_flash_std_remove(struct udevice *dev)
|
||||
{
|
||||
#if CONFIG_IS_ENABLED(SPI_FLASH_MTD)
|
||||
spi_flash_mtd_unregister();
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dm_spi_flash_ops spi_flash_std_ops = {
|
||||
.read = spi_flash_std_read,
|
||||
.write = spi_flash_std_write,
|
||||
.erase = spi_flash_std_erase,
|
||||
.get_sw_write_prot = spi_flash_std_get_sw_write_prot,
|
||||
};
|
||||
|
||||
static const struct udevice_id spi_flash_std_ids[] = {
|
||||
{ .compatible = "jedec,spi-nor" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(spi_flash_std) = {
|
||||
.name = "spi_flash_std",
|
||||
.id = UCLASS_SPI_FLASH,
|
||||
.of_match = spi_flash_std_ids,
|
||||
.probe = spi_flash_std_probe,
|
||||
.remove = spi_flash_std_remove,
|
||||
.priv_auto_alloc_size = sizeof(struct spi_flash),
|
||||
.ops = &spi_flash_std_ops,
|
||||
};
|
||||
|
||||
#endif /* CONFIG_DM_SPI_FLASH */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,320 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
*
|
||||
* Copyright (C) 2013 Jagannadha Sutradharudu Teki, Xilinx Inc.
|
||||
* Copyright (C) 2016 Jagan Teki <jagan@openedev.com>
|
||||
* Copyright (C) 2018 Texas Instruments Incorporated - http://www.ti.com/
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <spi.h>
|
||||
#include <spi_flash.h>
|
||||
|
||||
#include "sf_internal.h"
|
||||
|
||||
/* Exclude chip names for SPL to save space */
|
||||
#if !CONFIG_IS_ENABLED(SPI_FLASH_TINY)
|
||||
#define INFO_NAME(_name) .name = _name,
|
||||
#else
|
||||
#define INFO_NAME(_name)
|
||||
#endif
|
||||
|
||||
/* Used when the "_ext_id" is two bytes at most */
|
||||
#define INFO(_name, _jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \
|
||||
INFO_NAME(_name) \
|
||||
.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(_name, _jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \
|
||||
INFO_NAME(_name) \
|
||||
.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),
|
||||
|
||||
/* NOTE: double check command sets and memory organization when you add
|
||||
* more nor chips. This current list focusses on newer chips, which
|
||||
* have been converging on command sets which including JEDEC ID.
|
||||
*
|
||||
* All newly added entries should describe *hardware* and should use SECT_4K
|
||||
* (or SECT_4K_PMC) if hardware supports erasing 4 KiB sectors. For usage
|
||||
* scenarios excluding small sectors there is config option that can be
|
||||
* disabled: CONFIG_SPI_FLASH_USE_4K_SECTORS.
|
||||
* For historical (and compatibility) reasons (before we got above config) some
|
||||
* old entries may be missing 4K flag.
|
||||
*/
|
||||
const struct flash_info spi_nor_ids[] = {
|
||||
#ifdef CONFIG_SPI_FLASH_ATMEL /* ATMEL */
|
||||
/* Atmel -- some are (confusingly) marketed as "DataFlash" */
|
||||
{ INFO("at26df321", 0x1f4700, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ INFO("at25df321a", 0x1f4701, 0, 64 * 1024, 64, SECT_4K) },
|
||||
|
||||
{ INFO("at45db011d", 0x1f2200, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ INFO("at45db021d", 0x1f2300, 0, 64 * 1024, 8, SECT_4K) },
|
||||
{ INFO("at45db041d", 0x1f2400, 0, 64 * 1024, 8, SECT_4K) },
|
||||
{ INFO("at45db081d", 0x1f2500, 0, 64 * 1024, 16, SECT_4K) },
|
||||
{ INFO("at45db161d", 0x1f2600, 0, 64 * 1024, 32, SECT_4K) },
|
||||
{ INFO("at45db321d", 0x1f2700, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ INFO("at45db641d", 0x1f2800, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ INFO("at25sl321", 0x1f4216, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ INFO("at26df081a", 0x1f4501, 0, 64 * 1024, 16, SECT_4K) },
|
||||
#endif
|
||||
#ifdef CONFIG_SPI_FLASH_EON /* EON */
|
||||
/* EON -- en25xxx */
|
||||
{ INFO("en25q32b", 0x1c3016, 0, 64 * 1024, 64, 0) },
|
||||
{ INFO("en25q64", 0x1c3017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ INFO("en25qh128", 0x1c7018, 0, 64 * 1024, 256, 0) },
|
||||
{ INFO("en25s64", 0x1c3817, 0, 64 * 1024, 128, SECT_4K) },
|
||||
#endif
|
||||
#ifdef CONFIG_SPI_FLASH_GIGADEVICE /* GIGADEVICE */
|
||||
/* GigaDevice */
|
||||
{
|
||||
INFO("gd25q16", 0xc84015, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{
|
||||
INFO("gd25q32", 0xc84016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{
|
||||
INFO("gd25lq32", 0xc86016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{
|
||||
INFO("gd25q64", 0xc84017, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{
|
||||
INFO("gd25q128", 0xc84018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{
|
||||
INFO("gd25lq128", 0xc86018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
#endif
|
||||
#ifdef CONFIG_SPI_FLASH_ISSI /* ISSI */
|
||||
/* ISSI */
|
||||
{ INFO("is25lq040b", 0x9d4013, 0, 64 * 1024, 8,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("is25lp032", 0x9d6016, 0, 64 * 1024, 64, 0) },
|
||||
{ INFO("is25lp064", 0x9d6017, 0, 64 * 1024, 128, 0) },
|
||||
{ INFO("is25lp128", 0x9d6018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ) },
|
||||
{ INFO("is25lp256", 0x9d6019, 0, 64 * 1024, 512,
|
||||
SECT_4K | SPI_NOR_DUAL_READ) },
|
||||
{ INFO("is25wp032", 0x9d7016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("is25wp064", 0x9d7017, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("is25wp128", 0x9d7018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("is25wp256", 0x9d7019, 0, 64 * 1024, 512,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
#endif
|
||||
#ifdef CONFIG_SPI_FLASH_MACRONIX /* MACRONIX */
|
||||
/* Macronix */
|
||||
{ INFO("mx25l2005a", 0xc22012, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ INFO("mx25l4005a", 0xc22013, 0, 64 * 1024, 8, SECT_4K) },
|
||||
{ INFO("mx25l8005", 0xc22014, 0, 64 * 1024, 16, 0) },
|
||||
{ INFO("mx25l1606e", 0xc22015, 0, 64 * 1024, 32, SECT_4K) },
|
||||
{ INFO("mx25l3205d", 0xc22016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ INFO("mx25l6405d", 0xc22017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ INFO("mx25u2033e", 0xc22532, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ INFO("mx25u1635e", 0xc22535, 0, 64 * 1024, 32, SECT_4K) },
|
||||
{ INFO("mx25u6435f", 0xc22537, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ INFO("mx25l12805d", 0xc22018, 0, 64 * 1024, 256, 0) },
|
||||
{ INFO("mx25l12855e", 0xc22618, 0, 64 * 1024, 256, 0) },
|
||||
{ INFO("mx25l25635e", 0xc22019, 0, 64 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("mx25u25635f", 0xc22539, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_4B_OPCODES) },
|
||||
{ INFO("mx25l25655e", 0xc22619, 0, 64 * 1024, 512, 0) },
|
||||
{ INFO("mx66l51235l", 0xc2201a, 0, 64 * 1024, 1024, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
|
||||
{ INFO("mx66u51235f", 0xc2253a, 0, 64 * 1024, 1024, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
|
||||
{ INFO("mx66u2g45g", 0xc2253c, 0, 64 * 1024, 4096, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
|
||||
{ INFO("mx66l1g45g", 0xc2201b, 0, 64 * 1024, 2048, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("mx25l1633e", 0xc22415, 0, 64 * 1024, 32, SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES | SECT_4K) },
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SPI_FLASH_STMICRO /* STMICRO */
|
||||
/* Micron */
|
||||
{ INFO("n25q016a", 0x20bb15, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("n25q032", 0x20ba16, 0, 64 * 1024, 64, SPI_NOR_QUAD_READ) },
|
||||
{ INFO("n25q032a", 0x20bb16, 0, 64 * 1024, 64, SPI_NOR_QUAD_READ) },
|
||||
{ INFO("n25q064", 0x20ba17, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("n25q064a", 0x20bb17, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("n25q128a11", 0x20bb18, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("n25q128a13", 0x20ba18, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
{ INFO6("mt25ql256a", 0x20ba19, 0x104400, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES | USE_FSR) },
|
||||
{ INFO("n25q256a", 0x20ba19, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_FSR) },
|
||||
{ INFO6("mt25qu256a", 0x20bb19, 0x104400, 64 * 1024, 512, SECT_4K | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES | USE_FSR) },
|
||||
{ INFO("n25q256ax1", 0x20bb19, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_QUAD_READ | USE_FSR) },
|
||||
{ INFO6("mt25qu512a", 0x20bb20, 0x104400, 64 * 1024, 1024,
|
||||
SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES |
|
||||
USE_FSR) },
|
||||
{ INFO("n25q512a", 0x20bb20, 0, 64 * 1024, 1024, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ) },
|
||||
{ INFO6("mt25ql512a", 0x20ba20, 0x104400, 64 * 1024, 1024, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
|
||||
{ INFO("n25q512ax3", 0x20ba20, 0, 64 * 1024, 1024, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("n25q00", 0x20ba21, 0, 64 * 1024, 2048, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ | NO_CHIP_ERASE) },
|
||||
{ INFO("n25q00a", 0x20bb21, 0, 64 * 1024, 2048, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ | NO_CHIP_ERASE) },
|
||||
{ INFO("mt25qu02g", 0x20bb22, 0, 64 * 1024, 4096, SECT_4K | USE_FSR | SPI_NOR_QUAD_READ | NO_CHIP_ERASE) },
|
||||
{ INFO("mt35xu512aba", 0x2c5b1a, 0, 128 * 1024, 512, USE_FSR | SPI_NOR_4B_OPCODES) },
|
||||
{ INFO("mt35xu02g", 0x2c5b1c, 0, 128 * 1024, 2048, USE_FSR | SPI_NOR_4B_OPCODES) },
|
||||
#endif
|
||||
#ifdef CONFIG_SPI_FLASH_SPANSION /* SPANSION */
|
||||
/* Spansion/Cypress -- single (large) sector size only, at least
|
||||
* for the chips listed here (without boot sectors).
|
||||
*/
|
||||
{ INFO("s25sl032p", 0x010215, 0x4d00, 64 * 1024, 64, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("s25sl064p", 0x010216, 0x4d00, 64 * 1024, 128, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("s25fl256s0", 0x010219, 0x4d00, 256 * 1024, 128, USE_CLSR) },
|
||||
{ INFO("s25fl256s1", 0x010219, 0x4d01, 64 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
|
||||
{ INFO6("s25fl512s", 0x010220, 0x4d0081, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
|
||||
{ INFO("s25fl512s_256k", 0x010220, 0x4d00, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
|
||||
{ INFO("s25fl512s_64k", 0x010220, 0x4d01, 64 * 1024, 1024, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
|
||||
{ INFO("s25fl512s_512k", 0x010220, 0x4f00, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
|
||||
{ INFO("s25sl12800", 0x012018, 0x0300, 256 * 1024, 64, 0) },
|
||||
{ INFO("s25sl12801", 0x012018, 0x0301, 64 * 1024, 256, 0) },
|
||||
{ INFO6("s25fl128s", 0x012018, 0x4d0180, 64 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
|
||||
{ INFO("s25fl129p0", 0x012018, 0x4d00, 256 * 1024, 64, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
|
||||
{ INFO("s25fl129p1", 0x012018, 0x4d01, 64 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
|
||||
{ INFO("s25sl008a", 0x010213, 0, 64 * 1024, 16, 0) },
|
||||
{ INFO("s25sl016a", 0x010214, 0, 64 * 1024, 32, 0) },
|
||||
{ INFO("s25sl032a", 0x010215, 0, 64 * 1024, 64, 0) },
|
||||
{ INFO("s25sl064a", 0x010216, 0, 64 * 1024, 128, 0) },
|
||||
{ INFO("s25fl116k", 0x014015, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("s25fl164k", 0x014017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ INFO("s25fl208k", 0x014014, 0, 64 * 1024, 16, SECT_4K | SPI_NOR_DUAL_READ) },
|
||||
{ INFO("s25fl064l", 0x016017, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
|
||||
{ INFO("s25fl128l", 0x016018, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
|
||||
#endif
|
||||
#ifdef CONFIG_SPI_FLASH_SST /* SST */
|
||||
/* SST -- large erase sizes are "overlays", "sectors" are 4K */
|
||||
{ INFO("sst25vf040b", 0xbf258d, 0, 64 * 1024, 8, SECT_4K | SST_WRITE) },
|
||||
{ INFO("sst25vf080b", 0xbf258e, 0, 64 * 1024, 16, SECT_4K | SST_WRITE) },
|
||||
{ INFO("sst25vf016b", 0xbf2541, 0, 64 * 1024, 32, SECT_4K | SST_WRITE) },
|
||||
{ INFO("sst25vf032b", 0xbf254a, 0, 64 * 1024, 64, SECT_4K | SST_WRITE) },
|
||||
{ INFO("sst25vf064c", 0xbf254b, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ INFO("sst25wf512", 0xbf2501, 0, 64 * 1024, 1, SECT_4K | SST_WRITE) },
|
||||
{ INFO("sst25wf010", 0xbf2502, 0, 64 * 1024, 2, SECT_4K | SST_WRITE) },
|
||||
{ INFO("sst25wf020", 0xbf2503, 0, 64 * 1024, 4, SECT_4K | SST_WRITE) },
|
||||
{ INFO("sst25wf020a", 0x621612, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ INFO("sst25wf040b", 0x621613, 0, 64 * 1024, 8, SECT_4K) },
|
||||
{ INFO("sst25wf040", 0xbf2504, 0, 64 * 1024, 8, SECT_4K | SST_WRITE) },
|
||||
{ INFO("sst25wf080", 0xbf2505, 0, 64 * 1024, 16, SECT_4K | SST_WRITE) },
|
||||
{ INFO("sst26vf064b", 0xbf2643, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_HAS_SST26LOCK | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("sst26wf016", 0xbf2651, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_HAS_SST26LOCK) },
|
||||
{ INFO("sst26wf032", 0xbf2622, 0, 64 * 1024, 64, SECT_4K | SPI_NOR_HAS_SST26LOCK) },
|
||||
{ INFO("sst26wf064", 0xbf2643, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_HAS_SST26LOCK) },
|
||||
#endif
|
||||
#ifdef CONFIG_SPI_FLASH_STMICRO /* STMICRO */
|
||||
/* ST Microelectronics -- newer production may have feature updates */
|
||||
{ INFO("m25p10", 0x202011, 0, 32 * 1024, 4, 0) },
|
||||
{ INFO("m25p20", 0x202012, 0, 64 * 1024, 4, 0) },
|
||||
{ INFO("m25p40", 0x202013, 0, 64 * 1024, 8, 0) },
|
||||
{ INFO("m25p80", 0x202014, 0, 64 * 1024, 16, 0) },
|
||||
{ INFO("m25p16", 0x202015, 0, 64 * 1024, 32, 0) },
|
||||
{ INFO("m25p32", 0x202016, 0, 64 * 1024, 64, 0) },
|
||||
{ INFO("m25p64", 0x202017, 0, 64 * 1024, 128, 0) },
|
||||
{ INFO("m25p128", 0x202018, 0, 256 * 1024, 64, 0) },
|
||||
{ INFO("m25pe16", 0x208015, 0, 64 * 1024, 32, SECT_4K) },
|
||||
{ INFO("m25px16", 0x207115, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("m25px64", 0x207117, 0, 64 * 1024, 128, 0) },
|
||||
#endif
|
||||
#ifdef CONFIG_SPI_FLASH_WINBOND /* WINBOND */
|
||||
/* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */
|
||||
{ INFO("w25p80", 0xef2014, 0x0, 64 * 1024, 16, 0) },
|
||||
{ INFO("w25p16", 0xef2015, 0x0, 64 * 1024, 32, 0) },
|
||||
{ INFO("w25p32", 0xef2016, 0x0, 64 * 1024, 64, 0) },
|
||||
{ INFO("w25x05", 0xef3010, 0, 64 * 1024, 1, SECT_4K) },
|
||||
{ INFO("w25x40", 0xef3013, 0, 64 * 1024, 8, SECT_4K) },
|
||||
{ INFO("w25x16", 0xef3015, 0, 64 * 1024, 32, SECT_4K) },
|
||||
{
|
||||
INFO("w25q16dw", 0xef6015, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{ INFO("w25x32", 0xef3016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ INFO("w25q20cl", 0xef4012, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ INFO("w25q20bw", 0xef5012, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ INFO("w25q20ew", 0xef6012, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ INFO("w25q32", 0xef4016, 0, 64 * 1024, 64, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{
|
||||
INFO("w25q32dw", 0xef6016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{
|
||||
INFO("w25q32jv", 0xef7016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{ INFO("w25x64", 0xef3017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{
|
||||
INFO("w25q64dw", 0xef6017, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{
|
||||
INFO("w25q64jv", 0xef7017, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{
|
||||
INFO("w25q128fw", 0xef6018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{
|
||||
INFO("w25q128jv", 0xef7018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{
|
||||
INFO("w25q256fw", 0xef6019, 0, 64 * 1024, 512,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{
|
||||
INFO("w25q256jw", 0xef7019, 0, 64 * 1024, 512,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{ INFO("w25q80", 0xef5014, 0, 64 * 1024, 16, SECT_4K) },
|
||||
{ INFO("w25q80bl", 0xef4014, 0, 64 * 1024, 16, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("w25q16cl", 0xef4015, 0, 64 * 1024, 32, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("w25q64cv", 0xef4017, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("w25q128", 0xef4018, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("w25q256", 0xef4019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
#endif
|
||||
#ifdef CONFIG_SPI_FLASH_XMC
|
||||
/* XMC (Wuhan Xinxin Semiconductor Manufacturing Corp.) */
|
||||
{ INFO("XM25QH64A", 0x207017, 0, 64 * 1024, 128, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ INFO("XM25QH128A", 0x207018, 0, 64 * 1024, 256, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
#endif
|
||||
{ },
|
||||
};
|
||||
@@ -0,0 +1,804 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Based on m25p80.c, by Mike Lavender (mike@steroidmicros.com), with
|
||||
* influence from lart.c (Abraham Van Der Merwe) and mtd_dataflash.c
|
||||
*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*
|
||||
* Synced from Linux v4.19
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/log2.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
#include <spi-mem.h>
|
||||
#include <spi.h>
|
||||
|
||||
#include "sf_internal.h"
|
||||
|
||||
/* Define max times to check status register before we give up. */
|
||||
|
||||
/*
|
||||
* For everything but full-chip erase; probably could be much smaller, but kept
|
||||
* around for safety for now
|
||||
*/
|
||||
|
||||
#define HZ CONFIG_SYS_HZ
|
||||
|
||||
#define DEFAULT_READY_WAIT_JIFFIES (40UL * HZ)
|
||||
|
||||
static int spi_nor_read_write_reg(struct spi_nor *nor, struct spi_mem_op
|
||||
*op, void *buf)
|
||||
{
|
||||
if (op->data.dir == SPI_MEM_DATA_IN)
|
||||
op->data.buf.in = buf;
|
||||
else
|
||||
op->data.buf.out = buf;
|
||||
return spi_mem_exec_op(nor->spi, op);
|
||||
}
|
||||
|
||||
static int spi_nor_read_reg(struct spi_nor *nor, u8 code, u8 *val, int len)
|
||||
{
|
||||
struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(code, 1),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_IN(len, NULL, 1));
|
||||
int ret;
|
||||
|
||||
ret = spi_nor_read_write_reg(nor, &op, val);
|
||||
if (ret < 0)
|
||||
dev_dbg(&flash->spimem->spi->dev, "error %d reading %x\n", ret,
|
||||
code);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spi_nor_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
|
||||
{
|
||||
struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 1),
|
||||
SPI_MEM_OP_NO_ADDR,
|
||||
SPI_MEM_OP_NO_DUMMY,
|
||||
SPI_MEM_OP_DATA_OUT(len, NULL, 1));
|
||||
|
||||
return spi_nor_read_write_reg(nor, &op, buf);
|
||||
}
|
||||
|
||||
static ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
|
||||
u_char *buf)
|
||||
{
|
||||
struct spi_mem_op op =
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 1),
|
||||
SPI_MEM_OP_ADDR(nor->addr_width, from, 1),
|
||||
SPI_MEM_OP_DUMMY(nor->read_dummy, 1),
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 1));
|
||||
size_t remaining = len;
|
||||
int ret;
|
||||
|
||||
/* get transfer protocols. */
|
||||
op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->read_proto);
|
||||
op.addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->read_proto);
|
||||
op.dummy.buswidth = op.addr.buswidth;
|
||||
op.data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto);
|
||||
|
||||
/* convert the dummy cycles to the number of bytes */
|
||||
op.dummy.nbytes = (nor->read_dummy * op.dummy.buswidth) / 8;
|
||||
|
||||
while (remaining) {
|
||||
op.data.nbytes = remaining < UINT_MAX ? remaining : UINT_MAX;
|
||||
ret = spi_mem_adjust_op_size(nor->spi, &op);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = spi_mem_exec_op(nor->spi, &op);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
op.addr.val += op.data.nbytes;
|
||||
remaining -= op.data.nbytes;
|
||||
op.data.buf.in += op.data.nbytes;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_SPI_FLASH_SPANSION) || defined(CONFIG_SPI_FLASH_WINBOND)
|
||||
/*
|
||||
* Read configuration register, returning its value in the
|
||||
* location. Return the configuration register value.
|
||||
* Returns negative if error occurred.
|
||||
*/
|
||||
static int read_cr(struct spi_nor *nor)
|
||||
{
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
ret = spi_nor_read_reg(nor, SPINOR_OP_RDCR, &val, 1);
|
||||
if (ret < 0) {
|
||||
dev_dbg(nor->dev, "error %d reading CR\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Write status register 1 byte
|
||||
* Returns negative if error occurred.
|
||||
*/
|
||||
static inline int write_sr(struct spi_nor *nor, u8 val)
|
||||
{
|
||||
nor->cmd_buf[0] = val;
|
||||
return spi_nor_write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set write enable latch with Write Enable command.
|
||||
* Returns negative if error occurred.
|
||||
*/
|
||||
static inline int write_enable(struct spi_nor *nor)
|
||||
{
|
||||
return spi_nor_write_reg(nor, SPINOR_OP_WREN, NULL, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Send write disable instruction to the chip.
|
||||
*/
|
||||
static inline int write_disable(struct spi_nor *nor)
|
||||
{
|
||||
return spi_nor_write_reg(nor, SPINOR_OP_WRDI, NULL, 0);
|
||||
}
|
||||
|
||||
static inline struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd)
|
||||
{
|
||||
return mtd->priv;
|
||||
}
|
||||
|
||||
static u8 spi_nor_convert_opcode(u8 opcode, const u8 table[][2], size_t size)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < size; i++)
|
||||
if (table[i][0] == opcode)
|
||||
return table[i][1];
|
||||
|
||||
/* No conversion found, keep input op code. */
|
||||
return opcode;
|
||||
}
|
||||
|
||||
static inline u8 spi_nor_convert_3to4_read(u8 opcode)
|
||||
{
|
||||
static const u8 spi_nor_3to4_read[][2] = {
|
||||
{ SPINOR_OP_READ, SPINOR_OP_READ_4B },
|
||||
{ SPINOR_OP_READ_FAST, SPINOR_OP_READ_FAST_4B },
|
||||
{ SPINOR_OP_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B },
|
||||
{ SPINOR_OP_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B },
|
||||
{ SPINOR_OP_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B },
|
||||
{ SPINOR_OP_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B },
|
||||
};
|
||||
|
||||
return spi_nor_convert_opcode(opcode, spi_nor_3to4_read,
|
||||
ARRAY_SIZE(spi_nor_3to4_read));
|
||||
}
|
||||
|
||||
static void spi_nor_set_4byte_opcodes(struct spi_nor *nor,
|
||||
const struct flash_info *info)
|
||||
{
|
||||
nor->read_opcode = spi_nor_convert_3to4_read(nor->read_opcode);
|
||||
}
|
||||
|
||||
/* Enable/disable 4-byte addressing mode. */
|
||||
static inline int set_4byte(struct spi_nor *nor, const struct flash_info *info,
|
||||
int enable)
|
||||
{
|
||||
int status;
|
||||
bool need_wren = false;
|
||||
u8 cmd;
|
||||
|
||||
switch (JEDEC_MFR(info)) {
|
||||
case SNOR_MFR_ST:
|
||||
case SNOR_MFR_MICRON:
|
||||
/* Some Micron need WREN command; all will accept it */
|
||||
need_wren = true;
|
||||
case SNOR_MFR_MACRONIX:
|
||||
case SNOR_MFR_WINBOND:
|
||||
if (need_wren)
|
||||
write_enable(nor);
|
||||
|
||||
cmd = enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B;
|
||||
status = spi_nor_write_reg(nor, cmd, NULL, 0);
|
||||
if (need_wren)
|
||||
write_disable(nor);
|
||||
|
||||
if (!status && !enable &&
|
||||
JEDEC_MFR(info) == SNOR_MFR_WINBOND) {
|
||||
/*
|
||||
* On Winbond W25Q256FV, leaving 4byte mode causes
|
||||
* the Extended Address Register to be set to 1, so all
|
||||
* 3-byte-address reads come from the second 16M.
|
||||
* We must clear the register to enable normal behavior.
|
||||
*/
|
||||
write_enable(nor);
|
||||
nor->cmd_buf[0] = 0;
|
||||
spi_nor_write_reg(nor, SPINOR_OP_WREAR,
|
||||
nor->cmd_buf, 1);
|
||||
write_disable(nor);
|
||||
}
|
||||
|
||||
return status;
|
||||
default:
|
||||
/* Spansion style */
|
||||
nor->cmd_buf[0] = enable << 7;
|
||||
return spi_nor_write_reg(nor, SPINOR_OP_BRWR, nor->cmd_buf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(CONFIG_SPI_FLASH_SPANSION) || \
|
||||
defined(CONFIG_SPI_FLASH_WINBOND) || \
|
||||
defined(CONFIG_SPI_FLASH_MACRONIX)
|
||||
/*
|
||||
* 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_nor *nor)
|
||||
{
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
ret = spi_nor_read_reg(nor, SPINOR_OP_RDSR, &val, 1);
|
||||
if (ret < 0) {
|
||||
pr_debug("error %d reading SR\n", (int)ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the flag status register, returning its value in the location
|
||||
* Return the status register value.
|
||||
* Returns negative if error occurred.
|
||||
*/
|
||||
static int read_fsr(struct spi_nor *nor)
|
||||
{
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
ret = spi_nor_read_reg(nor, SPINOR_OP_RDFSR, &val, 1);
|
||||
if (ret < 0) {
|
||||
pr_debug("error %d reading FSR\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static int spi_nor_sr_ready(struct spi_nor *nor)
|
||||
{
|
||||
int sr = read_sr(nor);
|
||||
|
||||
if (sr < 0)
|
||||
return sr;
|
||||
|
||||
return !(sr & SR_WIP);
|
||||
}
|
||||
|
||||
static int spi_nor_fsr_ready(struct spi_nor *nor)
|
||||
{
|
||||
int fsr = read_fsr(nor);
|
||||
|
||||
if (fsr < 0)
|
||||
return fsr;
|
||||
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 = nor->flags & SNOR_F_USE_FSR ? spi_nor_fsr_ready(nor) : 1;
|
||||
if (fsr < 0)
|
||||
return fsr;
|
||||
return sr && fsr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Service routine to read status register until ready, or timeout occurs.
|
||||
* Returns non-zero if error.
|
||||
*/
|
||||
static int spi_nor_wait_till_ready_with_timeout(struct spi_nor *nor,
|
||||
unsigned long timeout)
|
||||
{
|
||||
unsigned long timebase;
|
||||
int ret;
|
||||
|
||||
timebase = get_timer(0);
|
||||
|
||||
while (get_timer(timebase) < timeout) {
|
||||
ret = spi_nor_ready(nor);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret)
|
||||
return 0;
|
||||
}
|
||||
|
||||
dev_err(nor->dev, "flash operation timed out\n");
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
static int spi_nor_wait_till_ready(struct spi_nor *nor)
|
||||
{
|
||||
return spi_nor_wait_till_ready_with_timeout(nor,
|
||||
DEFAULT_READY_WAIT_JIFFIES);
|
||||
}
|
||||
#endif /* CONFIG_SPI_FLASH_SPANSION */
|
||||
|
||||
/*
|
||||
* Erase an address range on the nor chip. The address range may extend
|
||||
* one or more erase sectors. Return an error is there is a problem erasing.
|
||||
*/
|
||||
static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static const struct flash_info *spi_nor_read_id(struct spi_nor *nor)
|
||||
{
|
||||
int tmp;
|
||||
u8 id[SPI_NOR_MAX_ID_LEN];
|
||||
const struct flash_info *info;
|
||||
|
||||
tmp = spi_nor_read_reg(nor, SPINOR_OP_RDID, id, SPI_NOR_MAX_ID_LEN);
|
||||
if (tmp < 0) {
|
||||
dev_dbg(nor->dev, "error %d reading JEDEC ID\n", tmp);
|
||||
return ERR_PTR(tmp);
|
||||
}
|
||||
|
||||
info = spi_nor_ids;
|
||||
for (; info->sector_size != 0; info++) {
|
||||
if (info->id_len) {
|
||||
if (!memcmp(info->id, id, info->id_len))
|
||||
return info;
|
||||
}
|
||||
}
|
||||
dev_dbg(nor->dev, "unrecognized JEDEC id bytes: %02x, %02x, %02x\n",
|
||||
id[0], id[1], id[2]);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct spi_nor *nor = mtd_to_spi_nor(mtd);
|
||||
int ret;
|
||||
|
||||
dev_dbg(nor->dev, "from 0x%08x, len %zd\n", (u32)from, len);
|
||||
|
||||
while (len) {
|
||||
loff_t addr = from;
|
||||
|
||||
ret = spi_nor_read_data(nor, addr, len, buf);
|
||||
if (ret == 0) {
|
||||
/* We shouldn't see 0-length reads */
|
||||
ret = -EIO;
|
||||
goto read_err;
|
||||
}
|
||||
if (ret < 0)
|
||||
goto read_err;
|
||||
|
||||
*retlen += ret;
|
||||
buf += ret;
|
||||
from += ret;
|
||||
len -= ret;
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
read_err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Write an address range to the nor 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 spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SPI_FLASH_MACRONIX
|
||||
/**
|
||||
* macronix_quad_enable() - set QE bit in Status Register.
|
||||
* @nor: pointer to a 'struct spi_nor'
|
||||
*
|
||||
* Set the Quad Enable (QE) bit in the Status Register.
|
||||
*
|
||||
* bit 6 of the Status Register is the QE bit for Macronix like QSPI memories.
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
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);
|
||||
|
||||
write_sr(nor, val | SR_QUAD_EN_MX);
|
||||
|
||||
ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = read_sr(nor);
|
||||
if (!(ret > 0 && (ret & SR_QUAD_EN_MX))) {
|
||||
dev_err(nor->dev, "Macronix Quad bit not set\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SPI_FLASH_SPANSION) || defined(CONFIG_SPI_FLASH_WINBOND)
|
||||
/*
|
||||
* Write status Register and configuration register with 2 bytes
|
||||
* The first byte will be written to the status register, while the
|
||||
* second byte will be written to the configuration register.
|
||||
* Return negative if error occurred.
|
||||
*/
|
||||
static int write_sr_cr(struct spi_nor *nor, u8 *sr_cr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
write_enable(nor);
|
||||
|
||||
ret = spi_nor_write_reg(nor, SPINOR_OP_WRSR, sr_cr, 2);
|
||||
if (ret < 0) {
|
||||
dev_dbg(nor->dev,
|
||||
"error while writing configuration register\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret) {
|
||||
dev_dbg(nor->dev,
|
||||
"timeout while writing configuration register\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* spansion_read_cr_quad_enable() - set QE bit in Configuration Register.
|
||||
* @nor: pointer to a 'struct spi_nor'
|
||||
*
|
||||
* Set the Quad Enable (QE) bit in the Configuration Register.
|
||||
* This function should be used with QSPI memories supporting the Read
|
||||
* Configuration Register (35h) instruction.
|
||||
*
|
||||
* bit 1 of the Configuration Register is the QE bit for Spansion like QSPI
|
||||
* memories.
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
static int spansion_read_cr_quad_enable(struct spi_nor *nor)
|
||||
{
|
||||
u8 sr_cr[2];
|
||||
int ret;
|
||||
|
||||
/* Check current Quad Enable bit value. */
|
||||
ret = read_cr(nor);
|
||||
if (ret < 0) {
|
||||
dev_dbg(dev, "error while reading configuration register\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ret & CR_QUAD_EN_SPAN)
|
||||
return 0;
|
||||
|
||||
sr_cr[1] = ret | CR_QUAD_EN_SPAN;
|
||||
|
||||
/* Keep the current value of the Status Register. */
|
||||
ret = read_sr(nor);
|
||||
if (ret < 0) {
|
||||
dev_dbg(dev, "error while reading status register\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
sr_cr[0] = ret;
|
||||
|
||||
ret = write_sr_cr(nor, sr_cr);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Read back and check it. */
|
||||
ret = read_cr(nor);
|
||||
if (!(ret > 0 && (ret & CR_QUAD_EN_SPAN))) {
|
||||
dev_dbg(nor->dev, "Spansion Quad bit not set\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_SPI_FLASH_SPANSION */
|
||||
|
||||
struct spi_nor_read_command {
|
||||
u8 num_mode_clocks;
|
||||
u8 num_wait_states;
|
||||
u8 opcode;
|
||||
enum spi_nor_protocol proto;
|
||||
};
|
||||
|
||||
enum spi_nor_read_command_index {
|
||||
SNOR_CMD_READ,
|
||||
SNOR_CMD_READ_FAST,
|
||||
|
||||
/* Quad SPI */
|
||||
SNOR_CMD_READ_1_1_4,
|
||||
|
||||
SNOR_CMD_READ_MAX
|
||||
};
|
||||
|
||||
struct spi_nor_flash_parameter {
|
||||
struct spi_nor_hwcaps hwcaps;
|
||||
struct spi_nor_read_command reads[SNOR_CMD_READ_MAX];
|
||||
};
|
||||
|
||||
static void
|
||||
spi_nor_set_read_settings(struct spi_nor_read_command *read,
|
||||
u8 num_mode_clocks,
|
||||
u8 num_wait_states,
|
||||
u8 opcode,
|
||||
enum spi_nor_protocol proto)
|
||||
{
|
||||
read->num_mode_clocks = num_mode_clocks;
|
||||
read->num_wait_states = num_wait_states;
|
||||
read->opcode = opcode;
|
||||
read->proto = proto;
|
||||
}
|
||||
|
||||
static int spi_nor_init_params(struct spi_nor *nor,
|
||||
const struct flash_info *info,
|
||||
struct spi_nor_flash_parameter *params)
|
||||
{
|
||||
/* (Fast) Read settings. */
|
||||
params->hwcaps.mask = SNOR_HWCAPS_READ;
|
||||
spi_nor_set_read_settings(¶ms->reads[SNOR_CMD_READ],
|
||||
0, 0, SPINOR_OP_READ,
|
||||
SNOR_PROTO_1_1_1);
|
||||
|
||||
if (!(info->flags & SPI_NOR_NO_FR)) {
|
||||
params->hwcaps.mask |= SNOR_HWCAPS_READ_FAST;
|
||||
spi_nor_set_read_settings(¶ms->reads[SNOR_CMD_READ_FAST],
|
||||
0, 8, SPINOR_OP_READ_FAST,
|
||||
SNOR_PROTO_1_1_1);
|
||||
}
|
||||
|
||||
if (info->flags & SPI_NOR_QUAD_READ) {
|
||||
params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
|
||||
spi_nor_set_read_settings(¶ms->reads[SNOR_CMD_READ_1_1_4],
|
||||
0, 8, SPINOR_OP_READ_1_1_4,
|
||||
SNOR_PROTO_1_1_4);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_nor_select_read(struct spi_nor *nor,
|
||||
const struct spi_nor_flash_parameter *params,
|
||||
u32 shared_hwcaps)
|
||||
{
|
||||
int best_match = shared_hwcaps & SNOR_HWCAPS_READ_MASK;
|
||||
int cmd;
|
||||
const struct spi_nor_read_command *read;
|
||||
|
||||
if (best_match < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (best_match & SNOR_HWCAPS_READ_1_1_4)
|
||||
cmd = SNOR_CMD_READ_1_1_4;
|
||||
else if (best_match & SNOR_HWCAPS_READ_FAST)
|
||||
cmd = SNOR_CMD_READ_FAST;
|
||||
else
|
||||
cmd = SNOR_CMD_READ;
|
||||
|
||||
read = ¶ms->reads[cmd];
|
||||
nor->read_opcode = read->opcode;
|
||||
nor->read_proto = read->proto;
|
||||
|
||||
/*
|
||||
* In the spi-nor framework, we don't need to make the difference
|
||||
* between mode clock cycles and wait state clock cycles.
|
||||
* Indeed, the value of the mode clock cycles is used by a QSPI
|
||||
* flash memory to know whether it should enter or leave its 0-4-4
|
||||
* (Continuous Read / XIP) mode.
|
||||
* eXecution In Place is out of the scope of the mtd sub-system.
|
||||
* Hence we choose to merge both mode and wait state clock cycles
|
||||
* into the so called dummy clock cycles.
|
||||
*/
|
||||
nor->read_dummy = read->num_mode_clocks + read->num_wait_states;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_nor_setup(struct spi_nor *nor, const struct flash_info *info,
|
||||
const struct spi_nor_flash_parameter *params,
|
||||
const struct spi_nor_hwcaps *hwcaps)
|
||||
{
|
||||
u32 shared_mask;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* Keep only the hardware capabilities supported by both the SPI
|
||||
* controller and the SPI flash memory.
|
||||
*/
|
||||
shared_mask = hwcaps->mask & params->hwcaps.mask;
|
||||
|
||||
/* Select the (Fast) Read command. */
|
||||
err = spi_nor_select_read(nor, params, shared_mask);
|
||||
if (err) {
|
||||
dev_dbg(nor->dev,
|
||||
"can't select read settings supported by both the SPI controller and memory.\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Enable Quad I/O if needed. */
|
||||
if (spi_nor_get_protocol_width(nor->read_proto) == 4) {
|
||||
switch (JEDEC_MFR(info)) {
|
||||
#ifdef CONFIG_SPI_FLASH_MACRONIX
|
||||
case SNOR_MFR_MACRONIX:
|
||||
err = macronix_quad_enable(nor);
|
||||
break;
|
||||
#endif
|
||||
case SNOR_MFR_ST:
|
||||
case SNOR_MFR_MICRON:
|
||||
break;
|
||||
|
||||
default:
|
||||
#if defined(CONFIG_SPI_FLASH_SPANSION) || defined(CONFIG_SPI_FLASH_WINBOND)
|
||||
/* Kept only for backward compatibility purpose. */
|
||||
err = spansion_read_cr_quad_enable(nor);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (err) {
|
||||
dev_dbg(nor->dev, "quad mode not supported\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spi_nor_init(struct spi_nor *nor)
|
||||
{
|
||||
if (nor->addr_width == 4 &&
|
||||
(JEDEC_MFR(nor->info) != SNOR_MFR_SPANSION) &&
|
||||
!(nor->info->flags & SPI_NOR_4B_OPCODES)) {
|
||||
/*
|
||||
* If the RESET# pin isn't hooked up properly, or the system
|
||||
* otherwise doesn't perform a reset command in the boot
|
||||
* sequence, it's impossible to 100% protect against unexpected
|
||||
* reboots (e.g., crashes). Warn the user (or hopefully, system
|
||||
* designer) that this is bad.
|
||||
*/
|
||||
if (nor->flags & SNOR_F_BROKEN_RESET)
|
||||
printf("enabling reset hack; may not recover from unexpected reboots\n");
|
||||
set_4byte(nor, nor->info, 1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int spi_nor_scan(struct spi_nor *nor)
|
||||
{
|
||||
struct spi_nor_flash_parameter params;
|
||||
const struct flash_info *info = NULL;
|
||||
struct mtd_info *mtd = &nor->mtd;
|
||||
struct spi_nor_hwcaps hwcaps = {
|
||||
.mask = SNOR_HWCAPS_READ |
|
||||
SNOR_HWCAPS_READ_FAST
|
||||
};
|
||||
struct spi_slave *spi = nor->spi;
|
||||
int ret;
|
||||
|
||||
/* Reset SPI protocol for all commands. */
|
||||
nor->reg_proto = SNOR_PROTO_1_1_1;
|
||||
nor->read_proto = SNOR_PROTO_1_1_1;
|
||||
nor->write_proto = SNOR_PROTO_1_1_1;
|
||||
|
||||
if (spi->mode & SPI_RX_QUAD)
|
||||
hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
|
||||
|
||||
info = spi_nor_read_id(nor);
|
||||
if (IS_ERR_OR_NULL(info))
|
||||
return -ENOENT;
|
||||
/* Parse the Serial Flash Discoverable Parameters table. */
|
||||
ret = spi_nor_init_params(nor, info, ¶ms);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
mtd->name = "spi-flash";
|
||||
mtd->priv = nor;
|
||||
mtd->type = MTD_NORFLASH;
|
||||
mtd->writesize = 1;
|
||||
mtd->flags = MTD_CAP_NORFLASH;
|
||||
mtd->size = info->sector_size * info->n_sectors;
|
||||
mtd->_erase = spi_nor_erase;
|
||||
mtd->_read = spi_nor_read;
|
||||
mtd->_write = spi_nor_write;
|
||||
|
||||
nor->size = mtd->size;
|
||||
|
||||
if (info->flags & USE_FSR)
|
||||
nor->flags |= SNOR_F_USE_FSR;
|
||||
if (info->flags & USE_CLSR)
|
||||
nor->flags |= SNOR_F_USE_CLSR;
|
||||
|
||||
if (info->flags & SPI_NOR_NO_FR)
|
||||
params.hwcaps.mask &= ~SNOR_HWCAPS_READ_FAST;
|
||||
|
||||
/*
|
||||
* Configure the SPI memory:
|
||||
* - select op codes for (Fast) Read, Page Program and Sector Erase.
|
||||
* - set the number of dummy cycles (mode cycles + wait states).
|
||||
* - set the SPI protocols for register and memory accesses.
|
||||
* - set the Quad Enable bit if needed (required by SPI x-y-4 protos).
|
||||
*/
|
||||
ret = spi_nor_setup(nor, info, ¶ms, &hwcaps);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (nor->addr_width) {
|
||||
/* already configured from SFDP */
|
||||
} else if (info->addr_width) {
|
||||
nor->addr_width = info->addr_width;
|
||||
} else if (mtd->size > 0x1000000) {
|
||||
/* enable 4-byte addressing if the device exceeds 16MiB */
|
||||
nor->addr_width = 4;
|
||||
if (JEDEC_MFR(info) == SNOR_MFR_SPANSION ||
|
||||
info->flags & SPI_NOR_4B_OPCODES)
|
||||
spi_nor_set_4byte_opcodes(nor, info);
|
||||
} else {
|
||||
nor->addr_width = 3;
|
||||
}
|
||||
|
||||
if (nor->addr_width > SPI_NOR_MAX_ADDR_WIDTH) {
|
||||
dev_dbg(dev, "address width is too large: %u\n",
|
||||
nor->addr_width);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Send all the required SPI flash commands to initialize device */
|
||||
nor->info = info;
|
||||
ret = spi_nor_init(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* U-Boot specific functions, need to extend MTD to support these */
|
||||
int spi_flash_cmd_get_sw_write_prot(struct spi_nor *nor)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
Reference in New Issue
Block a user