bcm53xx: backport spi-nor changes and update bcm53xxspiflash
Signed-off-by: Rafał Miłecki <zajec5@gmail.com> SVN-Revision: 43738
This commit is contained in:
parent
d27c682def
commit
e21e9c5548
12 changed files with 1651 additions and 1422 deletions
227
target/linux/bcm53xx/files/drivers/mtd/spi-nor/bcm53xxspiflash.c
Normal file
227
target/linux/bcm53xx/files/drivers/mtd/spi-nor/bcm53xxspiflash.c
Normal file
|
@ -0,0 +1,227 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/cfi.h>
|
||||
|
||||
static const char * const probes[] = { "bcm47xxpart", NULL };
|
||||
|
||||
struct bcm53xxsf {
|
||||
struct spi_device *spi;
|
||||
struct mtd_info mtd;
|
||||
struct spi_nor nor;
|
||||
};
|
||||
|
||||
/**************************************************
|
||||
* spi-nor API
|
||||
**************************************************/
|
||||
|
||||
static int bcm53xxspiflash_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
|
||||
int len)
|
||||
{
|
||||
struct bcm53xxsf *b53sf = nor->priv;
|
||||
|
||||
return spi_write_then_read(b53sf->spi, &opcode, 1, buf, len);
|
||||
}
|
||||
|
||||
static int bcm53xxspiflash_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
|
||||
int len, int write_enable)
|
||||
{
|
||||
struct bcm53xxsf *b53sf = nor->priv;
|
||||
u8 *cmd = kzalloc(len + 1, GFP_KERNEL);
|
||||
int err;
|
||||
|
||||
if (!cmd)
|
||||
return -ENOMEM;
|
||||
|
||||
cmd[0] = opcode;
|
||||
memcpy(&cmd[1], buf, len);
|
||||
err = spi_write(b53sf->spi, cmd, len + 1);
|
||||
|
||||
kfree(cmd);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int bcm53xxspiflash_read(struct spi_nor *nor, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct bcm53xxsf *b53sf = nor->priv;
|
||||
struct spi_message m;
|
||||
struct spi_transfer t[2] = { { 0 }, { 0 } };
|
||||
unsigned char cmd[5];
|
||||
int cmd_len = 0;
|
||||
int err;
|
||||
|
||||
spi_message_init(&m);
|
||||
|
||||
cmd[cmd_len++] = SPINOR_OP_READ;
|
||||
if (b53sf->mtd.size > 0x1000000)
|
||||
cmd[cmd_len++] = (from & 0xFF000000) >> 24;
|
||||
cmd[cmd_len++] = (from & 0x00FF0000) >> 16;
|
||||
cmd[cmd_len++] = (from & 0x0000FF00) >> 8;
|
||||
cmd[cmd_len++] = (from & 0x000000FF) >> 0;
|
||||
|
||||
t[0].tx_buf = cmd;
|
||||
t[0].len = cmd_len;
|
||||
spi_message_add_tail(&t[0], &m);
|
||||
|
||||
t[1].rx_buf = buf;
|
||||
t[1].len = len;
|
||||
spi_message_add_tail(&t[1], &m);
|
||||
|
||||
err = spi_sync(b53sf->spi, &m);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (retlen && m.actual_length > cmd_len)
|
||||
*retlen = m.actual_length - cmd_len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bcm53xxspiflash_write(struct spi_nor *nor, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct bcm53xxsf *b53sf = nor->priv;
|
||||
struct spi_message m;
|
||||
struct spi_transfer t = { 0 };
|
||||
u8 *cmd = kzalloc(len + 5, GFP_KERNEL);
|
||||
int cmd_len = 0;
|
||||
int err;
|
||||
|
||||
if (!cmd)
|
||||
return;
|
||||
|
||||
spi_message_init(&m);
|
||||
|
||||
cmd[cmd_len++] = nor->program_opcode;
|
||||
if (b53sf->mtd.size > 0x1000000)
|
||||
cmd[cmd_len++] = (to & 0xFF000000) >> 24;
|
||||
cmd[cmd_len++] = (to & 0x00FF0000) >> 16;
|
||||
cmd[cmd_len++] = (to & 0x0000FF00) >> 8;
|
||||
cmd[cmd_len++] = (to & 0x000000FF) >> 0;
|
||||
memcpy(&cmd[cmd_len], buf, len);
|
||||
|
||||
t.tx_buf = cmd;
|
||||
t.len = cmd_len + len;
|
||||
spi_message_add_tail(&t, &m);
|
||||
|
||||
err = spi_sync(b53sf->spi, &m);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
if (retlen && m.actual_length > cmd_len)
|
||||
*retlen += m.actual_length - cmd_len;
|
||||
|
||||
out:
|
||||
kfree(cmd);
|
||||
}
|
||||
|
||||
static int bcm53xxspiflash_erase(struct spi_nor *nor, loff_t offs)
|
||||
{
|
||||
struct bcm53xxsf *b53sf = nor->priv;
|
||||
unsigned char cmd[5];
|
||||
int i;
|
||||
|
||||
i = 0;
|
||||
cmd[i++] = nor->erase_opcode;
|
||||
if (b53sf->mtd.size > 0x1000000)
|
||||
cmd[i++] = (offs & 0xFF000000) >> 24;
|
||||
cmd[i++] = ((offs & 0x00FF0000) >> 16);
|
||||
cmd[i++] = ((offs & 0x0000FF00) >> 8);
|
||||
cmd[i++] = ((offs & 0x000000FF) >> 0);
|
||||
|
||||
return spi_write(b53sf->spi, cmd, i);
|
||||
}
|
||||
|
||||
static const char *bcm53xxspiflash_chip_name(struct spi_nor *nor)
|
||||
{
|
||||
struct bcm53xxsf *b53sf = nor->priv;
|
||||
struct device *dev = &b53sf->spi->dev;
|
||||
unsigned char cmd[4];
|
||||
unsigned char resp[2];
|
||||
int err;
|
||||
|
||||
/* SST and Winbond/NexFlash specific command */
|
||||
cmd[0] = 0x90; /* Read Manufacturer / Device ID */
|
||||
cmd[1] = 0;
|
||||
cmd[2] = 0;
|
||||
cmd[3] = 0;
|
||||
err = spi_write_then_read(b53sf->spi, cmd, 4, resp, 2);
|
||||
if (err < 0) {
|
||||
dev_err(dev, "error reading SPI flash id\n");
|
||||
return ERR_PTR(-EBUSY);
|
||||
}
|
||||
switch (resp[0]) {
|
||||
case 0xef: /* Winbond/NexFlash */
|
||||
switch (resp[1]) {
|
||||
case 0x17:
|
||||
return "w25q128";
|
||||
}
|
||||
dev_err(dev, "Unknown Winbond/NexFlash flash: %02X %02X\n",
|
||||
resp[0], resp[1]);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* TODO: Try more ID commands */
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**************************************************
|
||||
* SPI driver
|
||||
**************************************************/
|
||||
|
||||
static int bcm53xxspiflash_probe(struct spi_device *spi)
|
||||
{
|
||||
struct bcm53xxsf *b53sf;
|
||||
struct spi_nor *nor;
|
||||
int err;
|
||||
|
||||
b53sf = devm_kzalloc(&spi->dev, sizeof(*b53sf), GFP_KERNEL);
|
||||
if (!b53sf)
|
||||
return -ENOMEM;
|
||||
spi_set_drvdata(spi, b53sf);
|
||||
|
||||
nor = &b53sf->nor;
|
||||
b53sf->spi = spi;
|
||||
b53sf->mtd.priv = &b53sf->nor;
|
||||
|
||||
nor->mtd = &b53sf->mtd;
|
||||
nor->dev = &spi->dev;
|
||||
nor->read_reg = bcm53xxspiflash_read_reg;
|
||||
nor->write_reg = bcm53xxspiflash_write_reg;
|
||||
nor->read = bcm53xxspiflash_read;
|
||||
nor->write = bcm53xxspiflash_write;
|
||||
nor->erase = bcm53xxspiflash_erase;
|
||||
nor->priv = b53sf;
|
||||
|
||||
err = spi_nor_scan(&b53sf->nor, bcm53xxspiflash_chip_name(nor),
|
||||
SPI_NOR_NORMAL);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = mtd_device_parse_register(&b53sf->mtd, probes, NULL, NULL, 0);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bcm53xxspiflash_remove(struct spi_device *spi)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spi_driver bcm53xxspiflash_driver = {
|
||||
.driver = {
|
||||
.name = "bcm53xxspiflash",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = bcm53xxspiflash_probe,
|
||||
.remove = bcm53xxspiflash_remove,
|
||||
};
|
||||
|
||||
module_spi_driver(bcm53xxspiflash_driver);
|
|
@ -1,6 +1,24 @@
|
|||
--- a/drivers/mtd/spi-nor/spi-nor.c
|
||||
+++ b/drivers/mtd/spi-nor/spi-nor.c
|
||||
@@ -611,6 +611,7 @@ const struct spi_device_id spi_nor_ids[]
|
||||
@@ -28,6 +28,8 @@
|
||||
|
||||
#define JEDEC_MFR(_jedec_id) ((_jedec_id) >> 16)
|
||||
|
||||
+static const struct spi_device_id *spi_nor_match_id(const char *name);
|
||||
+
|
||||
/*
|
||||
* Read the status register, returning its value in the location
|
||||
* Return the status register value.
|
||||
@@ -473,7 +475,7 @@ struct flash_info {
|
||||
* more nor chips. This current list focusses on newer chips, which
|
||||
* have been converging on command sets which including JEDEC ID.
|
||||
*/
|
||||
-const struct spi_device_id spi_nor_ids[] = {
|
||||
+static const struct spi_device_id spi_nor_ids[] = {
|
||||
/* Atmel -- some are (confusingly) marketed as "DataFlash" */
|
||||
{ "at25fs010", INFO(0x1f6601, 0, 32 * 1024, 4, SECT_4K) },
|
||||
{ "at25fs040", INFO(0x1f6604, 0, 64 * 1024, 8, SECT_4K) },
|
||||
@@ -611,6 +613,7 @@ const struct spi_device_id spi_nor_ids[]
|
||||
{ "m25px32-s0", INFO(0x207316, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "m25px32-s1", INFO(0x206316, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "m25px64", INFO(0x207117, 0, 64 * 1024, 128, 0) },
|
||||
|
@ -8,7 +26,7 @@
|
|||
|
||||
/* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */
|
||||
{ "w25x10", INFO(0xef3011, 0, 64 * 1024, 2, SECT_4K) },
|
||||
@@ -623,7 +624,6 @@ const struct spi_device_id spi_nor_ids[]
|
||||
@@ -623,7 +626,6 @@ const struct spi_device_id spi_nor_ids[]
|
||||
{ "w25q32dw", INFO(0xef6016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ "w25q64", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
|
@ -16,7 +34,15 @@
|
|||
{ "w25q80", INFO(0xef5014, 0, 64 * 1024, 16, SECT_4K) },
|
||||
{ "w25q80bl", INFO(0xef4014, 0, 64 * 1024, 16, SECT_4K) },
|
||||
{ "w25q128", INFO(0xef4018, 0, 64 * 1024, 256, SECT_4K) },
|
||||
@@ -671,11 +671,6 @@ static const struct spi_device_id *spi_n
|
||||
@@ -637,7 +639,6 @@ const struct spi_device_id spi_nor_ids[]
|
||||
{ "cat25128", CAT25_INFO(2048, 8, 64, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
{ },
|
||||
};
|
||||
-EXPORT_SYMBOL_GPL(spi_nor_ids);
|
||||
|
||||
static const struct spi_device_id *spi_nor_read_id(struct spi_nor *nor)
|
||||
{
|
||||
@@ -671,11 +672,6 @@ static const struct spi_device_id *spi_n
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
|
@ -28,15 +54,21 @@
|
|||
static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
@@ -920,7 +915,6 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
enum read_mode mode)
|
||||
@@ -916,11 +912,10 @@ static int spi_nor_check(struct spi_nor
|
||||
return 0;
|
||||
}
|
||||
|
||||
-int spi_nor_scan(struct spi_nor *nor, const struct spi_device_id *id,
|
||||
- enum read_mode mode)
|
||||
+int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode)
|
||||
{
|
||||
+ const struct spi_device_id *id = NULL;
|
||||
struct flash_info *info;
|
||||
- struct flash_platform_data *data;
|
||||
struct device *dev = nor->dev;
|
||||
struct mtd_info *mtd = nor->mtd;
|
||||
struct device_node *np = dev->of_node;
|
||||
@@ -931,34 +925,12 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
@@ -931,34 +926,16 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -61,7 +93,10 @@
|
|||
- else
|
||||
- dev_warn(dev, "unrecognized id %s\n", data->type);
|
||||
- }
|
||||
-
|
||||
+ id = spi_nor_match_id(name);
|
||||
+ if (!id)
|
||||
+ return -ENOENT;
|
||||
|
||||
info = (void *)id->driver_data;
|
||||
|
||||
if (info->jedec_id) {
|
||||
|
@ -72,7 +107,7 @@
|
|||
if (IS_ERR(jid)) {
|
||||
return PTR_ERR(jid);
|
||||
} else if (jid != id) {
|
||||
@@ -990,11 +962,8 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
@@ -990,11 +967,8 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
write_sr(nor, 0);
|
||||
}
|
||||
|
||||
|
@ -85,7 +120,7 @@
|
|||
mtd->type = MTD_NORFLASH;
|
||||
mtd->writesize = 1;
|
||||
mtd->flags = MTD_CAP_NORFLASH;
|
||||
@@ -1018,6 +987,7 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
@@ -1018,6 +992,7 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
nor->wait_till_ready == spi_nor_wait_till_ready)
|
||||
nor->wait_till_ready = spi_nor_wait_till_fsr_ready;
|
||||
|
||||
|
@ -93,7 +128,7 @@
|
|||
/* prefer "small sector" erase if possible */
|
||||
if (info->flags & SECT_4K) {
|
||||
nor->erase_opcode = SPINOR_OP_BE_4K;
|
||||
@@ -1025,7 +995,9 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
@@ -1025,7 +1000,9 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
} else if (info->flags & SECT_4K_PMC) {
|
||||
nor->erase_opcode = SPINOR_OP_BE_4K_PMC;
|
||||
mtd->erasesize = 4096;
|
||||
|
@ -104,6 +139,23 @@
|
|||
nor->erase_opcode = SPINOR_OP_SE;
|
||||
mtd->erasesize = info->sector_size;
|
||||
}
|
||||
@@ -1141,7 +1118,7 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_nor_scan);
|
||||
|
||||
-const struct spi_device_id *spi_nor_match_id(char *name)
|
||||
+static const struct spi_device_id *spi_nor_match_id(const char *name)
|
||||
{
|
||||
const struct spi_device_id *id = spi_nor_ids;
|
||||
|
||||
@@ -1152,7 +1129,6 @@ const struct spi_device_id *spi_nor_matc
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
-EXPORT_SYMBOL_GPL(spi_nor_match_id);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Huang Shijie <shijie8@gmail.com>");
|
||||
--- a/drivers/mtd/spi-nor/Kconfig
|
||||
+++ b/drivers/mtd/spi-nor/Kconfig
|
||||
@@ -7,6 +7,20 @@ menuconfig MTD_SPI_NOR
|
||||
|
@ -127,3 +179,41 @@
|
|||
config SPI_FSL_QUADSPI
|
||||
tristate "Freescale Quad SPI controller"
|
||||
depends on ARCH_MXC
|
||||
--- a/include/linux/mtd/spi-nor.h
|
||||
+++ b/include/linux/mtd/spi-nor.h
|
||||
@@ -187,32 +187,17 @@ struct spi_nor {
|
||||
/**
|
||||
* spi_nor_scan() - scan the SPI NOR
|
||||
* @nor: the spi_nor structure
|
||||
- * @id: the spi_device_id provided by the driver
|
||||
+ * @name: the chip type name
|
||||
* @mode: the read mode supported by the driver
|
||||
*
|
||||
* The drivers can use this fuction to scan the SPI NOR.
|
||||
* In the scanning, it will try to get all the necessary information to
|
||||
* fill the mtd_info{} and the spi_nor{}.
|
||||
*
|
||||
- * The board may assigns a spi_device_id with @id which be used to compared with
|
||||
- * the spi_device_id detected by the scanning.
|
||||
+ * The chip type name can be provided through the @name parameter.
|
||||
*
|
||||
* Return: 0 for success, others for failure.
|
||||
*/
|
||||
-int spi_nor_scan(struct spi_nor *nor, const struct spi_device_id *id,
|
||||
- enum read_mode mode);
|
||||
-extern const struct spi_device_id spi_nor_ids[];
|
||||
-
|
||||
-/**
|
||||
- * spi_nor_match_id() - find the spi_device_id by the name
|
||||
- * @name: the name of the spi_device_id
|
||||
- *
|
||||
- * The drivers use this function to find the spi_device_id
|
||||
- * specified by the @name.
|
||||
- *
|
||||
- * Return: returns the right spi_device_id pointer on success,
|
||||
- * and returns NULL on failure.
|
||||
- */
|
||||
-const struct spi_device_id *spi_nor_match_id(char *name);
|
||||
+int spi_nor_scan(struct spi_nor *nor, const char *name, enum read_mode mode);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,662 @@
|
|||
--- a/drivers/mtd/spi-nor/spi-nor.c
|
||||
+++ b/drivers/mtd/spi-nor/spi-nor.c
|
||||
@@ -26,7 +26,38 @@
|
||||
/* Define max times to check status register before we give up. */
|
||||
#define MAX_READY_WAIT_JIFFIES (40 * HZ) /* M25P16 specs 40s max chip erase */
|
||||
|
||||
-#define JEDEC_MFR(_jedec_id) ((_jedec_id) >> 16)
|
||||
+#define SPI_NOR_MAX_ID_LEN 6
|
||||
+
|
||||
+struct flash_info {
|
||||
+ /*
|
||||
+ * 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 sector_size;
|
||||
+ u16 n_sectors;
|
||||
+
|
||||
+ u16 page_size;
|
||||
+ u16 addr_width;
|
||||
+
|
||||
+ u16 flags;
|
||||
+#define SECT_4K 0x01 /* SPINOR_OP_BE_4K works uniformly */
|
||||
+#define SPI_NOR_NO_ERASE 0x02 /* No erase command needed */
|
||||
+#define SST_WRITE 0x04 /* use SST byte programming */
|
||||
+#define SPI_NOR_NO_FR 0x08 /* Can't do fastread */
|
||||
+#define SECT_4K_PMC 0x10 /* SPINOR_OP_BE_4K_PMC works uniformly */
|
||||
+#define SPI_NOR_DUAL_READ 0x20 /* Flash supports Dual Read */
|
||||
+#define SPI_NOR_QUAD_READ 0x40 /* Flash supports Quad Read */
|
||||
+#define USE_FSR 0x80 /* use flag status register */
|
||||
+};
|
||||
+
|
||||
+#define JEDEC_MFR(info) ((info)->id[0])
|
||||
|
||||
static const struct spi_device_id *spi_nor_match_id(const char *name);
|
||||
|
||||
@@ -98,7 +129,7 @@ static inline int spi_nor_read_dummy_cyc
|
||||
case SPI_NOR_FAST:
|
||||
case SPI_NOR_DUAL:
|
||||
case SPI_NOR_QUAD:
|
||||
- return 1;
|
||||
+ return 8;
|
||||
case SPI_NOR_NORMAL:
|
||||
return 0;
|
||||
}
|
||||
@@ -138,13 +169,14 @@ static inline struct spi_nor *mtd_to_spi
|
||||
}
|
||||
|
||||
/* Enable/disable 4-byte addressing mode. */
|
||||
-static inline int set_4byte(struct spi_nor *nor, u32 jedec_id, int enable)
|
||||
+static inline int set_4byte(struct spi_nor *nor, struct flash_info *info,
|
||||
+ int enable)
|
||||
{
|
||||
int status;
|
||||
bool need_wren = false;
|
||||
u8 cmd;
|
||||
|
||||
- switch (JEDEC_MFR(jedec_id)) {
|
||||
+ switch (JEDEC_MFR(info)) {
|
||||
case CFI_MFR_ST: /* Micron, actually */
|
||||
/* Some Micron need WREN command; all will accept it */
|
||||
need_wren = true;
|
||||
@@ -165,81 +197,74 @@ static inline int set_4byte(struct spi_n
|
||||
return nor->write_reg(nor, SPINOR_OP_BRWR, nor->cmd_buf, 1, 0);
|
||||
}
|
||||
}
|
||||
-
|
||||
-static int spi_nor_wait_till_ready(struct spi_nor *nor)
|
||||
+static inline int spi_nor_sr_ready(struct spi_nor *nor)
|
||||
{
|
||||
- unsigned long deadline;
|
||||
- int sr;
|
||||
-
|
||||
- deadline = jiffies + MAX_READY_WAIT_JIFFIES;
|
||||
-
|
||||
- do {
|
||||
- cond_resched();
|
||||
+ int sr = read_sr(nor);
|
||||
+ if (sr < 0)
|
||||
+ return sr;
|
||||
+ else
|
||||
+ return !(sr & SR_WIP);
|
||||
+}
|
||||
|
||||
- sr = read_sr(nor);
|
||||
- if (sr < 0)
|
||||
- break;
|
||||
- else if (!(sr & SR_WIP))
|
||||
- return 0;
|
||||
- } while (!time_after_eq(jiffies, deadline));
|
||||
+static inline int spi_nor_fsr_ready(struct spi_nor *nor)
|
||||
+{
|
||||
+ int fsr = read_fsr(nor);
|
||||
+ if (fsr < 0)
|
||||
+ return fsr;
|
||||
+ else
|
||||
+ return fsr & FSR_READY;
|
||||
+}
|
||||
|
||||
- return -ETIMEDOUT;
|
||||
+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;
|
||||
}
|
||||
|
||||
-static int spi_nor_wait_till_fsr_ready(struct spi_nor *nor)
|
||||
+/*
|
||||
+ * Service routine to read status register until ready, or timeout occurs.
|
||||
+ * Returns non-zero if error.
|
||||
+ */
|
||||
+static int spi_nor_wait_till_ready(struct spi_nor *nor)
|
||||
{
|
||||
unsigned long deadline;
|
||||
- int sr;
|
||||
- int fsr;
|
||||
+ int timeout = 0, ret;
|
||||
|
||||
deadline = jiffies + MAX_READY_WAIT_JIFFIES;
|
||||
|
||||
- do {
|
||||
+ while (!timeout) {
|
||||
+ if (time_after_eq(jiffies, deadline))
|
||||
+ timeout = 1;
|
||||
+
|
||||
+ ret = spi_nor_ready(nor);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+ if (ret)
|
||||
+ return 0;
|
||||
+
|
||||
cond_resched();
|
||||
+ }
|
||||
|
||||
- sr = read_sr(nor);
|
||||
- if (sr < 0) {
|
||||
- break;
|
||||
- } else if (!(sr & SR_WIP)) {
|
||||
- fsr = read_fsr(nor);
|
||||
- if (fsr < 0)
|
||||
- break;
|
||||
- if (fsr & FSR_READY)
|
||||
- return 0;
|
||||
- }
|
||||
- } while (!time_after_eq(jiffies, deadline));
|
||||
+ dev_err(nor->dev, "flash operation timed out\n");
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/*
|
||||
- * Service routine to read status register until ready, or timeout occurs.
|
||||
- * Returns non-zero if error.
|
||||
- */
|
||||
-static int wait_till_ready(struct spi_nor *nor)
|
||||
-{
|
||||
- return nor->wait_till_ready(nor);
|
||||
-}
|
||||
-
|
||||
-/*
|
||||
* Erase the whole flash memory
|
||||
*
|
||||
* Returns 0 if successful, non-zero otherwise.
|
||||
*/
|
||||
static int erase_chip(struct spi_nor *nor)
|
||||
{
|
||||
- int ret;
|
||||
-
|
||||
dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd->size >> 10));
|
||||
|
||||
- /* Wait until finished previous write command. */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
-
|
||||
- /* Send write enable, then erase commands. */
|
||||
- write_enable(nor);
|
||||
-
|
||||
return nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0, 0);
|
||||
}
|
||||
|
||||
@@ -294,11 +319,17 @@ static int spi_nor_erase(struct mtd_info
|
||||
|
||||
/* whole-chip erase? */
|
||||
if (len == mtd->size) {
|
||||
+ write_enable(nor);
|
||||
+
|
||||
if (erase_chip(nor)) {
|
||||
ret = -EIO;
|
||||
goto erase_err;
|
||||
}
|
||||
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
+ if (ret)
|
||||
+ goto erase_err;
|
||||
+
|
||||
/* REVISIT in some cases we could speed up erasing large regions
|
||||
* by using SPINOR_OP_SE instead of SPINOR_OP_BE_4K. We may have set up
|
||||
* to use "small sector erase", but that's not always optimal.
|
||||
@@ -307,6 +338,8 @@ static int spi_nor_erase(struct mtd_info
|
||||
/* "sector"-at-a-time erase */
|
||||
} else {
|
||||
while (len) {
|
||||
+ write_enable(nor);
|
||||
+
|
||||
if (nor->erase(nor, addr)) {
|
||||
ret = -EIO;
|
||||
goto erase_err;
|
||||
@@ -314,9 +347,15 @@ static int spi_nor_erase(struct mtd_info
|
||||
|
||||
addr += mtd->erasesize;
|
||||
len -= mtd->erasesize;
|
||||
+
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
+ if (ret)
|
||||
+ goto erase_err;
|
||||
}
|
||||
}
|
||||
|
||||
+ write_disable(nor);
|
||||
+
|
||||
spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE);
|
||||
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
@@ -341,11 +380,6 @@ static int spi_nor_lock(struct mtd_info
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous command */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto err;
|
||||
-
|
||||
status_old = read_sr(nor);
|
||||
|
||||
if (offset < mtd->size - (mtd->size / 2))
|
||||
@@ -388,11 +422,6 @@ static int spi_nor_unlock(struct mtd_inf
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous command */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto err;
|
||||
-
|
||||
status_old = read_sr(nor);
|
||||
|
||||
if (offset+len > mtd->size - (mtd->size / 64))
|
||||
@@ -424,38 +453,34 @@ err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
-struct flash_info {
|
||||
- /* JEDEC id zero means "no ID" (most older chips); otherwise it has
|
||||
- * a high byte of zero plus three data bytes: the manufacturer id,
|
||||
- * then a two byte device id.
|
||||
- */
|
||||
- u32 jedec_id;
|
||||
- u16 ext_id;
|
||||
-
|
||||
- /* The size listed here is what works with SPINOR_OP_SE, which isn't
|
||||
- * necessarily called a "sector" by the vendor.
|
||||
- */
|
||||
- unsigned sector_size;
|
||||
- u16 n_sectors;
|
||||
-
|
||||
- u16 page_size;
|
||||
- u16 addr_width;
|
||||
-
|
||||
- u16 flags;
|
||||
-#define SECT_4K 0x01 /* SPINOR_OP_BE_4K works uniformly */
|
||||
-#define SPI_NOR_NO_ERASE 0x02 /* No erase command needed */
|
||||
-#define SST_WRITE 0x04 /* use SST byte programming */
|
||||
-#define SPI_NOR_NO_FR 0x08 /* Can't do fastread */
|
||||
-#define SECT_4K_PMC 0x10 /* SPINOR_OP_BE_4K_PMC works uniformly */
|
||||
-#define SPI_NOR_DUAL_READ 0x20 /* Flash supports Dual Read */
|
||||
-#define SPI_NOR_QUAD_READ 0x40 /* Flash supports Quad Read */
|
||||
-#define USE_FSR 0x80 /* use flag status register */
|
||||
-};
|
||||
-
|
||||
+/* Used when the "_ext_id" is two bytes at most */
|
||||
#define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \
|
||||
((kernel_ulong_t)&(struct flash_info) { \
|
||||
- .jedec_id = (_jedec_id), \
|
||||
- .ext_id = (_ext_id), \
|
||||
+ .id = { \
|
||||
+ ((_jedec_id) >> 16) & 0xff, \
|
||||
+ ((_jedec_id) >> 8) & 0xff, \
|
||||
+ (_jedec_id) & 0xff, \
|
||||
+ ((_ext_id) >> 8) & 0xff, \
|
||||
+ (_ext_id) & 0xff, \
|
||||
+ }, \
|
||||
+ .id_len = (!(_jedec_id) ? 0 : (3 + ((_ext_id) ? 2 : 0))), \
|
||||
+ .sector_size = (_sector_size), \
|
||||
+ .n_sectors = (_n_sectors), \
|
||||
+ .page_size = 256, \
|
||||
+ .flags = (_flags), \
|
||||
+ })
|
||||
+
|
||||
+#define INFO6(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \
|
||||
+ ((kernel_ulong_t)&(struct flash_info) { \
|
||||
+ .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, \
|
||||
@@ -507,6 +532,9 @@ static const struct spi_device_id spi_no
|
||||
{ "mr25h256", CAT25_INFO( 32 * 1024, 1, 256, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
{ "mr25h10", CAT25_INFO(128 * 1024, 1, 256, 3, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
|
||||
+ /* Fujitsu */
|
||||
+ { "mb85rs1mt", INFO(0x047f27, 0, 128 * 1024, 1, SPI_NOR_NO_ERASE) },
|
||||
+
|
||||
/* GigaDevice */
|
||||
{ "gd25q32", INFO(0xc84016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "gd25q64", INFO(0xc84017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
@@ -532,6 +560,7 @@ static const struct spi_device_id spi_no
|
||||
{ "mx66l1g55g", INFO(0xc2261b, 0, 64 * 1024, 2048, SPI_NOR_QUAD_READ) },
|
||||
|
||||
/* Micron */
|
||||
+ { "n25q032", INFO(0x20ba16, 0, 64 * 1024, 64, 0) },
|
||||
{ "n25q064", INFO(0x20ba17, 0, 64 * 1024, 128, 0) },
|
||||
{ "n25q128a11", INFO(0x20bb18, 0, 64 * 1024, 256, 0) },
|
||||
{ "n25q128a13", INFO(0x20ba18, 0, 64 * 1024, 256, 0) },
|
||||
@@ -556,6 +585,7 @@ static const struct spi_device_id spi_no
|
||||
{ "s70fl01gs", INFO(0x010221, 0x4d00, 256 * 1024, 256, 0) },
|
||||
{ "s25sl12800", INFO(0x012018, 0x0300, 256 * 1024, 64, 0) },
|
||||
{ "s25sl12801", INFO(0x012018, 0x0301, 64 * 1024, 256, 0) },
|
||||
+ { "s25fl128s", INFO6(0x012018, 0x4d0180, 64 * 1024, 256, SPI_NOR_QUAD_READ) },
|
||||
{ "s25fl129p0", INFO(0x012018, 0x4d00, 256 * 1024, 64, 0) },
|
||||
{ "s25fl129p1", INFO(0x012018, 0x4d01, 64 * 1024, 256, 0) },
|
||||
{ "s25sl004a", INFO(0x010212, 0, 64 * 1024, 8, 0) },
|
||||
@@ -577,6 +607,7 @@ static const struct spi_device_id spi_no
|
||||
{ "sst25wf010", INFO(0xbf2502, 0, 64 * 1024, 2, SECT_4K | SST_WRITE) },
|
||||
{ "sst25wf020", INFO(0xbf2503, 0, 64 * 1024, 4, SECT_4K | SST_WRITE) },
|
||||
{ "sst25wf040", INFO(0xbf2504, 0, 64 * 1024, 8, SECT_4K | SST_WRITE) },
|
||||
+ { "sst25wf080", INFO(0xbf2505, 0, 64 * 1024, 16, SECT_4K | SST_WRITE) },
|
||||
|
||||
/* ST Microelectronics -- newer production may have feature updates */
|
||||
{ "m25p05", INFO(0x202010, 0, 32 * 1024, 2, 0) },
|
||||
@@ -588,7 +619,6 @@ static const struct spi_device_id spi_no
|
||||
{ "m25p32", INFO(0x202016, 0, 64 * 1024, 64, 0) },
|
||||
{ "m25p64", INFO(0x202017, 0, 64 * 1024, 128, 0) },
|
||||
{ "m25p128", INFO(0x202018, 0, 256 * 1024, 64, 0) },
|
||||
- { "n25q032", INFO(0x20ba16, 0, 64 * 1024, 64, 0) },
|
||||
|
||||
{ "m25p05-nonjedec", INFO(0, 0, 32 * 1024, 2, 0) },
|
||||
{ "m25p10-nonjedec", INFO(0, 0, 32 * 1024, 4, 0) },
|
||||
@@ -643,32 +673,24 @@ static const struct spi_device_id spi_no
|
||||
static const struct spi_device_id *spi_nor_read_id(struct spi_nor *nor)
|
||||
{
|
||||
int tmp;
|
||||
- u8 id[5];
|
||||
- u32 jedec;
|
||||
- u16 ext_jedec;
|
||||
+ u8 id[SPI_NOR_MAX_ID_LEN];
|
||||
struct flash_info *info;
|
||||
|
||||
- tmp = nor->read_reg(nor, SPINOR_OP_RDID, id, 5);
|
||||
+ tmp = 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);
|
||||
}
|
||||
- jedec = id[0];
|
||||
- jedec = jedec << 8;
|
||||
- jedec |= id[1];
|
||||
- jedec = jedec << 8;
|
||||
- jedec |= id[2];
|
||||
-
|
||||
- ext_jedec = id[3] << 8 | id[4];
|
||||
|
||||
for (tmp = 0; tmp < ARRAY_SIZE(spi_nor_ids) - 1; tmp++) {
|
||||
info = (void *)spi_nor_ids[tmp].driver_data;
|
||||
- if (info->jedec_id == jedec) {
|
||||
- if (info->ext_id == 0 || info->ext_id == ext_jedec)
|
||||
+ if (info->id_len) {
|
||||
+ if (!memcmp(info->id, id, info->id_len))
|
||||
return &spi_nor_ids[tmp];
|
||||
}
|
||||
}
|
||||
- dev_err(nor->dev, "unrecognized JEDEC id %06x\n", jedec);
|
||||
+ dev_err(nor->dev, "unrecognized JEDEC id bytes: %02x, %2x, %2x\n",
|
||||
+ id[0], id[1], id[2]);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
@@ -703,11 +725,6 @@ static int sst_write(struct mtd_info *mt
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous write command. */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto time_out;
|
||||
-
|
||||
write_enable(nor);
|
||||
|
||||
nor->sst_write_second = false;
|
||||
@@ -719,7 +736,7 @@ static int sst_write(struct mtd_info *mt
|
||||
|
||||
/* write one byte. */
|
||||
nor->write(nor, to, 1, retlen, buf);
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
}
|
||||
@@ -731,7 +748,7 @@ static int sst_write(struct mtd_info *mt
|
||||
|
||||
/* write two bytes. */
|
||||
nor->write(nor, to, 2, retlen, buf + actual);
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
to += 2;
|
||||
@@ -740,7 +757,7 @@ static int sst_write(struct mtd_info *mt
|
||||
nor->sst_write_second = false;
|
||||
|
||||
write_disable(nor);
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
|
||||
@@ -751,7 +768,7 @@ static int sst_write(struct mtd_info *mt
|
||||
nor->program_opcode = SPINOR_OP_BP;
|
||||
nor->write(nor, to, 1, retlen, buf + actual);
|
||||
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
write_disable(nor);
|
||||
@@ -779,11 +796,6 @@ static int spi_nor_write(struct mtd_info
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous write command. */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto write_err;
|
||||
-
|
||||
write_enable(nor);
|
||||
|
||||
page_offset = to & (nor->page_size - 1);
|
||||
@@ -802,16 +814,20 @@ static int spi_nor_write(struct mtd_info
|
||||
if (page_size > nor->page_size)
|
||||
page_size = nor->page_size;
|
||||
|
||||
- wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
+ if (ret)
|
||||
+ goto write_err;
|
||||
+
|
||||
write_enable(nor);
|
||||
|
||||
nor->write(nor, to + i, page_size, retlen, buf + i);
|
||||
}
|
||||
}
|
||||
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
write_err:
|
||||
spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE);
|
||||
- return 0;
|
||||
+ return ret;
|
||||
}
|
||||
|
||||
static int macronix_quad_enable(struct spi_nor *nor)
|
||||
@@ -824,7 +840,7 @@ static int macronix_quad_enable(struct s
|
||||
nor->cmd_buf[0] = val | SR_QUAD_EN_MX;
|
||||
nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 1, 0);
|
||||
|
||||
- if (wait_till_ready(nor))
|
||||
+ if (spi_nor_wait_till_ready(nor))
|
||||
return 1;
|
||||
|
||||
ret = read_sr(nor);
|
||||
@@ -874,11 +890,11 @@ static int spansion_quad_enable(struct s
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int set_quad_mode(struct spi_nor *nor, u32 jedec_id)
|
||||
+static int set_quad_mode(struct spi_nor *nor, struct flash_info *info)
|
||||
{
|
||||
int status;
|
||||
|
||||
- switch (JEDEC_MFR(jedec_id)) {
|
||||
+ switch (JEDEC_MFR(info)) {
|
||||
case CFI_MFR_MACRONIX:
|
||||
status = macronix_quad_enable(nor);
|
||||
if (status) {
|
||||
@@ -904,11 +920,6 @@ static int spi_nor_check(struct spi_nor
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
- if (!nor->read_id)
|
||||
- nor->read_id = spi_nor_read_id;
|
||||
- if (!nor->wait_till_ready)
|
||||
- nor->wait_till_ready = spi_nor_wait_till_ready;
|
||||
-
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -926,16 +937,24 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- id = spi_nor_match_id(name);
|
||||
- if (!id)
|
||||
+ /* Try to auto-detect if chip name wasn't specified */
|
||||
+ if (!name)
|
||||
+ id = spi_nor_read_id(nor);
|
||||
+ else
|
||||
+ id = spi_nor_match_id(name);
|
||||
+ if (IS_ERR_OR_NULL(id))
|
||||
return -ENOENT;
|
||||
|
||||
info = (void *)id->driver_data;
|
||||
|
||||
- if (info->jedec_id) {
|
||||
+ /*
|
||||
+ * If caller has specified name of flash model that can normally be
|
||||
+ * detected using JEDEC, let's verify it.
|
||||
+ */
|
||||
+ if (name && info->id_len) {
|
||||
const struct spi_device_id *jid;
|
||||
|
||||
- jid = nor->read_id(nor);
|
||||
+ jid = spi_nor_read_id(nor);
|
||||
if (IS_ERR(jid)) {
|
||||
return PTR_ERR(jid);
|
||||
} else if (jid != id) {
|
||||
@@ -960,9 +979,9 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
* up with the software protection bits set
|
||||
*/
|
||||
|
||||
- if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ATMEL ||
|
||||
- JEDEC_MFR(info->jedec_id) == CFI_MFR_INTEL ||
|
||||
- JEDEC_MFR(info->jedec_id) == CFI_MFR_SST) {
|
||||
+ if (JEDEC_MFR(info) == CFI_MFR_ATMEL ||
|
||||
+ JEDEC_MFR(info) == CFI_MFR_INTEL ||
|
||||
+ JEDEC_MFR(info) == CFI_MFR_SST) {
|
||||
write_enable(nor);
|
||||
write_sr(nor, 0);
|
||||
}
|
||||
@@ -977,7 +996,7 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
mtd->_read = spi_nor_read;
|
||||
|
||||
/* nor protection support for STmicro chips */
|
||||
- if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ST) {
|
||||
+ if (JEDEC_MFR(info) == CFI_MFR_ST) {
|
||||
mtd->_lock = spi_nor_lock;
|
||||
mtd->_unlock = spi_nor_unlock;
|
||||
}
|
||||
@@ -988,9 +1007,8 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
else
|
||||
mtd->_write = spi_nor_write;
|
||||
|
||||
- if ((info->flags & USE_FSR) &&
|
||||
- nor->wait_till_ready == spi_nor_wait_till_ready)
|
||||
- nor->wait_till_ready = spi_nor_wait_till_fsr_ready;
|
||||
+ if (info->flags & USE_FSR)
|
||||
+ nor->flags |= SNOR_F_USE_FSR;
|
||||
|
||||
#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
|
||||
/* prefer "small sector" erase if possible */
|
||||
@@ -1031,7 +1049,7 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
|
||||
/* Quad/Dual-read mode takes precedence over fast/normal */
|
||||
if (mode == SPI_NOR_QUAD && info->flags & SPI_NOR_QUAD_READ) {
|
||||
- ret = set_quad_mode(nor, info->jedec_id);
|
||||
+ ret = set_quad_mode(nor, info);
|
||||
if (ret) {
|
||||
dev_err(dev, "quad mode not supported\n");
|
||||
return ret;
|
||||
@@ -1067,7 +1085,7 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
else if (mtd->size > 0x1000000) {
|
||||
/* enable 4-byte addressing if the device exceeds 16MiB */
|
||||
nor->addr_width = 4;
|
||||
- if (JEDEC_MFR(info->jedec_id) == CFI_MFR_AMD) {
|
||||
+ if (JEDEC_MFR(info) == CFI_MFR_AMD) {
|
||||
/* Dedicated 4-byte command set */
|
||||
switch (nor->flash_read) {
|
||||
case SPI_NOR_QUAD:
|
||||
@@ -1088,7 +1106,7 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
nor->erase_opcode = SPINOR_OP_SE_4B;
|
||||
mtd->erasesize = info->sector_size;
|
||||
} else
|
||||
- set_4byte(nor, info->jedec_id, 1);
|
||||
+ set_4byte(nor, info, 1);
|
||||
} else {
|
||||
nor->addr_width = 3;
|
||||
}
|
||||
--- a/include/linux/mtd/spi-nor.h
|
||||
+++ b/include/linux/mtd/spi-nor.h
|
||||
@@ -116,6 +116,10 @@ enum spi_nor_ops {
|
||||
SPI_NOR_OPS_UNLOCK,
|
||||
};
|
||||
|
||||
+enum spi_nor_option_flags {
|
||||
+ SNOR_F_USE_FSR = BIT(0),
|
||||
+};
|
||||
+
|
||||
/**
|
||||
* struct spi_nor - Structure for defining a the SPI NOR layer
|
||||
* @mtd: point to a mtd_info structure
|
||||
@@ -129,6 +133,7 @@ enum spi_nor_ops {
|
||||
* @program_opcode: the program opcode
|
||||
* @flash_read: the mode of the read
|
||||
* @sst_write_second: used by the SST write operation
|
||||
+ * @flags: flag options for the current SPI-NOR (SNOR_F_*)
|
||||
* @cfg: used by the read_xfer/write_xfer
|
||||
* @cmd_buf: used by the write_reg
|
||||
* @prepare: [OPTIONAL] do some preparations for the
|
||||
@@ -139,9 +144,6 @@ enum spi_nor_ops {
|
||||
* @write_xfer: [OPTIONAL] the writefundamental primitive
|
||||
* @read_reg: [DRIVER-SPECIFIC] read out the register
|
||||
* @write_reg: [DRIVER-SPECIFIC] write data to the register
|
||||
- * @read_id: [REPLACEABLE] read out the ID data, and find
|
||||
- * the proper spi_device_id
|
||||
- * @wait_till_ready: [REPLACEABLE] wait till the NOR becomes ready
|
||||
* @read: [DRIVER-SPECIFIC] read data from the SPI NOR
|
||||
* @write: [DRIVER-SPECIFIC] write data to the SPI NOR
|
||||
* @erase: [DRIVER-SPECIFIC] erase a sector of the SPI NOR
|
||||
@@ -160,6 +162,7 @@ struct spi_nor {
|
||||
u8 program_opcode;
|
||||
enum read_mode flash_read;
|
||||
bool sst_write_second;
|
||||
+ u32 flags;
|
||||
struct spi_nor_xfer_cfg cfg;
|
||||
u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];
|
||||
|
||||
@@ -172,8 +175,6 @@ struct spi_nor {
|
||||
int (*read_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len);
|
||||
int (*write_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len,
|
||||
int write_enable);
|
||||
- const struct spi_device_id *(*read_id)(struct spi_nor *nor);
|
||||
- int (*wait_till_ready)(struct spi_nor *nor);
|
||||
|
||||
int (*read)(struct spi_nor *nor, loff_t from,
|
||||
size_t len, size_t *retlen, u_char *read_buf);
|
|
@ -1,46 +0,0 @@
|
|||
--- a/drivers/mtd/spi-nor/spi-nor.c
|
||||
+++ b/drivers/mtd/spi-nor/spi-nor.c
|
||||
@@ -925,29 +925,23 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- info = (void *)id->driver_data;
|
||||
-
|
||||
- if (info->jedec_id) {
|
||||
- const struct spi_device_id *jid;
|
||||
-
|
||||
- jid = nor->read_id(nor);
|
||||
- if (IS_ERR(jid)) {
|
||||
- return PTR_ERR(jid);
|
||||
- } else if (jid != id) {
|
||||
- /*
|
||||
- * JEDEC knows better, so overwrite platform ID. We
|
||||
- * can't trust partitions any longer, but we'll let
|
||||
- * mtd apply them anyway, since some partitions may be
|
||||
- * marked read-only, and we don't want to lose that
|
||||
- * information, even if it's not 100% accurate.
|
||||
- */
|
||||
- dev_warn(dev, "found %s, expected %s\n",
|
||||
- jid->name, id->name);
|
||||
- id = jid;
|
||||
- info = (void *)jid->driver_data;
|
||||
+ if (id) {
|
||||
+ info = (void *)id->driver_data;
|
||||
+ if (info->jedec_id) {
|
||||
+ dev_warn(dev,
|
||||
+ "passed SPI device ID (%s) contains JEDEC, ignoring it, driver should be fixed!\n",
|
||||
+ id->name);
|
||||
+ id = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
+ if (!id) {
|
||||
+ id = nor->read_id(nor);
|
||||
+ if (IS_ERR(id))
|
||||
+ return PTR_ERR(id);
|
||||
+ }
|
||||
+ info = (void *)id->driver_data;
|
||||
+
|
||||
mutex_init(&nor->lock);
|
||||
|
||||
/*
|
|
@ -1,374 +0,0 @@
|
|||
--- a/drivers/mtd/spi-nor/fsl-quadspi.c
|
||||
+++ b/drivers/mtd/spi-nor/fsl-quadspi.c
|
||||
@@ -719,16 +719,10 @@ static int fsl_qspi_read(struct spi_nor
|
||||
{
|
||||
struct fsl_qspi *q = nor->priv;
|
||||
u8 cmd = nor->read_opcode;
|
||||
- int ret;
|
||||
|
||||
dev_dbg(q->dev, "cmd [%x],read from (0x%p, 0x%.8x, 0x%.8x),len:%d\n",
|
||||
cmd, q->ahb_base, q->chip_base_addr, (unsigned int)from, len);
|
||||
|
||||
- /* Wait until the previous command is finished. */
|
||||
- ret = nor->wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
-
|
||||
/* Read out the data directly from the AHB buffer.*/
|
||||
memcpy(buf, q->ahb_base + q->chip_base_addr + from, len);
|
||||
|
||||
@@ -744,16 +738,6 @@ static int fsl_qspi_erase(struct spi_nor
|
||||
dev_dbg(nor->dev, "%dKiB at 0x%08x:0x%08x\n",
|
||||
nor->mtd->erasesize / 1024, q->chip_base_addr, (u32)offs);
|
||||
|
||||
- /* Wait until finished previous write command. */
|
||||
- ret = nor->wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
-
|
||||
- /* Send write enable, then erase commands. */
|
||||
- ret = nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0, 0);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
-
|
||||
ret = fsl_qspi_runcmd(q, nor->erase_opcode, offs, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
--- a/drivers/mtd/spi-nor/spi-nor.c
|
||||
+++ b/drivers/mtd/spi-nor/spi-nor.c
|
||||
@@ -163,81 +163,69 @@ static inline int set_4byte(struct spi_n
|
||||
return nor->write_reg(nor, SPINOR_OP_BRWR, nor->cmd_buf, 1, 0);
|
||||
}
|
||||
}
|
||||
-
|
||||
-static int spi_nor_wait_till_ready(struct spi_nor *nor)
|
||||
+static inline int spi_nor_sr_ready(struct spi_nor *nor)
|
||||
{
|
||||
- unsigned long deadline;
|
||||
- int sr;
|
||||
-
|
||||
- deadline = jiffies + MAX_READY_WAIT_JIFFIES;
|
||||
-
|
||||
- do {
|
||||
- cond_resched();
|
||||
+ int sr = read_sr(nor);
|
||||
+ if (sr < 0)
|
||||
+ return sr;
|
||||
+ else
|
||||
+ return !(sr & SR_WIP);
|
||||
+}
|
||||
|
||||
- sr = read_sr(nor);
|
||||
- if (sr < 0)
|
||||
- break;
|
||||
- else if (!(sr & SR_WIP))
|
||||
- return 0;
|
||||
- } while (!time_after_eq(jiffies, deadline));
|
||||
+static inline int spi_nor_fsr_ready(struct spi_nor *nor)
|
||||
+{
|
||||
+ int fsr = read_fsr(nor);
|
||||
+ if (fsr < 0)
|
||||
+ return fsr;
|
||||
+ else
|
||||
+ return fsr & FSR_READY;
|
||||
+}
|
||||
|
||||
- return -ETIMEDOUT;
|
||||
+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 sr;
|
||||
+ return sr && fsr;
|
||||
}
|
||||
|
||||
-static int spi_nor_wait_till_fsr_ready(struct spi_nor *nor)
|
||||
+/*
|
||||
+ * Service routine to read status register until ready, or timeout occurs.
|
||||
+ * Returns non-zero if error.
|
||||
+ */
|
||||
+static int spi_nor_wait_till_ready(struct spi_nor *nor)
|
||||
{
|
||||
unsigned long deadline;
|
||||
- int sr;
|
||||
- int fsr;
|
||||
+ int ret;
|
||||
|
||||
deadline = jiffies + MAX_READY_WAIT_JIFFIES;
|
||||
|
||||
do {
|
||||
cond_resched();
|
||||
|
||||
- sr = read_sr(nor);
|
||||
- if (sr < 0) {
|
||||
- break;
|
||||
- } else if (!(sr & SR_WIP)) {
|
||||
- fsr = read_fsr(nor);
|
||||
- if (fsr < 0)
|
||||
- break;
|
||||
- if (fsr & FSR_READY)
|
||||
- return 0;
|
||||
- }
|
||||
+ ret = spi_nor_ready(nor);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+ if (ret)
|
||||
+ return 0;
|
||||
} while (!time_after_eq(jiffies, deadline));
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/*
|
||||
- * Service routine to read status register until ready, or timeout occurs.
|
||||
- * Returns non-zero if error.
|
||||
- */
|
||||
-static int wait_till_ready(struct spi_nor *nor)
|
||||
-{
|
||||
- return nor->wait_till_ready(nor);
|
||||
-}
|
||||
-
|
||||
-/*
|
||||
* Erase the whole flash memory
|
||||
*
|
||||
* Returns 0 if successful, non-zero otherwise.
|
||||
*/
|
||||
static int erase_chip(struct spi_nor *nor)
|
||||
{
|
||||
- int ret;
|
||||
-
|
||||
dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd->size >> 10));
|
||||
|
||||
- /* Wait until finished previous write command. */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
-
|
||||
- /* Send write enable, then erase commands. */
|
||||
- write_enable(nor);
|
||||
-
|
||||
return nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0, 0);
|
||||
}
|
||||
|
||||
@@ -290,6 +278,8 @@ static int spi_nor_erase(struct mtd_info
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
+ write_enable(nor);
|
||||
+
|
||||
/* whole-chip erase? */
|
||||
if (len == mtd->size) {
|
||||
if (erase_chip(nor)) {
|
||||
@@ -297,6 +287,10 @@ static int spi_nor_erase(struct mtd_info
|
||||
goto erase_err;
|
||||
}
|
||||
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
+ if (ret)
|
||||
+ goto erase_err;
|
||||
+
|
||||
/* REVISIT in some cases we could speed up erasing large regions
|
||||
* by using SPINOR_OP_SE instead of SPINOR_OP_BE_4K. We may have set up
|
||||
* to use "small sector erase", but that's not always optimal.
|
||||
@@ -312,9 +306,15 @@ static int spi_nor_erase(struct mtd_info
|
||||
|
||||
addr += mtd->erasesize;
|
||||
len -= mtd->erasesize;
|
||||
+
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
+ if (ret)
|
||||
+ goto erase_err;
|
||||
}
|
||||
}
|
||||
|
||||
+ write_disable(nor);
|
||||
+
|
||||
spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE);
|
||||
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
@@ -339,11 +339,6 @@ static int spi_nor_lock(struct mtd_info
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous command */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto err;
|
||||
-
|
||||
status_old = read_sr(nor);
|
||||
|
||||
if (offset < mtd->size - (mtd->size / 2))
|
||||
@@ -386,11 +381,6 @@ static int spi_nor_unlock(struct mtd_inf
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous command */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto err;
|
||||
-
|
||||
status_old = read_sr(nor);
|
||||
|
||||
if (offset+len > mtd->size - (mtd->size / 64))
|
||||
@@ -702,11 +692,6 @@ static int sst_write(struct mtd_info *mt
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous write command. */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto time_out;
|
||||
-
|
||||
write_enable(nor);
|
||||
|
||||
nor->sst_write_second = false;
|
||||
@@ -718,7 +703,7 @@ static int sst_write(struct mtd_info *mt
|
||||
|
||||
/* write one byte. */
|
||||
nor->write(nor, to, 1, retlen, buf);
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
}
|
||||
@@ -730,7 +715,7 @@ static int sst_write(struct mtd_info *mt
|
||||
|
||||
/* write two bytes. */
|
||||
nor->write(nor, to, 2, retlen, buf + actual);
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
to += 2;
|
||||
@@ -739,7 +724,7 @@ static int sst_write(struct mtd_info *mt
|
||||
nor->sst_write_second = false;
|
||||
|
||||
write_disable(nor);
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
|
||||
@@ -750,7 +735,7 @@ static int sst_write(struct mtd_info *mt
|
||||
nor->program_opcode = SPINOR_OP_BP;
|
||||
nor->write(nor, to, 1, retlen, buf + actual);
|
||||
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
write_disable(nor);
|
||||
@@ -778,11 +763,6 @@ static int spi_nor_write(struct mtd_info
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous write command. */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto write_err;
|
||||
-
|
||||
write_enable(nor);
|
||||
|
||||
page_offset = to & (nor->page_size - 1);
|
||||
@@ -801,16 +781,20 @@ static int spi_nor_write(struct mtd_info
|
||||
if (page_size > nor->page_size)
|
||||
page_size = nor->page_size;
|
||||
|
||||
- wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
+ if (ret)
|
||||
+ goto write_err;
|
||||
+
|
||||
write_enable(nor);
|
||||
|
||||
nor->write(nor, to + i, page_size, retlen, buf + i);
|
||||
}
|
||||
}
|
||||
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
write_err:
|
||||
spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE);
|
||||
- return 0;
|
||||
+ return ret;
|
||||
}
|
||||
|
||||
static int macronix_quad_enable(struct spi_nor *nor)
|
||||
@@ -823,7 +807,7 @@ static int macronix_quad_enable(struct s
|
||||
nor->cmd_buf[0] = val | SR_QUAD_EN_MX;
|
||||
nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 1, 0);
|
||||
|
||||
- if (wait_till_ready(nor))
|
||||
+ if (spi_nor_wait_till_ready(nor))
|
||||
return 1;
|
||||
|
||||
ret = read_sr(nor);
|
||||
@@ -905,8 +889,6 @@ static int spi_nor_check(struct spi_nor
|
||||
|
||||
if (!nor->read_id)
|
||||
nor->read_id = spi_nor_read_id;
|
||||
- if (!nor->wait_till_ready)
|
||||
- nor->wait_till_ready = spi_nor_wait_till_ready;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -977,9 +959,8 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
else
|
||||
mtd->_write = spi_nor_write;
|
||||
|
||||
- if ((info->flags & USE_FSR) &&
|
||||
- nor->wait_till_ready == spi_nor_wait_till_ready)
|
||||
- nor->wait_till_ready = spi_nor_wait_till_fsr_ready;
|
||||
+ if (info->flags & USE_FSR)
|
||||
+ nor->flags |= SNOR_F_USE_FSR;
|
||||
|
||||
#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
|
||||
/* prefer "small sector" erase if possible */
|
||||
--- a/include/linux/mtd/spi-nor.h
|
||||
+++ b/include/linux/mtd/spi-nor.h
|
||||
@@ -116,6 +116,10 @@ enum spi_nor_ops {
|
||||
SPI_NOR_OPS_UNLOCK,
|
||||
};
|
||||
|
||||
+enum spi_nor_option_flags {
|
||||
+ SNOR_F_USE_FSR = BIT(0),
|
||||
+};
|
||||
+
|
||||
/**
|
||||
* struct spi_nor - Structure for defining a the SPI NOR layer
|
||||
* @mtd: point to a mtd_info structure
|
||||
@@ -129,6 +133,7 @@ enum spi_nor_ops {
|
||||
* @program_opcode: the program opcode
|
||||
* @flash_read: the mode of the read
|
||||
* @sst_write_second: used by the SST write operation
|
||||
+ * @flags: flag options for the current SPI-NOR (SNOR_F_*)
|
||||
* @cfg: used by the read_xfer/write_xfer
|
||||
* @cmd_buf: used by the write_reg
|
||||
* @prepare: [OPTIONAL] do some preparations for the
|
||||
@@ -141,7 +146,6 @@ enum spi_nor_ops {
|
||||
* @write_reg: [DRIVER-SPECIFIC] write data to the register
|
||||
* @read_id: [REPLACEABLE] read out the ID data, and find
|
||||
* the proper spi_device_id
|
||||
- * @wait_till_ready: [REPLACEABLE] wait till the NOR becomes ready
|
||||
* @read: [DRIVER-SPECIFIC] read data from the SPI NOR
|
||||
* @write: [DRIVER-SPECIFIC] write data to the SPI NOR
|
||||
* @erase: [DRIVER-SPECIFIC] erase a sector of the SPI NOR
|
||||
@@ -160,6 +164,7 @@ struct spi_nor {
|
||||
u8 program_opcode;
|
||||
enum read_mode flash_read;
|
||||
bool sst_write_second;
|
||||
+ u32 flags;
|
||||
struct spi_nor_xfer_cfg cfg;
|
||||
u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];
|
||||
|
||||
@@ -173,7 +178,6 @@ struct spi_nor {
|
||||
int (*write_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len,
|
||||
int write_enable);
|
||||
const struct spi_device_id *(*read_id)(struct spi_nor *nor);
|
||||
- int (*wait_till_ready)(struct spi_nor *nor);
|
||||
|
||||
int (*read)(struct spi_nor *nor, loff_t from,
|
||||
size_t len, size_t *retlen, u_char *read_buf);
|
|
@ -17,247 +17,3 @@
|
|||
obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o
|
||||
obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o
|
||||
+obj-$(CONFIG_MTD_SPI_BCM53XXSPIFLASH) += bcm53xxspiflash.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/mtd/spi-nor/bcm53xxspiflash.c
|
||||
@@ -0,0 +1,241 @@
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/spi/spi.h>
|
||||
+#include <linux/mtd/spi-nor.h>
|
||||
+#include <linux/mtd/mtd.h>
|
||||
+#include <linux/mtd/cfi.h>
|
||||
+
|
||||
+static const char * const probes[] = { "bcm47xxpart", NULL };
|
||||
+
|
||||
+struct bcm53xxsf {
|
||||
+ struct spi_device *spi;
|
||||
+ struct mtd_info mtd;
|
||||
+ struct spi_nor nor;
|
||||
+};
|
||||
+
|
||||
+/**************************************************
|
||||
+ * spi-nor API
|
||||
+ **************************************************/
|
||||
+
|
||||
+static int bcm53xxspiflash_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
|
||||
+ int len)
|
||||
+{
|
||||
+ struct bcm53xxsf *b53sf = nor->priv;
|
||||
+
|
||||
+ return spi_write_then_read(b53sf->spi, &opcode, 1, buf, len);
|
||||
+}
|
||||
+
|
||||
+static int bcm53xxspiflash_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
|
||||
+ int len, int write_enable)
|
||||
+{
|
||||
+ struct bcm53xxsf *b53sf = nor->priv;
|
||||
+ u8 *cmd = kzalloc(len + 1, GFP_KERNEL);
|
||||
+ int err;
|
||||
+
|
||||
+ if (!cmd)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ cmd[0] = opcode;
|
||||
+ memcpy(&cmd[1], buf, len);
|
||||
+ err = spi_write(b53sf->spi, cmd, len + 1);
|
||||
+
|
||||
+ kfree(cmd);
|
||||
+
|
||||
+ return err;
|
||||
+}
|
||||
+
|
||||
+static int bcm53xxspiflash_read(struct spi_nor *nor, loff_t from, size_t len,
|
||||
+ size_t *retlen, u_char *buf)
|
||||
+{
|
||||
+ struct bcm53xxsf *b53sf = nor->priv;
|
||||
+ struct spi_message m;
|
||||
+ struct spi_transfer t[2] = { { 0 }, { 0 } };
|
||||
+ unsigned char cmd[5];
|
||||
+ int cmd_len = 0;
|
||||
+ int err;
|
||||
+
|
||||
+ spi_message_init(&m);
|
||||
+
|
||||
+ cmd[cmd_len++] = SPINOR_OP_READ;
|
||||
+ if (b53sf->mtd.size > 0x1000000)
|
||||
+ cmd[cmd_len++] = (from & 0xFF000000) >> 24;
|
||||
+ cmd[cmd_len++] = (from & 0x00FF0000) >> 16;
|
||||
+ cmd[cmd_len++] = (from & 0x0000FF00) >> 8;
|
||||
+ cmd[cmd_len++] = (from & 0x000000FF) >> 0;
|
||||
+
|
||||
+ t[0].tx_buf = cmd;
|
||||
+ t[0].len = cmd_len;
|
||||
+ spi_message_add_tail(&t[0], &m);
|
||||
+
|
||||
+ t[1].rx_buf = buf;
|
||||
+ t[1].len = len;
|
||||
+ spi_message_add_tail(&t[1], &m);
|
||||
+
|
||||
+ err = spi_sync(b53sf->spi, &m);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ if (retlen && m.actual_length > cmd_len)
|
||||
+ *retlen = m.actual_length - cmd_len;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void bcm53xxspiflash_write(struct spi_nor *nor, loff_t to, size_t len,
|
||||
+ size_t *retlen, const u_char *buf)
|
||||
+{
|
||||
+ struct bcm53xxsf *b53sf = nor->priv;
|
||||
+ struct spi_message m;
|
||||
+ struct spi_transfer t = { 0 };
|
||||
+ u8 *cmd = kzalloc(len + 5, GFP_KERNEL);
|
||||
+ int cmd_len = 0;
|
||||
+ int err;
|
||||
+
|
||||
+ if (!cmd)
|
||||
+ return;
|
||||
+
|
||||
+ spi_message_init(&m);
|
||||
+
|
||||
+ cmd[cmd_len++] = nor->program_opcode;
|
||||
+ if (b53sf->mtd.size > 0x1000000)
|
||||
+ cmd[cmd_len++] = (to & 0xFF000000) >> 24;
|
||||
+ cmd[cmd_len++] = (to & 0x00FF0000) >> 16;
|
||||
+ cmd[cmd_len++] = (to & 0x0000FF00) >> 8;
|
||||
+ cmd[cmd_len++] = (to & 0x000000FF) >> 0;
|
||||
+ memcpy(&cmd[cmd_len], buf, len);
|
||||
+
|
||||
+ t.tx_buf = cmd;
|
||||
+ t.len = cmd_len + len;
|
||||
+ spi_message_add_tail(&t, &m);
|
||||
+
|
||||
+ err = spi_sync(b53sf->spi, &m);
|
||||
+ if (err)
|
||||
+ goto out;
|
||||
+
|
||||
+ if (retlen && m.actual_length > cmd_len)
|
||||
+ *retlen += m.actual_length - cmd_len;
|
||||
+
|
||||
+out:
|
||||
+ kfree(cmd);
|
||||
+}
|
||||
+
|
||||
+static int bcm53xxspiflash_erase(struct spi_nor *nor, loff_t offs)
|
||||
+{
|
||||
+ struct bcm53xxsf *b53sf = nor->priv;
|
||||
+ unsigned char cmd[5];
|
||||
+ int i;
|
||||
+
|
||||
+ i = 0;
|
||||
+ cmd[i++] = nor->erase_opcode;
|
||||
+ if (b53sf->mtd.size > 0x1000000)
|
||||
+ cmd[i++] = (offs & 0xFF000000) >> 24;
|
||||
+ cmd[i++] = ((offs & 0x00FF0000) >> 16);
|
||||
+ cmd[i++] = ((offs & 0x0000FF00) >> 8);
|
||||
+ cmd[i++] = ((offs & 0x000000FF) >> 0);
|
||||
+
|
||||
+ return spi_write(b53sf->spi, cmd, i);
|
||||
+}
|
||||
+
|
||||
+static const struct spi_device_id *bcm53xxspiflash_read_id(struct spi_nor *nor)
|
||||
+{
|
||||
+ struct bcm53xxsf *b53sf = nor->priv;
|
||||
+ struct device *dev = &b53sf->spi->dev;
|
||||
+ const struct spi_device_id *id;
|
||||
+ unsigned char cmd[4];
|
||||
+ unsigned char resp[2];
|
||||
+ char *name = NULL;
|
||||
+ int err;
|
||||
+
|
||||
+ /* SST and Winbond/NexFlash specific command */
|
||||
+ cmd[0] = 0x90; /* Read Manufacturer / Device ID */
|
||||
+ cmd[1] = 0;
|
||||
+ cmd[2] = 0;
|
||||
+ cmd[3] = 0;
|
||||
+ err = spi_write_then_read(b53sf->spi, cmd, 4, resp, 2);
|
||||
+ if (err < 0) {
|
||||
+ dev_err(dev, "error reading SPI flash id\n");
|
||||
+ return ERR_PTR(-EBUSY);
|
||||
+ }
|
||||
+ switch (resp[0]) {
|
||||
+ case 0xef: /* Winbond/NexFlash */
|
||||
+ switch (resp[1]) {
|
||||
+ case 0x17:
|
||||
+ name = "w25q128";
|
||||
+ break;
|
||||
+ }
|
||||
+ if (!name) {
|
||||
+ dev_err(dev, "Unknown Winbond/NexFlash flash: %02X %02X\n",
|
||||
+ resp[0], resp[1]);
|
||||
+ return ERR_PTR(-ENOTSUPP);
|
||||
+ }
|
||||
+ goto found_name;
|
||||
+ }
|
||||
+
|
||||
+ /* TODO: Try more ID commands */
|
||||
+
|
||||
+ return ERR_PTR(-ENODEV);
|
||||
+
|
||||
+found_name:
|
||||
+ id = spi_nor_match_id(name);
|
||||
+ if (!id) {
|
||||
+ dev_err(dev, "No matching entry for %s flash\n", name);
|
||||
+ return ERR_PTR(-ENOENT);
|
||||
+ }
|
||||
+
|
||||
+ return id;
|
||||
+}
|
||||
+
|
||||
+/**************************************************
|
||||
+ * SPI driver
|
||||
+ **************************************************/
|
||||
+
|
||||
+static int bcm53xxspiflash_probe(struct spi_device *spi)
|
||||
+{
|
||||
+ struct bcm53xxsf *b53sf;
|
||||
+ int err;
|
||||
+
|
||||
+ b53sf = devm_kzalloc(&spi->dev, sizeof(*b53sf), GFP_KERNEL);
|
||||
+ if (!b53sf)
|
||||
+ return -ENOMEM;
|
||||
+ spi_set_drvdata(spi, b53sf);
|
||||
+
|
||||
+ b53sf->spi = spi;
|
||||
+
|
||||
+ b53sf->mtd.priv = &b53sf->nor;
|
||||
+
|
||||
+ b53sf->nor.mtd = &b53sf->mtd;
|
||||
+ b53sf->nor.dev = &spi->dev;
|
||||
+ b53sf->nor.read_reg = bcm53xxspiflash_read_reg;
|
||||
+ b53sf->nor.write_reg = bcm53xxspiflash_write_reg;
|
||||
+ b53sf->nor.read = bcm53xxspiflash_read;
|
||||
+ b53sf->nor.write = bcm53xxspiflash_write;
|
||||
+ b53sf->nor.erase = bcm53xxspiflash_erase;
|
||||
+ b53sf->nor.read_id = bcm53xxspiflash_read_id;
|
||||
+ b53sf->nor.priv = b53sf;
|
||||
+
|
||||
+ err = spi_nor_scan(&b53sf->nor, NULL, SPI_NOR_NORMAL);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ err = mtd_device_parse_register(&b53sf->mtd, probes, NULL, NULL, 0);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int bcm53xxspiflash_remove(struct spi_device *spi)
|
||||
+{
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct spi_driver bcm53xxspiflash_driver = {
|
||||
+ .driver = {
|
||||
+ .name = "bcm53xxspiflash",
|
||||
+ .owner = THIS_MODULE,
|
||||
+ },
|
||||
+ .probe = bcm53xxspiflash_probe,
|
||||
+ .remove = bcm53xxspiflash_remove,
|
||||
+};
|
||||
+
|
||||
+module_spi_driver(bcm53xxspiflash_driver);
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
--- a/drivers/mtd/spi-nor/bcm53xxspiflash.c
|
||||
+++ b/drivers/mtd/spi-nor/bcm53xxspiflash.c
|
||||
@@ -173,7 +173,8 @@ static const struct spi_device_id *bcm53
|
||||
|
||||
/* TODO: Try more ID commands */
|
||||
|
||||
- return ERR_PTR(-ENODEV);
|
||||
+ /* Some chips used by Broadcom may actually support JEDEC */
|
||||
+ return spi_nor_read_id(nor);
|
||||
|
||||
found_name:
|
||||
id = spi_nor_match_id(name);
|
||||
--- a/drivers/mtd/spi-nor/spi-nor.c
|
||||
+++ b/drivers/mtd/spi-nor/spi-nor.c
|
||||
@@ -629,7 +629,7 @@ const struct spi_device_id spi_nor_ids[]
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(spi_nor_ids);
|
||||
|
||||
-static const struct spi_device_id *spi_nor_read_id(struct spi_nor *nor)
|
||||
+const struct spi_device_id *spi_nor_read_id(struct spi_nor *nor)
|
||||
{
|
||||
int tmp;
|
||||
u8 id[5];
|
||||
@@ -660,6 +660,7 @@ static const struct spi_device_id *spi_n
|
||||
dev_err(nor->dev, "unrecognized JEDEC id %06x\n", jedec);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
+EXPORT_SYMBOL_GPL(spi_nor_read_id);
|
||||
|
||||
static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
--- a/include/linux/mtd/spi-nor.h
|
||||
+++ b/include/linux/mtd/spi-nor.h
|
||||
@@ -188,6 +188,8 @@ struct spi_nor {
|
||||
void *priv;
|
||||
};
|
||||
|
||||
+const struct spi_device_id *spi_nor_read_id(struct spi_nor *nor);
|
||||
+
|
||||
/**
|
||||
* spi_nor_scan() - scan the SPI NOR
|
||||
* @nor: the spi_nor structure
|
|
@ -0,0 +1,662 @@
|
|||
--- a/drivers/mtd/spi-nor/spi-nor.c
|
||||
+++ b/drivers/mtd/spi-nor/spi-nor.c
|
||||
@@ -26,7 +26,38 @@
|
||||
/* Define max times to check status register before we give up. */
|
||||
#define MAX_READY_WAIT_JIFFIES (40 * HZ) /* M25P16 specs 40s max chip erase */
|
||||
|
||||
-#define JEDEC_MFR(_jedec_id) ((_jedec_id) >> 16)
|
||||
+#define SPI_NOR_MAX_ID_LEN 6
|
||||
+
|
||||
+struct flash_info {
|
||||
+ /*
|
||||
+ * 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 sector_size;
|
||||
+ u16 n_sectors;
|
||||
+
|
||||
+ u16 page_size;
|
||||
+ u16 addr_width;
|
||||
+
|
||||
+ u16 flags;
|
||||
+#define SECT_4K 0x01 /* SPINOR_OP_BE_4K works uniformly */
|
||||
+#define SPI_NOR_NO_ERASE 0x02 /* No erase command needed */
|
||||
+#define SST_WRITE 0x04 /* use SST byte programming */
|
||||
+#define SPI_NOR_NO_FR 0x08 /* Can't do fastread */
|
||||
+#define SECT_4K_PMC 0x10 /* SPINOR_OP_BE_4K_PMC works uniformly */
|
||||
+#define SPI_NOR_DUAL_READ 0x20 /* Flash supports Dual Read */
|
||||
+#define SPI_NOR_QUAD_READ 0x40 /* Flash supports Quad Read */
|
||||
+#define USE_FSR 0x80 /* use flag status register */
|
||||
+};
|
||||
+
|
||||
+#define JEDEC_MFR(info) ((info)->id[0])
|
||||
|
||||
static const struct spi_device_id *spi_nor_match_id(const char *name);
|
||||
|
||||
@@ -98,7 +129,7 @@ static inline int spi_nor_read_dummy_cyc
|
||||
case SPI_NOR_FAST:
|
||||
case SPI_NOR_DUAL:
|
||||
case SPI_NOR_QUAD:
|
||||
- return 1;
|
||||
+ return 8;
|
||||
case SPI_NOR_NORMAL:
|
||||
return 0;
|
||||
}
|
||||
@@ -138,13 +169,14 @@ static inline struct spi_nor *mtd_to_spi
|
||||
}
|
||||
|
||||
/* Enable/disable 4-byte addressing mode. */
|
||||
-static inline int set_4byte(struct spi_nor *nor, u32 jedec_id, int enable)
|
||||
+static inline int set_4byte(struct spi_nor *nor, struct flash_info *info,
|
||||
+ int enable)
|
||||
{
|
||||
int status;
|
||||
bool need_wren = false;
|
||||
u8 cmd;
|
||||
|
||||
- switch (JEDEC_MFR(jedec_id)) {
|
||||
+ switch (JEDEC_MFR(info)) {
|
||||
case CFI_MFR_ST: /* Micron, actually */
|
||||
/* Some Micron need WREN command; all will accept it */
|
||||
need_wren = true;
|
||||
@@ -165,81 +197,74 @@ static inline int set_4byte(struct spi_n
|
||||
return nor->write_reg(nor, SPINOR_OP_BRWR, nor->cmd_buf, 1, 0);
|
||||
}
|
||||
}
|
||||
-
|
||||
-static int spi_nor_wait_till_ready(struct spi_nor *nor)
|
||||
+static inline int spi_nor_sr_ready(struct spi_nor *nor)
|
||||
{
|
||||
- unsigned long deadline;
|
||||
- int sr;
|
||||
-
|
||||
- deadline = jiffies + MAX_READY_WAIT_JIFFIES;
|
||||
-
|
||||
- do {
|
||||
- cond_resched();
|
||||
+ int sr = read_sr(nor);
|
||||
+ if (sr < 0)
|
||||
+ return sr;
|
||||
+ else
|
||||
+ return !(sr & SR_WIP);
|
||||
+}
|
||||
|
||||
- sr = read_sr(nor);
|
||||
- if (sr < 0)
|
||||
- break;
|
||||
- else if (!(sr & SR_WIP))
|
||||
- return 0;
|
||||
- } while (!time_after_eq(jiffies, deadline));
|
||||
+static inline int spi_nor_fsr_ready(struct spi_nor *nor)
|
||||
+{
|
||||
+ int fsr = read_fsr(nor);
|
||||
+ if (fsr < 0)
|
||||
+ return fsr;
|
||||
+ else
|
||||
+ return fsr & FSR_READY;
|
||||
+}
|
||||
|
||||
- return -ETIMEDOUT;
|
||||
+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;
|
||||
}
|
||||
|
||||
-static int spi_nor_wait_till_fsr_ready(struct spi_nor *nor)
|
||||
+/*
|
||||
+ * Service routine to read status register until ready, or timeout occurs.
|
||||
+ * Returns non-zero if error.
|
||||
+ */
|
||||
+static int spi_nor_wait_till_ready(struct spi_nor *nor)
|
||||
{
|
||||
unsigned long deadline;
|
||||
- int sr;
|
||||
- int fsr;
|
||||
+ int timeout = 0, ret;
|
||||
|
||||
deadline = jiffies + MAX_READY_WAIT_JIFFIES;
|
||||
|
||||
- do {
|
||||
+ while (!timeout) {
|
||||
+ if (time_after_eq(jiffies, deadline))
|
||||
+ timeout = 1;
|
||||
+
|
||||
+ ret = spi_nor_ready(nor);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+ if (ret)
|
||||
+ return 0;
|
||||
+
|
||||
cond_resched();
|
||||
+ }
|
||||
|
||||
- sr = read_sr(nor);
|
||||
- if (sr < 0) {
|
||||
- break;
|
||||
- } else if (!(sr & SR_WIP)) {
|
||||
- fsr = read_fsr(nor);
|
||||
- if (fsr < 0)
|
||||
- break;
|
||||
- if (fsr & FSR_READY)
|
||||
- return 0;
|
||||
- }
|
||||
- } while (!time_after_eq(jiffies, deadline));
|
||||
+ dev_err(nor->dev, "flash operation timed out\n");
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/*
|
||||
- * Service routine to read status register until ready, or timeout occurs.
|
||||
- * Returns non-zero if error.
|
||||
- */
|
||||
-static int wait_till_ready(struct spi_nor *nor)
|
||||
-{
|
||||
- return nor->wait_till_ready(nor);
|
||||
-}
|
||||
-
|
||||
-/*
|
||||
* Erase the whole flash memory
|
||||
*
|
||||
* Returns 0 if successful, non-zero otherwise.
|
||||
*/
|
||||
static int erase_chip(struct spi_nor *nor)
|
||||
{
|
||||
- int ret;
|
||||
-
|
||||
dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd->size >> 10));
|
||||
|
||||
- /* Wait until finished previous write command. */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
-
|
||||
- /* Send write enable, then erase commands. */
|
||||
- write_enable(nor);
|
||||
-
|
||||
return nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0, 0);
|
||||
}
|
||||
|
||||
@@ -294,11 +319,17 @@ static int spi_nor_erase(struct mtd_info
|
||||
|
||||
/* whole-chip erase? */
|
||||
if (len == mtd->size) {
|
||||
+ write_enable(nor);
|
||||
+
|
||||
if (erase_chip(nor)) {
|
||||
ret = -EIO;
|
||||
goto erase_err;
|
||||
}
|
||||
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
+ if (ret)
|
||||
+ goto erase_err;
|
||||
+
|
||||
/* REVISIT in some cases we could speed up erasing large regions
|
||||
* by using SPINOR_OP_SE instead of SPINOR_OP_BE_4K. We may have set up
|
||||
* to use "small sector erase", but that's not always optimal.
|
||||
@@ -307,6 +338,8 @@ static int spi_nor_erase(struct mtd_info
|
||||
/* "sector"-at-a-time erase */
|
||||
} else {
|
||||
while (len) {
|
||||
+ write_enable(nor);
|
||||
+
|
||||
if (nor->erase(nor, addr)) {
|
||||
ret = -EIO;
|
||||
goto erase_err;
|
||||
@@ -314,9 +347,15 @@ static int spi_nor_erase(struct mtd_info
|
||||
|
||||
addr += mtd->erasesize;
|
||||
len -= mtd->erasesize;
|
||||
+
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
+ if (ret)
|
||||
+ goto erase_err;
|
||||
}
|
||||
}
|
||||
|
||||
+ write_disable(nor);
|
||||
+
|
||||
spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE);
|
||||
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
@@ -341,11 +380,6 @@ static int spi_nor_lock(struct mtd_info
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous command */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto err;
|
||||
-
|
||||
status_old = read_sr(nor);
|
||||
|
||||
if (offset < mtd->size - (mtd->size / 2))
|
||||
@@ -388,11 +422,6 @@ static int spi_nor_unlock(struct mtd_inf
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous command */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto err;
|
||||
-
|
||||
status_old = read_sr(nor);
|
||||
|
||||
if (offset+len > mtd->size - (mtd->size / 64))
|
||||
@@ -424,38 +453,34 @@ err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
-struct flash_info {
|
||||
- /* JEDEC id zero means "no ID" (most older chips); otherwise it has
|
||||
- * a high byte of zero plus three data bytes: the manufacturer id,
|
||||
- * then a two byte device id.
|
||||
- */
|
||||
- u32 jedec_id;
|
||||
- u16 ext_id;
|
||||
-
|
||||
- /* The size listed here is what works with SPINOR_OP_SE, which isn't
|
||||
- * necessarily called a "sector" by the vendor.
|
||||
- */
|
||||
- unsigned sector_size;
|
||||
- u16 n_sectors;
|
||||
-
|
||||
- u16 page_size;
|
||||
- u16 addr_width;
|
||||
-
|
||||
- u16 flags;
|
||||
-#define SECT_4K 0x01 /* SPINOR_OP_BE_4K works uniformly */
|
||||
-#define SPI_NOR_NO_ERASE 0x02 /* No erase command needed */
|
||||
-#define SST_WRITE 0x04 /* use SST byte programming */
|
||||
-#define SPI_NOR_NO_FR 0x08 /* Can't do fastread */
|
||||
-#define SECT_4K_PMC 0x10 /* SPINOR_OP_BE_4K_PMC works uniformly */
|
||||
-#define SPI_NOR_DUAL_READ 0x20 /* Flash supports Dual Read */
|
||||
-#define SPI_NOR_QUAD_READ 0x40 /* Flash supports Quad Read */
|
||||
-#define USE_FSR 0x80 /* use flag status register */
|
||||
-};
|
||||
-
|
||||
+/* Used when the "_ext_id" is two bytes at most */
|
||||
#define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \
|
||||
((kernel_ulong_t)&(struct flash_info) { \
|
||||
- .jedec_id = (_jedec_id), \
|
||||
- .ext_id = (_ext_id), \
|
||||
+ .id = { \
|
||||
+ ((_jedec_id) >> 16) & 0xff, \
|
||||
+ ((_jedec_id) >> 8) & 0xff, \
|
||||
+ (_jedec_id) & 0xff, \
|
||||
+ ((_ext_id) >> 8) & 0xff, \
|
||||
+ (_ext_id) & 0xff, \
|
||||
+ }, \
|
||||
+ .id_len = (!(_jedec_id) ? 0 : (3 + ((_ext_id) ? 2 : 0))), \
|
||||
+ .sector_size = (_sector_size), \
|
||||
+ .n_sectors = (_n_sectors), \
|
||||
+ .page_size = 256, \
|
||||
+ .flags = (_flags), \
|
||||
+ })
|
||||
+
|
||||
+#define INFO6(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \
|
||||
+ ((kernel_ulong_t)&(struct flash_info) { \
|
||||
+ .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, \
|
||||
@@ -507,6 +532,9 @@ static const struct spi_device_id spi_no
|
||||
{ "mr25h256", CAT25_INFO( 32 * 1024, 1, 256, 2, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
{ "mr25h10", CAT25_INFO(128 * 1024, 1, 256, 3, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
|
||||
+ /* Fujitsu */
|
||||
+ { "mb85rs1mt", INFO(0x047f27, 0, 128 * 1024, 1, SPI_NOR_NO_ERASE) },
|
||||
+
|
||||
/* GigaDevice */
|
||||
{ "gd25q32", INFO(0xc84016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "gd25q64", INFO(0xc84017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
@@ -532,6 +560,7 @@ static const struct spi_device_id spi_no
|
||||
{ "mx66l1g55g", INFO(0xc2261b, 0, 64 * 1024, 2048, SPI_NOR_QUAD_READ) },
|
||||
|
||||
/* Micron */
|
||||
+ { "n25q032", INFO(0x20ba16, 0, 64 * 1024, 64, 0) },
|
||||
{ "n25q064", INFO(0x20ba17, 0, 64 * 1024, 128, 0) },
|
||||
{ "n25q128a11", INFO(0x20bb18, 0, 64 * 1024, 256, 0) },
|
||||
{ "n25q128a13", INFO(0x20ba18, 0, 64 * 1024, 256, 0) },
|
||||
@@ -556,6 +585,7 @@ static const struct spi_device_id spi_no
|
||||
{ "s70fl01gs", INFO(0x010221, 0x4d00, 256 * 1024, 256, 0) },
|
||||
{ "s25sl12800", INFO(0x012018, 0x0300, 256 * 1024, 64, 0) },
|
||||
{ "s25sl12801", INFO(0x012018, 0x0301, 64 * 1024, 256, 0) },
|
||||
+ { "s25fl128s", INFO6(0x012018, 0x4d0180, 64 * 1024, 256, SPI_NOR_QUAD_READ) },
|
||||
{ "s25fl129p0", INFO(0x012018, 0x4d00, 256 * 1024, 64, 0) },
|
||||
{ "s25fl129p1", INFO(0x012018, 0x4d01, 64 * 1024, 256, 0) },
|
||||
{ "s25sl004a", INFO(0x010212, 0, 64 * 1024, 8, 0) },
|
||||
@@ -577,6 +607,7 @@ static const struct spi_device_id spi_no
|
||||
{ "sst25wf010", INFO(0xbf2502, 0, 64 * 1024, 2, SECT_4K | SST_WRITE) },
|
||||
{ "sst25wf020", INFO(0xbf2503, 0, 64 * 1024, 4, SECT_4K | SST_WRITE) },
|
||||
{ "sst25wf040", INFO(0xbf2504, 0, 64 * 1024, 8, SECT_4K | SST_WRITE) },
|
||||
+ { "sst25wf080", INFO(0xbf2505, 0, 64 * 1024, 16, SECT_4K | SST_WRITE) },
|
||||
|
||||
/* ST Microelectronics -- newer production may have feature updates */
|
||||
{ "m25p05", INFO(0x202010, 0, 32 * 1024, 2, 0) },
|
||||
@@ -588,7 +619,6 @@ static const struct spi_device_id spi_no
|
||||
{ "m25p32", INFO(0x202016, 0, 64 * 1024, 64, 0) },
|
||||
{ "m25p64", INFO(0x202017, 0, 64 * 1024, 128, 0) },
|
||||
{ "m25p128", INFO(0x202018, 0, 256 * 1024, 64, 0) },
|
||||
- { "n25q032", INFO(0x20ba16, 0, 64 * 1024, 64, 0) },
|
||||
|
||||
{ "m25p05-nonjedec", INFO(0, 0, 32 * 1024, 2, 0) },
|
||||
{ "m25p10-nonjedec", INFO(0, 0, 32 * 1024, 4, 0) },
|
||||
@@ -644,32 +674,24 @@ static const struct spi_device_id spi_no
|
||||
static const struct spi_device_id *spi_nor_read_id(struct spi_nor *nor)
|
||||
{
|
||||
int tmp;
|
||||
- u8 id[5];
|
||||
- u32 jedec;
|
||||
- u16 ext_jedec;
|
||||
+ u8 id[SPI_NOR_MAX_ID_LEN];
|
||||
struct flash_info *info;
|
||||
|
||||
- tmp = nor->read_reg(nor, SPINOR_OP_RDID, id, 5);
|
||||
+ tmp = 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);
|
||||
}
|
||||
- jedec = id[0];
|
||||
- jedec = jedec << 8;
|
||||
- jedec |= id[1];
|
||||
- jedec = jedec << 8;
|
||||
- jedec |= id[2];
|
||||
-
|
||||
- ext_jedec = id[3] << 8 | id[4];
|
||||
|
||||
for (tmp = 0; tmp < ARRAY_SIZE(spi_nor_ids) - 1; tmp++) {
|
||||
info = (void *)spi_nor_ids[tmp].driver_data;
|
||||
- if (info->jedec_id == jedec) {
|
||||
- if (info->ext_id == 0 || info->ext_id == ext_jedec)
|
||||
+ if (info->id_len) {
|
||||
+ if (!memcmp(info->id, id, info->id_len))
|
||||
return &spi_nor_ids[tmp];
|
||||
}
|
||||
}
|
||||
- dev_err(nor->dev, "unrecognized JEDEC id %06x\n", jedec);
|
||||
+ dev_err(nor->dev, "unrecognized JEDEC id bytes: %02x, %2x, %2x\n",
|
||||
+ id[0], id[1], id[2]);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
@@ -704,11 +726,6 @@ static int sst_write(struct mtd_info *mt
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous write command. */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto time_out;
|
||||
-
|
||||
write_enable(nor);
|
||||
|
||||
nor->sst_write_second = false;
|
||||
@@ -720,7 +737,7 @@ static int sst_write(struct mtd_info *mt
|
||||
|
||||
/* write one byte. */
|
||||
nor->write(nor, to, 1, retlen, buf);
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
}
|
||||
@@ -732,7 +749,7 @@ static int sst_write(struct mtd_info *mt
|
||||
|
||||
/* write two bytes. */
|
||||
nor->write(nor, to, 2, retlen, buf + actual);
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
to += 2;
|
||||
@@ -741,7 +758,7 @@ static int sst_write(struct mtd_info *mt
|
||||
nor->sst_write_second = false;
|
||||
|
||||
write_disable(nor);
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
|
||||
@@ -752,7 +769,7 @@ static int sst_write(struct mtd_info *mt
|
||||
nor->program_opcode = SPINOR_OP_BP;
|
||||
nor->write(nor, to, 1, retlen, buf + actual);
|
||||
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
write_disable(nor);
|
||||
@@ -780,11 +797,6 @@ static int spi_nor_write(struct mtd_info
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous write command. */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto write_err;
|
||||
-
|
||||
write_enable(nor);
|
||||
|
||||
page_offset = to & (nor->page_size - 1);
|
||||
@@ -803,16 +815,20 @@ static int spi_nor_write(struct mtd_info
|
||||
if (page_size > nor->page_size)
|
||||
page_size = nor->page_size;
|
||||
|
||||
- wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
+ if (ret)
|
||||
+ goto write_err;
|
||||
+
|
||||
write_enable(nor);
|
||||
|
||||
nor->write(nor, to + i, page_size, retlen, buf + i);
|
||||
}
|
||||
}
|
||||
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
write_err:
|
||||
spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE);
|
||||
- return 0;
|
||||
+ return ret;
|
||||
}
|
||||
|
||||
static int macronix_quad_enable(struct spi_nor *nor)
|
||||
@@ -825,7 +841,7 @@ static int macronix_quad_enable(struct s
|
||||
nor->cmd_buf[0] = val | SR_QUAD_EN_MX;
|
||||
nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 1, 0);
|
||||
|
||||
- if (wait_till_ready(nor))
|
||||
+ if (spi_nor_wait_till_ready(nor))
|
||||
return 1;
|
||||
|
||||
ret = read_sr(nor);
|
||||
@@ -875,11 +891,11 @@ static int spansion_quad_enable(struct s
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static int set_quad_mode(struct spi_nor *nor, u32 jedec_id)
|
||||
+static int set_quad_mode(struct spi_nor *nor, struct flash_info *info)
|
||||
{
|
||||
int status;
|
||||
|
||||
- switch (JEDEC_MFR(jedec_id)) {
|
||||
+ switch (JEDEC_MFR(info)) {
|
||||
case CFI_MFR_MACRONIX:
|
||||
status = macronix_quad_enable(nor);
|
||||
if (status) {
|
||||
@@ -905,11 +921,6 @@ static int spi_nor_check(struct spi_nor
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
- if (!nor->read_id)
|
||||
- nor->read_id = spi_nor_read_id;
|
||||
- if (!nor->wait_till_ready)
|
||||
- nor->wait_till_ready = spi_nor_wait_till_ready;
|
||||
-
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -927,16 +938,24 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- id = spi_nor_match_id(name);
|
||||
- if (!id)
|
||||
+ /* Try to auto-detect if chip name wasn't specified */
|
||||
+ if (!name)
|
||||
+ id = spi_nor_read_id(nor);
|
||||
+ else
|
||||
+ id = spi_nor_match_id(name);
|
||||
+ if (IS_ERR_OR_NULL(id))
|
||||
return -ENOENT;
|
||||
|
||||
info = (void *)id->driver_data;
|
||||
|
||||
- if (info->jedec_id) {
|
||||
+ /*
|
||||
+ * If caller has specified name of flash model that can normally be
|
||||
+ * detected using JEDEC, let's verify it.
|
||||
+ */
|
||||
+ if (name && info->id_len) {
|
||||
const struct spi_device_id *jid;
|
||||
|
||||
- jid = nor->read_id(nor);
|
||||
+ jid = spi_nor_read_id(nor);
|
||||
if (IS_ERR(jid)) {
|
||||
return PTR_ERR(jid);
|
||||
} else if (jid != id) {
|
||||
@@ -961,9 +980,9 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
* up with the software protection bits set
|
||||
*/
|
||||
|
||||
- if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ATMEL ||
|
||||
- JEDEC_MFR(info->jedec_id) == CFI_MFR_INTEL ||
|
||||
- JEDEC_MFR(info->jedec_id) == CFI_MFR_SST) {
|
||||
+ if (JEDEC_MFR(info) == CFI_MFR_ATMEL ||
|
||||
+ JEDEC_MFR(info) == CFI_MFR_INTEL ||
|
||||
+ JEDEC_MFR(info) == CFI_MFR_SST) {
|
||||
write_enable(nor);
|
||||
write_sr(nor, 0);
|
||||
}
|
||||
@@ -978,7 +997,7 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
mtd->_read = spi_nor_read;
|
||||
|
||||
/* nor protection support for STmicro chips */
|
||||
- if (JEDEC_MFR(info->jedec_id) == CFI_MFR_ST) {
|
||||
+ if (JEDEC_MFR(info) == CFI_MFR_ST) {
|
||||
mtd->_lock = spi_nor_lock;
|
||||
mtd->_unlock = spi_nor_unlock;
|
||||
}
|
||||
@@ -989,9 +1008,8 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
else
|
||||
mtd->_write = spi_nor_write;
|
||||
|
||||
- if ((info->flags & USE_FSR) &&
|
||||
- nor->wait_till_ready == spi_nor_wait_till_ready)
|
||||
- nor->wait_till_ready = spi_nor_wait_till_fsr_ready;
|
||||
+ if (info->flags & USE_FSR)
|
||||
+ nor->flags |= SNOR_F_USE_FSR;
|
||||
|
||||
#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
|
||||
/* prefer "small sector" erase if possible */
|
||||
@@ -1032,7 +1050,7 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
|
||||
/* Quad/Dual-read mode takes precedence over fast/normal */
|
||||
if (mode == SPI_NOR_QUAD && info->flags & SPI_NOR_QUAD_READ) {
|
||||
- ret = set_quad_mode(nor, info->jedec_id);
|
||||
+ ret = set_quad_mode(nor, info);
|
||||
if (ret) {
|
||||
dev_err(dev, "quad mode not supported\n");
|
||||
return ret;
|
||||
@@ -1068,7 +1086,7 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
else if (mtd->size > 0x1000000) {
|
||||
/* enable 4-byte addressing if the device exceeds 16MiB */
|
||||
nor->addr_width = 4;
|
||||
- if (JEDEC_MFR(info->jedec_id) == CFI_MFR_AMD) {
|
||||
+ if (JEDEC_MFR(info) == CFI_MFR_AMD) {
|
||||
/* Dedicated 4-byte command set */
|
||||
switch (nor->flash_read) {
|
||||
case SPI_NOR_QUAD:
|
||||
@@ -1089,7 +1107,7 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
nor->erase_opcode = SPINOR_OP_SE_4B;
|
||||
mtd->erasesize = info->sector_size;
|
||||
} else
|
||||
- set_4byte(nor, info->jedec_id, 1);
|
||||
+ set_4byte(nor, info, 1);
|
||||
} else {
|
||||
nor->addr_width = 3;
|
||||
}
|
||||
--- a/include/linux/mtd/spi-nor.h
|
||||
+++ b/include/linux/mtd/spi-nor.h
|
||||
@@ -116,6 +116,10 @@ enum spi_nor_ops {
|
||||
SPI_NOR_OPS_UNLOCK,
|
||||
};
|
||||
|
||||
+enum spi_nor_option_flags {
|
||||
+ SNOR_F_USE_FSR = BIT(0),
|
||||
+};
|
||||
+
|
||||
/**
|
||||
* struct spi_nor - Structure for defining a the SPI NOR layer
|
||||
* @mtd: point to a mtd_info structure
|
||||
@@ -129,6 +133,7 @@ enum spi_nor_ops {
|
||||
* @program_opcode: the program opcode
|
||||
* @flash_read: the mode of the read
|
||||
* @sst_write_second: used by the SST write operation
|
||||
+ * @flags: flag options for the current SPI-NOR (SNOR_F_*)
|
||||
* @cfg: used by the read_xfer/write_xfer
|
||||
* @cmd_buf: used by the write_reg
|
||||
* @prepare: [OPTIONAL] do some preparations for the
|
||||
@@ -139,9 +144,6 @@ enum spi_nor_ops {
|
||||
* @write_xfer: [OPTIONAL] the writefundamental primitive
|
||||
* @read_reg: [DRIVER-SPECIFIC] read out the register
|
||||
* @write_reg: [DRIVER-SPECIFIC] write data to the register
|
||||
- * @read_id: [REPLACEABLE] read out the ID data, and find
|
||||
- * the proper spi_device_id
|
||||
- * @wait_till_ready: [REPLACEABLE] wait till the NOR becomes ready
|
||||
* @read: [DRIVER-SPECIFIC] read data from the SPI NOR
|
||||
* @write: [DRIVER-SPECIFIC] write data to the SPI NOR
|
||||
* @erase: [DRIVER-SPECIFIC] erase a sector of the SPI NOR
|
||||
@@ -160,6 +162,7 @@ struct spi_nor {
|
||||
u8 program_opcode;
|
||||
enum read_mode flash_read;
|
||||
bool sst_write_second;
|
||||
+ u32 flags;
|
||||
struct spi_nor_xfer_cfg cfg;
|
||||
u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];
|
||||
|
||||
@@ -172,8 +175,6 @@ struct spi_nor {
|
||||
int (*read_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len);
|
||||
int (*write_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len,
|
||||
int write_enable);
|
||||
- const struct spi_device_id *(*read_id)(struct spi_nor *nor);
|
||||
- int (*wait_till_ready)(struct spi_nor *nor);
|
||||
|
||||
int (*read)(struct spi_nor *nor, loff_t from,
|
||||
size_t len, size_t *retlen, u_char *read_buf);
|
|
@ -1,46 +0,0 @@
|
|||
--- a/drivers/mtd/spi-nor/spi-nor.c
|
||||
+++ b/drivers/mtd/spi-nor/spi-nor.c
|
||||
@@ -931,29 +931,23 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
if (!id)
|
||||
return -ENOENT;
|
||||
|
||||
- info = (void *)id->driver_data;
|
||||
-
|
||||
- if (info->jedec_id) {
|
||||
- const struct spi_device_id *jid;
|
||||
-
|
||||
- jid = nor->read_id(nor);
|
||||
- if (IS_ERR(jid)) {
|
||||
- return PTR_ERR(jid);
|
||||
- } else if (jid != id) {
|
||||
- /*
|
||||
- * JEDEC knows better, so overwrite platform ID. We
|
||||
- * can't trust partitions any longer, but we'll let
|
||||
- * mtd apply them anyway, since some partitions may be
|
||||
- * marked read-only, and we don't want to lose that
|
||||
- * information, even if it's not 100% accurate.
|
||||
- */
|
||||
- dev_warn(dev, "found %s, expected %s\n",
|
||||
- jid->name, id->name);
|
||||
- id = jid;
|
||||
- info = (void *)jid->driver_data;
|
||||
+ if (id) {
|
||||
+ info = (void *)id->driver_data;
|
||||
+ if (info->jedec_id) {
|
||||
+ dev_warn(dev,
|
||||
+ "passed SPI device ID (%s) contains JEDEC, ignoring it, driver should be fixed!\n",
|
||||
+ id->name);
|
||||
+ id = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
+ if (!id) {
|
||||
+ id = nor->read_id(nor);
|
||||
+ if (IS_ERR(id))
|
||||
+ return PTR_ERR(id);
|
||||
+ }
|
||||
+ info = (void *)id->driver_data;
|
||||
+
|
||||
mutex_init(&nor->lock);
|
||||
|
||||
/*
|
|
@ -1,374 +0,0 @@
|
|||
--- a/drivers/mtd/spi-nor/fsl-quadspi.c
|
||||
+++ b/drivers/mtd/spi-nor/fsl-quadspi.c
|
||||
@@ -719,16 +719,10 @@ static int fsl_qspi_read(struct spi_nor
|
||||
{
|
||||
struct fsl_qspi *q = nor->priv;
|
||||
u8 cmd = nor->read_opcode;
|
||||
- int ret;
|
||||
|
||||
dev_dbg(q->dev, "cmd [%x],read from (0x%p, 0x%.8x, 0x%.8x),len:%d\n",
|
||||
cmd, q->ahb_base, q->chip_base_addr, (unsigned int)from, len);
|
||||
|
||||
- /* Wait until the previous command is finished. */
|
||||
- ret = nor->wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
-
|
||||
/* Read out the data directly from the AHB buffer.*/
|
||||
memcpy(buf, q->ahb_base + q->chip_base_addr + from, len);
|
||||
|
||||
@@ -744,16 +738,6 @@ static int fsl_qspi_erase(struct spi_nor
|
||||
dev_dbg(nor->dev, "%dKiB at 0x%08x:0x%08x\n",
|
||||
nor->mtd->erasesize / 1024, q->chip_base_addr, (u32)offs);
|
||||
|
||||
- /* Wait until finished previous write command. */
|
||||
- ret = nor->wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
-
|
||||
- /* Send write enable, then erase commands. */
|
||||
- ret = nor->write_reg(nor, SPINOR_OP_WREN, NULL, 0, 0);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
-
|
||||
ret = fsl_qspi_runcmd(q, nor->erase_opcode, offs, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
--- a/drivers/mtd/spi-nor/spi-nor.c
|
||||
+++ b/drivers/mtd/spi-nor/spi-nor.c
|
||||
@@ -165,81 +165,69 @@ static inline int set_4byte(struct spi_n
|
||||
return nor->write_reg(nor, SPINOR_OP_BRWR, nor->cmd_buf, 1, 0);
|
||||
}
|
||||
}
|
||||
-
|
||||
-static int spi_nor_wait_till_ready(struct spi_nor *nor)
|
||||
+static inline int spi_nor_sr_ready(struct spi_nor *nor)
|
||||
{
|
||||
- unsigned long deadline;
|
||||
- int sr;
|
||||
-
|
||||
- deadline = jiffies + MAX_READY_WAIT_JIFFIES;
|
||||
-
|
||||
- do {
|
||||
- cond_resched();
|
||||
+ int sr = read_sr(nor);
|
||||
+ if (sr < 0)
|
||||
+ return sr;
|
||||
+ else
|
||||
+ return !(sr & SR_WIP);
|
||||
+}
|
||||
|
||||
- sr = read_sr(nor);
|
||||
- if (sr < 0)
|
||||
- break;
|
||||
- else if (!(sr & SR_WIP))
|
||||
- return 0;
|
||||
- } while (!time_after_eq(jiffies, deadline));
|
||||
+static inline int spi_nor_fsr_ready(struct spi_nor *nor)
|
||||
+{
|
||||
+ int fsr = read_fsr(nor);
|
||||
+ if (fsr < 0)
|
||||
+ return fsr;
|
||||
+ else
|
||||
+ return fsr & FSR_READY;
|
||||
+}
|
||||
|
||||
- return -ETIMEDOUT;
|
||||
+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 sr;
|
||||
+ return sr && fsr;
|
||||
}
|
||||
|
||||
-static int spi_nor_wait_till_fsr_ready(struct spi_nor *nor)
|
||||
+/*
|
||||
+ * Service routine to read status register until ready, or timeout occurs.
|
||||
+ * Returns non-zero if error.
|
||||
+ */
|
||||
+static int spi_nor_wait_till_ready(struct spi_nor *nor)
|
||||
{
|
||||
unsigned long deadline;
|
||||
- int sr;
|
||||
- int fsr;
|
||||
+ int ret;
|
||||
|
||||
deadline = jiffies + MAX_READY_WAIT_JIFFIES;
|
||||
|
||||
do {
|
||||
cond_resched();
|
||||
|
||||
- sr = read_sr(nor);
|
||||
- if (sr < 0) {
|
||||
- break;
|
||||
- } else if (!(sr & SR_WIP)) {
|
||||
- fsr = read_fsr(nor);
|
||||
- if (fsr < 0)
|
||||
- break;
|
||||
- if (fsr & FSR_READY)
|
||||
- return 0;
|
||||
- }
|
||||
+ ret = spi_nor_ready(nor);
|
||||
+ if (ret < 0)
|
||||
+ return ret;
|
||||
+ if (ret)
|
||||
+ return 0;
|
||||
} while (!time_after_eq(jiffies, deadline));
|
||||
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
/*
|
||||
- * Service routine to read status register until ready, or timeout occurs.
|
||||
- * Returns non-zero if error.
|
||||
- */
|
||||
-static int wait_till_ready(struct spi_nor *nor)
|
||||
-{
|
||||
- return nor->wait_till_ready(nor);
|
||||
-}
|
||||
-
|
||||
-/*
|
||||
* Erase the whole flash memory
|
||||
*
|
||||
* Returns 0 if successful, non-zero otherwise.
|
||||
*/
|
||||
static int erase_chip(struct spi_nor *nor)
|
||||
{
|
||||
- int ret;
|
||||
-
|
||||
dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd->size >> 10));
|
||||
|
||||
- /* Wait until finished previous write command. */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- return ret;
|
||||
-
|
||||
- /* Send write enable, then erase commands. */
|
||||
- write_enable(nor);
|
||||
-
|
||||
return nor->write_reg(nor, SPINOR_OP_CHIP_ERASE, NULL, 0, 0);
|
||||
}
|
||||
|
||||
@@ -292,6 +280,8 @@ static int spi_nor_erase(struct mtd_info
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
+ write_enable(nor);
|
||||
+
|
||||
/* whole-chip erase? */
|
||||
if (len == mtd->size) {
|
||||
if (erase_chip(nor)) {
|
||||
@@ -299,6 +289,10 @@ static int spi_nor_erase(struct mtd_info
|
||||
goto erase_err;
|
||||
}
|
||||
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
+ if (ret)
|
||||
+ goto erase_err;
|
||||
+
|
||||
/* REVISIT in some cases we could speed up erasing large regions
|
||||
* by using SPINOR_OP_SE instead of SPINOR_OP_BE_4K. We may have set up
|
||||
* to use "small sector erase", but that's not always optimal.
|
||||
@@ -314,9 +308,15 @@ static int spi_nor_erase(struct mtd_info
|
||||
|
||||
addr += mtd->erasesize;
|
||||
len -= mtd->erasesize;
|
||||
+
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
+ if (ret)
|
||||
+ goto erase_err;
|
||||
}
|
||||
}
|
||||
|
||||
+ write_disable(nor);
|
||||
+
|
||||
spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE);
|
||||
|
||||
instr->state = MTD_ERASE_DONE;
|
||||
@@ -341,11 +341,6 @@ static int spi_nor_lock(struct mtd_info
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous command */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto err;
|
||||
-
|
||||
status_old = read_sr(nor);
|
||||
|
||||
if (offset < mtd->size - (mtd->size / 2))
|
||||
@@ -388,11 +383,6 @@ static int spi_nor_unlock(struct mtd_inf
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous command */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto err;
|
||||
-
|
||||
status_old = read_sr(nor);
|
||||
|
||||
if (offset+len > mtd->size - (mtd->size / 64))
|
||||
@@ -704,11 +694,6 @@ static int sst_write(struct mtd_info *mt
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous write command. */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto time_out;
|
||||
-
|
||||
write_enable(nor);
|
||||
|
||||
nor->sst_write_second = false;
|
||||
@@ -720,7 +705,7 @@ static int sst_write(struct mtd_info *mt
|
||||
|
||||
/* write one byte. */
|
||||
nor->write(nor, to, 1, retlen, buf);
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
}
|
||||
@@ -732,7 +717,7 @@ static int sst_write(struct mtd_info *mt
|
||||
|
||||
/* write two bytes. */
|
||||
nor->write(nor, to, 2, retlen, buf + actual);
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
to += 2;
|
||||
@@ -741,7 +726,7 @@ static int sst_write(struct mtd_info *mt
|
||||
nor->sst_write_second = false;
|
||||
|
||||
write_disable(nor);
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
|
||||
@@ -752,7 +737,7 @@ static int sst_write(struct mtd_info *mt
|
||||
nor->program_opcode = SPINOR_OP_BP;
|
||||
nor->write(nor, to, 1, retlen, buf + actual);
|
||||
|
||||
- ret = wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto time_out;
|
||||
write_disable(nor);
|
||||
@@ -780,11 +765,6 @@ static int spi_nor_write(struct mtd_info
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
- /* Wait until finished previous write command. */
|
||||
- ret = wait_till_ready(nor);
|
||||
- if (ret)
|
||||
- goto write_err;
|
||||
-
|
||||
write_enable(nor);
|
||||
|
||||
page_offset = to & (nor->page_size - 1);
|
||||
@@ -803,16 +783,20 @@ static int spi_nor_write(struct mtd_info
|
||||
if (page_size > nor->page_size)
|
||||
page_size = nor->page_size;
|
||||
|
||||
- wait_till_ready(nor);
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
+ if (ret)
|
||||
+ goto write_err;
|
||||
+
|
||||
write_enable(nor);
|
||||
|
||||
nor->write(nor, to + i, page_size, retlen, buf + i);
|
||||
}
|
||||
}
|
||||
|
||||
+ ret = spi_nor_wait_till_ready(nor);
|
||||
write_err:
|
||||
spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE);
|
||||
- return 0;
|
||||
+ return ret;
|
||||
}
|
||||
|
||||
static int macronix_quad_enable(struct spi_nor *nor)
|
||||
@@ -825,7 +809,7 @@ static int macronix_quad_enable(struct s
|
||||
nor->cmd_buf[0] = val | SR_QUAD_EN_MX;
|
||||
nor->write_reg(nor, SPINOR_OP_WRSR, nor->cmd_buf, 1, 0);
|
||||
|
||||
- if (wait_till_ready(nor))
|
||||
+ if (spi_nor_wait_till_ready(nor))
|
||||
return 1;
|
||||
|
||||
ret = read_sr(nor);
|
||||
@@ -907,8 +891,6 @@ static int spi_nor_check(struct spi_nor
|
||||
|
||||
if (!nor->read_id)
|
||||
nor->read_id = spi_nor_read_id;
|
||||
- if (!nor->wait_till_ready)
|
||||
- nor->wait_till_ready = spi_nor_wait_till_ready;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -983,9 +965,8 @@ int spi_nor_scan(struct spi_nor *nor, co
|
||||
else
|
||||
mtd->_write = spi_nor_write;
|
||||
|
||||
- if ((info->flags & USE_FSR) &&
|
||||
- nor->wait_till_ready == spi_nor_wait_till_ready)
|
||||
- nor->wait_till_ready = spi_nor_wait_till_fsr_ready;
|
||||
+ if (info->flags & USE_FSR)
|
||||
+ nor->flags |= SNOR_F_USE_FSR;
|
||||
|
||||
#ifdef CONFIG_MTD_SPI_NOR_USE_4K_SECTORS
|
||||
/* prefer "small sector" erase if possible */
|
||||
--- a/include/linux/mtd/spi-nor.h
|
||||
+++ b/include/linux/mtd/spi-nor.h
|
||||
@@ -116,6 +116,10 @@ enum spi_nor_ops {
|
||||
SPI_NOR_OPS_UNLOCK,
|
||||
};
|
||||
|
||||
+enum spi_nor_option_flags {
|
||||
+ SNOR_F_USE_FSR = BIT(0),
|
||||
+};
|
||||
+
|
||||
/**
|
||||
* struct spi_nor - Structure for defining a the SPI NOR layer
|
||||
* @mtd: point to a mtd_info structure
|
||||
@@ -129,6 +133,7 @@ enum spi_nor_ops {
|
||||
* @program_opcode: the program opcode
|
||||
* @flash_read: the mode of the read
|
||||
* @sst_write_second: used by the SST write operation
|
||||
+ * @flags: flag options for the current SPI-NOR (SNOR_F_*)
|
||||
* @cfg: used by the read_xfer/write_xfer
|
||||
* @cmd_buf: used by the write_reg
|
||||
* @prepare: [OPTIONAL] do some preparations for the
|
||||
@@ -141,7 +146,6 @@ enum spi_nor_ops {
|
||||
* @write_reg: [DRIVER-SPECIFIC] write data to the register
|
||||
* @read_id: [REPLACEABLE] read out the ID data, and find
|
||||
* the proper spi_device_id
|
||||
- * @wait_till_ready: [REPLACEABLE] wait till the NOR becomes ready
|
||||
* @read: [DRIVER-SPECIFIC] read data from the SPI NOR
|
||||
* @write: [DRIVER-SPECIFIC] write data to the SPI NOR
|
||||
* @erase: [DRIVER-SPECIFIC] erase a sector of the SPI NOR
|
||||
@@ -160,6 +164,7 @@ struct spi_nor {
|
||||
u8 program_opcode;
|
||||
enum read_mode flash_read;
|
||||
bool sst_write_second;
|
||||
+ u32 flags;
|
||||
struct spi_nor_xfer_cfg cfg;
|
||||
u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];
|
||||
|
||||
@@ -173,7 +178,6 @@ struct spi_nor {
|
||||
int (*write_reg)(struct spi_nor *nor, u8 opcode, u8 *buf, int len,
|
||||
int write_enable);
|
||||
const struct spi_device_id *(*read_id)(struct spi_nor *nor);
|
||||
- int (*wait_till_ready)(struct spi_nor *nor);
|
||||
|
||||
int (*read)(struct spi_nor *nor, loff_t from,
|
||||
size_t len, size_t *retlen, u_char *read_buf);
|
|
@ -17,247 +17,3 @@
|
|||
obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o
|
||||
obj-$(CONFIG_SPI_FSL_QUADSPI) += fsl-quadspi.o
|
||||
+obj-$(CONFIG_MTD_SPI_BCM53XXSPIFLASH) += bcm53xxspiflash.o
|
||||
--- /dev/null
|
||||
+++ b/drivers/mtd/spi-nor/bcm53xxspiflash.c
|
||||
@@ -0,0 +1,241 @@
|
||||
+#include <linux/module.h>
|
||||
+#include <linux/delay.h>
|
||||
+#include <linux/spi/spi.h>
|
||||
+#include <linux/mtd/spi-nor.h>
|
||||
+#include <linux/mtd/mtd.h>
|
||||
+#include <linux/mtd/cfi.h>
|
||||
+
|
||||
+static const char * const probes[] = { "bcm47xxpart", NULL };
|
||||
+
|
||||
+struct bcm53xxsf {
|
||||
+ struct spi_device *spi;
|
||||
+ struct mtd_info mtd;
|
||||
+ struct spi_nor nor;
|
||||
+};
|
||||
+
|
||||
+/**************************************************
|
||||
+ * spi-nor API
|
||||
+ **************************************************/
|
||||
+
|
||||
+static int bcm53xxspiflash_read_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
|
||||
+ int len)
|
||||
+{
|
||||
+ struct bcm53xxsf *b53sf = nor->priv;
|
||||
+
|
||||
+ return spi_write_then_read(b53sf->spi, &opcode, 1, buf, len);
|
||||
+}
|
||||
+
|
||||
+static int bcm53xxspiflash_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf,
|
||||
+ int len, int write_enable)
|
||||
+{
|
||||
+ struct bcm53xxsf *b53sf = nor->priv;
|
||||
+ u8 *cmd = kzalloc(len + 1, GFP_KERNEL);
|
||||
+ int err;
|
||||
+
|
||||
+ if (!cmd)
|
||||
+ return -ENOMEM;
|
||||
+
|
||||
+ cmd[0] = opcode;
|
||||
+ memcpy(&cmd[1], buf, len);
|
||||
+ err = spi_write(b53sf->spi, cmd, len + 1);
|
||||
+
|
||||
+ kfree(cmd);
|
||||
+
|
||||
+ return err;
|
||||
+}
|
||||
+
|
||||
+static int bcm53xxspiflash_read(struct spi_nor *nor, loff_t from, size_t len,
|
||||
+ size_t *retlen, u_char *buf)
|
||||
+{
|
||||
+ struct bcm53xxsf *b53sf = nor->priv;
|
||||
+ struct spi_message m;
|
||||
+ struct spi_transfer t[2] = { { 0 }, { 0 } };
|
||||
+ unsigned char cmd[5];
|
||||
+ int cmd_len = 0;
|
||||
+ int err;
|
||||
+
|
||||
+ spi_message_init(&m);
|
||||
+
|
||||
+ cmd[cmd_len++] = SPINOR_OP_READ;
|
||||
+ if (b53sf->mtd.size > 0x1000000)
|
||||
+ cmd[cmd_len++] = (from & 0xFF000000) >> 24;
|
||||
+ cmd[cmd_len++] = (from & 0x00FF0000) >> 16;
|
||||
+ cmd[cmd_len++] = (from & 0x0000FF00) >> 8;
|
||||
+ cmd[cmd_len++] = (from & 0x000000FF) >> 0;
|
||||
+
|
||||
+ t[0].tx_buf = cmd;
|
||||
+ t[0].len = cmd_len;
|
||||
+ spi_message_add_tail(&t[0], &m);
|
||||
+
|
||||
+ t[1].rx_buf = buf;
|
||||
+ t[1].len = len;
|
||||
+ spi_message_add_tail(&t[1], &m);
|
||||
+
|
||||
+ err = spi_sync(b53sf->spi, &m);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ if (retlen && m.actual_length > cmd_len)
|
||||
+ *retlen = m.actual_length - cmd_len;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static void bcm53xxspiflash_write(struct spi_nor *nor, loff_t to, size_t len,
|
||||
+ size_t *retlen, const u_char *buf)
|
||||
+{
|
||||
+ struct bcm53xxsf *b53sf = nor->priv;
|
||||
+ struct spi_message m;
|
||||
+ struct spi_transfer t = { 0 };
|
||||
+ u8 *cmd = kzalloc(len + 5, GFP_KERNEL);
|
||||
+ int cmd_len = 0;
|
||||
+ int err;
|
||||
+
|
||||
+ if (!cmd)
|
||||
+ return;
|
||||
+
|
||||
+ spi_message_init(&m);
|
||||
+
|
||||
+ cmd[cmd_len++] = nor->program_opcode;
|
||||
+ if (b53sf->mtd.size > 0x1000000)
|
||||
+ cmd[cmd_len++] = (to & 0xFF000000) >> 24;
|
||||
+ cmd[cmd_len++] = (to & 0x00FF0000) >> 16;
|
||||
+ cmd[cmd_len++] = (to & 0x0000FF00) >> 8;
|
||||
+ cmd[cmd_len++] = (to & 0x000000FF) >> 0;
|
||||
+ memcpy(&cmd[cmd_len], buf, len);
|
||||
+
|
||||
+ t.tx_buf = cmd;
|
||||
+ t.len = cmd_len + len;
|
||||
+ spi_message_add_tail(&t, &m);
|
||||
+
|
||||
+ err = spi_sync(b53sf->spi, &m);
|
||||
+ if (err)
|
||||
+ goto out;
|
||||
+
|
||||
+ if (retlen && m.actual_length > cmd_len)
|
||||
+ *retlen += m.actual_length - cmd_len;
|
||||
+
|
||||
+out:
|
||||
+ kfree(cmd);
|
||||
+}
|
||||
+
|
||||
+static int bcm53xxspiflash_erase(struct spi_nor *nor, loff_t offs)
|
||||
+{
|
||||
+ struct bcm53xxsf *b53sf = nor->priv;
|
||||
+ unsigned char cmd[5];
|
||||
+ int i;
|
||||
+
|
||||
+ i = 0;
|
||||
+ cmd[i++] = nor->erase_opcode;
|
||||
+ if (b53sf->mtd.size > 0x1000000)
|
||||
+ cmd[i++] = (offs & 0xFF000000) >> 24;
|
||||
+ cmd[i++] = ((offs & 0x00FF0000) >> 16);
|
||||
+ cmd[i++] = ((offs & 0x0000FF00) >> 8);
|
||||
+ cmd[i++] = ((offs & 0x000000FF) >> 0);
|
||||
+
|
||||
+ return spi_write(b53sf->spi, cmd, i);
|
||||
+}
|
||||
+
|
||||
+static const struct spi_device_id *bcm53xxspiflash_read_id(struct spi_nor *nor)
|
||||
+{
|
||||
+ struct bcm53xxsf *b53sf = nor->priv;
|
||||
+ struct device *dev = &b53sf->spi->dev;
|
||||
+ const struct spi_device_id *id;
|
||||
+ unsigned char cmd[4];
|
||||
+ unsigned char resp[2];
|
||||
+ char *name = NULL;
|
||||
+ int err;
|
||||
+
|
||||
+ /* SST and Winbond/NexFlash specific command */
|
||||
+ cmd[0] = 0x90; /* Read Manufacturer / Device ID */
|
||||
+ cmd[1] = 0;
|
||||
+ cmd[2] = 0;
|
||||
+ cmd[3] = 0;
|
||||
+ err = spi_write_then_read(b53sf->spi, cmd, 4, resp, 2);
|
||||
+ if (err < 0) {
|
||||
+ dev_err(dev, "error reading SPI flash id\n");
|
||||
+ return ERR_PTR(-EBUSY);
|
||||
+ }
|
||||
+ switch (resp[0]) {
|
||||
+ case 0xef: /* Winbond/NexFlash */
|
||||
+ switch (resp[1]) {
|
||||
+ case 0x17:
|
||||
+ name = "w25q128";
|
||||
+ break;
|
||||
+ }
|
||||
+ if (!name) {
|
||||
+ dev_err(dev, "Unknown Winbond/NexFlash flash: %02X %02X\n",
|
||||
+ resp[0], resp[1]);
|
||||
+ return ERR_PTR(-ENOTSUPP);
|
||||
+ }
|
||||
+ goto found_name;
|
||||
+ }
|
||||
+
|
||||
+ /* TODO: Try more ID commands */
|
||||
+
|
||||
+ return ERR_PTR(-ENODEV);
|
||||
+
|
||||
+found_name:
|
||||
+// id = spi_nor_match_id(name);
|
||||
+// if (!id) {
|
||||
+// dev_err(dev, "No matching entry for %s flash\n", name);
|
||||
+// return ERR_PTR(-ENOENT);
|
||||
+// }
|
||||
+
|
||||
+ return id;
|
||||
+}
|
||||
+
|
||||
+/**************************************************
|
||||
+ * SPI driver
|
||||
+ **************************************************/
|
||||
+
|
||||
+static int bcm53xxspiflash_probe(struct spi_device *spi)
|
||||
+{
|
||||
+ struct bcm53xxsf *b53sf;
|
||||
+ int err;
|
||||
+
|
||||
+ b53sf = devm_kzalloc(&spi->dev, sizeof(*b53sf), GFP_KERNEL);
|
||||
+ if (!b53sf)
|
||||
+ return -ENOMEM;
|
||||
+ spi_set_drvdata(spi, b53sf);
|
||||
+
|
||||
+ b53sf->spi = spi;
|
||||
+
|
||||
+ b53sf->mtd.priv = &b53sf->nor;
|
||||
+
|
||||
+ b53sf->nor.mtd = &b53sf->mtd;
|
||||
+ b53sf->nor.dev = &spi->dev;
|
||||
+ b53sf->nor.read_reg = bcm53xxspiflash_read_reg;
|
||||
+ b53sf->nor.write_reg = bcm53xxspiflash_write_reg;
|
||||
+ b53sf->nor.read = bcm53xxspiflash_read;
|
||||
+ b53sf->nor.write = bcm53xxspiflash_write;
|
||||
+ b53sf->nor.erase = bcm53xxspiflash_erase;
|
||||
+ b53sf->nor.read_id = bcm53xxspiflash_read_id;
|
||||
+ b53sf->nor.priv = b53sf;
|
||||
+
|
||||
+ err = spi_nor_scan(&b53sf->nor, "w25q128", SPI_NOR_NORMAL);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ err = mtd_device_parse_register(&b53sf->mtd, probes, NULL, NULL, 0);
|
||||
+ if (err)
|
||||
+ return err;
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static int bcm53xxspiflash_remove(struct spi_device *spi)
|
||||
+{
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
+static struct spi_driver bcm53xxspiflash_driver = {
|
||||
+ .driver = {
|
||||
+ .name = "bcm53xxspiflash",
|
||||
+ .owner = THIS_MODULE,
|
||||
+ },
|
||||
+ .probe = bcm53xxspiflash_probe,
|
||||
+ .remove = bcm53xxspiflash_remove,
|
||||
+};
|
||||
+
|
||||
+module_spi_driver(bcm53xxspiflash_driver);
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
--- a/drivers/mtd/spi-nor/bcm53xxspiflash.c
|
||||
+++ b/drivers/mtd/spi-nor/bcm53xxspiflash.c
|
||||
@@ -173,7 +173,8 @@ static const struct spi_device_id *bcm53
|
||||
|
||||
/* TODO: Try more ID commands */
|
||||
|
||||
- return ERR_PTR(-ENODEV);
|
||||
+ /* Some chips used by Broadcom may actually support JEDEC */
|
||||
+ return spi_nor_read_id(nor);
|
||||
|
||||
found_name:
|
||||
// id = spi_nor_match_id(name);
|
||||
--- a/drivers/mtd/spi-nor/spi-nor.c
|
||||
+++ b/drivers/mtd/spi-nor/spi-nor.c
|
||||
@@ -631,7 +631,7 @@ static const struct spi_device_id spi_no
|
||||
{ },
|
||||
};
|
||||
|
||||
-static const struct spi_device_id *spi_nor_read_id(struct spi_nor *nor)
|
||||
+const struct spi_device_id *spi_nor_read_id(struct spi_nor *nor)
|
||||
{
|
||||
int tmp;
|
||||
u8 id[5];
|
||||
@@ -662,6 +662,7 @@ static const struct spi_device_id *spi_n
|
||||
dev_err(nor->dev, "unrecognized JEDEC id %06x\n", jedec);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
+EXPORT_SYMBOL_GPL(spi_nor_read_id);
|
||||
|
||||
static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
--- a/include/linux/mtd/spi-nor.h
|
||||
+++ b/include/linux/mtd/spi-nor.h
|
||||
@@ -188,6 +188,8 @@ struct spi_nor {
|
||||
void *priv;
|
||||
};
|
||||
|
||||
+const struct spi_device_id *spi_nor_read_id(struct spi_nor *nor);
|
||||
+
|
||||
/**
|
||||
* spi_nor_scan() - scan the SPI NOR
|
||||
* @nor: the spi_nor structure
|
Loading…
Reference in a new issue