This pull-request contains the following changes for MTD:
MTD core changes: - add debugfs nodes for querying the flash name and id - mtd parser reorganization SPI NOR core changes: - always use bounce buffer for register read/writes - move m25p80 code in spi-nor.c - rework hwcaps selection for the spi-mem case - rework the core in order to move the manufacturer specific code out of it: - regroup flash parameters in 'struct spi_nor_flash_parameter' - add default_init() and post_sfdp() hooks to tweak the flash parameters - introduce the ->set_4byte(), ->convert_addr() and ->setup() methods, to deal with manufacturer specific code - rework the SPI NOR lock/unlock logic - fix an error code in spi_nor_read_raw() - fix a memory leak bug - enable the debugfs for the partname and partid - add support for few flashes SPI NOR controller drivers changes: - intel-spi: - Whitelist 4B read commands - Add support for Intel Tiger Lake SPI serial flash - aspeed-smc: Add of_node_put() - hisi-sfc: Add of_node_put() - cadence-quadspi: Fix QSPI RCU Schedule Stall NAND core: - Fixing typos - Adding missing of_node_put() in various drivers Raw NAND controller drivers: - Macronix: new controller driver - Omap2: Fixing the number of bitflips returned - Brcmnand: Fix a pointer not iterating over all the page chunks - W90x900: Driver removed - Onenand: Fix a memory leak - Sharpsl: Missing include guard - STM32: Avoid warnings when building with W=1 - Ingenic: Fix a coccinelle warning - r852: Call a helper to simplify the code CFI core: - Kill useless initializer in mtd_do_chip_probe() - Fix a rare write failure seen on some cfi_cmdset_0002 compliant Parallel NORs - Bunch of cleanups for cfi_cmdset_0002 driver's write functions by Tokunori Ikegami <ikegami.t@gmail.com> -----BEGIN PGP SIGNATURE----- iQJKBAABCAA0FiEEdgfidid8lnn52cLTZvlZhesYu8EFAl2FyfUWHHJpY2hhcmRA c2lnbWEtc3Rhci5hdAAKCRBm+VmF6xi7wQ+2D/9CzeK9eK27kwC5P1JUDyAVCqQL 09QDJ5/NF9Enrs7wtiLfZmadqo1SOFn98HVy9KCI9g0UkW9YzZjpAH3FfJCgYeaA t4wcDx6IXH8sKoqihyKnGN2IBPXCHzW6W81OUUmArpQ5ShqgJROQ9oo4tGukzDpw vnEShdHsrCe65/jqRTghl17i+AfzsD/IEjJLp/unBLsxa4sJqOjE7x3oizBtNi9H icBdJP1LocVEUDIxSFRgUgftWTW4xhIEmlS/BSBcX8dQ0cs0AeQ0Nr0gMw+sLzyq FFFwaRY7nedib7Q551HEzvd5YrWNc3/J//Qc+/aJMG8aE0sBSXx3fqpKHr9SPZRl 7lYeT2lbvT3V1m2HVPIfJAR2rX9D5kTOWb+5TLkdVW3DKxIO+nsEIjc1koZYLu/A KM0KoqdVny+TsFOnDA3PcEzF+wiqxq/+TYj5EP8f8RfeGdQD1evbA3wwDVcPexQ6 B0WEIrgAA4DWTcD34X1Fp8EOclRMgreQIb3/+Mi/MAUbIwmMPdHKJxW9dYWyC9cd /vCjJ+nvRg9OjFrox3E52JCMN8CeaCEXMwtzJDgBslSGLCkyg4EqetjVr7nkp1qE 6u3EM+tzA18h9ltG3s2SNHK9kaQCW1kGbx58Mkh9N8he6Aa7shVH9O1sZnNF4xmz gR43ERplKI/KGARTrQ== =fMNy -----END PGP SIGNATURE----- Merge tag 'mtd/for-5.4' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux Pull MTD updates from Richard Weinberger: "MTD core changes: - add debugfs nodes for querying the flash name and id - mtd parser reorganization SPI NOR core changes: - always use bounce buffer for register read/writes - move m25p80 code in spi-nor.c - rework hwcaps selection for the spi-mem case - rework the core in order to move the manufacturer specific code out of it: - regroup flash parameters in 'struct spi_nor_flash_parameter' - add default_init() and post_sfdp() hooks to tweak the flash parameters - introduce the ->set_4byte(), ->convert_addr() and ->setup() methods, to deal with manufacturer specific code - rework the SPI NOR lock/unlock logic - fix an error code in spi_nor_read_raw() - fix a memory leak bug - enable the debugfs for the partname and partid - add support for few flashes SPI NOR controller drivers changes: - intel-spi: - Whitelist 4B read commands - Add support for Intel Tiger Lake SPI serial flash - aspeed-smc: Add of_node_put() - hisi-sfc: add of_node_put() - cadence-quadspi: Fix QSPI RCU Schedule Stall NAND core: - Fixing typos - Adding missing of_node_put() in various drivers Raw NAND controller drivers: - Macronix: new controller driver - Omap2: fix the number of bitflips returned - Brcmnand: fix a pointer not iterating over all the page chunks - W90x900: driver removed - Onenand: fix a memory leak - Sharpsl: missing include guard - STM32: avoid warnings when building with W=1 - Ingenic: fix a coccinelle warning - r852: call a helper to simplify the code CFI core: - Kill useless initializer in mtd_do_chip_probe() - Fix a rare write failure seen on some cfi_cmdset_0002 compliant Parallel NORs - Bunch of cleanups for cfi_cmdset_0002 driver's write functions by Tokunori Ikegami" * tag 'mtd/for-5.4' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux: (77 commits) mtd: pmc551: Remove set but not used variable 'soff_lo' mtd: cfi_cmdset_0002: Fix do_erase_chip() to get chip as erasing mode mtd: sm_ftl: Fix memory leak in sm_init_zone() error path mtd: parsers: Move CMDLINE parser mtd: parsers: Move OF parser mtd: parsers: Move BCM63xx parser mtd: parsers: Move BCM47xx parser mtd: parsers: Move TI AR7 parser mtd: pismo: Simplify getting the adapter of a client mtd: phram: Module parameters add writable permissions mtd: pxa2xx: Use ioremap_cache insted of ioremap_cached mtd: spi-nor: Rename "n25q512a" to "mt25qu512a (n25q512a)" mtd: spi-nor: Add support for mt35xu02g mtd: rawnand: omap2: Fix number of bitflips reporting with ELM mtd: rawnand: brcmnand: Fix ecc chunk calculation for erased page bitfips mtd: spi-nor: remove superfluous pass of nor->info->sector_size mtd: spi-nor: enable the debugfs for the partname and partid mtd: mtdcore: add debugfs nodes for querying the flash name and id mtd: spi-nor: hisi-sfc: Add of_node_put() before break mtd: spi-nor: aspeed-smc: Add of_node_put() ...
This commit is contained in:
commit
4553d469d6
|
@ -0,0 +1,36 @@
|
||||||
|
Macronix Raw NAND Controller Device Tree Bindings
|
||||||
|
-------------------------------------------------
|
||||||
|
|
||||||
|
Required properties:
|
||||||
|
- compatible: should be "mxic,multi-itfc-v009-nand-controller"
|
||||||
|
- reg: should contain 1 entry for the registers
|
||||||
|
- #address-cells: should be set to 1
|
||||||
|
- #size-cells: should be set to 0
|
||||||
|
- interrupts: interrupt line connected to this raw NAND controller
|
||||||
|
- clock-names: should contain "ps", "send" and "send_dly"
|
||||||
|
- clocks: should contain 3 phandles for the "ps", "send" and
|
||||||
|
"send_dly" clocks
|
||||||
|
|
||||||
|
Children nodes:
|
||||||
|
- children nodes represent the available NAND chips.
|
||||||
|
|
||||||
|
See Documentation/devicetree/bindings/mtd/nand-controller.yaml
|
||||||
|
for more details on generic bindings.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
nand: nand-controller@43c30000 {
|
||||||
|
compatible = "mxic,multi-itfc-v009-nand-controller";
|
||||||
|
reg = <0x43c30000 0x10000>;
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <0>;
|
||||||
|
interrupts = <GIC_SPI 0x1d IRQ_TYPE_EDGE_RISING>;
|
||||||
|
clocks = <&clkwizard 0>, <&clkwizard 1>, <&clkc 15>;
|
||||||
|
clock-names = "send", "send_dly", "ps";
|
||||||
|
|
||||||
|
nand@0 {
|
||||||
|
reg = <0>;
|
||||||
|
nand-ecc-mode = "soft";
|
||||||
|
nand-ecc-algo = "bch";
|
||||||
|
};
|
||||||
|
};
|
|
@ -23,73 +23,6 @@ config MTD_TESTS
|
||||||
WARNING: some of the tests will ERASE entire MTD device which they
|
WARNING: some of the tests will ERASE entire MTD device which they
|
||||||
test. Do not use these tests unless you really know what you do.
|
test. Do not use these tests unless you really know what you do.
|
||||||
|
|
||||||
config MTD_CMDLINE_PARTS
|
|
||||||
tristate "Command line partition table parsing"
|
|
||||||
depends on MTD
|
|
||||||
help
|
|
||||||
Allow generic configuration of the MTD partition tables via the kernel
|
|
||||||
command line. Multiple flash resources are supported for hardware where
|
|
||||||
different kinds of flash memory are available.
|
|
||||||
|
|
||||||
You will still need the parsing functions to be called by the driver
|
|
||||||
for your particular device. It won't happen automatically. The
|
|
||||||
SA1100 map driver (CONFIG_MTD_SA1100) has an option for this, for
|
|
||||||
example.
|
|
||||||
|
|
||||||
The format for the command line is as follows:
|
|
||||||
|
|
||||||
mtdparts=<mtddef>[;<mtddef]
|
|
||||||
<mtddef> := <mtd-id>:<partdef>[,<partdef>]
|
|
||||||
<partdef> := <size>[@offset][<name>][ro]
|
|
||||||
<mtd-id> := unique id used in mapping driver/device
|
|
||||||
<size> := standard linux memsize OR "-" to denote all
|
|
||||||
remaining space
|
|
||||||
<name> := (NAME)
|
|
||||||
|
|
||||||
Due to the way Linux handles the command line, no spaces are
|
|
||||||
allowed in the partition definition, including mtd id's and partition
|
|
||||||
names.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
1 flash resource (mtd-id "sa1100"), with 1 single writable partition:
|
|
||||||
mtdparts=sa1100:-
|
|
||||||
|
|
||||||
Same flash, but 2 named partitions, the first one being read-only:
|
|
||||||
mtdparts=sa1100:256k(ARMboot)ro,-(root)
|
|
||||||
|
|
||||||
If unsure, say 'N'.
|
|
||||||
|
|
||||||
config MTD_OF_PARTS
|
|
||||||
tristate "OpenFirmware partitioning information support"
|
|
||||||
default y
|
|
||||||
depends on OF
|
|
||||||
help
|
|
||||||
This provides a partition parsing function which derives
|
|
||||||
the partition map from the children of the flash node,
|
|
||||||
as described in Documentation/devicetree/bindings/mtd/partition.txt.
|
|
||||||
|
|
||||||
config MTD_AR7_PARTS
|
|
||||||
tristate "TI AR7 partitioning support"
|
|
||||||
help
|
|
||||||
TI AR7 partitioning support
|
|
||||||
|
|
||||||
config MTD_BCM63XX_PARTS
|
|
||||||
tristate "BCM63XX CFE partitioning support"
|
|
||||||
depends on BCM63XX || BMIPS_GENERIC || COMPILE_TEST
|
|
||||||
select CRC32
|
|
||||||
select MTD_PARSER_IMAGETAG
|
|
||||||
help
|
|
||||||
This provides partition parsing for BCM63xx devices with CFE
|
|
||||||
bootloaders.
|
|
||||||
|
|
||||||
config MTD_BCM47XX_PARTS
|
|
||||||
tristate "BCM47XX partitioning support"
|
|
||||||
depends on BCM47XX || ARCH_BCM_5301X
|
|
||||||
help
|
|
||||||
This provides partitions parser for devices based on BCM47xx
|
|
||||||
boards.
|
|
||||||
|
|
||||||
menu "Partition parsers"
|
menu "Partition parsers"
|
||||||
source "drivers/mtd/parsers/Kconfig"
|
source "drivers/mtd/parsers/Kconfig"
|
||||||
endmenu
|
endmenu
|
||||||
|
|
|
@ -7,11 +7,6 @@
|
||||||
obj-$(CONFIG_MTD) += mtd.o
|
obj-$(CONFIG_MTD) += mtd.o
|
||||||
mtd-y := mtdcore.o mtdsuper.o mtdconcat.o mtdpart.o mtdchar.o
|
mtd-y := mtdcore.o mtdsuper.o mtdconcat.o mtdpart.o mtdchar.o
|
||||||
|
|
||||||
obj-$(CONFIG_MTD_OF_PARTS) += ofpart.o
|
|
||||||
obj-$(CONFIG_MTD_CMDLINE_PARTS) += cmdlinepart.o
|
|
||||||
obj-$(CONFIG_MTD_AR7_PARTS) += ar7part.o
|
|
||||||
obj-$(CONFIG_MTD_BCM63XX_PARTS) += bcm63xxpart.o
|
|
||||||
obj-$(CONFIG_MTD_BCM47XX_PARTS) += bcm47xxpart.o
|
|
||||||
obj-y += parsers/
|
obj-y += parsers/
|
||||||
|
|
||||||
# 'Users' - code which presents functionality to userspace.
|
# 'Users' - code which presents functionality to userspace.
|
||||||
|
|
|
@ -61,7 +61,9 @@
|
||||||
|
|
||||||
static int cfi_amdstd_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
|
static int cfi_amdstd_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *);
|
||||||
static int cfi_amdstd_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
|
static int cfi_amdstd_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
|
||||||
|
#if !FORCE_WORD_WRITE
|
||||||
static int cfi_amdstd_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
|
static int cfi_amdstd_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *);
|
||||||
|
#endif
|
||||||
static int cfi_amdstd_erase_chip(struct mtd_info *, struct erase_info *);
|
static int cfi_amdstd_erase_chip(struct mtd_info *, struct erase_info *);
|
||||||
static int cfi_amdstd_erase_varsize(struct mtd_info *, struct erase_info *);
|
static int cfi_amdstd_erase_varsize(struct mtd_info *, struct erase_info *);
|
||||||
static void cfi_amdstd_sync (struct mtd_info *);
|
static void cfi_amdstd_sync (struct mtd_info *);
|
||||||
|
@ -256,6 +258,7 @@ static void fixup_amd_bootblock(struct mtd_info *mtd)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !FORCE_WORD_WRITE
|
||||||
static void fixup_use_write_buffers(struct mtd_info *mtd)
|
static void fixup_use_write_buffers(struct mtd_info *mtd)
|
||||||
{
|
{
|
||||||
struct map_info *map = mtd->priv;
|
struct map_info *map = mtd->priv;
|
||||||
|
@ -265,6 +268,7 @@ static void fixup_use_write_buffers(struct mtd_info *mtd)
|
||||||
mtd->_write = cfi_amdstd_write_buffers;
|
mtd->_write = cfi_amdstd_write_buffers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif /* !FORCE_WORD_WRITE */
|
||||||
|
|
||||||
/* Atmel chips don't use the same PRI format as AMD chips */
|
/* Atmel chips don't use the same PRI format as AMD chips */
|
||||||
static void fixup_convert_atmel_pri(struct mtd_info *mtd)
|
static void fixup_convert_atmel_pri(struct mtd_info *mtd)
|
||||||
|
@ -1637,11 +1641,11 @@ static int cfi_amdstd_lock_user_prot_reg(struct mtd_info *mtd, loff_t from,
|
||||||
do_otp_lock, 1);
|
do_otp_lock, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
|
static int __xipram do_write_oneword_once(struct map_info *map,
|
||||||
unsigned long adr, map_word datum,
|
struct flchip *chip,
|
||||||
int mode)
|
unsigned long adr, map_word datum,
|
||||||
|
int mode, struct cfi_private *cfi)
|
||||||
{
|
{
|
||||||
struct cfi_private *cfi = map->fldrv_priv;
|
|
||||||
unsigned long timeo = jiffies + HZ;
|
unsigned long timeo = jiffies + HZ;
|
||||||
/*
|
/*
|
||||||
* We use a 1ms + 1 jiffies generic timeout for writes (most devices
|
* We use a 1ms + 1 jiffies generic timeout for writes (most devices
|
||||||
|
@ -1654,42 +1658,7 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
|
||||||
*/
|
*/
|
||||||
unsigned long uWriteTimeout = (HZ / 1000) + 1;
|
unsigned long uWriteTimeout = (HZ / 1000) + 1;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
map_word oldd;
|
|
||||||
int retry_cnt = 0;
|
|
||||||
|
|
||||||
adr += chip->start;
|
|
||||||
|
|
||||||
mutex_lock(&chip->mutex);
|
|
||||||
ret = get_chip(map, chip, adr, mode);
|
|
||||||
if (ret) {
|
|
||||||
mutex_unlock(&chip->mutex);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
pr_debug("MTD %s(): WRITE 0x%.8lx(0x%.8lx)\n",
|
|
||||||
__func__, adr, datum.x[0]);
|
|
||||||
|
|
||||||
if (mode == FL_OTP_WRITE)
|
|
||||||
otp_enter(map, chip, adr, map_bankwidth(map));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check for a NOP for the case when the datum to write is already
|
|
||||||
* present - it saves time and works around buggy chips that corrupt
|
|
||||||
* data at other locations when 0xff is written to a location that
|
|
||||||
* already contains 0xff.
|
|
||||||
*/
|
|
||||||
oldd = map_read(map, adr);
|
|
||||||
if (map_word_equal(map, oldd, datum)) {
|
|
||||||
pr_debug("MTD %s(): NOP\n",
|
|
||||||
__func__);
|
|
||||||
goto op_done;
|
|
||||||
}
|
|
||||||
|
|
||||||
XIP_INVAL_CACHED_RANGE(map, adr, map_bankwidth(map));
|
|
||||||
ENABLE_VPP(map);
|
|
||||||
xip_disable(map, chip, adr);
|
|
||||||
|
|
||||||
retry:
|
|
||||||
cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
|
cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
|
||||||
cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL);
|
cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL);
|
||||||
cfi_send_gen_cmd(0xA0, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
|
cfi_send_gen_cmd(0xA0, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL);
|
||||||
|
@ -1717,40 +1686,125 @@ static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We check "time_after" and "!chip_good" before checking
|
||||||
|
* "chip_good" to avoid the failure due to scheduling.
|
||||||
|
*/
|
||||||
if (time_after(jiffies, timeo) &&
|
if (time_after(jiffies, timeo) &&
|
||||||
!chip_ready(map, chip, adr)) {
|
!chip_good(map, chip, adr, datum)) {
|
||||||
xip_enable(map, chip, adr);
|
xip_enable(map, chip, adr);
|
||||||
printk(KERN_WARNING "MTD %s(): software timeout\n", __func__);
|
printk(KERN_WARNING "MTD %s(): software timeout\n", __func__);
|
||||||
xip_disable(map, chip, adr);
|
xip_disable(map, chip, adr);
|
||||||
|
ret = -EIO;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chip_ready(map, chip, adr))
|
if (chip_good(map, chip, adr, datum))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Latency issues. Drop the lock, wait a while and retry */
|
/* Latency issues. Drop the lock, wait a while and retry */
|
||||||
UDELAY(map, chip, adr, 1);
|
UDELAY(map, chip, adr, 1);
|
||||||
}
|
}
|
||||||
/* Did we succeed? */
|
|
||||||
if (!chip_good(map, chip, adr, datum)) {
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __xipram do_write_oneword_start(struct map_info *map,
|
||||||
|
struct flchip *chip,
|
||||||
|
unsigned long adr, int mode)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
mutex_lock(&chip->mutex);
|
||||||
|
|
||||||
|
ret = get_chip(map, chip, adr, mode);
|
||||||
|
if (ret) {
|
||||||
|
mutex_unlock(&chip->mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode == FL_OTP_WRITE)
|
||||||
|
otp_enter(map, chip, adr, map_bankwidth(map));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __xipram do_write_oneword_done(struct map_info *map,
|
||||||
|
struct flchip *chip,
|
||||||
|
unsigned long adr, int mode)
|
||||||
|
{
|
||||||
|
if (mode == FL_OTP_WRITE)
|
||||||
|
otp_exit(map, chip, adr, map_bankwidth(map));
|
||||||
|
|
||||||
|
chip->state = FL_READY;
|
||||||
|
DISABLE_VPP(map);
|
||||||
|
put_chip(map, chip, adr);
|
||||||
|
|
||||||
|
mutex_unlock(&chip->mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __xipram do_write_oneword_retry(struct map_info *map,
|
||||||
|
struct flchip *chip,
|
||||||
|
unsigned long adr, map_word datum,
|
||||||
|
int mode)
|
||||||
|
{
|
||||||
|
struct cfi_private *cfi = map->fldrv_priv;
|
||||||
|
int ret = 0;
|
||||||
|
map_word oldd;
|
||||||
|
int retry_cnt = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check for a NOP for the case when the datum to write is already
|
||||||
|
* present - it saves time and works around buggy chips that corrupt
|
||||||
|
* data at other locations when 0xff is written to a location that
|
||||||
|
* already contains 0xff.
|
||||||
|
*/
|
||||||
|
oldd = map_read(map, adr);
|
||||||
|
if (map_word_equal(map, oldd, datum)) {
|
||||||
|
pr_debug("MTD %s(): NOP\n", __func__);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
XIP_INVAL_CACHED_RANGE(map, adr, map_bankwidth(map));
|
||||||
|
ENABLE_VPP(map);
|
||||||
|
xip_disable(map, chip, adr);
|
||||||
|
|
||||||
|
retry:
|
||||||
|
ret = do_write_oneword_once(map, chip, adr, datum, mode, cfi);
|
||||||
|
if (ret) {
|
||||||
/* reset on all failures. */
|
/* reset on all failures. */
|
||||||
cfi_check_err_status(map, chip, adr);
|
cfi_check_err_status(map, chip, adr);
|
||||||
map_write(map, CMD(0xF0), chip->start);
|
map_write(map, CMD(0xF0), chip->start);
|
||||||
/* FIXME - should have reset delay before continuing */
|
/* FIXME - should have reset delay before continuing */
|
||||||
|
|
||||||
if (++retry_cnt <= MAX_RETRIES)
|
if (++retry_cnt <= MAX_RETRIES) {
|
||||||
|
ret = 0;
|
||||||
goto retry;
|
goto retry;
|
||||||
|
}
|
||||||
ret = -EIO;
|
|
||||||
}
|
}
|
||||||
xip_enable(map, chip, adr);
|
xip_enable(map, chip, adr);
|
||||||
op_done:
|
|
||||||
if (mode == FL_OTP_WRITE)
|
return ret;
|
||||||
otp_exit(map, chip, adr, map_bankwidth(map));
|
}
|
||||||
chip->state = FL_READY;
|
|
||||||
DISABLE_VPP(map);
|
static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip,
|
||||||
put_chip(map, chip, adr);
|
unsigned long adr, map_word datum,
|
||||||
mutex_unlock(&chip->mutex);
|
int mode)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
adr += chip->start;
|
||||||
|
|
||||||
|
pr_debug("MTD %s(): WRITE 0x%.8lx(0x%.8lx)\n", __func__, adr,
|
||||||
|
datum.x[0]);
|
||||||
|
|
||||||
|
ret = do_write_oneword_start(map, chip, adr, mode);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = do_write_oneword_retry(map, chip, adr, datum, mode);
|
||||||
|
|
||||||
|
do_write_oneword_done(map, chip, adr, mode);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -1879,6 +1933,78 @@ static int cfi_amdstd_write_words(struct mtd_info *mtd, loff_t to, size_t len,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !FORCE_WORD_WRITE
|
||||||
|
static int __xipram do_write_buffer_wait(struct map_info *map,
|
||||||
|
struct flchip *chip, unsigned long adr,
|
||||||
|
map_word datum)
|
||||||
|
{
|
||||||
|
unsigned long timeo;
|
||||||
|
unsigned long u_write_timeout;
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Timeout is calculated according to CFI data, if available.
|
||||||
|
* See more comments in cfi_cmdset_0002().
|
||||||
|
*/
|
||||||
|
u_write_timeout = usecs_to_jiffies(chip->buffer_write_time_max);
|
||||||
|
timeo = jiffies + u_write_timeout;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (chip->state != FL_WRITING) {
|
||||||
|
/* Someone's suspended the write. Sleep */
|
||||||
|
DECLARE_WAITQUEUE(wait, current);
|
||||||
|
|
||||||
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||||
|
add_wait_queue(&chip->wq, &wait);
|
||||||
|
mutex_unlock(&chip->mutex);
|
||||||
|
schedule();
|
||||||
|
remove_wait_queue(&chip->wq, &wait);
|
||||||
|
timeo = jiffies + (HZ / 2); /* FIXME */
|
||||||
|
mutex_lock(&chip->mutex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We check "time_after" and "!chip_good" before checking
|
||||||
|
* "chip_good" to avoid the failure due to scheduling.
|
||||||
|
*/
|
||||||
|
if (time_after(jiffies, timeo) &&
|
||||||
|
!chip_good(map, chip, adr, datum)) {
|
||||||
|
ret = -EIO;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chip_good(map, chip, adr, datum))
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Latency issues. Drop the lock, wait a while and retry */
|
||||||
|
UDELAY(map, chip, adr, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __xipram do_write_buffer_reset(struct map_info *map,
|
||||||
|
struct flchip *chip,
|
||||||
|
struct cfi_private *cfi)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Recovery from write-buffer programming failures requires
|
||||||
|
* the write-to-buffer-reset sequence. Since the last part
|
||||||
|
* of the sequence also works as a normal reset, we can run
|
||||||
|
* the same commands regardless of why we are here.
|
||||||
|
* See e.g.
|
||||||
|
* http://www.spansion.com/Support/Application%20Notes/MirrorBit_Write_Buffer_Prog_Page_Buffer_Read_AN.pdf
|
||||||
|
*/
|
||||||
|
cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi,
|
||||||
|
cfi->device_type, NULL);
|
||||||
|
cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi,
|
||||||
|
cfi->device_type, NULL);
|
||||||
|
cfi_send_gen_cmd(0xF0, cfi->addr_unlock1, chip->start, map, cfi,
|
||||||
|
cfi->device_type, NULL);
|
||||||
|
|
||||||
|
/* FIXME - should have reset delay before continuing */
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* FIXME: interleaved mode not tested, and probably not supported!
|
* FIXME: interleaved mode not tested, and probably not supported!
|
||||||
|
@ -1888,13 +2014,6 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip,
|
||||||
int len)
|
int len)
|
||||||
{
|
{
|
||||||
struct cfi_private *cfi = map->fldrv_priv;
|
struct cfi_private *cfi = map->fldrv_priv;
|
||||||
unsigned long timeo = jiffies + HZ;
|
|
||||||
/*
|
|
||||||
* Timeout is calculated according to CFI data, if available.
|
|
||||||
* See more comments in cfi_cmdset_0002().
|
|
||||||
*/
|
|
||||||
unsigned long uWriteTimeout =
|
|
||||||
usecs_to_jiffies(chip->buffer_write_time_max);
|
|
||||||
int ret = -EIO;
|
int ret = -EIO;
|
||||||
unsigned long cmd_adr;
|
unsigned long cmd_adr;
|
||||||
int z, words;
|
int z, words;
|
||||||
|
@ -1951,63 +2070,16 @@ static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip,
|
||||||
adr, map_bankwidth(map),
|
adr, map_bankwidth(map),
|
||||||
chip->word_write_time);
|
chip->word_write_time);
|
||||||
|
|
||||||
timeo = jiffies + uWriteTimeout;
|
ret = do_write_buffer_wait(map, chip, adr, datum);
|
||||||
|
if (ret) {
|
||||||
for (;;) {
|
cfi_check_err_status(map, chip, adr);
|
||||||
if (chip->state != FL_WRITING) {
|
do_write_buffer_reset(map, chip, cfi);
|
||||||
/* Someone's suspended the write. Sleep */
|
pr_err("MTD %s(): software timeout, address:0x%.8lx.\n",
|
||||||
DECLARE_WAITQUEUE(wait, current);
|
__func__, adr);
|
||||||
|
|
||||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
|
||||||
add_wait_queue(&chip->wq, &wait);
|
|
||||||
mutex_unlock(&chip->mutex);
|
|
||||||
schedule();
|
|
||||||
remove_wait_queue(&chip->wq, &wait);
|
|
||||||
timeo = jiffies + (HZ / 2); /* FIXME */
|
|
||||||
mutex_lock(&chip->mutex);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We check "time_after" and "!chip_good" before checking "chip_good" to avoid
|
|
||||||
* the failure due to scheduling.
|
|
||||||
*/
|
|
||||||
if (time_after(jiffies, timeo) &&
|
|
||||||
!chip_good(map, chip, adr, datum))
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (chip_good(map, chip, adr, datum)) {
|
|
||||||
xip_enable(map, chip, adr);
|
|
||||||
goto op_done;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Latency issues. Drop the lock, wait a while and retry */
|
|
||||||
UDELAY(map, chip, adr, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Recovery from write-buffer programming failures requires
|
|
||||||
* the write-to-buffer-reset sequence. Since the last part
|
|
||||||
* of the sequence also works as a normal reset, we can run
|
|
||||||
* the same commands regardless of why we are here.
|
|
||||||
* See e.g.
|
|
||||||
* http://www.spansion.com/Support/Application%20Notes/MirrorBit_Write_Buffer_Prog_Page_Buffer_Read_AN.pdf
|
|
||||||
*/
|
|
||||||
cfi_check_err_status(map, chip, adr);
|
|
||||||
cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi,
|
|
||||||
cfi->device_type, NULL);
|
|
||||||
cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi,
|
|
||||||
cfi->device_type, NULL);
|
|
||||||
cfi_send_gen_cmd(0xF0, cfi->addr_unlock1, chip->start, map, cfi,
|
|
||||||
cfi->device_type, NULL);
|
|
||||||
xip_enable(map, chip, adr);
|
xip_enable(map, chip, adr);
|
||||||
/* FIXME - should have reset delay before continuing */
|
|
||||||
|
|
||||||
printk(KERN_WARNING "MTD %s(): software timeout, address:0x%.8lx.\n",
|
|
||||||
__func__, adr);
|
|
||||||
|
|
||||||
ret = -EIO;
|
|
||||||
op_done:
|
|
||||||
chip->state = FL_READY;
|
chip->state = FL_READY;
|
||||||
DISABLE_VPP(map);
|
DISABLE_VPP(map);
|
||||||
put_chip(map, chip, adr);
|
put_chip(map, chip, adr);
|
||||||
|
@ -2091,6 +2163,7 @@ static int cfi_amdstd_write_buffers(struct mtd_info *mtd, loff_t to, size_t len,
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
#endif /* !FORCE_WORD_WRITE */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Wait for the flash chip to become ready to write data
|
* Wait for the flash chip to become ready to write data
|
||||||
|
@ -2344,7 +2417,7 @@ static int __xipram do_erase_chip(struct map_info *map, struct flchip *chip)
|
||||||
adr = cfi->addr_unlock1;
|
adr = cfi->addr_unlock1;
|
||||||
|
|
||||||
mutex_lock(&chip->mutex);
|
mutex_lock(&chip->mutex);
|
||||||
ret = get_chip(map, chip, adr, FL_WRITING);
|
ret = get_chip(map, chip, adr, FL_ERASING);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
mutex_unlock(&chip->mutex);
|
mutex_unlock(&chip->mutex);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
@ -20,7 +20,7 @@ static int genprobe_new_chip(struct map_info *map, struct chip_probe *cp,
|
||||||
|
|
||||||
struct mtd_info *mtd_do_chip_probe(struct map_info *map, struct chip_probe *cp)
|
struct mtd_info *mtd_do_chip_probe(struct map_info *map, struct chip_probe *cp)
|
||||||
{
|
{
|
||||||
struct mtd_info *mtd = NULL;
|
struct mtd_info *mtd;
|
||||||
struct cfi_private *cfi;
|
struct cfi_private *cfi;
|
||||||
|
|
||||||
/* First probe the map to see if we have CFI stuff there. */
|
/* First probe the map to see if we have CFI stuff there. */
|
||||||
|
|
|
@ -79,24 +79,6 @@ config MTD_DATAFLASH_OTP
|
||||||
other key product data. The second half is programmed with a
|
other key product data. The second half is programmed with a
|
||||||
unique-to-each-chip bit pattern at the factory.
|
unique-to-each-chip bit pattern at the factory.
|
||||||
|
|
||||||
config MTD_M25P80
|
|
||||||
tristate "Support most SPI Flash chips (AT26DF, M25P, W25X, ...)"
|
|
||||||
depends on SPI_MASTER && MTD_SPI_NOR
|
|
||||||
select SPI_MEM
|
|
||||||
help
|
|
||||||
This enables access to most modern SPI flash chips, used for
|
|
||||||
program and data storage. Series supported include Atmel AT26DF,
|
|
||||||
Spansion S25SL, SST 25VF, ST M25P, and Winbond W25X. Other chips
|
|
||||||
are supported as well. See the driver source for the current list,
|
|
||||||
or to add other chips.
|
|
||||||
|
|
||||||
Note that the original DataFlash chips (AT45 series, not AT26DF),
|
|
||||||
need an entirely different driver.
|
|
||||||
|
|
||||||
Set up your spi devices with the right board-specific platform data,
|
|
||||||
if you want to specify device partitioning or to use a device which
|
|
||||||
doesn't support the JEDEC ID instruction.
|
|
||||||
|
|
||||||
config MTD_MCHP23K256
|
config MTD_MCHP23K256
|
||||||
tristate "Microchip 23K256 SRAM"
|
tristate "Microchip 23K256 SRAM"
|
||||||
depends on SPI_MASTER
|
depends on SPI_MASTER
|
||||||
|
|
|
@ -12,7 +12,6 @@ obj-$(CONFIG_MTD_MTDRAM) += mtdram.o
|
||||||
obj-$(CONFIG_MTD_LART) += lart.o
|
obj-$(CONFIG_MTD_LART) += lart.o
|
||||||
obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o
|
obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o
|
||||||
obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o
|
obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o
|
||||||
obj-$(CONFIG_MTD_M25P80) += m25p80.o
|
|
||||||
obj-$(CONFIG_MTD_MCHP23K256) += mchp23k256.o
|
obj-$(CONFIG_MTD_MCHP23K256) += mchp23k256.o
|
||||||
obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o
|
obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o
|
||||||
obj-$(CONFIG_MTD_SST25L) += sst25l.o
|
obj-$(CONFIG_MTD_SST25L) += sst25l.o
|
||||||
|
|
|
@ -1,347 +0,0 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
/*
|
|
||||||
* MTD SPI driver for ST M25Pxx (and similar) serial flash chips
|
|
||||||
*
|
|
||||||
* Author: Mike Lavender, mike@steroidmicros.com
|
|
||||||
*
|
|
||||||
* Copyright (c) 2005, Intec Automation Inc.
|
|
||||||
*
|
|
||||||
* Some parts are based on lart.c by Abraham Van Der Merwe
|
|
||||||
*
|
|
||||||
* Cleaned up and generalized based on mtd_dataflash.c
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/err.h>
|
|
||||||
#include <linux/errno.h>
|
|
||||||
#include <linux/module.h>
|
|
||||||
#include <linux/device.h>
|
|
||||||
|
|
||||||
#include <linux/mtd/mtd.h>
|
|
||||||
#include <linux/mtd/partitions.h>
|
|
||||||
|
|
||||||
#include <linux/spi/spi.h>
|
|
||||||
#include <linux/spi/spi-mem.h>
|
|
||||||
#include <linux/spi/flash.h>
|
|
||||||
#include <linux/mtd/spi-nor.h>
|
|
||||||
|
|
||||||
struct m25p {
|
|
||||||
struct spi_mem *spimem;
|
|
||||||
struct spi_nor spi_nor;
|
|
||||||
};
|
|
||||||
|
|
||||||
static int m25p80_read_reg(struct spi_nor *nor, u8 code, u8 *val, int len)
|
|
||||||
{
|
|
||||||
struct m25p *flash = nor->priv;
|
|
||||||
struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(code, 1),
|
|
||||||
SPI_MEM_OP_NO_ADDR,
|
|
||||||
SPI_MEM_OP_NO_DUMMY,
|
|
||||||
SPI_MEM_OP_DATA_IN(len, NULL, 1));
|
|
||||||
void *scratchbuf;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
scratchbuf = kmalloc(len, GFP_KERNEL);
|
|
||||||
if (!scratchbuf)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
op.data.buf.in = scratchbuf;
|
|
||||||
ret = spi_mem_exec_op(flash->spimem, &op);
|
|
||||||
if (ret < 0)
|
|
||||||
dev_err(&flash->spimem->spi->dev, "error %d reading %x\n", ret,
|
|
||||||
code);
|
|
||||||
else
|
|
||||||
memcpy(val, scratchbuf, len);
|
|
||||||
|
|
||||||
kfree(scratchbuf);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int m25p80_write_reg(struct spi_nor *nor, u8 opcode, u8 *buf, int len)
|
|
||||||
{
|
|
||||||
struct m25p *flash = nor->priv;
|
|
||||||
struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 1),
|
|
||||||
SPI_MEM_OP_NO_ADDR,
|
|
||||||
SPI_MEM_OP_NO_DUMMY,
|
|
||||||
SPI_MEM_OP_DATA_OUT(len, NULL, 1));
|
|
||||||
void *scratchbuf;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
scratchbuf = kmemdup(buf, len, GFP_KERNEL);
|
|
||||||
if (!scratchbuf)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
op.data.buf.out = scratchbuf;
|
|
||||||
ret = spi_mem_exec_op(flash->spimem, &op);
|
|
||||||
kfree(scratchbuf);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static ssize_t m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
|
|
||||||
const u_char *buf)
|
|
||||||
{
|
|
||||||
struct m25p *flash = nor->priv;
|
|
||||||
struct spi_mem_op op =
|
|
||||||
SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 1),
|
|
||||||
SPI_MEM_OP_ADDR(nor->addr_width, to, 1),
|
|
||||||
SPI_MEM_OP_NO_DUMMY,
|
|
||||||
SPI_MEM_OP_DATA_OUT(len, buf, 1));
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
/* get transfer protocols. */
|
|
||||||
op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->write_proto);
|
|
||||||
op.addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->write_proto);
|
|
||||||
op.data.buswidth = spi_nor_get_protocol_data_nbits(nor->write_proto);
|
|
||||||
|
|
||||||
if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second)
|
|
||||||
op.addr.nbytes = 0;
|
|
||||||
|
|
||||||
ret = spi_mem_adjust_op_size(flash->spimem, &op);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
op.data.nbytes = len < op.data.nbytes ? len : op.data.nbytes;
|
|
||||||
|
|
||||||
ret = spi_mem_exec_op(flash->spimem, &op);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
return op.data.nbytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Read an address range from the nor chip. The address range
|
|
||||||
* may be any size provided it is within the physical boundaries.
|
|
||||||
*/
|
|
||||||
static ssize_t m25p80_read(struct spi_nor *nor, loff_t from, size_t len,
|
|
||||||
u_char *buf)
|
|
||||||
{
|
|
||||||
struct m25p *flash = nor->priv;
|
|
||||||
struct spi_mem_op op =
|
|
||||||
SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 1),
|
|
||||||
SPI_MEM_OP_ADDR(nor->addr_width, from, 1),
|
|
||||||
SPI_MEM_OP_DUMMY(nor->read_dummy, 1),
|
|
||||||
SPI_MEM_OP_DATA_IN(len, buf, 1));
|
|
||||||
size_t remaining = len;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
/* get transfer protocols. */
|
|
||||||
op.cmd.buswidth = spi_nor_get_protocol_inst_nbits(nor->read_proto);
|
|
||||||
op.addr.buswidth = spi_nor_get_protocol_addr_nbits(nor->read_proto);
|
|
||||||
op.dummy.buswidth = op.addr.buswidth;
|
|
||||||
op.data.buswidth = spi_nor_get_protocol_data_nbits(nor->read_proto);
|
|
||||||
|
|
||||||
/* convert the dummy cycles to the number of bytes */
|
|
||||||
op.dummy.nbytes = (nor->read_dummy * op.dummy.buswidth) / 8;
|
|
||||||
|
|
||||||
while (remaining) {
|
|
||||||
op.data.nbytes = remaining < UINT_MAX ? remaining : UINT_MAX;
|
|
||||||
ret = spi_mem_adjust_op_size(flash->spimem, &op);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
ret = spi_mem_exec_op(flash->spimem, &op);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
op.addr.val += op.data.nbytes;
|
|
||||||
remaining -= op.data.nbytes;
|
|
||||||
op.data.buf.in += op.data.nbytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
return len;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* board specific setup should have ensured the SPI clock used here
|
|
||||||
* matches what the READ command supports, at least until this driver
|
|
||||||
* understands FAST_READ (for clocks over 25 MHz).
|
|
||||||
*/
|
|
||||||
static int m25p_probe(struct spi_mem *spimem)
|
|
||||||
{
|
|
||||||
struct spi_device *spi = spimem->spi;
|
|
||||||
struct flash_platform_data *data;
|
|
||||||
struct m25p *flash;
|
|
||||||
struct spi_nor *nor;
|
|
||||||
struct spi_nor_hwcaps hwcaps = {
|
|
||||||
.mask = SNOR_HWCAPS_READ |
|
|
||||||
SNOR_HWCAPS_READ_FAST |
|
|
||||||
SNOR_HWCAPS_PP,
|
|
||||||
};
|
|
||||||
char *flash_name;
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
data = dev_get_platdata(&spimem->spi->dev);
|
|
||||||
|
|
||||||
flash = devm_kzalloc(&spimem->spi->dev, sizeof(*flash), GFP_KERNEL);
|
|
||||||
if (!flash)
|
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
nor = &flash->spi_nor;
|
|
||||||
|
|
||||||
/* install the hooks */
|
|
||||||
nor->read = m25p80_read;
|
|
||||||
nor->write = m25p80_write;
|
|
||||||
nor->write_reg = m25p80_write_reg;
|
|
||||||
nor->read_reg = m25p80_read_reg;
|
|
||||||
|
|
||||||
nor->dev = &spimem->spi->dev;
|
|
||||||
spi_nor_set_flash_node(nor, spi->dev.of_node);
|
|
||||||
nor->priv = flash;
|
|
||||||
|
|
||||||
spi_mem_set_drvdata(spimem, flash);
|
|
||||||
flash->spimem = spimem;
|
|
||||||
|
|
||||||
if (spi->mode & SPI_RX_OCTAL) {
|
|
||||||
hwcaps.mask |= SNOR_HWCAPS_READ_1_1_8;
|
|
||||||
|
|
||||||
if (spi->mode & SPI_TX_OCTAL)
|
|
||||||
hwcaps.mask |= (SNOR_HWCAPS_READ_1_8_8 |
|
|
||||||
SNOR_HWCAPS_PP_1_1_8 |
|
|
||||||
SNOR_HWCAPS_PP_1_8_8);
|
|
||||||
} else if (spi->mode & SPI_RX_QUAD) {
|
|
||||||
hwcaps.mask |= SNOR_HWCAPS_READ_1_1_4;
|
|
||||||
|
|
||||||
if (spi->mode & SPI_TX_QUAD)
|
|
||||||
hwcaps.mask |= (SNOR_HWCAPS_READ_1_4_4 |
|
|
||||||
SNOR_HWCAPS_PP_1_1_4 |
|
|
||||||
SNOR_HWCAPS_PP_1_4_4);
|
|
||||||
} else if (spi->mode & SPI_RX_DUAL) {
|
|
||||||
hwcaps.mask |= SNOR_HWCAPS_READ_1_1_2;
|
|
||||||
|
|
||||||
if (spi->mode & SPI_TX_DUAL)
|
|
||||||
hwcaps.mask |= SNOR_HWCAPS_READ_1_2_2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data && data->name)
|
|
||||||
nor->mtd.name = data->name;
|
|
||||||
|
|
||||||
if (!nor->mtd.name)
|
|
||||||
nor->mtd.name = spi_mem_get_name(spimem);
|
|
||||||
|
|
||||||
/* For some (historical?) reason many platforms provide two different
|
|
||||||
* names in flash_platform_data: "name" and "type". Quite often name is
|
|
||||||
* set to "m25p80" and then "type" provides a real chip name.
|
|
||||||
* If that's the case, respect "type" and ignore a "name".
|
|
||||||
*/
|
|
||||||
if (data && data->type)
|
|
||||||
flash_name = data->type;
|
|
||||||
else if (!strcmp(spi->modalias, "spi-nor"))
|
|
||||||
flash_name = NULL; /* auto-detect */
|
|
||||||
else
|
|
||||||
flash_name = spi->modalias;
|
|
||||||
|
|
||||||
ret = spi_nor_scan(nor, flash_name, &hwcaps);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
return mtd_device_register(&nor->mtd, data ? data->parts : NULL,
|
|
||||||
data ? data->nr_parts : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int m25p_remove(struct spi_mem *spimem)
|
|
||||||
{
|
|
||||||
struct m25p *flash = spi_mem_get_drvdata(spimem);
|
|
||||||
|
|
||||||
spi_nor_restore(&flash->spi_nor);
|
|
||||||
|
|
||||||
/* Clean up MTD stuff. */
|
|
||||||
return mtd_device_unregister(&flash->spi_nor.mtd);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void m25p_shutdown(struct spi_mem *spimem)
|
|
||||||
{
|
|
||||||
struct m25p *flash = spi_mem_get_drvdata(spimem);
|
|
||||||
|
|
||||||
spi_nor_restore(&flash->spi_nor);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Do NOT add to this array without reading the following:
|
|
||||||
*
|
|
||||||
* Historically, many flash devices are bound to this driver by their name. But
|
|
||||||
* since most of these flash are compatible to some extent, and their
|
|
||||||
* differences can often be differentiated by the JEDEC read-ID command, we
|
|
||||||
* encourage new users to add support to the spi-nor library, and simply bind
|
|
||||||
* against a generic string here (e.g., "jedec,spi-nor").
|
|
||||||
*
|
|
||||||
* Many flash names are kept here in this list (as well as in spi-nor.c) to
|
|
||||||
* keep them available as module aliases for existing platforms.
|
|
||||||
*/
|
|
||||||
static const struct spi_device_id m25p_ids[] = {
|
|
||||||
/*
|
|
||||||
* Allow non-DT platform devices to bind to the "spi-nor" modalias, and
|
|
||||||
* hack around the fact that the SPI core does not provide uevent
|
|
||||||
* matching for .of_match_table
|
|
||||||
*/
|
|
||||||
{"spi-nor"},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Entries not used in DTs that should be safe to drop after replacing
|
|
||||||
* them with "spi-nor" in platform data.
|
|
||||||
*/
|
|
||||||
{"s25sl064a"}, {"w25x16"}, {"m25p10"}, {"m25px64"},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Entries that were used in DTs without "jedec,spi-nor" fallback and
|
|
||||||
* should be kept for backward compatibility.
|
|
||||||
*/
|
|
||||||
{"at25df321a"}, {"at25df641"}, {"at26df081a"},
|
|
||||||
{"mx25l4005a"}, {"mx25l1606e"}, {"mx25l6405d"}, {"mx25l12805d"},
|
|
||||||
{"mx25l25635e"},{"mx66l51235l"},
|
|
||||||
{"n25q064"}, {"n25q128a11"}, {"n25q128a13"}, {"n25q512a"},
|
|
||||||
{"s25fl256s1"}, {"s25fl512s"}, {"s25sl12801"}, {"s25fl008k"},
|
|
||||||
{"s25fl064k"},
|
|
||||||
{"sst25vf040b"},{"sst25vf016b"},{"sst25vf032b"},{"sst25wf040"},
|
|
||||||
{"m25p40"}, {"m25p80"}, {"m25p16"}, {"m25p32"},
|
|
||||||
{"m25p64"}, {"m25p128"},
|
|
||||||
{"w25x80"}, {"w25x32"}, {"w25q32"}, {"w25q32dw"},
|
|
||||||
{"w25q80bl"}, {"w25q128"}, {"w25q256"},
|
|
||||||
|
|
||||||
/* Flashes that can't be detected using JEDEC */
|
|
||||||
{"m25p05-nonjedec"}, {"m25p10-nonjedec"}, {"m25p20-nonjedec"},
|
|
||||||
{"m25p40-nonjedec"}, {"m25p80-nonjedec"}, {"m25p16-nonjedec"},
|
|
||||||
{"m25p32-nonjedec"}, {"m25p64-nonjedec"}, {"m25p128-nonjedec"},
|
|
||||||
|
|
||||||
/* Everspin MRAMs (non-JEDEC) */
|
|
||||||
{ "mr25h128" }, /* 128 Kib, 40 MHz */
|
|
||||||
{ "mr25h256" }, /* 256 Kib, 40 MHz */
|
|
||||||
{ "mr25h10" }, /* 1 Mib, 40 MHz */
|
|
||||||
{ "mr25h40" }, /* 4 Mib, 40 MHz */
|
|
||||||
|
|
||||||
{ },
|
|
||||||
};
|
|
||||||
MODULE_DEVICE_TABLE(spi, m25p_ids);
|
|
||||||
|
|
||||||
static const struct of_device_id m25p_of_table[] = {
|
|
||||||
/*
|
|
||||||
* Generic compatibility for SPI NOR that can be identified by the
|
|
||||||
* JEDEC READ ID opcode (0x9F). Use this, if possible.
|
|
||||||
*/
|
|
||||||
{ .compatible = "jedec,spi-nor" },
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
MODULE_DEVICE_TABLE(of, m25p_of_table);
|
|
||||||
|
|
||||||
static struct spi_mem_driver m25p80_driver = {
|
|
||||||
.spidrv = {
|
|
||||||
.driver = {
|
|
||||||
.name = "m25p80",
|
|
||||||
.of_match_table = m25p_of_table,
|
|
||||||
},
|
|
||||||
.id_table = m25p_ids,
|
|
||||||
},
|
|
||||||
.probe = m25p_probe,
|
|
||||||
.remove = m25p_remove,
|
|
||||||
.shutdown = m25p_shutdown,
|
|
||||||
|
|
||||||
/* REVISIT: many of these chips have deep power-down modes, which
|
|
||||||
* should clearly be entered on suspend() to minimize power use.
|
|
||||||
* And also when they're otherwise idle...
|
|
||||||
*/
|
|
||||||
};
|
|
||||||
|
|
||||||
module_spi_mem_driver(m25p80_driver);
|
|
||||||
|
|
||||||
MODULE_LICENSE("GPL");
|
|
||||||
MODULE_AUTHOR("Mike Lavender");
|
|
||||||
MODULE_DESCRIPTION("MTD SPI driver for ST M25Pxx flash chips");
|
|
|
@ -294,7 +294,7 @@ static int phram_param_call(const char *val, const struct kernel_param *kp)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
module_param_call(phram, phram_param_call, NULL, NULL, 000);
|
module_param_call(phram, phram_param_call, NULL, NULL, 0200);
|
||||||
MODULE_PARM_DESC(phram, "Memory region to map. \"phram=<name>,<start>,<length>\"");
|
MODULE_PARM_DESC(phram, "Memory region to map. \"phram=<name>,<start>,<length>\"");
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -135,7 +135,7 @@ static int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len,
|
||||||
static int pmc551_erase(struct mtd_info *mtd, struct erase_info *instr)
|
static int pmc551_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||||
{
|
{
|
||||||
struct mypriv *priv = mtd->priv;
|
struct mypriv *priv = mtd->priv;
|
||||||
u32 soff_hi, soff_lo; /* start address offset hi/lo */
|
u32 soff_hi; /* start address offset hi */
|
||||||
u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
|
u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
|
||||||
unsigned long end;
|
unsigned long end;
|
||||||
u_char *ptr;
|
u_char *ptr;
|
||||||
|
@ -150,7 +150,6 @@ static int pmc551_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||||
eoff_hi = end & ~(priv->asize - 1);
|
eoff_hi = end & ~(priv->asize - 1);
|
||||||
soff_hi = instr->addr & ~(priv->asize - 1);
|
soff_hi = instr->addr & ~(priv->asize - 1);
|
||||||
eoff_lo = end & (priv->asize - 1);
|
eoff_lo = end & (priv->asize - 1);
|
||||||
soff_lo = instr->addr & (priv->asize - 1);
|
|
||||||
|
|
||||||
pmc551_point(mtd, instr->addr, instr->len, &retlen,
|
pmc551_point(mtd, instr->addr, instr->len, &retlen,
|
||||||
(void **)&ptr, NULL);
|
(void **)&ptr, NULL);
|
||||||
|
@ -225,7 +224,7 @@ static int pmc551_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||||
size_t * retlen, u_char * buf)
|
size_t * retlen, u_char * buf)
|
||||||
{
|
{
|
||||||
struct mypriv *priv = mtd->priv;
|
struct mypriv *priv = mtd->priv;
|
||||||
u32 soff_hi, soff_lo; /* start address offset hi/lo */
|
u32 soff_hi; /* start address offset hi */
|
||||||
u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
|
u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
|
||||||
unsigned long end;
|
unsigned long end;
|
||||||
u_char *ptr;
|
u_char *ptr;
|
||||||
|
@ -239,7 +238,6 @@ static int pmc551_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||||
end = from + len - 1;
|
end = from + len - 1;
|
||||||
soff_hi = from & ~(priv->asize - 1);
|
soff_hi = from & ~(priv->asize - 1);
|
||||||
eoff_hi = end & ~(priv->asize - 1);
|
eoff_hi = end & ~(priv->asize - 1);
|
||||||
soff_lo = from & (priv->asize - 1);
|
|
||||||
eoff_lo = end & (priv->asize - 1);
|
eoff_lo = end & (priv->asize - 1);
|
||||||
|
|
||||||
pmc551_point(mtd, from, len, retlen, (void **)&ptr, NULL);
|
pmc551_point(mtd, from, len, retlen, (void **)&ptr, NULL);
|
||||||
|
@ -282,7 +280,7 @@ static int pmc551_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||||
size_t * retlen, const u_char * buf)
|
size_t * retlen, const u_char * buf)
|
||||||
{
|
{
|
||||||
struct mypriv *priv = mtd->priv;
|
struct mypriv *priv = mtd->priv;
|
||||||
u32 soff_hi, soff_lo; /* start address offset hi/lo */
|
u32 soff_hi; /* start address offset hi */
|
||||||
u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
|
u32 eoff_hi, eoff_lo; /* end address offset hi/lo */
|
||||||
unsigned long end;
|
unsigned long end;
|
||||||
u_char *ptr;
|
u_char *ptr;
|
||||||
|
@ -296,7 +294,6 @@ static int pmc551_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||||
end = to + len - 1;
|
end = to + len - 1;
|
||||||
soff_hi = to & ~(priv->asize - 1);
|
soff_hi = to & ~(priv->asize - 1);
|
||||||
eoff_hi = end & ~(priv->asize - 1);
|
eoff_hi = end & ~(priv->asize - 1);
|
||||||
soff_lo = to & (priv->asize - 1);
|
|
||||||
eoff_lo = end & (priv->asize - 1);
|
eoff_lo = end & (priv->asize - 1);
|
||||||
|
|
||||||
pmc551_point(mtd, to, len, retlen, (void **)&ptr, NULL);
|
pmc551_point(mtd, to, len, retlen, (void **)&ptr, NULL);
|
||||||
|
|
|
@ -211,13 +211,12 @@ static int pismo_remove(struct i2c_client *client)
|
||||||
static int pismo_probe(struct i2c_client *client,
|
static int pismo_probe(struct i2c_client *client,
|
||||||
const struct i2c_device_id *id)
|
const struct i2c_device_id *id)
|
||||||
{
|
{
|
||||||
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
|
|
||||||
struct pismo_pdata *pdata = client->dev.platform_data;
|
struct pismo_pdata *pdata = client->dev.platform_data;
|
||||||
struct pismo_eeprom eeprom;
|
struct pismo_eeprom eeprom;
|
||||||
struct pismo_data *pismo;
|
struct pismo_data *pismo;
|
||||||
int ret, i;
|
int ret, i;
|
||||||
|
|
||||||
if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) {
|
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
||||||
dev_err(&client->dev, "functionality mismatch\n");
|
dev_err(&client->dev, "functionality mismatch\n");
|
||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,8 +68,7 @@ static int pxa2xx_flash_probe(struct platform_device *pdev)
|
||||||
info->map.name);
|
info->map.name);
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
info->map.cached =
|
info->map.cached = ioremap_cache(info->map.phys, info->map.size);
|
||||||
ioremap_cached(info->map.phys, info->map.size);
|
|
||||||
if (!info->map.cached)
|
if (!info->map.cached)
|
||||||
printk(KERN_WARNING "Failed to ioremap cached %s\n",
|
printk(KERN_WARNING "Failed to ioremap cached %s\n",
|
||||||
info->map.name);
|
info->map.name);
|
||||||
|
|
|
@ -335,6 +335,82 @@ static const struct device_type mtd_devtype = {
|
||||||
.release = mtd_release,
|
.release = mtd_release,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int mtd_partid_show(struct seq_file *s, void *p)
|
||||||
|
{
|
||||||
|
struct mtd_info *mtd = s->private;
|
||||||
|
|
||||||
|
seq_printf(s, "%s\n", mtd->dbg.partid);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtd_partid_debugfs_open(struct inode *inode, struct file *file)
|
||||||
|
{
|
||||||
|
return single_open(file, mtd_partid_show, inode->i_private);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct file_operations mtd_partid_debug_fops = {
|
||||||
|
.open = mtd_partid_debugfs_open,
|
||||||
|
.read = seq_read,
|
||||||
|
.llseek = seq_lseek,
|
||||||
|
.release = single_release,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int mtd_partname_show(struct seq_file *s, void *p)
|
||||||
|
{
|
||||||
|
struct mtd_info *mtd = s->private;
|
||||||
|
|
||||||
|
seq_printf(s, "%s\n", mtd->dbg.partname);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mtd_partname_debugfs_open(struct inode *inode, struct file *file)
|
||||||
|
{
|
||||||
|
return single_open(file, mtd_partname_show, inode->i_private);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct file_operations mtd_partname_debug_fops = {
|
||||||
|
.open = mtd_partname_debugfs_open,
|
||||||
|
.read = seq_read,
|
||||||
|
.llseek = seq_lseek,
|
||||||
|
.release = single_release,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct dentry *dfs_dir_mtd;
|
||||||
|
|
||||||
|
static void mtd_debugfs_populate(struct mtd_info *mtd)
|
||||||
|
{
|
||||||
|
struct device *dev = &mtd->dev;
|
||||||
|
struct dentry *root, *dent;
|
||||||
|
|
||||||
|
if (IS_ERR_OR_NULL(dfs_dir_mtd))
|
||||||
|
return;
|
||||||
|
|
||||||
|
root = debugfs_create_dir(dev_name(dev), dfs_dir_mtd);
|
||||||
|
if (IS_ERR_OR_NULL(root)) {
|
||||||
|
dev_dbg(dev, "won't show data in debugfs\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mtd->dbg.dfs_dir = root;
|
||||||
|
|
||||||
|
if (mtd->dbg.partid) {
|
||||||
|
dent = debugfs_create_file("partid", 0400, root, mtd,
|
||||||
|
&mtd_partid_debug_fops);
|
||||||
|
if (IS_ERR_OR_NULL(dent))
|
||||||
|
dev_err(dev, "can't create debugfs entry for partid\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mtd->dbg.partname) {
|
||||||
|
dent = debugfs_create_file("partname", 0400, root, mtd,
|
||||||
|
&mtd_partname_debug_fops);
|
||||||
|
if (IS_ERR_OR_NULL(dent))
|
||||||
|
dev_err(dev,
|
||||||
|
"can't create debugfs entry for partname\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef CONFIG_MMU
|
#ifndef CONFIG_MMU
|
||||||
unsigned mtd_mmap_capabilities(struct mtd_info *mtd)
|
unsigned mtd_mmap_capabilities(struct mtd_info *mtd)
|
||||||
{
|
{
|
||||||
|
@ -512,8 +588,6 @@ static int mtd_nvmem_add(struct mtd_info *mtd)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct dentry *dfs_dir_mtd;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* add_mtd_device - register an MTD device
|
* add_mtd_device - register an MTD device
|
||||||
* @mtd: pointer to new MTD device info structure
|
* @mtd: pointer to new MTD device info structure
|
||||||
|
@ -607,13 +681,7 @@ int add_mtd_device(struct mtd_info *mtd)
|
||||||
if (error)
|
if (error)
|
||||||
goto fail_nvmem_add;
|
goto fail_nvmem_add;
|
||||||
|
|
||||||
if (!IS_ERR_OR_NULL(dfs_dir_mtd)) {
|
mtd_debugfs_populate(mtd);
|
||||||
mtd->dbg.dfs_dir = debugfs_create_dir(dev_name(&mtd->dev), dfs_dir_mtd);
|
|
||||||
if (IS_ERR_OR_NULL(mtd->dbg.dfs_dir)) {
|
|
||||||
pr_debug("mtd device %s won't show data in debugfs\n",
|
|
||||||
dev_name(&mtd->dev));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL,
|
device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL,
|
||||||
"mtd%dro", i);
|
"mtd%dro", i);
|
||||||
|
|
|
@ -3880,6 +3880,9 @@ int onenand_scan(struct mtd_info *mtd, int maxchips)
|
||||||
if (!this->oob_buf) {
|
if (!this->oob_buf) {
|
||||||
if (this->options & ONENAND_PAGEBUF_ALLOC) {
|
if (this->options & ONENAND_PAGEBUF_ALLOC) {
|
||||||
this->options &= ~ONENAND_PAGEBUF_ALLOC;
|
this->options &= ~ONENAND_PAGEBUF_ALLOC;
|
||||||
|
#ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE
|
||||||
|
kfree(this->verify_buf);
|
||||||
|
#endif
|
||||||
kfree(this->page_buf);
|
kfree(this->page_buf);
|
||||||
}
|
}
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
|
@ -351,14 +351,6 @@ config MTD_NAND_SOCRATES
|
||||||
help
|
help
|
||||||
Enables support for NAND Flash chips wired onto Socrates board.
|
Enables support for NAND Flash chips wired onto Socrates board.
|
||||||
|
|
||||||
config MTD_NAND_NUC900
|
|
||||||
tristate "Nuvoton NUC9xx/w90p910 NAND controller"
|
|
||||||
depends on ARCH_W90X900 || COMPILE_TEST
|
|
||||||
depends on HAS_IOMEM
|
|
||||||
help
|
|
||||||
This enables the driver for the NAND Flash on evaluation board based
|
|
||||||
on w90p910 / NUC9xx.
|
|
||||||
|
|
||||||
source "drivers/mtd/nand/raw/ingenic/Kconfig"
|
source "drivers/mtd/nand/raw/ingenic/Kconfig"
|
||||||
|
|
||||||
config MTD_NAND_FSMC
|
config MTD_NAND_FSMC
|
||||||
|
@ -407,6 +399,12 @@ config MTD_NAND_MTK
|
||||||
Enables support for NAND controller on MTK SoCs.
|
Enables support for NAND controller on MTK SoCs.
|
||||||
This controller is found on mt27xx, mt81xx, mt65xx SoCs.
|
This controller is found on mt27xx, mt81xx, mt65xx SoCs.
|
||||||
|
|
||||||
|
config MTD_NAND_MXIC
|
||||||
|
tristate "Macronix raw NAND controller"
|
||||||
|
depends on HAS_IOMEM || COMPILE_TEST
|
||||||
|
help
|
||||||
|
This selects the Macronix raw NAND controller driver.
|
||||||
|
|
||||||
config MTD_NAND_TEGRA
|
config MTD_NAND_TEGRA
|
||||||
tristate "NVIDIA Tegra NAND controller"
|
tristate "NVIDIA Tegra NAND controller"
|
||||||
depends on ARCH_TEGRA || COMPILE_TEST
|
depends on ARCH_TEGRA || COMPILE_TEST
|
||||||
|
|
|
@ -41,7 +41,6 @@ obj-$(CONFIG_MTD_NAND_SH_FLCTL) += sh_flctl.o
|
||||||
obj-$(CONFIG_MTD_NAND_MXC) += mxc_nand.o
|
obj-$(CONFIG_MTD_NAND_MXC) += mxc_nand.o
|
||||||
obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o
|
obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o
|
||||||
obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o
|
obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o
|
||||||
obj-$(CONFIG_MTD_NAND_NUC900) += nuc900_nand.o
|
|
||||||
obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mpc5121_nfc.o
|
obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mpc5121_nfc.o
|
||||||
obj-$(CONFIG_MTD_NAND_VF610_NFC) += vf610_nfc.o
|
obj-$(CONFIG_MTD_NAND_VF610_NFC) += vf610_nfc.o
|
||||||
obj-$(CONFIG_MTD_NAND_RICOH) += r852.o
|
obj-$(CONFIG_MTD_NAND_RICOH) += r852.o
|
||||||
|
@ -54,6 +53,7 @@ obj-$(CONFIG_MTD_NAND_HISI504) += hisi504_nand.o
|
||||||
obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand/
|
obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand/
|
||||||
obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o
|
obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o
|
||||||
obj-$(CONFIG_MTD_NAND_MTK) += mtk_ecc.o mtk_nand.o
|
obj-$(CONFIG_MTD_NAND_MTK) += mtk_ecc.o mtk_nand.o
|
||||||
|
obj-$(CONFIG_MTD_NAND_MXIC) += mxic_nand.o
|
||||||
obj-$(CONFIG_MTD_NAND_TEGRA) += tegra_nand.o
|
obj-$(CONFIG_MTD_NAND_TEGRA) += tegra_nand.o
|
||||||
obj-$(CONFIG_MTD_NAND_STM32_FMC2) += stm32_fmc2_nand.o
|
obj-$(CONFIG_MTD_NAND_STM32_FMC2) += stm32_fmc2_nand.o
|
||||||
obj-$(CONFIG_MTD_NAND_MESON) += meson_nand.o
|
obj-$(CONFIG_MTD_NAND_MESON) += meson_nand.o
|
||||||
|
|
|
@ -1792,6 +1792,7 @@ static int brcmstb_nand_verify_erased_page(struct mtd_info *mtd,
|
||||||
int bitflips = 0;
|
int bitflips = 0;
|
||||||
int page = addr >> chip->page_shift;
|
int page = addr >> chip->page_shift;
|
||||||
int ret;
|
int ret;
|
||||||
|
void *ecc_chunk;
|
||||||
|
|
||||||
if (!buf)
|
if (!buf)
|
||||||
buf = nand_get_data_buf(chip);
|
buf = nand_get_data_buf(chip);
|
||||||
|
@ -1804,7 +1805,9 @@ static int brcmstb_nand_verify_erased_page(struct mtd_info *mtd,
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
for (i = 0; i < chip->ecc.steps; i++, oob += sas) {
|
for (i = 0; i < chip->ecc.steps; i++, oob += sas) {
|
||||||
ret = nand_check_erased_ecc_chunk(buf, chip->ecc.size,
|
ecc_chunk = buf + chip->ecc.size * i;
|
||||||
|
ret = nand_check_erased_ecc_chunk(ecc_chunk,
|
||||||
|
chip->ecc.size,
|
||||||
oob, sas, NULL, 0,
|
oob, sas, NULL, 0,
|
||||||
chip->ecc.strength);
|
chip->ecc.strength);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
|
|
|
@ -310,7 +310,6 @@ static int ingenic_nand_init_chip(struct platform_device *pdev,
|
||||||
struct device *dev = &pdev->dev;
|
struct device *dev = &pdev->dev;
|
||||||
struct ingenic_nand *nand;
|
struct ingenic_nand *nand;
|
||||||
struct ingenic_nand_cs *cs;
|
struct ingenic_nand_cs *cs;
|
||||||
struct resource *res;
|
|
||||||
struct nand_chip *chip;
|
struct nand_chip *chip;
|
||||||
struct mtd_info *mtd;
|
struct mtd_info *mtd;
|
||||||
const __be32 *reg;
|
const __be32 *reg;
|
||||||
|
@ -326,8 +325,7 @@ static int ingenic_nand_init_chip(struct platform_device *pdev,
|
||||||
|
|
||||||
jz4780_nemc_set_type(nfc->dev, cs->bank, JZ4780_NEMC_BANK_NAND);
|
jz4780_nemc_set_type(nfc->dev, cs->bank, JZ4780_NEMC_BANK_NAND);
|
||||||
|
|
||||||
res = platform_get_resource(pdev, IORESOURCE_MEM, chipnr);
|
cs->base = devm_platform_ioremap_resource(pdev, chipnr);
|
||||||
cs->base = devm_ioremap_resource(dev, res);
|
|
||||||
if (IS_ERR(cs->base))
|
if (IS_ERR(cs->base))
|
||||||
return PTR_ERR(cs->base);
|
return PTR_ERR(cs->base);
|
||||||
|
|
||||||
|
@ -418,6 +416,7 @@ static int ingenic_nand_init_chips(struct ingenic_nfc *nfc,
|
||||||
ret = ingenic_nand_init_chip(pdev, nfc, np, i);
|
ret = ingenic_nand_init_chip(pdev, nfc, np, i);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
ingenic_nand_cleanup_chips(nfc);
|
ingenic_nand_cleanup_chips(nfc);
|
||||||
|
of_node_put(np);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1320,6 +1320,7 @@ static int meson_nfc_nand_chips_init(struct device *dev,
|
||||||
ret = meson_nfc_nand_chip_init(dev, nfc, nand_np);
|
ret = meson_nfc_nand_chip_init(dev, nfc, nand_np);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
meson_nfc_nand_chip_cleanup(nfc);
|
meson_nfc_nand_chip_cleanup(nfc);
|
||||||
|
of_node_put(nand_np);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,582 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Macronix International Co., Ltd.
|
||||||
|
*
|
||||||
|
* Author:
|
||||||
|
* Mason Yang <masonccyang@mxic.com.tw>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/clk.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
#include <linux/iopoll.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/mtd/rawnand.h>
|
||||||
|
#include <linux/mtd/nand_ecc.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
|
||||||
|
#include "internals.h"
|
||||||
|
|
||||||
|
#define HC_CFG 0x0
|
||||||
|
#define HC_CFG_IF_CFG(x) ((x) << 27)
|
||||||
|
#define HC_CFG_DUAL_SLAVE BIT(31)
|
||||||
|
#define HC_CFG_INDIVIDUAL BIT(30)
|
||||||
|
#define HC_CFG_NIO(x) (((x) / 4) << 27)
|
||||||
|
#define HC_CFG_TYPE(s, t) ((t) << (23 + ((s) * 2)))
|
||||||
|
#define HC_CFG_TYPE_SPI_NOR 0
|
||||||
|
#define HC_CFG_TYPE_SPI_NAND 1
|
||||||
|
#define HC_CFG_TYPE_SPI_RAM 2
|
||||||
|
#define HC_CFG_TYPE_RAW_NAND 3
|
||||||
|
#define HC_CFG_SLV_ACT(x) ((x) << 21)
|
||||||
|
#define HC_CFG_CLK_PH_EN BIT(20)
|
||||||
|
#define HC_CFG_CLK_POL_INV BIT(19)
|
||||||
|
#define HC_CFG_BIG_ENDIAN BIT(18)
|
||||||
|
#define HC_CFG_DATA_PASS BIT(17)
|
||||||
|
#define HC_CFG_IDLE_SIO_LVL(x) ((x) << 16)
|
||||||
|
#define HC_CFG_MAN_START_EN BIT(3)
|
||||||
|
#define HC_CFG_MAN_START BIT(2)
|
||||||
|
#define HC_CFG_MAN_CS_EN BIT(1)
|
||||||
|
#define HC_CFG_MAN_CS_ASSERT BIT(0)
|
||||||
|
|
||||||
|
#define INT_STS 0x4
|
||||||
|
#define INT_STS_EN 0x8
|
||||||
|
#define INT_SIG_EN 0xc
|
||||||
|
#define INT_STS_ALL GENMASK(31, 0)
|
||||||
|
#define INT_RDY_PIN BIT(26)
|
||||||
|
#define INT_RDY_SR BIT(25)
|
||||||
|
#define INT_LNR_SUSP BIT(24)
|
||||||
|
#define INT_ECC_ERR BIT(17)
|
||||||
|
#define INT_CRC_ERR BIT(16)
|
||||||
|
#define INT_LWR_DIS BIT(12)
|
||||||
|
#define INT_LRD_DIS BIT(11)
|
||||||
|
#define INT_SDMA_INT BIT(10)
|
||||||
|
#define INT_DMA_FINISH BIT(9)
|
||||||
|
#define INT_RX_NOT_FULL BIT(3)
|
||||||
|
#define INT_RX_NOT_EMPTY BIT(2)
|
||||||
|
#define INT_TX_NOT_FULL BIT(1)
|
||||||
|
#define INT_TX_EMPTY BIT(0)
|
||||||
|
|
||||||
|
#define HC_EN 0x10
|
||||||
|
#define HC_EN_BIT BIT(0)
|
||||||
|
|
||||||
|
#define TXD(x) (0x14 + ((x) * 4))
|
||||||
|
#define RXD 0x24
|
||||||
|
|
||||||
|
#define SS_CTRL(s) (0x30 + ((s) * 4))
|
||||||
|
#define LRD_CFG 0x44
|
||||||
|
#define LWR_CFG 0x80
|
||||||
|
#define RWW_CFG 0x70
|
||||||
|
#define OP_READ BIT(23)
|
||||||
|
#define OP_DUMMY_CYC(x) ((x) << 17)
|
||||||
|
#define OP_ADDR_BYTES(x) ((x) << 14)
|
||||||
|
#define OP_CMD_BYTES(x) (((x) - 1) << 13)
|
||||||
|
#define OP_OCTA_CRC_EN BIT(12)
|
||||||
|
#define OP_DQS_EN BIT(11)
|
||||||
|
#define OP_ENHC_EN BIT(10)
|
||||||
|
#define OP_PREAMBLE_EN BIT(9)
|
||||||
|
#define OP_DATA_DDR BIT(8)
|
||||||
|
#define OP_DATA_BUSW(x) ((x) << 6)
|
||||||
|
#define OP_ADDR_DDR BIT(5)
|
||||||
|
#define OP_ADDR_BUSW(x) ((x) << 3)
|
||||||
|
#define OP_CMD_DDR BIT(2)
|
||||||
|
#define OP_CMD_BUSW(x) (x)
|
||||||
|
#define OP_BUSW_1 0
|
||||||
|
#define OP_BUSW_2 1
|
||||||
|
#define OP_BUSW_4 2
|
||||||
|
#define OP_BUSW_8 3
|
||||||
|
|
||||||
|
#define OCTA_CRC 0x38
|
||||||
|
#define OCTA_CRC_IN_EN(s) BIT(3 + ((s) * 16))
|
||||||
|
#define OCTA_CRC_CHUNK(s, x) ((fls((x) / 32)) << (1 + ((s) * 16)))
|
||||||
|
#define OCTA_CRC_OUT_EN(s) BIT(0 + ((s) * 16))
|
||||||
|
|
||||||
|
#define ONFI_DIN_CNT(s) (0x3c + (s))
|
||||||
|
|
||||||
|
#define LRD_CTRL 0x48
|
||||||
|
#define RWW_CTRL 0x74
|
||||||
|
#define LWR_CTRL 0x84
|
||||||
|
#define LMODE_EN BIT(31)
|
||||||
|
#define LMODE_SLV_ACT(x) ((x) << 21)
|
||||||
|
#define LMODE_CMD1(x) ((x) << 8)
|
||||||
|
#define LMODE_CMD0(x) (x)
|
||||||
|
|
||||||
|
#define LRD_ADDR 0x4c
|
||||||
|
#define LWR_ADDR 0x88
|
||||||
|
#define LRD_RANGE 0x50
|
||||||
|
#define LWR_RANGE 0x8c
|
||||||
|
|
||||||
|
#define AXI_SLV_ADDR 0x54
|
||||||
|
|
||||||
|
#define DMAC_RD_CFG 0x58
|
||||||
|
#define DMAC_WR_CFG 0x94
|
||||||
|
#define DMAC_CFG_PERIPH_EN BIT(31)
|
||||||
|
#define DMAC_CFG_ALLFLUSH_EN BIT(30)
|
||||||
|
#define DMAC_CFG_LASTFLUSH_EN BIT(29)
|
||||||
|
#define DMAC_CFG_QE(x) (((x) + 1) << 16)
|
||||||
|
#define DMAC_CFG_BURST_LEN(x) (((x) + 1) << 12)
|
||||||
|
#define DMAC_CFG_BURST_SZ(x) ((x) << 8)
|
||||||
|
#define DMAC_CFG_DIR_READ BIT(1)
|
||||||
|
#define DMAC_CFG_START BIT(0)
|
||||||
|
|
||||||
|
#define DMAC_RD_CNT 0x5c
|
||||||
|
#define DMAC_WR_CNT 0x98
|
||||||
|
|
||||||
|
#define SDMA_ADDR 0x60
|
||||||
|
|
||||||
|
#define DMAM_CFG 0x64
|
||||||
|
#define DMAM_CFG_START BIT(31)
|
||||||
|
#define DMAM_CFG_CONT BIT(30)
|
||||||
|
#define DMAM_CFG_SDMA_GAP(x) (fls((x) / 8192) << 2)
|
||||||
|
#define DMAM_CFG_DIR_READ BIT(1)
|
||||||
|
#define DMAM_CFG_EN BIT(0)
|
||||||
|
|
||||||
|
#define DMAM_CNT 0x68
|
||||||
|
|
||||||
|
#define LNR_TIMER_TH 0x6c
|
||||||
|
|
||||||
|
#define RDM_CFG0 0x78
|
||||||
|
#define RDM_CFG0_POLY(x) (x)
|
||||||
|
|
||||||
|
#define RDM_CFG1 0x7c
|
||||||
|
#define RDM_CFG1_RDM_EN BIT(31)
|
||||||
|
#define RDM_CFG1_SEED(x) (x)
|
||||||
|
|
||||||
|
#define LWR_SUSP_CTRL 0x90
|
||||||
|
#define LWR_SUSP_CTRL_EN BIT(31)
|
||||||
|
|
||||||
|
#define DMAS_CTRL 0x9c
|
||||||
|
#define DMAS_CTRL_EN BIT(31)
|
||||||
|
#define DMAS_CTRL_DIR_READ BIT(30)
|
||||||
|
|
||||||
|
#define DATA_STROB 0xa0
|
||||||
|
#define DATA_STROB_EDO_EN BIT(2)
|
||||||
|
#define DATA_STROB_INV_POL BIT(1)
|
||||||
|
#define DATA_STROB_DELAY_2CYC BIT(0)
|
||||||
|
|
||||||
|
#define IDLY_CODE(x) (0xa4 + ((x) * 4))
|
||||||
|
#define IDLY_CODE_VAL(x, v) ((v) << (((x) % 4) * 8))
|
||||||
|
|
||||||
|
#define GPIO 0xc4
|
||||||
|
#define GPIO_PT(x) BIT(3 + ((x) * 16))
|
||||||
|
#define GPIO_RESET(x) BIT(2 + ((x) * 16))
|
||||||
|
#define GPIO_HOLDB(x) BIT(1 + ((x) * 16))
|
||||||
|
#define GPIO_WPB(x) BIT((x) * 16)
|
||||||
|
|
||||||
|
#define HC_VER 0xd0
|
||||||
|
|
||||||
|
#define HW_TEST(x) (0xe0 + ((x) * 4))
|
||||||
|
|
||||||
|
#define MXIC_NFC_MAX_CLK_HZ 50000000
|
||||||
|
#define IRQ_TIMEOUT 1000
|
||||||
|
|
||||||
|
struct mxic_nand_ctlr {
|
||||||
|
struct clk *ps_clk;
|
||||||
|
struct clk *send_clk;
|
||||||
|
struct clk *send_dly_clk;
|
||||||
|
struct completion complete;
|
||||||
|
void __iomem *regs;
|
||||||
|
struct nand_controller controller;
|
||||||
|
struct device *dev;
|
||||||
|
struct nand_chip chip;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int mxic_nfc_clk_enable(struct mxic_nand_ctlr *nfc)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(nfc->ps_clk);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(nfc->send_clk);
|
||||||
|
if (ret)
|
||||||
|
goto err_ps_clk;
|
||||||
|
|
||||||
|
ret = clk_prepare_enable(nfc->send_dly_clk);
|
||||||
|
if (ret)
|
||||||
|
goto err_send_dly_clk;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
err_send_dly_clk:
|
||||||
|
clk_disable_unprepare(nfc->send_clk);
|
||||||
|
err_ps_clk:
|
||||||
|
clk_disable_unprepare(nfc->ps_clk);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mxic_nfc_clk_disable(struct mxic_nand_ctlr *nfc)
|
||||||
|
{
|
||||||
|
clk_disable_unprepare(nfc->send_clk);
|
||||||
|
clk_disable_unprepare(nfc->send_dly_clk);
|
||||||
|
clk_disable_unprepare(nfc->ps_clk);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mxic_nfc_set_input_delay(struct mxic_nand_ctlr *nfc, u8 idly_code)
|
||||||
|
{
|
||||||
|
writel(IDLY_CODE_VAL(0, idly_code) |
|
||||||
|
IDLY_CODE_VAL(1, idly_code) |
|
||||||
|
IDLY_CODE_VAL(2, idly_code) |
|
||||||
|
IDLY_CODE_VAL(3, idly_code),
|
||||||
|
nfc->regs + IDLY_CODE(0));
|
||||||
|
writel(IDLY_CODE_VAL(4, idly_code) |
|
||||||
|
IDLY_CODE_VAL(5, idly_code) |
|
||||||
|
IDLY_CODE_VAL(6, idly_code) |
|
||||||
|
IDLY_CODE_VAL(7, idly_code),
|
||||||
|
nfc->regs + IDLY_CODE(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mxic_nfc_clk_setup(struct mxic_nand_ctlr *nfc, unsigned long freq)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = clk_set_rate(nfc->send_clk, freq);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = clk_set_rate(nfc->send_dly_clk, freq);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A constant delay range from 0x0 ~ 0x1F for input delay,
|
||||||
|
* the unit is 78 ps, the max input delay is 2.418 ns.
|
||||||
|
*/
|
||||||
|
mxic_nfc_set_input_delay(nfc, 0xf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Phase degree = 360 * freq * output-delay
|
||||||
|
* where output-delay is a constant value 1 ns in FPGA.
|
||||||
|
*
|
||||||
|
* Get Phase degree = 360 * freq * 1 ns
|
||||||
|
* = 360 * freq * 1 sec / 1000000000
|
||||||
|
* = 9 * freq / 25000000
|
||||||
|
*/
|
||||||
|
ret = clk_set_phase(nfc->send_dly_clk, 9 * freq / 25000000);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mxic_nfc_set_freq(struct mxic_nand_ctlr *nfc, unsigned long freq)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (freq > MXIC_NFC_MAX_CLK_HZ)
|
||||||
|
freq = MXIC_NFC_MAX_CLK_HZ;
|
||||||
|
|
||||||
|
mxic_nfc_clk_disable(nfc);
|
||||||
|
ret = mxic_nfc_clk_setup(nfc, freq);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = mxic_nfc_clk_enable(nfc);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static irqreturn_t mxic_nfc_isr(int irq, void *dev_id)
|
||||||
|
{
|
||||||
|
struct mxic_nand_ctlr *nfc = dev_id;
|
||||||
|
u32 sts;
|
||||||
|
|
||||||
|
sts = readl(nfc->regs + INT_STS);
|
||||||
|
if (sts & INT_RDY_PIN)
|
||||||
|
complete(&nfc->complete);
|
||||||
|
else
|
||||||
|
return IRQ_NONE;
|
||||||
|
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mxic_nfc_hw_init(struct mxic_nand_ctlr *nfc)
|
||||||
|
{
|
||||||
|
writel(HC_CFG_NIO(8) | HC_CFG_TYPE(1, HC_CFG_TYPE_RAW_NAND) |
|
||||||
|
HC_CFG_SLV_ACT(0) | HC_CFG_MAN_CS_EN |
|
||||||
|
HC_CFG_IDLE_SIO_LVL(1), nfc->regs + HC_CFG);
|
||||||
|
writel(INT_STS_ALL, nfc->regs + INT_STS_EN);
|
||||||
|
writel(INT_RDY_PIN, nfc->regs + INT_SIG_EN);
|
||||||
|
writel(0x0, nfc->regs + ONFI_DIN_CNT(0));
|
||||||
|
writel(0, nfc->regs + LRD_CFG);
|
||||||
|
writel(0, nfc->regs + LRD_CTRL);
|
||||||
|
writel(0x0, nfc->regs + HC_EN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mxic_nfc_cs_enable(struct mxic_nand_ctlr *nfc)
|
||||||
|
{
|
||||||
|
writel(readl(nfc->regs + HC_CFG) | HC_CFG_MAN_CS_EN,
|
||||||
|
nfc->regs + HC_CFG);
|
||||||
|
writel(HC_CFG_MAN_CS_ASSERT | readl(nfc->regs + HC_CFG),
|
||||||
|
nfc->regs + HC_CFG);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mxic_nfc_cs_disable(struct mxic_nand_ctlr *nfc)
|
||||||
|
{
|
||||||
|
writel(~HC_CFG_MAN_CS_ASSERT & readl(nfc->regs + HC_CFG),
|
||||||
|
nfc->regs + HC_CFG);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mxic_nfc_wait_ready(struct nand_chip *chip)
|
||||||
|
{
|
||||||
|
struct mxic_nand_ctlr *nfc = nand_get_controller_data(chip);
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = wait_for_completion_timeout(&nfc->complete,
|
||||||
|
msecs_to_jiffies(IRQ_TIMEOUT));
|
||||||
|
if (!ret) {
|
||||||
|
dev_err(nfc->dev, "nand device timeout\n");
|
||||||
|
return -ETIMEDOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mxic_nfc_data_xfer(struct mxic_nand_ctlr *nfc, const void *txbuf,
|
||||||
|
void *rxbuf, unsigned int len)
|
||||||
|
{
|
||||||
|
unsigned int pos = 0;
|
||||||
|
|
||||||
|
while (pos < len) {
|
||||||
|
unsigned int nbytes = len - pos;
|
||||||
|
u32 data = 0xffffffff;
|
||||||
|
u32 sts;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (nbytes > 4)
|
||||||
|
nbytes = 4;
|
||||||
|
|
||||||
|
if (txbuf)
|
||||||
|
memcpy(&data, txbuf + pos, nbytes);
|
||||||
|
|
||||||
|
ret = readl_poll_timeout(nfc->regs + INT_STS, sts,
|
||||||
|
sts & INT_TX_EMPTY, 0, USEC_PER_SEC);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
writel(data, nfc->regs + TXD(nbytes % 4));
|
||||||
|
|
||||||
|
ret = readl_poll_timeout(nfc->regs + INT_STS, sts,
|
||||||
|
sts & INT_TX_EMPTY, 0, USEC_PER_SEC);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = readl_poll_timeout(nfc->regs + INT_STS, sts,
|
||||||
|
sts & INT_RX_NOT_EMPTY, 0,
|
||||||
|
USEC_PER_SEC);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
data = readl(nfc->regs + RXD);
|
||||||
|
if (rxbuf) {
|
||||||
|
data >>= (8 * (4 - nbytes));
|
||||||
|
memcpy(rxbuf + pos, &data, nbytes);
|
||||||
|
}
|
||||||
|
if (readl(nfc->regs + INT_STS) & INT_RX_NOT_EMPTY)
|
||||||
|
dev_warn(nfc->dev, "RX FIFO not empty\n");
|
||||||
|
|
||||||
|
pos += nbytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mxic_nfc_exec_op(struct nand_chip *chip,
|
||||||
|
const struct nand_operation *op, bool check_only)
|
||||||
|
{
|
||||||
|
struct mxic_nand_ctlr *nfc = nand_get_controller_data(chip);
|
||||||
|
const struct nand_op_instr *instr = NULL;
|
||||||
|
int ret = 0;
|
||||||
|
unsigned int op_id;
|
||||||
|
|
||||||
|
mxic_nfc_cs_enable(nfc);
|
||||||
|
init_completion(&nfc->complete);
|
||||||
|
for (op_id = 0; op_id < op->ninstrs; op_id++) {
|
||||||
|
instr = &op->instrs[op_id];
|
||||||
|
|
||||||
|
switch (instr->type) {
|
||||||
|
case NAND_OP_CMD_INSTR:
|
||||||
|
writel(0, nfc->regs + HC_EN);
|
||||||
|
writel(HC_EN_BIT, nfc->regs + HC_EN);
|
||||||
|
writel(OP_CMD_BUSW(OP_BUSW_8) | OP_DUMMY_CYC(0x3F) |
|
||||||
|
OP_CMD_BYTES(0), nfc->regs + SS_CTRL(0));
|
||||||
|
|
||||||
|
ret = mxic_nfc_data_xfer(nfc,
|
||||||
|
&instr->ctx.cmd.opcode,
|
||||||
|
NULL, 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NAND_OP_ADDR_INSTR:
|
||||||
|
writel(OP_ADDR_BUSW(OP_BUSW_8) | OP_DUMMY_CYC(0x3F) |
|
||||||
|
OP_ADDR_BYTES(instr->ctx.addr.naddrs),
|
||||||
|
nfc->regs + SS_CTRL(0));
|
||||||
|
ret = mxic_nfc_data_xfer(nfc,
|
||||||
|
instr->ctx.addr.addrs, NULL,
|
||||||
|
instr->ctx.addr.naddrs);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NAND_OP_DATA_IN_INSTR:
|
||||||
|
writel(0x0, nfc->regs + ONFI_DIN_CNT(0));
|
||||||
|
writel(OP_DATA_BUSW(OP_BUSW_8) | OP_DUMMY_CYC(0x3F) |
|
||||||
|
OP_READ, nfc->regs + SS_CTRL(0));
|
||||||
|
ret = mxic_nfc_data_xfer(nfc, NULL,
|
||||||
|
instr->ctx.data.buf.in,
|
||||||
|
instr->ctx.data.len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NAND_OP_DATA_OUT_INSTR:
|
||||||
|
writel(instr->ctx.data.len,
|
||||||
|
nfc->regs + ONFI_DIN_CNT(0));
|
||||||
|
writel(OP_DATA_BUSW(OP_BUSW_8) | OP_DUMMY_CYC(0x3F),
|
||||||
|
nfc->regs + SS_CTRL(0));
|
||||||
|
ret = mxic_nfc_data_xfer(nfc,
|
||||||
|
instr->ctx.data.buf.out, NULL,
|
||||||
|
instr->ctx.data.len);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NAND_OP_WAITRDY_INSTR:
|
||||||
|
ret = mxic_nfc_wait_ready(chip);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mxic_nfc_cs_disable(nfc);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mxic_nfc_setup_data_interface(struct nand_chip *chip, int chipnr,
|
||||||
|
const struct nand_data_interface *conf)
|
||||||
|
{
|
||||||
|
struct mxic_nand_ctlr *nfc = nand_get_controller_data(chip);
|
||||||
|
const struct nand_sdr_timings *sdr;
|
||||||
|
unsigned long freq;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
sdr = nand_get_sdr_timings(conf);
|
||||||
|
if (IS_ERR(sdr))
|
||||||
|
return PTR_ERR(sdr);
|
||||||
|
|
||||||
|
if (chipnr == NAND_DATA_IFACE_CHECK_ONLY)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
freq = NSEC_PER_SEC / (sdr->tRC_min / 1000);
|
||||||
|
|
||||||
|
ret = mxic_nfc_set_freq(nfc, freq);
|
||||||
|
if (ret)
|
||||||
|
dev_err(nfc->dev, "set freq:%ld failed\n", freq);
|
||||||
|
|
||||||
|
if (sdr->tRC_min < 30000)
|
||||||
|
writel(DATA_STROB_EDO_EN, nfc->regs + DATA_STROB);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct nand_controller_ops mxic_nand_controller_ops = {
|
||||||
|
.exec_op = mxic_nfc_exec_op,
|
||||||
|
.setup_data_interface = mxic_nfc_setup_data_interface,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int mxic_nfc_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device_node *nand_np, *np = pdev->dev.of_node;
|
||||||
|
struct mtd_info *mtd;
|
||||||
|
struct mxic_nand_ctlr *nfc;
|
||||||
|
struct nand_chip *nand_chip;
|
||||||
|
int err;
|
||||||
|
int irq;
|
||||||
|
|
||||||
|
nfc = devm_kzalloc(&pdev->dev, sizeof(struct mxic_nand_ctlr),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!nfc)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
nfc->ps_clk = devm_clk_get(&pdev->dev, "ps");
|
||||||
|
if (IS_ERR(nfc->ps_clk))
|
||||||
|
return PTR_ERR(nfc->ps_clk);
|
||||||
|
|
||||||
|
nfc->send_clk = devm_clk_get(&pdev->dev, "send");
|
||||||
|
if (IS_ERR(nfc->send_clk))
|
||||||
|
return PTR_ERR(nfc->send_clk);
|
||||||
|
|
||||||
|
nfc->send_dly_clk = devm_clk_get(&pdev->dev, "send_dly");
|
||||||
|
if (IS_ERR(nfc->send_dly_clk))
|
||||||
|
return PTR_ERR(nfc->send_dly_clk);
|
||||||
|
|
||||||
|
nfc->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||||
|
if (IS_ERR(nfc->regs))
|
||||||
|
return PTR_ERR(nfc->regs);
|
||||||
|
|
||||||
|
nand_chip = &nfc->chip;
|
||||||
|
mtd = nand_to_mtd(nand_chip);
|
||||||
|
mtd->dev.parent = &pdev->dev;
|
||||||
|
|
||||||
|
for_each_child_of_node(np, nand_np)
|
||||||
|
nand_set_flash_node(nand_chip, nand_np);
|
||||||
|
|
||||||
|
nand_chip->priv = nfc;
|
||||||
|
nfc->dev = &pdev->dev;
|
||||||
|
nfc->controller.ops = &mxic_nand_controller_ops;
|
||||||
|
nand_controller_init(&nfc->controller);
|
||||||
|
nand_chip->controller = &nfc->controller;
|
||||||
|
|
||||||
|
irq = platform_get_irq(pdev, 0);
|
||||||
|
if (irq < 0) {
|
||||||
|
dev_err(&pdev->dev, "failed to retrieve irq\n");
|
||||||
|
return irq;
|
||||||
|
}
|
||||||
|
|
||||||
|
mxic_nfc_hw_init(nfc);
|
||||||
|
|
||||||
|
err = devm_request_irq(&pdev->dev, irq, mxic_nfc_isr,
|
||||||
|
0, "mxic-nfc", nfc);
|
||||||
|
if (err)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
err = nand_scan(nand_chip, 1);
|
||||||
|
if (err)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
err = mtd_device_register(mtd, NULL, 0);
|
||||||
|
if (err)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, nfc);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
mxic_nfc_clk_disable(nfc);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int mxic_nfc_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct mxic_nand_ctlr *nfc = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
nand_release(&nfc->chip);
|
||||||
|
mxic_nfc_clk_disable(nfc);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id mxic_nfc_of_ids[] = {
|
||||||
|
{ .compatible = "mxic,multi-itfc-v009-nand-controller", },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, mxic_nfc_of_ids);
|
||||||
|
|
||||||
|
static struct platform_driver mxic_nfc_driver = {
|
||||||
|
.probe = mxic_nfc_probe,
|
||||||
|
.remove = mxic_nfc_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = "mxic-nfc",
|
||||||
|
.of_match_table = mxic_nfc_of_ids,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
module_platform_driver(mxic_nfc_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Mason Yang <masonccyang@mxic.com.tw>");
|
||||||
|
MODULE_DESCRIPTION("Macronix raw NAND controller driver");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
|
@ -4112,7 +4112,7 @@ static int nand_write_oob(struct mtd_info *mtd, loff_t to,
|
||||||
struct mtd_oob_ops *ops)
|
struct mtd_oob_ops *ops)
|
||||||
{
|
{
|
||||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||||
int ret = -ENOTSUPP;
|
int ret;
|
||||||
|
|
||||||
ops->retlen = 0;
|
ops->retlen = 0;
|
||||||
|
|
||||||
|
|
|
@ -1232,7 +1232,7 @@ static int nand_scan_bbt(struct nand_chip *this, struct nand_bbt_descr *bd)
|
||||||
if (!td) {
|
if (!td) {
|
||||||
if ((res = nand_memory_bbt(this, bd))) {
|
if ((res = nand_memory_bbt(this, bd))) {
|
||||||
pr_err("nand_bbt: can't scan flash and build the RAM-based BBT\n");
|
pr_err("nand_bbt: can't scan flash and build the RAM-based BBT\n");
|
||||||
goto err;
|
goto err_free_bbt;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1245,7 +1245,7 @@ static int nand_scan_bbt(struct nand_chip *this, struct nand_bbt_descr *bd)
|
||||||
buf = vmalloc(len);
|
buf = vmalloc(len);
|
||||||
if (!buf) {
|
if (!buf) {
|
||||||
res = -ENOMEM;
|
res = -ENOMEM;
|
||||||
goto err;
|
goto err_free_bbt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Is the bbt at a given page? */
|
/* Is the bbt at a given page? */
|
||||||
|
@ -1258,7 +1258,7 @@ static int nand_scan_bbt(struct nand_chip *this, struct nand_bbt_descr *bd)
|
||||||
|
|
||||||
res = check_create(this, buf, bd);
|
res = check_create(this, buf, bd);
|
||||||
if (res)
|
if (res)
|
||||||
goto err;
|
goto err_free_buf;
|
||||||
|
|
||||||
/* Prevent the bbt regions from erasing / writing */
|
/* Prevent the bbt regions from erasing / writing */
|
||||||
mark_bbt_region(this, td);
|
mark_bbt_region(this, td);
|
||||||
|
@ -1268,7 +1268,9 @@ static int nand_scan_bbt(struct nand_chip *this, struct nand_bbt_descr *bd)
|
||||||
vfree(buf);
|
vfree(buf);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
err:
|
err_free_buf:
|
||||||
|
vfree(buf);
|
||||||
|
err_free_bbt:
|
||||||
kfree(this->bbt);
|
kfree(this->bbt);
|
||||||
this->bbt = NULL;
|
this->bbt = NULL;
|
||||||
return res;
|
return res;
|
||||||
|
|
|
@ -1,304 +0,0 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-only
|
|
||||||
/*
|
|
||||||
* Copyright © 2009 Nuvoton technology corporation.
|
|
||||||
*
|
|
||||||
* Wan ZongShun <mcuos.com@gmail.com>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <linux/slab.h>
|
|
||||||
#include <linux/module.h>
|
|
||||||
#include <linux/interrupt.h>
|
|
||||||
#include <linux/io.h>
|
|
||||||
#include <linux/platform_device.h>
|
|
||||||
#include <linux/delay.h>
|
|
||||||
#include <linux/clk.h>
|
|
||||||
#include <linux/err.h>
|
|
||||||
|
|
||||||
#include <linux/mtd/mtd.h>
|
|
||||||
#include <linux/mtd/rawnand.h>
|
|
||||||
#include <linux/mtd/partitions.h>
|
|
||||||
|
|
||||||
#define REG_FMICSR 0x00
|
|
||||||
#define REG_SMCSR 0xa0
|
|
||||||
#define REG_SMISR 0xac
|
|
||||||
#define REG_SMCMD 0xb0
|
|
||||||
#define REG_SMADDR 0xb4
|
|
||||||
#define REG_SMDATA 0xb8
|
|
||||||
|
|
||||||
#define RESET_FMI 0x01
|
|
||||||
#define NAND_EN 0x08
|
|
||||||
#define READYBUSY (0x01 << 18)
|
|
||||||
|
|
||||||
#define SWRST 0x01
|
|
||||||
#define PSIZE (0x01 << 3)
|
|
||||||
#define DMARWEN (0x03 << 1)
|
|
||||||
#define BUSWID (0x01 << 4)
|
|
||||||
#define ECC4EN (0x01 << 5)
|
|
||||||
#define WP (0x01 << 24)
|
|
||||||
#define NANDCS (0x01 << 25)
|
|
||||||
#define ENDADDR (0x01 << 31)
|
|
||||||
|
|
||||||
#define read_data_reg(dev) \
|
|
||||||
__raw_readl((dev)->reg + REG_SMDATA)
|
|
||||||
|
|
||||||
#define write_data_reg(dev, val) \
|
|
||||||
__raw_writel((val), (dev)->reg + REG_SMDATA)
|
|
||||||
|
|
||||||
#define write_cmd_reg(dev, val) \
|
|
||||||
__raw_writel((val), (dev)->reg + REG_SMCMD)
|
|
||||||
|
|
||||||
#define write_addr_reg(dev, val) \
|
|
||||||
__raw_writel((val), (dev)->reg + REG_SMADDR)
|
|
||||||
|
|
||||||
struct nuc900_nand {
|
|
||||||
struct nand_chip chip;
|
|
||||||
void __iomem *reg;
|
|
||||||
struct clk *clk;
|
|
||||||
spinlock_t lock;
|
|
||||||
};
|
|
||||||
|
|
||||||
static inline struct nuc900_nand *mtd_to_nuc900(struct mtd_info *mtd)
|
|
||||||
{
|
|
||||||
return container_of(mtd_to_nand(mtd), struct nuc900_nand, chip);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const struct mtd_partition partitions[] = {
|
|
||||||
{
|
|
||||||
.name = "NAND FS 0",
|
|
||||||
.offset = 0,
|
|
||||||
.size = 8 * 1024 * 1024
|
|
||||||
},
|
|
||||||
{
|
|
||||||
.name = "NAND FS 1",
|
|
||||||
.offset = MTDPART_OFS_APPEND,
|
|
||||||
.size = MTDPART_SIZ_FULL
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static unsigned char nuc900_nand_read_byte(struct nand_chip *chip)
|
|
||||||
{
|
|
||||||
unsigned char ret;
|
|
||||||
struct nuc900_nand *nand = mtd_to_nuc900(nand_to_mtd(chip));
|
|
||||||
|
|
||||||
ret = (unsigned char)read_data_reg(nand);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nuc900_nand_read_buf(struct nand_chip *chip,
|
|
||||||
unsigned char *buf, int len)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
struct nuc900_nand *nand = mtd_to_nuc900(nand_to_mtd(chip));
|
|
||||||
|
|
||||||
for (i = 0; i < len; i++)
|
|
||||||
buf[i] = (unsigned char)read_data_reg(nand);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nuc900_nand_write_buf(struct nand_chip *chip,
|
|
||||||
const unsigned char *buf, int len)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
struct nuc900_nand *nand = mtd_to_nuc900(nand_to_mtd(chip));
|
|
||||||
|
|
||||||
for (i = 0; i < len; i++)
|
|
||||||
write_data_reg(nand, buf[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int nuc900_check_rb(struct nuc900_nand *nand)
|
|
||||||
{
|
|
||||||
unsigned int val;
|
|
||||||
spin_lock(&nand->lock);
|
|
||||||
val = __raw_readl(nand->reg + REG_SMISR);
|
|
||||||
val &= READYBUSY;
|
|
||||||
spin_unlock(&nand->lock);
|
|
||||||
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int nuc900_nand_devready(struct nand_chip *chip)
|
|
||||||
{
|
|
||||||
struct nuc900_nand *nand = mtd_to_nuc900(nand_to_mtd(chip));
|
|
||||||
int ready;
|
|
||||||
|
|
||||||
ready = (nuc900_check_rb(nand)) ? 1 : 0;
|
|
||||||
return ready;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void nuc900_nand_command_lp(struct nand_chip *chip,
|
|
||||||
unsigned int command,
|
|
||||||
int column, int page_addr)
|
|
||||||
{
|
|
||||||
struct mtd_info *mtd = nand_to_mtd(chip);
|
|
||||||
struct nuc900_nand *nand = mtd_to_nuc900(mtd);
|
|
||||||
|
|
||||||
if (command == NAND_CMD_READOOB) {
|
|
||||||
column += mtd->writesize;
|
|
||||||
command = NAND_CMD_READ0;
|
|
||||||
}
|
|
||||||
|
|
||||||
write_cmd_reg(nand, command & 0xff);
|
|
||||||
|
|
||||||
if (column != -1 || page_addr != -1) {
|
|
||||||
|
|
||||||
if (column != -1) {
|
|
||||||
if (chip->options & NAND_BUSWIDTH_16 &&
|
|
||||||
!nand_opcode_8bits(command))
|
|
||||||
column >>= 1;
|
|
||||||
write_addr_reg(nand, column);
|
|
||||||
write_addr_reg(nand, column >> 8 | ENDADDR);
|
|
||||||
}
|
|
||||||
if (page_addr != -1) {
|
|
||||||
write_addr_reg(nand, page_addr);
|
|
||||||
|
|
||||||
if (chip->options & NAND_ROW_ADDR_3) {
|
|
||||||
write_addr_reg(nand, page_addr >> 8);
|
|
||||||
write_addr_reg(nand, page_addr >> 16 | ENDADDR);
|
|
||||||
} else {
|
|
||||||
write_addr_reg(nand, page_addr >> 8 | ENDADDR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (command) {
|
|
||||||
case NAND_CMD_CACHEDPROG:
|
|
||||||
case NAND_CMD_PAGEPROG:
|
|
||||||
case NAND_CMD_ERASE1:
|
|
||||||
case NAND_CMD_ERASE2:
|
|
||||||
case NAND_CMD_SEQIN:
|
|
||||||
case NAND_CMD_RNDIN:
|
|
||||||
case NAND_CMD_STATUS:
|
|
||||||
return;
|
|
||||||
|
|
||||||
case NAND_CMD_RESET:
|
|
||||||
if (chip->legacy.dev_ready)
|
|
||||||
break;
|
|
||||||
udelay(chip->legacy.chip_delay);
|
|
||||||
|
|
||||||
write_cmd_reg(nand, NAND_CMD_STATUS);
|
|
||||||
write_cmd_reg(nand, command);
|
|
||||||
|
|
||||||
while (!nuc900_check_rb(nand))
|
|
||||||
;
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
case NAND_CMD_RNDOUT:
|
|
||||||
write_cmd_reg(nand, NAND_CMD_RNDOUTSTART);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case NAND_CMD_READ0:
|
|
||||||
write_cmd_reg(nand, NAND_CMD_READSTART);
|
|
||||||
/* fall through */
|
|
||||||
|
|
||||||
default:
|
|
||||||
|
|
||||||
if (!chip->legacy.dev_ready) {
|
|
||||||
udelay(chip->legacy.chip_delay);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Apply this short delay always to ensure that we do wait tWB in
|
|
||||||
* any case on any machine. */
|
|
||||||
ndelay(100);
|
|
||||||
|
|
||||||
while (!chip->legacy.dev_ready(chip))
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void nuc900_nand_enable(struct nuc900_nand *nand)
|
|
||||||
{
|
|
||||||
unsigned int val;
|
|
||||||
spin_lock(&nand->lock);
|
|
||||||
__raw_writel(RESET_FMI, (nand->reg + REG_FMICSR));
|
|
||||||
|
|
||||||
val = __raw_readl(nand->reg + REG_FMICSR);
|
|
||||||
|
|
||||||
if (!(val & NAND_EN))
|
|
||||||
__raw_writel(val | NAND_EN, nand->reg + REG_FMICSR);
|
|
||||||
|
|
||||||
val = __raw_readl(nand->reg + REG_SMCSR);
|
|
||||||
|
|
||||||
val &= ~(SWRST|PSIZE|DMARWEN|BUSWID|ECC4EN|NANDCS);
|
|
||||||
val |= WP;
|
|
||||||
|
|
||||||
__raw_writel(val, nand->reg + REG_SMCSR);
|
|
||||||
|
|
||||||
spin_unlock(&nand->lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int nuc900_nand_probe(struct platform_device *pdev)
|
|
||||||
{
|
|
||||||
struct nuc900_nand *nuc900_nand;
|
|
||||||
struct nand_chip *chip;
|
|
||||||
struct mtd_info *mtd;
|
|
||||||
struct resource *res;
|
|
||||||
|
|
||||||
nuc900_nand = devm_kzalloc(&pdev->dev, sizeof(struct nuc900_nand),
|
|
||||||
GFP_KERNEL);
|
|
||||||
if (!nuc900_nand)
|
|
||||||
return -ENOMEM;
|
|
||||||
chip = &(nuc900_nand->chip);
|
|
||||||
mtd = nand_to_mtd(chip);
|
|
||||||
|
|
||||||
mtd->dev.parent = &pdev->dev;
|
|
||||||
spin_lock_init(&nuc900_nand->lock);
|
|
||||||
|
|
||||||
nuc900_nand->clk = devm_clk_get(&pdev->dev, NULL);
|
|
||||||
if (IS_ERR(nuc900_nand->clk))
|
|
||||||
return -ENOENT;
|
|
||||||
clk_enable(nuc900_nand->clk);
|
|
||||||
|
|
||||||
chip->legacy.cmdfunc = nuc900_nand_command_lp;
|
|
||||||
chip->legacy.dev_ready = nuc900_nand_devready;
|
|
||||||
chip->legacy.read_byte = nuc900_nand_read_byte;
|
|
||||||
chip->legacy.write_buf = nuc900_nand_write_buf;
|
|
||||||
chip->legacy.read_buf = nuc900_nand_read_buf;
|
|
||||||
chip->legacy.chip_delay = 50;
|
|
||||||
chip->options = 0;
|
|
||||||
chip->ecc.mode = NAND_ECC_SOFT;
|
|
||||||
chip->ecc.algo = NAND_ECC_HAMMING;
|
|
||||||
|
|
||||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
||||||
nuc900_nand->reg = devm_ioremap_resource(&pdev->dev, res);
|
|
||||||
if (IS_ERR(nuc900_nand->reg))
|
|
||||||
return PTR_ERR(nuc900_nand->reg);
|
|
||||||
|
|
||||||
nuc900_nand_enable(nuc900_nand);
|
|
||||||
|
|
||||||
if (nand_scan(chip, 1))
|
|
||||||
return -ENXIO;
|
|
||||||
|
|
||||||
mtd_device_register(mtd, partitions, ARRAY_SIZE(partitions));
|
|
||||||
|
|
||||||
platform_set_drvdata(pdev, nuc900_nand);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int nuc900_nand_remove(struct platform_device *pdev)
|
|
||||||
{
|
|
||||||
struct nuc900_nand *nuc900_nand = platform_get_drvdata(pdev);
|
|
||||||
|
|
||||||
nand_release(&nuc900_nand->chip);
|
|
||||||
clk_disable(nuc900_nand->clk);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct platform_driver nuc900_nand_driver = {
|
|
||||||
.probe = nuc900_nand_probe,
|
|
||||||
.remove = nuc900_nand_remove,
|
|
||||||
.driver = {
|
|
||||||
.name = "nuc900-fmi",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
module_platform_driver(nuc900_nand_driver);
|
|
||||||
|
|
||||||
MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>");
|
|
||||||
MODULE_DESCRIPTION("w90p910/NUC9xx nand driver!");
|
|
||||||
MODULE_LICENSE("GPL");
|
|
||||||
MODULE_ALIAS("platform:nuc900-fmi");
|
|
|
@ -1501,7 +1501,7 @@ static int omap_elm_correct_data(struct nand_chip *chip, u_char *data,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update number of correctable errors */
|
/* Update number of correctable errors */
|
||||||
stat += err_vec[i].error_count;
|
stat = max_t(unsigned int, stat, err_vec[i].error_count);
|
||||||
|
|
||||||
/* Update page data with sector size */
|
/* Update page data with sector size */
|
||||||
data += ecc->size;
|
data += ecc->size;
|
||||||
|
|
|
@ -116,7 +116,7 @@ static int oxnas_nand_probe(struct platform_device *pdev)
|
||||||
GFP_KERNEL);
|
GFP_KERNEL);
|
||||||
if (!chip) {
|
if (!chip) {
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
goto err_clk_unprepare;
|
goto err_release_child;
|
||||||
}
|
}
|
||||||
|
|
||||||
chip->controller = &oxnas->base;
|
chip->controller = &oxnas->base;
|
||||||
|
@ -137,12 +137,12 @@ static int oxnas_nand_probe(struct platform_device *pdev)
|
||||||
/* Scan to find existence of the device */
|
/* Scan to find existence of the device */
|
||||||
err = nand_scan(chip, 1);
|
err = nand_scan(chip, 1);
|
||||||
if (err)
|
if (err)
|
||||||
goto err_clk_unprepare;
|
goto err_release_child;
|
||||||
|
|
||||||
err = mtd_device_register(mtd, NULL, 0);
|
err = mtd_device_register(mtd, NULL, 0);
|
||||||
if (err) {
|
if (err) {
|
||||||
nand_release(chip);
|
nand_release(chip);
|
||||||
goto err_clk_unprepare;
|
goto err_release_child;
|
||||||
}
|
}
|
||||||
|
|
||||||
oxnas->chips[nchips] = chip;
|
oxnas->chips[nchips] = chip;
|
||||||
|
@ -159,6 +159,8 @@ static int oxnas_nand_probe(struct platform_device *pdev)
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
err_release_child:
|
||||||
|
of_node_put(nand_np);
|
||||||
err_clk_unprepare:
|
err_clk_unprepare:
|
||||||
clk_disable_unprepare(oxnas->clk);
|
clk_disable_unprepare(oxnas->clk);
|
||||||
return err;
|
return err;
|
||||||
|
|
|
@ -998,7 +998,7 @@ static void r852_shutdown(struct pci_dev *pci_dev)
|
||||||
#ifdef CONFIG_PM_SLEEP
|
#ifdef CONFIG_PM_SLEEP
|
||||||
static int r852_suspend(struct device *device)
|
static int r852_suspend(struct device *device)
|
||||||
{
|
{
|
||||||
struct r852_device *dev = pci_get_drvdata(to_pci_dev(device));
|
struct r852_device *dev = dev_get_drvdata(device);
|
||||||
|
|
||||||
if (dev->ctlreg & R852_CTL_CARDENABLE)
|
if (dev->ctlreg & R852_CTL_CARDENABLE)
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
|
@ -1019,7 +1019,7 @@ static int r852_suspend(struct device *device)
|
||||||
|
|
||||||
static int r852_resume(struct device *device)
|
static int r852_resume(struct device *device)
|
||||||
{
|
{
|
||||||
struct r852_device *dev = pci_get_drvdata(to_pci_dev(device));
|
struct r852_device *dev = dev_get_drvdata(device);
|
||||||
|
|
||||||
r852_disable_irqs(dev);
|
r852_disable_irqs(dev);
|
||||||
r852_card_update_present(dev);
|
r852_card_update_present(dev);
|
||||||
|
|
|
@ -1427,21 +1427,16 @@ static void stm32_fmc2_calc_timings(struct nand_chip *chip,
|
||||||
struct stm32_fmc2_timings *tims = &nand->timings;
|
struct stm32_fmc2_timings *tims = &nand->timings;
|
||||||
unsigned long hclk = clk_get_rate(fmc2->clk);
|
unsigned long hclk = clk_get_rate(fmc2->clk);
|
||||||
unsigned long hclkp = NSEC_PER_SEC / (hclk / 1000);
|
unsigned long hclkp = NSEC_PER_SEC / (hclk / 1000);
|
||||||
int tar, tclr, thiz, twait, tset_mem, tset_att, thold_mem, thold_att;
|
unsigned long timing, tar, tclr, thiz, twait;
|
||||||
|
unsigned long tset_mem, tset_att, thold_mem, thold_att;
|
||||||
|
|
||||||
tar = hclkp;
|
tar = max_t(unsigned long, hclkp, sdrt->tAR_min);
|
||||||
if (tar < sdrt->tAR_min)
|
timing = DIV_ROUND_UP(tar, hclkp) - 1;
|
||||||
tar = sdrt->tAR_min;
|
tims->tar = min_t(unsigned long, timing, FMC2_PCR_TIMING_MASK);
|
||||||
tims->tar = DIV_ROUND_UP(tar, hclkp) - 1;
|
|
||||||
if (tims->tar > FMC2_PCR_TIMING_MASK)
|
|
||||||
tims->tar = FMC2_PCR_TIMING_MASK;
|
|
||||||
|
|
||||||
tclr = hclkp;
|
tclr = max_t(unsigned long, hclkp, sdrt->tCLR_min);
|
||||||
if (tclr < sdrt->tCLR_min)
|
timing = DIV_ROUND_UP(tclr, hclkp) - 1;
|
||||||
tclr = sdrt->tCLR_min;
|
tims->tclr = min_t(unsigned long, timing, FMC2_PCR_TIMING_MASK);
|
||||||
tims->tclr = DIV_ROUND_UP(tclr, hclkp) - 1;
|
|
||||||
if (tims->tclr > FMC2_PCR_TIMING_MASK)
|
|
||||||
tims->tclr = FMC2_PCR_TIMING_MASK;
|
|
||||||
|
|
||||||
tims->thiz = FMC2_THIZ;
|
tims->thiz = FMC2_THIZ;
|
||||||
thiz = (tims->thiz + 1) * hclkp;
|
thiz = (tims->thiz + 1) * hclkp;
|
||||||
|
@ -1451,18 +1446,11 @@ static void stm32_fmc2_calc_timings(struct nand_chip *chip,
|
||||||
* tWAIT > tWP
|
* tWAIT > tWP
|
||||||
* tWAIT > tREA + tIO
|
* tWAIT > tREA + tIO
|
||||||
*/
|
*/
|
||||||
twait = hclkp;
|
twait = max_t(unsigned long, hclkp, sdrt->tRP_min);
|
||||||
if (twait < sdrt->tRP_min)
|
twait = max_t(unsigned long, twait, sdrt->tWP_min);
|
||||||
twait = sdrt->tRP_min;
|
twait = max_t(unsigned long, twait, sdrt->tREA_max + FMC2_TIO);
|
||||||
if (twait < sdrt->tWP_min)
|
timing = DIV_ROUND_UP(twait, hclkp);
|
||||||
twait = sdrt->tWP_min;
|
tims->twait = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK);
|
||||||
if (twait < sdrt->tREA_max + FMC2_TIO)
|
|
||||||
twait = sdrt->tREA_max + FMC2_TIO;
|
|
||||||
tims->twait = DIV_ROUND_UP(twait, hclkp);
|
|
||||||
if (tims->twait == 0)
|
|
||||||
tims->twait = 1;
|
|
||||||
else if (tims->twait > FMC2_PMEM_PATT_TIMING_MASK)
|
|
||||||
tims->twait = FMC2_PMEM_PATT_TIMING_MASK;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* tSETUP_MEM > tCS - tWAIT
|
* tSETUP_MEM > tCS - tWAIT
|
||||||
|
@ -1477,20 +1465,15 @@ static void stm32_fmc2_calc_timings(struct nand_chip *chip,
|
||||||
if (twait > thiz && (sdrt->tDS_min > twait - thiz) &&
|
if (twait > thiz && (sdrt->tDS_min > twait - thiz) &&
|
||||||
(tset_mem < sdrt->tDS_min - (twait - thiz)))
|
(tset_mem < sdrt->tDS_min - (twait - thiz)))
|
||||||
tset_mem = sdrt->tDS_min - (twait - thiz);
|
tset_mem = sdrt->tDS_min - (twait - thiz);
|
||||||
tims->tset_mem = DIV_ROUND_UP(tset_mem, hclkp);
|
timing = DIV_ROUND_UP(tset_mem, hclkp);
|
||||||
if (tims->tset_mem == 0)
|
tims->tset_mem = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK);
|
||||||
tims->tset_mem = 1;
|
|
||||||
else if (tims->tset_mem > FMC2_PMEM_PATT_TIMING_MASK)
|
|
||||||
tims->tset_mem = FMC2_PMEM_PATT_TIMING_MASK;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* tHOLD_MEM > tCH
|
* tHOLD_MEM > tCH
|
||||||
* tHOLD_MEM > tREH - tSETUP_MEM
|
* tHOLD_MEM > tREH - tSETUP_MEM
|
||||||
* tHOLD_MEM > max(tRC, tWC) - (tSETUP_MEM + tWAIT)
|
* tHOLD_MEM > max(tRC, tWC) - (tSETUP_MEM + tWAIT)
|
||||||
*/
|
*/
|
||||||
thold_mem = hclkp;
|
thold_mem = max_t(unsigned long, hclkp, sdrt->tCH_min);
|
||||||
if (thold_mem < sdrt->tCH_min)
|
|
||||||
thold_mem = sdrt->tCH_min;
|
|
||||||
if (sdrt->tREH_min > tset_mem &&
|
if (sdrt->tREH_min > tset_mem &&
|
||||||
(thold_mem < sdrt->tREH_min - tset_mem))
|
(thold_mem < sdrt->tREH_min - tset_mem))
|
||||||
thold_mem = sdrt->tREH_min - tset_mem;
|
thold_mem = sdrt->tREH_min - tset_mem;
|
||||||
|
@ -1500,11 +1483,8 @@ static void stm32_fmc2_calc_timings(struct nand_chip *chip,
|
||||||
if ((sdrt->tWC_min > tset_mem + twait) &&
|
if ((sdrt->tWC_min > tset_mem + twait) &&
|
||||||
(thold_mem < sdrt->tWC_min - (tset_mem + twait)))
|
(thold_mem < sdrt->tWC_min - (tset_mem + twait)))
|
||||||
thold_mem = sdrt->tWC_min - (tset_mem + twait);
|
thold_mem = sdrt->tWC_min - (tset_mem + twait);
|
||||||
tims->thold_mem = DIV_ROUND_UP(thold_mem, hclkp);
|
timing = DIV_ROUND_UP(thold_mem, hclkp);
|
||||||
if (tims->thold_mem == 0)
|
tims->thold_mem = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK);
|
||||||
tims->thold_mem = 1;
|
|
||||||
else if (tims->thold_mem > FMC2_PMEM_PATT_TIMING_MASK)
|
|
||||||
tims->thold_mem = FMC2_PMEM_PATT_TIMING_MASK;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* tSETUP_ATT > tCS - tWAIT
|
* tSETUP_ATT > tCS - tWAIT
|
||||||
|
@ -1526,11 +1506,8 @@ static void stm32_fmc2_calc_timings(struct nand_chip *chip,
|
||||||
if (twait > thiz && (sdrt->tDS_min > twait - thiz) &&
|
if (twait > thiz && (sdrt->tDS_min > twait - thiz) &&
|
||||||
(tset_att < sdrt->tDS_min - (twait - thiz)))
|
(tset_att < sdrt->tDS_min - (twait - thiz)))
|
||||||
tset_att = sdrt->tDS_min - (twait - thiz);
|
tset_att = sdrt->tDS_min - (twait - thiz);
|
||||||
tims->tset_att = DIV_ROUND_UP(tset_att, hclkp);
|
timing = DIV_ROUND_UP(tset_att, hclkp);
|
||||||
if (tims->tset_att == 0)
|
tims->tset_att = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK);
|
||||||
tims->tset_att = 1;
|
|
||||||
else if (tims->tset_att > FMC2_PMEM_PATT_TIMING_MASK)
|
|
||||||
tims->tset_att = FMC2_PMEM_PATT_TIMING_MASK;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* tHOLD_ATT > tALH
|
* tHOLD_ATT > tALH
|
||||||
|
@ -1545,17 +1522,11 @@ static void stm32_fmc2_calc_timings(struct nand_chip *chip,
|
||||||
* tHOLD_ATT > tRC - (tSETUP_ATT + tWAIT)
|
* tHOLD_ATT > tRC - (tSETUP_ATT + tWAIT)
|
||||||
* tHOLD_ATT > tWC - (tSETUP_ATT + tWAIT)
|
* tHOLD_ATT > tWC - (tSETUP_ATT + tWAIT)
|
||||||
*/
|
*/
|
||||||
thold_att = hclkp;
|
thold_att = max_t(unsigned long, hclkp, sdrt->tALH_min);
|
||||||
if (thold_att < sdrt->tALH_min)
|
thold_att = max_t(unsigned long, thold_att, sdrt->tCH_min);
|
||||||
thold_att = sdrt->tALH_min;
|
thold_att = max_t(unsigned long, thold_att, sdrt->tCLH_min);
|
||||||
if (thold_att < sdrt->tCH_min)
|
thold_att = max_t(unsigned long, thold_att, sdrt->tCOH_min);
|
||||||
thold_att = sdrt->tCH_min;
|
thold_att = max_t(unsigned long, thold_att, sdrt->tDH_min);
|
||||||
if (thold_att < sdrt->tCLH_min)
|
|
||||||
thold_att = sdrt->tCLH_min;
|
|
||||||
if (thold_att < sdrt->tCOH_min)
|
|
||||||
thold_att = sdrt->tCOH_min;
|
|
||||||
if (thold_att < sdrt->tDH_min)
|
|
||||||
thold_att = sdrt->tDH_min;
|
|
||||||
if ((sdrt->tWB_max + FMC2_TIO + FMC2_TSYNC > tset_mem) &&
|
if ((sdrt->tWB_max + FMC2_TIO + FMC2_TSYNC > tset_mem) &&
|
||||||
(thold_att < sdrt->tWB_max + FMC2_TIO + FMC2_TSYNC - tset_mem))
|
(thold_att < sdrt->tWB_max + FMC2_TIO + FMC2_TSYNC - tset_mem))
|
||||||
thold_att = sdrt->tWB_max + FMC2_TIO + FMC2_TSYNC - tset_mem;
|
thold_att = sdrt->tWB_max + FMC2_TIO + FMC2_TSYNC - tset_mem;
|
||||||
|
@ -1574,11 +1545,8 @@ static void stm32_fmc2_calc_timings(struct nand_chip *chip,
|
||||||
if ((sdrt->tWC_min > tset_att + twait) &&
|
if ((sdrt->tWC_min > tset_att + twait) &&
|
||||||
(thold_att < sdrt->tWC_min - (tset_att + twait)))
|
(thold_att < sdrt->tWC_min - (tset_att + twait)))
|
||||||
thold_att = sdrt->tWC_min - (tset_att + twait);
|
thold_att = sdrt->tWC_min - (tset_att + twait);
|
||||||
tims->thold_att = DIV_ROUND_UP(thold_att, hclkp);
|
timing = DIV_ROUND_UP(thold_att, hclkp);
|
||||||
if (tims->thold_att == 0)
|
tims->thold_att = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK);
|
||||||
tims->thold_att = 1;
|
|
||||||
else if (tims->thold_att > FMC2_PMEM_PATT_TIMING_MASK)
|
|
||||||
tims->thold_att = FMC2_PMEM_PATT_TIMING_MASK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int stm32_fmc2_setup_interface(struct nand_chip *chip, int chipnr,
|
static int stm32_fmc2_setup_interface(struct nand_chip *chip, int chipnr,
|
||||||
|
|
|
@ -659,6 +659,7 @@ static int tango_nand_probe(struct platform_device *pdev)
|
||||||
err = chip_init(&pdev->dev, np);
|
err = chip_init(&pdev->dev, np);
|
||||||
if (err) {
|
if (err) {
|
||||||
tango_nand_remove(pdev);
|
tango_nand_remove(pdev);
|
||||||
|
of_node_put(np);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -862,6 +862,7 @@ static int vf610_nfc_probe(struct platform_device *pdev)
|
||||||
dev_err(nfc->dev,
|
dev_err(nfc->dev,
|
||||||
"Only one NAND chip supported!\n");
|
"Only one NAND chip supported!\n");
|
||||||
err = -EINVAL;
|
err = -EINVAL;
|
||||||
|
of_node_put(child);
|
||||||
goto err_disable_clk;
|
goto err_disable_clk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,72 @@
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
config MTD_AR7_PARTS
|
||||||
|
tristate "TI AR7 partitioning parser"
|
||||||
|
help
|
||||||
|
TI AR7 partitioning parser support
|
||||||
|
|
||||||
|
config MTD_BCM47XX_PARTS
|
||||||
|
tristate "BCM47XX partitioning parser"
|
||||||
|
depends on BCM47XX || ARCH_BCM_5301X
|
||||||
|
help
|
||||||
|
This provides partitions parser for devices based on BCM47xx
|
||||||
|
boards.
|
||||||
|
|
||||||
|
config MTD_BCM63XX_PARTS
|
||||||
|
tristate "BCM63XX CFE partitioning parser"
|
||||||
|
depends on BCM63XX || BMIPS_GENERIC || COMPILE_TEST
|
||||||
|
select CRC32
|
||||||
|
select MTD_PARSER_IMAGETAG
|
||||||
|
help
|
||||||
|
This provides partition parsing for BCM63xx devices with CFE
|
||||||
|
bootloaders.
|
||||||
|
|
||||||
|
config MTD_CMDLINE_PARTS
|
||||||
|
tristate "Command line partition table parsing"
|
||||||
|
depends on MTD
|
||||||
|
help
|
||||||
|
Allow generic configuration of the MTD partition tables via the kernel
|
||||||
|
command line. Multiple flash resources are supported for hardware where
|
||||||
|
different kinds of flash memory are available.
|
||||||
|
|
||||||
|
You will still need the parsing functions to be called by the driver
|
||||||
|
for your particular device. It won't happen automatically. The
|
||||||
|
SA1100 map driver (CONFIG_MTD_SA1100) has an option for this, for
|
||||||
|
example.
|
||||||
|
|
||||||
|
The format for the command line is as follows:
|
||||||
|
|
||||||
|
mtdparts=<mtddef>[;<mtddef]
|
||||||
|
<mtddef> := <mtd-id>:<partdef>[,<partdef>]
|
||||||
|
<partdef> := <size>[@offset][<name>][ro]
|
||||||
|
<mtd-id> := unique id used in mapping driver/device
|
||||||
|
<size> := standard linux memsize OR "-" to denote all
|
||||||
|
remaining space
|
||||||
|
<name> := (NAME)
|
||||||
|
|
||||||
|
Due to the way Linux handles the command line, no spaces are
|
||||||
|
allowed in the partition definition, including mtd id's and partition
|
||||||
|
names.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
1 flash resource (mtd-id "sa1100"), with 1 single writable partition:
|
||||||
|
mtdparts=sa1100:-
|
||||||
|
|
||||||
|
Same flash, but 2 named partitions, the first one being read-only:
|
||||||
|
mtdparts=sa1100:256k(ARMboot)ro,-(root)
|
||||||
|
|
||||||
|
If unsure, say 'N'.
|
||||||
|
|
||||||
|
config MTD_OF_PARTS
|
||||||
|
tristate "OpenFirmware (device tree) partitioning parser"
|
||||||
|
default y
|
||||||
|
depends on OF
|
||||||
|
help
|
||||||
|
This provides a open firmware device tree partition parser
|
||||||
|
which derives the partition map from the children of the
|
||||||
|
flash memory node, as described in
|
||||||
|
Documentation/devicetree/bindings/mtd/partition.txt.
|
||||||
|
|
||||||
config MTD_PARSER_IMAGETAG
|
config MTD_PARSER_IMAGETAG
|
||||||
tristate "Parser for BCM963XX Image Tag format partitions"
|
tristate "Parser for BCM963XX Image Tag format partitions"
|
||||||
depends on BCM63XX || BMIPS_GENERIC || COMPILE_TEST
|
depends on BCM63XX || BMIPS_GENERIC || COMPILE_TEST
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
# SPDX-License-Identifier: GPL-2.0-only
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
obj-$(CONFIG_MTD_AR7_PARTS) += ar7part.o
|
||||||
|
obj-$(CONFIG_MTD_BCM47XX_PARTS) += bcm47xxpart.o
|
||||||
|
obj-$(CONFIG_MTD_BCM63XX_PARTS) += bcm63xxpart.o
|
||||||
|
obj-$(CONFIG_MTD_CMDLINE_PARTS) += cmdlinepart.o
|
||||||
|
obj-$(CONFIG_MTD_OF_PARTS) += ofpart.o
|
||||||
obj-$(CONFIG_MTD_PARSER_IMAGETAG) += parser_imagetag.o
|
obj-$(CONFIG_MTD_PARSER_IMAGETAG) += parser_imagetag.o
|
||||||
obj-$(CONFIG_MTD_AFS_PARTS) += afs.o
|
obj-$(CONFIG_MTD_AFS_PARTS) += afs.o
|
||||||
obj-$(CONFIG_MTD_PARSER_TRX) += parser_trx.o
|
obj-$(CONFIG_MTD_PARSER_TRX) += parser_trx.o
|
||||||
|
|
|
@ -774,8 +774,11 @@ static int sm_init_zone(struct sm_ftl *ftl, int zone_num)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
/* Read the oob of first sector */
|
/* Read the oob of first sector */
|
||||||
if (sm_read_sector(ftl, zone_num, block, 0, NULL, &oob))
|
if (sm_read_sector(ftl, zone_num, block, 0, NULL, &oob)) {
|
||||||
|
kfifo_free(&zone->free_sectors);
|
||||||
|
kfree(zone->lba_to_phys_table);
|
||||||
return -EIO;
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
/* Test to see if block is erased. It is enough to test
|
/* Test to see if block is erased. It is enough to test
|
||||||
first sector, because erase happens in one shot */
|
first sector, because erase happens in one shot */
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
menuconfig MTD_SPI_NOR
|
menuconfig MTD_SPI_NOR
|
||||||
tristate "SPI-NOR device support"
|
tristate "SPI-NOR device support"
|
||||||
depends on MTD
|
depends on MTD
|
||||||
|
depends on MTD && SPI_MASTER
|
||||||
|
select SPI_MEM
|
||||||
help
|
help
|
||||||
This is the framework for the SPI NOR which can be used by the SPI
|
This is the framework for the SPI NOR which can be used by the SPI
|
||||||
device drivers and the SPI-NOR device driver.
|
device drivers and the SPI-NOR device driver.
|
||||||
|
|
|
@ -836,8 +836,10 @@ static int aspeed_smc_setup_flash(struct aspeed_smc_controller *controller,
|
||||||
controller->chips[cs] = chip;
|
controller->chips[cs] = chip;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ret)
|
if (ret) {
|
||||||
|
of_node_put(child);
|
||||||
aspeed_smc_unregister(controller);
|
aspeed_smc_unregister(controller);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include <linux/errno.h>
|
#include <linux/errno.h>
|
||||||
#include <linux/interrupt.h>
|
#include <linux/interrupt.h>
|
||||||
#include <linux/io.h>
|
#include <linux/io.h>
|
||||||
|
#include <linux/iopoll.h>
|
||||||
#include <linux/jiffies.h>
|
#include <linux/jiffies.h>
|
||||||
#include <linux/kernel.h>
|
#include <linux/kernel.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
|
@ -241,23 +242,13 @@ struct cqspi_driver_platdata {
|
||||||
|
|
||||||
#define CQSPI_IRQ_STATUS_MASK 0x1FFFF
|
#define CQSPI_IRQ_STATUS_MASK 0x1FFFF
|
||||||
|
|
||||||
static int cqspi_wait_for_bit(void __iomem *reg, const u32 mask, bool clear)
|
static int cqspi_wait_for_bit(void __iomem *reg, const u32 mask, bool clr)
|
||||||
{
|
{
|
||||||
unsigned long end = jiffies + msecs_to_jiffies(CQSPI_TIMEOUT_MS);
|
|
||||||
u32 val;
|
u32 val;
|
||||||
|
|
||||||
while (1) {
|
return readl_relaxed_poll_timeout(reg, val,
|
||||||
val = readl(reg);
|
(((clr ? ~val : val) & mask) == mask),
|
||||||
if (clear)
|
10, CQSPI_TIMEOUT_MS * 1000);
|
||||||
val = ~val;
|
|
||||||
val &= mask;
|
|
||||||
|
|
||||||
if (val == mask)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (time_after(jiffies, end))
|
|
||||||
return -ETIMEDOUT;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool cqspi_is_idle(struct cqspi_st *cqspi)
|
static bool cqspi_is_idle(struct cqspi_st *cqspi)
|
||||||
|
|
|
@ -401,6 +401,7 @@ static int hisi_spi_nor_register_all(struct hifmc_host *host)
|
||||||
|
|
||||||
if (host->num_chip == HIFMC_MAX_CHIP_NUM) {
|
if (host->num_chip == HIFMC_MAX_CHIP_NUM) {
|
||||||
dev_warn(dev, "Flash device number exceeds the maximum chipselect number\n");
|
dev_warn(dev, "Flash device number exceeds the maximum chipselect number\n");
|
||||||
|
of_node_put(np);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@ static const struct pci_device_id intel_spi_pci_ids[] = {
|
||||||
{ PCI_VDEVICE(INTEL, 0x19e0), (unsigned long)&bxt_info },
|
{ PCI_VDEVICE(INTEL, 0x19e0), (unsigned long)&bxt_info },
|
||||||
{ PCI_VDEVICE(INTEL, 0x34a4), (unsigned long)&bxt_info },
|
{ PCI_VDEVICE(INTEL, 0x34a4), (unsigned long)&bxt_info },
|
||||||
{ PCI_VDEVICE(INTEL, 0x4b24), (unsigned long)&bxt_info },
|
{ PCI_VDEVICE(INTEL, 0x4b24), (unsigned long)&bxt_info },
|
||||||
|
{ PCI_VDEVICE(INTEL, 0xa0a4), (unsigned long)&bxt_info },
|
||||||
{ PCI_VDEVICE(INTEL, 0xa1a4), (unsigned long)&bxt_info },
|
{ PCI_VDEVICE(INTEL, 0xa1a4), (unsigned long)&bxt_info },
|
||||||
{ PCI_VDEVICE(INTEL, 0xa224), (unsigned long)&bxt_info },
|
{ PCI_VDEVICE(INTEL, 0xa224), (unsigned long)&bxt_info },
|
||||||
{ },
|
{ },
|
||||||
|
|
|
@ -621,6 +621,8 @@ static ssize_t intel_spi_read(struct spi_nor *nor, loff_t from, size_t len,
|
||||||
switch (nor->read_opcode) {
|
switch (nor->read_opcode) {
|
||||||
case SPINOR_OP_READ:
|
case SPINOR_OP_READ:
|
||||||
case SPINOR_OP_READ_FAST:
|
case SPINOR_OP_READ_FAST:
|
||||||
|
case SPINOR_OP_READ_4B:
|
||||||
|
case SPINOR_OP_READ_FAST_4B:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -189,6 +189,9 @@ struct module; /* only needed for owner field in mtd_info */
|
||||||
*/
|
*/
|
||||||
struct mtd_debug_info {
|
struct mtd_debug_info {
|
||||||
struct dentry *dfs_dir;
|
struct dentry *dfs_dir;
|
||||||
|
|
||||||
|
const char *partname;
|
||||||
|
const char *partid;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mtd_info {
|
struct mtd_info {
|
||||||
|
|
|
@ -346,7 +346,7 @@ static inline unsigned int nanddev_ntargets(const struct nand_device *nand)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* nanddev_neraseblocks() - Get the total number of erasablocks
|
* nanddev_neraseblocks() - Get the total number of eraseblocks
|
||||||
* @nand: NAND device
|
* @nand: NAND device
|
||||||
*
|
*
|
||||||
* Return: the total number of eraseblocks exposed by @nand.
|
* Return: the total number of eraseblocks exposed by @nand.
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
* Copyright (C) 2008 Dmitry Baryshkov
|
* Copyright (C) 2008 Dmitry Baryshkov
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#ifndef _MTD_SHARPSL_H
|
||||||
|
#define _MTD_SHARPSL_H
|
||||||
|
|
||||||
#include <linux/mtd/rawnand.h>
|
#include <linux/mtd/rawnand.h>
|
||||||
#include <linux/mtd/nand_ecc.h>
|
#include <linux/mtd/nand_ecc.h>
|
||||||
#include <linux/mtd/partitions.h>
|
#include <linux/mtd/partitions.h>
|
||||||
|
@ -16,3 +19,5 @@ struct sharpsl_nand_platform_data {
|
||||||
unsigned int nr_partitions;
|
unsigned int nr_partitions;
|
||||||
const char *const *part_parsers;
|
const char *const *part_parsers;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#endif /* _MTD_SHARPSL_H */
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <linux/bitops.h>
|
#include <linux/bitops.h>
|
||||||
#include <linux/mtd/cfi.h>
|
#include <linux/mtd/cfi.h>
|
||||||
#include <linux/mtd/mtd.h>
|
#include <linux/mtd/mtd.h>
|
||||||
|
#include <linux/spi/spi-mem.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Manufacturer IDs
|
* Manufacturer IDs
|
||||||
|
@ -224,7 +225,6 @@ static inline u8 spi_nor_get_protocol_width(enum spi_nor_protocol proto)
|
||||||
return spi_nor_get_protocol_data_nbits(proto);
|
return spi_nor_get_protocol_data_nbits(proto);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SPI_NOR_MAX_CMD_SIZE 8
|
|
||||||
enum spi_nor_ops {
|
enum spi_nor_ops {
|
||||||
SPI_NOR_OPS_READ = 0,
|
SPI_NOR_OPS_READ = 0,
|
||||||
SPI_NOR_OPS_WRITE,
|
SPI_NOR_OPS_WRITE,
|
||||||
|
@ -237,12 +237,12 @@ enum spi_nor_option_flags {
|
||||||
SNOR_F_USE_FSR = BIT(0),
|
SNOR_F_USE_FSR = BIT(0),
|
||||||
SNOR_F_HAS_SR_TB = BIT(1),
|
SNOR_F_HAS_SR_TB = BIT(1),
|
||||||
SNOR_F_NO_OP_CHIP_ERASE = BIT(2),
|
SNOR_F_NO_OP_CHIP_ERASE = BIT(2),
|
||||||
SNOR_F_S3AN_ADDR_DEFAULT = BIT(3),
|
SNOR_F_READY_XSR_RDY = BIT(3),
|
||||||
SNOR_F_READY_XSR_RDY = BIT(4),
|
SNOR_F_USE_CLSR = BIT(4),
|
||||||
SNOR_F_USE_CLSR = BIT(5),
|
SNOR_F_BROKEN_RESET = BIT(5),
|
||||||
SNOR_F_BROKEN_RESET = BIT(6),
|
SNOR_F_4B_OPCODES = BIT(6),
|
||||||
SNOR_F_4B_OPCODES = BIT(7),
|
SNOR_F_HAS_4BAIT = BIT(7),
|
||||||
SNOR_F_HAS_4BAIT = BIT(8),
|
SNOR_F_HAS_LOCK = BIT(8),
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -333,130 +333,6 @@ struct spi_nor_erase_map {
|
||||||
u8 uniform_erase_type;
|
u8 uniform_erase_type;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* struct flash_info - Forward declaration of a structure used internally by
|
|
||||||
* spi_nor_scan()
|
|
||||||
*/
|
|
||||||
struct flash_info;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* struct spi_nor - Structure for defining a the SPI NOR layer
|
|
||||||
* @mtd: point to a mtd_info structure
|
|
||||||
* @lock: the lock for the read/write/erase/lock/unlock operations
|
|
||||||
* @dev: point to a spi device, or a spi nor controller device.
|
|
||||||
* @info: spi-nor part JDEC MFR id and other info
|
|
||||||
* @page_size: the page size of the SPI NOR
|
|
||||||
* @addr_width: number of address bytes
|
|
||||||
* @erase_opcode: the opcode for erasing a sector
|
|
||||||
* @read_opcode: the read opcode
|
|
||||||
* @read_dummy: the dummy needed by the read operation
|
|
||||||
* @program_opcode: the program opcode
|
|
||||||
* @sst_write_second: used by the SST write operation
|
|
||||||
* @flags: flag options for the current SPI-NOR (SNOR_F_*)
|
|
||||||
* @read_proto: the SPI protocol for read operations
|
|
||||||
* @write_proto: the SPI protocol for write operations
|
|
||||||
* @reg_proto the SPI protocol for read_reg/write_reg/erase operations
|
|
||||||
* @cmd_buf: used by the write_reg
|
|
||||||
* @erase_map: the erase map of the SPI NOR
|
|
||||||
* @prepare: [OPTIONAL] do some preparations for the
|
|
||||||
* read/write/erase/lock/unlock operations
|
|
||||||
* @unprepare: [OPTIONAL] do some post work after the
|
|
||||||
* read/write/erase/lock/unlock operations
|
|
||||||
* @read_reg: [DRIVER-SPECIFIC] read out the register
|
|
||||||
* @write_reg: [DRIVER-SPECIFIC] write data to the register
|
|
||||||
* @read: [DRIVER-SPECIFIC] read data from the SPI NOR
|
|
||||||
* @write: [DRIVER-SPECIFIC] write data to the SPI NOR
|
|
||||||
* @erase: [DRIVER-SPECIFIC] erase a sector of the SPI NOR
|
|
||||||
* at the offset @offs; if not provided by the driver,
|
|
||||||
* spi-nor will send the erase opcode via write_reg()
|
|
||||||
* @flash_lock: [FLASH-SPECIFIC] lock a region of the SPI NOR
|
|
||||||
* @flash_unlock: [FLASH-SPECIFIC] unlock a region of the SPI NOR
|
|
||||||
* @flash_is_locked: [FLASH-SPECIFIC] check if a region of the SPI NOR is
|
|
||||||
* @quad_enable: [FLASH-SPECIFIC] enables SPI NOR quad mode
|
|
||||||
* @clear_sr_bp: [FLASH-SPECIFIC] clears the Block Protection Bits from
|
|
||||||
* the SPI NOR Status Register.
|
|
||||||
* completely locked
|
|
||||||
* @priv: the private data
|
|
||||||
*/
|
|
||||||
struct spi_nor {
|
|
||||||
struct mtd_info mtd;
|
|
||||||
struct mutex lock;
|
|
||||||
struct device *dev;
|
|
||||||
const struct flash_info *info;
|
|
||||||
u32 page_size;
|
|
||||||
u8 addr_width;
|
|
||||||
u8 erase_opcode;
|
|
||||||
u8 read_opcode;
|
|
||||||
u8 read_dummy;
|
|
||||||
u8 program_opcode;
|
|
||||||
enum spi_nor_protocol read_proto;
|
|
||||||
enum spi_nor_protocol write_proto;
|
|
||||||
enum spi_nor_protocol reg_proto;
|
|
||||||
bool sst_write_second;
|
|
||||||
u32 flags;
|
|
||||||
u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];
|
|
||||||
struct spi_nor_erase_map erase_map;
|
|
||||||
|
|
||||||
int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops);
|
|
||||||
void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops);
|
|
||||||
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);
|
|
||||||
|
|
||||||
ssize_t (*read)(struct spi_nor *nor, loff_t from,
|
|
||||||
size_t len, u_char *read_buf);
|
|
||||||
ssize_t (*write)(struct spi_nor *nor, loff_t to,
|
|
||||||
size_t len, const u_char *write_buf);
|
|
||||||
int (*erase)(struct spi_nor *nor, loff_t offs);
|
|
||||||
|
|
||||||
int (*flash_lock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
|
|
||||||
int (*flash_unlock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
|
|
||||||
int (*flash_is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len);
|
|
||||||
int (*quad_enable)(struct spi_nor *nor);
|
|
||||||
int (*clear_sr_bp)(struct spi_nor *nor);
|
|
||||||
|
|
||||||
void *priv;
|
|
||||||
};
|
|
||||||
|
|
||||||
static u64 __maybe_unused
|
|
||||||
spi_nor_region_is_last(const struct spi_nor_erase_region *region)
|
|
||||||
{
|
|
||||||
return region->offset & SNOR_LAST_REGION;
|
|
||||||
}
|
|
||||||
|
|
||||||
static u64 __maybe_unused
|
|
||||||
spi_nor_region_end(const struct spi_nor_erase_region *region)
|
|
||||||
{
|
|
||||||
return (region->offset & ~SNOR_ERASE_FLAGS_MASK) + region->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __maybe_unused
|
|
||||||
spi_nor_region_mark_end(struct spi_nor_erase_region *region)
|
|
||||||
{
|
|
||||||
region->offset |= SNOR_LAST_REGION;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void __maybe_unused
|
|
||||||
spi_nor_region_mark_overlay(struct spi_nor_erase_region *region)
|
|
||||||
{
|
|
||||||
region->offset |= SNOR_OVERLAID_REGION;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool __maybe_unused spi_nor_has_uniform_erase(const struct spi_nor *nor)
|
|
||||||
{
|
|
||||||
return !!nor->erase_map.uniform_erase_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void spi_nor_set_flash_node(struct spi_nor *nor,
|
|
||||||
struct device_node *np)
|
|
||||||
{
|
|
||||||
mtd_set_of_node(&nor->mtd, np);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline struct device_node *spi_nor_get_flash_node(struct spi_nor *nor)
|
|
||||||
{
|
|
||||||
return mtd_get_of_node(&nor->mtd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct spi_nor_hwcaps - Structure for describing the hardware capabilies
|
* struct spi_nor_hwcaps - Structure for describing the hardware capabilies
|
||||||
* supported by the SPI controller (bus master).
|
* supported by the SPI controller (bus master).
|
||||||
|
@ -518,6 +394,257 @@ struct spi_nor_hwcaps {
|
||||||
#define SNOR_HWCAPS_PP_1_8_8 BIT(21)
|
#define SNOR_HWCAPS_PP_1_8_8 BIT(21)
|
||||||
#define SNOR_HWCAPS_PP_8_8_8 BIT(22)
|
#define SNOR_HWCAPS_PP_8_8_8 BIT(22)
|
||||||
|
|
||||||
|
#define SNOR_HWCAPS_X_X_X (SNOR_HWCAPS_READ_2_2_2 | \
|
||||||
|
SNOR_HWCAPS_READ_4_4_4 | \
|
||||||
|
SNOR_HWCAPS_READ_8_8_8 | \
|
||||||
|
SNOR_HWCAPS_PP_4_4_4 | \
|
||||||
|
SNOR_HWCAPS_PP_8_8_8)
|
||||||
|
|
||||||
|
#define SNOR_HWCAPS_DTR (SNOR_HWCAPS_READ_1_1_1_DTR | \
|
||||||
|
SNOR_HWCAPS_READ_1_2_2_DTR | \
|
||||||
|
SNOR_HWCAPS_READ_1_4_4_DTR | \
|
||||||
|
SNOR_HWCAPS_READ_1_8_8_DTR)
|
||||||
|
|
||||||
|
#define SNOR_HWCAPS_ALL (SNOR_HWCAPS_READ_MASK | \
|
||||||
|
SNOR_HWCAPS_PP_MASK)
|
||||||
|
|
||||||
|
struct spi_nor_read_command {
|
||||||
|
u8 num_mode_clocks;
|
||||||
|
u8 num_wait_states;
|
||||||
|
u8 opcode;
|
||||||
|
enum spi_nor_protocol proto;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct spi_nor_pp_command {
|
||||||
|
u8 opcode;
|
||||||
|
enum spi_nor_protocol proto;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum spi_nor_read_command_index {
|
||||||
|
SNOR_CMD_READ,
|
||||||
|
SNOR_CMD_READ_FAST,
|
||||||
|
SNOR_CMD_READ_1_1_1_DTR,
|
||||||
|
|
||||||
|
/* Dual SPI */
|
||||||
|
SNOR_CMD_READ_1_1_2,
|
||||||
|
SNOR_CMD_READ_1_2_2,
|
||||||
|
SNOR_CMD_READ_2_2_2,
|
||||||
|
SNOR_CMD_READ_1_2_2_DTR,
|
||||||
|
|
||||||
|
/* Quad SPI */
|
||||||
|
SNOR_CMD_READ_1_1_4,
|
||||||
|
SNOR_CMD_READ_1_4_4,
|
||||||
|
SNOR_CMD_READ_4_4_4,
|
||||||
|
SNOR_CMD_READ_1_4_4_DTR,
|
||||||
|
|
||||||
|
/* Octal SPI */
|
||||||
|
SNOR_CMD_READ_1_1_8,
|
||||||
|
SNOR_CMD_READ_1_8_8,
|
||||||
|
SNOR_CMD_READ_8_8_8,
|
||||||
|
SNOR_CMD_READ_1_8_8_DTR,
|
||||||
|
|
||||||
|
SNOR_CMD_READ_MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
enum spi_nor_pp_command_index {
|
||||||
|
SNOR_CMD_PP,
|
||||||
|
|
||||||
|
/* Quad SPI */
|
||||||
|
SNOR_CMD_PP_1_1_4,
|
||||||
|
SNOR_CMD_PP_1_4_4,
|
||||||
|
SNOR_CMD_PP_4_4_4,
|
||||||
|
|
||||||
|
/* Octal SPI */
|
||||||
|
SNOR_CMD_PP_1_1_8,
|
||||||
|
SNOR_CMD_PP_1_8_8,
|
||||||
|
SNOR_CMD_PP_8_8_8,
|
||||||
|
|
||||||
|
SNOR_CMD_PP_MAX
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Forward declaration that will be used in 'struct spi_nor_flash_parameter' */
|
||||||
|
struct spi_nor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct spi_nor_locking_ops - SPI NOR locking methods
|
||||||
|
* @lock: lock a region of the SPI NOR.
|
||||||
|
* @unlock: unlock a region of the SPI NOR.
|
||||||
|
* @is_locked: check if a region of the SPI NOR is completely locked
|
||||||
|
*/
|
||||||
|
struct spi_nor_locking_ops {
|
||||||
|
int (*lock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
|
||||||
|
int (*unlock)(struct spi_nor *nor, loff_t ofs, uint64_t len);
|
||||||
|
int (*is_locked)(struct spi_nor *nor, loff_t ofs, uint64_t len);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct spi_nor_flash_parameter - SPI NOR flash parameters and settings.
|
||||||
|
* Includes legacy flash parameters and settings that can be overwritten
|
||||||
|
* by the spi_nor_fixups hooks, or dynamically when parsing the JESD216
|
||||||
|
* Serial Flash Discoverable Parameters (SFDP) tables.
|
||||||
|
*
|
||||||
|
* @size: the flash memory density in bytes.
|
||||||
|
* @page_size: the page size of the SPI NOR flash memory.
|
||||||
|
* @hwcaps: describes the read and page program hardware
|
||||||
|
* capabilities.
|
||||||
|
* @reads: read capabilities ordered by priority: the higher index
|
||||||
|
* in the array, the higher priority.
|
||||||
|
* @page_programs: page program capabilities ordered by priority: the
|
||||||
|
* higher index in the array, the higher priority.
|
||||||
|
* @erase_map: the erase map parsed from the SFDP Sector Map Parameter
|
||||||
|
* Table.
|
||||||
|
* @quad_enable: enables SPI NOR quad mode.
|
||||||
|
* @set_4byte: puts the SPI NOR in 4 byte addressing mode.
|
||||||
|
* @convert_addr: converts an absolute address into something the flash
|
||||||
|
* will understand. Particularly useful when pagesize is
|
||||||
|
* not a power-of-2.
|
||||||
|
* @setup: configures the SPI NOR memory. Useful for SPI NOR
|
||||||
|
* flashes that have peculiarities to the SPI NOR standard
|
||||||
|
* e.g. different opcodes, specific address calculation,
|
||||||
|
* page size, etc.
|
||||||
|
* @locking_ops: SPI NOR locking methods.
|
||||||
|
*/
|
||||||
|
struct spi_nor_flash_parameter {
|
||||||
|
u64 size;
|
||||||
|
u32 page_size;
|
||||||
|
|
||||||
|
struct spi_nor_hwcaps hwcaps;
|
||||||
|
struct spi_nor_read_command reads[SNOR_CMD_READ_MAX];
|
||||||
|
struct spi_nor_pp_command page_programs[SNOR_CMD_PP_MAX];
|
||||||
|
|
||||||
|
struct spi_nor_erase_map erase_map;
|
||||||
|
|
||||||
|
int (*quad_enable)(struct spi_nor *nor);
|
||||||
|
int (*set_4byte)(struct spi_nor *nor, bool enable);
|
||||||
|
u32 (*convert_addr)(struct spi_nor *nor, u32 addr);
|
||||||
|
int (*setup)(struct spi_nor *nor, const struct spi_nor_hwcaps *hwcaps);
|
||||||
|
|
||||||
|
const struct spi_nor_locking_ops *locking_ops;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct flash_info - Forward declaration of a structure used internally by
|
||||||
|
* spi_nor_scan()
|
||||||
|
*/
|
||||||
|
struct flash_info;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct spi_nor - Structure for defining a the SPI NOR layer
|
||||||
|
* @mtd: point to a mtd_info structure
|
||||||
|
* @lock: the lock for the read/write/erase/lock/unlock operations
|
||||||
|
* @dev: point to a spi device, or a spi nor controller device.
|
||||||
|
* @spimem: point to the spi mem device
|
||||||
|
* @bouncebuf: bounce buffer used when the buffer passed by the MTD
|
||||||
|
* layer is not DMA-able
|
||||||
|
* @bouncebuf_size: size of the bounce buffer
|
||||||
|
* @info: spi-nor part JDEC MFR id and other info
|
||||||
|
* @page_size: the page size of the SPI NOR
|
||||||
|
* @addr_width: number of address bytes
|
||||||
|
* @erase_opcode: the opcode for erasing a sector
|
||||||
|
* @read_opcode: the read opcode
|
||||||
|
* @read_dummy: the dummy needed by the read operation
|
||||||
|
* @program_opcode: the program opcode
|
||||||
|
* @sst_write_second: used by the SST write operation
|
||||||
|
* @flags: flag options for the current SPI-NOR (SNOR_F_*)
|
||||||
|
* @read_proto: the SPI protocol for read operations
|
||||||
|
* @write_proto: the SPI protocol for write operations
|
||||||
|
* @reg_proto the SPI protocol for read_reg/write_reg/erase operations
|
||||||
|
* @prepare: [OPTIONAL] do some preparations for the
|
||||||
|
* read/write/erase/lock/unlock operations
|
||||||
|
* @unprepare: [OPTIONAL] do some post work after the
|
||||||
|
* read/write/erase/lock/unlock operations
|
||||||
|
* @read_reg: [DRIVER-SPECIFIC] read out the register
|
||||||
|
* @write_reg: [DRIVER-SPECIFIC] write data to the register
|
||||||
|
* @read: [DRIVER-SPECIFIC] read data from the SPI NOR
|
||||||
|
* @write: [DRIVER-SPECIFIC] write data to the SPI NOR
|
||||||
|
* @erase: [DRIVER-SPECIFIC] erase a sector of the SPI NOR
|
||||||
|
* at the offset @offs; if not provided by the driver,
|
||||||
|
* spi-nor will send the erase opcode via write_reg()
|
||||||
|
* @clear_sr_bp: [FLASH-SPECIFIC] clears the Block Protection Bits from
|
||||||
|
* the SPI NOR Status Register.
|
||||||
|
* @params: [FLASH-SPECIFIC] SPI-NOR flash parameters and settings.
|
||||||
|
* The structure includes legacy flash parameters and
|
||||||
|
* settings that can be overwritten by the spi_nor_fixups
|
||||||
|
* hooks, or dynamically when parsing the SFDP tables.
|
||||||
|
* @priv: the private data
|
||||||
|
*/
|
||||||
|
struct spi_nor {
|
||||||
|
struct mtd_info mtd;
|
||||||
|
struct mutex lock;
|
||||||
|
struct device *dev;
|
||||||
|
struct spi_mem *spimem;
|
||||||
|
u8 *bouncebuf;
|
||||||
|
size_t bouncebuf_size;
|
||||||
|
const struct flash_info *info;
|
||||||
|
u32 page_size;
|
||||||
|
u8 addr_width;
|
||||||
|
u8 erase_opcode;
|
||||||
|
u8 read_opcode;
|
||||||
|
u8 read_dummy;
|
||||||
|
u8 program_opcode;
|
||||||
|
enum spi_nor_protocol read_proto;
|
||||||
|
enum spi_nor_protocol write_proto;
|
||||||
|
enum spi_nor_protocol reg_proto;
|
||||||
|
bool sst_write_second;
|
||||||
|
u32 flags;
|
||||||
|
|
||||||
|
int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops);
|
||||||
|
void (*unprepare)(struct spi_nor *nor, enum spi_nor_ops ops);
|
||||||
|
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);
|
||||||
|
|
||||||
|
ssize_t (*read)(struct spi_nor *nor, loff_t from,
|
||||||
|
size_t len, u_char *read_buf);
|
||||||
|
ssize_t (*write)(struct spi_nor *nor, loff_t to,
|
||||||
|
size_t len, const u_char *write_buf);
|
||||||
|
int (*erase)(struct spi_nor *nor, loff_t offs);
|
||||||
|
|
||||||
|
int (*clear_sr_bp)(struct spi_nor *nor);
|
||||||
|
struct spi_nor_flash_parameter params;
|
||||||
|
|
||||||
|
void *priv;
|
||||||
|
};
|
||||||
|
|
||||||
|
static u64 __maybe_unused
|
||||||
|
spi_nor_region_is_last(const struct spi_nor_erase_region *region)
|
||||||
|
{
|
||||||
|
return region->offset & SNOR_LAST_REGION;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u64 __maybe_unused
|
||||||
|
spi_nor_region_end(const struct spi_nor_erase_region *region)
|
||||||
|
{
|
||||||
|
return (region->offset & ~SNOR_ERASE_FLAGS_MASK) + region->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __maybe_unused
|
||||||
|
spi_nor_region_mark_end(struct spi_nor_erase_region *region)
|
||||||
|
{
|
||||||
|
region->offset |= SNOR_LAST_REGION;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void __maybe_unused
|
||||||
|
spi_nor_region_mark_overlay(struct spi_nor_erase_region *region)
|
||||||
|
{
|
||||||
|
region->offset |= SNOR_OVERLAID_REGION;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool __maybe_unused spi_nor_has_uniform_erase(const struct spi_nor *nor)
|
||||||
|
{
|
||||||
|
return !!nor->params.erase_map.uniform_erase_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void spi_nor_set_flash_node(struct spi_nor *nor,
|
||||||
|
struct device_node *np)
|
||||||
|
{
|
||||||
|
mtd_set_of_node(&nor->mtd, np);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct device_node *spi_nor_get_flash_node(struct spi_nor *nor)
|
||||||
|
{
|
||||||
|
return mtd_get_of_node(&nor->mtd);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* spi_nor_scan() - scan the SPI NOR
|
* spi_nor_scan() - scan the SPI NOR
|
||||||
* @nor: the spi_nor structure
|
* @nor: the spi_nor structure
|
||||||
|
|
Loading…
Reference in New Issue