* MTD
Apart from preventing the mtdblk to run on top of ftl or ubiblk (which may cause security issues and has no meaning anyway), there are a few misc fixes. * Raw NAND Two meaningful changes this time. The conversion of the brcmnand driver to the ->exec_op() API, this series brought additional changes to the core in order to help controller drivers to handle themselves the WP pin during destructive operations when relevant. There is also a series bringing important fixes to the sequential read feature. As always, there is as well a whole bunch of miscellaneous W=1 fixes, together with a few runtime fixes (double free, timeout value, OOB layout, missing register initialization) and the usual load of remove callbacks turned into void (which led to switch the txx9ndfmc driver to use module_platform_driver()). * SPI NOR SPI NOR comes with die erase support for multi die flashes, with new octal protocols (1-1-8 and 1-8-8) parsed from SFDP and with an updated documentation about what the contributors shall consider when proposing flash additions or updates. Michael Walle stepped out from the reviewer role to maintainer. -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE9HuaYnbmDhq/XIDIJWrqGEe9VoQFAmWFeUsACgkQJWrqGEe9 VoSBzQgAsUDieAMF4zIo5QN6l+8DpDMrkOK1Z5l4B/3goA2ZUz4cs80Kj/53l/kO tD8Ckn5SA82ZrVZiCJS5D8yplB+4+IWFU9dV/TcoINafLew5R/bBqo4XwgfVgvwy a4PuFlV9eedDW18cfbZA29TsnKoWdGaWxsyY+Gceukm94VuQbaZIPs3wkmBdWEOM V+FZaWg7vLW99x2XFDNpBqKFSzjTPAt1W5WM2ASdrb3pSKVOlt02qFlvMFwodVeR YExYwd1BNNsn9I6lKF/07a5wdX4NygXzqIpYytIaTzeBV3iRgN59uMfWbOh6tHeu MOEnmWoc3RwsyBXlBTKGafk2DTB6zg== =gbYM -----END PGP SIGNATURE----- Merge tag 'mtd/for-6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux Pull mtd updates from Miquel Raynal: "MTD: - Apart from preventing the mtdblk to run on top of ftl or ubiblk (which may cause security issues and has no meaning anyway), there are a few misc fixes. Raw NAND: - Two meaningful changes this time. The conversion of the brcmnand driver to the ->exec_op() API, this series brought additional changes to the core in order to help controller drivers to handle themselves the WP pin during destructive operations when relevant. - There is also a series bringing important fixes to the sequential read feature. - As always, there is as well a whole bunch of miscellaneous W=1 fixes, together with a few runtime fixes (double free, timeout value, OOB layout, missing register initialization) and the usual load of remove callbacks turned into void (which led to switch the txx9ndfmc driver to use module_platform_driver()). SPI NOR: - SPI NOR comes with die erase support for multi die flashes, with new octal protocols (1-1-8 and 1-8-8) parsed from SFDP and with an updated documentation about what the contributors shall consider when proposing flash additions or updates. - Michael Walle stepped out from the reviewer role to maintainer" * tag 'mtd/for-6.8' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux: (39 commits) mtd: rawnand: Clarify conditions to enable continuous reads mtd: rawnand: Prevent sequential reads with on-die ECC engines mtd: rawnand: Fix core interference with sequential reads mtd: rawnand: Prevent crossing LUN boundaries during sequential reads mtd: Fix gluebi NULL pointer dereference caused by ftl notifier dt-bindings: mtd: partitions: u-boot: Fix typo mtd: rawnand: s3c2410: fix Excess struct member description kernel-doc warnings MAINTAINERS: change my mail to the kernel.org one mtd: spi-nor: sfdp: get the 1-1-8 and 1-8-8 protocol from SFDP mtd: spi-nor: drop superfluous debug prints mtd: spi-nor: sysfs: hide the flash name if not set mtd: spi-nor: mark the flash name as obsolete mtd: spi-nor: print flash ID instead of name mtd: maps: vmu-flash: Fix the (mtd core) switch to ref counters mtd: ssfdc: Remove an unused variable mtd: rawnand: diskonchip: fix a potential double free in doc_probe mtd: rawnand: rockchip: Add missing title to a kernel doc comment mtd: rawnand: rockchip: Rename a structure mtd: rawnand: pl353: Fix kernel doc mtd: spi-nor: micron-st: Add support for mt25qu01g ...
This commit is contained in:
commit
35f11a3710
@ -25,6 +25,9 @@ KernelVersion: 5.14
|
||||
Contact: linux-mtd@lists.infradead.org
|
||||
Description: (RO) Part name of the SPI NOR flash.
|
||||
|
||||
The attribute is optional. User space should not rely on
|
||||
it to be present or even correct. Instead, user space
|
||||
should read the jedec_id attribute.
|
||||
|
||||
What: /sys/bus/spi/devices/.../spi-nor/sfdp
|
||||
Date: April 2021
|
||||
|
@ -7,7 +7,7 @@ $schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
title: U-Boot bootloader partition
|
||||
|
||||
description: |
|
||||
U-Boot is a bootlodaer commonly used in embedded devices. It's almost always
|
||||
U-Boot is a bootloader commonly used in embedded devices. It's almost always
|
||||
located on some kind of flash device.
|
||||
|
||||
Device configuration is stored as a set of environment variables that are
|
||||
|
@ -2,64 +2,204 @@
|
||||
SPI NOR framework
|
||||
=================
|
||||
|
||||
Part I - Why do we need this framework?
|
||||
---------------------------------------
|
||||
How to propose a new flash addition
|
||||
-----------------------------------
|
||||
|
||||
SPI bus controllers (drivers/spi/) only deal with streams of bytes; the bus
|
||||
controller operates agnostic of the specific device attached. However, some
|
||||
controllers (such as Freescale's QuadSPI controller) cannot easily handle
|
||||
arbitrary streams of bytes, but rather are designed specifically for SPI NOR.
|
||||
Most SPI NOR flashes comply with the JEDEC JESD216
|
||||
Serial Flash Discoverable Parameter (SFDP) standard. SFDP describes
|
||||
the functional and feature capabilities of serial flash devices in a
|
||||
standard set of internal read-only parameter tables.
|
||||
|
||||
In particular, Freescale's QuadSPI controller must know the NOR commands to
|
||||
find the right LUT sequence. Unfortunately, the SPI subsystem has no notion of
|
||||
opcodes, addresses, or data payloads; a SPI controller simply knows to send or
|
||||
receive bytes (Tx and Rx). Therefore, we must define a new layering scheme under
|
||||
which the controller driver is aware of the opcodes, addressing, and other
|
||||
details of the SPI NOR protocol.
|
||||
The SPI NOR driver queries the SFDP tables in order to determine the
|
||||
flash's parameters and settings. If the flash defines the SFDP tables
|
||||
it's likely that you won't need a flash entry at all, and instead
|
||||
rely on the generic flash driver which probes the flash solely based
|
||||
on its SFDP data. All one has to do is to specify the "jedec,spi-nor"
|
||||
compatible in the device tree.
|
||||
|
||||
Part II - How does the framework work?
|
||||
--------------------------------------
|
||||
There are cases however where you need to define an explicit flash
|
||||
entry. This typically happens when the flash has settings or support
|
||||
that is not covered by the SFDP tables (e.g. Block Protection), or
|
||||
when the flash contains mangled SFDP data. If the later, one needs
|
||||
to implement the ``spi_nor_fixups`` hooks in order to amend the SFDP
|
||||
parameters with the correct values.
|
||||
|
||||
This framework just adds a new layer between the MTD and the SPI bus driver.
|
||||
With this new layer, the SPI NOR controller driver does not depend on the
|
||||
m25p80 code anymore.
|
||||
Minimum testing requirements
|
||||
-----------------------------
|
||||
|
||||
Before this framework, the layer is like::
|
||||
Do all the tests from below and paste them in the commit's comments
|
||||
section, after the ``---`` marker.
|
||||
|
||||
MTD
|
||||
------------------------
|
||||
m25p80
|
||||
------------------------
|
||||
SPI bus driver
|
||||
------------------------
|
||||
SPI NOR chip
|
||||
1) Specify the controller that you used to test the flash and specify
|
||||
the frequency at which the flash was operated, e.g.::
|
||||
|
||||
After this framework, the layer is like::
|
||||
This flash is populated on the X board and was tested at Y
|
||||
frequency using the Z (put compatible) SPI controller.
|
||||
|
||||
MTD
|
||||
------------------------
|
||||
SPI NOR framework
|
||||
------------------------
|
||||
m25p80
|
||||
------------------------
|
||||
SPI bus driver
|
||||
------------------------
|
||||
SPI NOR chip
|
||||
2) Dump the sysfs entries and print the md5/sha1/sha256 SFDP checksum::
|
||||
|
||||
With the SPI NOR controller driver (Freescale QuadSPI), it looks like::
|
||||
root@1:~# cat /sys/bus/spi/devices/spi0.0/spi-nor/partname
|
||||
sst26vf064b
|
||||
root@1:~# cat /sys/bus/spi/devices/spi0.0/spi-nor/jedec_id
|
||||
bf2643
|
||||
root@1:~# cat /sys/bus/spi/devices/spi0.0/spi-nor/manufacturer
|
||||
sst
|
||||
root@1:~# xxd -p /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
|
||||
53464450060102ff00060110300000ff81000106000100ffbf0001180002
|
||||
0001fffffffffffffffffffffffffffffffffd20f1ffffffff0344eb086b
|
||||
083b80bbfeffffffffff00ffffff440b0c200dd80fd810d820914824806f
|
||||
1d81ed0f773830b030b0f7ffffff29c25cfff030c080ffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffff0004fff37f0000f57f0000f9ff
|
||||
7d00f57f0000f37f0000ffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
ffffbf2643ffb95ffdff30f260f332ff0a122346ff0f19320f1919ffffff
|
||||
ffffffff00669938ff05013506040232b03072428de89888a585c09faf5a
|
||||
ffff06ec060c0003080bffffffffff07ffff0202ff060300fdfd040700fc
|
||||
0300fefe0202070e
|
||||
root@1:~# sha256sum /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
|
||||
428f34d0461876f189ac97f93e68a05fa6428c6650b3b7baf736a921e5898ed1 /sys/bus/spi/devices/spi0.0/spi-nor/sfdp
|
||||
|
||||
MTD
|
||||
------------------------
|
||||
SPI NOR framework
|
||||
------------------------
|
||||
fsl-quadSPI
|
||||
------------------------
|
||||
SPI NOR chip
|
||||
Please dump the SFDP tables using ``xxd -p``. It enables us to do
|
||||
the reverse operation and convert the hexdump to binary with
|
||||
``xxd -rp``. Dumping the SFDP data with ``hexdump -Cv`` is accepted,
|
||||
but less desirable.
|
||||
|
||||
Part III - How can drivers use the framework?
|
||||
---------------------------------------------
|
||||
3) Dump debugfs data::
|
||||
|
||||
The main API is spi_nor_scan(). Before you call the hook, a driver should
|
||||
initialize the necessary fields for spi_nor{}. Please see
|
||||
drivers/mtd/spi-nor/spi-nor.c for detail. Please also refer to spi-fsl-qspi.c
|
||||
when you want to write a new driver for a SPI NOR controller.
|
||||
root@1:~# cat /sys/kernel/debug/spi-nor/spi0.0/capabilities
|
||||
Supported read modes by the flash
|
||||
1S-1S-1S
|
||||
opcode 0x03
|
||||
mode cycles 0
|
||||
dummy cycles 0
|
||||
1S-1S-1S (fast read)
|
||||
opcode 0x0b
|
||||
mode cycles 0
|
||||
dummy cycles 8
|
||||
1S-1S-2S
|
||||
opcode 0x3b
|
||||
mode cycles 0
|
||||
dummy cycles 8
|
||||
1S-2S-2S
|
||||
opcode 0xbb
|
||||
mode cycles 4
|
||||
dummy cycles 0
|
||||
1S-1S-4S
|
||||
opcode 0x6b
|
||||
mode cycles 0
|
||||
dummy cycles 8
|
||||
1S-4S-4S
|
||||
opcode 0xeb
|
||||
mode cycles 2
|
||||
dummy cycles 4
|
||||
4S-4S-4S
|
||||
opcode 0x0b
|
||||
mode cycles 2
|
||||
dummy cycles 4
|
||||
|
||||
Supported page program modes by the flash
|
||||
1S-1S-1S
|
||||
opcode 0x02
|
||||
|
||||
root@1:~# cat /sys/kernel/debug/spi-nor/spi0.0/params
|
||||
name sst26vf064b
|
||||
id bf 26 43 bf 26 43
|
||||
size 8.00 MiB
|
||||
write size 1
|
||||
page size 256
|
||||
address nbytes 3
|
||||
flags HAS_LOCK | HAS_16BIT_SR | SOFT_RESET | SWP_IS_VOLATILE
|
||||
|
||||
opcodes
|
||||
read 0xeb
|
||||
dummy cycles 6
|
||||
erase 0x20
|
||||
program 0x02
|
||||
8D extension none
|
||||
|
||||
protocols
|
||||
read 1S-4S-4S
|
||||
write 1S-1S-1S
|
||||
register 1S-1S-1S
|
||||
|
||||
erase commands
|
||||
20 (4.00 KiB) [0]
|
||||
d8 (8.00 KiB) [1]
|
||||
d8 (32.0 KiB) [2]
|
||||
d8 (64.0 KiB) [3]
|
||||
c7 (8.00 MiB)
|
||||
|
||||
sector map
|
||||
region (in hex) | erase mask | flags
|
||||
------------------+------------+----------
|
||||
00000000-00007fff | [01 ] |
|
||||
00008000-0000ffff | [0 2 ] |
|
||||
00010000-007effff | [0 3] |
|
||||
007f0000-007f7fff | [0 2 ] |
|
||||
007f8000-007fffff | [01 ] |
|
||||
|
||||
4) Use `mtd-utils <https://git.infradead.org/mtd-utils.git>`__
|
||||
and verify that erase, read and page program operations work fine::
|
||||
|
||||
root@1:~# dd if=/dev/urandom of=./spi_test bs=1M count=2
|
||||
2+0 records in
|
||||
2+0 records out
|
||||
2097152 bytes (2.1 MB, 2.0 MiB) copied, 0.848566 s, 2.5 MB/s
|
||||
|
||||
root@1:~# mtd_debug erase /dev/mtd0 0 2097152
|
||||
Erased 2097152 bytes from address 0x00000000 in flash
|
||||
|
||||
root@1:~# mtd_debug read /dev/mtd0 0 2097152 spi_read
|
||||
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
|
||||
|
||||
root@1:~# hexdump spi_read
|
||||
0000000 ffff ffff ffff ffff ffff ffff ffff ffff
|
||||
*
|
||||
0200000
|
||||
|
||||
root@1:~# sha256sum spi_read
|
||||
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5 spi_read
|
||||
|
||||
root@1:~# mtd_debug write /dev/mtd0 0 2097152 spi_test
|
||||
Copied 2097152 bytes from spi_test to address 0x00000000 in flash
|
||||
|
||||
root@1:~# mtd_debug read /dev/mtd0 0 2097152 spi_read
|
||||
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
|
||||
|
||||
root@1:~# sha256sum spi*
|
||||
c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_read
|
||||
c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_test
|
||||
|
||||
If the flash comes erased by default and the previous erase was ignored,
|
||||
we won't catch it, thus test the erase again::
|
||||
|
||||
root@1:~# mtd_debug erase /dev/mtd0 0 2097152
|
||||
Erased 2097152 bytes from address 0x00000000 in flash
|
||||
|
||||
root@1:~# mtd_debug read /dev/mtd0 0 2097152 spi_read
|
||||
Copied 2097152 bytes from address 0x00000000 in flash to spi_read
|
||||
|
||||
root@1:~# sha256sum spi*
|
||||
4bda3a28f4ffe603c0ec1258c0034d65a1a0d35ab7bd523a834608adabf03cc5 spi_read
|
||||
c444216a6ba2a4a66cccd60a0dd062bce4b865dd52b200ef5e21838c4b899ac8 spi_test
|
||||
|
||||
Dump some other relevant data::
|
||||
|
||||
root@1:~# mtd_debug info /dev/mtd0
|
||||
mtd.type = MTD_NORFLASH
|
||||
mtd.flags = MTD_CAP_NORFLASH
|
||||
mtd.size = 8388608 (8M)
|
||||
mtd.erasesize = 4096 (4K)
|
||||
mtd.writesize = 1
|
||||
mtd.oobsize = 0
|
||||
regions = 0
|
||||
|
@ -9026,7 +9026,7 @@ F: drivers/gpio/gpio-mockup.c
|
||||
F: tools/testing/selftests/gpio/
|
||||
|
||||
GPIO REGMAP
|
||||
M: Michael Walle <michael@walle.cc>
|
||||
M: Michael Walle <mwalle@kernel.org>
|
||||
S: Maintained
|
||||
F: drivers/gpio/gpio-regmap.c
|
||||
F: include/linux/gpio/regmap.h
|
||||
@ -19868,7 +19868,7 @@ W: http://www.winischhofer.at/linuxsisusbvga.shtml
|
||||
F: drivers/usb/misc/sisusbvga/
|
||||
|
||||
SL28 CPLD MFD DRIVER
|
||||
M: Michael Walle <michael@walle.cc>
|
||||
M: Michael Walle <mwalle@kernel.org>
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/gpio/kontron,sl28cpld-gpio.yaml
|
||||
F: Documentation/devicetree/bindings/hwmon/kontron,sl28cpld-hwmon.yaml
|
||||
@ -19883,7 +19883,7 @@ F: drivers/pwm/pwm-sl28cpld.c
|
||||
F: drivers/watchdog/sl28cpld_wdt.c
|
||||
|
||||
SL28 VPD NVMEM LAYOUT DRIVER
|
||||
M: Michael Walle <michael@walle.cc>
|
||||
M: Michael Walle <mwalle@kernel.org>
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/nvmem/layouts/kontron,sl28-vpd.yaml
|
||||
F: drivers/nvmem/layouts/sl28vpd.c
|
||||
@ -20393,7 +20393,7 @@ F: drivers/pinctrl/spear/
|
||||
SPI NOR SUBSYSTEM
|
||||
M: Tudor Ambarus <tudor.ambarus@linaro.org>
|
||||
M: Pratyush Yadav <pratyush@kernel.org>
|
||||
R: Michael Walle <michael@walle.cc>
|
||||
M: Michael Walle <mwalle@kernel.org>
|
||||
L: linux-mtd@lists.infradead.org
|
||||
S: Maintained
|
||||
W: http://www.linux-mtd.infradead.org/
|
||||
|
@ -719,7 +719,7 @@ static int vmu_can_unload(struct maple_device *mdev)
|
||||
card = maple_get_drvdata(mdev);
|
||||
for (x = 0; x < card->partitions; x++) {
|
||||
mtd = &((card->mtd)[x]);
|
||||
if (mtd->usecount > 0)
|
||||
if (kref_read(&mtd->refcnt))
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
|
@ -463,7 +463,7 @@ static void blktrans_notify_add(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_blktrans_ops *tr;
|
||||
|
||||
if (mtd->type == MTD_ABSENT)
|
||||
if (mtd->type == MTD_ABSENT || mtd->type == MTD_UBIVOLUME)
|
||||
return;
|
||||
|
||||
list_for_each_entry(tr, &blktrans_majors, list)
|
||||
@ -503,7 +503,7 @@ int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
|
||||
mutex_lock(&mtd_table_mutex);
|
||||
list_add(&tr->list, &blktrans_majors);
|
||||
mtd_for_each_device(mtd)
|
||||
if (mtd->type != MTD_ABSENT)
|
||||
if (mtd->type != MTD_ABSENT && mtd->type != MTD_UBIVOLUME)
|
||||
tr->add_mtd(tr, mtd);
|
||||
mutex_unlock(&mtd_table_mutex);
|
||||
return 0;
|
||||
|
@ -85,7 +85,7 @@ MODULE_DEVICE_TABLE(of, bcm63138_nand_of_match);
|
||||
|
||||
static struct platform_driver bcm63138_nand_driver = {
|
||||
.probe = bcm63138_nand_probe,
|
||||
.remove = brcmnand_remove,
|
||||
.remove_new = brcmnand_remove,
|
||||
.driver = {
|
||||
.name = "bcm63138_nand",
|
||||
.pm = &brcmnand_pm_ops,
|
||||
|
@ -117,7 +117,7 @@ MODULE_DEVICE_TABLE(of, bcm6368_nand_of_match);
|
||||
|
||||
static struct platform_driver bcm6368_nand_driver = {
|
||||
.probe = bcm6368_nand_probe,
|
||||
.remove = brcmnand_remove,
|
||||
.remove_new = brcmnand_remove,
|
||||
.driver = {
|
||||
.name = "bcm6368_nand",
|
||||
.pm = &brcmnand_pm_ops,
|
||||
|
@ -119,7 +119,7 @@ static int brcmnand_bcma_nand_probe(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver brcmnand_bcma_nand_driver = {
|
||||
.probe = brcmnand_bcma_nand_probe,
|
||||
.remove = brcmnand_remove,
|
||||
.remove_new = brcmnand_remove,
|
||||
.driver = {
|
||||
.name = "bcma_brcmnand",
|
||||
.pm = &brcmnand_pm_ops,
|
||||
|
@ -625,6 +625,8 @@ enum {
|
||||
/* Only for v7.2 */
|
||||
#define ACC_CONTROL_ECC_EXT_SHIFT 13
|
||||
|
||||
static u8 brcmnand_status(struct brcmnand_host *host);
|
||||
|
||||
static inline bool brcmnand_non_mmio_ops(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
#if IS_ENABLED(CONFIG_MTD_NAND_BRCMNAND_BCMA)
|
||||
@ -1022,19 +1024,6 @@ static inline int brcmnand_sector_1k_shift(struct brcmnand_controller *ctrl)
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int brcmnand_get_sector_size_1k(struct brcmnand_host *host)
|
||||
{
|
||||
struct brcmnand_controller *ctrl = host->ctrl;
|
||||
int shift = brcmnand_sector_1k_shift(ctrl);
|
||||
u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs,
|
||||
BRCMNAND_CS_ACC_CONTROL);
|
||||
|
||||
if (shift < 0)
|
||||
return 0;
|
||||
|
||||
return (nand_readreg(ctrl, acc_control_offs) >> shift) & 0x1;
|
||||
}
|
||||
|
||||
static void brcmnand_set_sector_size_1k(struct brcmnand_host *host, int val)
|
||||
{
|
||||
struct brcmnand_controller *ctrl = host->ctrl;
|
||||
@ -1061,10 +1050,11 @@ enum {
|
||||
CS_SELECT_AUTO_DEVICE_ID_CFG = BIT(30),
|
||||
};
|
||||
|
||||
static int bcmnand_ctrl_poll_status(struct brcmnand_controller *ctrl,
|
||||
static int bcmnand_ctrl_poll_status(struct brcmnand_host *host,
|
||||
u32 mask, u32 expected_val,
|
||||
unsigned long timeout_ms)
|
||||
{
|
||||
struct brcmnand_controller *ctrl = host->ctrl;
|
||||
unsigned long limit;
|
||||
u32 val;
|
||||
|
||||
@ -1073,6 +1063,9 @@ static int bcmnand_ctrl_poll_status(struct brcmnand_controller *ctrl,
|
||||
|
||||
limit = jiffies + msecs_to_jiffies(timeout_ms);
|
||||
do {
|
||||
if (mask & INTFC_FLASH_STATUS)
|
||||
brcmnand_status(host);
|
||||
|
||||
val = brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS);
|
||||
if ((val & mask) == expected_val)
|
||||
return 0;
|
||||
@ -1084,6 +1077,9 @@ static int bcmnand_ctrl_poll_status(struct brcmnand_controller *ctrl,
|
||||
* do a final check after time out in case the CPU was busy and the driver
|
||||
* did not get enough time to perform the polling to avoid false alarms
|
||||
*/
|
||||
if (mask & INTFC_FLASH_STATUS)
|
||||
brcmnand_status(host);
|
||||
|
||||
val = brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS);
|
||||
if ((val & mask) == expected_val)
|
||||
return 0;
|
||||
@ -1379,7 +1375,7 @@ static void brcmnand_wp(struct mtd_info *mtd, int wp)
|
||||
* make sure ctrl/flash ready before and after
|
||||
* changing state of #WP pin
|
||||
*/
|
||||
ret = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY |
|
||||
ret = bcmnand_ctrl_poll_status(host, NAND_CTRL_RDY |
|
||||
NAND_STATUS_READY,
|
||||
NAND_CTRL_RDY |
|
||||
NAND_STATUS_READY, 0);
|
||||
@ -1387,9 +1383,10 @@ static void brcmnand_wp(struct mtd_info *mtd, int wp)
|
||||
return;
|
||||
|
||||
brcmnand_set_wp(ctrl, wp);
|
||||
nand_status_op(chip, NULL);
|
||||
/* force controller operation to update internal copy of NAND chip status */
|
||||
brcmnand_status(host);
|
||||
/* NAND_STATUS_WP 0x00 = protected, 0x80 = not protected */
|
||||
ret = bcmnand_ctrl_poll_status(ctrl,
|
||||
ret = bcmnand_ctrl_poll_status(host,
|
||||
NAND_CTRL_RDY |
|
||||
NAND_STATUS_READY |
|
||||
NAND_STATUS_WP,
|
||||
@ -1629,13 +1626,13 @@ static void brcmnand_send_cmd(struct brcmnand_host *host, int cmd)
|
||||
*/
|
||||
if (oops_in_progress) {
|
||||
if (ctrl->cmd_pending &&
|
||||
bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY, NAND_CTRL_RDY, 0))
|
||||
bcmnand_ctrl_poll_status(host, NAND_CTRL_RDY, NAND_CTRL_RDY, 0))
|
||||
return;
|
||||
} else
|
||||
BUG_ON(ctrl->cmd_pending != 0);
|
||||
ctrl->cmd_pending = cmd;
|
||||
|
||||
ret = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY, NAND_CTRL_RDY, 0);
|
||||
ret = bcmnand_ctrl_poll_status(host, NAND_CTRL_RDY, NAND_CTRL_RDY, 0);
|
||||
WARN_ON(ret);
|
||||
|
||||
mb(); /* flush previous writes */
|
||||
@ -1643,16 +1640,6 @@ static void brcmnand_send_cmd(struct brcmnand_host *host, int cmd)
|
||||
cmd << brcmnand_cmd_shift(ctrl));
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* NAND MTD API: read/program/erase
|
||||
***********************************************************************/
|
||||
|
||||
static void brcmnand_cmd_ctrl(struct nand_chip *chip, int dat,
|
||||
unsigned int ctrl)
|
||||
{
|
||||
/* intentionally left blank */
|
||||
}
|
||||
|
||||
static bool brcmstb_nand_wait_for_completion(struct nand_chip *chip)
|
||||
{
|
||||
struct brcmnand_host *host = nand_get_controller_data(chip);
|
||||
@ -1664,7 +1651,7 @@ static bool brcmstb_nand_wait_for_completion(struct nand_chip *chip)
|
||||
if (mtd->oops_panic_write || ctrl->irq < 0) {
|
||||
/* switch to interrupt polling and PIO mode */
|
||||
disable_ctrl_irqs(ctrl);
|
||||
sts = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY,
|
||||
sts = bcmnand_ctrl_poll_status(host, NAND_CTRL_RDY,
|
||||
NAND_CTRL_RDY, 0);
|
||||
err = sts < 0;
|
||||
} else {
|
||||
@ -1703,6 +1690,26 @@ static int brcmnand_waitfunc(struct nand_chip *chip)
|
||||
INTFC_FLASH_STATUS;
|
||||
}
|
||||
|
||||
static u8 brcmnand_status(struct brcmnand_host *host)
|
||||
{
|
||||
struct nand_chip *chip = &host->chip;
|
||||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
|
||||
brcmnand_set_cmd_addr(mtd, 0);
|
||||
brcmnand_send_cmd(host, CMD_STATUS_READ);
|
||||
|
||||
return brcmnand_waitfunc(chip);
|
||||
}
|
||||
|
||||
static u8 brcmnand_reset(struct brcmnand_host *host)
|
||||
{
|
||||
struct nand_chip *chip = &host->chip;
|
||||
|
||||
brcmnand_send_cmd(host, CMD_FLASH_RESET);
|
||||
|
||||
return brcmnand_waitfunc(chip);
|
||||
}
|
||||
|
||||
enum {
|
||||
LLOP_RE = BIT(16),
|
||||
LLOP_WE = BIT(17),
|
||||
@ -1752,190 +1759,6 @@ static int brcmnand_low_level_op(struct brcmnand_host *host,
|
||||
return brcmnand_waitfunc(chip);
|
||||
}
|
||||
|
||||
static void brcmnand_cmdfunc(struct nand_chip *chip, unsigned command,
|
||||
int column, int page_addr)
|
||||
{
|
||||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
struct brcmnand_host *host = nand_get_controller_data(chip);
|
||||
struct brcmnand_controller *ctrl = host->ctrl;
|
||||
u64 addr = (u64)page_addr << chip->page_shift;
|
||||
int native_cmd = 0;
|
||||
|
||||
if (command == NAND_CMD_READID || command == NAND_CMD_PARAM ||
|
||||
command == NAND_CMD_RNDOUT)
|
||||
addr = (u64)column;
|
||||
/* Avoid propagating a negative, don't-care address */
|
||||
else if (page_addr < 0)
|
||||
addr = 0;
|
||||
|
||||
dev_dbg(ctrl->dev, "cmd 0x%x addr 0x%llx\n", command,
|
||||
(unsigned long long)addr);
|
||||
|
||||
host->last_cmd = command;
|
||||
host->last_byte = 0;
|
||||
host->last_addr = addr;
|
||||
|
||||
switch (command) {
|
||||
case NAND_CMD_RESET:
|
||||
native_cmd = CMD_FLASH_RESET;
|
||||
break;
|
||||
case NAND_CMD_STATUS:
|
||||
native_cmd = CMD_STATUS_READ;
|
||||
break;
|
||||
case NAND_CMD_READID:
|
||||
native_cmd = CMD_DEVICE_ID_READ;
|
||||
break;
|
||||
case NAND_CMD_READOOB:
|
||||
native_cmd = CMD_SPARE_AREA_READ;
|
||||
break;
|
||||
case NAND_CMD_ERASE1:
|
||||
native_cmd = CMD_BLOCK_ERASE;
|
||||
brcmnand_wp(mtd, 0);
|
||||
break;
|
||||
case NAND_CMD_PARAM:
|
||||
native_cmd = CMD_PARAMETER_READ;
|
||||
break;
|
||||
case NAND_CMD_SET_FEATURES:
|
||||
case NAND_CMD_GET_FEATURES:
|
||||
brcmnand_low_level_op(host, LL_OP_CMD, command, false);
|
||||
brcmnand_low_level_op(host, LL_OP_ADDR, column, false);
|
||||
break;
|
||||
case NAND_CMD_RNDOUT:
|
||||
native_cmd = CMD_PARAMETER_CHANGE_COL;
|
||||
addr &= ~((u64)(FC_BYTES - 1));
|
||||
/*
|
||||
* HW quirk: PARAMETER_CHANGE_COL requires SECTOR_SIZE_1K=0
|
||||
* NB: hwcfg.sector_size_1k may not be initialized yet
|
||||
*/
|
||||
if (brcmnand_get_sector_size_1k(host)) {
|
||||
host->hwcfg.sector_size_1k =
|
||||
brcmnand_get_sector_size_1k(host);
|
||||
brcmnand_set_sector_size_1k(host, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!native_cmd)
|
||||
return;
|
||||
|
||||
brcmnand_set_cmd_addr(mtd, addr);
|
||||
brcmnand_send_cmd(host, native_cmd);
|
||||
brcmnand_waitfunc(chip);
|
||||
|
||||
if (native_cmd == CMD_PARAMETER_READ ||
|
||||
native_cmd == CMD_PARAMETER_CHANGE_COL) {
|
||||
/* Copy flash cache word-wise */
|
||||
u32 *flash_cache = (u32 *)ctrl->flash_cache;
|
||||
int i;
|
||||
|
||||
brcmnand_soc_data_bus_prepare(ctrl->soc, true);
|
||||
|
||||
/*
|
||||
* Must cache the FLASH_CACHE now, since changes in
|
||||
* SECTOR_SIZE_1K may invalidate it
|
||||
*/
|
||||
for (i = 0; i < FC_WORDS; i++)
|
||||
/*
|
||||
* Flash cache is big endian for parameter pages, at
|
||||
* least on STB SoCs
|
||||
*/
|
||||
flash_cache[i] = be32_to_cpu(brcmnand_read_fc(ctrl, i));
|
||||
|
||||
brcmnand_soc_data_bus_unprepare(ctrl->soc, true);
|
||||
|
||||
/* Cleanup from HW quirk: restore SECTOR_SIZE_1K */
|
||||
if (host->hwcfg.sector_size_1k)
|
||||
brcmnand_set_sector_size_1k(host,
|
||||
host->hwcfg.sector_size_1k);
|
||||
}
|
||||
|
||||
/* Re-enable protection is necessary only after erase */
|
||||
if (command == NAND_CMD_ERASE1)
|
||||
brcmnand_wp(mtd, 1);
|
||||
}
|
||||
|
||||
static uint8_t brcmnand_read_byte(struct nand_chip *chip)
|
||||
{
|
||||
struct brcmnand_host *host = nand_get_controller_data(chip);
|
||||
struct brcmnand_controller *ctrl = host->ctrl;
|
||||
uint8_t ret = 0;
|
||||
int addr, offs;
|
||||
|
||||
switch (host->last_cmd) {
|
||||
case NAND_CMD_READID:
|
||||
if (host->last_byte < 4)
|
||||
ret = brcmnand_read_reg(ctrl, BRCMNAND_ID) >>
|
||||
(24 - (host->last_byte << 3));
|
||||
else if (host->last_byte < 8)
|
||||
ret = brcmnand_read_reg(ctrl, BRCMNAND_ID_EXT) >>
|
||||
(56 - (host->last_byte << 3));
|
||||
break;
|
||||
|
||||
case NAND_CMD_READOOB:
|
||||
ret = oob_reg_read(ctrl, host->last_byte);
|
||||
break;
|
||||
|
||||
case NAND_CMD_STATUS:
|
||||
ret = brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS) &
|
||||
INTFC_FLASH_STATUS;
|
||||
if (wp_on) /* hide WP status */
|
||||
ret |= NAND_STATUS_WP;
|
||||
break;
|
||||
|
||||
case NAND_CMD_PARAM:
|
||||
case NAND_CMD_RNDOUT:
|
||||
addr = host->last_addr + host->last_byte;
|
||||
offs = addr & (FC_BYTES - 1);
|
||||
|
||||
/* At FC_BYTES boundary, switch to next column */
|
||||
if (host->last_byte > 0 && offs == 0)
|
||||
nand_change_read_column_op(chip, addr, NULL, 0, false);
|
||||
|
||||
ret = ctrl->flash_cache[offs];
|
||||
break;
|
||||
case NAND_CMD_GET_FEATURES:
|
||||
if (host->last_byte >= ONFI_SUBFEATURE_PARAM_LEN) {
|
||||
ret = 0;
|
||||
} else {
|
||||
bool last = host->last_byte ==
|
||||
ONFI_SUBFEATURE_PARAM_LEN - 1;
|
||||
brcmnand_low_level_op(host, LL_OP_RD, 0, last);
|
||||
ret = brcmnand_read_reg(ctrl, BRCMNAND_LL_RDATA) & 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
dev_dbg(ctrl->dev, "read byte = 0x%02x\n", ret);
|
||||
host->last_byte++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void brcmnand_read_buf(struct nand_chip *chip, uint8_t *buf, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < len; i++, buf++)
|
||||
*buf = brcmnand_read_byte(chip);
|
||||
}
|
||||
|
||||
static void brcmnand_write_buf(struct nand_chip *chip, const uint8_t *buf,
|
||||
int len)
|
||||
{
|
||||
int i;
|
||||
struct brcmnand_host *host = nand_get_controller_data(chip);
|
||||
|
||||
switch (host->last_cmd) {
|
||||
case NAND_CMD_SET_FEATURES:
|
||||
for (i = 0; i < len; i++)
|
||||
brcmnand_low_level_op(host, LL_OP_WR, buf[i],
|
||||
(i + 1) == len);
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Kick EDU engine
|
||||
*/
|
||||
@ -2345,8 +2168,9 @@ static int brcmnand_read_page(struct nand_chip *chip, uint8_t *buf,
|
||||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
struct brcmnand_host *host = nand_get_controller_data(chip);
|
||||
u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL;
|
||||
u64 addr = (u64)page << chip->page_shift;
|
||||
|
||||
nand_read_page_op(chip, page, 0, NULL, 0);
|
||||
host->last_addr = addr;
|
||||
|
||||
return brcmnand_read(mtd, chip, host->last_addr,
|
||||
mtd->writesize >> FC_SHIFT, (u32 *)buf, oob);
|
||||
@ -2359,8 +2183,9 @@ static int brcmnand_read_page_raw(struct nand_chip *chip, uint8_t *buf,
|
||||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL;
|
||||
int ret;
|
||||
u64 addr = (u64)page << chip->page_shift;
|
||||
|
||||
nand_read_page_op(chip, page, 0, NULL, 0);
|
||||
host->last_addr = addr;
|
||||
|
||||
brcmnand_set_ecc_enabled(host, 0);
|
||||
ret = brcmnand_read(mtd, chip, host->last_addr,
|
||||
@ -2468,11 +2293,11 @@ static int brcmnand_write_page(struct nand_chip *chip, const uint8_t *buf,
|
||||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
struct brcmnand_host *host = nand_get_controller_data(chip);
|
||||
void *oob = oob_required ? chip->oob_poi : NULL;
|
||||
u64 addr = (u64)page << chip->page_shift;
|
||||
|
||||
nand_prog_page_begin_op(chip, page, 0, NULL, 0);
|
||||
brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
|
||||
host->last_addr = addr;
|
||||
|
||||
return nand_prog_page_end_op(chip);
|
||||
return brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
|
||||
}
|
||||
|
||||
static int brcmnand_write_page_raw(struct nand_chip *chip, const uint8_t *buf,
|
||||
@ -2481,13 +2306,15 @@ static int brcmnand_write_page_raw(struct nand_chip *chip, const uint8_t *buf,
|
||||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
struct brcmnand_host *host = nand_get_controller_data(chip);
|
||||
void *oob = oob_required ? chip->oob_poi : NULL;
|
||||
u64 addr = (u64)page << chip->page_shift;
|
||||
int ret = 0;
|
||||
|
||||
nand_prog_page_begin_op(chip, page, 0, NULL, 0);
|
||||
host->last_addr = addr;
|
||||
brcmnand_set_ecc_enabled(host, 0);
|
||||
brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
|
||||
ret = brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
|
||||
brcmnand_set_ecc_enabled(host, 1);
|
||||
|
||||
return nand_prog_page_end_op(chip);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int brcmnand_write_oob(struct nand_chip *chip, int page)
|
||||
@ -2511,6 +2338,130 @@ static int brcmnand_write_oob_raw(struct nand_chip *chip, int page)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int brcmnand_exec_instr(struct brcmnand_host *host, int i,
|
||||
const struct nand_operation *op)
|
||||
{
|
||||
const struct nand_op_instr *instr = &op->instrs[i];
|
||||
struct brcmnand_controller *ctrl = host->ctrl;
|
||||
const u8 *out;
|
||||
bool last_op;
|
||||
int ret = 0;
|
||||
u8 *in;
|
||||
|
||||
/*
|
||||
* The controller needs to be aware of the last command in the operation
|
||||
* (WAITRDY excepted).
|
||||
*/
|
||||
last_op = ((i == (op->ninstrs - 1)) && (instr->type != NAND_OP_WAITRDY_INSTR)) ||
|
||||
((i == (op->ninstrs - 2)) && (op->instrs[i+1].type == NAND_OP_WAITRDY_INSTR));
|
||||
|
||||
switch (instr->type) {
|
||||
case NAND_OP_CMD_INSTR:
|
||||
brcmnand_low_level_op(host, LL_OP_CMD, instr->ctx.cmd.opcode, last_op);
|
||||
break;
|
||||
|
||||
case NAND_OP_ADDR_INSTR:
|
||||
for (i = 0; i < instr->ctx.addr.naddrs; i++)
|
||||
brcmnand_low_level_op(host, LL_OP_ADDR, instr->ctx.addr.addrs[i],
|
||||
last_op && (i == (instr->ctx.addr.naddrs - 1)));
|
||||
break;
|
||||
|
||||
case NAND_OP_DATA_IN_INSTR:
|
||||
in = instr->ctx.data.buf.in;
|
||||
for (i = 0; i < instr->ctx.data.len; i++) {
|
||||
brcmnand_low_level_op(host, LL_OP_RD, 0,
|
||||
last_op && (i == (instr->ctx.data.len - 1)));
|
||||
in[i] = brcmnand_read_reg(host->ctrl, BRCMNAND_LL_RDATA);
|
||||
}
|
||||
break;
|
||||
|
||||
case NAND_OP_DATA_OUT_INSTR:
|
||||
out = instr->ctx.data.buf.out;
|
||||
for (i = 0; i < instr->ctx.data.len; i++)
|
||||
brcmnand_low_level_op(host, LL_OP_WR, out[i],
|
||||
last_op && (i == (instr->ctx.data.len - 1)));
|
||||
break;
|
||||
|
||||
case NAND_OP_WAITRDY_INSTR:
|
||||
ret = bcmnand_ctrl_poll_status(host, NAND_CTRL_RDY, NAND_CTRL_RDY, 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(ctrl->dev, "unsupported instruction type: %d\n",
|
||||
instr->type);
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int brcmnand_op_is_status(const struct nand_operation *op)
|
||||
{
|
||||
if ((op->ninstrs == 2) &&
|
||||
(op->instrs[0].type == NAND_OP_CMD_INSTR) &&
|
||||
(op->instrs[0].ctx.cmd.opcode == NAND_CMD_STATUS) &&
|
||||
(op->instrs[1].type == NAND_OP_DATA_IN_INSTR))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int brcmnand_op_is_reset(const struct nand_operation *op)
|
||||
{
|
||||
if ((op->ninstrs == 2) &&
|
||||
(op->instrs[0].type == NAND_OP_CMD_INSTR) &&
|
||||
(op->instrs[0].ctx.cmd.opcode == NAND_CMD_RESET) &&
|
||||
(op->instrs[1].type == NAND_OP_WAITRDY_INSTR))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int brcmnand_exec_op(struct nand_chip *chip,
|
||||
const struct nand_operation *op,
|
||||
bool check_only)
|
||||
{
|
||||
struct brcmnand_host *host = nand_get_controller_data(chip);
|
||||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
u8 *status;
|
||||
unsigned int i;
|
||||
int ret = 0;
|
||||
|
||||
if (check_only)
|
||||
return 0;
|
||||
|
||||
if (brcmnand_op_is_status(op)) {
|
||||
status = op->instrs[1].ctx.data.buf.in;
|
||||
*status = brcmnand_status(host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
else if (brcmnand_op_is_reset(op)) {
|
||||
ret = brcmnand_reset(host);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
brcmnand_wp(mtd, 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (op->deassert_wp)
|
||||
brcmnand_wp(mtd, 0);
|
||||
|
||||
for (i = 0; i < op->ninstrs; i++) {
|
||||
ret = brcmnand_exec_instr(host, i, op);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
|
||||
if (op->deassert_wp)
|
||||
brcmnand_wp(mtd, 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
* Per-CS setup (1 NAND device)
|
||||
***********************************************************************/
|
||||
@ -2821,6 +2772,7 @@ static int brcmnand_attach_chip(struct nand_chip *chip)
|
||||
|
||||
static const struct nand_controller_ops brcmnand_controller_ops = {
|
||||
.attach_chip = brcmnand_attach_chip,
|
||||
.exec_op = brcmnand_exec_op,
|
||||
};
|
||||
|
||||
static int brcmnand_init_cs(struct brcmnand_host *host,
|
||||
@ -2845,13 +2797,6 @@ static int brcmnand_init_cs(struct brcmnand_host *host,
|
||||
mtd->owner = THIS_MODULE;
|
||||
mtd->dev.parent = dev;
|
||||
|
||||
chip->legacy.cmd_ctrl = brcmnand_cmd_ctrl;
|
||||
chip->legacy.cmdfunc = brcmnand_cmdfunc;
|
||||
chip->legacy.waitfunc = brcmnand_waitfunc;
|
||||
chip->legacy.read_byte = brcmnand_read_byte;
|
||||
chip->legacy.read_buf = brcmnand_read_buf;
|
||||
chip->legacy.write_buf = brcmnand_write_buf;
|
||||
|
||||
chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
|
||||
chip->ecc.read_page = brcmnand_read_page;
|
||||
chip->ecc.write_page = brcmnand_write_page;
|
||||
@ -2863,6 +2808,7 @@ static int brcmnand_init_cs(struct brcmnand_host *host,
|
||||
chip->ecc.write_oob = brcmnand_write_oob;
|
||||
|
||||
chip->controller = &ctrl->controller;
|
||||
ctrl->controller.controller_wp = 1;
|
||||
|
||||
/*
|
||||
* The bootloader might have configured 16bit mode but
|
||||
@ -3299,7 +3245,7 @@ err:
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(brcmnand_probe);
|
||||
|
||||
int brcmnand_remove(struct platform_device *pdev)
|
||||
void brcmnand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct brcmnand_controller *ctrl = dev_get_drvdata(&pdev->dev);
|
||||
struct brcmnand_host *host;
|
||||
@ -3316,8 +3262,6 @@ int brcmnand_remove(struct platform_device *pdev)
|
||||
clk_disable_unprepare(ctrl->clk);
|
||||
|
||||
dev_set_drvdata(&pdev->dev, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(brcmnand_remove);
|
||||
|
||||
|
@ -88,7 +88,7 @@ static inline void brcmnand_soc_write(struct brcmnand_soc *soc, u32 val,
|
||||
}
|
||||
|
||||
int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc);
|
||||
int brcmnand_remove(struct platform_device *pdev);
|
||||
void brcmnand_remove(struct platform_device *pdev);
|
||||
|
||||
extern const struct dev_pm_ops brcmnand_pm_ops;
|
||||
|
||||
|
@ -23,7 +23,7 @@ static int brcmstb_nand_probe(struct platform_device *pdev)
|
||||
|
||||
static struct platform_driver brcmstb_nand_driver = {
|
||||
.probe = brcmstb_nand_probe,
|
||||
.remove = brcmnand_remove,
|
||||
.remove_new = brcmnand_remove,
|
||||
.driver = {
|
||||
.name = "brcmstb_nand",
|
||||
.pm = &brcmnand_pm_ops,
|
||||
|
@ -134,7 +134,7 @@ MODULE_DEVICE_TABLE(of, iproc_nand_of_match);
|
||||
|
||||
static struct platform_driver iproc_nand_driver = {
|
||||
.probe = iproc_nand_probe,
|
||||
.remove = brcmnand_remove,
|
||||
.remove_new = brcmnand_remove,
|
||||
.driver = {
|
||||
.name = "iproc_nand",
|
||||
.pm = &brcmnand_pm_ops,
|
||||
|
@ -1491,10 +1491,12 @@ static int __init doc_probe(unsigned long physadr)
|
||||
else
|
||||
numchips = doc2001_init(mtd);
|
||||
|
||||
if ((ret = nand_scan(nand, numchips)) || (ret = doc->late_init(mtd))) {
|
||||
/* DBB note: i believe nand_cleanup is necessary here, as
|
||||
buffers may have been allocated in nand_base. Check with
|
||||
Thomas. FIX ME! */
|
||||
ret = nand_scan(nand, numchips);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
ret = doc->late_init(mtd);
|
||||
if (ret) {
|
||||
nand_cleanup(nand);
|
||||
goto fail;
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
#define ERR_BYTE 0xFF /* Value returned for read
|
||||
bytes when read failed */
|
||||
#define IFC_TIMEOUT_MSECS 500 /* Maximum number of mSecs to wait
|
||||
#define IFC_TIMEOUT_MSECS 1000 /* Maximum timeout to wait
|
||||
for IFC NAND Machine */
|
||||
|
||||
struct fsl_ifc_ctrl;
|
||||
|
@ -90,6 +90,8 @@
|
||||
|
||||
/* eMMC clock register, misc control */
|
||||
#define CLK_SELECT_NAND BIT(31)
|
||||
#define CLK_ALWAYS_ON_NAND BIT(24)
|
||||
#define CLK_SELECT_FIX_PLL2 BIT(6)
|
||||
|
||||
#define NFC_CLK_CYCLE 6
|
||||
|
||||
@ -509,7 +511,7 @@ static void meson_nfc_set_user_byte(struct nand_chip *nand, u8 *oob_buf)
|
||||
__le64 *info;
|
||||
int i, count;
|
||||
|
||||
for (i = 0, count = 0; i < nand->ecc.steps; i++, count += 2) {
|
||||
for (i = 0, count = 0; i < nand->ecc.steps; i++, count += (2 + nand->ecc.bytes)) {
|
||||
info = &meson_chip->info_buf[i];
|
||||
*info |= oob_buf[count];
|
||||
*info |= oob_buf[count + 1] << 8;
|
||||
@ -522,7 +524,7 @@ static void meson_nfc_get_user_byte(struct nand_chip *nand, u8 *oob_buf)
|
||||
__le64 *info;
|
||||
int i, count;
|
||||
|
||||
for (i = 0, count = 0; i < nand->ecc.steps; i++, count += 2) {
|
||||
for (i = 0, count = 0; i < nand->ecc.steps; i++, count += (2 + nand->ecc.bytes)) {
|
||||
info = &meson_chip->info_buf[i];
|
||||
oob_buf[count] = *info;
|
||||
oob_buf[count + 1] = *info >> 8;
|
||||
@ -1154,7 +1156,7 @@ static int meson_nfc_clk_init(struct meson_nfc *nfc)
|
||||
return PTR_ERR(nfc->nand_clk);
|
||||
|
||||
/* init SD_EMMC_CLOCK to sane defaults w/min clock rate */
|
||||
writel(CLK_SELECT_NAND | readl(nfc->reg_clk),
|
||||
writel(CLK_ALWAYS_ON_NAND | CLK_SELECT_NAND | CLK_SELECT_FIX_PLL2,
|
||||
nfc->reg_clk);
|
||||
|
||||
ret = clk_prepare_enable(nfc->core_clk);
|
||||
|
@ -366,6 +366,10 @@ static int nand_check_wp(struct nand_chip *chip)
|
||||
if (chip->options & NAND_BROKEN_XD)
|
||||
return 0;
|
||||
|
||||
/* controller responsible for NAND write protect */
|
||||
if (chip->controller->controller_wp)
|
||||
return 0;
|
||||
|
||||
/* Check the WP bit */
|
||||
ret = nand_status_op(chip, &status);
|
||||
if (ret)
|
||||
@ -1207,6 +1211,23 @@ static int nand_lp_exec_read_page_op(struct nand_chip *chip, unsigned int page,
|
||||
return nand_exec_op(chip, &op);
|
||||
}
|
||||
|
||||
static void rawnand_cap_cont_reads(struct nand_chip *chip)
|
||||
{
|
||||
struct nand_memory_organization *memorg;
|
||||
unsigned int pages_per_lun, first_lun, last_lun;
|
||||
|
||||
memorg = nanddev_get_memorg(&chip->base);
|
||||
pages_per_lun = memorg->pages_per_eraseblock * memorg->eraseblocks_per_lun;
|
||||
first_lun = chip->cont_read.first_page / pages_per_lun;
|
||||
last_lun = chip->cont_read.last_page / pages_per_lun;
|
||||
|
||||
/* Prevent sequential cache reads across LUN boundaries */
|
||||
if (first_lun != last_lun)
|
||||
chip->cont_read.pause_page = first_lun * pages_per_lun + pages_per_lun - 1;
|
||||
else
|
||||
chip->cont_read.pause_page = chip->cont_read.last_page;
|
||||
}
|
||||
|
||||
static int nand_lp_exec_cont_read_page_op(struct nand_chip *chip, unsigned int page,
|
||||
unsigned int offset_in_page, void *buf,
|
||||
unsigned int len, bool check_only)
|
||||
@ -1225,7 +1246,7 @@ static int nand_lp_exec_cont_read_page_op(struct nand_chip *chip, unsigned int p
|
||||
NAND_OP_DATA_IN(len, buf, 0),
|
||||
};
|
||||
struct nand_op_instr cont_instrs[] = {
|
||||
NAND_OP_CMD(page == chip->cont_read.last_page ?
|
||||
NAND_OP_CMD(page == chip->cont_read.pause_page ?
|
||||
NAND_CMD_READCACHEEND : NAND_CMD_READCACHESEQ,
|
||||
NAND_COMMON_TIMING_NS(conf, tWB_max)),
|
||||
NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tR_max),
|
||||
@ -1262,16 +1283,29 @@ static int nand_lp_exec_cont_read_page_op(struct nand_chip *chip, unsigned int p
|
||||
}
|
||||
|
||||
if (page == chip->cont_read.first_page)
|
||||
return nand_exec_op(chip, &start_op);
|
||||
ret = nand_exec_op(chip, &start_op);
|
||||
else
|
||||
return nand_exec_op(chip, &cont_op);
|
||||
ret = nand_exec_op(chip, &cont_op);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!chip->cont_read.ongoing)
|
||||
return 0;
|
||||
|
||||
if (page == chip->cont_read.pause_page &&
|
||||
page != chip->cont_read.last_page) {
|
||||
chip->cont_read.first_page = chip->cont_read.pause_page + 1;
|
||||
rawnand_cap_cont_reads(chip);
|
||||
} else if (page == chip->cont_read.last_page) {
|
||||
chip->cont_read.ongoing = false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool rawnand_cont_read_ongoing(struct nand_chip *chip, unsigned int page)
|
||||
{
|
||||
return chip->cont_read.ongoing &&
|
||||
page >= chip->cont_read.first_page &&
|
||||
page <= chip->cont_read.last_page;
|
||||
return chip->cont_read.ongoing && page >= chip->cont_read.first_page;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1493,7 +1527,8 @@ static int nand_exec_prog_page_op(struct nand_chip *chip, unsigned int page,
|
||||
NAND_COMMON_TIMING_NS(conf, tWB_max)),
|
||||
NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tPROG_max), 0),
|
||||
};
|
||||
struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs);
|
||||
struct nand_operation op = NAND_DESTRUCTIVE_OPERATION(chip->cur_cs,
|
||||
instrs);
|
||||
int naddrs = nand_fill_column_cycles(chip, addrs, offset_in_page);
|
||||
|
||||
if (naddrs < 0)
|
||||
@ -1916,7 +1951,8 @@ int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock)
|
||||
NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tBERS_max),
|
||||
0),
|
||||
};
|
||||
struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs);
|
||||
struct nand_operation op = NAND_DESTRUCTIVE_OPERATION(chip->cur_cs,
|
||||
instrs);
|
||||
|
||||
if (chip->options & NAND_ROW_ADDR_3)
|
||||
instrs[1].ctx.addr.naddrs++;
|
||||
@ -3430,21 +3466,42 @@ static void rawnand_enable_cont_reads(struct nand_chip *chip, unsigned int page,
|
||||
u32 readlen, int col)
|
||||
{
|
||||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
unsigned int end_page, end_col;
|
||||
|
||||
chip->cont_read.ongoing = false;
|
||||
|
||||
if (!chip->controller->supported_op.cont_read)
|
||||
return;
|
||||
|
||||
if ((col && col + readlen < (3 * mtd->writesize)) ||
|
||||
(!col && readlen < (2 * mtd->writesize))) {
|
||||
chip->cont_read.ongoing = false;
|
||||
return;
|
||||
}
|
||||
end_page = DIV_ROUND_UP(col + readlen, mtd->writesize);
|
||||
end_col = (col + readlen) % mtd->writesize;
|
||||
|
||||
chip->cont_read.ongoing = true;
|
||||
chip->cont_read.first_page = page;
|
||||
if (col)
|
||||
page++;
|
||||
|
||||
if (end_col && end_page)
|
||||
end_page--;
|
||||
|
||||
if (page + 1 > end_page)
|
||||
return;
|
||||
|
||||
chip->cont_read.first_page = page;
|
||||
chip->cont_read.last_page = end_page;
|
||||
chip->cont_read.ongoing = true;
|
||||
|
||||
rawnand_cap_cont_reads(chip);
|
||||
}
|
||||
|
||||
static void rawnand_cont_read_skip_first_page(struct nand_chip *chip, unsigned int page)
|
||||
{
|
||||
if (!chip->cont_read.ongoing || page != chip->cont_read.first_page)
|
||||
return;
|
||||
|
||||
chip->cont_read.first_page++;
|
||||
if (chip->cont_read.first_page == chip->cont_read.pause_page)
|
||||
chip->cont_read.first_page++;
|
||||
chip->cont_read.last_page = page + ((readlen >> chip->page_shift) & chip->pagemask);
|
||||
if (chip->cont_read.first_page >= chip->cont_read.last_page)
|
||||
chip->cont_read.ongoing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3621,6 +3678,8 @@ read_retry:
|
||||
buf += bytes;
|
||||
max_bitflips = max_t(unsigned int, max_bitflips,
|
||||
chip->pagecache.bitflips);
|
||||
|
||||
rawnand_cont_read_skip_first_page(chip, page);
|
||||
}
|
||||
|
||||
readlen -= bytes;
|
||||
@ -5125,6 +5184,14 @@ static void rawnand_late_check_supported_ops(struct nand_chip *chip)
|
||||
/* The supported_op fields should not be set by individual drivers */
|
||||
WARN_ON_ONCE(chip->controller->supported_op.cont_read);
|
||||
|
||||
/*
|
||||
* Too many devices do not support sequential cached reads with on-die
|
||||
* ECC correction enabled, so in this case refuse to perform the
|
||||
* automation.
|
||||
*/
|
||||
if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_DIE)
|
||||
return;
|
||||
|
||||
if (!nand_has_exec_op(chip))
|
||||
return;
|
||||
|
||||
|
@ -128,7 +128,7 @@ struct pl35x_nand {
|
||||
* @conf_regs: SMC configuration registers for command phase
|
||||
* @io_regs: NAND data registers for data phase
|
||||
* @controller: Core NAND controller structure
|
||||
* @chip: NAND chip information structure
|
||||
* @chips: List of connected NAND chips
|
||||
* @selected_chip: NAND chip currently selected by the controller
|
||||
* @assigned_cs: List of assigned CS
|
||||
* @ecc_buf: Temporary buffer to extract ECC bytes
|
||||
|
@ -98,7 +98,7 @@ enum nfc_type {
|
||||
* @high: ECC count high bit index at register.
|
||||
* @high_mask: mask bit
|
||||
*/
|
||||
struct ecc_cnt_status {
|
||||
struct rk_ecc_cnt_status {
|
||||
u8 err_flag_bit;
|
||||
u8 low;
|
||||
u8 low_mask;
|
||||
@ -108,6 +108,7 @@ struct ecc_cnt_status {
|
||||
};
|
||||
|
||||
/**
|
||||
* struct nfc_cfg: Rockchip NAND controller configuration
|
||||
* @type: NFC version
|
||||
* @ecc_strengths: ECC strengths
|
||||
* @ecc_cfgs: ECC config values
|
||||
@ -144,8 +145,8 @@ struct nfc_cfg {
|
||||
u32 int_st_off;
|
||||
u32 oob0_off;
|
||||
u32 oob1_off;
|
||||
struct ecc_cnt_status ecc0;
|
||||
struct ecc_cnt_status ecc1;
|
||||
struct rk_ecc_cnt_status ecc0;
|
||||
struct rk_ecc_cnt_status ecc1;
|
||||
};
|
||||
|
||||
struct rk_nfc_nand_chip {
|
||||
|
@ -105,7 +105,6 @@ struct s3c2410_nand_info;
|
||||
|
||||
/**
|
||||
* struct s3c2410_nand_mtd - driver MTD structure
|
||||
* @mtd: The MTD instance to pass to the MTD layer.
|
||||
* @chip: The NAND chip information.
|
||||
* @set: The platform information supplied for this set of NAND chips.
|
||||
* @info: Link back to the hardware information.
|
||||
@ -145,7 +144,6 @@ enum s3c_nand_clk_state {
|
||||
* @clk_rate: The clock rate from @clk.
|
||||
* @clk_state: The current clock state.
|
||||
* @cpu_type: The exact type of this controller.
|
||||
* @freq_transition: CPUFreq notifier block
|
||||
*/
|
||||
struct s3c2410_nand_info {
|
||||
/* mtd info */
|
||||
|
@ -276,7 +276,7 @@ static const struct nand_controller_ops txx9ndfmc_controller_ops = {
|
||||
.attach_chip = txx9ndfmc_attach_chip,
|
||||
};
|
||||
|
||||
static int __init txx9ndfmc_probe(struct platform_device *dev)
|
||||
static int txx9ndfmc_probe(struct platform_device *dev)
|
||||
{
|
||||
struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev);
|
||||
int hold, spw;
|
||||
@ -369,13 +369,11 @@ static int __init txx9ndfmc_probe(struct platform_device *dev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __exit txx9ndfmc_remove(struct platform_device *dev)
|
||||
static void txx9ndfmc_remove(struct platform_device *dev)
|
||||
{
|
||||
struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev);
|
||||
int ret, i;
|
||||
|
||||
if (!drvdata)
|
||||
return 0;
|
||||
for (i = 0; i < MAX_TXX9NDFMC_DEV; i++) {
|
||||
struct mtd_info *mtd = drvdata->mtds[i];
|
||||
struct nand_chip *chip;
|
||||
@ -392,7 +390,6 @@ static int __exit txx9ndfmc_remove(struct platform_device *dev)
|
||||
kfree(txx9_priv->mtdname);
|
||||
kfree(txx9_priv);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
@ -407,14 +404,14 @@ static int txx9ndfmc_resume(struct platform_device *dev)
|
||||
#endif
|
||||
|
||||
static struct platform_driver txx9ndfmc_driver = {
|
||||
.remove = __exit_p(txx9ndfmc_remove),
|
||||
.probe = txx9ndfmc_probe,
|
||||
.remove_new = txx9ndfmc_remove,
|
||||
.resume = txx9ndfmc_resume,
|
||||
.driver = {
|
||||
.name = "txx9ndfmc",
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver_probe(txx9ndfmc_driver, txx9ndfmc_probe);
|
||||
module_platform_driver(txx9ndfmc_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("TXx9 SoC NAND flash controller driver");
|
||||
|
@ -16,12 +16,12 @@
|
||||
* is to unlock the whole flash array on startup. Therefore, we have to support
|
||||
* exactly this operation.
|
||||
*/
|
||||
static int at25fs_nor_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int at25fs_nor_lock(struct spi_nor *nor, loff_t ofs, u64 len)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static int at25fs_nor_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int at25fs_nor_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -37,7 +37,7 @@ static int at25fs_nor_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int at25fs_nor_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int at25fs_nor_is_locked(struct spi_nor *nor, loff_t ofs, u64 len)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
@ -69,7 +69,7 @@ static const struct spi_nor_fixups at25fs_nor_fixups = {
|
||||
* Return: 0 on success, -error otherwise.
|
||||
*/
|
||||
static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs,
|
||||
uint64_t len, bool is_protect)
|
||||
u64 len, bool is_protect)
|
||||
{
|
||||
int ret;
|
||||
u8 sr;
|
||||
@ -118,20 +118,18 @@ static int atmel_nor_set_global_protection(struct spi_nor *nor, loff_t ofs,
|
||||
return spi_nor_write_sr(nor, nor->bouncebuf, 1);
|
||||
}
|
||||
|
||||
static int atmel_nor_global_protect(struct spi_nor *nor, loff_t ofs,
|
||||
uint64_t len)
|
||||
static int atmel_nor_global_protect(struct spi_nor *nor, loff_t ofs, u64 len)
|
||||
{
|
||||
return atmel_nor_set_global_protection(nor, ofs, len, true);
|
||||
}
|
||||
|
||||
static int atmel_nor_global_unprotect(struct spi_nor *nor, loff_t ofs,
|
||||
uint64_t len)
|
||||
static int atmel_nor_global_unprotect(struct spi_nor *nor, loff_t ofs, u64 len)
|
||||
{
|
||||
return atmel_nor_set_global_protection(nor, ofs, len, false);
|
||||
}
|
||||
|
||||
static int atmel_nor_is_global_protected(struct spi_nor *nor, loff_t ofs,
|
||||
uint64_t len)
|
||||
u64 len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
@ -1060,24 +1060,32 @@ static int spi_nor_read_sr2(struct spi_nor *nor, u8 *sr2)
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_nor_erase_chip() - Erase the entire flash memory.
|
||||
* spi_nor_erase_die() - Erase the entire die.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
* @addr: address of the die.
|
||||
* @die_size: size of the die.
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
static int spi_nor_erase_chip(struct spi_nor *nor)
|
||||
static int spi_nor_erase_die(struct spi_nor *nor, loff_t addr, size_t die_size)
|
||||
{
|
||||
bool multi_die = nor->mtd.size != die_size;
|
||||
int ret;
|
||||
|
||||
dev_dbg(nor->dev, " %lldKiB\n", (long long)(nor->mtd.size >> 10));
|
||||
dev_dbg(nor->dev, " %lldKiB\n", (long long)(die_size >> 10));
|
||||
|
||||
if (nor->spimem) {
|
||||
struct spi_mem_op op = SPI_NOR_CHIP_ERASE_OP;
|
||||
struct spi_mem_op op =
|
||||
SPI_NOR_DIE_ERASE_OP(nor->params->die_erase_opcode,
|
||||
nor->addr_nbytes, addr, multi_die);
|
||||
|
||||
spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
|
||||
|
||||
ret = spi_mem_exec_op(nor->spimem, &op);
|
||||
} else {
|
||||
if (multi_die)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = spi_nor_controller_ops_write_reg(nor,
|
||||
SPINOR_OP_CHIP_ERASE,
|
||||
NULL, 0);
|
||||
@ -1792,6 +1800,51 @@ destroy_erase_cmd_list:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spi_nor_erase_dice(struct spi_nor *nor, loff_t addr,
|
||||
size_t len, size_t die_size)
|
||||
{
|
||||
unsigned long timeout;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Scale the timeout linearly with the size of the flash, with
|
||||
* a minimum calibrated to an old 2MB flash. We could try to
|
||||
* pull these from CFI/SFDP, but these values should be good
|
||||
* enough for now.
|
||||
*/
|
||||
timeout = max(CHIP_ERASE_2MB_READY_WAIT_JIFFIES,
|
||||
CHIP_ERASE_2MB_READY_WAIT_JIFFIES *
|
||||
(unsigned long)(nor->mtd.size / SZ_2M));
|
||||
|
||||
do {
|
||||
ret = spi_nor_lock_device(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = spi_nor_write_enable(nor);
|
||||
if (ret) {
|
||||
spi_nor_unlock_device(nor);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = spi_nor_erase_die(nor, addr, die_size);
|
||||
|
||||
spi_nor_unlock_device(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = spi_nor_wait_till_ready_with_timeout(nor, timeout);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
addr += die_size;
|
||||
len -= die_size;
|
||||
|
||||
} while (len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Erase an address range on the nor chip. The address range may extend
|
||||
* one or more erase sectors. Return an error if there is a problem erasing.
|
||||
@ -1799,8 +1852,10 @@ destroy_erase_cmd_list:
|
||||
static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
struct spi_nor *nor = mtd_to_spi_nor(mtd);
|
||||
u32 addr, len;
|
||||
uint32_t rem;
|
||||
u8 n_dice = nor->params->n_dice;
|
||||
bool multi_die_erase = false;
|
||||
u32 addr, len, rem;
|
||||
size_t die_size;
|
||||
int ret;
|
||||
|
||||
dev_dbg(nor->dev, "at 0x%llx, len %lld\n", (long long)instr->addr,
|
||||
@ -1815,39 +1870,22 @@ static int spi_nor_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
addr = instr->addr;
|
||||
len = instr->len;
|
||||
|
||||
if (n_dice) {
|
||||
die_size = div_u64(mtd->size, n_dice);
|
||||
if (!(len & (die_size - 1)) && !(addr & (die_size - 1)))
|
||||
multi_die_erase = true;
|
||||
} else {
|
||||
die_size = mtd->size;
|
||||
}
|
||||
|
||||
ret = spi_nor_prep_and_lock_pe(nor, instr->addr, instr->len);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* whole-chip erase? */
|
||||
if (len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
|
||||
unsigned long timeout;
|
||||
|
||||
ret = spi_nor_lock_device(nor);
|
||||
if (ret)
|
||||
goto erase_err;
|
||||
|
||||
ret = spi_nor_write_enable(nor);
|
||||
if (ret) {
|
||||
spi_nor_unlock_device(nor);
|
||||
goto erase_err;
|
||||
}
|
||||
|
||||
ret = spi_nor_erase_chip(nor);
|
||||
spi_nor_unlock_device(nor);
|
||||
if (ret)
|
||||
goto erase_err;
|
||||
|
||||
/*
|
||||
* Scale the timeout linearly with the size of the flash, with
|
||||
* a minimum calibrated to an old 2MB flash. We could try to
|
||||
* pull these from CFI/SFDP, but these values should be good
|
||||
* enough for now.
|
||||
*/
|
||||
timeout = max(CHIP_ERASE_2MB_READY_WAIT_JIFFIES,
|
||||
CHIP_ERASE_2MB_READY_WAIT_JIFFIES *
|
||||
(unsigned long)(mtd->size / SZ_2M));
|
||||
ret = spi_nor_wait_till_ready_with_timeout(nor, timeout);
|
||||
/* chip (die) erase? */
|
||||
if ((len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) ||
|
||||
multi_die_erase) {
|
||||
ret = spi_nor_erase_dice(nor, addr, len, die_size);
|
||||
if (ret)
|
||||
goto erase_err;
|
||||
|
||||
@ -2146,7 +2184,7 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
if (is_power_of_2(page_size)) {
|
||||
page_offset = addr & (page_size - 1);
|
||||
} else {
|
||||
uint64_t aux = addr;
|
||||
u64 aux = addr;
|
||||
|
||||
page_offset = do_div(aux, page_size);
|
||||
}
|
||||
@ -2850,9 +2888,6 @@ static void spi_nor_init_flags(struct spi_nor *nor)
|
||||
nor->flags |= SNOR_F_HAS_SR_BP3_BIT6;
|
||||
}
|
||||
|
||||
if (flags & NO_CHIP_ERASE)
|
||||
nor->flags |= SNOR_F_NO_OP_CHIP_ERASE;
|
||||
|
||||
if (flags & SPI_NOR_RWW && nor->params->n_banks > 1 &&
|
||||
!nor->controller_ops)
|
||||
nor->flags |= SNOR_F_RWW;
|
||||
@ -2897,17 +2932,22 @@ static int spi_nor_late_init_params(struct spi_nor *nor)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Needed by some flashes late_init hooks. */
|
||||
spi_nor_init_flags(nor);
|
||||
|
||||
if (nor->info->fixups && nor->info->fixups->late_init) {
|
||||
ret = nor->info->fixups->late_init(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!nor->params->die_erase_opcode)
|
||||
nor->params->die_erase_opcode = SPINOR_OP_CHIP_ERASE;
|
||||
|
||||
/* Default method kept for backward compatibility. */
|
||||
if (!params->set_4byte_addr_mode)
|
||||
params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode_brwr;
|
||||
|
||||
spi_nor_init_flags(nor);
|
||||
spi_nor_init_fixup_flags(nor);
|
||||
|
||||
/*
|
||||
@ -3145,6 +3185,18 @@ int spi_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
|
||||
struct spi_nor_flash_parameter *params = nor->params;
|
||||
int ret;
|
||||
|
||||
if (enable) {
|
||||
/*
|
||||
* If the RESET# pin isn't hooked up properly, or the system
|
||||
* otherwise doesn't perform a reset command in the boot
|
||||
* sequence, it's impossible to 100% protect against unexpected
|
||||
* reboots (e.g., crashes). Warn the user (or hopefully, system
|
||||
* designer) that this is bad.
|
||||
*/
|
||||
WARN_ONCE(nor->flags & SNOR_F_BROKEN_RESET,
|
||||
"enabling reset hack; may not recover from unexpected reboots\n");
|
||||
}
|
||||
|
||||
ret = params->set_4byte_addr_mode(nor, enable);
|
||||
if (ret && ret != -EOPNOTSUPP)
|
||||
return ret;
|
||||
@ -3193,20 +3245,8 @@ static int spi_nor_init(struct spi_nor *nor)
|
||||
|
||||
if (nor->addr_nbytes == 4 &&
|
||||
nor->read_proto != SNOR_PROTO_8_8_8_DTR &&
|
||||
!(nor->flags & SNOR_F_4B_OPCODES)) {
|
||||
/*
|
||||
* If the RESET# pin isn't hooked up properly, or the system
|
||||
* otherwise doesn't perform a reset command in the boot
|
||||
* sequence, it's impossible to 100% protect against unexpected
|
||||
* reboots (e.g., crashes). Warn the user (or hopefully, system
|
||||
* designer) that this is bad.
|
||||
*/
|
||||
WARN_ONCE(nor->flags & SNOR_F_BROKEN_RESET,
|
||||
"enabling reset hack; may not recover from unexpected reboots\n");
|
||||
err = spi_nor_set_4byte_addr_mode(nor, true);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
!(nor->flags & SNOR_F_4B_OPCODES))
|
||||
return spi_nor_set_4byte_addr_mode(nor, true);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -3453,9 +3493,7 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
|
||||
{
|
||||
const struct flash_info *info;
|
||||
struct device *dev = nor->dev;
|
||||
struct mtd_info *mtd = &nor->mtd;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
ret = spi_nor_check(nor);
|
||||
if (ret)
|
||||
@ -3519,25 +3557,9 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
|
||||
/* No mtd_info fields should be used up to this point. */
|
||||
spi_nor_set_mtd_info(nor);
|
||||
|
||||
dev_info(dev, "%s (%lld Kbytes)\n", info->name,
|
||||
(long long)mtd->size >> 10);
|
||||
dev_dbg(dev, "Manufacturer and device ID: %*phN\n",
|
||||
SPI_NOR_MAX_ID_LEN, nor->id);
|
||||
|
||||
dev_dbg(dev,
|
||||
"mtd .name = %s, .size = 0x%llx (%lldMiB), "
|
||||
".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n",
|
||||
mtd->name, (long long)mtd->size, (long long)(mtd->size >> 20),
|
||||
mtd->erasesize, mtd->erasesize / 1024, mtd->numeraseregions);
|
||||
|
||||
if (mtd->numeraseregions)
|
||||
for (i = 0; i < mtd->numeraseregions; i++)
|
||||
dev_dbg(dev,
|
||||
"mtd.eraseregions[%d] = { .offset = 0x%llx, "
|
||||
".erasesize = 0x%.8x (%uKiB), "
|
||||
".numblocks = %d }\n",
|
||||
i, (long long)mtd->eraseregions[i].offset,
|
||||
mtd->eraseregions[i].erasesize,
|
||||
mtd->eraseregions[i].erasesize / 1024,
|
||||
mtd->eraseregions[i].numblocks);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_nor_scan);
|
||||
|
@ -85,9 +85,9 @@
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
#define SPI_NOR_CHIP_ERASE_OP \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(SPINOR_OP_CHIP_ERASE, 0), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
#define SPI_NOR_DIE_ERASE_OP(opcode, addr_nbytes, addr, dice) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0), \
|
||||
SPI_MEM_OP_ADDR(dice ? addr_nbytes : 0, addr, 0), \
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
@ -293,9 +293,9 @@ struct spi_nor_erase_map {
|
||||
* @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);
|
||||
int (*lock)(struct spi_nor *nor, loff_t ofs, u64 len);
|
||||
int (*unlock)(struct spi_nor *nor, loff_t ofs, u64 len);
|
||||
int (*is_locked)(struct spi_nor *nor, loff_t ofs, u64 len);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -362,6 +362,7 @@ struct spi_nor_otp {
|
||||
* command in octal DTR mode.
|
||||
* @n_banks: number of banks.
|
||||
* @n_dice: number of dice in the flash memory.
|
||||
* @die_erase_opcode: die erase opcode. Defaults to SPINOR_OP_CHIP_ERASE.
|
||||
* @vreg_offset: volatile register offset for each die.
|
||||
* @hwcaps: describes the read and page program hardware
|
||||
* capabilities.
|
||||
@ -399,6 +400,7 @@ struct spi_nor_flash_parameter {
|
||||
u8 rdsr_addr_nbytes;
|
||||
u8 n_banks;
|
||||
u8 n_dice;
|
||||
u8 die_erase_opcode;
|
||||
u32 *vreg_offset;
|
||||
|
||||
struct spi_nor_hwcaps hwcaps;
|
||||
@ -463,7 +465,7 @@ struct spi_nor_id {
|
||||
* struct flash_info - SPI NOR flash_info entry.
|
||||
* @id: pointer to struct spi_nor_id or NULL, which means "no ID" (mostly
|
||||
* older chips).
|
||||
* @name: the name of the flash.
|
||||
* @name: (obsolete) the name of the flash. Do not set it for new additions.
|
||||
* @size: the size of the flash in bytes.
|
||||
* @sector_size: (optional) the size listed here is what works with
|
||||
* SPINOR_OP_SE, which isn't necessarily called a "sector" by
|
||||
@ -487,7 +489,6 @@ struct spi_nor_id {
|
||||
* Usually these will power-up in a write-protected
|
||||
* state.
|
||||
* SPI_NOR_NO_ERASE: no erase command needed.
|
||||
* NO_CHIP_ERASE: chip does not support chip erase.
|
||||
* SPI_NOR_NO_FR: can't do fastread.
|
||||
* SPI_NOR_QUAD_PP: flash supports Quad Input Page Program.
|
||||
* SPI_NOR_RWW: flash supports reads while write.
|
||||
@ -537,10 +538,9 @@ struct flash_info {
|
||||
#define SPI_NOR_BP3_SR_BIT6 BIT(4)
|
||||
#define SPI_NOR_SWP_IS_VOLATILE BIT(5)
|
||||
#define SPI_NOR_NO_ERASE BIT(6)
|
||||
#define NO_CHIP_ERASE BIT(7)
|
||||
#define SPI_NOR_NO_FR BIT(8)
|
||||
#define SPI_NOR_QUAD_PP BIT(9)
|
||||
#define SPI_NOR_RWW BIT(10)
|
||||
#define SPI_NOR_NO_FR BIT(7)
|
||||
#define SPI_NOR_QUAD_PP BIT(8)
|
||||
#define SPI_NOR_RWW BIT(9)
|
||||
|
||||
u8 no_sfdp_flags;
|
||||
#define SPI_NOR_SKIP_SFDP BIT(0)
|
||||
|
@ -138,7 +138,7 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
|
||||
|
||||
if (!(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
|
||||
string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf));
|
||||
seq_printf(s, " %02x (%s)\n", SPINOR_OP_CHIP_ERASE, buf);
|
||||
seq_printf(s, " %02x (%s)\n", nor->params->die_erase_opcode, buf);
|
||||
}
|
||||
|
||||
seq_puts(s, "\nsector map\n");
|
||||
|
@ -11,6 +11,7 @@
|
||||
/* flash_info mfr_flag. Used to read proprietary FSR register. */
|
||||
#define USE_FSR BIT(0)
|
||||
|
||||
#define SPINOR_OP_MT_DIE_ERASE 0xc4 /* Chip (die) erase opcode */
|
||||
#define SPINOR_OP_RDFSR 0x70 /* Read flag status register */
|
||||
#define SPINOR_OP_CLFSR 0x50 /* Clear flag status register */
|
||||
#define SPINOR_OP_MT_DTR_RD 0xfd /* Fast Read opcode in DTR mode */
|
||||
@ -192,6 +193,50 @@ static struct spi_nor_fixups mt25qu512a_fixups = {
|
||||
.post_bfpt = mt25qu512a_post_bfpt_fixup,
|
||||
};
|
||||
|
||||
static int st_nor_four_die_late_init(struct spi_nor *nor)
|
||||
{
|
||||
struct spi_nor_flash_parameter *params = nor->params;
|
||||
|
||||
params->die_erase_opcode = SPINOR_OP_MT_DIE_ERASE;
|
||||
params->n_dice = 4;
|
||||
|
||||
/*
|
||||
* Unfortunately the die erase opcode does not have a 4-byte opcode
|
||||
* correspondent for these flashes. The SFDP 4BAIT table fails to
|
||||
* consider the die erase too. We're forced to enter in the 4 byte
|
||||
* address mode in order to benefit of the die erase.
|
||||
*/
|
||||
return spi_nor_set_4byte_addr_mode(nor, true);
|
||||
}
|
||||
|
||||
static int st_nor_two_die_late_init(struct spi_nor *nor)
|
||||
{
|
||||
struct spi_nor_flash_parameter *params = nor->params;
|
||||
|
||||
params->die_erase_opcode = SPINOR_OP_MT_DIE_ERASE;
|
||||
params->n_dice = 2;
|
||||
|
||||
/*
|
||||
* Unfortunately the die erase opcode does not have a 4-byte opcode
|
||||
* correspondent for these flashes. The SFDP 4BAIT table fails to
|
||||
* consider the die erase too. We're forced to enter in the 4 byte
|
||||
* address mode in order to benefit of the die erase.
|
||||
*/
|
||||
return spi_nor_set_4byte_addr_mode(nor, true);
|
||||
}
|
||||
|
||||
static struct spi_nor_fixups n25q00_fixups = {
|
||||
.late_init = st_nor_four_die_late_init,
|
||||
};
|
||||
|
||||
static struct spi_nor_fixups mt25q01_fixups = {
|
||||
.late_init = st_nor_two_die_late_init,
|
||||
};
|
||||
|
||||
static struct spi_nor_fixups mt25q02_fixups = {
|
||||
.late_init = st_nor_four_die_late_init,
|
||||
};
|
||||
|
||||
static const struct flash_info st_nor_parts[] = {
|
||||
{
|
||||
.name = "m25p05-nonjedec",
|
||||
@ -366,16 +411,17 @@ static const struct flash_info st_nor_parts[] = {
|
||||
.name = "n25q00",
|
||||
.size = SZ_128M,
|
||||
.flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_4BIT_BP |
|
||||
SPI_NOR_BP3_SR_BIT6 | NO_CHIP_ERASE,
|
||||
SPI_NOR_BP3_SR_BIT6,
|
||||
.no_sfdp_flags = SECT_4K | SPI_NOR_QUAD_READ,
|
||||
.mfr_flags = USE_FSR,
|
||||
.fixups = &n25q00_fixups,
|
||||
}, {
|
||||
.id = SNOR_ID(0x20, 0xba, 0x22),
|
||||
.name = "mt25ql02g",
|
||||
.size = SZ_256M,
|
||||
.flags = NO_CHIP_ERASE,
|
||||
.no_sfdp_flags = SECT_4K | SPI_NOR_QUAD_READ,
|
||||
.mfr_flags = USE_FSR,
|
||||
.fixups = &mt25q02_fixups,
|
||||
}, {
|
||||
.id = SNOR_ID(0x20, 0xbb, 0x15),
|
||||
.name = "n25q016a",
|
||||
@ -429,20 +475,25 @@ static const struct flash_info st_nor_parts[] = {
|
||||
SPI_NOR_BP3_SR_BIT6,
|
||||
.no_sfdp_flags = SECT_4K | SPI_NOR_QUAD_READ,
|
||||
.mfr_flags = USE_FSR,
|
||||
}, {
|
||||
.id = SNOR_ID(0x20, 0xbb, 0x21, 0x10, 0x44, 0x00),
|
||||
.name = "mt25qu01g",
|
||||
.mfr_flags = USE_FSR,
|
||||
.fixups = &mt25q01_fixups,
|
||||
}, {
|
||||
.id = SNOR_ID(0x20, 0xbb, 0x21),
|
||||
.name = "n25q00a",
|
||||
.size = SZ_128M,
|
||||
.flags = NO_CHIP_ERASE,
|
||||
.no_sfdp_flags = SECT_4K | SPI_NOR_QUAD_READ,
|
||||
.mfr_flags = USE_FSR,
|
||||
.fixups = &n25q00_fixups,
|
||||
}, {
|
||||
.id = SNOR_ID(0x20, 0xbb, 0x22),
|
||||
.name = "mt25qu02g",
|
||||
.size = SZ_256M,
|
||||
.flags = NO_CHIP_ERASE,
|
||||
.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
|
||||
.mfr_flags = USE_FSR,
|
||||
.fixups = &mt25q02_fixups,
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -446,6 +446,7 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
|
||||
u32 dword;
|
||||
u16 half;
|
||||
u8 erase_mask;
|
||||
u8 wait_states, mode_clocks, opcode;
|
||||
|
||||
/* JESD216 Basic Flash Parameter Table length is at least 9 DWORDs. */
|
||||
if (bfpt_header->length < BFPT_DWORD_MAX_JESD216)
|
||||
@ -631,6 +632,32 @@ static int spi_nor_parse_bfpt(struct spi_nor *nor,
|
||||
if (bfpt_header->length == BFPT_DWORD_MAX_JESD216B)
|
||||
return spi_nor_post_bfpt_fixups(nor, bfpt_header, &bfpt);
|
||||
|
||||
/* Parse 1-1-8 read instruction */
|
||||
opcode = FIELD_GET(BFPT_DWORD17_RD_1_1_8_CMD, bfpt.dwords[SFDP_DWORD(17)]);
|
||||
if (opcode) {
|
||||
mode_clocks = FIELD_GET(BFPT_DWORD17_RD_1_1_8_MODE_CLOCKS,
|
||||
bfpt.dwords[SFDP_DWORD(17)]);
|
||||
wait_states = FIELD_GET(BFPT_DWORD17_RD_1_1_8_WAIT_STATES,
|
||||
bfpt.dwords[SFDP_DWORD(17)]);
|
||||
params->hwcaps.mask |= SNOR_HWCAPS_READ_1_1_8;
|
||||
spi_nor_set_read_settings(¶ms->reads[SNOR_CMD_READ_1_1_8],
|
||||
mode_clocks, wait_states, opcode,
|
||||
SNOR_PROTO_1_1_8);
|
||||
}
|
||||
|
||||
/* Parse 1-8-8 read instruction */
|
||||
opcode = FIELD_GET(BFPT_DWORD17_RD_1_8_8_CMD, bfpt.dwords[SFDP_DWORD(17)]);
|
||||
if (opcode) {
|
||||
mode_clocks = FIELD_GET(BFPT_DWORD17_RD_1_8_8_MODE_CLOCKS,
|
||||
bfpt.dwords[SFDP_DWORD(17)]);
|
||||
wait_states = FIELD_GET(BFPT_DWORD17_RD_1_8_8_WAIT_STATES,
|
||||
bfpt.dwords[SFDP_DWORD(17)]);
|
||||
params->hwcaps.mask |= SNOR_HWCAPS_READ_1_8_8;
|
||||
spi_nor_set_read_settings(¶ms->reads[SNOR_CMD_READ_1_8_8],
|
||||
mode_clocks, wait_states, opcode,
|
||||
SNOR_PROTO_1_8_8);
|
||||
}
|
||||
|
||||
/* 8D-8D-8D command extension. */
|
||||
switch (bfpt.dwords[SFDP_DWORD(18)] & BFPT_DWORD18_CMD_EXT_MASK) {
|
||||
case BFPT_DWORD18_CMD_EXT_REP:
|
||||
@ -968,6 +995,8 @@ static int spi_nor_parse_4bait(struct spi_nor *nor,
|
||||
{ SNOR_HWCAPS_READ_1_1_1_DTR, BIT(13) },
|
||||
{ SNOR_HWCAPS_READ_1_2_2_DTR, BIT(14) },
|
||||
{ SNOR_HWCAPS_READ_1_4_4_DTR, BIT(15) },
|
||||
{ SNOR_HWCAPS_READ_1_1_8, BIT(20) },
|
||||
{ SNOR_HWCAPS_READ_1_8_8, BIT(21) },
|
||||
};
|
||||
static const struct sfdp_4bait programs[] = {
|
||||
{ SNOR_HWCAPS_PP, BIT(6) },
|
||||
|
@ -118,6 +118,13 @@ struct sfdp_bfpt {
|
||||
(BFPT_DWORD16_EN4B_EN4B | BFPT_DWORD16_EX4B_EX4B)
|
||||
#define BFPT_DWORD16_SWRST_EN_RST BIT(12)
|
||||
|
||||
#define BFPT_DWORD17_RD_1_1_8_CMD GENMASK(31, 24)
|
||||
#define BFPT_DWORD17_RD_1_1_8_MODE_CLOCKS GENMASK(23, 21)
|
||||
#define BFPT_DWORD17_RD_1_1_8_WAIT_STATES GENMASK(20, 16)
|
||||
#define BFPT_DWORD17_RD_1_8_8_CMD GENMASK(15, 8)
|
||||
#define BFPT_DWORD17_RD_1_8_8_MODE_CLOCKS GENMASK(7, 5)
|
||||
#define BFPT_DWORD17_RD_1_8_8_WAIT_STATES GENMASK(4, 0)
|
||||
|
||||
#define BFPT_DWORD18_CMD_EXT_MASK GENMASK(30, 29)
|
||||
#define BFPT_DWORD18_CMD_EXT_REP (0x0UL << 29) /* Repeat */
|
||||
#define BFPT_DWORD18_CMD_EXT_INV (0x1UL << 29) /* Invert */
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
#define SPINOR_OP_CLSR 0x30 /* Clear status register 1 */
|
||||
#define SPINOR_OP_CLPEF 0x82 /* Clear program/erase failure flags */
|
||||
#define SPINOR_OP_CYPRESS_DIE_ERASE 0x61 /* Chip (die) erase */
|
||||
#define SPINOR_OP_RD_ANY_REG 0x65 /* Read any register */
|
||||
#define SPINOR_OP_WR_ANY_REG 0x71 /* Write any register */
|
||||
#define SPINOR_REG_CYPRESS_VREG 0x00800000
|
||||
@ -644,6 +645,7 @@ static int s25hx_t_late_init(struct spi_nor *nor)
|
||||
params->ready = cypress_nor_sr_ready_and_clear;
|
||||
cypress_nor_ecc_init(nor);
|
||||
|
||||
params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -933,7 +935,6 @@ static const struct flash_info spansion_nor_parts[] = {
|
||||
.id = SNOR_ID(0x34, 0x2a, 0x1c, 0x0f, 0x00, 0x90),
|
||||
.name = "s25hl02gt",
|
||||
.mfr_flags = USE_CLPEF,
|
||||
.flags = NO_CHIP_ERASE,
|
||||
.fixups = &s25hx_t_fixups
|
||||
}, {
|
||||
.id = SNOR_ID(0x34, 0x2b, 0x19, 0x0f, 0x08, 0x90),
|
||||
@ -954,7 +955,6 @@ static const struct flash_info spansion_nor_parts[] = {
|
||||
.id = SNOR_ID(0x34, 0x2b, 0x1c, 0x0f, 0x00, 0x90),
|
||||
.name = "s25hs02gt",
|
||||
.mfr_flags = USE_CLPEF,
|
||||
.flags = NO_CHIP_ERASE,
|
||||
.fixups = &s25hx_t_fixups
|
||||
}, {
|
||||
.id = SNOR_ID(0x34, 0x5a, 0x1a),
|
||||
|
@ -13,12 +13,12 @@
|
||||
|
||||
#define SST26VF_CR_BPNV BIT(3)
|
||||
|
||||
static int sst26vf_nor_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int sst26vf_nor_lock(struct spi_nor *nor, loff_t ofs, u64 len)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static int sst26vf_nor_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int sst26vf_nor_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -38,7 +38,7 @@ static int sst26vf_nor_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
return spi_nor_global_block_unlock(nor);
|
||||
}
|
||||
|
||||
static int sst26vf_nor_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int sst26vf_nor_is_locked(struct spi_nor *nor, loff_t ofs, u64 len)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
|
||||
}
|
||||
|
||||
static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs,
|
||||
uint64_t *len)
|
||||
u64 *len)
|
||||
{
|
||||
struct mtd_info *mtd = &nor->mtd;
|
||||
u64 min_prot_len;
|
||||
@ -90,10 +90,10 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs,
|
||||
* (if @locked is false); false otherwise.
|
||||
*/
|
||||
static bool spi_nor_check_lock_status_sr(struct spi_nor *nor, loff_t ofs,
|
||||
uint64_t len, u8 sr, bool locked)
|
||||
u64 len, u8 sr, bool locked)
|
||||
{
|
||||
loff_t lock_offs, lock_offs_max, offs_max;
|
||||
uint64_t lock_len;
|
||||
u64 lock_len;
|
||||
|
||||
if (!len)
|
||||
return true;
|
||||
@ -111,14 +111,13 @@ static bool spi_nor_check_lock_status_sr(struct spi_nor *nor, loff_t ofs,
|
||||
return (ofs >= lock_offs_max) || (offs_max <= lock_offs);
|
||||
}
|
||||
|
||||
static bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, uint64_t len,
|
||||
u8 sr)
|
||||
static bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, u8 sr)
|
||||
{
|
||||
return spi_nor_check_lock_status_sr(nor, ofs, len, sr, true);
|
||||
}
|
||||
|
||||
static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs,
|
||||
uint64_t len, u8 sr)
|
||||
static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, u64 len,
|
||||
u8 sr)
|
||||
{
|
||||
return spi_nor_check_lock_status_sr(nor, ofs, len, sr, false);
|
||||
}
|
||||
@ -156,7 +155,7 @@ static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs,
|
||||
*
|
||||
* Returns negative on errors, 0 on success.
|
||||
*/
|
||||
static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
|
||||
{
|
||||
struct mtd_info *mtd = &nor->mtd;
|
||||
u64 min_prot_len;
|
||||
@ -246,7 +245,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
*
|
||||
* Returns negative on errors, 0 on success.
|
||||
*/
|
||||
static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
|
||||
{
|
||||
struct mtd_info *mtd = &nor->mtd;
|
||||
u64 min_prot_len;
|
||||
@ -331,7 +330,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
* Returns 1 if entire region is locked, 0 if any portion is unlocked, and
|
||||
* negative on errors.
|
||||
*/
|
||||
static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, uint64_t len)
|
||||
static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, u64 len)
|
||||
{
|
||||
int ret;
|
||||
|
||||
@ -353,7 +352,7 @@ void spi_nor_init_default_locking_ops(struct spi_nor *nor)
|
||||
nor->params->locking_ops = &spi_nor_sr_locking_ops;
|
||||
}
|
||||
|
||||
static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, u64 len)
|
||||
{
|
||||
struct spi_nor *nor = mtd_to_spi_nor(mtd);
|
||||
int ret;
|
||||
@ -368,7 +367,7 @@ static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spi_nor_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
static int spi_nor_unlock(struct mtd_info *mtd, loff_t ofs, u64 len)
|
||||
{
|
||||
struct spi_nor *nor = mtd_to_spi_nor(mtd);
|
||||
int ret;
|
||||
@ -383,7 +382,7 @@ static int spi_nor_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spi_nor_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
static int spi_nor_is_locked(struct mtd_info *mtd, loff_t ofs, u64 len)
|
||||
{
|
||||
struct spi_nor *nor = mtd_to_spi_nor(mtd);
|
||||
int ret;
|
||||
|
@ -78,6 +78,8 @@ static umode_t spi_nor_sysfs_is_visible(struct kobject *kobj,
|
||||
|
||||
if (attr == &dev_attr_manufacturer.attr && !nor->manufacturer)
|
||||
return 0;
|
||||
if (attr == &dev_attr_partname.attr && !nor->info->name)
|
||||
return 0;
|
||||
if (attr == &dev_attr_jedec_id.attr && !nor->info->id && !nor->id)
|
||||
return 0;
|
||||
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
struct ssfdcr_record {
|
||||
struct mtd_blktrans_dev mbd;
|
||||
int usecount;
|
||||
unsigned char heads;
|
||||
unsigned char sectors;
|
||||
unsigned short cylinders;
|
||||
|
@ -1003,6 +1003,8 @@ struct nand_op_parser {
|
||||
/**
|
||||
* struct nand_operation - NAND operation descriptor
|
||||
* @cs: the CS line to select for this NAND operation
|
||||
* @deassert_wp: set to true when the operation requires the WP pin to be
|
||||
* de-asserted (ERASE, PROG, ...)
|
||||
* @instrs: array of instructions to execute
|
||||
* @ninstrs: length of the @instrs array
|
||||
*
|
||||
@ -1010,6 +1012,7 @@ struct nand_op_parser {
|
||||
*/
|
||||
struct nand_operation {
|
||||
unsigned int cs;
|
||||
bool deassert_wp;
|
||||
const struct nand_op_instr *instrs;
|
||||
unsigned int ninstrs;
|
||||
};
|
||||
@ -1021,6 +1024,14 @@ struct nand_operation {
|
||||
.ninstrs = ARRAY_SIZE(_instrs), \
|
||||
}
|
||||
|
||||
#define NAND_DESTRUCTIVE_OPERATION(_cs, _instrs) \
|
||||
{ \
|
||||
.cs = _cs, \
|
||||
.deassert_wp = true, \
|
||||
.instrs = _instrs, \
|
||||
.ninstrs = ARRAY_SIZE(_instrs), \
|
||||
}
|
||||
|
||||
int nand_op_parser_exec_op(struct nand_chip *chip,
|
||||
const struct nand_op_parser *parser,
|
||||
const struct nand_operation *op, bool check_only);
|
||||
@ -1104,6 +1115,7 @@ struct nand_controller_ops {
|
||||
* the bus without restarting an entire read operation nor
|
||||
* changing the column.
|
||||
* @supported_op.cont_read: The controller supports sequential cache reads.
|
||||
* @controller_wp: the controller is in charge of handling the WP pin.
|
||||
*/
|
||||
struct nand_controller {
|
||||
struct mutex lock;
|
||||
@ -1112,6 +1124,7 @@ struct nand_controller {
|
||||
unsigned int data_only_read: 1;
|
||||
unsigned int cont_read: 1;
|
||||
} supported_op;
|
||||
bool controller_wp;
|
||||
};
|
||||
|
||||
static inline void nand_controller_init(struct nand_controller *nfc)
|
||||
@ -1265,6 +1278,7 @@ struct nand_secure_region {
|
||||
* @cont_read: Sequential page read internals
|
||||
* @cont_read.ongoing: Whether a continuous read is ongoing or not
|
||||
* @cont_read.first_page: Start of the continuous read operation
|
||||
* @cont_read.pause_page: End of the current sequential cache read operation
|
||||
* @cont_read.last_page: End of the continuous read operation
|
||||
* @controller: The hardware controller structure which is shared among multiple
|
||||
* independent devices
|
||||
@ -1321,6 +1335,7 @@ struct nand_chip {
|
||||
struct {
|
||||
bool ongoing;
|
||||
unsigned int first_page;
|
||||
unsigned int pause_page;
|
||||
unsigned int last_page;
|
||||
} cont_read;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user