mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-11-24 14:20:55 +07:00
MTD core changes:
* Fix issue where write_cached_data() fails but write() still returns success * maps: sa1100-flash: Replace zero-length array with flexible-array member * phram: Fix a double free issue in error path * Convert fallthrough comments into statements * MAINTAINERS: Add the IRC channel to the MTD related subsystems Raw NAND core changes: * Add support for manufacturer specific suspend/resume operation * Add support for manufacturer specific lock/unlock operation * Replace zero-length array with flexible-array member * Fix a typo ("manufecturer") * Ensure nand_soft_waitrdy wait period is enough Raw NAND controller driver changes: * Brcmnand: - Add support for flash-edu for dma transfers (+ bindings) * Cadence: - Reinit completion before executing a new command - Change bad block marker size - Fix the calculation of the avaialble OOB size - Get meta data size from registers * Qualcom: - Use dma_request_chan() instead dma_request_slave_channel() - Release resources on failure within qcom_nandc_alloc() * Allwinner: - Use dma_request_chan() instead dma_request_slave_channel() * Marvell: - Use dma_request_chan() instead dma_request_slave_channel() - Release DMA channel on error * Freescale: - Use dma_request_chan() instead dma_request_slave_channel() * Macronix: - Add support for Macronix NAND randomizer (+ bindings) * Ams-delta: - Rename structures and functions to gpio_nand* - Make the driver custom I/O ready - Drop useless local variable - Support custom driver initialisation - Add module device tables - Handle more GPIO pins as optional - Make read pulses optional - Don't hardcode read/write pulse widths - Push inversion handling to gpiolib - Enable OF partition info support - Drop board specific partition info - Use struct gpio_nand_platdata - Write protect device during probe * Ingenic: - Use devm_platform_ioremap_resource() - Add dependency on MIPS || COMPILE_TEST * Denali: - Deassert write protect pin * ST: - Use dma_request_chan() instead dma_request_slave_channel() Raw NAND chip driver changes: * Toshiba: - Support reading the number of bitflips for BENAND (Built-in ECC NAND) * Macronix: - Add support for deep power down mode - Add support for block protection SPI-NAND core changes: * Do not erase the block before writing a bad block marker * Explicitly use MTD_OPS_RAW to write the bad block marker to OOB * Stop using spinand->oobbuf for buffering bad block markers * Rework detect procedure for different READ_ID operation SPI-NAND driver changes: * Toshiba: - Support for new Kioxia Serial NAND - Rename function name to change suffix and prefix (8Gbit) - Add comment about Kioxia ID * Micron: - Add new Micron SPI NAND devices with multiple dies - Add M70A series Micron SPI NAND devices - identify SPI NAND device with Continuous Read mode - Add new Micron SPI NAND devices - Describe the SPI NAND device MT29F2G01ABAGD - Generalize the OOB layout structure and function names SPI NOR core changes: * Move all the manufacturer specific quirks/code out of the core, to make the core logic more readable and thus ease maintenance. * Move the SFDP logic out of the core, it provides a better separation between the SFDP parsing and core logic. * Trim what is exposed in spi-nor.h. The SPI NOR controllers drivers must not be able to use structures that are meant just for the SPI NOR core. * Use the spi-mem direct mapping API to let advanced controllers optimize the read/write operations when they support direct mapping. * Add generic formula for the Status Register block protection handling. It fixes some long standing locking limitations and eases the addition of the 4bit block protection support. * Add block protection support for flashes with 4 block protection bits in the Status Register. SPI NOR controller drivers changes: * The mtk-quadspi driver is replaced by the new spi-mem spi-mtk-nor driver. * Merge tag 'mtk-mtd-spi-move' into spi-nor/next to avoid conflicts. HyperBus changes: * Print error msg when compatible is wrong or missing * Move mapping of direct access window from core to individual drivers -----BEGIN PGP SIGNATURE----- iQEzBAABCgAdFiEE9HuaYnbmDhq/XIDIJWrqGEe9VoQFAl6GEU8ACgkQJWrqGEe9 VoSQ8gf/WV1vfxfAlgKRQ6WGkdrI+EndmciKx/sqf9KfAu6uNLHx3qcxQeibqvgC KmIgJbCLXhgCUBZQLqyjqHtO3zImgPI2WWWR/tkieNaT9NT5jrAunYZpiqkCkjVD HjML4iVcLwDJoHPZWTTTsFWd2cDLA8nFVXJQeaGRgdsx3j3D9OSOnfLGTUGfcg2H lC0BxA6JRO7tdZlErqzy9BXUasjO5C7rsRzqgTiGT4h/H2yhYmDQhMW+Kz86zWiB XdZsZJ2qLlTKntQmzR+30vbolId3iZqsO9q0mVkqWd2UQn5TpmmzNqLoYA6ys8KK lW/1E53E3/Pq0+5ejGbD5m4afff+nw== =/p2x -----END PGP SIGNATURE----- Merge tag 'mtd/for-5.7' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux Pull MTD updates from Miquel Raynal: "MTD core changes: - Fix issue where write_cached_data() fails but write() still returns success - maps: sa1100-flash: Replace zero-length array with flexible-array member - phram: Fix a double free issue in error path - Convert fallthrough comments into statements - MAINTAINERS: Add the IRC channel to the MTD related subsystems Raw NAND core changes: - Add support for manufacturer specific suspend/resume operation - Add support for manufacturer specific lock/unlock operation - Replace zero-length array with flexible-array member - Fix a typo ("manufecturer") - Ensure nand_soft_waitrdy wait period is enough Raw NAND controller driver changes: - Brcmnand: * Add support for flash-edu for dma transfers (+ bindings) - Cadence: * Reinit completion before executing a new command * Change bad block marker size * Fix the calculation of the avaialble OOB size * Get meta data size from registers - Qualcom: * Use dma_request_chan() instead dma_request_slave_channel() * Release resources on failure within qcom_nandc_alloc() - Allwinner: * Use dma_request_chan() instead dma_request_slave_channel() - Marvell: * Use dma_request_chan() instead dma_request_slave_channel() * Release DMA channel on error - Freescale: * Use dma_request_chan() instead dma_request_slave_channel() - Macronix: * Add support for Macronix NAND randomizer (+ bindings) - Ams-delta: * Rename structures and functions to gpio_nand* * Make the driver custom I/O ready * Drop useless local variable * Support custom driver initialisation * Add module device tables * Handle more GPIO pins as optional * Make read pulses optional * Don't hardcode read/write pulse widths * Push inversion handling to gpiolib * Enable OF partition info support * Drop board specific partition info * Use struct gpio_nand_platdata * Write protect device during probe - Ingenic: * Use devm_platform_ioremap_resource() * Add dependency on MIPS || COMPILE_TEST - Denali: * Deassert write protect pin - ST: * Use dma_request_chan() instead dma_request_slave_channel() Raw NAND chip driver changes: - Toshiba: * Support reading the number of bitflips for BENAND (Built-in ECC NAND) - Macronix: * Add support for deep power down mode * Add support for block protection SPI-NAND core changes: - Do not erase the block before writing a bad block marker - Explicitly use MTD_OPS_RAW to write the bad block marker to OOB - Stop using spinand->oobbuf for buffering bad block markers - Rework detect procedure for different READ_ID operation SPI-NAND driver changes: - Toshiba: * Support for new Kioxia Serial NAND * Rename function name to change suffix and prefix (8Gbit) * Add comment about Kioxia ID - Micron: * Add new Micron SPI NAND devices with multiple dies * Add M70A series Micron SPI NAND devices * identify SPI NAND device with Continuous Read mode * Add new Micron SPI NAND devices * Describe the SPI NAND device MT29F2G01ABAGD * Generalize the OOB layout structure and function names SPI NOR core changes: - Move all the manufacturer specific quirks/code out of the core, to make the core logic more readable and thus ease maintenance. - Move the SFDP logic out of the core, it provides a better separation between the SFDP parsing and core logic. - Trim what is exposed in spi-nor.h. The SPI NOR controllers drivers must not be able to use structures that are meant just for the SPI NOR core. - Use the spi-mem direct mapping API to let advanced controllers optimize the read/write operations when they support direct mapping. - Add generic formula for the Status Register block protection handling. It fixes some long standing locking limitations and eases the addition of the 4bit block protection support. - Add block protection support for flashes with 4 block protection bits in the Status Register. SPI NOR controller drivers changes: - The mtk-quadspi driver is replaced by the new spi-mem spi-mtk-nor driver. - Merge tag 'mtk-mtd-spi-move' into spi-nor/next to avoid conflicts. HyperBus changes: - Print error msg when compatible is wrong or missing - Move mapping of direct access window from core to individual drivers" * tag 'mtd/for-5.7' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux: (103 commits) mtd: Convert fallthrough comments into statements mtd: rawnand: toshiba: Support reading the number of bitflips for BENAND (Built-in ECC NAND) MAINTAINERS: Add the IRC channel to the MTD related subsystems mtd: Fix issue where write_cached_data() fails but write() still returns success mtd: maps: sa1100-flash: Replace zero-length array with flexible-array member mtd: phram: fix a double free issue in error path mtd: spinand: toshiba: Support for new Kioxia Serial NAND mtd: spinand: toshiba: Rename function name to change suffix and prefix (8Gbit) mtd: rawnand: macronix: Add support for deep power down mode mtd: rawnand: Add support for manufacturer specific suspend/resume operation mtd: spi-nor: Enable locking for n25q512ax3/n25q512a mtd: spi-nor: Add SR 4bit block protection support mtd: spi-nor: Add generic formula for SR block protection handling mtd: spi-nor: Set all BP bits to one when lock_len == mtd->size mtd: spi-nor: controllers: aspeed-smc: Replace zero-length array with flexible-array member mtd: spi-nor: Clear WEL bit when erase or program errors occur MAINTAINERS: update entry after SPI NOR controller move mtd: spi-nor: Trim what is exposed in spi-nor.h mtd: spi-nor: Drop the MFR definitions mtd: spi-nor: Get rid of the now empty spi_nor_ids[] table ...
This commit is contained in:
commit
e109f50607
@ -35,11 +35,11 @@ Required properties:
|
||||
(optional) NAND flash cache range (if at non-standard offset)
|
||||
- reg-names : a list of the names corresponding to the previous register
|
||||
ranges. Should contain "nand" and (optionally)
|
||||
"flash-dma" and/or "nand-cache".
|
||||
- interrupts : The NAND CTLRDY interrupt and (if Flash DMA is available)
|
||||
FLASH_DMA_DONE
|
||||
- interrupt-names : May be "nand_ctlrdy" or "flash_dma_done", if broken out as
|
||||
individual interrupts.
|
||||
"flash-dma" or "flash-edu" and/or "nand-cache".
|
||||
- interrupts : The NAND CTLRDY interrupt, (if Flash DMA is available)
|
||||
FLASH_DMA_DONE and if EDU is avaialble and used FLASH_EDU_DONE
|
||||
- interrupt-names : May be "nand_ctlrdy" or "flash_dma_done" or "flash_edu_done",
|
||||
if broken out as individual interrupts.
|
||||
May be "nand", if the SoC has the individual NAND
|
||||
interrupts multiplexed behind another custom piece of
|
||||
hardware
|
||||
|
27
Documentation/devicetree/bindings/mtd/nand-macronix.txt
Normal file
27
Documentation/devicetree/bindings/mtd/nand-macronix.txt
Normal file
@ -0,0 +1,27 @@
|
||||
Macronix NANDs Device Tree Bindings
|
||||
-----------------------------------
|
||||
|
||||
Macronix NANDs support randomizer operation for scrambling user data,
|
||||
which can be enabled with a SET_FEATURE. The penalty when using the
|
||||
randomizer are subpage accesses prohibited and more time period needed
|
||||
for program operation, i.e., tPROG 300us to 340us (randomizer enabled).
|
||||
Enabling the randomizer is a one time persistent and non reversible
|
||||
operation.
|
||||
|
||||
For more high-reliability concern, if subpage write is not available
|
||||
with hardware ECC and not enabled at UBI level, then enabling the
|
||||
randomizer is recommended by default by adding a new specific property
|
||||
in children nodes.
|
||||
|
||||
Required NAND chip properties in children mode:
|
||||
- randomizer enable: should be "mxic,enable-randomizer-otp"
|
||||
|
||||
Example:
|
||||
|
||||
nand: nand-controller@unit-address {
|
||||
|
||||
nand@0 {
|
||||
reg = <0>;
|
||||
mxic,enable-randomizer-otp;
|
||||
};
|
||||
};
|
@ -1945,7 +1945,7 @@ F: Documentation/devicetree/bindings/i2c/i2c-lpc2k.txt
|
||||
F: arch/arm/boot/dts/lpc43*
|
||||
F: drivers/i2c/busses/i2c-lpc2k.c
|
||||
F: drivers/memory/pl172.c
|
||||
F: drivers/mtd/spi-nor/nxp-spifi.c
|
||||
F: drivers/mtd/spi-nor/controllers/nxp-spifi.c
|
||||
F: drivers/rtc/rtc-lpc24xx.c
|
||||
N: lpc18xx
|
||||
|
||||
@ -7851,6 +7851,10 @@ F: Documentation/ABI/testing/debugfs-hyperv
|
||||
|
||||
HYPERBUS SUPPORT
|
||||
M: Vignesh Raghavendra <vigneshr@ti.com>
|
||||
L: linux-mtd@lists.infradead.org
|
||||
Q: http://patchwork.ozlabs.org/project/linux-mtd/list/
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git cfi/next
|
||||
C: irc://irc.oftc.net/mtd
|
||||
S: Supported
|
||||
F: drivers/mtd/hyperbus/
|
||||
F: include/linux/mtd/hyperbus.h
|
||||
@ -11552,6 +11556,7 @@ L: linux-mtd@lists.infradead.org
|
||||
W: http://www.linux-mtd.infradead.org/
|
||||
Q: http://patchwork.ozlabs.org/project/linux-mtd/list/
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git nand/next
|
||||
C: irc://irc.oftc.net/mtd
|
||||
S: Maintained
|
||||
F: drivers/mtd/nand/
|
||||
F: include/linux/mtd/*nand*.h
|
||||
@ -15835,6 +15840,7 @@ L: linux-mtd@lists.infradead.org
|
||||
W: http://www.linux-mtd.infradead.org/
|
||||
Q: http://patchwork.ozlabs.org/project/linux-mtd/list/
|
||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux.git spi-nor/next
|
||||
C: irc://irc.oftc.net/mtd
|
||||
S: Maintained
|
||||
F: drivers/mtd/spi-nor/
|
||||
F: include/linux/mtd/spi-nor.h
|
||||
|
@ -17,6 +17,8 @@
|
||||
#include <linux/input.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/mtd/nand-gpio.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/regulator/fixed.h>
|
||||
@ -294,9 +296,42 @@ struct modem_private_data {
|
||||
|
||||
static struct modem_private_data modem_priv;
|
||||
|
||||
/*
|
||||
* Define partitions for flash device
|
||||
*/
|
||||
|
||||
static struct mtd_partition partition_info[] = {
|
||||
{ .name = "Kernel",
|
||||
.offset = 0,
|
||||
.size = 3 * SZ_1M + SZ_512K },
|
||||
{ .name = "u-boot",
|
||||
.offset = 3 * SZ_1M + SZ_512K,
|
||||
.size = SZ_256K },
|
||||
{ .name = "u-boot params",
|
||||
.offset = 3 * SZ_1M + SZ_512K + SZ_256K,
|
||||
.size = SZ_256K },
|
||||
{ .name = "Amstrad LDR",
|
||||
.offset = 4 * SZ_1M,
|
||||
.size = SZ_256K },
|
||||
{ .name = "File system",
|
||||
.offset = 4 * SZ_1M + 1 * SZ_256K,
|
||||
.size = 27 * SZ_1M },
|
||||
{ .name = "PBL reserved",
|
||||
.offset = 32 * SZ_1M - 3 * SZ_256K,
|
||||
.size = 3 * SZ_256K },
|
||||
};
|
||||
|
||||
static struct gpio_nand_platdata nand_platdata = {
|
||||
.parts = partition_info,
|
||||
.num_parts = ARRAY_SIZE(partition_info),
|
||||
};
|
||||
|
||||
static struct platform_device ams_delta_nand_device = {
|
||||
.name = "ams-delta-nand",
|
||||
.id = -1,
|
||||
.dev = {
|
||||
.platform_data = &nand_platdata,
|
||||
},
|
||||
};
|
||||
|
||||
#define OMAP_GPIO_LABEL "gpio-0-15"
|
||||
@ -306,10 +341,14 @@ static struct gpiod_lookup_table ams_delta_nand_gpio_table = {
|
||||
.table = {
|
||||
GPIO_LOOKUP(OMAP_GPIO_LABEL, AMS_DELTA_GPIO_PIN_NAND_RB, "rdy",
|
||||
0),
|
||||
GPIO_LOOKUP(LATCH2_LABEL, LATCH2_PIN_NAND_NCE, "nce", 0),
|
||||
GPIO_LOOKUP(LATCH2_LABEL, LATCH2_PIN_NAND_NRE, "nre", 0),
|
||||
GPIO_LOOKUP(LATCH2_LABEL, LATCH2_PIN_NAND_NWP, "nwp", 0),
|
||||
GPIO_LOOKUP(LATCH2_LABEL, LATCH2_PIN_NAND_NWE, "nwe", 0),
|
||||
GPIO_LOOKUP(LATCH2_LABEL, LATCH2_PIN_NAND_NCE, "nce",
|
||||
GPIO_ACTIVE_LOW),
|
||||
GPIO_LOOKUP(LATCH2_LABEL, LATCH2_PIN_NAND_NRE, "nre",
|
||||
GPIO_ACTIVE_LOW),
|
||||
GPIO_LOOKUP(LATCH2_LABEL, LATCH2_PIN_NAND_NWP, "nwp",
|
||||
GPIO_ACTIVE_LOW),
|
||||
GPIO_LOOKUP(LATCH2_LABEL, LATCH2_PIN_NAND_NWE, "nwe",
|
||||
GPIO_ACTIVE_LOW),
|
||||
GPIO_LOOKUP(LATCH2_LABEL, LATCH2_PIN_NAND_ALE, "ale", 0),
|
||||
GPIO_LOOKUP(LATCH2_LABEL, LATCH2_PIN_NAND_CLE, "cle", 0),
|
||||
GPIO_LOOKUP_IDX(OMAP_MPUIO_LABEL, 0, "data", 0, 0),
|
||||
|
@ -403,8 +403,8 @@ nand: nand@41b800 {
|
||||
compatible = "brcm,brcmnand-v5.0", "brcm,brcmnand";
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg-names = "nand";
|
||||
reg = <0x41b800 0x400>;
|
||||
reg-names = "nand", "flash-edu";
|
||||
reg = <0x41b800 0x400>, <0x41bc00 0x24>;
|
||||
interrupt-parent = <&hif_l2_intc>;
|
||||
interrupts = <24>;
|
||||
status = "disabled";
|
||||
|
@ -834,7 +834,7 @@ static int chip_ready (struct map_info *map, struct flchip *chip, unsigned long
|
||||
/* Someone else might have been playing with it. */
|
||||
return -EAGAIN;
|
||||
}
|
||||
/* Fall through */
|
||||
fallthrough;
|
||||
case FL_READY:
|
||||
case FL_CFI_QUERY:
|
||||
case FL_JEDEC_QUERY:
|
||||
@ -907,7 +907,7 @@ static int chip_ready (struct map_info *map, struct flchip *chip, unsigned long
|
||||
/* Only if there's no operation suspended... */
|
||||
if (mode == FL_READY && chip->oldstate == FL_READY)
|
||||
return 0;
|
||||
/* Fall through */
|
||||
fallthrough;
|
||||
default:
|
||||
sleep:
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
|
@ -966,8 +966,7 @@ static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr
|
||||
/* Only if there's no operation suspended... */
|
||||
if (mode == FL_READY && chip->oldstate == FL_READY)
|
||||
return 0;
|
||||
/* fall through */
|
||||
|
||||
fallthrough;
|
||||
default:
|
||||
sleep:
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
@ -2935,7 +2934,7 @@ static void cfi_amdstd_sync (struct mtd_info *mtd)
|
||||
* as the whole point is that nobody can do anything
|
||||
* with the chip now anyway.
|
||||
*/
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case FL_SYNCING:
|
||||
mutex_unlock(&chip->mutex);
|
||||
break;
|
||||
|
@ -324,8 +324,7 @@ static inline int do_read_onechip(struct map_info *map, struct flchip *chip, lof
|
||||
case FL_JEDEC_QUERY:
|
||||
map_write(map, CMD(0x70), cmd_addr);
|
||||
chip->state = FL_STATUS;
|
||||
/* Fall through */
|
||||
|
||||
fallthrough;
|
||||
case FL_STATUS:
|
||||
status = map_read(map, cmd_addr);
|
||||
if (map_word_andequal(map, status, status_OK, status_OK)) {
|
||||
@ -462,8 +461,7 @@ static int do_write_buffer(struct map_info *map, struct flchip *chip,
|
||||
#ifdef DEBUG_CFI_FEATURES
|
||||
printk("%s: 1 status[%x]\n", __func__, map_read(map, cmd_adr));
|
||||
#endif
|
||||
/* Fall through */
|
||||
|
||||
fallthrough;
|
||||
case FL_STATUS:
|
||||
status = map_read(map, cmd_adr);
|
||||
if (map_word_andequal(map, status, status_OK, status_OK))
|
||||
@ -756,8 +754,7 @@ static inline int do_erase_oneblock(struct map_info *map, struct flchip *chip, u
|
||||
case FL_READY:
|
||||
map_write(map, CMD(0x70), adr);
|
||||
chip->state = FL_STATUS;
|
||||
/* Fall through */
|
||||
|
||||
fallthrough;
|
||||
case FL_STATUS:
|
||||
status = map_read(map, adr);
|
||||
if (map_word_andequal(map, status, status_OK, status_OK))
|
||||
@ -998,7 +995,7 @@ static void cfi_staa_sync (struct mtd_info *mtd)
|
||||
* as the whole point is that nobody can do anything
|
||||
* with the chip now anyway.
|
||||
*/
|
||||
/* Fall through */
|
||||
fallthrough;
|
||||
case FL_SYNCING:
|
||||
mutex_unlock(&chip->mutex);
|
||||
break;
|
||||
@ -1054,8 +1051,7 @@ static inline int do_lock_oneblock(struct map_info *map, struct flchip *chip, un
|
||||
case FL_READY:
|
||||
map_write(map, CMD(0x70), adr);
|
||||
chip->state = FL_STATUS;
|
||||
/* Fall through */
|
||||
|
||||
fallthrough;
|
||||
case FL_STATUS:
|
||||
status = map_read(map, adr);
|
||||
if (map_word_andequal(map, status, status_OK, status_OK))
|
||||
@ -1201,8 +1197,7 @@ static inline int do_unlock_oneblock(struct map_info *map, struct flchip *chip,
|
||||
case FL_READY:
|
||||
map_write(map, CMD(0x70), adr);
|
||||
chip->state = FL_STATUS;
|
||||
/* Fall through */
|
||||
|
||||
fallthrough;
|
||||
case FL_STATUS:
|
||||
status = map_read(map, adr);
|
||||
if (map_word_andequal(map, status, status_OK, status_OK))
|
||||
|
@ -109,13 +109,13 @@ map_word cfi_build_cmd(u_long cmd, struct map_info *map, struct cfi_private *cfi
|
||||
case 8:
|
||||
onecmd |= (onecmd << (chip_mode * 32));
|
||||
#endif
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 4:
|
||||
onecmd |= (onecmd << (chip_mode * 16));
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 2:
|
||||
onecmd |= (onecmd << (chip_mode * 8));
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 1:
|
||||
;
|
||||
}
|
||||
@ -165,13 +165,13 @@ unsigned long cfi_merge_status(map_word val, struct map_info *map,
|
||||
case 8:
|
||||
res |= (onestat >> (chip_mode * 32));
|
||||
#endif
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 4:
|
||||
res |= (onestat >> (chip_mode * 16));
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 2:
|
||||
res |= (onestat >> (chip_mode * 8));
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 1:
|
||||
;
|
||||
}
|
||||
|
@ -329,10 +329,10 @@ static int ustrtoul(const char *cp, char **endp, unsigned int base)
|
||||
switch (**endp) {
|
||||
case 'G' :
|
||||
result *= 1024;
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 'M':
|
||||
result *= 1024;
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 'K':
|
||||
case 'k':
|
||||
result *= 1024;
|
||||
|
@ -148,10 +148,10 @@ static int parse_num64(uint64_t *num64, char *token)
|
||||
switch (token[len - 2]) {
|
||||
case 'G':
|
||||
shift += 10;
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 'M':
|
||||
shift += 10;
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 'k':
|
||||
shift += 10;
|
||||
token[len - 2] = 0;
|
||||
@ -243,22 +243,25 @@ static int phram_setup(const char *val)
|
||||
|
||||
ret = parse_num64(&start, token[1]);
|
||||
if (ret) {
|
||||
kfree(name);
|
||||
parse_err("illegal start address\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = parse_num64(&len, token[2]);
|
||||
if (ret) {
|
||||
kfree(name);
|
||||
parse_err("illegal device length\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
ret = register_device(name, start, len);
|
||||
if (!ret)
|
||||
pr_info("%s device: %#llx at %#llx\n", name, len, start);
|
||||
else
|
||||
kfree(name);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
pr_info("%s device: %#llx at %#llx\n", name, len, start);
|
||||
return 0;
|
||||
|
||||
error:
|
||||
kfree(name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mux/consumer.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/types.h>
|
||||
@ -57,8 +58,10 @@ static const struct hyperbus_ops am654_hbmc_ops = {
|
||||
|
||||
static int am654_hbmc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct am654_hbmc_priv *priv;
|
||||
struct resource res;
|
||||
int ret;
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
@ -67,6 +70,10 @@ static int am654_hbmc_probe(struct platform_device *pdev)
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
ret = of_address_to_resource(np, 0, &res);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (of_property_read_bool(dev->of_node, "mux-controls")) {
|
||||
struct mux_control *control = devm_mux_control_get(dev, NULL);
|
||||
|
||||
@ -88,6 +95,11 @@ static int am654_hbmc_probe(struct platform_device *pdev)
|
||||
goto disable_pm;
|
||||
}
|
||||
|
||||
priv->hbdev.map.size = resource_size(&res);
|
||||
priv->hbdev.map.virt = devm_ioremap_resource(dev, &res);
|
||||
if (IS_ERR(priv->hbdev.map.virt))
|
||||
return PTR_ERR(priv->hbdev.map.virt);
|
||||
|
||||
priv->ctlr.dev = dev;
|
||||
priv->ctlr.ops = &am654_hbmc_ops;
|
||||
priv->hbdev.ctlr = &priv->ctlr;
|
||||
|
@ -10,7 +10,6 @@
|
||||
#include <linux/mtd/map.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
static struct hyperbus_device *map_to_hbdev(struct map_info *map)
|
||||
@ -62,7 +61,6 @@ int hyperbus_register_device(struct hyperbus_device *hbdev)
|
||||
struct hyperbus_ctlr *ctlr;
|
||||
struct device_node *np;
|
||||
struct map_info *map;
|
||||
struct resource res;
|
||||
struct device *dev;
|
||||
int ret;
|
||||
|
||||
@ -73,22 +71,15 @@ int hyperbus_register_device(struct hyperbus_device *hbdev)
|
||||
|
||||
np = hbdev->np;
|
||||
ctlr = hbdev->ctlr;
|
||||
if (!of_device_is_compatible(np, "cypress,hyperflash"))
|
||||
if (!of_device_is_compatible(np, "cypress,hyperflash")) {
|
||||
dev_err(ctlr->dev, "\"cypress,hyperflash\" compatible missing\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
hbdev->memtype = HYPERFLASH;
|
||||
|
||||
ret = of_address_to_resource(np, 0, &res);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev = ctlr->dev;
|
||||
map = &hbdev->map;
|
||||
map->size = resource_size(&res);
|
||||
map->virt = devm_ioremap_resource(dev, &res);
|
||||
if (IS_ERR(map->virt))
|
||||
return PTR_ERR(map->virt);
|
||||
|
||||
map->name = dev_name(dev);
|
||||
map->bankwidth = 2;
|
||||
map->device_node = np;
|
||||
|
@ -130,7 +130,7 @@ static int find_boot_record(struct INFTLrecord *inftl)
|
||||
" NoOfBootImageBlocks = %d\n"
|
||||
" NoOfBinaryPartitions = %d\n"
|
||||
" NoOfBDTLPartitions = %d\n"
|
||||
" BlockMultiplerBits = %d\n"
|
||||
" BlockMultiplierBits = %d\n"
|
||||
" FormatFlgs = %d\n"
|
||||
" OsakVersion = 0x%x\n"
|
||||
" PercentUsed = %d\n",
|
||||
|
@ -68,7 +68,6 @@ struct mtd_info *lpddr_cmdset(struct map_info *map)
|
||||
shared = kmalloc_array(lpddr->numchips, sizeof(struct flchip_shared),
|
||||
GFP_KERNEL);
|
||||
if (!shared) {
|
||||
kfree(lpddr);
|
||||
kfree(mtd);
|
||||
return NULL;
|
||||
}
|
||||
@ -305,8 +304,7 @@ static int chip_ready(struct map_info *map, struct flchip *chip, int mode)
|
||||
/* Only if there's no operation suspended... */
|
||||
if (mode == FL_READY && chip->oldstate == FL_READY)
|
||||
return 0;
|
||||
/* fall through */
|
||||
|
||||
fallthrough;
|
||||
default:
|
||||
sleep:
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
|
@ -34,7 +34,7 @@ struct sa_subdev_info {
|
||||
struct sa_info {
|
||||
struct mtd_info *mtd;
|
||||
int num_subdev;
|
||||
struct sa_subdev_info subdev[0];
|
||||
struct sa_subdev_info subdev[];
|
||||
};
|
||||
|
||||
static DEFINE_SPINLOCK(sa1100_vpp_lock);
|
||||
@ -81,8 +81,7 @@ static int sa1100_probe_subdev(struct sa_subdev_info *subdev, struct resource *r
|
||||
default:
|
||||
printk(KERN_WARNING "SA1100 flash: unknown base address "
|
||||
"0x%08lx, assuming CS0\n", phys);
|
||||
/* Fall through */
|
||||
|
||||
fallthrough;
|
||||
case SA1100_CS0_PHYS:
|
||||
subdev->map.bankwidth = (MSC0 & MSC_RBW) ? 2 : 4;
|
||||
break;
|
||||
|
@ -294,12 +294,13 @@ static void mtdblock_release(struct mtd_blktrans_dev *mbd)
|
||||
static int mtdblock_flush(struct mtd_blktrans_dev *dev)
|
||||
{
|
||||
struct mtdblk_dev *mtdblk = container_of(dev, struct mtdblk_dev, mbd);
|
||||
int ret;
|
||||
|
||||
mutex_lock(&mtdblk->cache_mutex);
|
||||
write_cached_data(mtdblk);
|
||||
ret = write_cached_data(mtdblk);
|
||||
mutex_unlock(&mtdblk->cache_mutex);
|
||||
mtd_sync(dev->mtd);
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
|
||||
|
@ -349,6 +349,7 @@ static int mtdchar_writeoob(struct file *file, struct mtd_info *mtd,
|
||||
uint64_t start, uint32_t length, void __user *ptr,
|
||||
uint32_t __user *retp)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
struct mtd_file_info *mfi = file->private_data;
|
||||
struct mtd_oob_ops ops = {};
|
||||
uint32_t retlen;
|
||||
@ -360,7 +361,7 @@ static int mtdchar_writeoob(struct file *file, struct mtd_info *mtd,
|
||||
if (length > 4096)
|
||||
return -EINVAL;
|
||||
|
||||
if (!mtd->_write_oob)
|
||||
if (!master->_write_oob)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ops.ooblen = length;
|
||||
@ -586,6 +587,7 @@ static int mtdchar_blkpg_ioctl(struct mtd_info *mtd,
|
||||
static int mtdchar_write_ioctl(struct mtd_info *mtd,
|
||||
struct mtd_write_req __user *argp)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
struct mtd_write_req req;
|
||||
struct mtd_oob_ops ops = {};
|
||||
const void __user *usr_data, *usr_oob;
|
||||
@ -597,9 +599,8 @@ static int mtdchar_write_ioctl(struct mtd_info *mtd,
|
||||
usr_data = (const void __user *)(uintptr_t)req.usr_data;
|
||||
usr_oob = (const void __user *)(uintptr_t)req.usr_oob;
|
||||
|
||||
if (!mtd->_write_oob)
|
||||
if (!master->_write_oob)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ops.mode = req.mode;
|
||||
ops.len = (size_t)req.len;
|
||||
ops.ooblen = (size_t)req.ooblen;
|
||||
@ -635,6 +636,7 @@ static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg)
|
||||
{
|
||||
struct mtd_file_info *mfi = file->private_data;
|
||||
struct mtd_info *mtd = mfi->mtd;
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
void __user *argp = (void __user *)arg;
|
||||
int ret = 0;
|
||||
struct mtd_info_user info;
|
||||
@ -824,7 +826,7 @@ static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg)
|
||||
{
|
||||
struct nand_oobinfo oi;
|
||||
|
||||
if (!mtd->ooblayout)
|
||||
if (!master->ooblayout)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
ret = get_oobinfo(mtd, &oi);
|
||||
@ -918,7 +920,7 @@ static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg)
|
||||
{
|
||||
struct nand_ecclayout_user *usrlay;
|
||||
|
||||
if (!mtd->ooblayout)
|
||||
if (!master->ooblayout)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
usrlay = kmalloc(sizeof(*usrlay), GFP_KERNEL);
|
||||
|
@ -456,13 +456,14 @@ static int mtd_reboot_notifier(struct notifier_block *n, unsigned long state,
|
||||
int mtd_wunit_to_pairing_info(struct mtd_info *mtd, int wunit,
|
||||
struct mtd_pairing_info *info)
|
||||
{
|
||||
int npairs = mtd_wunit_per_eb(mtd) / mtd_pairing_groups(mtd);
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
int npairs = mtd_wunit_per_eb(master) / mtd_pairing_groups(master);
|
||||
|
||||
if (wunit < 0 || wunit >= npairs)
|
||||
return -EINVAL;
|
||||
|
||||
if (mtd->pairing && mtd->pairing->get_info)
|
||||
return mtd->pairing->get_info(mtd, wunit, info);
|
||||
if (master->pairing && master->pairing->get_info)
|
||||
return master->pairing->get_info(master, wunit, info);
|
||||
|
||||
info->group = 0;
|
||||
info->pair = wunit;
|
||||
@ -498,15 +499,16 @@ EXPORT_SYMBOL_GPL(mtd_wunit_to_pairing_info);
|
||||
int mtd_pairing_info_to_wunit(struct mtd_info *mtd,
|
||||
const struct mtd_pairing_info *info)
|
||||
{
|
||||
int ngroups = mtd_pairing_groups(mtd);
|
||||
int npairs = mtd_wunit_per_eb(mtd) / ngroups;
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
int ngroups = mtd_pairing_groups(master);
|
||||
int npairs = mtd_wunit_per_eb(master) / ngroups;
|
||||
|
||||
if (!info || info->pair < 0 || info->pair >= npairs ||
|
||||
info->group < 0 || info->group >= ngroups)
|
||||
return -EINVAL;
|
||||
|
||||
if (mtd->pairing && mtd->pairing->get_wunit)
|
||||
return mtd->pairing->get_wunit(mtd, info);
|
||||
if (master->pairing && master->pairing->get_wunit)
|
||||
return mtd->pairing->get_wunit(master, info);
|
||||
|
||||
return info->pair;
|
||||
}
|
||||
@ -524,10 +526,12 @@ EXPORT_SYMBOL_GPL(mtd_pairing_info_to_wunit);
|
||||
*/
|
||||
int mtd_pairing_groups(struct mtd_info *mtd)
|
||||
{
|
||||
if (!mtd->pairing || !mtd->pairing->ngroups)
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
if (!master->pairing || !master->pairing->ngroups)
|
||||
return 1;
|
||||
|
||||
return mtd->pairing->ngroups;
|
||||
return master->pairing->ngroups;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_pairing_groups);
|
||||
|
||||
@ -587,6 +591,7 @@ static int mtd_nvmem_add(struct mtd_info *mtd)
|
||||
|
||||
int add_mtd_device(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
struct mtd_notifier *not;
|
||||
int i, error;
|
||||
|
||||
@ -608,7 +613,7 @@ int add_mtd_device(struct mtd_info *mtd)
|
||||
(mtd->_read && mtd->_read_oob)))
|
||||
return -EINVAL;
|
||||
|
||||
if (WARN_ON((!mtd->erasesize || !mtd->_erase) &&
|
||||
if (WARN_ON((!mtd->erasesize || !master->_erase) &&
|
||||
!(mtd->flags & MTD_NO_ERASE)))
|
||||
return -EINVAL;
|
||||
|
||||
@ -765,7 +770,8 @@ static void mtd_set_dev_defaults(struct mtd_info *mtd)
|
||||
pr_debug("mtd device won't show a device symlink in sysfs\n");
|
||||
}
|
||||
|
||||
mtd->orig_flags = mtd->flags;
|
||||
INIT_LIST_HEAD(&mtd->partitions);
|
||||
mutex_init(&mtd->master.partitions_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -971,20 +977,26 @@ EXPORT_SYMBOL_GPL(get_mtd_device);
|
||||
|
||||
int __get_mtd_device(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
int err;
|
||||
|
||||
if (!try_module_get(mtd->owner))
|
||||
if (!try_module_get(master->owner))
|
||||
return -ENODEV;
|
||||
|
||||
if (mtd->_get_device) {
|
||||
err = mtd->_get_device(mtd);
|
||||
if (master->_get_device) {
|
||||
err = master->_get_device(mtd);
|
||||
|
||||
if (err) {
|
||||
module_put(mtd->owner);
|
||||
module_put(master->owner);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
mtd->usecount++;
|
||||
|
||||
while (mtd->parent) {
|
||||
mtd->usecount++;
|
||||
mtd = mtd->parent;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__get_mtd_device);
|
||||
@ -1038,13 +1050,18 @@ EXPORT_SYMBOL_GPL(put_mtd_device);
|
||||
|
||||
void __put_mtd_device(struct mtd_info *mtd)
|
||||
{
|
||||
--mtd->usecount;
|
||||
BUG_ON(mtd->usecount < 0);
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
if (mtd->_put_device)
|
||||
mtd->_put_device(mtd);
|
||||
while (mtd->parent) {
|
||||
--mtd->usecount;
|
||||
BUG_ON(mtd->usecount < 0);
|
||||
mtd = mtd->parent;
|
||||
}
|
||||
|
||||
module_put(mtd->owner);
|
||||
if (master->_put_device)
|
||||
master->_put_device(master);
|
||||
|
||||
module_put(master->owner);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__put_mtd_device);
|
||||
|
||||
@ -1055,9 +1072,13 @@ EXPORT_SYMBOL_GPL(__put_mtd_device);
|
||||
*/
|
||||
int mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
u64 mst_ofs = mtd_get_master_ofs(mtd, 0);
|
||||
int ret;
|
||||
|
||||
instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
|
||||
|
||||
if (!mtd->erasesize || !mtd->_erase)
|
||||
if (!mtd->erasesize || !master->_erase)
|
||||
return -ENOTSUPP;
|
||||
|
||||
if (instr->addr >= mtd->size || instr->len > mtd->size - instr->addr)
|
||||
@ -1069,7 +1090,14 @@ int mtd_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
return 0;
|
||||
|
||||
ledtrig_mtd_activity();
|
||||
return mtd->_erase(mtd, instr);
|
||||
|
||||
instr->addr += mst_ofs;
|
||||
ret = master->_erase(master, instr);
|
||||
if (instr->fail_addr != MTD_FAIL_ADDR_UNKNOWN)
|
||||
instr->fail_addr -= mst_ofs;
|
||||
|
||||
instr->addr -= mst_ofs;
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_erase);
|
||||
|
||||
@ -1079,30 +1107,36 @@ EXPORT_SYMBOL_GPL(mtd_erase);
|
||||
int mtd_point(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
|
||||
void **virt, resource_size_t *phys)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
*retlen = 0;
|
||||
*virt = NULL;
|
||||
if (phys)
|
||||
*phys = 0;
|
||||
if (!mtd->_point)
|
||||
if (!master->_point)
|
||||
return -EOPNOTSUPP;
|
||||
if (from < 0 || from >= mtd->size || len > mtd->size - from)
|
||||
return -EINVAL;
|
||||
if (!len)
|
||||
return 0;
|
||||
return mtd->_point(mtd, from, len, retlen, virt, phys);
|
||||
|
||||
from = mtd_get_master_ofs(mtd, from);
|
||||
return master->_point(master, from, len, retlen, virt, phys);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_point);
|
||||
|
||||
/* We probably shouldn't allow XIP if the unpoint isn't a NULL */
|
||||
int mtd_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
|
||||
{
|
||||
if (!mtd->_unpoint)
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
if (!master->_unpoint)
|
||||
return -EOPNOTSUPP;
|
||||
if (from < 0 || from >= mtd->size || len > mtd->size - from)
|
||||
return -EINVAL;
|
||||
if (!len)
|
||||
return 0;
|
||||
return mtd->_unpoint(mtd, from, len);
|
||||
return master->_unpoint(master, mtd_get_master_ofs(mtd, from), len);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_unpoint);
|
||||
|
||||
@ -1129,6 +1163,25 @@ unsigned long mtd_get_unmapped_area(struct mtd_info *mtd, unsigned long len,
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_get_unmapped_area);
|
||||
|
||||
static void mtd_update_ecc_stats(struct mtd_info *mtd, struct mtd_info *master,
|
||||
const struct mtd_ecc_stats *old_stats)
|
||||
{
|
||||
struct mtd_ecc_stats diff;
|
||||
|
||||
if (master == mtd)
|
||||
return;
|
||||
|
||||
diff = master->ecc_stats;
|
||||
diff.failed -= old_stats->failed;
|
||||
diff.corrected -= old_stats->corrected;
|
||||
|
||||
while (mtd->parent) {
|
||||
mtd->ecc_stats.failed += diff.failed;
|
||||
mtd->ecc_stats.corrected += diff.corrected;
|
||||
mtd = mtd->parent;
|
||||
}
|
||||
}
|
||||
|
||||
int mtd_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen,
|
||||
u_char *buf)
|
||||
{
|
||||
@ -1171,8 +1224,10 @@ EXPORT_SYMBOL_GPL(mtd_write);
|
||||
int mtd_panic_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
|
||||
const u_char *buf)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
*retlen = 0;
|
||||
if (!mtd->_panic_write)
|
||||
if (!master->_panic_write)
|
||||
return -EOPNOTSUPP;
|
||||
if (to < 0 || to >= mtd->size || len > mtd->size - to)
|
||||
return -EINVAL;
|
||||
@ -1183,7 +1238,8 @@ int mtd_panic_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen,
|
||||
if (!mtd->oops_panic_write)
|
||||
mtd->oops_panic_write = true;
|
||||
|
||||
return mtd->_panic_write(mtd, to, len, retlen, buf);
|
||||
return master->_panic_write(master, mtd_get_master_ofs(mtd, to), len,
|
||||
retlen, buf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_panic_write);
|
||||
|
||||
@ -1222,7 +1278,10 @@ static int mtd_check_oob_ops(struct mtd_info *mtd, loff_t offs,
|
||||
|
||||
int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
struct mtd_ecc_stats old_stats = master->ecc_stats;
|
||||
int ret_code;
|
||||
|
||||
ops->retlen = ops->oobretlen = 0;
|
||||
|
||||
ret_code = mtd_check_oob_ops(mtd, from, ops);
|
||||
@ -1232,14 +1291,17 @@ int mtd_read_oob(struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops)
|
||||
ledtrig_mtd_activity();
|
||||
|
||||
/* Check the validity of a potential fallback on mtd->_read */
|
||||
if (!mtd->_read_oob && (!mtd->_read || ops->oobbuf))
|
||||
if (!master->_read_oob && (!master->_read || ops->oobbuf))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (mtd->_read_oob)
|
||||
ret_code = mtd->_read_oob(mtd, from, ops);
|
||||
from = mtd_get_master_ofs(mtd, from);
|
||||
if (master->_read_oob)
|
||||
ret_code = master->_read_oob(master, from, ops);
|
||||
else
|
||||
ret_code = mtd->_read(mtd, from, ops->len, &ops->retlen,
|
||||
ops->datbuf);
|
||||
ret_code = master->_read(master, from, ops->len, &ops->retlen,
|
||||
ops->datbuf);
|
||||
|
||||
mtd_update_ecc_stats(mtd, master, &old_stats);
|
||||
|
||||
/*
|
||||
* In cases where ops->datbuf != NULL, mtd->_read_oob() has semantics
|
||||
@ -1258,6 +1320,7 @@ EXPORT_SYMBOL_GPL(mtd_read_oob);
|
||||
int mtd_write_oob(struct mtd_info *mtd, loff_t to,
|
||||
struct mtd_oob_ops *ops)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
int ret;
|
||||
|
||||
ops->retlen = ops->oobretlen = 0;
|
||||
@ -1272,14 +1335,16 @@ int mtd_write_oob(struct mtd_info *mtd, loff_t to,
|
||||
ledtrig_mtd_activity();
|
||||
|
||||
/* Check the validity of a potential fallback on mtd->_write */
|
||||
if (!mtd->_write_oob && (!mtd->_write || ops->oobbuf))
|
||||
if (!master->_write_oob && (!master->_write || ops->oobbuf))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (mtd->_write_oob)
|
||||
return mtd->_write_oob(mtd, to, ops);
|
||||
to = mtd_get_master_ofs(mtd, to);
|
||||
|
||||
if (master->_write_oob)
|
||||
return master->_write_oob(master, to, ops);
|
||||
else
|
||||
return mtd->_write(mtd, to, ops->len, &ops->retlen,
|
||||
ops->datbuf);
|
||||
return master->_write(master, to, ops->len, &ops->retlen,
|
||||
ops->datbuf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_write_oob);
|
||||
|
||||
@ -1302,15 +1367,17 @@ EXPORT_SYMBOL_GPL(mtd_write_oob);
|
||||
int mtd_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *oobecc)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
memset(oobecc, 0, sizeof(*oobecc));
|
||||
|
||||
if (!mtd || section < 0)
|
||||
if (!master || section < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (!mtd->ooblayout || !mtd->ooblayout->ecc)
|
||||
if (!master->ooblayout || !master->ooblayout->ecc)
|
||||
return -ENOTSUPP;
|
||||
|
||||
return mtd->ooblayout->ecc(mtd, section, oobecc);
|
||||
return master->ooblayout->ecc(master, section, oobecc);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_ooblayout_ecc);
|
||||
|
||||
@ -1334,15 +1401,17 @@ EXPORT_SYMBOL_GPL(mtd_ooblayout_ecc);
|
||||
int mtd_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *oobfree)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
memset(oobfree, 0, sizeof(*oobfree));
|
||||
|
||||
if (!mtd || section < 0)
|
||||
if (!master || section < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (!mtd->ooblayout || !mtd->ooblayout->free)
|
||||
if (!master->ooblayout || !master->ooblayout->free)
|
||||
return -ENOTSUPP;
|
||||
|
||||
return mtd->ooblayout->free(mtd, section, oobfree);
|
||||
return master->ooblayout->free(master, section, oobfree);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_ooblayout_free);
|
||||
|
||||
@ -1651,60 +1720,69 @@ EXPORT_SYMBOL_GPL(mtd_ooblayout_count_eccbytes);
|
||||
int mtd_get_fact_prot_info(struct mtd_info *mtd, size_t len, size_t *retlen,
|
||||
struct otp_info *buf)
|
||||
{
|
||||
if (!mtd->_get_fact_prot_info)
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
if (!master->_get_fact_prot_info)
|
||||
return -EOPNOTSUPP;
|
||||
if (!len)
|
||||
return 0;
|
||||
return mtd->_get_fact_prot_info(mtd, len, retlen, buf);
|
||||
return master->_get_fact_prot_info(master, len, retlen, buf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_get_fact_prot_info);
|
||||
|
||||
int mtd_read_fact_prot_reg(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
*retlen = 0;
|
||||
if (!mtd->_read_fact_prot_reg)
|
||||
if (!master->_read_fact_prot_reg)
|
||||
return -EOPNOTSUPP;
|
||||
if (!len)
|
||||
return 0;
|
||||
return mtd->_read_fact_prot_reg(mtd, from, len, retlen, buf);
|
||||
return master->_read_fact_prot_reg(master, from, len, retlen, buf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_read_fact_prot_reg);
|
||||
|
||||
int mtd_get_user_prot_info(struct mtd_info *mtd, size_t len, size_t *retlen,
|
||||
struct otp_info *buf)
|
||||
{
|
||||
if (!mtd->_get_user_prot_info)
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
if (!master->_get_user_prot_info)
|
||||
return -EOPNOTSUPP;
|
||||
if (!len)
|
||||
return 0;
|
||||
return mtd->_get_user_prot_info(mtd, len, retlen, buf);
|
||||
return master->_get_user_prot_info(master, len, retlen, buf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_get_user_prot_info);
|
||||
|
||||
int mtd_read_user_prot_reg(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
*retlen = 0;
|
||||
if (!mtd->_read_user_prot_reg)
|
||||
if (!master->_read_user_prot_reg)
|
||||
return -EOPNOTSUPP;
|
||||
if (!len)
|
||||
return 0;
|
||||
return mtd->_read_user_prot_reg(mtd, from, len, retlen, buf);
|
||||
return master->_read_user_prot_reg(master, from, len, retlen, buf);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_read_user_prot_reg);
|
||||
|
||||
int mtd_write_user_prot_reg(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
int ret;
|
||||
|
||||
*retlen = 0;
|
||||
if (!mtd->_write_user_prot_reg)
|
||||
if (!master->_write_user_prot_reg)
|
||||
return -EOPNOTSUPP;
|
||||
if (!len)
|
||||
return 0;
|
||||
ret = mtd->_write_user_prot_reg(mtd, to, len, retlen, buf);
|
||||
ret = master->_write_user_prot_reg(master, to, len, retlen, buf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@ -1718,80 +1796,105 @@ EXPORT_SYMBOL_GPL(mtd_write_user_prot_reg);
|
||||
|
||||
int mtd_lock_user_prot_reg(struct mtd_info *mtd, loff_t from, size_t len)
|
||||
{
|
||||
if (!mtd->_lock_user_prot_reg)
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
if (!master->_lock_user_prot_reg)
|
||||
return -EOPNOTSUPP;
|
||||
if (!len)
|
||||
return 0;
|
||||
return mtd->_lock_user_prot_reg(mtd, from, len);
|
||||
return master->_lock_user_prot_reg(master, from, len);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_lock_user_prot_reg);
|
||||
|
||||
/* Chip-supported device locking */
|
||||
int mtd_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
if (!mtd->_lock)
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
if (!master->_lock)
|
||||
return -EOPNOTSUPP;
|
||||
if (ofs < 0 || ofs >= mtd->size || len > mtd->size - ofs)
|
||||
return -EINVAL;
|
||||
if (!len)
|
||||
return 0;
|
||||
return mtd->_lock(mtd, ofs, len);
|
||||
return master->_lock(master, mtd_get_master_ofs(mtd, ofs), len);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_lock);
|
||||
|
||||
int mtd_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
if (!mtd->_unlock)
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
if (!master->_unlock)
|
||||
return -EOPNOTSUPP;
|
||||
if (ofs < 0 || ofs >= mtd->size || len > mtd->size - ofs)
|
||||
return -EINVAL;
|
||||
if (!len)
|
||||
return 0;
|
||||
return mtd->_unlock(mtd, ofs, len);
|
||||
return master->_unlock(master, mtd_get_master_ofs(mtd, ofs), len);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_unlock);
|
||||
|
||||
int mtd_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
if (!mtd->_is_locked)
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
if (!master->_is_locked)
|
||||
return -EOPNOTSUPP;
|
||||
if (ofs < 0 || ofs >= mtd->size || len > mtd->size - ofs)
|
||||
return -EINVAL;
|
||||
if (!len)
|
||||
return 0;
|
||||
return mtd->_is_locked(mtd, ofs, len);
|
||||
return master->_is_locked(master, mtd_get_master_ofs(mtd, ofs), len);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_is_locked);
|
||||
|
||||
int mtd_block_isreserved(struct mtd_info *mtd, loff_t ofs)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
if (ofs < 0 || ofs >= mtd->size)
|
||||
return -EINVAL;
|
||||
if (!mtd->_block_isreserved)
|
||||
if (!master->_block_isreserved)
|
||||
return 0;
|
||||
return mtd->_block_isreserved(mtd, ofs);
|
||||
return master->_block_isreserved(master, mtd_get_master_ofs(mtd, ofs));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_block_isreserved);
|
||||
|
||||
int mtd_block_isbad(struct mtd_info *mtd, loff_t ofs)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
if (ofs < 0 || ofs >= mtd->size)
|
||||
return -EINVAL;
|
||||
if (!mtd->_block_isbad)
|
||||
if (!master->_block_isbad)
|
||||
return 0;
|
||||
return mtd->_block_isbad(mtd, ofs);
|
||||
return master->_block_isbad(master, mtd_get_master_ofs(mtd, ofs));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_block_isbad);
|
||||
|
||||
int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs)
|
||||
{
|
||||
if (!mtd->_block_markbad)
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
int ret;
|
||||
|
||||
if (!master->_block_markbad)
|
||||
return -EOPNOTSUPP;
|
||||
if (ofs < 0 || ofs >= mtd->size)
|
||||
return -EINVAL;
|
||||
if (!(mtd->flags & MTD_WRITEABLE))
|
||||
return -EROFS;
|
||||
return mtd->_block_markbad(mtd, ofs);
|
||||
|
||||
ret = master->_block_markbad(master, mtd_get_master_ofs(mtd, ofs));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
while (mtd->parent) {
|
||||
mtd->ecc_stats.badblocks++;
|
||||
mtd = mtd->parent;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_block_markbad);
|
||||
|
||||
@ -1841,12 +1944,17 @@ static int default_mtd_writev(struct mtd_info *mtd, const struct kvec *vecs,
|
||||
int mtd_writev(struct mtd_info *mtd, const struct kvec *vecs,
|
||||
unsigned long count, loff_t to, size_t *retlen)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
*retlen = 0;
|
||||
if (!(mtd->flags & MTD_WRITEABLE))
|
||||
return -EROFS;
|
||||
if (!mtd->_writev)
|
||||
|
||||
if (!master->_writev)
|
||||
return default_mtd_writev(mtd, vecs, count, to, retlen);
|
||||
return mtd->_writev(mtd, vecs, count, to, retlen);
|
||||
|
||||
return master->_writev(master, vecs, count,
|
||||
mtd_get_master_ofs(mtd, to), retlen);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_writev);
|
||||
|
||||
|
@ -20,339 +20,52 @@
|
||||
|
||||
#include "mtdcore.h"
|
||||
|
||||
/* Our partition linked list */
|
||||
static LIST_HEAD(mtd_partitions);
|
||||
static DEFINE_MUTEX(mtd_partitions_mutex);
|
||||
|
||||
/**
|
||||
* struct mtd_part - our partition node structure
|
||||
*
|
||||
* @mtd: struct holding partition details
|
||||
* @parent: parent mtd - flash device or another partition
|
||||
* @offset: partition offset relative to the *flash device*
|
||||
*/
|
||||
struct mtd_part {
|
||||
struct mtd_info mtd;
|
||||
struct mtd_info *parent;
|
||||
uint64_t offset;
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
/*
|
||||
* Given a pointer to the MTD object in the mtd_part structure, we can retrieve
|
||||
* the pointer to that structure.
|
||||
*/
|
||||
static inline struct mtd_part *mtd_to_part(const struct mtd_info *mtd)
|
||||
{
|
||||
return container_of(mtd, struct mtd_part, mtd);
|
||||
}
|
||||
|
||||
static u64 part_absolute_offset(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
|
||||
if (!mtd_is_partition(mtd))
|
||||
return 0;
|
||||
|
||||
return part_absolute_offset(part->parent) + part->offset;
|
||||
}
|
||||
|
||||
/*
|
||||
* MTD methods which simply translate the effective address and pass through
|
||||
* to the _real_ device.
|
||||
*/
|
||||
|
||||
static int part_read(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, u_char *buf)
|
||||
static inline void free_partition(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
struct mtd_ecc_stats stats;
|
||||
int res;
|
||||
|
||||
stats = part->parent->ecc_stats;
|
||||
res = part->parent->_read(part->parent, from + part->offset, len,
|
||||
retlen, buf);
|
||||
if (unlikely(mtd_is_eccerr(res)))
|
||||
mtd->ecc_stats.failed +=
|
||||
part->parent->ecc_stats.failed - stats.failed;
|
||||
else
|
||||
mtd->ecc_stats.corrected +=
|
||||
part->parent->ecc_stats.corrected - stats.corrected;
|
||||
return res;
|
||||
kfree(mtd->name);
|
||||
kfree(mtd);
|
||||
}
|
||||
|
||||
static int part_point(struct mtd_info *mtd, loff_t from, size_t len,
|
||||
size_t *retlen, void **virt, resource_size_t *phys)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
|
||||
return part->parent->_point(part->parent, from + part->offset, len,
|
||||
retlen, virt, phys);
|
||||
}
|
||||
|
||||
static int part_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
|
||||
return part->parent->_unpoint(part->parent, from + part->offset, len);
|
||||
}
|
||||
|
||||
static int part_read_oob(struct mtd_info *mtd, loff_t from,
|
||||
struct mtd_oob_ops *ops)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
struct mtd_ecc_stats stats;
|
||||
int res;
|
||||
|
||||
stats = part->parent->ecc_stats;
|
||||
res = part->parent->_read_oob(part->parent, from + part->offset, ops);
|
||||
if (unlikely(mtd_is_eccerr(res)))
|
||||
mtd->ecc_stats.failed +=
|
||||
part->parent->ecc_stats.failed - stats.failed;
|
||||
else
|
||||
mtd->ecc_stats.corrected +=
|
||||
part->parent->ecc_stats.corrected - stats.corrected;
|
||||
return res;
|
||||
}
|
||||
|
||||
static int part_read_user_prot_reg(struct mtd_info *mtd, loff_t from,
|
||||
size_t len, size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return part->parent->_read_user_prot_reg(part->parent, from, len,
|
||||
retlen, buf);
|
||||
}
|
||||
|
||||
static int part_get_user_prot_info(struct mtd_info *mtd, size_t len,
|
||||
size_t *retlen, struct otp_info *buf)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return part->parent->_get_user_prot_info(part->parent, len, retlen,
|
||||
buf);
|
||||
}
|
||||
|
||||
static int part_read_fact_prot_reg(struct mtd_info *mtd, loff_t from,
|
||||
size_t len, size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return part->parent->_read_fact_prot_reg(part->parent, from, len,
|
||||
retlen, buf);
|
||||
}
|
||||
|
||||
static int part_get_fact_prot_info(struct mtd_info *mtd, size_t len,
|
||||
size_t *retlen, struct otp_info *buf)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return part->parent->_get_fact_prot_info(part->parent, len, retlen,
|
||||
buf);
|
||||
}
|
||||
|
||||
static int part_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return part->parent->_write(part->parent, to + part->offset, len,
|
||||
retlen, buf);
|
||||
}
|
||||
|
||||
static int part_panic_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return part->parent->_panic_write(part->parent, to + part->offset, len,
|
||||
retlen, buf);
|
||||
}
|
||||
|
||||
static int part_write_oob(struct mtd_info *mtd, loff_t to,
|
||||
struct mtd_oob_ops *ops)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
|
||||
return part->parent->_write_oob(part->parent, to + part->offset, ops);
|
||||
}
|
||||
|
||||
static int part_write_user_prot_reg(struct mtd_info *mtd, loff_t from,
|
||||
size_t len, size_t *retlen, u_char *buf)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return part->parent->_write_user_prot_reg(part->parent, from, len,
|
||||
retlen, buf);
|
||||
}
|
||||
|
||||
static int part_lock_user_prot_reg(struct mtd_info *mtd, loff_t from,
|
||||
size_t len)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return part->parent->_lock_user_prot_reg(part->parent, from, len);
|
||||
}
|
||||
|
||||
static int part_writev(struct mtd_info *mtd, const struct kvec *vecs,
|
||||
unsigned long count, loff_t to, size_t *retlen)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return part->parent->_writev(part->parent, vecs, count,
|
||||
to + part->offset, retlen);
|
||||
}
|
||||
|
||||
static int part_erase(struct mtd_info *mtd, struct erase_info *instr)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
int ret;
|
||||
|
||||
instr->addr += part->offset;
|
||||
ret = part->parent->_erase(part->parent, instr);
|
||||
if (instr->fail_addr != MTD_FAIL_ADDR_UNKNOWN)
|
||||
instr->fail_addr -= part->offset;
|
||||
instr->addr -= part->offset;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int part_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return part->parent->_lock(part->parent, ofs + part->offset, len);
|
||||
}
|
||||
|
||||
static int part_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return part->parent->_unlock(part->parent, ofs + part->offset, len);
|
||||
}
|
||||
|
||||
static int part_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return part->parent->_is_locked(part->parent, ofs + part->offset, len);
|
||||
}
|
||||
|
||||
static void part_sync(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
part->parent->_sync(part->parent);
|
||||
}
|
||||
|
||||
static int part_suspend(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return part->parent->_suspend(part->parent);
|
||||
}
|
||||
|
||||
static void part_resume(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
part->parent->_resume(part->parent);
|
||||
}
|
||||
|
||||
static int part_block_isreserved(struct mtd_info *mtd, loff_t ofs)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
ofs += part->offset;
|
||||
return part->parent->_block_isreserved(part->parent, ofs);
|
||||
}
|
||||
|
||||
static int part_block_isbad(struct mtd_info *mtd, loff_t ofs)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
ofs += part->offset;
|
||||
return part->parent->_block_isbad(part->parent, ofs);
|
||||
}
|
||||
|
||||
static int part_block_markbad(struct mtd_info *mtd, loff_t ofs)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
int res;
|
||||
|
||||
ofs += part->offset;
|
||||
res = part->parent->_block_markbad(part->parent, ofs);
|
||||
if (!res)
|
||||
mtd->ecc_stats.badblocks++;
|
||||
return res;
|
||||
}
|
||||
|
||||
static int part_get_device(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return part->parent->_get_device(part->parent);
|
||||
}
|
||||
|
||||
static void part_put_device(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
part->parent->_put_device(part->parent);
|
||||
}
|
||||
|
||||
static int part_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *oobregion)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
|
||||
return mtd_ooblayout_ecc(part->parent, section, oobregion);
|
||||
}
|
||||
|
||||
static int part_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *oobregion)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
|
||||
return mtd_ooblayout_free(part->parent, section, oobregion);
|
||||
}
|
||||
|
||||
static const struct mtd_ooblayout_ops part_ooblayout_ops = {
|
||||
.ecc = part_ooblayout_ecc,
|
||||
.free = part_ooblayout_free,
|
||||
};
|
||||
|
||||
static int part_max_bad_blocks(struct mtd_info *mtd, loff_t ofs, size_t len)
|
||||
{
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
|
||||
return part->parent->_max_bad_blocks(part->parent,
|
||||
ofs + part->offset, len);
|
||||
}
|
||||
|
||||
static inline void free_partition(struct mtd_part *p)
|
||||
{
|
||||
kfree(p->mtd.name);
|
||||
kfree(p);
|
||||
}
|
||||
|
||||
static struct mtd_part *allocate_partition(struct mtd_info *parent,
|
||||
const struct mtd_partition *part, int partno,
|
||||
uint64_t cur_offset)
|
||||
static struct mtd_info *allocate_partition(struct mtd_info *parent,
|
||||
const struct mtd_partition *part,
|
||||
int partno, uint64_t cur_offset)
|
||||
{
|
||||
int wr_alignment = (parent->flags & MTD_NO_ERASE) ? parent->writesize :
|
||||
parent->erasesize;
|
||||
struct mtd_part *slave;
|
||||
struct mtd_info *child, *master = mtd_get_master(parent);
|
||||
u32 remainder;
|
||||
char *name;
|
||||
u64 tmp;
|
||||
|
||||
/* allocate the partition structure */
|
||||
slave = kzalloc(sizeof(*slave), GFP_KERNEL);
|
||||
child = kzalloc(sizeof(*child), GFP_KERNEL);
|
||||
name = kstrdup(part->name, GFP_KERNEL);
|
||||
if (!name || !slave) {
|
||||
if (!name || !child) {
|
||||
printk(KERN_ERR"memory allocation error while creating partitions for \"%s\"\n",
|
||||
parent->name);
|
||||
kfree(name);
|
||||
kfree(slave);
|
||||
kfree(child);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
/* set up the MTD object for this partition */
|
||||
slave->mtd.type = parent->type;
|
||||
slave->mtd.flags = parent->orig_flags & ~part->mask_flags;
|
||||
slave->mtd.orig_flags = slave->mtd.flags;
|
||||
slave->mtd.size = part->size;
|
||||
slave->mtd.writesize = parent->writesize;
|
||||
slave->mtd.writebufsize = parent->writebufsize;
|
||||
slave->mtd.oobsize = parent->oobsize;
|
||||
slave->mtd.oobavail = parent->oobavail;
|
||||
slave->mtd.subpage_sft = parent->subpage_sft;
|
||||
slave->mtd.pairing = parent->pairing;
|
||||
child->type = parent->type;
|
||||
child->part.flags = parent->flags & ~part->mask_flags;
|
||||
child->flags = child->part.flags;
|
||||
child->size = part->size;
|
||||
child->writesize = parent->writesize;
|
||||
child->writebufsize = parent->writebufsize;
|
||||
child->oobsize = parent->oobsize;
|
||||
child->oobavail = parent->oobavail;
|
||||
child->subpage_sft = parent->subpage_sft;
|
||||
|
||||
slave->mtd.name = name;
|
||||
slave->mtd.owner = parent->owner;
|
||||
child->name = name;
|
||||
child->owner = parent->owner;
|
||||
|
||||
/* NOTE: Historically, we didn't arrange MTDs as a tree out of
|
||||
* concern for showing the same data in multiple partitions.
|
||||
@ -360,134 +73,76 @@ static struct mtd_part *allocate_partition(struct mtd_info *parent,
|
||||
* so the MTD_PARTITIONED_MASTER option allows that. The master
|
||||
* will have device nodes etc only if this is set, so make the
|
||||
* parent conditional on that option. Note, this is a way to
|
||||
* distinguish between the master and the partition in sysfs.
|
||||
* distinguish between the parent and its partitions in sysfs.
|
||||
*/
|
||||
slave->mtd.dev.parent = IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) || mtd_is_partition(parent) ?
|
||||
&parent->dev :
|
||||
parent->dev.parent;
|
||||
slave->mtd.dev.of_node = part->of_node;
|
||||
child->dev.parent = IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER) || mtd_is_partition(parent) ?
|
||||
&parent->dev : parent->dev.parent;
|
||||
child->dev.of_node = part->of_node;
|
||||
child->parent = parent;
|
||||
child->part.offset = part->offset;
|
||||
INIT_LIST_HEAD(&child->partitions);
|
||||
|
||||
if (parent->_read)
|
||||
slave->mtd._read = part_read;
|
||||
if (parent->_write)
|
||||
slave->mtd._write = part_write;
|
||||
|
||||
if (parent->_panic_write)
|
||||
slave->mtd._panic_write = part_panic_write;
|
||||
|
||||
if (parent->_point && parent->_unpoint) {
|
||||
slave->mtd._point = part_point;
|
||||
slave->mtd._unpoint = part_unpoint;
|
||||
}
|
||||
|
||||
if (parent->_read_oob)
|
||||
slave->mtd._read_oob = part_read_oob;
|
||||
if (parent->_write_oob)
|
||||
slave->mtd._write_oob = part_write_oob;
|
||||
if (parent->_read_user_prot_reg)
|
||||
slave->mtd._read_user_prot_reg = part_read_user_prot_reg;
|
||||
if (parent->_read_fact_prot_reg)
|
||||
slave->mtd._read_fact_prot_reg = part_read_fact_prot_reg;
|
||||
if (parent->_write_user_prot_reg)
|
||||
slave->mtd._write_user_prot_reg = part_write_user_prot_reg;
|
||||
if (parent->_lock_user_prot_reg)
|
||||
slave->mtd._lock_user_prot_reg = part_lock_user_prot_reg;
|
||||
if (parent->_get_user_prot_info)
|
||||
slave->mtd._get_user_prot_info = part_get_user_prot_info;
|
||||
if (parent->_get_fact_prot_info)
|
||||
slave->mtd._get_fact_prot_info = part_get_fact_prot_info;
|
||||
if (parent->_sync)
|
||||
slave->mtd._sync = part_sync;
|
||||
if (!partno && !parent->dev.class && parent->_suspend &&
|
||||
parent->_resume) {
|
||||
slave->mtd._suspend = part_suspend;
|
||||
slave->mtd._resume = part_resume;
|
||||
}
|
||||
if (parent->_writev)
|
||||
slave->mtd._writev = part_writev;
|
||||
if (parent->_lock)
|
||||
slave->mtd._lock = part_lock;
|
||||
if (parent->_unlock)
|
||||
slave->mtd._unlock = part_unlock;
|
||||
if (parent->_is_locked)
|
||||
slave->mtd._is_locked = part_is_locked;
|
||||
if (parent->_block_isreserved)
|
||||
slave->mtd._block_isreserved = part_block_isreserved;
|
||||
if (parent->_block_isbad)
|
||||
slave->mtd._block_isbad = part_block_isbad;
|
||||
if (parent->_block_markbad)
|
||||
slave->mtd._block_markbad = part_block_markbad;
|
||||
if (parent->_max_bad_blocks)
|
||||
slave->mtd._max_bad_blocks = part_max_bad_blocks;
|
||||
|
||||
if (parent->_get_device)
|
||||
slave->mtd._get_device = part_get_device;
|
||||
if (parent->_put_device)
|
||||
slave->mtd._put_device = part_put_device;
|
||||
|
||||
slave->mtd._erase = part_erase;
|
||||
slave->parent = parent;
|
||||
slave->offset = part->offset;
|
||||
|
||||
if (slave->offset == MTDPART_OFS_APPEND)
|
||||
slave->offset = cur_offset;
|
||||
if (slave->offset == MTDPART_OFS_NXTBLK) {
|
||||
if (child->part.offset == MTDPART_OFS_APPEND)
|
||||
child->part.offset = cur_offset;
|
||||
if (child->part.offset == MTDPART_OFS_NXTBLK) {
|
||||
tmp = cur_offset;
|
||||
slave->offset = cur_offset;
|
||||
child->part.offset = cur_offset;
|
||||
remainder = do_div(tmp, wr_alignment);
|
||||
if (remainder) {
|
||||
slave->offset += wr_alignment - remainder;
|
||||
child->part.offset += wr_alignment - remainder;
|
||||
printk(KERN_NOTICE "Moving partition %d: "
|
||||
"0x%012llx -> 0x%012llx\n", partno,
|
||||
(unsigned long long)cur_offset, (unsigned long long)slave->offset);
|
||||
(unsigned long long)cur_offset,
|
||||
child->part.offset);
|
||||
}
|
||||
}
|
||||
if (slave->offset == MTDPART_OFS_RETAIN) {
|
||||
slave->offset = cur_offset;
|
||||
if (parent->size - slave->offset >= slave->mtd.size) {
|
||||
slave->mtd.size = parent->size - slave->offset
|
||||
- slave->mtd.size;
|
||||
if (child->part.offset == MTDPART_OFS_RETAIN) {
|
||||
child->part.offset = cur_offset;
|
||||
if (parent->size - child->part.offset >= child->size) {
|
||||
child->size = parent->size - child->part.offset -
|
||||
child->size;
|
||||
} else {
|
||||
printk(KERN_ERR "mtd partition \"%s\" doesn't have enough space: %#llx < %#llx, disabled\n",
|
||||
part->name, parent->size - slave->offset,
|
||||
slave->mtd.size);
|
||||
part->name, parent->size - child->part.offset,
|
||||
child->size);
|
||||
/* register to preserve ordering */
|
||||
goto out_register;
|
||||
}
|
||||
}
|
||||
if (slave->mtd.size == MTDPART_SIZ_FULL)
|
||||
slave->mtd.size = parent->size - slave->offset;
|
||||
if (child->size == MTDPART_SIZ_FULL)
|
||||
child->size = parent->size - child->part.offset;
|
||||
|
||||
printk(KERN_NOTICE "0x%012llx-0x%012llx : \"%s\"\n", (unsigned long long)slave->offset,
|
||||
(unsigned long long)(slave->offset + slave->mtd.size), slave->mtd.name);
|
||||
printk(KERN_NOTICE "0x%012llx-0x%012llx : \"%s\"\n",
|
||||
child->part.offset, child->part.offset + child->size,
|
||||
child->name);
|
||||
|
||||
/* let's do some sanity checks */
|
||||
if (slave->offset >= parent->size) {
|
||||
if (child->part.offset >= parent->size) {
|
||||
/* let's register it anyway to preserve ordering */
|
||||
slave->offset = 0;
|
||||
slave->mtd.size = 0;
|
||||
child->part.offset = 0;
|
||||
child->size = 0;
|
||||
|
||||
/* Initialize ->erasesize to make add_mtd_device() happy. */
|
||||
slave->mtd.erasesize = parent->erasesize;
|
||||
|
||||
child->erasesize = parent->erasesize;
|
||||
printk(KERN_ERR"mtd: partition \"%s\" is out of reach -- disabled\n",
|
||||
part->name);
|
||||
goto out_register;
|
||||
}
|
||||
if (slave->offset + slave->mtd.size > parent->size) {
|
||||
slave->mtd.size = parent->size - slave->offset;
|
||||
if (child->part.offset + child->size > parent->size) {
|
||||
child->size = parent->size - child->part.offset;
|
||||
printk(KERN_WARNING"mtd: partition \"%s\" extends beyond the end of device \"%s\" -- size truncated to %#llx\n",
|
||||
part->name, parent->name, (unsigned long long)slave->mtd.size);
|
||||
part->name, parent->name, child->size);
|
||||
}
|
||||
if (parent->numeraseregions > 1) {
|
||||
/* Deal with variable erase size stuff */
|
||||
int i, max = parent->numeraseregions;
|
||||
u64 end = slave->offset + slave->mtd.size;
|
||||
u64 end = child->part.offset + child->size;
|
||||
struct mtd_erase_region_info *regions = parent->eraseregions;
|
||||
|
||||
/* Find the first erase regions which is part of this
|
||||
* partition. */
|
||||
for (i = 0; i < max && regions[i].offset <= slave->offset; i++)
|
||||
for (i = 0; i < max && regions[i].offset <= child->part.offset;
|
||||
i++)
|
||||
;
|
||||
/* The loop searched for the region _behind_ the first one */
|
||||
if (i > 0)
|
||||
@ -495,70 +150,68 @@ static struct mtd_part *allocate_partition(struct mtd_info *parent,
|
||||
|
||||
/* Pick biggest erasesize */
|
||||
for (; i < max && regions[i].offset < end; i++) {
|
||||
if (slave->mtd.erasesize < regions[i].erasesize) {
|
||||
slave->mtd.erasesize = regions[i].erasesize;
|
||||
}
|
||||
if (child->erasesize < regions[i].erasesize)
|
||||
child->erasesize = regions[i].erasesize;
|
||||
}
|
||||
BUG_ON(slave->mtd.erasesize == 0);
|
||||
BUG_ON(child->erasesize == 0);
|
||||
} else {
|
||||
/* Single erase size */
|
||||
slave->mtd.erasesize = parent->erasesize;
|
||||
child->erasesize = parent->erasesize;
|
||||
}
|
||||
|
||||
/*
|
||||
* Slave erasesize might differ from the master one if the master
|
||||
* Child erasesize might differ from the parent one if the parent
|
||||
* exposes several regions with different erasesize. Adjust
|
||||
* wr_alignment accordingly.
|
||||
*/
|
||||
if (!(slave->mtd.flags & MTD_NO_ERASE))
|
||||
wr_alignment = slave->mtd.erasesize;
|
||||
if (!(child->flags & MTD_NO_ERASE))
|
||||
wr_alignment = child->erasesize;
|
||||
|
||||
tmp = part_absolute_offset(parent) + slave->offset;
|
||||
tmp = mtd_get_master_ofs(child, 0);
|
||||
remainder = do_div(tmp, wr_alignment);
|
||||
if ((slave->mtd.flags & MTD_WRITEABLE) && remainder) {
|
||||
if ((child->flags & MTD_WRITEABLE) && remainder) {
|
||||
/* Doesn't start on a boundary of major erase size */
|
||||
/* FIXME: Let it be writable if it is on a boundary of
|
||||
* _minor_ erase size though */
|
||||
slave->mtd.flags &= ~MTD_WRITEABLE;
|
||||
child->flags &= ~MTD_WRITEABLE;
|
||||
printk(KERN_WARNING"mtd: partition \"%s\" doesn't start on an erase/write block boundary -- force read-only\n",
|
||||
part->name);
|
||||
}
|
||||
|
||||
tmp = part_absolute_offset(parent) + slave->mtd.size;
|
||||
tmp = mtd_get_master_ofs(child, 0) + child->size;
|
||||
remainder = do_div(tmp, wr_alignment);
|
||||
if ((slave->mtd.flags & MTD_WRITEABLE) && remainder) {
|
||||
slave->mtd.flags &= ~MTD_WRITEABLE;
|
||||
if ((child->flags & MTD_WRITEABLE) && remainder) {
|
||||
child->flags &= ~MTD_WRITEABLE;
|
||||
printk(KERN_WARNING"mtd: partition \"%s\" doesn't end on an erase/write block -- force read-only\n",
|
||||
part->name);
|
||||
}
|
||||
|
||||
mtd_set_ooblayout(&slave->mtd, &part_ooblayout_ops);
|
||||
slave->mtd.ecc_step_size = parent->ecc_step_size;
|
||||
slave->mtd.ecc_strength = parent->ecc_strength;
|
||||
slave->mtd.bitflip_threshold = parent->bitflip_threshold;
|
||||
child->ecc_step_size = parent->ecc_step_size;
|
||||
child->ecc_strength = parent->ecc_strength;
|
||||
child->bitflip_threshold = parent->bitflip_threshold;
|
||||
|
||||
if (parent->_block_isbad) {
|
||||
if (master->_block_isbad) {
|
||||
uint64_t offs = 0;
|
||||
|
||||
while (offs < slave->mtd.size) {
|
||||
if (mtd_block_isreserved(parent, offs + slave->offset))
|
||||
slave->mtd.ecc_stats.bbtblocks++;
|
||||
else if (mtd_block_isbad(parent, offs + slave->offset))
|
||||
slave->mtd.ecc_stats.badblocks++;
|
||||
offs += slave->mtd.erasesize;
|
||||
while (offs < child->size) {
|
||||
if (mtd_block_isreserved(child, offs))
|
||||
child->ecc_stats.bbtblocks++;
|
||||
else if (mtd_block_isbad(child, offs))
|
||||
child->ecc_stats.badblocks++;
|
||||
offs += child->erasesize;
|
||||
}
|
||||
}
|
||||
|
||||
out_register:
|
||||
return slave;
|
||||
return child;
|
||||
}
|
||||
|
||||
static ssize_t mtd_partition_offset_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct mtd_info *mtd = dev_get_drvdata(dev);
|
||||
struct mtd_part *part = mtd_to_part(mtd);
|
||||
return snprintf(buf, PAGE_SIZE, "%llu\n", part->offset);
|
||||
|
||||
return snprintf(buf, PAGE_SIZE, "%lld\n", mtd->part.offset);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(offset, S_IRUGO, mtd_partition_offset_show, NULL);
|
||||
@ -568,9 +221,9 @@ static const struct attribute *mtd_partition_attrs[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
static int mtd_add_partition_attrs(struct mtd_part *new)
|
||||
static int mtd_add_partition_attrs(struct mtd_info *new)
|
||||
{
|
||||
int ret = sysfs_create_files(&new->mtd.dev.kobj, mtd_partition_attrs);
|
||||
int ret = sysfs_create_files(&new->dev.kobj, mtd_partition_attrs);
|
||||
if (ret)
|
||||
printk(KERN_WARNING
|
||||
"mtd: failed to create partition attrs, err=%d\n", ret);
|
||||
@ -580,8 +233,9 @@ static int mtd_add_partition_attrs(struct mtd_part *new)
|
||||
int mtd_add_partition(struct mtd_info *parent, const char *name,
|
||||
long long offset, long long length)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(parent);
|
||||
struct mtd_partition part;
|
||||
struct mtd_part *new;
|
||||
struct mtd_info *child;
|
||||
int ret = 0;
|
||||
|
||||
/* the direct offset is expected */
|
||||
@ -600,28 +254,28 @@ int mtd_add_partition(struct mtd_info *parent, const char *name,
|
||||
part.size = length;
|
||||
part.offset = offset;
|
||||
|
||||
new = allocate_partition(parent, &part, -1, offset);
|
||||
if (IS_ERR(new))
|
||||
return PTR_ERR(new);
|
||||
child = allocate_partition(parent, &part, -1, offset);
|
||||
if (IS_ERR(child))
|
||||
return PTR_ERR(child);
|
||||
|
||||
mutex_lock(&mtd_partitions_mutex);
|
||||
list_add(&new->list, &mtd_partitions);
|
||||
mutex_unlock(&mtd_partitions_mutex);
|
||||
mutex_lock(&master->master.partitions_lock);
|
||||
list_add_tail(&child->part.node, &parent->partitions);
|
||||
mutex_unlock(&master->master.partitions_lock);
|
||||
|
||||
ret = add_mtd_device(&new->mtd);
|
||||
ret = add_mtd_device(child);
|
||||
if (ret)
|
||||
goto err_remove_part;
|
||||
|
||||
mtd_add_partition_attrs(new);
|
||||
mtd_add_partition_attrs(child);
|
||||
|
||||
return 0;
|
||||
|
||||
err_remove_part:
|
||||
mutex_lock(&mtd_partitions_mutex);
|
||||
list_del(&new->list);
|
||||
mutex_unlock(&mtd_partitions_mutex);
|
||||
mutex_lock(&master->master.partitions_lock);
|
||||
list_del(&child->part.node);
|
||||
mutex_unlock(&master->master.partitions_lock);
|
||||
|
||||
free_partition(new);
|
||||
free_partition(child);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -630,119 +284,142 @@ EXPORT_SYMBOL_GPL(mtd_add_partition);
|
||||
/**
|
||||
* __mtd_del_partition - delete MTD partition
|
||||
*
|
||||
* @priv: internal MTD struct for partition to be deleted
|
||||
* @priv: MTD structure to be deleted
|
||||
*
|
||||
* This function must be called with the partitions mutex locked.
|
||||
*/
|
||||
static int __mtd_del_partition(struct mtd_part *priv)
|
||||
static int __mtd_del_partition(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_part *child, *next;
|
||||
struct mtd_info *child, *next;
|
||||
int err;
|
||||
|
||||
list_for_each_entry_safe(child, next, &mtd_partitions, list) {
|
||||
if (child->parent == &priv->mtd) {
|
||||
err = __mtd_del_partition(child);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
list_for_each_entry_safe(child, next, &mtd->partitions, part.node) {
|
||||
err = __mtd_del_partition(child);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
sysfs_remove_files(&priv->mtd.dev.kobj, mtd_partition_attrs);
|
||||
sysfs_remove_files(&mtd->dev.kobj, mtd_partition_attrs);
|
||||
|
||||
err = del_mtd_device(&priv->mtd);
|
||||
err = del_mtd_device(mtd);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
list_del(&priv->list);
|
||||
free_partition(priv);
|
||||
list_del(&child->part.node);
|
||||
free_partition(mtd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function unregisters and destroy all slave MTD objects which are
|
||||
* attached to the given MTD object.
|
||||
* attached to the given MTD object, recursively.
|
||||
*/
|
||||
int del_mtd_partitions(struct mtd_info *mtd)
|
||||
static int __del_mtd_partitions(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_part *slave, *next;
|
||||
struct mtd_info *child, *next;
|
||||
LIST_HEAD(tmp_list);
|
||||
int ret, err = 0;
|
||||
|
||||
mutex_lock(&mtd_partitions_mutex);
|
||||
list_for_each_entry_safe(slave, next, &mtd_partitions, list)
|
||||
if (slave->parent == mtd) {
|
||||
ret = __mtd_del_partition(slave);
|
||||
if (ret < 0)
|
||||
err = ret;
|
||||
list_for_each_entry_safe(child, next, &mtd->partitions, part.node) {
|
||||
if (mtd_has_partitions(child))
|
||||
del_mtd_partitions(child);
|
||||
|
||||
pr_info("Deleting %s MTD partition\n", child->name);
|
||||
ret = del_mtd_device(child);
|
||||
if (ret < 0) {
|
||||
pr_err("Error when deleting partition \"%s\" (%d)\n",
|
||||
child->name, ret);
|
||||
err = ret;
|
||||
continue;
|
||||
}
|
||||
mutex_unlock(&mtd_partitions_mutex);
|
||||
|
||||
list_del(&child->part.node);
|
||||
free_partition(child);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int del_mtd_partitions(struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
int ret;
|
||||
|
||||
pr_info("Deleting MTD partitions on \"%s\":\n", mtd->name);
|
||||
|
||||
mutex_lock(&master->master.partitions_lock);
|
||||
ret = __del_mtd_partitions(mtd);
|
||||
mutex_unlock(&master->master.partitions_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mtd_del_partition(struct mtd_info *mtd, int partno)
|
||||
{
|
||||
struct mtd_part *slave, *next;
|
||||
struct mtd_info *child, *master = mtd_get_master(mtd);
|
||||
int ret = -EINVAL;
|
||||
|
||||
mutex_lock(&mtd_partitions_mutex);
|
||||
list_for_each_entry_safe(slave, next, &mtd_partitions, list)
|
||||
if ((slave->parent == mtd) &&
|
||||
(slave->mtd.index == partno)) {
|
||||
ret = __mtd_del_partition(slave);
|
||||
mutex_lock(&master->master.partitions_lock);
|
||||
list_for_each_entry(child, &mtd->partitions, part.node) {
|
||||
if (child->index == partno) {
|
||||
ret = __mtd_del_partition(child);
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&mtd_partitions_mutex);
|
||||
}
|
||||
mutex_unlock(&master->master.partitions_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_del_partition);
|
||||
|
||||
/*
|
||||
* This function, given a master MTD object and a partition table, creates
|
||||
* and registers slave MTD objects which are bound to the master according to
|
||||
* the partition definitions.
|
||||
* This function, given a parent MTD object and a partition table, creates
|
||||
* and registers the child MTD objects which are bound to the parent according
|
||||
* to the partition definitions.
|
||||
*
|
||||
* For historical reasons, this function's caller only registers the master
|
||||
* For historical reasons, this function's caller only registers the parent
|
||||
* if the MTD_PARTITIONED_MASTER config option is set.
|
||||
*/
|
||||
|
||||
int add_mtd_partitions(struct mtd_info *master,
|
||||
int add_mtd_partitions(struct mtd_info *parent,
|
||||
const struct mtd_partition *parts,
|
||||
int nbparts)
|
||||
{
|
||||
struct mtd_part *slave;
|
||||
struct mtd_info *child, *master = mtd_get_master(parent);
|
||||
uint64_t cur_offset = 0;
|
||||
int i, ret;
|
||||
|
||||
printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);
|
||||
printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n",
|
||||
nbparts, parent->name);
|
||||
|
||||
for (i = 0; i < nbparts; i++) {
|
||||
slave = allocate_partition(master, parts + i, i, cur_offset);
|
||||
if (IS_ERR(slave)) {
|
||||
ret = PTR_ERR(slave);
|
||||
child = allocate_partition(parent, parts + i, i, cur_offset);
|
||||
if (IS_ERR(child)) {
|
||||
ret = PTR_ERR(child);
|
||||
goto err_del_partitions;
|
||||
}
|
||||
|
||||
mutex_lock(&mtd_partitions_mutex);
|
||||
list_add(&slave->list, &mtd_partitions);
|
||||
mutex_unlock(&mtd_partitions_mutex);
|
||||
mutex_lock(&master->master.partitions_lock);
|
||||
list_add_tail(&child->part.node, &parent->partitions);
|
||||
mutex_unlock(&master->master.partitions_lock);
|
||||
|
||||
ret = add_mtd_device(&slave->mtd);
|
||||
ret = add_mtd_device(child);
|
||||
if (ret) {
|
||||
mutex_lock(&mtd_partitions_mutex);
|
||||
list_del(&slave->list);
|
||||
mutex_unlock(&mtd_partitions_mutex);
|
||||
mutex_lock(&master->master.partitions_lock);
|
||||
list_del(&child->part.node);
|
||||
mutex_unlock(&master->master.partitions_lock);
|
||||
|
||||
free_partition(slave);
|
||||
free_partition(child);
|
||||
goto err_del_partitions;
|
||||
}
|
||||
|
||||
mtd_add_partition_attrs(slave);
|
||||
/* Look for subpartitions */
|
||||
parse_mtd_partitions(&slave->mtd, parts[i].types, NULL);
|
||||
mtd_add_partition_attrs(child);
|
||||
|
||||
cur_offset = slave->offset + slave->mtd.size;
|
||||
/* Look for subpartitions */
|
||||
parse_mtd_partitions(child, parts[i].types, NULL);
|
||||
|
||||
cur_offset = child->part.offset + child->size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -1023,29 +700,11 @@ void mtd_part_parser_cleanup(struct mtd_partitions *parts)
|
||||
}
|
||||
}
|
||||
|
||||
int mtd_is_partition(const struct mtd_info *mtd)
|
||||
{
|
||||
struct mtd_part *part;
|
||||
int ispart = 0;
|
||||
|
||||
mutex_lock(&mtd_partitions_mutex);
|
||||
list_for_each_entry(part, &mtd_partitions, list)
|
||||
if (&part->mtd == mtd) {
|
||||
ispart = 1;
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&mtd_partitions_mutex);
|
||||
|
||||
return ispart;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_is_partition);
|
||||
|
||||
/* Returns the size of the entire flash chip */
|
||||
uint64_t mtd_get_device_size(const struct mtd_info *mtd)
|
||||
{
|
||||
if (!mtd_is_partition(mtd))
|
||||
return mtd->size;
|
||||
struct mtd_info *master = mtd_get_master((struct mtd_info *)mtd);
|
||||
|
||||
return mtd_get_device_size(mtd_to_part(mtd)->parent);
|
||||
return master->size;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mtd_get_device_size);
|
||||
|
@ -3259,7 +3259,7 @@ static void onenand_check_features(struct mtd_info *mtd)
|
||||
switch (density) {
|
||||
case ONENAND_DEVICE_DENSITY_8Gb:
|
||||
this->options |= ONENAND_HAS_NOP_1;
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case ONENAND_DEVICE_DENSITY_4Gb:
|
||||
if (ONENAND_IS_DDP(this))
|
||||
this->options |= ONENAND_HAS_2PLANE;
|
||||
|
@ -19,15 +19,17 @@
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/mtd/nand-gpio.h>
|
||||
#include <linux/mtd/rawnand.h>
|
||||
#include <linux/mtd/partitions.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/sizes.h>
|
||||
|
||||
/*
|
||||
* MTD structure for E3 (Delta)
|
||||
*/
|
||||
struct ams_delta_nand {
|
||||
struct gpio_nand {
|
||||
struct nand_controller base;
|
||||
struct nand_chip nand_chip;
|
||||
struct gpio_desc *gpiod_rdy;
|
||||
@ -39,41 +41,20 @@ struct ams_delta_nand {
|
||||
struct gpio_desc *gpiod_cle;
|
||||
struct gpio_descs *data_gpiods;
|
||||
bool data_in;
|
||||
unsigned int tRP;
|
||||
unsigned int tWP;
|
||||
u8 (*io_read)(struct gpio_nand *this);
|
||||
void (*io_write)(struct gpio_nand *this, u8 byte);
|
||||
};
|
||||
|
||||
/*
|
||||
* Define partitions for flash devices
|
||||
*/
|
||||
|
||||
static const struct mtd_partition partition_info[] = {
|
||||
{ .name = "Kernel",
|
||||
.offset = 0,
|
||||
.size = 3 * SZ_1M + SZ_512K },
|
||||
{ .name = "u-boot",
|
||||
.offset = 3 * SZ_1M + SZ_512K,
|
||||
.size = SZ_256K },
|
||||
{ .name = "u-boot params",
|
||||
.offset = 3 * SZ_1M + SZ_512K + SZ_256K,
|
||||
.size = SZ_256K },
|
||||
{ .name = "Amstrad LDR",
|
||||
.offset = 4 * SZ_1M,
|
||||
.size = SZ_256K },
|
||||
{ .name = "File system",
|
||||
.offset = 4 * SZ_1M + 1 * SZ_256K,
|
||||
.size = 27 * SZ_1M },
|
||||
{ .name = "PBL reserved",
|
||||
.offset = 32 * SZ_1M - 3 * SZ_256K,
|
||||
.size = 3 * SZ_256K },
|
||||
};
|
||||
|
||||
static void ams_delta_write_commit(struct ams_delta_nand *priv)
|
||||
static void gpio_nand_write_commit(struct gpio_nand *priv)
|
||||
{
|
||||
gpiod_set_value(priv->gpiod_nwe, 0);
|
||||
ndelay(40);
|
||||
gpiod_set_value(priv->gpiod_nwe, 1);
|
||||
ndelay(priv->tWP);
|
||||
gpiod_set_value(priv->gpiod_nwe, 0);
|
||||
}
|
||||
|
||||
static void ams_delta_io_write(struct ams_delta_nand *priv, u8 byte)
|
||||
static void gpio_nand_io_write(struct gpio_nand *priv, u8 byte)
|
||||
{
|
||||
struct gpio_descs *data_gpiods = priv->data_gpiods;
|
||||
DECLARE_BITMAP(values, BITS_PER_TYPE(byte)) = { byte, };
|
||||
@ -81,10 +62,10 @@ static void ams_delta_io_write(struct ams_delta_nand *priv, u8 byte)
|
||||
gpiod_set_raw_array_value(data_gpiods->ndescs, data_gpiods->desc,
|
||||
data_gpiods->info, values);
|
||||
|
||||
ams_delta_write_commit(priv);
|
||||
gpio_nand_write_commit(priv);
|
||||
}
|
||||
|
||||
static void ams_delta_dir_output(struct ams_delta_nand *priv, u8 byte)
|
||||
static void gpio_nand_dir_output(struct gpio_nand *priv, u8 byte)
|
||||
{
|
||||
struct gpio_descs *data_gpiods = priv->data_gpiods;
|
||||
DECLARE_BITMAP(values, BITS_PER_TYPE(byte)) = { byte, };
|
||||
@ -94,30 +75,30 @@ static void ams_delta_dir_output(struct ams_delta_nand *priv, u8 byte)
|
||||
gpiod_direction_output_raw(data_gpiods->desc[i],
|
||||
test_bit(i, values));
|
||||
|
||||
ams_delta_write_commit(priv);
|
||||
gpio_nand_write_commit(priv);
|
||||
|
||||
priv->data_in = false;
|
||||
}
|
||||
|
||||
static u8 ams_delta_io_read(struct ams_delta_nand *priv)
|
||||
static u8 gpio_nand_io_read(struct gpio_nand *priv)
|
||||
{
|
||||
u8 res;
|
||||
struct gpio_descs *data_gpiods = priv->data_gpiods;
|
||||
DECLARE_BITMAP(values, BITS_PER_TYPE(res)) = { 0, };
|
||||
|
||||
gpiod_set_value(priv->gpiod_nre, 0);
|
||||
ndelay(40);
|
||||
gpiod_set_value(priv->gpiod_nre, 1);
|
||||
ndelay(priv->tRP);
|
||||
|
||||
gpiod_get_raw_array_value(data_gpiods->ndescs, data_gpiods->desc,
|
||||
data_gpiods->info, values);
|
||||
|
||||
gpiod_set_value(priv->gpiod_nre, 1);
|
||||
gpiod_set_value(priv->gpiod_nre, 0);
|
||||
|
||||
res = values[0];
|
||||
return res;
|
||||
}
|
||||
|
||||
static void ams_delta_dir_input(struct ams_delta_nand *priv)
|
||||
static void gpio_nand_dir_input(struct gpio_nand *priv)
|
||||
{
|
||||
struct gpio_descs *data_gpiods = priv->data_gpiods;
|
||||
int i;
|
||||
@ -128,68 +109,67 @@ static void ams_delta_dir_input(struct ams_delta_nand *priv)
|
||||
priv->data_in = true;
|
||||
}
|
||||
|
||||
static void ams_delta_write_buf(struct ams_delta_nand *priv, const u8 *buf,
|
||||
int len)
|
||||
static void gpio_nand_write_buf(struct gpio_nand *priv, const u8 *buf, int len)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
if (len > 0 && priv->data_in)
|
||||
ams_delta_dir_output(priv, buf[i++]);
|
||||
gpio_nand_dir_output(priv, buf[i++]);
|
||||
|
||||
while (i < len)
|
||||
ams_delta_io_write(priv, buf[i++]);
|
||||
priv->io_write(priv, buf[i++]);
|
||||
}
|
||||
|
||||
static void ams_delta_read_buf(struct ams_delta_nand *priv, u8 *buf, int len)
|
||||
static void gpio_nand_read_buf(struct gpio_nand *priv, u8 *buf, int len)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!priv->data_in)
|
||||
ams_delta_dir_input(priv);
|
||||
if (priv->data_gpiods && !priv->data_in)
|
||||
gpio_nand_dir_input(priv);
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
buf[i] = ams_delta_io_read(priv);
|
||||
buf[i] = priv->io_read(priv);
|
||||
}
|
||||
|
||||
static void ams_delta_ctrl_cs(struct ams_delta_nand *priv, bool assert)
|
||||
static void gpio_nand_ctrl_cs(struct gpio_nand *priv, bool assert)
|
||||
{
|
||||
gpiod_set_value(priv->gpiod_nce, assert ? 0 : 1);
|
||||
gpiod_set_value(priv->gpiod_nce, assert);
|
||||
}
|
||||
|
||||
static int ams_delta_exec_op(struct nand_chip *this,
|
||||
static int gpio_nand_exec_op(struct nand_chip *this,
|
||||
const struct nand_operation *op, bool check_only)
|
||||
{
|
||||
struct ams_delta_nand *priv = nand_get_controller_data(this);
|
||||
struct gpio_nand *priv = nand_get_controller_data(this);
|
||||
const struct nand_op_instr *instr;
|
||||
int ret = 0;
|
||||
|
||||
if (check_only)
|
||||
return 0;
|
||||
|
||||
ams_delta_ctrl_cs(priv, 1);
|
||||
gpio_nand_ctrl_cs(priv, 1);
|
||||
|
||||
for (instr = op->instrs; instr < op->instrs + op->ninstrs; instr++) {
|
||||
switch (instr->type) {
|
||||
case NAND_OP_CMD_INSTR:
|
||||
gpiod_set_value(priv->gpiod_cle, 1);
|
||||
ams_delta_write_buf(priv, &instr->ctx.cmd.opcode, 1);
|
||||
gpio_nand_write_buf(priv, &instr->ctx.cmd.opcode, 1);
|
||||
gpiod_set_value(priv->gpiod_cle, 0);
|
||||
break;
|
||||
|
||||
case NAND_OP_ADDR_INSTR:
|
||||
gpiod_set_value(priv->gpiod_ale, 1);
|
||||
ams_delta_write_buf(priv, instr->ctx.addr.addrs,
|
||||
gpio_nand_write_buf(priv, instr->ctx.addr.addrs,
|
||||
instr->ctx.addr.naddrs);
|
||||
gpiod_set_value(priv->gpiod_ale, 0);
|
||||
break;
|
||||
|
||||
case NAND_OP_DATA_IN_INSTR:
|
||||
ams_delta_read_buf(priv, instr->ctx.data.buf.in,
|
||||
gpio_nand_read_buf(priv, instr->ctx.data.buf.in,
|
||||
instr->ctx.data.len);
|
||||
break;
|
||||
|
||||
case NAND_OP_DATA_OUT_INSTR:
|
||||
ams_delta_write_buf(priv, instr->ctx.data.buf.out,
|
||||
gpio_nand_write_buf(priv, instr->ctx.data.buf.out,
|
||||
instr->ctx.data.len);
|
||||
break;
|
||||
|
||||
@ -206,28 +186,61 @@ static int ams_delta_exec_op(struct nand_chip *this,
|
||||
break;
|
||||
}
|
||||
|
||||
ams_delta_ctrl_cs(priv, 0);
|
||||
gpio_nand_ctrl_cs(priv, 0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct nand_controller_ops ams_delta_ops = {
|
||||
.exec_op = ams_delta_exec_op,
|
||||
static int gpio_nand_setup_data_interface(struct nand_chip *this, int csline,
|
||||
const struct nand_data_interface *cf)
|
||||
{
|
||||
struct gpio_nand *priv = nand_get_controller_data(this);
|
||||
const struct nand_sdr_timings *sdr = nand_get_sdr_timings(cf);
|
||||
struct device *dev = &nand_to_mtd(this)->dev;
|
||||
|
||||
if (IS_ERR(sdr))
|
||||
return PTR_ERR(sdr);
|
||||
|
||||
if (csline == NAND_DATA_IFACE_CHECK_ONLY)
|
||||
return 0;
|
||||
|
||||
if (priv->gpiod_nre) {
|
||||
priv->tRP = DIV_ROUND_UP(sdr->tRP_min, 1000);
|
||||
dev_dbg(dev, "using %u ns read pulse width\n", priv->tRP);
|
||||
}
|
||||
|
||||
priv->tWP = DIV_ROUND_UP(sdr->tWP_min, 1000);
|
||||
dev_dbg(dev, "using %u ns write pulse width\n", priv->tWP);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct nand_controller_ops gpio_nand_ops = {
|
||||
.exec_op = gpio_nand_exec_op,
|
||||
.setup_data_interface = gpio_nand_setup_data_interface,
|
||||
};
|
||||
|
||||
/*
|
||||
* Main initialization routine
|
||||
*/
|
||||
static int ams_delta_init(struct platform_device *pdev)
|
||||
static int gpio_nand_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct ams_delta_nand *priv;
|
||||
struct gpio_nand_platdata *pdata = dev_get_platdata(&pdev->dev);
|
||||
const struct mtd_partition *partitions = NULL;
|
||||
int num_partitions = 0;
|
||||
struct gpio_nand *priv;
|
||||
struct nand_chip *this;
|
||||
struct mtd_info *mtd;
|
||||
struct gpio_descs *data_gpiods;
|
||||
int (*probe)(struct platform_device *pdev, struct gpio_nand *priv);
|
||||
int err = 0;
|
||||
|
||||
if (pdata) {
|
||||
partitions = pdata->parts;
|
||||
num_partitions = pdata->num_parts;
|
||||
}
|
||||
|
||||
/* Allocate memory for MTD device structure and private data */
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(struct ams_delta_nand),
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(struct gpio_nand),
|
||||
GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
@ -238,6 +251,7 @@ static int ams_delta_init(struct platform_device *pdev)
|
||||
mtd->dev.parent = &pdev->dev;
|
||||
|
||||
nand_set_controller_data(this, priv);
|
||||
nand_set_flash_node(this, pdev->dev.of_node);
|
||||
|
||||
priv->gpiod_rdy = devm_gpiod_get_optional(&pdev->dev, "rdy", GPIOD_IN);
|
||||
if (IS_ERR(priv->gpiod_rdy)) {
|
||||
@ -251,29 +265,33 @@ static int ams_delta_init(struct platform_device *pdev)
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
/* Set chip enabled, but */
|
||||
priv->gpiod_nwp = devm_gpiod_get(&pdev->dev, "nwp", GPIOD_OUT_HIGH);
|
||||
/* Set chip enabled but write protected */
|
||||
priv->gpiod_nwp = devm_gpiod_get_optional(&pdev->dev, "nwp",
|
||||
GPIOD_OUT_HIGH);
|
||||
if (IS_ERR(priv->gpiod_nwp)) {
|
||||
err = PTR_ERR(priv->gpiod_nwp);
|
||||
dev_err(&pdev->dev, "NWP GPIO request failed (%d)\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
priv->gpiod_nce = devm_gpiod_get(&pdev->dev, "nce", GPIOD_OUT_HIGH);
|
||||
priv->gpiod_nce = devm_gpiod_get_optional(&pdev->dev, "nce",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(priv->gpiod_nce)) {
|
||||
err = PTR_ERR(priv->gpiod_nce);
|
||||
dev_err(&pdev->dev, "NCE GPIO request failed (%d)\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
priv->gpiod_nre = devm_gpiod_get(&pdev->dev, "nre", GPIOD_OUT_HIGH);
|
||||
priv->gpiod_nre = devm_gpiod_get_optional(&pdev->dev, "nre",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(priv->gpiod_nre)) {
|
||||
err = PTR_ERR(priv->gpiod_nre);
|
||||
dev_err(&pdev->dev, "NRE GPIO request failed (%d)\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
priv->gpiod_nwe = devm_gpiod_get(&pdev->dev, "nwe", GPIOD_OUT_HIGH);
|
||||
priv->gpiod_nwe = devm_gpiod_get_optional(&pdev->dev, "nwe",
|
||||
GPIOD_OUT_LOW);
|
||||
if (IS_ERR(priv->gpiod_nwe)) {
|
||||
err = PTR_ERR(priv->gpiod_nwe);
|
||||
dev_err(&pdev->dev, "NWE GPIO request failed (%d)\n", err);
|
||||
@ -295,28 +313,62 @@ static int ams_delta_init(struct platform_device *pdev)
|
||||
}
|
||||
|
||||
/* Request array of data pins, initialize them as input */
|
||||
data_gpiods = devm_gpiod_get_array(&pdev->dev, "data", GPIOD_IN);
|
||||
if (IS_ERR(data_gpiods)) {
|
||||
err = PTR_ERR(data_gpiods);
|
||||
priv->data_gpiods = devm_gpiod_get_array_optional(&pdev->dev, "data",
|
||||
GPIOD_IN);
|
||||
if (IS_ERR(priv->data_gpiods)) {
|
||||
err = PTR_ERR(priv->data_gpiods);
|
||||
dev_err(&pdev->dev, "data GPIO request failed: %d\n", err);
|
||||
return err;
|
||||
}
|
||||
priv->data_gpiods = data_gpiods;
|
||||
priv->data_in = true;
|
||||
if (priv->data_gpiods) {
|
||||
if (!priv->gpiod_nwe) {
|
||||
dev_err(&pdev->dev,
|
||||
"mandatory NWE pin not provided by platform\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Initialize the NAND controller object embedded in ams_delta_nand. */
|
||||
priv->base.ops = &ams_delta_ops;
|
||||
priv->io_read = gpio_nand_io_read;
|
||||
priv->io_write = gpio_nand_io_write;
|
||||
priv->data_in = true;
|
||||
}
|
||||
|
||||
if (pdev->id_entry)
|
||||
probe = (void *) pdev->id_entry->driver_data;
|
||||
else
|
||||
probe = of_device_get_match_data(&pdev->dev);
|
||||
if (probe)
|
||||
err = probe(pdev, priv);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (!priv->io_read || !priv->io_write) {
|
||||
dev_err(&pdev->dev, "incomplete device configuration\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Initialize the NAND controller object embedded in gpio_nand. */
|
||||
priv->base.ops = &gpio_nand_ops;
|
||||
nand_controller_init(&priv->base);
|
||||
this->controller = &priv->base;
|
||||
|
||||
/*
|
||||
* FIXME: We should release write protection only after nand_scan() to
|
||||
* be on the safe side but we can't do that until we have a generic way
|
||||
* to assert/deassert WP from the core. Even if the core shouldn't
|
||||
* write things in the nand_scan() path, it should have control on this
|
||||
* pin just in case we ever need to disable write protection during
|
||||
* chip detection/initialization.
|
||||
*/
|
||||
/* Release write protection */
|
||||
gpiod_set_value(priv->gpiod_nwp, 0);
|
||||
|
||||
/* Scan to find existence of the device */
|
||||
err = nand_scan(this, 1);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Register the partitions */
|
||||
err = mtd_device_register(mtd, partition_info,
|
||||
ARRAY_SIZE(partition_info));
|
||||
err = mtd_device_register(mtd, partitions, num_partitions);
|
||||
if (err)
|
||||
goto err_nand_cleanup;
|
||||
|
||||
@ -331,26 +383,47 @@ static int ams_delta_init(struct platform_device *pdev)
|
||||
/*
|
||||
* Clean up routine
|
||||
*/
|
||||
static int ams_delta_cleanup(struct platform_device *pdev)
|
||||
static int gpio_nand_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct ams_delta_nand *priv = platform_get_drvdata(pdev);
|
||||
struct gpio_nand *priv = platform_get_drvdata(pdev);
|
||||
struct mtd_info *mtd = nand_to_mtd(&priv->nand_chip);
|
||||
|
||||
/* Apply write protection */
|
||||
gpiod_set_value(priv->gpiod_nwp, 1);
|
||||
|
||||
/* Unregister device */
|
||||
nand_release(mtd_to_nand(mtd));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ams_delta_nand_driver = {
|
||||
.probe = ams_delta_init,
|
||||
.remove = ams_delta_cleanup,
|
||||
static const struct of_device_id gpio_nand_of_id_table[] = {
|
||||
{
|
||||
/* sentinel */
|
||||
},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, gpio_nand_of_id_table);
|
||||
|
||||
static const struct platform_device_id gpio_nand_plat_id_table[] = {
|
||||
{
|
||||
.name = "ams-delta-nand",
|
||||
}, {
|
||||
/* sentinel */
|
||||
},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, gpio_nand_plat_id_table);
|
||||
|
||||
static struct platform_driver gpio_nand_driver = {
|
||||
.probe = gpio_nand_probe,
|
||||
.remove = gpio_nand_remove,
|
||||
.id_table = gpio_nand_plat_id_table,
|
||||
.driver = {
|
||||
.name = "ams-delta-nand",
|
||||
.of_match_table = of_match_ptr(gpio_nand_of_id_table),
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(ams_delta_nand_driver);
|
||||
module_platform_driver(gpio_nand_driver);
|
||||
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_AUTHOR("Jonathan McDowell <noodles@earth.li>");
|
||||
|
@ -102,6 +102,45 @@ struct brcm_nand_dma_desc {
|
||||
#define NAND_CTRL_RDY (INTFC_CTLR_READY | INTFC_FLASH_READY)
|
||||
#define NAND_POLL_STATUS_TIMEOUT_MS 100
|
||||
|
||||
#define EDU_CMD_WRITE 0x00
|
||||
#define EDU_CMD_READ 0x01
|
||||
#define EDU_STATUS_ACTIVE BIT(0)
|
||||
#define EDU_ERR_STATUS_ERRACK BIT(0)
|
||||
#define EDU_DONE_MASK GENMASK(1, 0)
|
||||
|
||||
#define EDU_CONFIG_MODE_NAND BIT(0)
|
||||
#define EDU_CONFIG_SWAP_BYTE BIT(1)
|
||||
#ifdef CONFIG_CPU_BIG_ENDIAN
|
||||
#define EDU_CONFIG_SWAP_CFG EDU_CONFIG_SWAP_BYTE
|
||||
#else
|
||||
#define EDU_CONFIG_SWAP_CFG 0
|
||||
#endif
|
||||
|
||||
/* edu registers */
|
||||
enum edu_reg {
|
||||
EDU_CONFIG = 0,
|
||||
EDU_DRAM_ADDR,
|
||||
EDU_EXT_ADDR,
|
||||
EDU_LENGTH,
|
||||
EDU_CMD,
|
||||
EDU_STOP,
|
||||
EDU_STATUS,
|
||||
EDU_DONE,
|
||||
EDU_ERR_STATUS,
|
||||
};
|
||||
|
||||
static const u16 edu_regs[] = {
|
||||
[EDU_CONFIG] = 0x00,
|
||||
[EDU_DRAM_ADDR] = 0x04,
|
||||
[EDU_EXT_ADDR] = 0x08,
|
||||
[EDU_LENGTH] = 0x0c,
|
||||
[EDU_CMD] = 0x10,
|
||||
[EDU_STOP] = 0x14,
|
||||
[EDU_STATUS] = 0x18,
|
||||
[EDU_DONE] = 0x1c,
|
||||
[EDU_ERR_STATUS] = 0x20,
|
||||
};
|
||||
|
||||
/* flash_dma registers */
|
||||
enum flash_dma_reg {
|
||||
FLASH_DMA_REVISION = 0,
|
||||
@ -167,6 +206,8 @@ enum {
|
||||
BRCMNAND_HAS_WP = BIT(3),
|
||||
};
|
||||
|
||||
struct brcmnand_host;
|
||||
|
||||
struct brcmnand_controller {
|
||||
struct device *dev;
|
||||
struct nand_controller controller;
|
||||
@ -185,17 +226,32 @@ struct brcmnand_controller {
|
||||
|
||||
int cmd_pending;
|
||||
bool dma_pending;
|
||||
bool edu_pending;
|
||||
struct completion done;
|
||||
struct completion dma_done;
|
||||
struct completion edu_done;
|
||||
|
||||
/* List of NAND hosts (one for each chip-select) */
|
||||
struct list_head host_list;
|
||||
|
||||
/* EDU info, per-transaction */
|
||||
const u16 *edu_offsets;
|
||||
void __iomem *edu_base;
|
||||
int edu_irq;
|
||||
int edu_count;
|
||||
u64 edu_dram_addr;
|
||||
u32 edu_ext_addr;
|
||||
u32 edu_cmd;
|
||||
u32 edu_config;
|
||||
|
||||
/* flash_dma reg */
|
||||
const u16 *flash_dma_offsets;
|
||||
struct brcm_nand_dma_desc *dma_desc;
|
||||
dma_addr_t dma_pa;
|
||||
|
||||
int (*dma_trans)(struct brcmnand_host *host, u64 addr, u32 *buf,
|
||||
u32 len, u8 dma_cmd);
|
||||
|
||||
/* in-memory cache of the FLASH_CACHE, used only for some commands */
|
||||
u8 flash_cache[FC_BYTES];
|
||||
|
||||
@ -216,6 +272,7 @@ struct brcmnand_controller {
|
||||
u32 nand_cs_nand_xor;
|
||||
u32 corr_stat_threshold;
|
||||
u32 flash_dma_mode;
|
||||
u32 flash_edu_mode;
|
||||
bool pio_poll_mode;
|
||||
};
|
||||
|
||||
@ -657,6 +714,22 @@ static inline void brcmnand_write_fc(struct brcmnand_controller *ctrl,
|
||||
__raw_writel(val, ctrl->nand_fc + word * 4);
|
||||
}
|
||||
|
||||
static inline void edu_writel(struct brcmnand_controller *ctrl,
|
||||
enum edu_reg reg, u32 val)
|
||||
{
|
||||
u16 offs = ctrl->edu_offsets[reg];
|
||||
|
||||
brcmnand_writel(val, ctrl->edu_base + offs);
|
||||
}
|
||||
|
||||
static inline u32 edu_readl(struct brcmnand_controller *ctrl,
|
||||
enum edu_reg reg)
|
||||
{
|
||||
u16 offs = ctrl->edu_offsets[reg];
|
||||
|
||||
return brcmnand_readl(ctrl->edu_base + offs);
|
||||
}
|
||||
|
||||
static void brcmnand_clear_ecc_addr(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
|
||||
@ -926,6 +999,16 @@ static inline bool has_flash_dma(struct brcmnand_controller *ctrl)
|
||||
return ctrl->flash_dma_base;
|
||||
}
|
||||
|
||||
static inline bool has_edu(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
return ctrl->edu_base;
|
||||
}
|
||||
|
||||
static inline bool use_dma(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
return has_flash_dma(ctrl) || has_edu(ctrl);
|
||||
}
|
||||
|
||||
static inline void disable_ctrl_irqs(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
if (ctrl->pio_poll_mode)
|
||||
@ -1299,6 +1382,52 @@ static int write_oob_to_regs(struct brcmnand_controller *ctrl, int i,
|
||||
return tbytes;
|
||||
}
|
||||
|
||||
static void brcmnand_edu_init(struct brcmnand_controller *ctrl)
|
||||
{
|
||||
/* initialize edu */
|
||||
edu_writel(ctrl, EDU_ERR_STATUS, 0);
|
||||
edu_readl(ctrl, EDU_ERR_STATUS);
|
||||
edu_writel(ctrl, EDU_DONE, 0);
|
||||
edu_writel(ctrl, EDU_DONE, 0);
|
||||
edu_writel(ctrl, EDU_DONE, 0);
|
||||
edu_writel(ctrl, EDU_DONE, 0);
|
||||
edu_readl(ctrl, EDU_DONE);
|
||||
}
|
||||
|
||||
/* edu irq */
|
||||
static irqreturn_t brcmnand_edu_irq(int irq, void *data)
|
||||
{
|
||||
struct brcmnand_controller *ctrl = data;
|
||||
|
||||
if (ctrl->edu_count) {
|
||||
ctrl->edu_count--;
|
||||
while (!(edu_readl(ctrl, EDU_DONE) & EDU_DONE_MASK))
|
||||
udelay(1);
|
||||
edu_writel(ctrl, EDU_DONE, 0);
|
||||
edu_readl(ctrl, EDU_DONE);
|
||||
}
|
||||
|
||||
if (ctrl->edu_count) {
|
||||
ctrl->edu_dram_addr += FC_BYTES;
|
||||
ctrl->edu_ext_addr += FC_BYTES;
|
||||
|
||||
edu_writel(ctrl, EDU_DRAM_ADDR, (u32)ctrl->edu_dram_addr);
|
||||
edu_readl(ctrl, EDU_DRAM_ADDR);
|
||||
edu_writel(ctrl, EDU_EXT_ADDR, ctrl->edu_ext_addr);
|
||||
edu_readl(ctrl, EDU_EXT_ADDR);
|
||||
|
||||
mb(); /* flush previous writes */
|
||||
edu_writel(ctrl, EDU_CMD, ctrl->edu_cmd);
|
||||
edu_readl(ctrl, EDU_CMD);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
complete(&ctrl->edu_done);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t brcmnand_ctlrdy_irq(int irq, void *data)
|
||||
{
|
||||
struct brcmnand_controller *ctrl = data;
|
||||
@ -1307,6 +1436,16 @@ static irqreturn_t brcmnand_ctlrdy_irq(int irq, void *data)
|
||||
if (ctrl->dma_pending)
|
||||
return IRQ_HANDLED;
|
||||
|
||||
/* check if you need to piggy back on the ctrlrdy irq */
|
||||
if (ctrl->edu_pending) {
|
||||
if (irq == ctrl->irq && ((int)ctrl->edu_irq >= 0))
|
||||
/* Discard interrupts while using dedicated edu irq */
|
||||
return IRQ_HANDLED;
|
||||
|
||||
/* no registered edu irq, call handler */
|
||||
return brcmnand_edu_irq(irq, data);
|
||||
}
|
||||
|
||||
complete(&ctrl->done);
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
@ -1644,6 +1783,81 @@ static void brcmnand_write_buf(struct nand_chip *chip, const uint8_t *buf,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kick EDU engine
|
||||
*/
|
||||
static int brcmnand_edu_trans(struct brcmnand_host *host, u64 addr, u32 *buf,
|
||||
u32 len, u8 cmd)
|
||||
{
|
||||
struct brcmnand_controller *ctrl = host->ctrl;
|
||||
unsigned long timeo = msecs_to_jiffies(200);
|
||||
int ret = 0;
|
||||
int dir = (cmd == CMD_PAGE_READ ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
|
||||
u8 edu_cmd = (cmd == CMD_PAGE_READ ? EDU_CMD_READ : EDU_CMD_WRITE);
|
||||
unsigned int trans = len >> FC_SHIFT;
|
||||
dma_addr_t pa;
|
||||
|
||||
pa = dma_map_single(ctrl->dev, buf, len, dir);
|
||||
if (dma_mapping_error(ctrl->dev, pa)) {
|
||||
dev_err(ctrl->dev, "unable to map buffer for EDU DMA\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ctrl->edu_pending = true;
|
||||
ctrl->edu_dram_addr = pa;
|
||||
ctrl->edu_ext_addr = addr;
|
||||
ctrl->edu_cmd = edu_cmd;
|
||||
ctrl->edu_count = trans;
|
||||
|
||||
edu_writel(ctrl, EDU_DRAM_ADDR, (u32)ctrl->edu_dram_addr);
|
||||
edu_readl(ctrl, EDU_DRAM_ADDR);
|
||||
edu_writel(ctrl, EDU_EXT_ADDR, ctrl->edu_ext_addr);
|
||||
edu_readl(ctrl, EDU_EXT_ADDR);
|
||||
edu_writel(ctrl, EDU_LENGTH, FC_BYTES);
|
||||
edu_readl(ctrl, EDU_LENGTH);
|
||||
|
||||
/* Start edu engine */
|
||||
mb(); /* flush previous writes */
|
||||
edu_writel(ctrl, EDU_CMD, ctrl->edu_cmd);
|
||||
edu_readl(ctrl, EDU_CMD);
|
||||
|
||||
if (wait_for_completion_timeout(&ctrl->edu_done, timeo) <= 0) {
|
||||
dev_err(ctrl->dev,
|
||||
"timeout waiting for EDU; status %#x, error status %#x\n",
|
||||
edu_readl(ctrl, EDU_STATUS),
|
||||
edu_readl(ctrl, EDU_ERR_STATUS));
|
||||
}
|
||||
|
||||
dma_unmap_single(ctrl->dev, pa, len, dir);
|
||||
|
||||
/* for program page check NAND status */
|
||||
if (((brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS) &
|
||||
INTFC_FLASH_STATUS) & NAND_STATUS_FAIL) &&
|
||||
edu_cmd == EDU_CMD_WRITE) {
|
||||
dev_info(ctrl->dev, "program failed at %llx\n",
|
||||
(unsigned long long)addr);
|
||||
ret = -EIO;
|
||||
}
|
||||
|
||||
/* Make sure the EDU status is clean */
|
||||
if (edu_readl(ctrl, EDU_STATUS) & EDU_STATUS_ACTIVE)
|
||||
dev_warn(ctrl->dev, "EDU still active: %#x\n",
|
||||
edu_readl(ctrl, EDU_STATUS));
|
||||
|
||||
if (unlikely(edu_readl(ctrl, EDU_ERR_STATUS) & EDU_ERR_STATUS_ERRACK)) {
|
||||
dev_warn(ctrl->dev, "EDU RBUS error at addr %llx\n",
|
||||
(unsigned long long)addr);
|
||||
ret = -EIO;
|
||||
}
|
||||
|
||||
ctrl->edu_pending = false;
|
||||
brcmnand_edu_init(ctrl);
|
||||
edu_writel(ctrl, EDU_STOP, 0); /* force stop */
|
||||
edu_readl(ctrl, EDU_STOP);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a FLASH_DMA descriptor as part of a linked list. You must know the
|
||||
* following ahead of time:
|
||||
@ -1850,9 +2064,11 @@ static int brcmnand_read(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
try_dmaread:
|
||||
brcmnand_clear_ecc_addr(ctrl);
|
||||
|
||||
if (has_flash_dma(ctrl) && !oob && flash_dma_buf_ok(buf)) {
|
||||
err = brcmnand_dma_trans(host, addr, buf, trans * FC_BYTES,
|
||||
CMD_PAGE_READ);
|
||||
if (ctrl->dma_trans && !oob && flash_dma_buf_ok(buf)) {
|
||||
err = ctrl->dma_trans(host, addr, buf,
|
||||
trans * FC_BYTES,
|
||||
CMD_PAGE_READ);
|
||||
|
||||
if (err) {
|
||||
if (mtd_is_bitflip_or_eccerr(err))
|
||||
err_addr = addr;
|
||||
@ -1988,10 +2204,12 @@ static int brcmnand_write(struct mtd_info *mtd, struct nand_chip *chip,
|
||||
for (i = 0; i < ctrl->max_oob; i += 4)
|
||||
oob_reg_write(ctrl, i, 0xffffffff);
|
||||
|
||||
if (has_flash_dma(ctrl) && !oob && flash_dma_buf_ok(buf)) {
|
||||
if (brcmnand_dma_trans(host, addr, (u32 *)buf,
|
||||
mtd->writesize, CMD_PROGRAM_PAGE))
|
||||
if (use_dma(ctrl) && !oob && flash_dma_buf_ok(buf)) {
|
||||
if (ctrl->dma_trans(host, addr, (u32 *)buf, mtd->writesize,
|
||||
CMD_PROGRAM_PAGE))
|
||||
|
||||
ret = -EIO;
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
@ -2494,6 +2712,8 @@ static int brcmnand_suspend(struct device *dev)
|
||||
|
||||
if (has_flash_dma(ctrl))
|
||||
ctrl->flash_dma_mode = flash_dma_readl(ctrl, FLASH_DMA_MODE);
|
||||
else if (has_edu(ctrl))
|
||||
ctrl->edu_config = edu_readl(ctrl, EDU_CONFIG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -2508,6 +2728,14 @@ static int brcmnand_resume(struct device *dev)
|
||||
flash_dma_writel(ctrl, FLASH_DMA_ERROR_STATUS, 0);
|
||||
}
|
||||
|
||||
if (has_edu(ctrl))
|
||||
ctrl->edu_config = edu_readl(ctrl, EDU_CONFIG);
|
||||
else {
|
||||
edu_writel(ctrl, EDU_CONFIG, ctrl->edu_config);
|
||||
edu_readl(ctrl, EDU_CONFIG);
|
||||
brcmnand_edu_init(ctrl);
|
||||
}
|
||||
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CS_SELECT, ctrl->nand_cs_nand_select);
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CS_XOR, ctrl->nand_cs_nand_xor);
|
||||
brcmnand_write_reg(ctrl, BRCMNAND_CORR_THRESHOLD,
|
||||
@ -2553,6 +2781,49 @@ MODULE_DEVICE_TABLE(of, brcmnand_of_match);
|
||||
/***********************************************************************
|
||||
* Platform driver setup (per controller)
|
||||
***********************************************************************/
|
||||
static int brcmnand_edu_setup(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct brcmnand_controller *ctrl = dev_get_drvdata(&pdev->dev);
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "flash-edu");
|
||||
if (res) {
|
||||
ctrl->edu_base = devm_ioremap_resource(dev, res);
|
||||
if (IS_ERR(ctrl->edu_base))
|
||||
return PTR_ERR(ctrl->edu_base);
|
||||
|
||||
ctrl->edu_offsets = edu_regs;
|
||||
|
||||
edu_writel(ctrl, EDU_CONFIG, EDU_CONFIG_MODE_NAND |
|
||||
EDU_CONFIG_SWAP_CFG);
|
||||
edu_readl(ctrl, EDU_CONFIG);
|
||||
|
||||
/* initialize edu */
|
||||
brcmnand_edu_init(ctrl);
|
||||
|
||||
ctrl->edu_irq = platform_get_irq_optional(pdev, 1);
|
||||
if (ctrl->edu_irq < 0) {
|
||||
dev_warn(dev,
|
||||
"FLASH EDU enabled, using ctlrdy irq\n");
|
||||
} else {
|
||||
ret = devm_request_irq(dev, ctrl->edu_irq,
|
||||
brcmnand_edu_irq, 0,
|
||||
"brcmnand-edu", ctrl);
|
||||
if (ret < 0) {
|
||||
dev_err(ctrl->dev, "can't allocate IRQ %d: error %d\n",
|
||||
ctrl->edu_irq, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dev_info(dev, "FLASH EDU enabled using irq %u\n",
|
||||
ctrl->edu_irq);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
|
||||
{
|
||||
@ -2578,6 +2849,7 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
|
||||
|
||||
init_completion(&ctrl->done);
|
||||
init_completion(&ctrl->dma_done);
|
||||
init_completion(&ctrl->edu_done);
|
||||
nand_controller_init(&ctrl->controller);
|
||||
ctrl->controller.ops = &brcmnand_controller_ops;
|
||||
INIT_LIST_HEAD(&ctrl->host_list);
|
||||
@ -2675,6 +2947,15 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
|
||||
}
|
||||
|
||||
dev_info(dev, "enabling FLASH_DMA\n");
|
||||
/* set flash dma transfer function to call */
|
||||
ctrl->dma_trans = brcmnand_dma_trans;
|
||||
} else {
|
||||
ret = brcmnand_edu_setup(pdev);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
/* set edu transfer function to call */
|
||||
ctrl->dma_trans = brcmnand_edu_trans;
|
||||
}
|
||||
|
||||
/* Disable automatic device ID config, direct addressing */
|
||||
|
@ -30,7 +30,6 @@
|
||||
* Generic mode is used for executing rest of commands.
|
||||
*/
|
||||
|
||||
#define MAX_OOB_SIZE_PER_SECTOR 32
|
||||
#define MAX_ADDRESS_CYC 6
|
||||
#define MAX_ERASE_ADDRESS_CYC 3
|
||||
#define MAX_DATA_SIZE 0xFFFC
|
||||
@ -190,6 +189,7 @@
|
||||
|
||||
/* BCH Engine identification register 3. */
|
||||
#define BCH_CFG_3 0x844
|
||||
#define BCH_CFG_3_METADATA_SIZE GENMASK(23, 16)
|
||||
|
||||
/* Ready/Busy# line status. */
|
||||
#define RBN_SETINGS 0x1004
|
||||
@ -499,6 +499,7 @@ struct cdns_nand_ctrl {
|
||||
|
||||
unsigned long assigned_cs;
|
||||
struct list_head chips;
|
||||
u8 bch_metadata_size;
|
||||
};
|
||||
|
||||
struct cdns_nand_chip {
|
||||
@ -997,6 +998,7 @@ static int cadence_nand_cdma_send(struct cdns_nand_ctrl *cdns_ctrl,
|
||||
return status;
|
||||
|
||||
cadence_nand_reset_irq(cdns_ctrl);
|
||||
reinit_completion(&cdns_ctrl->complete);
|
||||
|
||||
writel_relaxed((u32)cdns_ctrl->dma_cdma_desc,
|
||||
cdns_ctrl->reg + CMD_REG2);
|
||||
@ -1077,6 +1079,14 @@ static int cadence_nand_read_bch_caps(struct cdns_nand_ctrl *cdns_ctrl)
|
||||
int max_step_size = 0, nstrengths, i;
|
||||
u32 reg;
|
||||
|
||||
reg = readl_relaxed(cdns_ctrl->reg + BCH_CFG_3);
|
||||
cdns_ctrl->bch_metadata_size = FIELD_GET(BCH_CFG_3_METADATA_SIZE, reg);
|
||||
if (cdns_ctrl->bch_metadata_size < 4) {
|
||||
dev_err(cdns_ctrl->dev,
|
||||
"Driver needs at least 4 bytes of BCH meta data\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
reg = readl_relaxed(cdns_ctrl->reg + BCH_CFG_0);
|
||||
cdns_ctrl->ecc_strengths[0] = FIELD_GET(BCH_CFG_0_CORR_CAP_0, reg);
|
||||
cdns_ctrl->ecc_strengths[1] = FIELD_GET(BCH_CFG_0_CORR_CAP_1, reg);
|
||||
@ -1170,7 +1180,8 @@ static int cadence_nand_hw_init(struct cdns_nand_ctrl *cdns_ctrl)
|
||||
writel_relaxed(0xFFFFFFFF, cdns_ctrl->reg + INTR_STATUS);
|
||||
|
||||
cadence_nand_get_caps(cdns_ctrl);
|
||||
cadence_nand_read_bch_caps(cdns_ctrl);
|
||||
if (cadence_nand_read_bch_caps(cdns_ctrl))
|
||||
return -EIO;
|
||||
|
||||
/*
|
||||
* Set IO width access to 8.
|
||||
@ -2585,9 +2596,8 @@ int cadence_nand_attach_chip(struct nand_chip *chip)
|
||||
{
|
||||
struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller);
|
||||
struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip);
|
||||
u32 ecc_size = cdns_chip->sector_count * chip->ecc.bytes;
|
||||
u32 ecc_size;
|
||||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
u32 max_oob_data_size;
|
||||
int ret;
|
||||
|
||||
if (chip->options & NAND_BUSWIDTH_16) {
|
||||
@ -2603,12 +2613,9 @@ int cadence_nand_attach_chip(struct nand_chip *chip)
|
||||
chip->options |= NAND_NO_SUBPAGE_WRITE;
|
||||
|
||||
cdns_chip->bbm_offs = chip->badblockpos;
|
||||
if (chip->options & NAND_BUSWIDTH_16) {
|
||||
cdns_chip->bbm_offs &= ~0x01;
|
||||
cdns_chip->bbm_len = 2;
|
||||
} else {
|
||||
cdns_chip->bbm_len = 1;
|
||||
}
|
||||
cdns_chip->bbm_offs &= ~0x01;
|
||||
/* this value should be even number */
|
||||
cdns_chip->bbm_len = 2;
|
||||
|
||||
ret = nand_ecc_choose_conf(chip,
|
||||
&cdns_ctrl->ecc_caps,
|
||||
@ -2625,13 +2632,12 @@ int cadence_nand_attach_chip(struct nand_chip *chip)
|
||||
/* Error correction configuration. */
|
||||
cdns_chip->sector_size = chip->ecc.size;
|
||||
cdns_chip->sector_count = mtd->writesize / cdns_chip->sector_size;
|
||||
ecc_size = cdns_chip->sector_count * chip->ecc.bytes;
|
||||
|
||||
cdns_chip->avail_oob_size = mtd->oobsize - ecc_size;
|
||||
|
||||
max_oob_data_size = MAX_OOB_SIZE_PER_SECTOR;
|
||||
|
||||
if (cdns_chip->avail_oob_size > max_oob_data_size)
|
||||
cdns_chip->avail_oob_size = max_oob_data_size;
|
||||
if (cdns_chip->avail_oob_size > cdns_ctrl->bch_metadata_size)
|
||||
cdns_chip->avail_oob_size = cdns_ctrl->bch_metadata_size;
|
||||
|
||||
if ((cdns_chip->avail_oob_size + cdns_chip->bbm_len + ecc_size)
|
||||
> mtd->oobsize)
|
||||
|
@ -1317,6 +1317,7 @@ int denali_init(struct denali_controller *denali)
|
||||
iowrite32(CHIP_EN_DONT_CARE__FLAG, denali->reg + CHIP_ENABLE_DONT_CARE);
|
||||
iowrite32(ECC_ENABLE__FLAG, denali->reg + ECC_ENABLE);
|
||||
iowrite32(0xffff, denali->reg + SPARE_AREA_MARKER);
|
||||
iowrite32(WRITE_PROTECT__FLAG, denali->reg + WRITE_PROTECT);
|
||||
|
||||
denali_clear_irq_all(denali);
|
||||
|
||||
|
@ -328,7 +328,7 @@ struct denali_chip {
|
||||
struct nand_chip chip;
|
||||
struct list_head node;
|
||||
unsigned int nsels;
|
||||
struct denali_chip_sel sels[0];
|
||||
struct denali_chip_sel sels[];
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1169,7 +1169,7 @@ static inline int __init inftl_partscan(struct mtd_info *mtd, struct mtd_partiti
|
||||
" NoOfBootImageBlocks = %d\n"
|
||||
" NoOfBinaryPartitions = %d\n"
|
||||
" NoOfBDTLPartitions = %d\n"
|
||||
" BlockMultiplerBits = %d\n"
|
||||
" BlockMultiplierBits = %d\n"
|
||||
" FormatFlgs = %d\n"
|
||||
" OsakVersion = %d.%d.%d.%d\n"
|
||||
" PercentUsed = %d\n",
|
||||
@ -1482,7 +1482,7 @@ static int __init doc_probe(unsigned long physadr)
|
||||
break;
|
||||
case DOC_ChipID_DocMilPlus32:
|
||||
pr_err("DiskOnChip Millennium Plus 32MB is not supported, ignoring.\n");
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
default:
|
||||
ret = -ENODEV;
|
||||
goto notfound;
|
||||
|
@ -324,8 +324,7 @@ static void fsl_elbc_cmdfunc(struct nand_chip *chip, unsigned int command,
|
||||
/* READ0 and READ1 read the entire buffer to use hardware ECC. */
|
||||
case NAND_CMD_READ1:
|
||||
column += 256;
|
||||
|
||||
/* fall-through */
|
||||
fallthrough;
|
||||
case NAND_CMD_READ0:
|
||||
dev_dbg(priv->dev,
|
||||
"fsl_elbc_cmdfunc: NAND_CMD_READ0, page_addr:"
|
||||
|
@ -1148,20 +1148,21 @@ static int acquire_dma_channels(struct gpmi_nand_data *this)
|
||||
{
|
||||
struct platform_device *pdev = this->pdev;
|
||||
struct dma_chan *dma_chan;
|
||||
int ret = 0;
|
||||
|
||||
/* request dma channel */
|
||||
dma_chan = dma_request_slave_channel(&pdev->dev, "rx-tx");
|
||||
if (!dma_chan) {
|
||||
dev_err(this->dev, "Failed to request DMA channel.\n");
|
||||
goto acquire_err;
|
||||
dma_chan = dma_request_chan(&pdev->dev, "rx-tx");
|
||||
if (IS_ERR(dma_chan)) {
|
||||
ret = PTR_ERR(dma_chan);
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(this->dev, "DMA channel request failed: %d\n",
|
||||
ret);
|
||||
release_dma_channels(this);
|
||||
} else {
|
||||
this->dma_chans[0] = dma_chan;
|
||||
}
|
||||
|
||||
this->dma_chans[0] = dma_chan;
|
||||
return 0;
|
||||
|
||||
acquire_err:
|
||||
release_dma_channels(this);
|
||||
return -EINVAL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int gpmi_get_clks(struct gpmi_nand_data *this)
|
||||
|
@ -1,6 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config MTD_NAND_JZ4780
|
||||
tristate "JZ4780 NAND controller"
|
||||
depends on MIPS || COMPILE_TEST
|
||||
depends on JZ4780_NEMC
|
||||
help
|
||||
Enables support for NAND Flash connected to the NEMC on JZ4780 SoC
|
||||
|
@ -124,7 +124,6 @@ int ingenic_ecc_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct ingenic_ecc *ecc;
|
||||
struct resource *res;
|
||||
|
||||
ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL);
|
||||
if (!ecc)
|
||||
@ -134,8 +133,7 @@ int ingenic_ecc_probe(struct platform_device *pdev)
|
||||
if (!ecc->ops)
|
||||
return -EINVAL;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
ecc->base = devm_ioremap_resource(dev, res);
|
||||
ecc->base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(ecc->base))
|
||||
return PTR_ERR(ecc->base);
|
||||
|
||||
|
@ -253,7 +253,7 @@ static int ingenic_nand_attach_chip(struct nand_chip *chip)
|
||||
chip->ecc.hwctl = ingenic_nand_ecc_hwctl;
|
||||
chip->ecc.calculate = ingenic_nand_ecc_calculate;
|
||||
chip->ecc.correct = ingenic_nand_ecc_correct;
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case NAND_ECC_SOFT:
|
||||
dev_info(nfc->dev, "using %s (strength %d, size %d, bytes %d)\n",
|
||||
(nfc->ecc) ? "hardware ECC" : "software ECC",
|
||||
|
@ -145,10 +145,10 @@ static void jz4725b_bch_read_parity(struct ingenic_ecc *bch, u8 *buf,
|
||||
switch (size8) {
|
||||
case 3:
|
||||
dest8[2] = (val >> 16) & 0xff;
|
||||
/* fall-through */
|
||||
fallthrough;
|
||||
case 2:
|
||||
dest8[1] = (val >> 8) & 0xff;
|
||||
/* fall-through */
|
||||
fallthrough;
|
||||
case 1:
|
||||
dest8[0] = val & 0xff;
|
||||
break;
|
||||
|
@ -123,10 +123,10 @@ static void jz4780_bch_read_parity(struct ingenic_ecc *bch, void *buf,
|
||||
switch (size8) {
|
||||
case 3:
|
||||
dest8[2] = (val >> 16) & 0xff;
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 2:
|
||||
dest8[1] = (val >> 8) & 0xff;
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 1:
|
||||
dest8[0] = val & 0xff;
|
||||
break;
|
||||
|
@ -30,6 +30,7 @@
|
||||
#define NAND_MFR_SAMSUNG 0xec
|
||||
#define NAND_MFR_SANDISK 0x45
|
||||
#define NAND_MFR_STMICRO 0x20
|
||||
/* Kioxia is new name of Toshiba memory. */
|
||||
#define NAND_MFR_TOSHIBA 0x98
|
||||
#define NAND_MFR_WINBOND 0xef
|
||||
|
||||
|
@ -334,7 +334,7 @@ struct marvell_nand_chip {
|
||||
int addr_cyc;
|
||||
int selected_die;
|
||||
unsigned int nsels;
|
||||
struct marvell_nand_chip_sel sels[0];
|
||||
struct marvell_nand_chip_sel sels[];
|
||||
};
|
||||
|
||||
static inline struct marvell_nand_chip *to_marvell_nand(struct nand_chip *chip)
|
||||
@ -2743,16 +2743,21 @@ static int marvell_nfc_init_dma(struct marvell_nfc *nfc)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
nfc->dma_chan = dma_request_slave_channel(nfc->dev, "data");
|
||||
if (!nfc->dma_chan) {
|
||||
dev_err(nfc->dev,
|
||||
"Unable to request data DMA channel\n");
|
||||
return -ENODEV;
|
||||
nfc->dma_chan = dma_request_chan(nfc->dev, "data");
|
||||
if (IS_ERR(nfc->dma_chan)) {
|
||||
ret = PTR_ERR(nfc->dma_chan);
|
||||
nfc->dma_chan = NULL;
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(nfc->dev, "DMA channel request failed: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!r)
|
||||
return -ENXIO;
|
||||
if (!r) {
|
||||
ret = -ENXIO;
|
||||
goto release_channel;
|
||||
}
|
||||
|
||||
config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
|
||||
@ -2763,7 +2768,7 @@ static int marvell_nfc_init_dma(struct marvell_nfc *nfc)
|
||||
ret = dmaengine_slave_config(nfc->dma_chan, &config);
|
||||
if (ret < 0) {
|
||||
dev_err(nfc->dev, "Failed to configure DMA channel\n");
|
||||
return ret;
|
||||
goto release_channel;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2773,12 +2778,20 @@ static int marvell_nfc_init_dma(struct marvell_nfc *nfc)
|
||||
* the provided buffer.
|
||||
*/
|
||||
nfc->dma_buf = kmalloc(MAX_CHUNK_SIZE, GFP_KERNEL | GFP_DMA);
|
||||
if (!nfc->dma_buf)
|
||||
return -ENOMEM;
|
||||
if (!nfc->dma_buf) {
|
||||
ret = -ENOMEM;
|
||||
goto release_channel;
|
||||
}
|
||||
|
||||
nfc->use_dma = true;
|
||||
|
||||
return 0;
|
||||
|
||||
release_channel:
|
||||
dma_release_channel(nfc->dma_chan);
|
||||
nfc->dma_chan = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void marvell_nfc_reset(struct marvell_nfc *nfc)
|
||||
@ -2920,10 +2933,13 @@ static int marvell_nfc_probe(struct platform_device *pdev)
|
||||
|
||||
ret = marvell_nand_chips_init(dev, nfc);
|
||||
if (ret)
|
||||
goto unprepare_reg_clk;
|
||||
goto release_dma;
|
||||
|
||||
return 0;
|
||||
|
||||
release_dma:
|
||||
if (nfc->use_dma)
|
||||
dma_release_channel(nfc->dma_chan);
|
||||
unprepare_reg_clk:
|
||||
clk_disable_unprepare(nfc->reg_clk);
|
||||
unprepare_core_clk:
|
||||
|
@ -118,7 +118,7 @@ struct meson_nfc_nand_chip {
|
||||
u8 *data_buf;
|
||||
__le64 *info_buf;
|
||||
u32 nsels;
|
||||
u8 sels[0];
|
||||
u8 sels[];
|
||||
};
|
||||
|
||||
struct meson_nand_ecc {
|
||||
|
@ -131,7 +131,7 @@ struct mtk_nfc_nand_chip {
|
||||
u32 spare_per_sector;
|
||||
|
||||
int nsels;
|
||||
u8 sels[0];
|
||||
u8 sels[];
|
||||
/* nothing after this field */
|
||||
};
|
||||
|
||||
|
@ -683,7 +683,12 @@ int nand_soft_waitrdy(struct nand_chip *chip, unsigned long timeout_ms)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
timeout_ms = jiffies + msecs_to_jiffies(timeout_ms);
|
||||
/*
|
||||
* +1 below is necessary because if we are now in the last fraction
|
||||
* of jiffy and msecs_to_jiffies is 1 then we will wait only that
|
||||
* small jiffy fraction - possibly leading to false timeout
|
||||
*/
|
||||
timeout_ms = jiffies + msecs_to_jiffies(timeout_ms) + 1;
|
||||
do {
|
||||
ret = nand_read_data_op(chip, &status, sizeof(status), true);
|
||||
if (ret)
|
||||
@ -4321,16 +4326,22 @@ static int nand_block_markbad(struct mtd_info *mtd, loff_t ofs)
|
||||
/**
|
||||
* nand_suspend - [MTD Interface] Suspend the NAND flash
|
||||
* @mtd: MTD device structure
|
||||
*
|
||||
* Returns 0 for success or negative error code otherwise.
|
||||
*/
|
||||
static int nand_suspend(struct mtd_info *mtd)
|
||||
{
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
chip->suspended = 1;
|
||||
if (chip->suspend)
|
||||
ret = chip->suspend(chip);
|
||||
if (!ret)
|
||||
chip->suspended = 1;
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -4342,11 +4353,14 @@ static void nand_resume(struct mtd_info *mtd)
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
if (chip->suspended)
|
||||
if (chip->suspended) {
|
||||
if (chip->resume)
|
||||
chip->resume(chip);
|
||||
chip->suspended = 0;
|
||||
else
|
||||
} else {
|
||||
pr_err("%s called for a chip which is not in suspended state\n",
|
||||
__func__);
|
||||
}
|
||||
mutex_unlock(&chip->lock);
|
||||
}
|
||||
|
||||
@ -4360,6 +4374,38 @@ static void nand_shutdown(struct mtd_info *mtd)
|
||||
nand_suspend(mtd);
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_lock - [MTD Interface] Lock the NAND flash
|
||||
* @mtd: MTD device structure
|
||||
* @ofs: offset byte address
|
||||
* @len: number of bytes to lock (must be a multiple of block/page size)
|
||||
*/
|
||||
static int nand_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
|
||||
if (!chip->lock_area)
|
||||
return -ENOTSUPP;
|
||||
|
||||
return chip->lock_area(chip, ofs, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* nand_unlock - [MTD Interface] Unlock the NAND flash
|
||||
* @mtd: MTD device structure
|
||||
* @ofs: offset byte address
|
||||
* @len: number of bytes to unlock (must be a multiple of block/page size)
|
||||
*/
|
||||
static int nand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
|
||||
{
|
||||
struct nand_chip *chip = mtd_to_nand(mtd);
|
||||
|
||||
if (!chip->unlock_area)
|
||||
return -ENOTSUPP;
|
||||
|
||||
return chip->unlock_area(chip, ofs, len);
|
||||
}
|
||||
|
||||
/* Set default functions */
|
||||
static void nand_set_defaults(struct nand_chip *chip)
|
||||
{
|
||||
@ -5591,8 +5637,7 @@ static int nand_scan_tail(struct nand_chip *chip)
|
||||
}
|
||||
if (!ecc->read_page)
|
||||
ecc->read_page = nand_read_page_hwecc_oob_first;
|
||||
/* fall through */
|
||||
|
||||
fallthrough;
|
||||
case NAND_ECC_HW:
|
||||
/* Use standard hwecc read page function? */
|
||||
if (!ecc->read_page)
|
||||
@ -5611,8 +5656,7 @@ static int nand_scan_tail(struct nand_chip *chip)
|
||||
ecc->read_subpage = nand_read_subpage;
|
||||
if (!ecc->write_subpage && ecc->hwctl && ecc->calculate)
|
||||
ecc->write_subpage = nand_write_subpage_hwecc;
|
||||
/* fall through */
|
||||
|
||||
fallthrough;
|
||||
case NAND_ECC_HW_SYNDROME:
|
||||
if ((!ecc->calculate || !ecc->correct || !ecc->hwctl) &&
|
||||
(!ecc->read_page ||
|
||||
@ -5649,8 +5693,7 @@ static int nand_scan_tail(struct nand_chip *chip)
|
||||
ecc->size, mtd->writesize);
|
||||
ecc->mode = NAND_ECC_SOFT;
|
||||
ecc->algo = NAND_ECC_HAMMING;
|
||||
/* fall through */
|
||||
|
||||
fallthrough;
|
||||
case NAND_ECC_SOFT:
|
||||
ret = nand_set_ecc_soft_ops(chip);
|
||||
if (ret) {
|
||||
@ -5786,8 +5829,8 @@ static int nand_scan_tail(struct nand_chip *chip)
|
||||
mtd->_read_oob = nand_read_oob;
|
||||
mtd->_write_oob = nand_write_oob;
|
||||
mtd->_sync = nand_sync;
|
||||
mtd->_lock = NULL;
|
||||
mtd->_unlock = NULL;
|
||||
mtd->_lock = nand_lock;
|
||||
mtd->_unlock = nand_unlock;
|
||||
mtd->_suspend = nand_suspend;
|
||||
mtd->_resume = nand_resume;
|
||||
mtd->_reboot = nand_shutdown;
|
||||
@ -5907,6 +5950,8 @@ void nand_cleanup(struct nand_chip *chip)
|
||||
chip->ecc.algo == NAND_ECC_BCH)
|
||||
nand_bch_free((struct nand_bch_control *)chip->ecc.priv);
|
||||
|
||||
nanddev_cleanup(&chip->base);
|
||||
|
||||
/* Free bad block table memory */
|
||||
kfree(chip->bbt);
|
||||
kfree(chip->data_buf);
|
||||
|
@ -26,7 +26,7 @@
|
||||
struct hynix_read_retry {
|
||||
int nregs;
|
||||
const u8 *regs;
|
||||
u8 values[0];
|
||||
u8 values[];
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -331,8 +331,7 @@ static void nand_command(struct nand_chip *chip, unsigned int command,
|
||||
*/
|
||||
if (column == -1 && page_addr == -1)
|
||||
return;
|
||||
/* fall through */
|
||||
|
||||
fallthrough;
|
||||
default:
|
||||
/*
|
||||
* If we don't have access to the busy pin, we apply the given
|
||||
@ -483,8 +482,7 @@ static void nand_command_lp(struct nand_chip *chip, unsigned int command,
|
||||
NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
|
||||
chip->legacy.cmd_ctrl(chip, NAND_CMD_NONE,
|
||||
NAND_NCE | NAND_CTRL_CHANGE);
|
||||
|
||||
/* fall through - This applies to read commands */
|
||||
fallthrough; /* This applies to read commands */
|
||||
default:
|
||||
/*
|
||||
* If we don't have access to the busy pin, we apply the given
|
||||
|
@ -6,11 +6,31 @@
|
||||
* Author: Boris Brezillon <boris.brezillon@free-electrons.com>
|
||||
*/
|
||||
|
||||
#include "linux/delay.h"
|
||||
#include "internals.h"
|
||||
|
||||
#define MACRONIX_READ_RETRY_BIT BIT(0)
|
||||
#define MACRONIX_NUM_READ_RETRY_MODES 6
|
||||
|
||||
#define ONFI_FEATURE_ADDR_MXIC_PROTECTION 0xA0
|
||||
#define MXIC_BLOCK_PROTECTION_ALL_LOCK 0x38
|
||||
#define MXIC_BLOCK_PROTECTION_ALL_UNLOCK 0x0
|
||||
|
||||
#define ONFI_FEATURE_ADDR_MXIC_RANDOMIZER 0xB0
|
||||
#define MACRONIX_RANDOMIZER_BIT BIT(1)
|
||||
#define MACRONIX_RANDOMIZER_ENPGM BIT(0)
|
||||
#define MACRONIX_RANDOMIZER_RANDEN BIT(1)
|
||||
#define MACRONIX_RANDOMIZER_RANDOPT BIT(2)
|
||||
#define MACRONIX_RANDOMIZER_MODE_ENTER \
|
||||
(MACRONIX_RANDOMIZER_ENPGM | \
|
||||
MACRONIX_RANDOMIZER_RANDEN | \
|
||||
MACRONIX_RANDOMIZER_RANDOPT)
|
||||
#define MACRONIX_RANDOMIZER_MODE_EXIT \
|
||||
(MACRONIX_RANDOMIZER_RANDEN | \
|
||||
MACRONIX_RANDOMIZER_RANDOPT)
|
||||
|
||||
#define MXIC_CMD_POWER_DOWN 0xB9
|
||||
|
||||
struct nand_onfi_vendor_macronix {
|
||||
u8 reserved;
|
||||
u8 reliability_func;
|
||||
@ -29,15 +49,83 @@ static int macronix_nand_setup_read_retry(struct nand_chip *chip, int mode)
|
||||
return nand_set_features(chip, ONFI_FEATURE_ADDR_READ_RETRY, feature);
|
||||
}
|
||||
|
||||
static int macronix_nand_randomizer_check_enable(struct nand_chip *chip)
|
||||
{
|
||||
u8 feature[ONFI_SUBFEATURE_PARAM_LEN];
|
||||
int ret;
|
||||
|
||||
ret = nand_get_features(chip, ONFI_FEATURE_ADDR_MXIC_RANDOMIZER,
|
||||
feature);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (feature[0])
|
||||
return feature[0];
|
||||
|
||||
feature[0] = MACRONIX_RANDOMIZER_MODE_ENTER;
|
||||
ret = nand_set_features(chip, ONFI_FEATURE_ADDR_MXIC_RANDOMIZER,
|
||||
feature);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* RANDEN and RANDOPT OTP bits are programmed */
|
||||
feature[0] = 0x0;
|
||||
ret = nand_prog_page_op(chip, 0, 0, feature, 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = nand_get_features(chip, ONFI_FEATURE_ADDR_MXIC_RANDOMIZER,
|
||||
feature);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
feature[0] &= MACRONIX_RANDOMIZER_MODE_EXIT;
|
||||
ret = nand_set_features(chip, ONFI_FEATURE_ADDR_MXIC_RANDOMIZER,
|
||||
feature);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void macronix_nand_onfi_init(struct nand_chip *chip)
|
||||
{
|
||||
struct nand_parameters *p = &chip->parameters;
|
||||
struct nand_onfi_vendor_macronix *mxic;
|
||||
struct device_node *dn = nand_get_flash_node(chip);
|
||||
int rand_otp = 0;
|
||||
int ret;
|
||||
|
||||
if (!p->onfi)
|
||||
return;
|
||||
|
||||
if (of_find_property(dn, "mxic,enable-randomizer-otp", NULL))
|
||||
rand_otp = 1;
|
||||
|
||||
mxic = (struct nand_onfi_vendor_macronix *)p->onfi->vendor;
|
||||
/* Subpage write is prohibited in randomizer operatoin */
|
||||
if (rand_otp && chip->options & NAND_NO_SUBPAGE_WRITE &&
|
||||
mxic->reliability_func & MACRONIX_RANDOMIZER_BIT) {
|
||||
if (p->supports_set_get_features) {
|
||||
bitmap_set(p->set_feature_list,
|
||||
ONFI_FEATURE_ADDR_MXIC_RANDOMIZER, 1);
|
||||
bitmap_set(p->get_feature_list,
|
||||
ONFI_FEATURE_ADDR_MXIC_RANDOMIZER, 1);
|
||||
ret = macronix_nand_randomizer_check_enable(chip);
|
||||
if (ret < 0) {
|
||||
bitmap_clear(p->set_feature_list,
|
||||
ONFI_FEATURE_ADDR_MXIC_RANDOMIZER,
|
||||
1);
|
||||
bitmap_clear(p->get_feature_list,
|
||||
ONFI_FEATURE_ADDR_MXIC_RANDOMIZER,
|
||||
1);
|
||||
pr_info("Macronix NAND randomizer failed\n");
|
||||
} else {
|
||||
pr_info("Macronix NAND randomizer enabled\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((mxic->reliability_func & MACRONIX_READ_RETRY_BIT) == 0)
|
||||
return;
|
||||
|
||||
@ -91,6 +179,143 @@ static void macronix_nand_fix_broken_get_timings(struct nand_chip *chip)
|
||||
ONFI_FEATURE_ADDR_TIMING_MODE, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Macronix NAND supports Block Protection by Protectoin(PT) pin;
|
||||
* active high at power-on which protects the entire chip even the #WP is
|
||||
* disabled. Lock/unlock protection area can be partition according to
|
||||
* protection bits, i.e. upper 1/2 locked, upper 1/4 locked and so on.
|
||||
*/
|
||||
static int mxic_nand_lock(struct nand_chip *chip, loff_t ofs, uint64_t len)
|
||||
{
|
||||
u8 feature[ONFI_SUBFEATURE_PARAM_LEN];
|
||||
int ret;
|
||||
|
||||
feature[0] = MXIC_BLOCK_PROTECTION_ALL_LOCK;
|
||||
nand_select_target(chip, 0);
|
||||
ret = nand_set_features(chip, ONFI_FEATURE_ADDR_MXIC_PROTECTION,
|
||||
feature);
|
||||
nand_deselect_target(chip);
|
||||
if (ret)
|
||||
pr_err("%s all blocks failed\n", __func__);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mxic_nand_unlock(struct nand_chip *chip, loff_t ofs, uint64_t len)
|
||||
{
|
||||
u8 feature[ONFI_SUBFEATURE_PARAM_LEN];
|
||||
int ret;
|
||||
|
||||
feature[0] = MXIC_BLOCK_PROTECTION_ALL_UNLOCK;
|
||||
nand_select_target(chip, 0);
|
||||
ret = nand_set_features(chip, ONFI_FEATURE_ADDR_MXIC_PROTECTION,
|
||||
feature);
|
||||
nand_deselect_target(chip);
|
||||
if (ret)
|
||||
pr_err("%s all blocks failed\n", __func__);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void macronix_nand_block_protection_support(struct nand_chip *chip)
|
||||
{
|
||||
u8 feature[ONFI_SUBFEATURE_PARAM_LEN];
|
||||
int ret;
|
||||
|
||||
bitmap_set(chip->parameters.get_feature_list,
|
||||
ONFI_FEATURE_ADDR_MXIC_PROTECTION, 1);
|
||||
|
||||
feature[0] = MXIC_BLOCK_PROTECTION_ALL_UNLOCK;
|
||||
nand_select_target(chip, 0);
|
||||
ret = nand_get_features(chip, ONFI_FEATURE_ADDR_MXIC_PROTECTION,
|
||||
feature);
|
||||
nand_deselect_target(chip);
|
||||
if (ret || feature[0] != MXIC_BLOCK_PROTECTION_ALL_LOCK) {
|
||||
if (ret)
|
||||
pr_err("Block protection check failed\n");
|
||||
|
||||
bitmap_clear(chip->parameters.get_feature_list,
|
||||
ONFI_FEATURE_ADDR_MXIC_PROTECTION, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
bitmap_set(chip->parameters.set_feature_list,
|
||||
ONFI_FEATURE_ADDR_MXIC_PROTECTION, 1);
|
||||
|
||||
chip->lock_area = mxic_nand_lock;
|
||||
chip->unlock_area = mxic_nand_unlock;
|
||||
}
|
||||
|
||||
static int nand_power_down_op(struct nand_chip *chip)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (nand_has_exec_op(chip)) {
|
||||
struct nand_op_instr instrs[] = {
|
||||
NAND_OP_CMD(MXIC_CMD_POWER_DOWN, 0),
|
||||
};
|
||||
|
||||
struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs);
|
||||
|
||||
ret = nand_exec_op(chip, &op);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
} else {
|
||||
chip->legacy.cmdfunc(chip, MXIC_CMD_POWER_DOWN, -1, -1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mxic_nand_suspend(struct nand_chip *chip)
|
||||
{
|
||||
int ret;
|
||||
|
||||
nand_select_target(chip, 0);
|
||||
ret = nand_power_down_op(chip);
|
||||
if (ret < 0)
|
||||
pr_err("Suspending MXIC NAND chip failed (%d)\n", ret);
|
||||
nand_deselect_target(chip);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void mxic_nand_resume(struct nand_chip *chip)
|
||||
{
|
||||
/*
|
||||
* Toggle #CS pin to resume NAND device and don't care
|
||||
* of the others CLE, #WE, #RE pins status.
|
||||
* A NAND controller ensure it is able to assert/de-assert #CS
|
||||
* by sending any byte over the NAND bus.
|
||||
* i.e.,
|
||||
* NAND power down command or reset command w/o R/B# status checking.
|
||||
*/
|
||||
nand_select_target(chip, 0);
|
||||
nand_power_down_op(chip);
|
||||
/* The minimum of a recovery time tRDP is 35 us */
|
||||
usleep_range(35, 100);
|
||||
nand_deselect_target(chip);
|
||||
}
|
||||
|
||||
static void macronix_nand_deep_power_down_support(struct nand_chip *chip)
|
||||
{
|
||||
int i;
|
||||
static const char * const deep_power_down_dev[] = {
|
||||
"MX30UF1G28AD",
|
||||
"MX30UF2G28AD",
|
||||
"MX30UF4G28AD",
|
||||
};
|
||||
|
||||
i = match_string(deep_power_down_dev, ARRAY_SIZE(deep_power_down_dev),
|
||||
chip->parameters.model);
|
||||
if (i < 0)
|
||||
return;
|
||||
|
||||
chip->suspend = mxic_nand_suspend;
|
||||
chip->resume = mxic_nand_resume;
|
||||
}
|
||||
|
||||
static int macronix_nand_init(struct nand_chip *chip)
|
||||
{
|
||||
if (nand_is_slc(chip))
|
||||
@ -98,6 +323,8 @@ static int macronix_nand_init(struct nand_chip *chip)
|
||||
|
||||
macronix_nand_fix_broken_get_timings(chip);
|
||||
macronix_nand_onfi_init(chip);
|
||||
macronix_nand_block_protection_support(chip);
|
||||
macronix_nand_deep_power_down_support(chip);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -14,14 +14,68 @@
|
||||
/* Recommended to rewrite for BENAND */
|
||||
#define TOSHIBA_NAND_STATUS_REWRITE_RECOMMENDED BIT(3)
|
||||
|
||||
/* ECC Status Read Command for BENAND */
|
||||
#define TOSHIBA_NAND_CMD_ECC_STATUS_READ 0x7A
|
||||
|
||||
/* ECC Status Mask for BENAND */
|
||||
#define TOSHIBA_NAND_ECC_STATUS_MASK 0x0F
|
||||
|
||||
/* Uncorrectable Error for BENAND */
|
||||
#define TOSHIBA_NAND_ECC_STATUS_UNCORR 0x0F
|
||||
|
||||
/* Max ECC Steps for BENAND */
|
||||
#define TOSHIBA_NAND_MAX_ECC_STEPS 8
|
||||
|
||||
static int toshiba_nand_benand_read_eccstatus_op(struct nand_chip *chip,
|
||||
u8 *buf)
|
||||
{
|
||||
u8 *ecc_status = buf;
|
||||
|
||||
if (nand_has_exec_op(chip)) {
|
||||
const struct nand_sdr_timings *sdr =
|
||||
nand_get_sdr_timings(&chip->data_interface);
|
||||
struct nand_op_instr instrs[] = {
|
||||
NAND_OP_CMD(TOSHIBA_NAND_CMD_ECC_STATUS_READ,
|
||||
PSEC_TO_NSEC(sdr->tADL_min)),
|
||||
NAND_OP_8BIT_DATA_IN(chip->ecc.steps, ecc_status, 0),
|
||||
};
|
||||
struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs);
|
||||
|
||||
return nand_exec_op(chip, &op);
|
||||
}
|
||||
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static int toshiba_nand_benand_eccstatus(struct nand_chip *chip)
|
||||
{
|
||||
struct mtd_info *mtd = nand_to_mtd(chip);
|
||||
int ret;
|
||||
unsigned int max_bitflips = 0;
|
||||
u8 status;
|
||||
u8 status, ecc_status[TOSHIBA_NAND_MAX_ECC_STEPS];
|
||||
|
||||
/* Check Status */
|
||||
ret = toshiba_nand_benand_read_eccstatus_op(chip, ecc_status);
|
||||
if (!ret) {
|
||||
unsigned int i, bitflips = 0;
|
||||
|
||||
for (i = 0; i < chip->ecc.steps; i++) {
|
||||
bitflips = ecc_status[i] & TOSHIBA_NAND_ECC_STATUS_MASK;
|
||||
if (bitflips == TOSHIBA_NAND_ECC_STATUS_UNCORR) {
|
||||
mtd->ecc_stats.failed++;
|
||||
} else {
|
||||
mtd->ecc_stats.corrected += bitflips;
|
||||
max_bitflips = max(max_bitflips, bitflips);
|
||||
}
|
||||
}
|
||||
|
||||
return max_bitflips;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fallback to regular status check if
|
||||
* toshiba_nand_benand_read_eccstatus_op() failed.
|
||||
*/
|
||||
ret = nand_status_op(chip, &status);
|
||||
if (ret)
|
||||
return ret;
|
||||
@ -108,7 +162,7 @@ static void toshiba_nand_decode_id(struct nand_chip *chip)
|
||||
*/
|
||||
if (chip->id.len >= 6 && nand_is_slc(chip) &&
|
||||
(chip->id.data[5] & 0x7) == 0x6 /* 24nm */ &&
|
||||
!(chip->id.data[4] & 0x80) /* !BENAND */) {
|
||||
!(chip->id.data[4] & TOSHIBA_NAND_ID4_IS_BENAND) /* !BENAND */) {
|
||||
memorg->oobsize = 32 * memorg->pagesize >> 9;
|
||||
mtd->oobsize = memorg->oobsize;
|
||||
}
|
||||
|
@ -2251,10 +2251,10 @@ static int __init ns_init_module(void)
|
||||
switch (bbt) {
|
||||
case 2:
|
||||
chip->bbt_options |= NAND_BBT_NO_OOB;
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 1:
|
||||
chip->bbt_options |= NAND_BBT_USE_FLASH;
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
|
@ -455,13 +455,13 @@ static int elm_context_save(struct elm_info *info)
|
||||
ELM_SYNDROME_FRAGMENT_5 + offset);
|
||||
regs->elm_syndrome_fragment_4[i] = elm_read_reg(info,
|
||||
ELM_SYNDROME_FRAGMENT_4 + offset);
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case BCH8_ECC:
|
||||
regs->elm_syndrome_fragment_3[i] = elm_read_reg(info,
|
||||
ELM_SYNDROME_FRAGMENT_3 + offset);
|
||||
regs->elm_syndrome_fragment_2[i] = elm_read_reg(info,
|
||||
ELM_SYNDROME_FRAGMENT_2 + offset);
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case BCH4_ECC:
|
||||
regs->elm_syndrome_fragment_1[i] = elm_read_reg(info,
|
||||
ELM_SYNDROME_FRAGMENT_1 + offset);
|
||||
@ -503,13 +503,13 @@ static int elm_context_restore(struct elm_info *info)
|
||||
regs->elm_syndrome_fragment_5[i]);
|
||||
elm_write_reg(info, ELM_SYNDROME_FRAGMENT_4 + offset,
|
||||
regs->elm_syndrome_fragment_4[i]);
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case BCH8_ECC:
|
||||
elm_write_reg(info, ELM_SYNDROME_FRAGMENT_3 + offset,
|
||||
regs->elm_syndrome_fragment_3[i]);
|
||||
elm_write_reg(info, ELM_SYNDROME_FRAGMENT_2 + offset,
|
||||
regs->elm_syndrome_fragment_2[i]);
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case BCH4_ECC:
|
||||
elm_write_reg(info, ELM_SYNDROME_FRAGMENT_1 + offset,
|
||||
regs->elm_syndrome_fragment_1[i]);
|
||||
|
@ -2628,6 +2628,29 @@ static const struct nand_controller_ops qcom_nandc_ops = {
|
||||
.attach_chip = qcom_nand_attach_chip,
|
||||
};
|
||||
|
||||
static void qcom_nandc_unalloc(struct qcom_nand_controller *nandc)
|
||||
{
|
||||
if (nandc->props->is_bam) {
|
||||
if (!dma_mapping_error(nandc->dev, nandc->reg_read_dma))
|
||||
dma_unmap_single(nandc->dev, nandc->reg_read_dma,
|
||||
MAX_REG_RD *
|
||||
sizeof(*nandc->reg_read_buf),
|
||||
DMA_FROM_DEVICE);
|
||||
|
||||
if (nandc->tx_chan)
|
||||
dma_release_channel(nandc->tx_chan);
|
||||
|
||||
if (nandc->rx_chan)
|
||||
dma_release_channel(nandc->rx_chan);
|
||||
|
||||
if (nandc->cmd_chan)
|
||||
dma_release_channel(nandc->cmd_chan);
|
||||
} else {
|
||||
if (nandc->chan)
|
||||
dma_release_channel(nandc->chan);
|
||||
}
|
||||
}
|
||||
|
||||
static int qcom_nandc_alloc(struct qcom_nand_controller *nandc)
|
||||
{
|
||||
int ret;
|
||||
@ -2673,22 +2696,37 @@ static int qcom_nandc_alloc(struct qcom_nand_controller *nandc)
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
nandc->tx_chan = dma_request_slave_channel(nandc->dev, "tx");
|
||||
if (!nandc->tx_chan) {
|
||||
dev_err(nandc->dev, "failed to request tx channel\n");
|
||||
return -ENODEV;
|
||||
nandc->tx_chan = dma_request_chan(nandc->dev, "tx");
|
||||
if (IS_ERR(nandc->tx_chan)) {
|
||||
ret = PTR_ERR(nandc->tx_chan);
|
||||
nandc->tx_chan = NULL;
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(nandc->dev,
|
||||
"tx DMA channel request failed: %d\n",
|
||||
ret);
|
||||
goto unalloc;
|
||||
}
|
||||
|
||||
nandc->rx_chan = dma_request_slave_channel(nandc->dev, "rx");
|
||||
if (!nandc->rx_chan) {
|
||||
dev_err(nandc->dev, "failed to request rx channel\n");
|
||||
return -ENODEV;
|
||||
nandc->rx_chan = dma_request_chan(nandc->dev, "rx");
|
||||
if (IS_ERR(nandc->rx_chan)) {
|
||||
ret = PTR_ERR(nandc->rx_chan);
|
||||
nandc->rx_chan = NULL;
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(nandc->dev,
|
||||
"rx DMA channel request failed: %d\n",
|
||||
ret);
|
||||
goto unalloc;
|
||||
}
|
||||
|
||||
nandc->cmd_chan = dma_request_slave_channel(nandc->dev, "cmd");
|
||||
if (!nandc->cmd_chan) {
|
||||
dev_err(nandc->dev, "failed to request cmd channel\n");
|
||||
return -ENODEV;
|
||||
nandc->cmd_chan = dma_request_chan(nandc->dev, "cmd");
|
||||
if (IS_ERR(nandc->cmd_chan)) {
|
||||
ret = PTR_ERR(nandc->cmd_chan);
|
||||
nandc->cmd_chan = NULL;
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(nandc->dev,
|
||||
"cmd DMA channel request failed: %d\n",
|
||||
ret);
|
||||
goto unalloc;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -2702,14 +2740,19 @@ static int qcom_nandc_alloc(struct qcom_nand_controller *nandc)
|
||||
if (!nandc->bam_txn) {
|
||||
dev_err(nandc->dev,
|
||||
"failed to allocate bam transaction\n");
|
||||
return -ENOMEM;
|
||||
ret = -ENOMEM;
|
||||
goto unalloc;
|
||||
}
|
||||
} else {
|
||||
nandc->chan = dma_request_slave_channel(nandc->dev, "rxtx");
|
||||
if (!nandc->chan) {
|
||||
dev_err(nandc->dev,
|
||||
"failed to request slave channel\n");
|
||||
return -ENODEV;
|
||||
nandc->chan = dma_request_chan(nandc->dev, "rxtx");
|
||||
if (IS_ERR(nandc->chan)) {
|
||||
ret = PTR_ERR(nandc->chan);
|
||||
nandc->chan = NULL;
|
||||
if (ret != -EPROBE_DEFER)
|
||||
dev_err(nandc->dev,
|
||||
"rxtx DMA channel request failed: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2720,29 +2763,9 @@ static int qcom_nandc_alloc(struct qcom_nand_controller *nandc)
|
||||
nandc->controller.ops = &qcom_nandc_ops;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void qcom_nandc_unalloc(struct qcom_nand_controller *nandc)
|
||||
{
|
||||
if (nandc->props->is_bam) {
|
||||
if (!dma_mapping_error(nandc->dev, nandc->reg_read_dma))
|
||||
dma_unmap_single(nandc->dev, nandc->reg_read_dma,
|
||||
MAX_REG_RD *
|
||||
sizeof(*nandc->reg_read_buf),
|
||||
DMA_FROM_DEVICE);
|
||||
|
||||
if (nandc->tx_chan)
|
||||
dma_release_channel(nandc->tx_chan);
|
||||
|
||||
if (nandc->rx_chan)
|
||||
dma_release_channel(nandc->rx_chan);
|
||||
|
||||
if (nandc->cmd_chan)
|
||||
dma_release_channel(nandc->cmd_chan);
|
||||
} else {
|
||||
if (nandc->chan)
|
||||
dma_release_channel(nandc->chan);
|
||||
}
|
||||
unalloc:
|
||||
qcom_nandc_unalloc(nandc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* one time setup of a few nand controller registers */
|
||||
|
@ -1606,15 +1606,36 @@ static int stm32_fmc2_setup_interface(struct nand_chip *chip, int chipnr,
|
||||
/* DMA configuration */
|
||||
static int stm32_fmc2_dma_setup(struct stm32_fmc2_nfc *fmc2)
|
||||
{
|
||||
int ret;
|
||||
int ret = 0;
|
||||
|
||||
fmc2->dma_tx_ch = dma_request_slave_channel(fmc2->dev, "tx");
|
||||
fmc2->dma_rx_ch = dma_request_slave_channel(fmc2->dev, "rx");
|
||||
fmc2->dma_ecc_ch = dma_request_slave_channel(fmc2->dev, "ecc");
|
||||
fmc2->dma_tx_ch = dma_request_chan(fmc2->dev, "tx");
|
||||
if (IS_ERR(fmc2->dma_tx_ch)) {
|
||||
ret = PTR_ERR(fmc2->dma_tx_ch);
|
||||
if (ret != -ENODEV)
|
||||
dev_err(fmc2->dev,
|
||||
"failed to request tx DMA channel: %d\n", ret);
|
||||
fmc2->dma_tx_ch = NULL;
|
||||
goto err_dma;
|
||||
}
|
||||
|
||||
if (!fmc2->dma_tx_ch || !fmc2->dma_rx_ch || !fmc2->dma_ecc_ch) {
|
||||
dev_warn(fmc2->dev, "DMAs not defined in the device tree, polling mode is used\n");
|
||||
return 0;
|
||||
fmc2->dma_rx_ch = dma_request_chan(fmc2->dev, "rx");
|
||||
if (IS_ERR(fmc2->dma_rx_ch)) {
|
||||
ret = PTR_ERR(fmc2->dma_rx_ch);
|
||||
if (ret != -ENODEV)
|
||||
dev_err(fmc2->dev,
|
||||
"failed to request rx DMA channel: %d\n", ret);
|
||||
fmc2->dma_rx_ch = NULL;
|
||||
goto err_dma;
|
||||
}
|
||||
|
||||
fmc2->dma_ecc_ch = dma_request_chan(fmc2->dev, "ecc");
|
||||
if (IS_ERR(fmc2->dma_ecc_ch)) {
|
||||
ret = PTR_ERR(fmc2->dma_ecc_ch);
|
||||
if (ret != -ENODEV)
|
||||
dev_err(fmc2->dev,
|
||||
"failed to request ecc DMA channel: %d\n", ret);
|
||||
fmc2->dma_ecc_ch = NULL;
|
||||
goto err_dma;
|
||||
}
|
||||
|
||||
ret = sg_alloc_table(&fmc2->dma_ecc_sg, FMC2_MAX_SG, GFP_KERNEL);
|
||||
@ -1635,6 +1656,15 @@ static int stm32_fmc2_dma_setup(struct stm32_fmc2_nfc *fmc2)
|
||||
init_completion(&fmc2->dma_ecc_complete);
|
||||
|
||||
return 0;
|
||||
|
||||
err_dma:
|
||||
if (ret == -ENODEV) {
|
||||
dev_warn(fmc2->dev,
|
||||
"DMAs not defined in the DT, polling mode is used\n");
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* NAND callbacks setup */
|
||||
|
@ -195,7 +195,7 @@ struct sunxi_nand_chip {
|
||||
u32 timing_cfg;
|
||||
u32 timing_ctl;
|
||||
int nsels;
|
||||
struct sunxi_nand_chip_sel sels[0];
|
||||
struct sunxi_nand_chip_sel sels[];
|
||||
};
|
||||
|
||||
static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand)
|
||||
@ -2123,8 +2123,16 @@ static int sunxi_nfc_probe(struct platform_device *pdev)
|
||||
if (ret)
|
||||
goto out_ahb_reset_reassert;
|
||||
|
||||
nfc->dmac = dma_request_slave_channel(dev, "rxtx");
|
||||
if (nfc->dmac) {
|
||||
nfc->dmac = dma_request_chan(dev, "rxtx");
|
||||
if (IS_ERR(nfc->dmac)) {
|
||||
ret = PTR_ERR(nfc->dmac);
|
||||
if (ret == -EPROBE_DEFER)
|
||||
goto out_ahb_reset_reassert;
|
||||
|
||||
/* Ignore errors to fall back to PIO mode */
|
||||
dev_warn(dev, "failed to request rxtx DMA channel: %d\n", ret);
|
||||
nfc->dmac = NULL;
|
||||
} else {
|
||||
struct dma_slave_config dmac_cfg = { };
|
||||
|
||||
dmac_cfg.src_addr = r->start + nfc->caps->reg_io_data;
|
||||
@ -2138,9 +2146,6 @@ static int sunxi_nfc_probe(struct platform_device *pdev)
|
||||
if (nfc->caps->extra_mbus_conf)
|
||||
writel(readl(nfc->regs + NFC_REG_CTL) |
|
||||
NFC_DMA_TYPE_NORMAL, nfc->regs + NFC_REG_CTL);
|
||||
|
||||
} else {
|
||||
dev_warn(dev, "failed to request rxtx DMA channel\n");
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, nfc);
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <linux/mtd/spinand.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/spi/spi.h>
|
||||
#include <linux/spi/spi-mem.h>
|
||||
|
||||
@ -370,10 +371,11 @@ static int spinand_wait(struct spinand_device *spinand, u8 *s)
|
||||
return status & STATUS_BUSY ? -ETIMEDOUT : 0;
|
||||
}
|
||||
|
||||
static int spinand_read_id_op(struct spinand_device *spinand, u8 *buf)
|
||||
static int spinand_read_id_op(struct spinand_device *spinand, u8 naddr,
|
||||
u8 ndummy, u8 *buf)
|
||||
{
|
||||
struct spi_mem_op op = SPINAND_READID_OP(0, spinand->scratchbuf,
|
||||
SPINAND_MAX_ID_LEN);
|
||||
struct spi_mem_op op = SPINAND_READID_OP(
|
||||
naddr, ndummy, spinand->scratchbuf, SPINAND_MAX_ID_LEN);
|
||||
int ret;
|
||||
|
||||
ret = spi_mem_exec_op(spinand->spimem, &op);
|
||||
@ -568,18 +570,18 @@ static int spinand_mtd_write(struct mtd_info *mtd, loff_t to,
|
||||
static bool spinand_isbad(struct nand_device *nand, const struct nand_pos *pos)
|
||||
{
|
||||
struct spinand_device *spinand = nand_to_spinand(nand);
|
||||
u8 marker[2] = { };
|
||||
struct nand_page_io_req req = {
|
||||
.pos = *pos,
|
||||
.ooblen = 2,
|
||||
.ooblen = sizeof(marker),
|
||||
.ooboffs = 0,
|
||||
.oobbuf.in = spinand->oobbuf,
|
||||
.oobbuf.in = marker,
|
||||
.mode = MTD_OPS_RAW,
|
||||
};
|
||||
|
||||
memset(spinand->oobbuf, 0, 2);
|
||||
spinand_select_target(spinand, pos->target);
|
||||
spinand_read_page(spinand, &req, false);
|
||||
if (spinand->oobbuf[0] != 0xff || spinand->oobbuf[1] != 0xff)
|
||||
if (marker[0] != 0xff || marker[1] != 0xff)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
@ -603,15 +605,16 @@ static int spinand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs)
|
||||
static int spinand_markbad(struct nand_device *nand, const struct nand_pos *pos)
|
||||
{
|
||||
struct spinand_device *spinand = nand_to_spinand(nand);
|
||||
u8 marker[2] = { };
|
||||
struct nand_page_io_req req = {
|
||||
.pos = *pos,
|
||||
.ooboffs = 0,
|
||||
.ooblen = 2,
|
||||
.oobbuf.out = spinand->oobbuf,
|
||||
.ooblen = sizeof(marker),
|
||||
.oobbuf.out = marker,
|
||||
.mode = MTD_OPS_RAW,
|
||||
};
|
||||
int ret;
|
||||
|
||||
/* Erase block before marking it bad. */
|
||||
ret = spinand_select_target(spinand, pos->target);
|
||||
if (ret)
|
||||
return ret;
|
||||
@ -620,9 +623,6 @@ static int spinand_markbad(struct nand_device *nand, const struct nand_pos *pos)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
spinand_erase_op(spinand, pos);
|
||||
|
||||
memset(spinand->oobbuf, 0, 2);
|
||||
return spinand_write_page(spinand, &req);
|
||||
}
|
||||
|
||||
@ -762,24 +762,62 @@ static const struct spinand_manufacturer *spinand_manufacturers[] = {
|
||||
&winbond_spinand_manufacturer,
|
||||
};
|
||||
|
||||
static int spinand_manufacturer_detect(struct spinand_device *spinand)
|
||||
static int spinand_manufacturer_match(struct spinand_device *spinand,
|
||||
enum spinand_readid_method rdid_method)
|
||||
{
|
||||
u8 *id = spinand->id.data;
|
||||
unsigned int i;
|
||||
int ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(spinand_manufacturers); i++) {
|
||||
ret = spinand_manufacturers[i]->ops->detect(spinand);
|
||||
if (ret > 0) {
|
||||
spinand->manufacturer = spinand_manufacturers[i];
|
||||
return 0;
|
||||
} else if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
const struct spinand_manufacturer *manufacturer =
|
||||
spinand_manufacturers[i];
|
||||
|
||||
if (id[0] != manufacturer->id)
|
||||
continue;
|
||||
|
||||
ret = spinand_match_and_init(spinand,
|
||||
manufacturer->chips,
|
||||
manufacturer->nchips,
|
||||
rdid_method);
|
||||
if (ret < 0)
|
||||
continue;
|
||||
|
||||
spinand->manufacturer = manufacturer;
|
||||
return 0;
|
||||
}
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static int spinand_id_detect(struct spinand_device *spinand)
|
||||
{
|
||||
u8 *id = spinand->id.data;
|
||||
int ret;
|
||||
|
||||
ret = spinand_read_id_op(spinand, 0, 0, id);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = spinand_manufacturer_match(spinand, SPINAND_READID_METHOD_OPCODE);
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
ret = spinand_read_id_op(spinand, 1, 0, id);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = spinand_manufacturer_match(spinand,
|
||||
SPINAND_READID_METHOD_OPCODE_ADDR);
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
ret = spinand_read_id_op(spinand, 0, 1, id);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = spinand_manufacturer_match(spinand,
|
||||
SPINAND_READID_METHOD_OPCODE_DUMMY);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int spinand_manufacturer_init(struct spinand_device *spinand)
|
||||
{
|
||||
if (spinand->manufacturer->ops->init)
|
||||
@ -835,9 +873,9 @@ spinand_select_op_variant(struct spinand_device *spinand,
|
||||
* @spinand: SPI NAND object
|
||||
* @table: SPI NAND device description table
|
||||
* @table_size: size of the device description table
|
||||
* @rdid_method: read id method to match
|
||||
*
|
||||
* Should be used by SPI NAND manufacturer drivers when they want to find a
|
||||
* match between a device ID retrieved through the READ_ID command and an
|
||||
* Match between a device ID retrieved through the READ_ID command and an
|
||||
* entry in the SPI NAND description table. If a match is found, the spinand
|
||||
* object will be initialized with information provided by the matching
|
||||
* spinand_info entry.
|
||||
@ -846,8 +884,10 @@ spinand_select_op_variant(struct spinand_device *spinand,
|
||||
*/
|
||||
int spinand_match_and_init(struct spinand_device *spinand,
|
||||
const struct spinand_info *table,
|
||||
unsigned int table_size, u16 devid)
|
||||
unsigned int table_size,
|
||||
enum spinand_readid_method rdid_method)
|
||||
{
|
||||
u8 *id = spinand->id.data;
|
||||
struct nand_device *nand = spinand_to_nand(spinand);
|
||||
unsigned int i;
|
||||
|
||||
@ -855,13 +895,17 @@ int spinand_match_and_init(struct spinand_device *spinand,
|
||||
const struct spinand_info *info = &table[i];
|
||||
const struct spi_mem_op *op;
|
||||
|
||||
if (devid != info->devid)
|
||||
if (rdid_method != info->devid.method)
|
||||
continue;
|
||||
|
||||
if (memcmp(id + 1, info->devid.id, info->devid.len))
|
||||
continue;
|
||||
|
||||
nand->memorg = table[i].memorg;
|
||||
nand->eccreq = table[i].eccreq;
|
||||
spinand->eccinfo = table[i].eccinfo;
|
||||
spinand->flags = table[i].flags;
|
||||
spinand->id.len = 1 + table[i].devid.len;
|
||||
spinand->select_target = table[i].select_target;
|
||||
|
||||
op = spinand_select_op_variant(spinand,
|
||||
@ -898,13 +942,7 @@ static int spinand_detect(struct spinand_device *spinand)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = spinand_read_id_op(spinand, spinand->id.data);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
spinand->id.len = SPINAND_MAX_ID_LEN;
|
||||
|
||||
ret = spinand_manufacturer_detect(spinand);
|
||||
ret = spinand_id_detect(spinand);
|
||||
if (ret) {
|
||||
dev_err(dev, "unknown raw ID %*phN\n", SPINAND_MAX_ID_LEN,
|
||||
spinand->id.data);
|
||||
|
@ -195,7 +195,8 @@ static int gd5fxgq4ufxxg_ecc_get_status(struct spinand_device *spinand,
|
||||
}
|
||||
|
||||
static const struct spinand_info gigadevice_spinand_table[] = {
|
||||
SPINAND_INFO("GD5F1GQ4xA", 0xF1,
|
||||
SPINAND_INFO("GD5F1GQ4xA",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xf1),
|
||||
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
@ -204,7 +205,8 @@ static const struct spinand_info gigadevice_spinand_table[] = {
|
||||
0,
|
||||
SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout,
|
||||
gd5fxgq4xa_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F2GQ4xA", 0xF2,
|
||||
SPINAND_INFO("GD5F2GQ4xA",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xf2),
|
||||
NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
@ -213,7 +215,8 @@ static const struct spinand_info gigadevice_spinand_table[] = {
|
||||
0,
|
||||
SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout,
|
||||
gd5fxgq4xa_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F4GQ4xA", 0xF4,
|
||||
SPINAND_INFO("GD5F4GQ4xA",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xf4),
|
||||
NAND_MEMORG(1, 2048, 64, 64, 4096, 80, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
@ -222,7 +225,8 @@ static const struct spinand_info gigadevice_spinand_table[] = {
|
||||
0,
|
||||
SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout,
|
||||
gd5fxgq4xa_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F1GQ4UExxG", 0xd1,
|
||||
SPINAND_INFO("GD5F1GQ4UExxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xd1),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
@ -231,7 +235,8 @@ static const struct spinand_info gigadevice_spinand_table[] = {
|
||||
0,
|
||||
SPINAND_ECCINFO(&gd5fxgq4_variant2_ooblayout,
|
||||
gd5fxgq4uexxg_ecc_get_status)),
|
||||
SPINAND_INFO("GD5F1GQ4UFxxG", 0xb148,
|
||||
SPINAND_INFO("GD5F1GQ4UFxxG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE, 0xb1, 0x48),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants_f,
|
||||
@ -242,39 +247,13 @@ static const struct spinand_info gigadevice_spinand_table[] = {
|
||||
gd5fxgq4ufxxg_ecc_get_status)),
|
||||
};
|
||||
|
||||
static int gigadevice_spinand_detect(struct spinand_device *spinand)
|
||||
{
|
||||
u8 *id = spinand->id.data;
|
||||
u16 did;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Earlier GDF5-series devices (A,E) return [0][MID][DID]
|
||||
* Later (F) devices return [MID][DID1][DID2]
|
||||
*/
|
||||
|
||||
if (id[0] == SPINAND_MFR_GIGADEVICE)
|
||||
did = (id[1] << 8) + id[2];
|
||||
else if (id[0] == 0 && id[1] == SPINAND_MFR_GIGADEVICE)
|
||||
did = id[2];
|
||||
else
|
||||
return 0;
|
||||
|
||||
ret = spinand_match_and_init(spinand, gigadevice_spinand_table,
|
||||
ARRAY_SIZE(gigadevice_spinand_table),
|
||||
did);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = {
|
||||
.detect = gigadevice_spinand_detect,
|
||||
};
|
||||
|
||||
const struct spinand_manufacturer gigadevice_spinand_manufacturer = {
|
||||
.id = SPINAND_MFR_GIGADEVICE,
|
||||
.name = "GigaDevice",
|
||||
.chips = gigadevice_spinand_table,
|
||||
.nchips = ARRAY_SIZE(gigadevice_spinand_table),
|
||||
.ops = &gigadevice_spinand_manuf_ops,
|
||||
};
|
||||
|
@ -99,7 +99,8 @@ static int mx35lf1ge4ab_ecc_get_status(struct spinand_device *spinand,
|
||||
}
|
||||
|
||||
static const struct spinand_info macronix_spinand_table[] = {
|
||||
SPINAND_INFO("MX35LF1GE4AB", 0x12,
|
||||
SPINAND_INFO("MX35LF1GE4AB",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x12),
|
||||
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(4, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
@ -108,7 +109,8 @@ static const struct spinand_info macronix_spinand_table[] = {
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
|
||||
mx35lf1ge4ab_ecc_get_status)),
|
||||
SPINAND_INFO("MX35LF2GE4AB", 0x22,
|
||||
SPINAND_INFO("MX35LF2GE4AB",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x22),
|
||||
NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1),
|
||||
NAND_ECCREQ(4, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
@ -118,33 +120,13 @@ static const struct spinand_info macronix_spinand_table[] = {
|
||||
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)),
|
||||
};
|
||||
|
||||
static int macronix_spinand_detect(struct spinand_device *spinand)
|
||||
{
|
||||
u8 *id = spinand->id.data;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Macronix SPI NAND read ID needs a dummy byte, so the first byte in
|
||||
* raw_id is garbage.
|
||||
*/
|
||||
if (id[1] != SPINAND_MFR_MACRONIX)
|
||||
return 0;
|
||||
|
||||
ret = spinand_match_and_init(spinand, macronix_spinand_table,
|
||||
ARRAY_SIZE(macronix_spinand_table),
|
||||
id[2]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct spinand_manufacturer_ops macronix_spinand_manuf_ops = {
|
||||
.detect = macronix_spinand_detect,
|
||||
};
|
||||
|
||||
const struct spinand_manufacturer macronix_spinand_manufacturer = {
|
||||
.id = SPINAND_MFR_MACRONIX,
|
||||
.name = "Macronix",
|
||||
.chips = macronix_spinand_table,
|
||||
.nchips = ARRAY_SIZE(macronix_spinand_table),
|
||||
.ops = ¯onix_spinand_manuf_ops,
|
||||
};
|
||||
|
@ -18,6 +18,16 @@
|
||||
#define MICRON_STATUS_ECC_4TO6_BITFLIPS (3 << 4)
|
||||
#define MICRON_STATUS_ECC_7TO8_BITFLIPS (5 << 4)
|
||||
|
||||
#define MICRON_CFG_CR BIT(0)
|
||||
|
||||
/*
|
||||
* As per datasheet, die selection is done by the 6th bit of Die
|
||||
* Select Register (Address 0xD0).
|
||||
*/
|
||||
#define MICRON_DIE_SELECT_REG 0xD0
|
||||
|
||||
#define MICRON_SELECT_DIE(x) ((x) << 6)
|
||||
|
||||
static SPINAND_OP_VARIANTS(read_cache_variants,
|
||||
SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
|
||||
@ -34,38 +44,52 @@ static SPINAND_OP_VARIANTS(update_cache_variants,
|
||||
SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(false, 0, NULL, 0));
|
||||
|
||||
static int mt29f2g01abagd_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
static int micron_8_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section)
|
||||
return -ERANGE;
|
||||
|
||||
region->offset = 64;
|
||||
region->length = 64;
|
||||
region->offset = mtd->oobsize / 2;
|
||||
region->length = mtd->oobsize / 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mt29f2g01abagd_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
static int micron_8_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section)
|
||||
return -ERANGE;
|
||||
|
||||
/* Reserve 2 bytes for the BBM. */
|
||||
region->offset = 2;
|
||||
region->length = 62;
|
||||
region->length = (mtd->oobsize / 2) - 2;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mtd_ooblayout_ops mt29f2g01abagd_ooblayout = {
|
||||
.ecc = mt29f2g01abagd_ooblayout_ecc,
|
||||
.free = mt29f2g01abagd_ooblayout_free,
|
||||
static const struct mtd_ooblayout_ops micron_8_ooblayout = {
|
||||
.ecc = micron_8_ooblayout_ecc,
|
||||
.free = micron_8_ooblayout_free,
|
||||
};
|
||||
|
||||
static int mt29f2g01abagd_ecc_get_status(struct spinand_device *spinand,
|
||||
u8 status)
|
||||
static int micron_select_target(struct spinand_device *spinand,
|
||||
unsigned int target)
|
||||
{
|
||||
struct spi_mem_op op = SPINAND_SET_FEATURE_OP(MICRON_DIE_SELECT_REG,
|
||||
spinand->scratchbuf);
|
||||
|
||||
if (target > 1)
|
||||
return -EINVAL;
|
||||
|
||||
*spinand->scratchbuf = MICRON_SELECT_DIE(target);
|
||||
|
||||
return spi_mem_exec_op(spinand->spimem, &op);
|
||||
}
|
||||
|
||||
static int micron_8_ecc_get_status(struct spinand_device *spinand,
|
||||
u8 status)
|
||||
{
|
||||
switch (status & MICRON_STATUS_ECC_MASK) {
|
||||
case STATUS_ECC_NO_BITFLIPS:
|
||||
@ -91,43 +115,131 @@ static int mt29f2g01abagd_ecc_get_status(struct spinand_device *spinand,
|
||||
}
|
||||
|
||||
static const struct spinand_info micron_spinand_table[] = {
|
||||
SPINAND_INFO("MT29F2G01ABAGD", 0x24,
|
||||
/* M79A 2Gb 3.3V */
|
||||
SPINAND_INFO("MT29F2G01ABAGD",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x24),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(&mt29f2g01abagd_ooblayout,
|
||||
mt29f2g01abagd_ecc_get_status)),
|
||||
SPINAND_ECCINFO(µn_8_ooblayout,
|
||||
micron_8_ecc_get_status)),
|
||||
/* M79A 2Gb 1.8V */
|
||||
SPINAND_INFO("MT29F2G01ABBGD",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x25),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(µn_8_ooblayout,
|
||||
micron_8_ecc_get_status)),
|
||||
/* M78A 1Gb 3.3V */
|
||||
SPINAND_INFO("MT29F1G01ABAFD",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(µn_8_ooblayout,
|
||||
micron_8_ecc_get_status)),
|
||||
/* M78A 1Gb 1.8V */
|
||||
SPINAND_INFO("MT29F1G01ABAFD",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x15),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(µn_8_ooblayout,
|
||||
micron_8_ecc_get_status)),
|
||||
/* M79A 4Gb 3.3V */
|
||||
SPINAND_INFO("MT29F4G01ADAGD",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x36),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 80, 2, 1, 2),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(µn_8_ooblayout,
|
||||
micron_8_ecc_get_status),
|
||||
SPINAND_SELECT_TARGET(micron_select_target)),
|
||||
/* M70A 4Gb 3.3V */
|
||||
SPINAND_INFO("MT29F4G01ABAFD",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x34),
|
||||
NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_CR_FEAT_BIT,
|
||||
SPINAND_ECCINFO(µn_8_ooblayout,
|
||||
micron_8_ecc_get_status)),
|
||||
/* M70A 4Gb 1.8V */
|
||||
SPINAND_INFO("MT29F4G01ABBFD",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35),
|
||||
NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_CR_FEAT_BIT,
|
||||
SPINAND_ECCINFO(µn_8_ooblayout,
|
||||
micron_8_ecc_get_status)),
|
||||
/* M70A 8Gb 3.3V */
|
||||
SPINAND_INFO("MT29F8G01ADAFD",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x46),
|
||||
NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 2),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_CR_FEAT_BIT,
|
||||
SPINAND_ECCINFO(µn_8_ooblayout,
|
||||
micron_8_ecc_get_status),
|
||||
SPINAND_SELECT_TARGET(micron_select_target)),
|
||||
/* M70A 8Gb 1.8V */
|
||||
SPINAND_INFO("MT29F8G01ADBFD",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x47),
|
||||
NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 2),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
SPINAND_HAS_CR_FEAT_BIT,
|
||||
SPINAND_ECCINFO(µn_8_ooblayout,
|
||||
micron_8_ecc_get_status),
|
||||
SPINAND_SELECT_TARGET(micron_select_target)),
|
||||
};
|
||||
|
||||
static int micron_spinand_detect(struct spinand_device *spinand)
|
||||
static int micron_spinand_init(struct spinand_device *spinand)
|
||||
{
|
||||
u8 *id = spinand->id.data;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Micron SPI NAND read ID need a dummy byte,
|
||||
* so the first byte in raw_id is dummy.
|
||||
* M70A device series enable Continuous Read feature at Power-up,
|
||||
* which is not supported. Disable this bit to avoid any possible
|
||||
* failure.
|
||||
*/
|
||||
if (id[1] != SPINAND_MFR_MICRON)
|
||||
return 0;
|
||||
if (spinand->flags & SPINAND_HAS_CR_FEAT_BIT)
|
||||
return spinand_upd_cfg(spinand, MICRON_CFG_CR, 0);
|
||||
|
||||
ret = spinand_match_and_init(spinand, micron_spinand_table,
|
||||
ARRAY_SIZE(micron_spinand_table), id[2]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spinand_manufacturer_ops micron_spinand_manuf_ops = {
|
||||
.detect = micron_spinand_detect,
|
||||
.init = micron_spinand_init,
|
||||
};
|
||||
|
||||
const struct spinand_manufacturer micron_spinand_manufacturer = {
|
||||
.id = SPINAND_MFR_MICRON,
|
||||
.name = "Micron",
|
||||
.chips = micron_spinand_table,
|
||||
.nchips = ARRAY_SIZE(micron_spinand_table),
|
||||
.ops = µn_spinand_manuf_ops,
|
||||
};
|
||||
|
@ -97,7 +97,8 @@ static const struct mtd_ooblayout_ops pn26g0xa_ooblayout = {
|
||||
|
||||
|
||||
static const struct spinand_info paragon_spinand_table[] = {
|
||||
SPINAND_INFO("PN26G01A", 0xe1,
|
||||
SPINAND_INFO("PN26G01A",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe1),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 21, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
@ -106,7 +107,8 @@ static const struct spinand_info paragon_spinand_table[] = {
|
||||
0,
|
||||
SPINAND_ECCINFO(&pn26g0xa_ooblayout,
|
||||
pn26g0xa_ecc_get_status)),
|
||||
SPINAND_INFO("PN26G02A", 0xe2,
|
||||
SPINAND_INFO("PN26G02A",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe2),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 41, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
@ -117,31 +119,13 @@ static const struct spinand_info paragon_spinand_table[] = {
|
||||
pn26g0xa_ecc_get_status)),
|
||||
};
|
||||
|
||||
static int paragon_spinand_detect(struct spinand_device *spinand)
|
||||
{
|
||||
u8 *id = spinand->id.data;
|
||||
int ret;
|
||||
|
||||
/* Read ID returns [0][MID][DID] */
|
||||
|
||||
if (id[1] != SPINAND_MFR_PARAGON)
|
||||
return 0;
|
||||
|
||||
ret = spinand_match_and_init(spinand, paragon_spinand_table,
|
||||
ARRAY_SIZE(paragon_spinand_table),
|
||||
id[2]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static const struct spinand_manufacturer_ops paragon_spinand_manuf_ops = {
|
||||
.detect = paragon_spinand_detect,
|
||||
};
|
||||
|
||||
const struct spinand_manufacturer paragon_spinand_manufacturer = {
|
||||
.id = SPINAND_MFR_PARAGON,
|
||||
.name = "Paragon",
|
||||
.chips = paragon_spinand_table,
|
||||
.nchips = ARRAY_SIZE(paragon_spinand_table),
|
||||
.ops = ¶gon_spinand_manuf_ops,
|
||||
};
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mtd/spinand.h>
|
||||
|
||||
/* Kioxia is new name of Toshiba memory. */
|
||||
#define SPINAND_MFR_TOSHIBA 0x98
|
||||
#define TOSH_STATUS_ECC_HAS_BITFLIPS_T (3 << 4)
|
||||
|
||||
@ -19,14 +20,26 @@ static SPINAND_OP_VARIANTS(read_cache_variants,
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(true, 0, 1, NULL, 0),
|
||||
SPINAND_PAGE_READ_FROM_CACHE_OP(false, 0, 1, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(write_cache_x4_variants,
|
||||
SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(true, 0, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(update_cache_x4_variants,
|
||||
SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
|
||||
SPINAND_PROG_LOAD(false, 0, NULL, 0));
|
||||
|
||||
/**
|
||||
* Backward compatibility for 1st generation Serial NAND devices
|
||||
* which don't support Quad Program Load operation.
|
||||
*/
|
||||
static SPINAND_OP_VARIANTS(write_cache_variants,
|
||||
SPINAND_PROG_LOAD(true, 0, NULL, 0));
|
||||
|
||||
static SPINAND_OP_VARIANTS(update_cache_variants,
|
||||
SPINAND_PROG_LOAD(false, 0, NULL, 0));
|
||||
|
||||
static int tc58cxgxsx_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
static int tx58cxgxsxraix_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section > 0)
|
||||
return -ERANGE;
|
||||
@ -37,8 +50,8 @@ static int tc58cxgxsx_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tc58cxgxsx_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
static int tx58cxgxsxraix_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *region)
|
||||
{
|
||||
if (section > 0)
|
||||
return -ERANGE;
|
||||
@ -50,13 +63,13 @@ static int tc58cxgxsx_ooblayout_free(struct mtd_info *mtd, int section,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mtd_ooblayout_ops tc58cxgxsx_ooblayout = {
|
||||
.ecc = tc58cxgxsx_ooblayout_ecc,
|
||||
.free = tc58cxgxsx_ooblayout_free,
|
||||
static const struct mtd_ooblayout_ops tx58cxgxsxraix_ooblayout = {
|
||||
.ecc = tx58cxgxsxraix_ooblayout_ecc,
|
||||
.free = tx58cxgxsxraix_ooblayout_free,
|
||||
};
|
||||
|
||||
static int tc58cxgxsx_ecc_get_status(struct spinand_device *spinand,
|
||||
u8 status)
|
||||
static int tx58cxgxsxraix_ecc_get_status(struct spinand_device *spinand,
|
||||
u8 status)
|
||||
{
|
||||
struct nand_device *nand = spinand_to_nand(spinand);
|
||||
u8 mbf = 0;
|
||||
@ -94,105 +107,174 @@ static int tc58cxgxsx_ecc_get_status(struct spinand_device *spinand,
|
||||
}
|
||||
|
||||
static const struct spinand_info toshiba_spinand_table[] = {
|
||||
/* 3.3V 1Gb */
|
||||
SPINAND_INFO("TC58CVG0S3", 0xC2,
|
||||
/* 3.3V 1Gb (1st generation) */
|
||||
SPINAND_INFO("TC58CVG0S3HRAIG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xC2),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(&tc58cxgxsx_ooblayout,
|
||||
tc58cxgxsx_ecc_get_status)),
|
||||
/* 3.3V 2Gb */
|
||||
SPINAND_INFO("TC58CVG1S3", 0xCB,
|
||||
SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout,
|
||||
tx58cxgxsxraix_ecc_get_status)),
|
||||
/* 3.3V 2Gb (1st generation) */
|
||||
SPINAND_INFO("TC58CVG1S3HRAIG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xCB),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(&tc58cxgxsx_ooblayout,
|
||||
tc58cxgxsx_ecc_get_status)),
|
||||
/* 3.3V 4Gb */
|
||||
SPINAND_INFO("TC58CVG2S0", 0xCD,
|
||||
SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout,
|
||||
tx58cxgxsxraix_ecc_get_status)),
|
||||
/* 3.3V 4Gb (1st generation) */
|
||||
SPINAND_INFO("TC58CVG2S0HRAIG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xCD),
|
||||
NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(&tc58cxgxsx_ooblayout,
|
||||
tc58cxgxsx_ecc_get_status)),
|
||||
/* 3.3V 4Gb */
|
||||
SPINAND_INFO("TC58CVG2S0", 0xED,
|
||||
NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(&tc58cxgxsx_ooblayout,
|
||||
tc58cxgxsx_ecc_get_status)),
|
||||
/* 1.8V 1Gb */
|
||||
SPINAND_INFO("TC58CYG0S3", 0xB2,
|
||||
SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout,
|
||||
tx58cxgxsxraix_ecc_get_status)),
|
||||
/* 1.8V 1Gb (1st generation) */
|
||||
SPINAND_INFO("TC58CYG0S3HRAIG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xB2),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(&tc58cxgxsx_ooblayout,
|
||||
tc58cxgxsx_ecc_get_status)),
|
||||
/* 1.8V 2Gb */
|
||||
SPINAND_INFO("TC58CYG1S3", 0xBB,
|
||||
SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout,
|
||||
tx58cxgxsxraix_ecc_get_status)),
|
||||
/* 1.8V 2Gb (1st generation) */
|
||||
SPINAND_INFO("TC58CYG1S3HRAIG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xBB),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(&tc58cxgxsx_ooblayout,
|
||||
tc58cxgxsx_ecc_get_status)),
|
||||
/* 1.8V 4Gb */
|
||||
SPINAND_INFO("TC58CYG2S0", 0xBD,
|
||||
SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout,
|
||||
tx58cxgxsxraix_ecc_get_status)),
|
||||
/* 1.8V 4Gb (1st generation) */
|
||||
SPINAND_INFO("TC58CYG2S0HRAIG",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xBD),
|
||||
NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_variants,
|
||||
&update_cache_variants),
|
||||
0,
|
||||
SPINAND_ECCINFO(&tc58cxgxsx_ooblayout,
|
||||
tc58cxgxsx_ecc_get_status)),
|
||||
};
|
||||
|
||||
static int toshiba_spinand_detect(struct spinand_device *spinand)
|
||||
{
|
||||
u8 *id = spinand->id.data;
|
||||
int ret;
|
||||
SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout,
|
||||
tx58cxgxsxraix_ecc_get_status)),
|
||||
|
||||
/*
|
||||
* Toshiba SPI NAND read ID needs a dummy byte,
|
||||
* so the first byte in id is garbage.
|
||||
* 2nd generation serial nand has HOLD_D which is equivalent to
|
||||
* QE_BIT.
|
||||
*/
|
||||
if (id[1] != SPINAND_MFR_TOSHIBA)
|
||||
return 0;
|
||||
|
||||
ret = spinand_match_and_init(spinand, toshiba_spinand_table,
|
||||
ARRAY_SIZE(toshiba_spinand_table),
|
||||
id[2]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 1;
|
||||
}
|
||||
/* 3.3V 1Gb (2nd generation) */
|
||||
SPINAND_INFO("TC58CVG0S3HRAIJ",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE2),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_x4_variants,
|
||||
&update_cache_x4_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout,
|
||||
tx58cxgxsxraix_ecc_get_status)),
|
||||
/* 3.3V 2Gb (2nd generation) */
|
||||
SPINAND_INFO("TC58CVG1S3HRAIJ",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xEB),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_x4_variants,
|
||||
&update_cache_x4_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout,
|
||||
tx58cxgxsxraix_ecc_get_status)),
|
||||
/* 3.3V 4Gb (2nd generation) */
|
||||
SPINAND_INFO("TC58CVG2S0HRAIJ",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xED),
|
||||
NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_x4_variants,
|
||||
&update_cache_x4_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout,
|
||||
tx58cxgxsxraix_ecc_get_status)),
|
||||
/* 3.3V 8Gb (2nd generation) */
|
||||
SPINAND_INFO("TH58CVG3S0HRAIJ",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE4),
|
||||
NAND_MEMORG(1, 4096, 256, 64, 4096, 80, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_x4_variants,
|
||||
&update_cache_x4_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout,
|
||||
tx58cxgxsxraix_ecc_get_status)),
|
||||
/* 1.8V 1Gb (2nd generation) */
|
||||
SPINAND_INFO("TC58CYG0S3HRAIJ",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xD2),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_x4_variants,
|
||||
&update_cache_x4_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout,
|
||||
tx58cxgxsxraix_ecc_get_status)),
|
||||
/* 1.8V 2Gb (2nd generation) */
|
||||
SPINAND_INFO("TC58CYG1S3HRAIJ",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xDB),
|
||||
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_x4_variants,
|
||||
&update_cache_x4_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout,
|
||||
tx58cxgxsxraix_ecc_get_status)),
|
||||
/* 1.8V 4Gb (2nd generation) */
|
||||
SPINAND_INFO("TC58CYG2S0HRAIJ",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xDD),
|
||||
NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_x4_variants,
|
||||
&update_cache_x4_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout,
|
||||
tx58cxgxsxraix_ecc_get_status)),
|
||||
/* 1.8V 8Gb (2nd generation) */
|
||||
SPINAND_INFO("TH58CYG3S0HRAIJ",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xD4),
|
||||
NAND_MEMORG(1, 4096, 256, 64, 4096, 80, 1, 1, 1),
|
||||
NAND_ECCREQ(8, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
&write_cache_x4_variants,
|
||||
&update_cache_x4_variants),
|
||||
SPINAND_HAS_QE_BIT,
|
||||
SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout,
|
||||
tx58cxgxsxraix_ecc_get_status)),
|
||||
};
|
||||
|
||||
static const struct spinand_manufacturer_ops toshiba_spinand_manuf_ops = {
|
||||
.detect = toshiba_spinand_detect,
|
||||
};
|
||||
|
||||
const struct spinand_manufacturer toshiba_spinand_manufacturer = {
|
||||
.id = SPINAND_MFR_TOSHIBA,
|
||||
.name = "Toshiba",
|
||||
.chips = toshiba_spinand_table,
|
||||
.nchips = ARRAY_SIZE(toshiba_spinand_table),
|
||||
.ops = &toshiba_spinand_manuf_ops,
|
||||
};
|
||||
|
@ -75,7 +75,8 @@ static int w25m02gv_select_target(struct spinand_device *spinand,
|
||||
}
|
||||
|
||||
static const struct spinand_info winbond_spinand_table[] = {
|
||||
SPINAND_INFO("W25M02GV", 0xAB,
|
||||
SPINAND_INFO("W25M02GV",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab),
|
||||
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 2),
|
||||
NAND_ECCREQ(1, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
@ -84,7 +85,8 @@ static const struct spinand_info winbond_spinand_table[] = {
|
||||
0,
|
||||
SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL),
|
||||
SPINAND_SELECT_TARGET(w25m02gv_select_target)),
|
||||
SPINAND_INFO("W25N01GV", 0xAA,
|
||||
SPINAND_INFO("W25N01GV",
|
||||
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa),
|
||||
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
|
||||
NAND_ECCREQ(1, 512),
|
||||
SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
|
||||
@ -94,31 +96,6 @@ static const struct spinand_info winbond_spinand_table[] = {
|
||||
SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)),
|
||||
};
|
||||
|
||||
/**
|
||||
* winbond_spinand_detect - initialize device related part in spinand_device
|
||||
* struct if it is a Winbond device.
|
||||
* @spinand: SPI NAND device structure
|
||||
*/
|
||||
static int winbond_spinand_detect(struct spinand_device *spinand)
|
||||
{
|
||||
u8 *id = spinand->id.data;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Winbond SPI NAND read ID need a dummy byte,
|
||||
* so the first byte in raw_id is dummy.
|
||||
*/
|
||||
if (id[1] != SPINAND_MFR_WINBOND)
|
||||
return 0;
|
||||
|
||||
ret = spinand_match_and_init(spinand, winbond_spinand_table,
|
||||
ARRAY_SIZE(winbond_spinand_table), id[2]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int winbond_spinand_init(struct spinand_device *spinand)
|
||||
{
|
||||
struct nand_device *nand = spinand_to_nand(spinand);
|
||||
@ -138,12 +115,13 @@ static int winbond_spinand_init(struct spinand_device *spinand)
|
||||
}
|
||||
|
||||
static const struct spinand_manufacturer_ops winbond_spinand_manuf_ops = {
|
||||
.detect = winbond_spinand_detect,
|
||||
.init = winbond_spinand_init,
|
||||
};
|
||||
|
||||
const struct spinand_manufacturer winbond_spinand_manufacturer = {
|
||||
.id = SPINAND_MFR_WINBOND,
|
||||
.name = "Winbond",
|
||||
.chips = winbond_spinand_table,
|
||||
.nchips = ARRAY_SIZE(winbond_spinand_table),
|
||||
.ops = &winbond_spinand_manuf_ops,
|
||||
};
|
||||
|
@ -24,79 +24,6 @@ config MTD_SPI_NOR_USE_4K_SECTORS
|
||||
Please note that some tools/drivers/filesystems may not work with
|
||||
4096 B erase size (e.g. UBIFS requires 15 KiB as a minimum).
|
||||
|
||||
config SPI_ASPEED_SMC
|
||||
tristate "Aspeed flash controllers in SPI mode"
|
||||
depends on ARCH_ASPEED || COMPILE_TEST
|
||||
depends on HAS_IOMEM && OF
|
||||
help
|
||||
This enables support for the Firmware Memory controller (FMC)
|
||||
in the Aspeed AST2500/AST2400 SoCs when attached to SPI NOR chips,
|
||||
and support for the SPI flash memory controller (SPI) for
|
||||
the host firmware. The implementation only supports SPI NOR.
|
||||
|
||||
config SPI_CADENCE_QUADSPI
|
||||
tristate "Cadence Quad SPI controller"
|
||||
depends on OF && (ARM || ARM64 || COMPILE_TEST)
|
||||
help
|
||||
Enable support for the Cadence Quad SPI Flash controller.
|
||||
|
||||
Cadence QSPI is a specialized controller for connecting an SPI
|
||||
Flash over 1/2/4-bit wide bus. Enable this option if you have a
|
||||
device with a Cadence QSPI controller and want to access the
|
||||
Flash as an MTD device.
|
||||
|
||||
config SPI_HISI_SFC
|
||||
tristate "Hisilicon FMC SPI-NOR Flash Controller(SFC)"
|
||||
depends on ARCH_HISI || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
This enables support for HiSilicon FMC SPI-NOR flash controller.
|
||||
|
||||
config SPI_NXP_SPIFI
|
||||
tristate "NXP SPI Flash Interface (SPIFI)"
|
||||
depends on OF && (ARCH_LPC18XX || COMPILE_TEST)
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
Enable support for the NXP LPC SPI Flash Interface controller.
|
||||
|
||||
SPIFI is a specialized controller for connecting serial SPI
|
||||
Flash. Enable this option if you have a device with a SPIFI
|
||||
controller and want to access the Flash as a mtd device.
|
||||
|
||||
config SPI_INTEL_SPI
|
||||
tristate
|
||||
|
||||
config SPI_INTEL_SPI_PCI
|
||||
tristate "Intel PCH/PCU SPI flash PCI driver (DANGEROUS)"
|
||||
depends on X86 && PCI
|
||||
select SPI_INTEL_SPI
|
||||
help
|
||||
This enables PCI support for the Intel PCH/PCU SPI controller in
|
||||
master mode. This controller is present in modern Intel hardware
|
||||
and is used to hold BIOS and other persistent settings. Using
|
||||
this driver it is possible to upgrade BIOS directly from Linux.
|
||||
|
||||
Say N here unless you know what you are doing. Overwriting the
|
||||
SPI flash may render the system unbootable.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel-spi-pci.
|
||||
|
||||
config SPI_INTEL_SPI_PLATFORM
|
||||
tristate "Intel PCH/PCU SPI flash platform driver (DANGEROUS)"
|
||||
depends on X86
|
||||
select SPI_INTEL_SPI
|
||||
help
|
||||
This enables platform support for the Intel PCH/PCU SPI
|
||||
controller in master mode. This controller is present in modern
|
||||
Intel hardware and is used to hold BIOS and other persistent
|
||||
settings. Using this driver it is possible to upgrade BIOS
|
||||
directly from Linux.
|
||||
|
||||
Say N here unless you know what you are doing. Overwriting the
|
||||
SPI flash may render the system unbootable.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel-spi-platform.
|
||||
source "drivers/mtd/spi-nor/controllers/Kconfig"
|
||||
|
||||
endif # MTD_SPI_NOR
|
||||
|
@ -1,9 +1,20 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
spi-nor-objs := core.o sfdp.o
|
||||
spi-nor-objs += atmel.o
|
||||
spi-nor-objs += catalyst.o
|
||||
spi-nor-objs += eon.o
|
||||
spi-nor-objs += esmt.o
|
||||
spi-nor-objs += everspin.o
|
||||
spi-nor-objs += fujitsu.o
|
||||
spi-nor-objs += gigadevice.o
|
||||
spi-nor-objs += intel.o
|
||||
spi-nor-objs += issi.o
|
||||
spi-nor-objs += macronix.o
|
||||
spi-nor-objs += micron-st.o
|
||||
spi-nor-objs += spansion.o
|
||||
spi-nor-objs += sst.o
|
||||
spi-nor-objs += winbond.o
|
||||
spi-nor-objs += xilinx.o
|
||||
spi-nor-objs += xmc.o
|
||||
obj-$(CONFIG_MTD_SPI_NOR) += spi-nor.o
|
||||
obj-$(CONFIG_SPI_ASPEED_SMC) += aspeed-smc.o
|
||||
obj-$(CONFIG_SPI_CADENCE_QUADSPI) += cadence-quadspi.o
|
||||
obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o
|
||||
obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o
|
||||
obj-$(CONFIG_SPI_INTEL_SPI) += intel-spi.o
|
||||
obj-$(CONFIG_SPI_INTEL_SPI_PCI) += intel-spi-pci.o
|
||||
obj-$(CONFIG_SPI_INTEL_SPI_PLATFORM) += intel-spi-platform.o
|
||||
|
46
drivers/mtd/spi-nor/atmel.c
Normal file
46
drivers/mtd/spi-nor/atmel.c
Normal file
@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info atmel_parts[] = {
|
||||
/* Atmel -- some are (confusingly) marketed as "DataFlash" */
|
||||
{ "at25fs010", INFO(0x1f6601, 0, 32 * 1024, 4, SECT_4K) },
|
||||
{ "at25fs040", INFO(0x1f6604, 0, 64 * 1024, 8, SECT_4K) },
|
||||
|
||||
{ "at25df041a", INFO(0x1f4401, 0, 64 * 1024, 8, SECT_4K) },
|
||||
{ "at25df321", INFO(0x1f4700, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "at25df321a", INFO(0x1f4701, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "at25df641", INFO(0x1f4800, 0, 64 * 1024, 128, SECT_4K) },
|
||||
|
||||
{ "at25sl321", INFO(0x1f4216, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
|
||||
{ "at26f004", INFO(0x1f0400, 0, 64 * 1024, 8, SECT_4K) },
|
||||
{ "at26df081a", INFO(0x1f4501, 0, 64 * 1024, 16, SECT_4K) },
|
||||
{ "at26df161a", INFO(0x1f4601, 0, 64 * 1024, 32, SECT_4K) },
|
||||
{ "at26df321", INFO(0x1f4700, 0, 64 * 1024, 64, SECT_4K) },
|
||||
|
||||
{ "at45db081d", INFO(0x1f2500, 0, 64 * 1024, 16, SECT_4K) },
|
||||
};
|
||||
|
||||
static void atmel_default_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->flags |= SNOR_F_HAS_LOCK;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups atmel_fixups = {
|
||||
.default_init = atmel_default_init,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_atmel = {
|
||||
.name = "atmel",
|
||||
.parts = atmel_parts,
|
||||
.nparts = ARRAY_SIZE(atmel_parts),
|
||||
.fixups = &atmel_fixups,
|
||||
};
|
29
drivers/mtd/spi-nor/catalyst.c
Normal file
29
drivers/mtd/spi-nor/catalyst.c
Normal file
@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info catalyst_parts[] = {
|
||||
/* Catalyst / On Semiconductor -- non-JEDEC */
|
||||
{ "cat25c11", CAT25_INFO(16, 8, 16, 1,
|
||||
SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
{ "cat25c03", CAT25_INFO(32, 8, 16, 2,
|
||||
SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
{ "cat25c09", CAT25_INFO(128, 8, 32, 2,
|
||||
SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
{ "cat25c17", CAT25_INFO(256, 8, 32, 2,
|
||||
SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
{ "cat25128", CAT25_INFO(2048, 8, 64, 2,
|
||||
SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_catalyst = {
|
||||
.name = "catalyst",
|
||||
.parts = catalyst_parts,
|
||||
.nparts = ARRAY_SIZE(catalyst_parts),
|
||||
};
|
75
drivers/mtd/spi-nor/controllers/Kconfig
Normal file
75
drivers/mtd/spi-nor/controllers/Kconfig
Normal file
@ -0,0 +1,75 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config SPI_ASPEED_SMC
|
||||
tristate "Aspeed flash controllers in SPI mode"
|
||||
depends on ARCH_ASPEED || COMPILE_TEST
|
||||
depends on HAS_IOMEM && OF
|
||||
help
|
||||
This enables support for the Firmware Memory controller (FMC)
|
||||
in the Aspeed AST2500/AST2400 SoCs when attached to SPI NOR chips,
|
||||
and support for the SPI flash memory controller (SPI) for
|
||||
the host firmware. The implementation only supports SPI NOR.
|
||||
|
||||
config SPI_CADENCE_QUADSPI
|
||||
tristate "Cadence Quad SPI controller"
|
||||
depends on OF && (ARM || ARM64 || COMPILE_TEST)
|
||||
help
|
||||
Enable support for the Cadence Quad SPI Flash controller.
|
||||
|
||||
Cadence QSPI is a specialized controller for connecting an SPI
|
||||
Flash over 1/2/4-bit wide bus. Enable this option if you have a
|
||||
device with a Cadence QSPI controller and want to access the
|
||||
Flash as an MTD device.
|
||||
|
||||
config SPI_HISI_SFC
|
||||
tristate "Hisilicon FMC SPI-NOR Flash Controller(SFC)"
|
||||
depends on ARCH_HISI || COMPILE_TEST
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
This enables support for HiSilicon FMC SPI-NOR flash controller.
|
||||
|
||||
config SPI_NXP_SPIFI
|
||||
tristate "NXP SPI Flash Interface (SPIFI)"
|
||||
depends on OF && (ARCH_LPC18XX || COMPILE_TEST)
|
||||
depends on HAS_IOMEM
|
||||
help
|
||||
Enable support for the NXP LPC SPI Flash Interface controller.
|
||||
|
||||
SPIFI is a specialized controller for connecting serial SPI
|
||||
Flash. Enable this option if you have a device with a SPIFI
|
||||
controller and want to access the Flash as a mtd device.
|
||||
|
||||
config SPI_INTEL_SPI
|
||||
tristate
|
||||
|
||||
config SPI_INTEL_SPI_PCI
|
||||
tristate "Intel PCH/PCU SPI flash PCI driver (DANGEROUS)"
|
||||
depends on X86 && PCI
|
||||
select SPI_INTEL_SPI
|
||||
help
|
||||
This enables PCI support for the Intel PCH/PCU SPI controller in
|
||||
master mode. This controller is present in modern Intel hardware
|
||||
and is used to hold BIOS and other persistent settings. Using
|
||||
this driver it is possible to upgrade BIOS directly from Linux.
|
||||
|
||||
Say N here unless you know what you are doing. Overwriting the
|
||||
SPI flash may render the system unbootable.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel-spi-pci.
|
||||
|
||||
config SPI_INTEL_SPI_PLATFORM
|
||||
tristate "Intel PCH/PCU SPI flash platform driver (DANGEROUS)"
|
||||
depends on X86
|
||||
select SPI_INTEL_SPI
|
||||
help
|
||||
This enables platform support for the Intel PCH/PCU SPI
|
||||
controller in master mode. This controller is present in modern
|
||||
Intel hardware and is used to hold BIOS and other persistent
|
||||
settings. Using this driver it is possible to upgrade BIOS
|
||||
directly from Linux.
|
||||
|
||||
Say N here unless you know what you are doing. Overwriting the
|
||||
SPI flash may render the system unbootable.
|
||||
|
||||
To compile this driver as a module, choose M here: the module
|
||||
will be called intel-spi-platform.
|
8
drivers/mtd/spi-nor/controllers/Makefile
Normal file
8
drivers/mtd/spi-nor/controllers/Makefile
Normal file
@ -0,0 +1,8 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_SPI_ASPEED_SMC) += aspeed-smc.o
|
||||
obj-$(CONFIG_SPI_CADENCE_QUADSPI) += cadence-quadspi.o
|
||||
obj-$(CONFIG_SPI_HISI_SFC) += hisi-sfc.o
|
||||
obj-$(CONFIG_SPI_NXP_SPIFI) += nxp-spifi.o
|
||||
obj-$(CONFIG_SPI_INTEL_SPI) += intel-spi.o
|
||||
obj-$(CONFIG_SPI_INTEL_SPI_PCI) += intel-spi-pci.o
|
||||
obj-$(CONFIG_SPI_INTEL_SPI_PLATFORM) += intel-spi-platform.o
|
@ -109,7 +109,7 @@ struct aspeed_smc_controller {
|
||||
void __iomem *ahb_base; /* per-chip windows resource */
|
||||
u32 ahb_window_size; /* full mapping window size */
|
||||
|
||||
struct aspeed_smc_chip *chips[0]; /* pointers to attached chips */
|
||||
struct aspeed_smc_chip *chips[]; /* pointers to attached chips */
|
||||
};
|
||||
|
||||
/*
|
||||
@ -354,7 +354,7 @@ static void aspeed_smc_send_cmd_addr(struct spi_nor *nor, u8 cmd, u32 addr)
|
||||
default:
|
||||
WARN_ONCE(1, "Unexpected address width %u, defaulting to 3\n",
|
||||
nor->addr_width);
|
||||
/* FALLTHROUGH */
|
||||
fallthrough;
|
||||
case 3:
|
||||
cmdaddr = addr & 0xFFFFFF;
|
||||
cmdaddr |= cmd << 24;
|
3466
drivers/mtd/spi-nor/core.c
Normal file
3466
drivers/mtd/spi-nor/core.c
Normal file
File diff suppressed because it is too large
Load Diff
441
drivers/mtd/spi-nor/core.h
Normal file
441
drivers/mtd/spi-nor/core.h
Normal file
@ -0,0 +1,441 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_MTD_SPI_NOR_INTERNAL_H
|
||||
#define __LINUX_MTD_SPI_NOR_INTERNAL_H
|
||||
|
||||
#include "sfdp.h"
|
||||
|
||||
#define SPI_NOR_MAX_ID_LEN 6
|
||||
|
||||
enum spi_nor_option_flags {
|
||||
SNOR_F_USE_FSR = BIT(0),
|
||||
SNOR_F_HAS_SR_TB = BIT(1),
|
||||
SNOR_F_NO_OP_CHIP_ERASE = BIT(2),
|
||||
SNOR_F_READY_XSR_RDY = BIT(3),
|
||||
SNOR_F_USE_CLSR = BIT(4),
|
||||
SNOR_F_BROKEN_RESET = BIT(5),
|
||||
SNOR_F_4B_OPCODES = BIT(6),
|
||||
SNOR_F_HAS_4BAIT = BIT(7),
|
||||
SNOR_F_HAS_LOCK = BIT(8),
|
||||
SNOR_F_HAS_16BIT_SR = BIT(9),
|
||||
SNOR_F_NO_READ_CR = BIT(10),
|
||||
SNOR_F_HAS_SR_TB_BIT6 = BIT(11),
|
||||
SNOR_F_HAS_4BIT_BP = BIT(12),
|
||||
SNOR_F_HAS_SR_BP3_BIT6 = BIT(13),
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
/**
|
||||
* struct spi_nor_erase_type - Structure to describe a SPI NOR erase type
|
||||
* @size: the size of the sector/block erased by the erase type.
|
||||
* JEDEC JESD216B imposes erase sizes to be a power of 2.
|
||||
* @size_shift: @size is a power of 2, the shift is stored in
|
||||
* @size_shift.
|
||||
* @size_mask: the size mask based on @size_shift.
|
||||
* @opcode: the SPI command op code to erase the sector/block.
|
||||
* @idx: Erase Type index as sorted in the Basic Flash Parameter
|
||||
* Table. It will be used to synchronize the supported
|
||||
* Erase Types with the ones identified in the SFDP
|
||||
* optional tables.
|
||||
*/
|
||||
struct spi_nor_erase_type {
|
||||
u32 size;
|
||||
u32 size_shift;
|
||||
u32 size_mask;
|
||||
u8 opcode;
|
||||
u8 idx;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct spi_nor_erase_command - Used for non-uniform erases
|
||||
* The structure is used to describe a list of erase commands to be executed
|
||||
* once we validate that the erase can be performed. The elements in the list
|
||||
* are run-length encoded.
|
||||
* @list: for inclusion into the list of erase commands.
|
||||
* @count: how many times the same erase command should be
|
||||
* consecutively used.
|
||||
* @size: the size of the sector/block erased by the command.
|
||||
* @opcode: the SPI command op code to erase the sector/block.
|
||||
*/
|
||||
struct spi_nor_erase_command {
|
||||
struct list_head list;
|
||||
u32 count;
|
||||
u32 size;
|
||||
u8 opcode;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct spi_nor_erase_region - Structure to describe a SPI NOR erase region
|
||||
* @offset: the offset in the data array of erase region start.
|
||||
* LSB bits are used as a bitmask encoding flags to
|
||||
* determine if this region is overlaid, if this region is
|
||||
* the last in the SPI NOR flash memory and to indicate
|
||||
* all the supported erase commands inside this region.
|
||||
* The erase types are sorted in ascending order with the
|
||||
* smallest Erase Type size being at BIT(0).
|
||||
* @size: the size of the region in bytes.
|
||||
*/
|
||||
struct spi_nor_erase_region {
|
||||
u64 offset;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
#define SNOR_ERASE_TYPE_MAX 4
|
||||
#define SNOR_ERASE_TYPE_MASK GENMASK_ULL(SNOR_ERASE_TYPE_MAX - 1, 0)
|
||||
|
||||
#define SNOR_LAST_REGION BIT(4)
|
||||
#define SNOR_OVERLAID_REGION BIT(5)
|
||||
|
||||
#define SNOR_ERASE_FLAGS_MAX 6
|
||||
#define SNOR_ERASE_FLAGS_MASK GENMASK_ULL(SNOR_ERASE_FLAGS_MAX - 1, 0)
|
||||
|
||||
/**
|
||||
* struct spi_nor_erase_map - Structure to describe the SPI NOR erase map
|
||||
* @regions: array of erase regions. The regions are consecutive in
|
||||
* address space. Walking through the regions is done
|
||||
* incrementally.
|
||||
* @uniform_region: a pre-allocated erase region for SPI NOR with a uniform
|
||||
* sector size (legacy implementation).
|
||||
* @erase_type: an array of erase types shared by all the regions.
|
||||
* The erase types are sorted in ascending order, with the
|
||||
* smallest Erase Type size being the first member in the
|
||||
* erase_type array.
|
||||
* @uniform_erase_type: bitmask encoding erase types that can erase the
|
||||
* entire memory. This member is completed at init by
|
||||
* uniform and non-uniform SPI NOR flash memories if they
|
||||
* support at least one erase type that can erase the
|
||||
* entire memory.
|
||||
*/
|
||||
struct spi_nor_erase_map {
|
||||
struct spi_nor_erase_region *regions;
|
||||
struct spi_nor_erase_region uniform_region;
|
||||
struct spi_nor_erase_type erase_type[SNOR_ERASE_TYPE_MAX];
|
||||
u8 uniform_erase_type;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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_addr_mode: 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_addr_mode)(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 spi_nor_fixups - SPI NOR fixup hooks
|
||||
* @default_init: called after default flash parameters init. Used to tweak
|
||||
* flash parameters when information provided by the flash_info
|
||||
* table is incomplete or wrong.
|
||||
* @post_bfpt: called after the BFPT table has been parsed
|
||||
* @post_sfdp: called after SFDP has been parsed (is also called for SPI NORs
|
||||
* that do not support RDSFDP). Typically used to tweak various
|
||||
* parameters that could not be extracted by other means (i.e.
|
||||
* when information provided by the SFDP/flash_info tables are
|
||||
* incomplete or wrong).
|
||||
*
|
||||
* Those hooks can be used to tweak the SPI NOR configuration when the SFDP
|
||||
* table is broken or not available.
|
||||
*/
|
||||
struct spi_nor_fixups {
|
||||
void (*default_init)(struct spi_nor *nor);
|
||||
int (*post_bfpt)(struct spi_nor *nor,
|
||||
const struct sfdp_parameter_header *bfpt_header,
|
||||
const struct sfdp_bfpt *bfpt,
|
||||
struct spi_nor_flash_parameter *params);
|
||||
void (*post_sfdp)(struct spi_nor *nor);
|
||||
};
|
||||
|
||||
struct flash_info {
|
||||
char *name;
|
||||
|
||||
/*
|
||||
* This array stores the ID bytes.
|
||||
* The first three bytes are the JEDIC ID.
|
||||
* JEDEC ID zero means "no ID" (mostly older chips).
|
||||
*/
|
||||
u8 id[SPI_NOR_MAX_ID_LEN];
|
||||
u8 id_len;
|
||||
|
||||
/* The size listed here is what works with SPINOR_OP_SE, which isn't
|
||||
* necessarily called a "sector" by the vendor.
|
||||
*/
|
||||
unsigned sector_size;
|
||||
u16 n_sectors;
|
||||
|
||||
u16 page_size;
|
||||
u16 addr_width;
|
||||
|
||||
u32 flags;
|
||||
#define SECT_4K BIT(0) /* SPINOR_OP_BE_4K works uniformly */
|
||||
#define SPI_NOR_NO_ERASE BIT(1) /* No erase command needed */
|
||||
#define SST_WRITE BIT(2) /* use SST byte programming */
|
||||
#define SPI_NOR_NO_FR BIT(3) /* Can't do fastread */
|
||||
#define SECT_4K_PMC BIT(4) /* SPINOR_OP_BE_4K_PMC works uniformly */
|
||||
#define SPI_NOR_DUAL_READ BIT(5) /* Flash supports Dual Read */
|
||||
#define SPI_NOR_QUAD_READ BIT(6) /* Flash supports Quad Read */
|
||||
#define USE_FSR BIT(7) /* use flag status register */
|
||||
#define SPI_NOR_HAS_LOCK BIT(8) /* Flash supports lock/unlock via SR */
|
||||
#define SPI_NOR_HAS_TB BIT(9) /*
|
||||
* Flash SR has Top/Bottom (TB) protect
|
||||
* bit. Must be used with
|
||||
* SPI_NOR_HAS_LOCK.
|
||||
*/
|
||||
#define SPI_NOR_XSR_RDY BIT(10) /*
|
||||
* S3AN flashes have specific opcode to
|
||||
* read the status register.
|
||||
*/
|
||||
#define SPI_NOR_4B_OPCODES BIT(11) /*
|
||||
* Use dedicated 4byte address op codes
|
||||
* to support memory size above 128Mib.
|
||||
*/
|
||||
#define NO_CHIP_ERASE BIT(12) /* Chip does not support chip erase */
|
||||
#define SPI_NOR_SKIP_SFDP BIT(13) /* Skip parsing of SFDP tables */
|
||||
#define USE_CLSR BIT(14) /* use CLSR command */
|
||||
#define SPI_NOR_OCTAL_READ BIT(15) /* Flash supports Octal Read */
|
||||
#define SPI_NOR_TB_SR_BIT6 BIT(16) /*
|
||||
* Top/Bottom (TB) is bit 6 of
|
||||
* status register. Must be used with
|
||||
* SPI_NOR_HAS_TB.
|
||||
*/
|
||||
#define SPI_NOR_4BIT_BP BIT(17) /*
|
||||
* Flash SR has 4 bit fields (BP0-3)
|
||||
* for block protection.
|
||||
*/
|
||||
#define SPI_NOR_BP3_SR_BIT6 BIT(18) /*
|
||||
* BP3 is bit 6 of status register.
|
||||
* Must be used with SPI_NOR_4BIT_BP.
|
||||
*/
|
||||
|
||||
/* Part specific fixup hooks. */
|
||||
const struct spi_nor_fixups *fixups;
|
||||
};
|
||||
|
||||
/* Used when the "_ext_id" is two bytes at most */
|
||||
#define INFO(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \
|
||||
.id = { \
|
||||
((_jedec_id) >> 16) & 0xff, \
|
||||
((_jedec_id) >> 8) & 0xff, \
|
||||
(_jedec_id) & 0xff, \
|
||||
((_ext_id) >> 8) & 0xff, \
|
||||
(_ext_id) & 0xff, \
|
||||
}, \
|
||||
.id_len = (!(_jedec_id) ? 0 : (3 + ((_ext_id) ? 2 : 0))), \
|
||||
.sector_size = (_sector_size), \
|
||||
.n_sectors = (_n_sectors), \
|
||||
.page_size = 256, \
|
||||
.flags = (_flags),
|
||||
|
||||
#define INFO6(_jedec_id, _ext_id, _sector_size, _n_sectors, _flags) \
|
||||
.id = { \
|
||||
((_jedec_id) >> 16) & 0xff, \
|
||||
((_jedec_id) >> 8) & 0xff, \
|
||||
(_jedec_id) & 0xff, \
|
||||
((_ext_id) >> 16) & 0xff, \
|
||||
((_ext_id) >> 8) & 0xff, \
|
||||
(_ext_id) & 0xff, \
|
||||
}, \
|
||||
.id_len = 6, \
|
||||
.sector_size = (_sector_size), \
|
||||
.n_sectors = (_n_sectors), \
|
||||
.page_size = 256, \
|
||||
.flags = (_flags),
|
||||
|
||||
#define CAT25_INFO(_sector_size, _n_sectors, _page_size, _addr_width, _flags) \
|
||||
.sector_size = (_sector_size), \
|
||||
.n_sectors = (_n_sectors), \
|
||||
.page_size = (_page_size), \
|
||||
.addr_width = (_addr_width), \
|
||||
.flags = (_flags),
|
||||
|
||||
#define S3AN_INFO(_jedec_id, _n_sectors, _page_size) \
|
||||
.id = { \
|
||||
((_jedec_id) >> 16) & 0xff, \
|
||||
((_jedec_id) >> 8) & 0xff, \
|
||||
(_jedec_id) & 0xff \
|
||||
}, \
|
||||
.id_len = 3, \
|
||||
.sector_size = (8*_page_size), \
|
||||
.n_sectors = (_n_sectors), \
|
||||
.page_size = _page_size, \
|
||||
.addr_width = 3, \
|
||||
.flags = SPI_NOR_NO_FR | SPI_NOR_XSR_RDY,
|
||||
|
||||
/**
|
||||
* struct spi_nor_manufacturer - SPI NOR manufacturer object
|
||||
* @name: manufacturer name
|
||||
* @parts: array of parts supported by this manufacturer
|
||||
* @nparts: number of entries in the parts array
|
||||
* @fixups: hooks called at various points in time during spi_nor_scan()
|
||||
*/
|
||||
struct spi_nor_manufacturer {
|
||||
const char *name;
|
||||
const struct flash_info *parts;
|
||||
unsigned int nparts;
|
||||
const struct spi_nor_fixups *fixups;
|
||||
};
|
||||
|
||||
/* Manufacturer drivers. */
|
||||
extern const struct spi_nor_manufacturer spi_nor_atmel;
|
||||
extern const struct spi_nor_manufacturer spi_nor_catalyst;
|
||||
extern const struct spi_nor_manufacturer spi_nor_eon;
|
||||
extern const struct spi_nor_manufacturer spi_nor_esmt;
|
||||
extern const struct spi_nor_manufacturer spi_nor_everspin;
|
||||
extern const struct spi_nor_manufacturer spi_nor_fujitsu;
|
||||
extern const struct spi_nor_manufacturer spi_nor_gigadevice;
|
||||
extern const struct spi_nor_manufacturer spi_nor_intel;
|
||||
extern const struct spi_nor_manufacturer spi_nor_issi;
|
||||
extern const struct spi_nor_manufacturer spi_nor_macronix;
|
||||
extern const struct spi_nor_manufacturer spi_nor_micron;
|
||||
extern const struct spi_nor_manufacturer spi_nor_st;
|
||||
extern const struct spi_nor_manufacturer spi_nor_spansion;
|
||||
extern const struct spi_nor_manufacturer spi_nor_sst;
|
||||
extern const struct spi_nor_manufacturer spi_nor_winbond;
|
||||
extern const struct spi_nor_manufacturer spi_nor_xilinx;
|
||||
extern const struct spi_nor_manufacturer spi_nor_xmc;
|
||||
|
||||
int spi_nor_write_enable(struct spi_nor *nor);
|
||||
int spi_nor_write_disable(struct spi_nor *nor);
|
||||
int spi_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable);
|
||||
int spi_nor_write_ear(struct spi_nor *nor, u8 ear);
|
||||
int spi_nor_wait_till_ready(struct spi_nor *nor);
|
||||
int spi_nor_lock_and_prep(struct spi_nor *nor);
|
||||
void spi_nor_unlock_and_unprep(struct spi_nor *nor);
|
||||
int spi_nor_sr1_bit6_quad_enable(struct spi_nor *nor);
|
||||
int spi_nor_sr2_bit1_quad_enable(struct spi_nor *nor);
|
||||
int spi_nor_sr2_bit7_quad_enable(struct spi_nor *nor);
|
||||
|
||||
int spi_nor_xread_sr(struct spi_nor *nor, u8 *sr);
|
||||
ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
|
||||
u8 *buf);
|
||||
ssize_t spi_nor_write_data(struct spi_nor *nor, loff_t to, size_t len,
|
||||
const u8 *buf);
|
||||
|
||||
int spi_nor_hwcaps_read2cmd(u32 hwcaps);
|
||||
u8 spi_nor_convert_3to4_read(u8 opcode);
|
||||
void spi_nor_set_pp_settings(struct spi_nor_pp_command *pp, u8 opcode,
|
||||
enum spi_nor_protocol proto);
|
||||
|
||||
void spi_nor_set_erase_type(struct spi_nor_erase_type *erase, u32 size,
|
||||
u8 opcode);
|
||||
struct spi_nor_erase_region *
|
||||
spi_nor_region_next(struct spi_nor_erase_region *region);
|
||||
void spi_nor_init_uniform_erase_map(struct spi_nor_erase_map *map,
|
||||
u8 erase_mask, u64 flash_size);
|
||||
|
||||
int spi_nor_post_bfpt_fixups(struct spi_nor *nor,
|
||||
const struct sfdp_parameter_header *bfpt_header,
|
||||
const struct sfdp_bfpt *bfpt,
|
||||
struct spi_nor_flash_parameter *params);
|
||||
|
||||
static struct spi_nor __maybe_unused *mtd_to_spi_nor(struct mtd_info *mtd)
|
||||
{
|
||||
return mtd->priv;
|
||||
}
|
||||
|
||||
#endif /* __LINUX_MTD_SPI_NOR_INTERNAL_H */
|
34
drivers/mtd/spi-nor/eon.c
Normal file
34
drivers/mtd/spi-nor/eon.c
Normal file
@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info eon_parts[] = {
|
||||
/* EON -- en25xxx */
|
||||
{ "en25f32", INFO(0x1c3116, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "en25p32", INFO(0x1c2016, 0, 64 * 1024, 64, 0) },
|
||||
{ "en25q32b", INFO(0x1c3016, 0, 64 * 1024, 64, 0) },
|
||||
{ "en25p64", INFO(0x1c2017, 0, 64 * 1024, 128, 0) },
|
||||
{ "en25q64", INFO(0x1c3017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ "en25q80a", INFO(0x1c3014, 0, 64 * 1024, 16,
|
||||
SECT_4K | SPI_NOR_DUAL_READ) },
|
||||
{ "en25qh16", INFO(0x1c7015, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_DUAL_READ) },
|
||||
{ "en25qh32", INFO(0x1c7016, 0, 64 * 1024, 64, 0) },
|
||||
{ "en25qh64", INFO(0x1c7017, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_DUAL_READ) },
|
||||
{ "en25qh128", INFO(0x1c7018, 0, 64 * 1024, 256, 0) },
|
||||
{ "en25qh256", INFO(0x1c7019, 0, 64 * 1024, 512, 0) },
|
||||
{ "en25s64", INFO(0x1c3817, 0, 64 * 1024, 128, SECT_4K) },
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_eon = {
|
||||
.name = "eon",
|
||||
.parts = eon_parts,
|
||||
.nparts = ARRAY_SIZE(eon_parts),
|
||||
};
|
25
drivers/mtd/spi-nor/esmt.c
Normal file
25
drivers/mtd/spi-nor/esmt.c
Normal file
@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info esmt_parts[] = {
|
||||
/* ESMT */
|
||||
{ "f25l32pa", INFO(0x8c2016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_HAS_LOCK) },
|
||||
{ "f25l32qa", INFO(0x8c4116, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_HAS_LOCK) },
|
||||
{ "f25l64qa", INFO(0x8c4117, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_HAS_LOCK) },
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_esmt = {
|
||||
.name = "esmt",
|
||||
.parts = esmt_parts,
|
||||
.nparts = ARRAY_SIZE(esmt_parts),
|
||||
};
|
27
drivers/mtd/spi-nor/everspin.c
Normal file
27
drivers/mtd/spi-nor/everspin.c
Normal file
@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info everspin_parts[] = {
|
||||
/* Everspin */
|
||||
{ "mr25h128", CAT25_INFO(16 * 1024, 1, 256, 2,
|
||||
SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
{ "mr25h256", CAT25_INFO(32 * 1024, 1, 256, 2,
|
||||
SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
{ "mr25h10", CAT25_INFO(128 * 1024, 1, 256, 3,
|
||||
SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
{ "mr25h40", CAT25_INFO(512 * 1024, 1, 256, 3,
|
||||
SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_everspin = {
|
||||
.name = "everspin",
|
||||
.parts = everspin_parts,
|
||||
.nparts = ARRAY_SIZE(everspin_parts),
|
||||
};
|
20
drivers/mtd/spi-nor/fujitsu.c
Normal file
20
drivers/mtd/spi-nor/fujitsu.c
Normal file
@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info fujitsu_parts[] = {
|
||||
/* Fujitsu */
|
||||
{ "mb85rs1mt", INFO(0x047f27, 0, 128 * 1024, 1, SPI_NOR_NO_ERASE) },
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_fujitsu = {
|
||||
.name = "fujitsu",
|
||||
.parts = fujitsu_parts,
|
||||
.nparts = ARRAY_SIZE(fujitsu_parts),
|
||||
};
|
59
drivers/mtd/spi-nor/gigadevice.c
Normal file
59
drivers/mtd/spi-nor/gigadevice.c
Normal file
@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static void gd25q256_default_init(struct spi_nor *nor)
|
||||
{
|
||||
/*
|
||||
* Some manufacturer like GigaDevice may use different
|
||||
* bit to set QE on different memories, so the MFR can't
|
||||
* indicate the quad_enable method for this case, we need
|
||||
* to set it in the default_init fixup hook.
|
||||
*/
|
||||
nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
|
||||
}
|
||||
|
||||
static struct spi_nor_fixups gd25q256_fixups = {
|
||||
.default_init = gd25q256_default_init,
|
||||
};
|
||||
|
||||
static const struct flash_info gigadevice_parts[] = {
|
||||
{ "gd25q16", INFO(0xc84015, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
||||
{ "gd25q32", INFO(0xc84016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
||||
{ "gd25lq32", INFO(0xc86016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
||||
{ "gd25q64", INFO(0xc84017, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
||||
{ "gd25lq64c", INFO(0xc86017, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
||||
{ "gd25lq128d", INFO(0xc86018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
||||
{ "gd25q128", INFO(0xc84018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
||||
{ "gd25q256", INFO(0xc84019, 0, 64 * 1024, 512,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_4B_OPCODES | SPI_NOR_HAS_LOCK |
|
||||
SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6)
|
||||
.fixups = &gd25q256_fixups },
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_gigadevice = {
|
||||
.name = "gigadevice",
|
||||
.parts = gigadevice_parts,
|
||||
.nparts = ARRAY_SIZE(gigadevice_parts),
|
||||
};
|
32
drivers/mtd/spi-nor/intel.c
Normal file
32
drivers/mtd/spi-nor/intel.c
Normal file
@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info intel_parts[] = {
|
||||
/* Intel/Numonyx -- xxxs33b */
|
||||
{ "160s33b", INFO(0x898911, 0, 64 * 1024, 32, 0) },
|
||||
{ "320s33b", INFO(0x898912, 0, 64 * 1024, 64, 0) },
|
||||
{ "640s33b", INFO(0x898913, 0, 64 * 1024, 128, 0) },
|
||||
};
|
||||
|
||||
static void intel_default_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->flags |= SNOR_F_HAS_LOCK;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups intel_fixups = {
|
||||
.default_init = intel_default_init,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_intel = {
|
||||
.name = "intel",
|
||||
.parts = intel_parts,
|
||||
.nparts = ARRAY_SIZE(intel_parts),
|
||||
.fixups = &intel_fixups,
|
||||
};
|
83
drivers/mtd/spi-nor/issi.c
Normal file
83
drivers/mtd/spi-nor/issi.c
Normal file
@ -0,0 +1,83 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static int
|
||||
is25lp256_post_bfpt_fixups(struct spi_nor *nor,
|
||||
const struct sfdp_parameter_header *bfpt_header,
|
||||
const struct sfdp_bfpt *bfpt,
|
||||
struct spi_nor_flash_parameter *params)
|
||||
{
|
||||
/*
|
||||
* IS25LP256 supports 4B opcodes, but the BFPT advertises a
|
||||
* BFPT_DWORD1_ADDRESS_BYTES_3_ONLY address width.
|
||||
* Overwrite the address width advertised by the BFPT.
|
||||
*/
|
||||
if ((bfpt->dwords[BFPT_DWORD(1)] & BFPT_DWORD1_ADDRESS_BYTES_MASK) ==
|
||||
BFPT_DWORD1_ADDRESS_BYTES_3_ONLY)
|
||||
nor->addr_width = 4;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spi_nor_fixups is25lp256_fixups = {
|
||||
.post_bfpt = is25lp256_post_bfpt_fixups,
|
||||
};
|
||||
|
||||
static const struct flash_info issi_parts[] = {
|
||||
/* ISSI */
|
||||
{ "is25cd512", INFO(0x7f9d20, 0, 32 * 1024, 2, SECT_4K) },
|
||||
{ "is25lq040b", INFO(0x9d4013, 0, 64 * 1024, 8,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "is25lp016d", INFO(0x9d6015, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "is25lp080d", INFO(0x9d6014, 0, 64 * 1024, 16,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "is25lp032", INFO(0x9d6016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ) },
|
||||
{ "is25lp064", INFO(0x9d6017, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_DUAL_READ) },
|
||||
{ "is25lp128", INFO(0x9d6018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ) },
|
||||
{ "is25lp256", INFO(0x9d6019, 0, 64 * 1024, 512,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_4B_OPCODES)
|
||||
.fixups = &is25lp256_fixups },
|
||||
{ "is25wp032", INFO(0x9d7016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "is25wp064", INFO(0x9d7017, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "is25wp128", INFO(0x9d7018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "is25wp256", INFO(0x9d7019, 0, 64 * 1024, 512,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_4B_OPCODES)
|
||||
.fixups = &is25lp256_fixups },
|
||||
|
||||
/* PMC */
|
||||
{ "pm25lv512", INFO(0, 0, 32 * 1024, 2, SECT_4K_PMC) },
|
||||
{ "pm25lv010", INFO(0, 0, 32 * 1024, 4, SECT_4K_PMC) },
|
||||
{ "pm25lq032", INFO(0x7f9d46, 0, 64 * 1024, 64, SECT_4K) },
|
||||
};
|
||||
|
||||
static void issi_default_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups issi_fixups = {
|
||||
.default_init = issi_default_init,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_issi = {
|
||||
.name = "issi",
|
||||
.parts = issi_parts,
|
||||
.nparts = ARRAY_SIZE(issi_parts),
|
||||
.fixups = &issi_fixups,
|
||||
};
|
98
drivers/mtd/spi-nor/macronix.c
Normal file
98
drivers/mtd/spi-nor/macronix.c
Normal file
@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static int
|
||||
mx25l25635_post_bfpt_fixups(struct spi_nor *nor,
|
||||
const struct sfdp_parameter_header *bfpt_header,
|
||||
const struct sfdp_bfpt *bfpt,
|
||||
struct spi_nor_flash_parameter *params)
|
||||
{
|
||||
/*
|
||||
* MX25L25635F supports 4B opcodes but MX25L25635E does not.
|
||||
* Unfortunately, Macronix has re-used the same JEDEC ID for both
|
||||
* variants which prevents us from defining a new entry in the parts
|
||||
* table.
|
||||
* We need a way to differentiate MX25L25635E and MX25L25635F, and it
|
||||
* seems that the F version advertises support for Fast Read 4-4-4 in
|
||||
* its BFPT table.
|
||||
*/
|
||||
if (bfpt->dwords[BFPT_DWORD(5)] & BFPT_DWORD5_FAST_READ_4_4_4)
|
||||
nor->flags |= SNOR_F_4B_OPCODES;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spi_nor_fixups mx25l25635_fixups = {
|
||||
.post_bfpt = mx25l25635_post_bfpt_fixups,
|
||||
};
|
||||
|
||||
static const struct flash_info macronix_parts[] = {
|
||||
/* Macronix */
|
||||
{ "mx25l512e", INFO(0xc22010, 0, 64 * 1024, 1, SECT_4K) },
|
||||
{ "mx25l2005a", INFO(0xc22012, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ "mx25l4005a", INFO(0xc22013, 0, 64 * 1024, 8, SECT_4K) },
|
||||
{ "mx25l8005", INFO(0xc22014, 0, 64 * 1024, 16, 0) },
|
||||
{ "mx25l1606e", INFO(0xc22015, 0, 64 * 1024, 32, SECT_4K) },
|
||||
{ "mx25l3205d", INFO(0xc22016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "mx25l3255e", INFO(0xc29e16, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "mx25l6405d", INFO(0xc22017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ "mx25u2033e", INFO(0xc22532, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ "mx25u3235f", INFO(0xc22536, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ) },
|
||||
{ "mx25u4035", INFO(0xc22533, 0, 64 * 1024, 8, SECT_4K) },
|
||||
{ "mx25u8035", INFO(0xc22534, 0, 64 * 1024, 16, SECT_4K) },
|
||||
{ "mx25u6435f", INFO(0xc22537, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ "mx25l12805d", INFO(0xc22018, 0, 64 * 1024, 256, 0) },
|
||||
{ "mx25l12855e", INFO(0xc22618, 0, 64 * 1024, 256, 0) },
|
||||
{ "mx25r3235f", INFO(0xc22816, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ) },
|
||||
{ "mx25u12835f", INFO(0xc22538, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ) },
|
||||
{ "mx25l25635e", INFO(0xc22019, 0, 64 * 1024, 512,
|
||||
SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ)
|
||||
.fixups = &mx25l25635_fixups },
|
||||
{ "mx25u25635f", INFO(0xc22539, 0, 64 * 1024, 512,
|
||||
SECT_4K | SPI_NOR_4B_OPCODES) },
|
||||
{ "mx25v8035f", INFO(0xc22314, 0, 64 * 1024, 16,
|
||||
SECT_4K | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ) },
|
||||
{ "mx25l25655e", INFO(0xc22619, 0, 64 * 1024, 512, 0) },
|
||||
{ "mx66l51235l", INFO(0xc2201a, 0, 64 * 1024, 1024,
|
||||
SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_4B_OPCODES) },
|
||||
{ "mx66u51235f", INFO(0xc2253a, 0, 64 * 1024, 1024,
|
||||
SECT_4K | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
|
||||
{ "mx66l1g45g", INFO(0xc2201b, 0, 64 * 1024, 2048,
|
||||
SECT_4K | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ) },
|
||||
{ "mx66l1g55g", INFO(0xc2261b, 0, 64 * 1024, 2048,
|
||||
SPI_NOR_QUAD_READ) },
|
||||
};
|
||||
|
||||
static void macronix_default_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->params->quad_enable = spi_nor_sr1_bit6_quad_enable;
|
||||
nor->params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups macronix_fixups = {
|
||||
.default_init = macronix_default_init,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_macronix = {
|
||||
.name = "macronix",
|
||||
.parts = macronix_parts,
|
||||
.nparts = ARRAY_SIZE(macronix_parts),
|
||||
.fixups = ¯onix_fixups,
|
||||
};
|
157
drivers/mtd/spi-nor/micron-st.c
Normal file
157
drivers/mtd/spi-nor/micron-st.c
Normal file
@ -0,0 +1,157 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info micron_parts[] = {
|
||||
{ "mt35xu512aba", INFO(0x2c5b1a, 0, 128 * 1024, 512,
|
||||
SECT_4K | USE_FSR | SPI_NOR_OCTAL_READ |
|
||||
SPI_NOR_4B_OPCODES) },
|
||||
{ "mt35xu02g", INFO(0x2c5b1c, 0, 128 * 1024, 2048,
|
||||
SECT_4K | USE_FSR | SPI_NOR_OCTAL_READ |
|
||||
SPI_NOR_4B_OPCODES) },
|
||||
};
|
||||
|
||||
static const struct flash_info st_parts[] = {
|
||||
{ "n25q016a", INFO(0x20bb15, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
{ "n25q032", INFO(0x20ba16, 0, 64 * 1024, 64,
|
||||
SPI_NOR_QUAD_READ) },
|
||||
{ "n25q032a", INFO(0x20bb16, 0, 64 * 1024, 64,
|
||||
SPI_NOR_QUAD_READ) },
|
||||
{ "n25q064", INFO(0x20ba17, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
{ "n25q064a", INFO(0x20bb17, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_QUAD_READ) },
|
||||
{ "n25q128a11", INFO(0x20bb18, 0, 64 * 1024, 256,
|
||||
SECT_4K | USE_FSR | SPI_NOR_QUAD_READ) },
|
||||
{ "n25q128a13", INFO(0x20ba18, 0, 64 * 1024, 256,
|
||||
SECT_4K | USE_FSR | SPI_NOR_QUAD_READ) },
|
||||
{ "mt25ql256a", INFO6(0x20ba19, 0x104400, 64 * 1024, 512,
|
||||
SECT_4K | USE_FSR | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
|
||||
{ "n25q256a", INFO(0x20ba19, 0, 64 * 1024, 512, SECT_4K |
|
||||
USE_FSR | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ) },
|
||||
{ "mt25qu256a", INFO6(0x20bb19, 0x104400, 64 * 1024, 512,
|
||||
SECT_4K | USE_FSR | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
|
||||
{ "n25q256ax1", INFO(0x20bb19, 0, 64 * 1024, 512,
|
||||
SECT_4K | USE_FSR | SPI_NOR_QUAD_READ) },
|
||||
{ "mt25ql512a", INFO6(0x20ba20, 0x104400, 64 * 1024, 1024,
|
||||
SECT_4K | USE_FSR | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
|
||||
{ "n25q512ax3", INFO(0x20ba20, 0, 64 * 1024, 1024,
|
||||
SECT_4K | USE_FSR | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
|
||||
SPI_NOR_4BIT_BP | SPI_NOR_BP3_SR_BIT6) },
|
||||
{ "mt25qu512a", INFO6(0x20bb20, 0x104400, 64 * 1024, 1024,
|
||||
SECT_4K | USE_FSR | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ | SPI_NOR_4B_OPCODES) },
|
||||
{ "n25q512a", INFO(0x20bb20, 0, 64 * 1024, 1024,
|
||||
SECT_4K | USE_FSR | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB |
|
||||
SPI_NOR_4BIT_BP | SPI_NOR_BP3_SR_BIT6) },
|
||||
{ "n25q00", INFO(0x20ba21, 0, 64 * 1024, 2048,
|
||||
SECT_4K | USE_FSR | SPI_NOR_QUAD_READ |
|
||||
NO_CHIP_ERASE) },
|
||||
{ "n25q00a", INFO(0x20bb21, 0, 64 * 1024, 2048,
|
||||
SECT_4K | USE_FSR | SPI_NOR_QUAD_READ |
|
||||
NO_CHIP_ERASE) },
|
||||
{ "mt25ql02g", INFO(0x20ba22, 0, 64 * 1024, 4096,
|
||||
SECT_4K | USE_FSR | SPI_NOR_QUAD_READ |
|
||||
NO_CHIP_ERASE) },
|
||||
{ "mt25qu02g", INFO(0x20bb22, 0, 64 * 1024, 4096,
|
||||
SECT_4K | USE_FSR | SPI_NOR_QUAD_READ |
|
||||
NO_CHIP_ERASE) },
|
||||
|
||||
{ "m25p05", INFO(0x202010, 0, 32 * 1024, 2, 0) },
|
||||
{ "m25p10", INFO(0x202011, 0, 32 * 1024, 4, 0) },
|
||||
{ "m25p20", INFO(0x202012, 0, 64 * 1024, 4, 0) },
|
||||
{ "m25p40", INFO(0x202013, 0, 64 * 1024, 8, 0) },
|
||||
{ "m25p80", INFO(0x202014, 0, 64 * 1024, 16, 0) },
|
||||
{ "m25p16", INFO(0x202015, 0, 64 * 1024, 32, 0) },
|
||||
{ "m25p32", INFO(0x202016, 0, 64 * 1024, 64, 0) },
|
||||
{ "m25p64", INFO(0x202017, 0, 64 * 1024, 128, 0) },
|
||||
{ "m25p128", INFO(0x202018, 0, 256 * 1024, 64, 0) },
|
||||
|
||||
{ "m25p05-nonjedec", INFO(0, 0, 32 * 1024, 2, 0) },
|
||||
{ "m25p10-nonjedec", INFO(0, 0, 32 * 1024, 4, 0) },
|
||||
{ "m25p20-nonjedec", INFO(0, 0, 64 * 1024, 4, 0) },
|
||||
{ "m25p40-nonjedec", INFO(0, 0, 64 * 1024, 8, 0) },
|
||||
{ "m25p80-nonjedec", INFO(0, 0, 64 * 1024, 16, 0) },
|
||||
{ "m25p16-nonjedec", INFO(0, 0, 64 * 1024, 32, 0) },
|
||||
{ "m25p32-nonjedec", INFO(0, 0, 64 * 1024, 64, 0) },
|
||||
{ "m25p64-nonjedec", INFO(0, 0, 64 * 1024, 128, 0) },
|
||||
{ "m25p128-nonjedec", INFO(0, 0, 256 * 1024, 64, 0) },
|
||||
|
||||
{ "m45pe10", INFO(0x204011, 0, 64 * 1024, 2, 0) },
|
||||
{ "m45pe80", INFO(0x204014, 0, 64 * 1024, 16, 0) },
|
||||
{ "m45pe16", INFO(0x204015, 0, 64 * 1024, 32, 0) },
|
||||
|
||||
{ "m25pe20", INFO(0x208012, 0, 64 * 1024, 4, 0) },
|
||||
{ "m25pe80", INFO(0x208014, 0, 64 * 1024, 16, 0) },
|
||||
{ "m25pe16", INFO(0x208015, 0, 64 * 1024, 32, SECT_4K) },
|
||||
|
||||
{ "m25px16", INFO(0x207115, 0, 64 * 1024, 32, SECT_4K) },
|
||||
{ "m25px32", INFO(0x207116, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "m25px32-s0", INFO(0x207316, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "m25px32-s1", INFO(0x206316, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "m25px64", INFO(0x207117, 0, 64 * 1024, 128, 0) },
|
||||
{ "m25px80", INFO(0x207114, 0, 64 * 1024, 16, 0) },
|
||||
};
|
||||
|
||||
/**
|
||||
* st_micron_set_4byte_addr_mode() - Set 4-byte address mode for ST and Micron
|
||||
* flashes.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
* @enable: true to enter the 4-byte address mode, false to exit the 4-byte
|
||||
* address mode.
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
static int st_micron_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = spi_nor_write_enable(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = spi_nor_set_4byte_addr_mode(nor, enable);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return spi_nor_write_disable(nor);
|
||||
}
|
||||
|
||||
static void micron_st_default_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->flags |= SNOR_F_HAS_LOCK;
|
||||
nor->flags &= ~SNOR_F_HAS_16BIT_SR;
|
||||
nor->params->quad_enable = NULL;
|
||||
nor->params->set_4byte_addr_mode = st_micron_set_4byte_addr_mode;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups micron_st_fixups = {
|
||||
.default_init = micron_st_default_init,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_micron = {
|
||||
.name = "micron",
|
||||
.parts = micron_parts,
|
||||
.nparts = ARRAY_SIZE(micron_parts),
|
||||
.fixups = µn_st_fixups,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_st = {
|
||||
.name = "st",
|
||||
.parts = st_parts,
|
||||
.nparts = ARRAY_SIZE(st_parts),
|
||||
.fixups = µn_st_fixups,
|
||||
};
|
1204
drivers/mtd/spi-nor/sfdp.c
Normal file
1204
drivers/mtd/spi-nor/sfdp.c
Normal file
File diff suppressed because it is too large
Load Diff
98
drivers/mtd/spi-nor/sfdp.h
Normal file
98
drivers/mtd/spi-nor/sfdp.h
Normal file
@ -0,0 +1,98 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_MTD_SFDP_H
|
||||
#define __LINUX_MTD_SFDP_H
|
||||
|
||||
/* Basic Flash Parameter Table */
|
||||
|
||||
/*
|
||||
* JESD216 rev B defines a Basic Flash Parameter Table of 16 DWORDs.
|
||||
* They are indexed from 1 but C arrays are indexed from 0.
|
||||
*/
|
||||
#define BFPT_DWORD(i) ((i) - 1)
|
||||
#define BFPT_DWORD_MAX 16
|
||||
|
||||
struct sfdp_bfpt {
|
||||
u32 dwords[BFPT_DWORD_MAX];
|
||||
};
|
||||
|
||||
/* The first version of JESD216 defined only 9 DWORDs. */
|
||||
#define BFPT_DWORD_MAX_JESD216 9
|
||||
|
||||
/* 1st DWORD. */
|
||||
#define BFPT_DWORD1_FAST_READ_1_1_2 BIT(16)
|
||||
#define BFPT_DWORD1_ADDRESS_BYTES_MASK GENMASK(18, 17)
|
||||
#define BFPT_DWORD1_ADDRESS_BYTES_3_ONLY (0x0UL << 17)
|
||||
#define BFPT_DWORD1_ADDRESS_BYTES_3_OR_4 (0x1UL << 17)
|
||||
#define BFPT_DWORD1_ADDRESS_BYTES_4_ONLY (0x2UL << 17)
|
||||
#define BFPT_DWORD1_DTR BIT(19)
|
||||
#define BFPT_DWORD1_FAST_READ_1_2_2 BIT(20)
|
||||
#define BFPT_DWORD1_FAST_READ_1_4_4 BIT(21)
|
||||
#define BFPT_DWORD1_FAST_READ_1_1_4 BIT(22)
|
||||
|
||||
/* 5th DWORD. */
|
||||
#define BFPT_DWORD5_FAST_READ_2_2_2 BIT(0)
|
||||
#define BFPT_DWORD5_FAST_READ_4_4_4 BIT(4)
|
||||
|
||||
/* 11th DWORD. */
|
||||
#define BFPT_DWORD11_PAGE_SIZE_SHIFT 4
|
||||
#define BFPT_DWORD11_PAGE_SIZE_MASK GENMASK(7, 4)
|
||||
|
||||
/* 15th DWORD. */
|
||||
|
||||
/*
|
||||
* (from JESD216 rev B)
|
||||
* Quad Enable Requirements (QER):
|
||||
* - 000b: Device does not have a QE bit. Device detects 1-1-4 and 1-4-4
|
||||
* reads based on instruction. DQ3/HOLD# functions are hold during
|
||||
* instruction phase.
|
||||
* - 001b: QE is bit 1 of status register 2. It is set via Write Status with
|
||||
* two data bytes where bit 1 of the second byte is one.
|
||||
* [...]
|
||||
* Writing only one byte to the status register has the side-effect of
|
||||
* clearing status register 2, including the QE bit. The 100b code is
|
||||
* used if writing one byte to the status register does not modify
|
||||
* status register 2.
|
||||
* - 010b: QE is bit 6 of status register 1. It is set via Write Status with
|
||||
* one data byte where bit 6 is one.
|
||||
* [...]
|
||||
* - 011b: QE is bit 7 of status register 2. It is set via Write status
|
||||
* register 2 instruction 3Eh with one data byte where bit 7 is one.
|
||||
* [...]
|
||||
* The status register 2 is read using instruction 3Fh.
|
||||
* - 100b: QE is bit 1 of status register 2. It is set via Write Status with
|
||||
* two data bytes where bit 1 of the second byte is one.
|
||||
* [...]
|
||||
* In contrast to the 001b code, writing one byte to the status
|
||||
* register does not modify status register 2.
|
||||
* - 101b: QE is bit 1 of status register 2. Status register 1 is read using
|
||||
* Read Status instruction 05h. Status register2 is read using
|
||||
* instruction 35h. QE is set via Write Status instruction 01h with
|
||||
* two data bytes where bit 1 of the second byte is one.
|
||||
* [...]
|
||||
*/
|
||||
#define BFPT_DWORD15_QER_MASK GENMASK(22, 20)
|
||||
#define BFPT_DWORD15_QER_NONE (0x0UL << 20) /* Micron */
|
||||
#define BFPT_DWORD15_QER_SR2_BIT1_BUGGY (0x1UL << 20)
|
||||
#define BFPT_DWORD15_QER_SR1_BIT6 (0x2UL << 20) /* Macronix */
|
||||
#define BFPT_DWORD15_QER_SR2_BIT7 (0x3UL << 20)
|
||||
#define BFPT_DWORD15_QER_SR2_BIT1_NO_RD (0x4UL << 20)
|
||||
#define BFPT_DWORD15_QER_SR2_BIT1 (0x5UL << 20) /* Spansion */
|
||||
|
||||
struct sfdp_parameter_header {
|
||||
u8 id_lsb;
|
||||
u8 minor;
|
||||
u8 major;
|
||||
u8 length; /* in double words */
|
||||
u8 parameter_table_pointer[3]; /* byte address */
|
||||
u8 id_msb;
|
||||
};
|
||||
|
||||
int spi_nor_parse_sfdp(struct spi_nor *nor,
|
||||
struct spi_nor_flash_parameter *params);
|
||||
|
||||
#endif /* __LINUX_MTD_SFDP_H */
|
95
drivers/mtd/spi-nor/spansion.c
Normal file
95
drivers/mtd/spi-nor/spansion.c
Normal file
@ -0,0 +1,95 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info spansion_parts[] = {
|
||||
/* Spansion/Cypress -- single (large) sector size only, at least
|
||||
* for the chips listed here (without boot sectors).
|
||||
*/
|
||||
{ "s25sl032p", INFO(0x010215, 0x4d00, 64 * 1024, 64,
|
||||
SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "s25sl064p", INFO(0x010216, 0x4d00, 64 * 1024, 128,
|
||||
SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "s25fl128s0", INFO6(0x012018, 0x4d0080, 256 * 1024, 64,
|
||||
SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
USE_CLSR) },
|
||||
{ "s25fl128s1", INFO6(0x012018, 0x4d0180, 64 * 1024, 256,
|
||||
SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
USE_CLSR) },
|
||||
{ "s25fl256s0", INFO(0x010219, 0x4d00, 256 * 1024, 128, USE_CLSR) },
|
||||
{ "s25fl256s1", INFO(0x010219, 0x4d01, 64 * 1024, 512,
|
||||
SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
USE_CLSR) },
|
||||
{ "s25fl512s", INFO6(0x010220, 0x4d0080, 256 * 1024, 256,
|
||||
SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | USE_CLSR) },
|
||||
{ "s25fs512s", INFO6(0x010220, 0x4d0081, 256 * 1024, 256,
|
||||
SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
USE_CLSR) },
|
||||
{ "s70fl01gs", INFO(0x010221, 0x4d00, 256 * 1024, 256, 0) },
|
||||
{ "s25sl12800", INFO(0x012018, 0x0300, 256 * 1024, 64, 0) },
|
||||
{ "s25sl12801", INFO(0x012018, 0x0301, 64 * 1024, 256, 0) },
|
||||
{ "s25fl129p0", INFO(0x012018, 0x4d00, 256 * 1024, 64,
|
||||
SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
USE_CLSR) },
|
||||
{ "s25fl129p1", INFO(0x012018, 0x4d01, 64 * 1024, 256,
|
||||
SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
USE_CLSR) },
|
||||
{ "s25sl004a", INFO(0x010212, 0, 64 * 1024, 8, 0) },
|
||||
{ "s25sl008a", INFO(0x010213, 0, 64 * 1024, 16, 0) },
|
||||
{ "s25sl016a", INFO(0x010214, 0, 64 * 1024, 32, 0) },
|
||||
{ "s25sl032a", INFO(0x010215, 0, 64 * 1024, 64, 0) },
|
||||
{ "s25sl064a", INFO(0x010216, 0, 64 * 1024, 128, 0) },
|
||||
{ "s25fl004k", INFO(0xef4013, 0, 64 * 1024, 8,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "s25fl008k", INFO(0xef4014, 0, 64 * 1024, 16,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "s25fl016k", INFO(0xef4015, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "s25fl064k", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ "s25fl116k", INFO(0x014015, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "s25fl132k", INFO(0x014016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "s25fl164k", INFO(0x014017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ "s25fl204k", INFO(0x014013, 0, 64 * 1024, 8,
|
||||
SECT_4K | SPI_NOR_DUAL_READ) },
|
||||
{ "s25fl208k", INFO(0x014014, 0, 64 * 1024, 16,
|
||||
SECT_4K | SPI_NOR_DUAL_READ) },
|
||||
{ "s25fl064l", INFO(0x016017, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_4B_OPCODES) },
|
||||
{ "s25fl128l", INFO(0x016018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_4B_OPCODES) },
|
||||
{ "s25fl256l", INFO(0x016019, 0, 64 * 1024, 512,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_4B_OPCODES) },
|
||||
};
|
||||
|
||||
static void spansion_post_sfdp_fixups(struct spi_nor *nor)
|
||||
{
|
||||
if (nor->params->size <= SZ_16M)
|
||||
return;
|
||||
|
||||
nor->flags |= SNOR_F_4B_OPCODES;
|
||||
/* No small sector erase for 4-byte command set */
|
||||
nor->erase_opcode = SPINOR_OP_SE;
|
||||
nor->mtd.erasesize = nor->info->sector_size;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups spansion_fixups = {
|
||||
.post_sfdp = spansion_post_sfdp_fixups,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_spansion = {
|
||||
.name = "spansion",
|
||||
.parts = spansion_parts,
|
||||
.nparts = ARRAY_SIZE(spansion_parts),
|
||||
.fixups = &spansion_fixups,
|
||||
};
|
File diff suppressed because it is too large
Load Diff
151
drivers/mtd/spi-nor/sst.c
Normal file
151
drivers/mtd/spi-nor/sst.c
Normal file
@ -0,0 +1,151 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info sst_parts[] = {
|
||||
/* SST -- large erase sizes are "overlays", "sectors" are 4K */
|
||||
{ "sst25vf040b", INFO(0xbf258d, 0, 64 * 1024, 8,
|
||||
SECT_4K | SST_WRITE) },
|
||||
{ "sst25vf080b", INFO(0xbf258e, 0, 64 * 1024, 16,
|
||||
SECT_4K | SST_WRITE) },
|
||||
{ "sst25vf016b", INFO(0xbf2541, 0, 64 * 1024, 32,
|
||||
SECT_4K | SST_WRITE) },
|
||||
{ "sst25vf032b", INFO(0xbf254a, 0, 64 * 1024, 64,
|
||||
SECT_4K | SST_WRITE) },
|
||||
{ "sst25vf064c", INFO(0xbf254b, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ "sst25wf512", INFO(0xbf2501, 0, 64 * 1024, 1,
|
||||
SECT_4K | SST_WRITE) },
|
||||
{ "sst25wf010", INFO(0xbf2502, 0, 64 * 1024, 2,
|
||||
SECT_4K | SST_WRITE) },
|
||||
{ "sst25wf020", INFO(0xbf2503, 0, 64 * 1024, 4,
|
||||
SECT_4K | SST_WRITE) },
|
||||
{ "sst25wf020a", INFO(0x621612, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ "sst25wf040b", INFO(0x621613, 0, 64 * 1024, 8, SECT_4K) },
|
||||
{ "sst25wf040", INFO(0xbf2504, 0, 64 * 1024, 8,
|
||||
SECT_4K | SST_WRITE) },
|
||||
{ "sst25wf080", INFO(0xbf2505, 0, 64 * 1024, 16,
|
||||
SECT_4K | SST_WRITE) },
|
||||
{ "sst26wf016b", INFO(0xbf2651, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ) },
|
||||
{ "sst26vf016b", INFO(0xbf2641, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_DUAL_READ) },
|
||||
{ "sst26vf064b", INFO(0xbf2643, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ) },
|
||||
};
|
||||
|
||||
static int sst_write(struct mtd_info *mtd, loff_t to, size_t len,
|
||||
size_t *retlen, const u_char *buf)
|
||||
{
|
||||
struct spi_nor *nor = mtd_to_spi_nor(mtd);
|
||||
size_t actual = 0;
|
||||
int ret;
|
||||
|
||||
dev_dbg(nor->dev, "to 0x%08x, len %zd\n", (u32)to, len);
|
||||
|
||||
ret = spi_nor_lock_and_prep(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = spi_nor_write_enable(nor);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
nor->sst_write_second = false;
|
||||
|
||||
/* Start write from odd address. */
|
||||
if (to % 2) {
|
||||
nor->program_opcode = SPINOR_OP_BP;
|
||||
|
||||
/* write one byte. */
|
||||
ret = spi_nor_write_data(nor, to, 1, buf);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
WARN(ret != 1, "While writing 1 byte written %i bytes\n", ret);
|
||||
ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
to++;
|
||||
actual++;
|
||||
}
|
||||
|
||||
/* Write out most of the data here. */
|
||||
for (; actual < len - 1; actual += 2) {
|
||||
nor->program_opcode = SPINOR_OP_AAI_WP;
|
||||
|
||||
/* write two bytes. */
|
||||
ret = spi_nor_write_data(nor, to, 2, buf + actual);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
WARN(ret != 2, "While writing 2 bytes written %i bytes\n", ret);
|
||||
ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto out;
|
||||
to += 2;
|
||||
nor->sst_write_second = true;
|
||||
}
|
||||
nor->sst_write_second = false;
|
||||
|
||||
ret = spi_nor_write_disable(nor);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* Write out trailing byte if it exists. */
|
||||
if (actual != len) {
|
||||
ret = spi_nor_write_enable(nor);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
nor->program_opcode = SPINOR_OP_BP;
|
||||
ret = spi_nor_write_data(nor, to, 1, buf + actual);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
WARN(ret != 1, "While writing 1 byte written %i bytes\n", ret);
|
||||
ret = spi_nor_wait_till_ready(nor);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
actual += 1;
|
||||
|
||||
ret = spi_nor_write_disable(nor);
|
||||
}
|
||||
out:
|
||||
*retlen += actual;
|
||||
spi_nor_unlock_and_unprep(nor);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sst_default_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->flags |= SNOR_F_HAS_LOCK;
|
||||
}
|
||||
|
||||
static void sst_post_sfdp_fixups(struct spi_nor *nor)
|
||||
{
|
||||
if (nor->info->flags & SST_WRITE)
|
||||
nor->mtd._write = sst_write;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups sst_fixups = {
|
||||
.default_init = sst_default_init,
|
||||
.post_sfdp = sst_post_sfdp_fixups,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_sst = {
|
||||
.name = "sst",
|
||||
.parts = sst_parts,
|
||||
.nparts = ARRAY_SIZE(sst_parts),
|
||||
.fixups = &sst_fixups,
|
||||
};
|
112
drivers/mtd/spi-nor/winbond.c
Normal file
112
drivers/mtd/spi-nor/winbond.c
Normal file
@ -0,0 +1,112 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info winbond_parts[] = {
|
||||
/* Winbond -- w25x "blocks" are 64K, "sectors" are 4KiB */
|
||||
{ "w25x05", INFO(0xef3010, 0, 64 * 1024, 1, SECT_4K) },
|
||||
{ "w25x10", INFO(0xef3011, 0, 64 * 1024, 2, SECT_4K) },
|
||||
{ "w25x20", INFO(0xef3012, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ "w25x40", INFO(0xef3013, 0, 64 * 1024, 8, SECT_4K) },
|
||||
{ "w25x80", INFO(0xef3014, 0, 64 * 1024, 16, SECT_4K) },
|
||||
{ "w25x16", INFO(0xef3015, 0, 64 * 1024, 32, SECT_4K) },
|
||||
{ "w25q16dw", INFO(0xef6015, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
||||
{ "w25x32", INFO(0xef3016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "w25q16jv-im/jm", INFO(0xef7015, 0, 64 * 1024, 32,
|
||||
SECT_4K | SPI_NOR_DUAL_READ |
|
||||
SPI_NOR_QUAD_READ | SPI_NOR_HAS_LOCK |
|
||||
SPI_NOR_HAS_TB) },
|
||||
{ "w25q20cl", INFO(0xef4012, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ "w25q20bw", INFO(0xef5012, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ "w25q20ew", INFO(0xef6012, 0, 64 * 1024, 4, SECT_4K) },
|
||||
{ "w25q32", INFO(0xef4016, 0, 64 * 1024, 64, SECT_4K) },
|
||||
{ "w25q32dw", INFO(0xef6016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
||||
{ "w25q32jv", INFO(0xef7016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB)
|
||||
},
|
||||
{ "w25q32jwm", INFO(0xef8016, 0, 64 * 1024, 64,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
||||
{ "w25x64", INFO(0xef3017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ "w25q64", INFO(0xef4017, 0, 64 * 1024, 128, SECT_4K) },
|
||||
{ "w25q64dw", INFO(0xef6017, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
||||
{ "w25q128fw", INFO(0xef6018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
||||
{ "w25q128jv", INFO(0xef7018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB) },
|
||||
{ "w25q80", INFO(0xef5014, 0, 64 * 1024, 16, SECT_4K) },
|
||||
{ "w25q80bl", INFO(0xef4014, 0, 64 * 1024, 16, SECT_4K) },
|
||||
{ "w25q128", INFO(0xef4018, 0, 64 * 1024, 256, SECT_4K) },
|
||||
{ "w25q256", INFO(0xef4019, 0, 64 * 1024, 512,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ |
|
||||
SPI_NOR_4B_OPCODES) },
|
||||
{ "w25q256jvm", INFO(0xef7019, 0, 64 * 1024, 512,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "w25q256jw", INFO(0xef6019, 0, 64 * 1024, 512,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "w25m512jv", INFO(0xef7119, 0, 64 * 1024, 1024,
|
||||
SECT_4K | SPI_NOR_QUAD_READ | SPI_NOR_DUAL_READ) },
|
||||
};
|
||||
|
||||
/**
|
||||
* winbond_set_4byte_addr_mode() - Set 4-byte address mode for Winbond flashes.
|
||||
* @nor: pointer to 'struct spi_nor'.
|
||||
* @enable: true to enter the 4-byte address mode, false to exit the 4-byte
|
||||
* address mode.
|
||||
*
|
||||
* Return: 0 on success, -errno otherwise.
|
||||
*/
|
||||
static int winbond_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = spi_nor_set_4byte_addr_mode(nor, enable);
|
||||
if (ret || enable)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* On Winbond W25Q256FV, leaving 4byte mode causes the Extended Address
|
||||
* Register to be set to 1, so all 3-byte-address reads come from the
|
||||
* second 16M. We must clear the register to enable normal behavior.
|
||||
*/
|
||||
ret = spi_nor_write_enable(nor);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = spi_nor_write_ear(nor, 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return spi_nor_write_disable(nor);
|
||||
}
|
||||
|
||||
static void winbond_default_init(struct spi_nor *nor)
|
||||
{
|
||||
nor->params->set_4byte_addr_mode = winbond_set_4byte_addr_mode;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups winbond_fixups = {
|
||||
.default_init = winbond_default_init,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_winbond = {
|
||||
.name = "winbond",
|
||||
.parts = winbond_parts,
|
||||
.nparts = ARRAY_SIZE(winbond_parts),
|
||||
.fixups = &winbond_fixups,
|
||||
};
|
94
drivers/mtd/spi-nor/xilinx.c
Normal file
94
drivers/mtd/spi-nor/xilinx.c
Normal file
@ -0,0 +1,94 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info xilinx_parts[] = {
|
||||
/* Xilinx S3AN Internal Flash */
|
||||
{ "3S50AN", S3AN_INFO(0x1f2200, 64, 264) },
|
||||
{ "3S200AN", S3AN_INFO(0x1f2400, 256, 264) },
|
||||
{ "3S400AN", S3AN_INFO(0x1f2400, 256, 264) },
|
||||
{ "3S700AN", S3AN_INFO(0x1f2500, 512, 264) },
|
||||
{ "3S1400AN", S3AN_INFO(0x1f2600, 512, 528) },
|
||||
};
|
||||
|
||||
/*
|
||||
* This code converts an address to the Default Address Mode, that has non
|
||||
* power of two page sizes. We must support this mode because it is the default
|
||||
* mode supported by Xilinx tools, it can access the whole flash area and
|
||||
* changing over to the Power-of-two mode is irreversible and corrupts the
|
||||
* original data.
|
||||
* Addr can safely be unsigned int, the biggest S3AN device is smaller than
|
||||
* 4 MiB.
|
||||
*/
|
||||
static u32 s3an_convert_addr(struct spi_nor *nor, u32 addr)
|
||||
{
|
||||
u32 offset, page;
|
||||
|
||||
offset = addr % nor->page_size;
|
||||
page = addr / nor->page_size;
|
||||
page <<= (nor->page_size > 512) ? 10 : 9;
|
||||
|
||||
return page | offset;
|
||||
}
|
||||
|
||||
static int xilinx_nor_setup(struct spi_nor *nor,
|
||||
const struct spi_nor_hwcaps *hwcaps)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = spi_nor_xread_sr(nor, nor->bouncebuf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
nor->erase_opcode = SPINOR_OP_XSE;
|
||||
nor->program_opcode = SPINOR_OP_XPP;
|
||||
nor->read_opcode = SPINOR_OP_READ;
|
||||
nor->flags |= SNOR_F_NO_OP_CHIP_ERASE;
|
||||
|
||||
/*
|
||||
* This flashes have a page size of 264 or 528 bytes (known as
|
||||
* Default addressing mode). It can be changed to a more standard
|
||||
* Power of two mode where the page size is 256/512. This comes
|
||||
* with a price: there is 3% less of space, the data is corrupted
|
||||
* and the page size cannot be changed back to default addressing
|
||||
* mode.
|
||||
*
|
||||
* The current addressing mode can be read from the XRDSR register
|
||||
* and should not be changed, because is a destructive operation.
|
||||
*/
|
||||
if (nor->bouncebuf[0] & XSR_PAGESIZE) {
|
||||
/* Flash in Power of 2 mode */
|
||||
nor->page_size = (nor->page_size == 264) ? 256 : 512;
|
||||
nor->mtd.writebufsize = nor->page_size;
|
||||
nor->mtd.size = 8 * nor->page_size * nor->info->n_sectors;
|
||||
nor->mtd.erasesize = 8 * nor->page_size;
|
||||
} else {
|
||||
/* Flash in Default addressing mode */
|
||||
nor->params->convert_addr = s3an_convert_addr;
|
||||
nor->mtd.erasesize = nor->info->sector_size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void xilinx_post_sfdp_fixups(struct spi_nor *nor)
|
||||
{
|
||||
nor->params->setup = xilinx_nor_setup;
|
||||
}
|
||||
|
||||
static const struct spi_nor_fixups xilinx_fixups = {
|
||||
.post_sfdp = xilinx_post_sfdp_fixups,
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_xilinx = {
|
||||
.name = "xilinx",
|
||||
.parts = xilinx_parts,
|
||||
.nparts = ARRAY_SIZE(xilinx_parts),
|
||||
.fixups = &xilinx_fixups,
|
||||
};
|
23
drivers/mtd/spi-nor/xmc.c
Normal file
23
drivers/mtd/spi-nor/xmc.c
Normal file
@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Copyright (C) 2005, Intec Automation Inc.
|
||||
* Copyright (C) 2014, Freescale Semiconductor, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/mtd/spi-nor.h>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
static const struct flash_info xmc_parts[] = {
|
||||
/* XMC (Wuhan Xinxin Semiconductor Manufacturing Corp.) */
|
||||
{ "XM25QH64A", INFO(0x207017, 0, 64 * 1024, 128,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
{ "XM25QH128A", INFO(0x207018, 0, 64 * 1024, 256,
|
||||
SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
|
||||
};
|
||||
|
||||
const struct spi_nor_manufacturer spi_nor_xmc = {
|
||||
.name = "xmc",
|
||||
.parts = xmc_parts,
|
||||
.nparts = ARRAY_SIZE(xmc_parts),
|
||||
};
|
@ -1059,7 +1059,7 @@ static int scan_peb(struct ubi_device *ubi, struct ubi_attach_info *ai,
|
||||
* be a result of power cut during erasure.
|
||||
*/
|
||||
ai->maybe_bad_peb_count += 1;
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case UBI_IO_BAD_HDR:
|
||||
/*
|
||||
* If we're facing a bad VID header we have to drop *all*
|
||||
|
@ -1342,10 +1342,10 @@ static int bytes_str_to_int(const char *str)
|
||||
switch (*endp) {
|
||||
case 'G':
|
||||
result *= 1024;
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 'M':
|
||||
result *= 1024;
|
||||
/* fall through */
|
||||
fallthrough;
|
||||
case 'K':
|
||||
result *= 1024;
|
||||
if (endp[1] == 'i' && endp[2] == 'B')
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/uio.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/of.h>
|
||||
@ -194,10 +195,43 @@ struct mtd_debug_info {
|
||||
const char *partid;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mtd_part - MTD partition specific fields
|
||||
*
|
||||
* @node: list node used to add an MTD partition to the parent partition list
|
||||
* @offset: offset of the partition relatively to the parent offset
|
||||
* @flags: original flags (before the mtdpart logic decided to tweak them based
|
||||
* on flash constraints, like eraseblock/pagesize alignment)
|
||||
*
|
||||
* This struct is embedded in mtd_info and contains partition-specific
|
||||
* properties/fields.
|
||||
*/
|
||||
struct mtd_part {
|
||||
struct list_head node;
|
||||
u64 offset;
|
||||
u32 flags;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct mtd_master - MTD master specific fields
|
||||
*
|
||||
* @partitions_lock: lock protecting accesses to the partition list. Protects
|
||||
* not only the master partition list, but also all
|
||||
* sub-partitions.
|
||||
* @suspended: et to 1 when the device is suspended, 0 otherwise
|
||||
*
|
||||
* This struct is embedded in mtd_info and contains master-specific
|
||||
* properties/fields. The master is the root MTD device from the MTD partition
|
||||
* point of view.
|
||||
*/
|
||||
struct mtd_master {
|
||||
struct mutex partitions_lock;
|
||||
unsigned int suspended : 1;
|
||||
};
|
||||
|
||||
struct mtd_info {
|
||||
u_char type;
|
||||
uint32_t flags;
|
||||
uint32_t orig_flags; /* Flags as before running mtd checks */
|
||||
uint64_t size; // Total size of the MTD
|
||||
|
||||
/* "Major" erase size for the device. Naïve users may take this
|
||||
@ -339,8 +373,52 @@ struct mtd_info {
|
||||
int usecount;
|
||||
struct mtd_debug_info dbg;
|
||||
struct nvmem_device *nvmem;
|
||||
|
||||
/*
|
||||
* Parent device from the MTD partition point of view.
|
||||
*
|
||||
* MTD masters do not have any parent, MTD partitions do. The parent
|
||||
* MTD device can itself be a partition.
|
||||
*/
|
||||
struct mtd_info *parent;
|
||||
|
||||
/* List of partitions attached to this MTD device */
|
||||
struct list_head partitions;
|
||||
|
||||
union {
|
||||
struct mtd_part part;
|
||||
struct mtd_master master;
|
||||
};
|
||||
};
|
||||
|
||||
static inline struct mtd_info *mtd_get_master(struct mtd_info *mtd)
|
||||
{
|
||||
while (mtd->parent)
|
||||
mtd = mtd->parent;
|
||||
|
||||
return mtd;
|
||||
}
|
||||
|
||||
static inline u64 mtd_get_master_ofs(struct mtd_info *mtd, u64 ofs)
|
||||
{
|
||||
while (mtd->parent) {
|
||||
ofs += mtd->part.offset;
|
||||
mtd = mtd->parent;
|
||||
}
|
||||
|
||||
return ofs;
|
||||
}
|
||||
|
||||
static inline bool mtd_is_partition(const struct mtd_info *mtd)
|
||||
{
|
||||
return mtd->parent;
|
||||
}
|
||||
|
||||
static inline bool mtd_has_partitions(const struct mtd_info *mtd)
|
||||
{
|
||||
return !list_empty(&mtd->partitions);
|
||||
}
|
||||
|
||||
int mtd_ooblayout_ecc(struct mtd_info *mtd, int section,
|
||||
struct mtd_oob_region *oobecc);
|
||||
int mtd_ooblayout_find_eccregion(struct mtd_info *mtd, int eccbyte,
|
||||
@ -392,13 +470,16 @@ static inline u32 mtd_oobavail(struct mtd_info *mtd, struct mtd_oob_ops *ops)
|
||||
static inline int mtd_max_bad_blocks(struct mtd_info *mtd,
|
||||
loff_t ofs, size_t len)
|
||||
{
|
||||
if (!mtd->_max_bad_blocks)
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
if (!master->_max_bad_blocks)
|
||||
return -ENOTSUPP;
|
||||
|
||||
if (mtd->size < (len + ofs) || ofs < 0)
|
||||
return -EINVAL;
|
||||
|
||||
return mtd->_max_bad_blocks(mtd, ofs, len);
|
||||
return master->_max_bad_blocks(master, mtd_get_master_ofs(mtd, ofs),
|
||||
len);
|
||||
}
|
||||
|
||||
int mtd_wunit_to_pairing_info(struct mtd_info *mtd, int wunit,
|
||||
@ -439,8 +520,10 @@ int mtd_writev(struct mtd_info *mtd, const struct kvec *vecs,
|
||||
|
||||
static inline void mtd_sync(struct mtd_info *mtd)
|
||||
{
|
||||
if (mtd->_sync)
|
||||
mtd->_sync(mtd);
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
if (master->_sync)
|
||||
master->_sync(master);
|
||||
}
|
||||
|
||||
int mtd_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len);
|
||||
@ -452,13 +535,31 @@ int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs);
|
||||
|
||||
static inline int mtd_suspend(struct mtd_info *mtd)
|
||||
{
|
||||
return mtd->_suspend ? mtd->_suspend(mtd) : 0;
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
int ret;
|
||||
|
||||
if (master->master.suspended)
|
||||
return 0;
|
||||
|
||||
ret = master->_suspend ? master->_suspend(master) : 0;
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
master->master.suspended = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void mtd_resume(struct mtd_info *mtd)
|
||||
{
|
||||
if (mtd->_resume)
|
||||
mtd->_resume(mtd);
|
||||
struct mtd_info *master = mtd_get_master(mtd);
|
||||
|
||||
if (!master->master.suspended)
|
||||
return;
|
||||
|
||||
if (master->_resume)
|
||||
master->_resume(master);
|
||||
|
||||
master->master.suspended = 0;
|
||||
}
|
||||
|
||||
static inline uint32_t mtd_div_by_eb(uint64_t sz, struct mtd_info *mtd)
|
||||
@ -538,7 +639,9 @@ static inline loff_t mtd_wunit_to_offset(struct mtd_info *mtd, loff_t base,
|
||||
|
||||
static inline int mtd_has_oob(const struct mtd_info *mtd)
|
||||
{
|
||||
return mtd->_read_oob && mtd->_write_oob;
|
||||
struct mtd_info *master = mtd_get_master((struct mtd_info *)mtd);
|
||||
|
||||
return master->_read_oob && master->_write_oob;
|
||||
}
|
||||
|
||||
static inline int mtd_type_is_nand(const struct mtd_info *mtd)
|
||||
@ -548,7 +651,9 @@ static inline int mtd_type_is_nand(const struct mtd_info *mtd)
|
||||
|
||||
static inline int mtd_can_have_bb(const struct mtd_info *mtd)
|
||||
{
|
||||
return !!mtd->_block_isbad;
|
||||
struct mtd_info *master = mtd_get_master((struct mtd_info *)mtd);
|
||||
|
||||
return !!master->_block_isbad;
|
||||
}
|
||||
|
||||
/* Kernel-side ioctl definitions */
|
||||
|
@ -105,7 +105,6 @@ extern void deregister_mtd_parser(struct mtd_part_parser *parser);
|
||||
module_driver(__mtd_part_parser, register_mtd_parser, \
|
||||
deregister_mtd_parser)
|
||||
|
||||
int mtd_is_partition(const struct mtd_info *mtd);
|
||||
int mtd_add_partition(struct mtd_info *master, const char *name,
|
||||
long long offset, long long length);
|
||||
int mtd_del_partition(struct mtd_info *master, int partno);
|
||||
|
@ -1064,6 +1064,8 @@ struct nand_legacy {
|
||||
* @lock: lock protecting the suspended field. Also used to
|
||||
* serialize accesses to the NAND device.
|
||||
* @suspended: set to 1 when the device is suspended, 0 when it's not.
|
||||
* @suspend: [REPLACEABLE] specific NAND device suspend operation
|
||||
* @resume: [REPLACEABLE] specific NAND device resume operation
|
||||
* @bbt: [INTERN] bad block table pointer
|
||||
* @bbt_td: [REPLACEABLE] bad block table descriptor for flash
|
||||
* lookup.
|
||||
@ -1077,6 +1079,8 @@ struct nand_legacy {
|
||||
* @manufacturer: [INTERN] Contains manufacturer information
|
||||
* @manufacturer.desc: [INTERN] Contains manufacturer's description
|
||||
* @manufacturer.priv: [INTERN] Contains manufacturer private information
|
||||
* @lock_area: [REPLACEABLE] specific NAND chip lock operation
|
||||
* @unlock_area: [REPLACEABLE] specific NAND chip unlock operation
|
||||
*/
|
||||
|
||||
struct nand_chip {
|
||||
@ -1117,6 +1121,8 @@ struct nand_chip {
|
||||
|
||||
struct mutex lock;
|
||||
unsigned int suspended : 1;
|
||||
int (*suspend)(struct nand_chip *chip);
|
||||
void (*resume)(struct nand_chip *chip);
|
||||
|
||||
uint8_t *oob_poi;
|
||||
struct nand_controller *controller;
|
||||
@ -1136,6 +1142,9 @@ struct nand_chip {
|
||||
const struct nand_manufacturer *desc;
|
||||
void *priv;
|
||||
} manufacturer;
|
||||
|
||||
int (*lock_area)(struct nand_chip *chip, loff_t ofs, uint64_t len);
|
||||
int (*unlock_area)(struct nand_chip *chip, loff_t ofs, uint64_t len);
|
||||
};
|
||||
|
||||
extern const struct mtd_ooblayout_ops nand_ooblayout_sp_ops;
|
||||
@ -1215,7 +1224,7 @@ static inline struct device_node *nand_get_flash_node(struct nand_chip *chip)
|
||||
* struct nand_flash_dev - NAND Flash Device ID Structure
|
||||
* @name: a human-readable name of the NAND chip
|
||||
* @dev_id: the device ID (the second byte of the full chip ID array)
|
||||
* @mfr_id: manufecturer ID part of the full chip ID array (refers the same
|
||||
* @mfr_id: manufacturer ID part of the full chip ID array (refers the same
|
||||
* memory address as ``id[0]``)
|
||||
* @dev_id: device ID part of the full chip ID array (refers the same memory
|
||||
* address as ``id[1]``)
|
||||
|
@ -11,23 +11,6 @@
|
||||
#include <linux/mtd/mtd.h>
|
||||
#include <linux/spi/spi-mem.h>
|
||||
|
||||
/*
|
||||
* Manufacturer IDs
|
||||
*
|
||||
* The first byte returned from the flash after sending opcode SPINOR_OP_RDID.
|
||||
* Sometimes these are the same as CFI IDs, but sometimes they aren't.
|
||||
*/
|
||||
#define SNOR_MFR_ATMEL CFI_MFR_ATMEL
|
||||
#define SNOR_MFR_GIGADEVICE 0xc8
|
||||
#define SNOR_MFR_INTEL CFI_MFR_INTEL
|
||||
#define SNOR_MFR_ST CFI_MFR_ST /* ST Micro */
|
||||
#define SNOR_MFR_MICRON CFI_MFR_MICRON /* Micron */
|
||||
#define SNOR_MFR_ISSI CFI_MFR_PMC
|
||||
#define SNOR_MFR_MACRONIX CFI_MFR_MACRONIX
|
||||
#define SNOR_MFR_SPANSION CFI_MFR_AMD
|
||||
#define SNOR_MFR_SST CFI_MFR_SST
|
||||
#define SNOR_MFR_WINBOND 0xef /* Also used by some Spansion */
|
||||
|
||||
/*
|
||||
* Note on opcode nomenclature: some opcodes have a format like
|
||||
* SPINOR_OP_FUNCTION{4,}_x_y_z. The numbers x, y, and z stand for the number
|
||||
@ -128,7 +111,9 @@
|
||||
#define SR_BP0 BIT(2) /* Block protect 0 */
|
||||
#define SR_BP1 BIT(3) /* Block protect 1 */
|
||||
#define SR_BP2 BIT(4) /* Block protect 2 */
|
||||
#define SR_BP3 BIT(5) /* Block protect 3 */
|
||||
#define SR_TB_BIT5 BIT(5) /* Top/Bottom protect */
|
||||
#define SR_BP3_BIT6 BIT(6) /* Block protect 3 */
|
||||
#define SR_TB_BIT6 BIT(6) /* Top/Bottom protect */
|
||||
#define SR_SRWD BIT(7) /* SR write protect */
|
||||
/* Spansion/Cypress specific status bits */
|
||||
@ -137,6 +122,8 @@
|
||||
|
||||
#define SR1_QUAD_EN_BIT6 BIT(6)
|
||||
|
||||
#define SR_BP_SHIFT 2
|
||||
|
||||
/* Enhanced Volatile Configuration Register bits */
|
||||
#define EVCR_QUAD_EN_MICRON BIT(7) /* Micron Quad I/O */
|
||||
|
||||
@ -225,110 +212,6 @@ static inline u8 spi_nor_get_protocol_width(enum spi_nor_protocol proto)
|
||||
return spi_nor_get_protocol_data_nbits(proto);
|
||||
}
|
||||
|
||||
enum spi_nor_option_flags {
|
||||
SNOR_F_USE_FSR = BIT(0),
|
||||
SNOR_F_HAS_SR_TB = BIT(1),
|
||||
SNOR_F_NO_OP_CHIP_ERASE = BIT(2),
|
||||
SNOR_F_READY_XSR_RDY = BIT(3),
|
||||
SNOR_F_USE_CLSR = BIT(4),
|
||||
SNOR_F_BROKEN_RESET = BIT(5),
|
||||
SNOR_F_4B_OPCODES = BIT(6),
|
||||
SNOR_F_HAS_4BAIT = BIT(7),
|
||||
SNOR_F_HAS_LOCK = BIT(8),
|
||||
SNOR_F_HAS_16BIT_SR = BIT(9),
|
||||
SNOR_F_NO_READ_CR = BIT(10),
|
||||
SNOR_F_HAS_SR_TB_BIT6 = BIT(11),
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* struct spi_nor_erase_type - Structure to describe a SPI NOR erase type
|
||||
* @size: the size of the sector/block erased by the erase type.
|
||||
* JEDEC JESD216B imposes erase sizes to be a power of 2.
|
||||
* @size_shift: @size is a power of 2, the shift is stored in
|
||||
* @size_shift.
|
||||
* @size_mask: the size mask based on @size_shift.
|
||||
* @opcode: the SPI command op code to erase the sector/block.
|
||||
* @idx: Erase Type index as sorted in the Basic Flash Parameter
|
||||
* Table. It will be used to synchronize the supported
|
||||
* Erase Types with the ones identified in the SFDP
|
||||
* optional tables.
|
||||
*/
|
||||
struct spi_nor_erase_type {
|
||||
u32 size;
|
||||
u32 size_shift;
|
||||
u32 size_mask;
|
||||
u8 opcode;
|
||||
u8 idx;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct spi_nor_erase_command - Used for non-uniform erases
|
||||
* The structure is used to describe a list of erase commands to be executed
|
||||
* once we validate that the erase can be performed. The elements in the list
|
||||
* are run-length encoded.
|
||||
* @list: for inclusion into the list of erase commands.
|
||||
* @count: how many times the same erase command should be
|
||||
* consecutively used.
|
||||
* @size: the size of the sector/block erased by the command.
|
||||
* @opcode: the SPI command op code to erase the sector/block.
|
||||
*/
|
||||
struct spi_nor_erase_command {
|
||||
struct list_head list;
|
||||
u32 count;
|
||||
u32 size;
|
||||
u8 opcode;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct spi_nor_erase_region - Structure to describe a SPI NOR erase region
|
||||
* @offset: the offset in the data array of erase region start.
|
||||
* LSB bits are used as a bitmask encoding flags to
|
||||
* determine if this region is overlaid, if this region is
|
||||
* the last in the SPI NOR flash memory and to indicate
|
||||
* all the supported erase commands inside this region.
|
||||
* The erase types are sorted in ascending order with the
|
||||
* smallest Erase Type size being at BIT(0).
|
||||
* @size: the size of the region in bytes.
|
||||
*/
|
||||
struct spi_nor_erase_region {
|
||||
u64 offset;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
#define SNOR_ERASE_TYPE_MAX 4
|
||||
#define SNOR_ERASE_TYPE_MASK GENMASK_ULL(SNOR_ERASE_TYPE_MAX - 1, 0)
|
||||
|
||||
#define SNOR_LAST_REGION BIT(4)
|
||||
#define SNOR_OVERLAID_REGION BIT(5)
|
||||
|
||||
#define SNOR_ERASE_FLAGS_MAX 6
|
||||
#define SNOR_ERASE_FLAGS_MASK GENMASK_ULL(SNOR_ERASE_FLAGS_MAX - 1, 0)
|
||||
|
||||
/**
|
||||
* struct spi_nor_erase_map - Structure to describe the SPI NOR erase map
|
||||
* @regions: array of erase regions. The regions are consecutive in
|
||||
* address space. Walking through the regions is done
|
||||
* incrementally.
|
||||
* @uniform_region: a pre-allocated erase region for SPI NOR with a uniform
|
||||
* sector size (legacy implementation).
|
||||
* @erase_type: an array of erase types shared by all the regions.
|
||||
* The erase types are sorted in ascending order, with the
|
||||
* smallest Erase Type size being the first member in the
|
||||
* erase_type array.
|
||||
* @uniform_erase_type: bitmask encoding erase types that can erase the
|
||||
* entire memory. This member is completed at init by
|
||||
* uniform and non-uniform SPI NOR flash memories if they
|
||||
* support at least one erase type that can erase the
|
||||
* entire memory.
|
||||
*/
|
||||
struct spi_nor_erase_map {
|
||||
struct spi_nor_erase_region *regions;
|
||||
struct spi_nor_erase_region uniform_region;
|
||||
struct spi_nor_erase_type erase_type[SNOR_ERASE_TYPE_MAX];
|
||||
u8 uniform_erase_type;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct spi_nor_hwcaps - Structure for describing the hardware capabilies
|
||||
* supported by the SPI controller (bus master).
|
||||
@ -404,61 +287,7 @@ struct spi_nor_hwcaps {
|
||||
#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' */
|
||||
/* Forward declaration that is used in 'struct spi_nor_controller_ops' */
|
||||
struct spi_nor;
|
||||
|
||||
/**
|
||||
@ -489,68 +318,13 @@ struct spi_nor_controller_ops {
|
||||
int (*erase)(struct spi_nor *nor, loff_t offs);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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()
|
||||
/*
|
||||
* Forward declarations that are used internally by the core and manufacturer
|
||||
* drivers.
|
||||
*/
|
||||
struct flash_info;
|
||||
struct spi_nor_manufacturer;
|
||||
struct spi_nor_flash_parameter;
|
||||
|
||||
/**
|
||||
* struct spi_nor - Structure for defining a the SPI NOR layer
|
||||
@ -562,6 +336,7 @@ struct flash_info;
|
||||
* layer is not DMA-able
|
||||
* @bouncebuf_size: size of the bounce buffer
|
||||
* @info: spi-nor part JDEC MFR id and other info
|
||||
* @manufacturer: spi-nor manufacturer
|
||||
* @page_size: the page size of the SPI NOR
|
||||
* @addr_width: number of address bytes
|
||||
* @erase_opcode: the opcode for erasing a sector
|
||||
@ -578,6 +353,7 @@ struct flash_info;
|
||||
* 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.
|
||||
* @dirmap: pointers to struct spi_mem_dirmap_desc for reads/writes.
|
||||
* @priv: the private data
|
||||
*/
|
||||
struct spi_nor {
|
||||
@ -588,6 +364,7 @@ struct spi_nor {
|
||||
u8 *bouncebuf;
|
||||
size_t bouncebuf_size;
|
||||
const struct flash_info *info;
|
||||
const struct spi_nor_manufacturer *manufacturer;
|
||||
u32 page_size;
|
||||
u8 addr_width;
|
||||
u8 erase_opcode;
|
||||
@ -602,40 +379,16 @@ struct spi_nor {
|
||||
|
||||
const struct spi_nor_controller_ops *controller_ops;
|
||||
|
||||
struct spi_nor_flash_parameter params;
|
||||
struct spi_nor_flash_parameter *params;
|
||||
|
||||
struct {
|
||||
struct spi_mem_dirmap_desc *rdesc;
|
||||
struct spi_mem_dirmap_desc *wdesc;
|
||||
} dirmap;
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -32,9 +32,9 @@
|
||||
SPI_MEM_OP_NO_DUMMY, \
|
||||
SPI_MEM_OP_NO_DATA)
|
||||
|
||||
#define SPINAND_READID_OP(ndummy, buf, len) \
|
||||
#define SPINAND_READID_OP(naddr, ndummy, buf, len) \
|
||||
SPI_MEM_OP(SPI_MEM_OP_CMD(0x9f, 1), \
|
||||
SPI_MEM_OP_NO_ADDR, \
|
||||
SPI_MEM_OP_ADDR(naddr, 0, 1), \
|
||||
SPI_MEM_OP_DUMMY(ndummy, 1), \
|
||||
SPI_MEM_OP_DATA_IN(len, buf, 1))
|
||||
|
||||
@ -176,37 +176,46 @@ struct spinand_device;
|
||||
* @data: buffer containing the id bytes. Currently 4 bytes large, but can
|
||||
* be extended if required
|
||||
* @len: ID length
|
||||
*
|
||||
* struct_spinand_id->data contains all bytes returned after a READ_ID command,
|
||||
* including dummy bytes if the chip does not emit ID bytes right after the
|
||||
* READ_ID command. The responsibility to extract real ID bytes is left to
|
||||
* struct_manufacurer_ops->detect().
|
||||
*/
|
||||
struct spinand_id {
|
||||
u8 data[SPINAND_MAX_ID_LEN];
|
||||
int len;
|
||||
};
|
||||
|
||||
enum spinand_readid_method {
|
||||
SPINAND_READID_METHOD_OPCODE,
|
||||
SPINAND_READID_METHOD_OPCODE_ADDR,
|
||||
SPINAND_READID_METHOD_OPCODE_DUMMY,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct spinand_devid - SPI NAND device id structure
|
||||
* @id: device id of current chip
|
||||
* @len: number of bytes in device id
|
||||
* @method: method to read chip id
|
||||
* There are 3 possible variants:
|
||||
* SPINAND_READID_METHOD_OPCODE: chip id is returned immediately
|
||||
* after read_id opcode.
|
||||
* SPINAND_READID_METHOD_OPCODE_ADDR: chip id is returned after
|
||||
* read_id opcode + 1-byte address.
|
||||
* SPINAND_READID_METHOD_OPCODE_DUMMY: chip id is returned after
|
||||
* read_id opcode + 1 dummy byte.
|
||||
*/
|
||||
struct spinand_devid {
|
||||
const u8 *id;
|
||||
const u8 len;
|
||||
const enum spinand_readid_method method;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct manufacurer_ops - SPI NAND manufacturer specific operations
|
||||
* @detect: detect a SPI NAND device. Every time a SPI NAND device is probed
|
||||
* the core calls the struct_manufacurer_ops->detect() hook of each
|
||||
* registered manufacturer until one of them return 1. Note that
|
||||
* the first thing to check in this hook is that the manufacturer ID
|
||||
* in struct_spinand_device->id matches the manufacturer whose
|
||||
* ->detect() hook has been called. Should return 1 if there's a
|
||||
* match, 0 if the manufacturer ID does not match and a negative
|
||||
* error code otherwise. When true is returned, the core assumes
|
||||
* that properties of the NAND chip (spinand->base.memorg and
|
||||
* spinand->base.eccreq) have been filled
|
||||
* @init: initialize a SPI NAND device
|
||||
* @cleanup: cleanup a SPI NAND device
|
||||
*
|
||||
* Each SPI NAND manufacturer driver should implement this interface so that
|
||||
* NAND chips coming from this vendor can be detected and initialized properly.
|
||||
* NAND chips coming from this vendor can be initialized properly.
|
||||
*/
|
||||
struct spinand_manufacturer_ops {
|
||||
int (*detect)(struct spinand_device *spinand);
|
||||
int (*init)(struct spinand_device *spinand);
|
||||
void (*cleanup)(struct spinand_device *spinand);
|
||||
};
|
||||
@ -215,11 +224,16 @@ struct spinand_manufacturer_ops {
|
||||
* struct spinand_manufacturer - SPI NAND manufacturer instance
|
||||
* @id: manufacturer ID
|
||||
* @name: manufacturer name
|
||||
* @devid_len: number of bytes in device ID
|
||||
* @chips: supported SPI NANDs under current manufacturer
|
||||
* @nchips: number of SPI NANDs available in chips array
|
||||
* @ops: manufacturer operations
|
||||
*/
|
||||
struct spinand_manufacturer {
|
||||
u8 id;
|
||||
char *name;
|
||||
const struct spinand_info *chips;
|
||||
const size_t nchips;
|
||||
const struct spinand_manufacturer_ops *ops;
|
||||
};
|
||||
|
||||
@ -270,6 +284,7 @@ struct spinand_ecc_info {
|
||||
};
|
||||
|
||||
#define SPINAND_HAS_QE_BIT BIT(0)
|
||||
#define SPINAND_HAS_CR_FEAT_BIT BIT(1)
|
||||
|
||||
/**
|
||||
* struct spinand_info - Structure used to describe SPI NAND chips
|
||||
@ -291,7 +306,7 @@ struct spinand_ecc_info {
|
||||
*/
|
||||
struct spinand_info {
|
||||
const char *model;
|
||||
u16 devid;
|
||||
struct spinand_devid devid;
|
||||
u32 flags;
|
||||
struct nand_memory_organization memorg;
|
||||
struct nand_ecc_req eccreq;
|
||||
@ -305,6 +320,13 @@ struct spinand_info {
|
||||
unsigned int target);
|
||||
};
|
||||
|
||||
#define SPINAND_ID(__method, ...) \
|
||||
{ \
|
||||
.id = (const u8[]){ __VA_ARGS__ }, \
|
||||
.len = sizeof((u8[]){ __VA_ARGS__ }), \
|
||||
.method = __method, \
|
||||
}
|
||||
|
||||
#define SPINAND_INFO_OP_VARIANTS(__read, __write, __update) \
|
||||
{ \
|
||||
.read_cache = __read, \
|
||||
@ -451,9 +473,10 @@ static inline void spinand_set_of_node(struct spinand_device *spinand,
|
||||
nanddev_set_of_node(&spinand->base, np);
|
||||
}
|
||||
|
||||
int spinand_match_and_init(struct spinand_device *dev,
|
||||
int spinand_match_and_init(struct spinand_device *spinand,
|
||||
const struct spinand_info *table,
|
||||
unsigned int table_size, u16 devid);
|
||||
unsigned int table_size,
|
||||
enum spinand_readid_method rdid_method);
|
||||
|
||||
int spinand_upd_cfg(struct spinand_device *spinand, u8 mask, u8 val);
|
||||
int spinand_select_target(struct spinand_device *spinand, unsigned int target);
|
||||
|
Loading…
Reference in New Issue
Block a user