summaryrefslogtreecommitdiff
path: root/drivers/mtd
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/mtd')
-rw-r--r--drivers/mtd/Kconfig9
-rw-r--r--drivers/mtd/Makefile1
-rw-r--r--drivers/mtd/chips/cfi_cmdset_0001.c14
-rw-r--r--drivers/mtd/chips/cfi_cmdset_0002.c22
-rw-r--r--drivers/mtd/chips/cfi_cmdset_0020.c31
-rw-r--r--drivers/mtd/chips/cfi_probe.c2
-rw-r--r--drivers/mtd/chips/gen_probe.c2
-rw-r--r--drivers/mtd/chips/jedec_probe.c8
-rw-r--r--drivers/mtd/chips/map_absent.c2
-rw-r--r--drivers/mtd/chips/map_ram.c2
-rw-r--r--drivers/mtd/chips/map_rom.c2
-rw-r--r--drivers/mtd/devices/Kconfig13
-rw-r--r--drivers/mtd/devices/Makefile1
-rw-r--r--drivers/mtd/devices/block2mtd.c2
-rw-r--r--drivers/mtd/devices/docg3.c7
-rw-r--r--drivers/mtd/devices/docg3.h2
-rw-r--r--drivers/mtd/devices/mchp23k256.c2
-rw-r--r--drivers/mtd/devices/mchp48l640.c9
-rw-r--r--drivers/mtd/devices/ms02-nv.c12
-rw-r--r--drivers/mtd/devices/mtd_dataflash.c2
-rw-r--r--drivers/mtd/devices/mtd_intel_dg.c883
-rw-r--r--drivers/mtd/devices/mtdram.c2
-rw-r--r--drivers/mtd/devices/phram.c2
-rw-r--r--drivers/mtd/devices/pmc551.c4
-rw-r--r--drivers/mtd/devices/slram.c96
-rw-r--r--drivers/mtd/ftl.c15
-rw-r--r--drivers/mtd/hyperbus/hbmc-am654.c1
-rw-r--r--drivers/mtd/inftlcore.c11
-rw-r--r--drivers/mtd/inftlmount.c44
-rw-r--r--drivers/mtd/lpddr/lpddr_cmds.c23
-rw-r--r--drivers/mtd/lpddr/qinfo_probe.c9
-rw-r--r--drivers/mtd/maps/Kconfig58
-rw-r--r--drivers/mtd/maps/Makefile6
-rw-r--r--drivers/mtd/maps/amd76xrom.c12
-rw-r--r--drivers/mtd/maps/ck804xrom.c4
-rw-r--r--drivers/mtd/maps/esb2rom.c22
-rw-r--r--drivers/mtd/maps/ichxrom.c19
-rw-r--r--drivers/mtd/maps/impa7.c115
-rw-r--r--drivers/mtd/maps/netsc520.c127
-rw-r--r--drivers/mtd/maps/nettel.c462
-rw-r--r--drivers/mtd/maps/pci.c18
-rw-r--r--drivers/mtd/maps/pcmciamtd.c3
-rw-r--r--drivers/mtd/maps/physmap-bt1-rom.c125
-rw-r--r--drivers/mtd/maps/physmap-bt1-rom.h17
-rw-r--r--drivers/mtd/maps/physmap-core.c7
-rw-r--r--drivers/mtd/maps/physmap-gemini.c2
-rw-r--r--drivers/mtd/maps/pismo.c2
-rw-r--r--drivers/mtd/maps/plat-ram.c2
-rw-r--r--drivers/mtd/maps/pxa2xx-flash.c2
-rw-r--r--drivers/mtd/maps/sa1100-flash.c4
-rw-r--r--drivers/mtd/maps/sc520cdp.c294
-rw-r--r--drivers/mtd/maps/scb2_flash.c9
-rw-r--r--drivers/mtd/maps/sun_uflash.c2
-rw-r--r--drivers/mtd/maps/ts5500_flash.c108
-rw-r--r--drivers/mtd/maps/vmu-flash.c15
-rw-r--r--drivers/mtd/mtd_blkdevs.c6
-rw-r--r--drivers/mtd/mtd_virt_concat.c352
-rw-r--r--drivers/mtd/mtdblock.c2
-rw-r--r--drivers/mtd/mtdblock_ro.c2
-rw-r--r--drivers/mtd/mtdchar.c33
-rw-r--r--drivers/mtd/mtdconcat.c23
-rw-r--r--drivers/mtd/mtdcore.c119
-rw-r--r--drivers/mtd/mtdoops.c12
-rw-r--r--drivers/mtd/mtdpart.c26
-rw-r--r--drivers/mtd/mtdpstore.c12
-rw-r--r--drivers/mtd/mtdsuper.c8
-rw-r--r--drivers/mtd/mtdswap.c13
-rw-r--r--drivers/mtd/nand/Kconfig8
-rw-r--r--drivers/mtd/nand/Makefile2
-rw-r--r--drivers/mtd/nand/core.c131
-rw-r--r--drivers/mtd/nand/ecc-mtk.c24
-rw-r--r--drivers/mtd/nand/ecc-mxic.c16
-rw-r--r--drivers/mtd/nand/ecc-realtek.c466
-rw-r--r--drivers/mtd/nand/ecc-sw-bch.c2
-rw-r--r--drivers/mtd/nand/ecc-sw-hamming.c4
-rw-r--r--drivers/mtd/nand/ecc.c2
-rw-r--r--drivers/mtd/nand/onenand/generic.c2
-rw-r--r--drivers/mtd/nand/onenand/onenand_base.c5
-rw-r--r--drivers/mtd/nand/onenand/onenand_bbt.c2
-rw-r--r--drivers/mtd/nand/onenand/onenand_omap2.c1
-rw-r--r--drivers/mtd/nand/onenand/onenand_samsung.c9
-rw-r--r--drivers/mtd/nand/qpic_common.c48
-rw-r--r--drivers/mtd/nand/raw/Kconfig40
-rw-r--r--drivers/mtd/nand/raw/Makefile2
-rw-r--r--drivers/mtd/nand/raw/atmel/nand-controller.c41
-rw-r--r--drivers/mtd/nand/raw/atmel/pmecc.c7
-rw-r--r--drivers/mtd/nand/raw/au1550nd.c2
-rw-r--r--drivers/mtd/nand/raw/bcm47xxnflash/ops_bcm4706.c5
-rw-r--r--drivers/mtd/nand/raw/brcmnand/brcmnand.c328
-rw-r--r--drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c1
-rw-r--r--drivers/mtd/nand/raw/cadence-nand-controller.c280
-rw-r--r--drivers/mtd/nand/raw/cafe_nand.c12
-rw-r--r--drivers/mtd/nand/raw/cs553x_nand.c8
-rw-r--r--drivers/mtd/nand/raw/denali_dt.c7
-rw-r--r--drivers/mtd/nand/raw/denali_pci.c17
-rw-r--r--drivers/mtd/nand/raw/diskonchip.c2
-rw-r--r--drivers/mtd/nand/raw/fsl_elbc_nand.c4
-rw-r--r--drivers/mtd/nand/raw/fsl_ifc_nand.c21
-rw-r--r--drivers/mtd/nand/raw/fsmc_nand.c8
-rw-r--r--drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c33
-rw-r--r--drivers/mtd/nand/raw/ingenic/ingenic_ecc.c7
-rw-r--r--drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c4
-rw-r--r--drivers/mtd/nand/raw/loongson-nand-controller.c1024
-rw-r--r--drivers/mtd/nand/raw/lpc32xx_mlc.c12
-rw-r--r--drivers/mtd/nand/raw/lpc32xx_slc.c14
-rw-r--r--drivers/mtd/nand/raw/marvell_nand.c13
-rw-r--r--drivers/mtd/nand/raw/mxc_nand.c10
-rw-r--r--drivers/mtd/nand/raw/nand_base.c215
-rw-r--r--drivers/mtd/nand/raw/nand_bbt.c4
-rw-r--r--drivers/mtd/nand/raw/nand_hynix.c6
-rw-r--r--drivers/mtd/nand/raw/nand_ids.c2
-rw-r--r--drivers/mtd/nand/raw/nand_jedec.c4
-rw-r--r--drivers/mtd/nand/raw/nand_legacy.c2
-rw-r--r--drivers/mtd/nand/raw/nand_micron.c2
-rw-r--r--drivers/mtd/nand/raw/nand_onfi.c4
-rw-r--r--drivers/mtd/nand/raw/nandsim.c15
-rw-r--r--drivers/mtd/nand/raw/ndfc.c24
-rw-r--r--drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c4
-rw-r--r--drivers/mtd/nand/raw/omap2.c27
-rw-r--r--drivers/mtd/nand/raw/pasemi_nand.c2
-rw-r--r--drivers/mtd/nand/raw/pl35x-nand-controller.c12
-rw-r--r--drivers/mtd/nand/raw/plat_nand.c24
-rw-r--r--drivers/mtd/nand/raw/qcom_nandc.c84
-rw-r--r--drivers/mtd/nand/raw/r852.c11
-rw-r--r--drivers/mtd/nand/raw/renesas-nand-controller.c11
-rw-r--r--drivers/mtd/nand/raw/rockchip-nand-controller.c16
-rw-r--r--drivers/mtd/nand/raw/s3c2410.c1230
-rw-r--r--drivers/mtd/nand/raw/sharpsl.c2
-rw-r--r--drivers/mtd/nand/raw/stm32_fmc2_nand.c47
-rw-r--r--drivers/mtd/nand/raw/sunxi_nand.c697
-rw-r--r--drivers/mtd/nand/raw/txx9ndfmc.c3
-rw-r--r--drivers/mtd/nand/raw/vf610_nfc.c6
-rw-r--r--drivers/mtd/nand/spi/Makefile5
-rw-r--r--drivers/mtd/nand/spi/alliancememory.c20
-rw-r--r--drivers/mtd/nand/spi/ato.c14
-rw-r--r--drivers/mtd/nand/spi/core.c746
-rw-r--r--drivers/mtd/nand/spi/dosilicon.c91
-rw-r--r--drivers/mtd/nand/spi/esmt.c130
-rw-r--r--drivers/mtd/nand/spi/fmsh.c146
-rw-r--r--drivers/mtd/nand/spi/foresee.c50
-rw-r--r--drivers/mtd/nand/spi/gigadevice.c187
-rw-r--r--drivers/mtd/nand/spi/macronix.c160
-rw-r--r--drivers/mtd/nand/spi/micron.c171
-rw-r--r--drivers/mtd/nand/spi/otp.c362
-rw-r--r--drivers/mtd/nand/spi/paragon.c20
-rw-r--r--drivers/mtd/nand/spi/skyhigh.c20
-rw-r--r--drivers/mtd/nand/spi/toshiba.c23
-rw-r--r--drivers/mtd/nand/spi/winbond.c575
-rw-r--r--drivers/mtd/nand/spi/xtx.c20
-rw-r--r--drivers/mtd/nftlcore.c45
-rw-r--r--drivers/mtd/parsers/bcm47xxpart.c3
-rw-r--r--drivers/mtd/parsers/brcm_u-boot.c2
-rw-r--r--drivers/mtd/parsers/cmdlinepart.c3
-rw-r--r--drivers/mtd/parsers/ofpart_bcm4908.h6
-rw-r--r--drivers/mtd/parsers/ofpart_core.c32
-rw-r--r--drivers/mtd/parsers/ofpart_linksys_ns.h7
-rw-r--r--drivers/mtd/parsers/parser_trx.c3
-rw-r--r--drivers/mtd/parsers/qcomsmempart.c2
-rw-r--r--drivers/mtd/parsers/redboot.c8
-rw-r--r--drivers/mtd/parsers/scpart.c5
-rw-r--r--drivers/mtd/parsers/sharpslpart.c5
-rw-r--r--drivers/mtd/parsers/tplink_safeloader.c3
-rw-r--r--drivers/mtd/rfd_ftl.c11
-rw-r--r--drivers/mtd/sm_ftl.c53
-rw-r--r--drivers/mtd/sm_ftl.h2
-rw-r--r--drivers/mtd/spi-nor/Kconfig1
-rw-r--r--drivers/mtd/spi-nor/controllers/hisi-sfc.c8
-rw-r--r--drivers/mtd/spi-nor/core.c346
-rw-r--r--drivers/mtd/spi-nor/core.h33
-rw-r--r--drivers/mtd/spi-nor/debugfs.c76
-rw-r--r--drivers/mtd/spi-nor/macronix.c100
-rw-r--r--drivers/mtd/spi-nor/micron-st.c112
-rw-r--r--drivers/mtd/spi-nor/otp.c1
-rw-r--r--drivers/mtd/spi-nor/sfdp.c32
-rw-r--r--drivers/mtd/spi-nor/spansion.c80
-rw-r--r--drivers/mtd/spi-nor/sst.c13
-rw-r--r--drivers/mtd/spi-nor/swp.c383
-rw-r--r--drivers/mtd/spi-nor/sysfs.c2
-rw-r--r--drivers/mtd/spi-nor/winbond.c145
-rw-r--r--drivers/mtd/ssfdc.c2
-rw-r--r--drivers/mtd/tests/stresstest.c2
-rw-r--r--drivers/mtd/ubi/attach.c8
-rw-r--r--drivers/mtd/ubi/block.c10
-rw-r--r--drivers/mtd/ubi/build.c2
-rw-r--r--drivers/mtd/ubi/cdev.c6
-rw-r--r--drivers/mtd/ubi/eba.c19
-rw-r--r--drivers/mtd/ubi/fastmap-wl.c10
-rw-r--r--drivers/mtd/ubi/fastmap.c8
-rw-r--r--drivers/mtd/ubi/gluebi.c2
-rw-r--r--drivers/mtd/ubi/io.c10
-rw-r--r--drivers/mtd/ubi/kapi.c29
-rw-r--r--drivers/mtd/ubi/nvmem.c2
-rw-r--r--drivers/mtd/ubi/ubi.h14
-rw-r--r--drivers/mtd/ubi/vmt.c2
-rw-r--r--drivers/mtd/ubi/vtbl.c4
-rw-r--r--drivers/mtd/ubi/wl.c4
196 files changed, 8518 insertions, 4234 deletions
diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig
index 796a2eccbef0..0421c6208de7 100644
--- a/drivers/mtd/Kconfig
+++ b/drivers/mtd/Kconfig
@@ -206,6 +206,15 @@ config MTD_PARTITIONED_MASTER
the parent of the partition device be the master device, rather than
what lies behind the master.
+config MTD_VIRT_CONCAT
+ bool "Virtual concatenated MTD devices"
+ depends on MTD_PARTITIONED_MASTER
+ help
+ The driver enables the creation of virtual MTD device by
+ concatenating multiple physical MTD devices into a single
+ entity. This allows for the creation of partitions larger than
+ the individual physical chips, extending across chip boundaries.
+
source "drivers/mtd/chips/Kconfig"
source "drivers/mtd/maps/Kconfig"
diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile
index 593d0593a038..7b6dd53e8150 100644
--- a/drivers/mtd/Makefile
+++ b/drivers/mtd/Makefile
@@ -6,6 +6,7 @@
# Core functionality.
obj-$(CONFIG_MTD) += mtd.o
mtd-y := mtdcore.o mtdsuper.o mtdconcat.o mtdpart.o mtdchar.o
+mtd-$(CONFIG_MTD_VIRT_CONCAT) += mtd_virt_concat.o
obj-y += parsers/
diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c
index c10693ba265b..b73596a8e021 100644
--- a/drivers/mtd/chips/cfi_cmdset_0001.c
+++ b/drivers/mtd/chips/cfi_cmdset_0001.c
@@ -501,7 +501,7 @@ struct mtd_info *cfi_cmdset_0001(struct map_info *map, int primary)
struct mtd_info *mtd;
int i;
- mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
+ mtd = kzalloc_obj(*mtd);
if (!mtd)
return NULL;
mtd->priv = map;
@@ -627,9 +627,8 @@ static struct mtd_info *cfi_intelext_setup(struct mtd_info *mtd)
mtd->size = devsize * cfi->numchips;
mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips;
- mtd->eraseregions = kcalloc(mtd->numeraseregions,
- sizeof(struct mtd_erase_region_info),
- GFP_KERNEL);
+ mtd->eraseregions = kzalloc_objs(struct mtd_erase_region_info,
+ mtd->numeraseregions);
if (!mtd->eraseregions)
goto setup_err;
@@ -777,13 +776,10 @@ static int cfi_intelext_partition_fixup(struct mtd_info *mtd,
}
numvirtchips = cfi->numchips * numparts;
- newcfi = kmalloc(struct_size(newcfi, chips, numvirtchips),
- GFP_KERNEL);
+ newcfi = kmalloc_flex(*newcfi, chips, numvirtchips);
if (!newcfi)
return -ENOMEM;
- shared = kmalloc_array(cfi->numchips,
- sizeof(struct flchip_shared),
- GFP_KERNEL);
+ shared = kmalloc_objs(struct flchip_shared, cfi->numchips);
if (!shared) {
kfree(newcfi);
return -ENOMEM;
diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c
index 7c91429a670b..517db2f2707f 100644
--- a/drivers/mtd/chips/cfi_cmdset_0002.c
+++ b/drivers/mtd/chips/cfi_cmdset_0002.c
@@ -604,7 +604,7 @@ struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary)
struct mtd_info *mtd;
int i;
- mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
+ mtd = kzalloc_obj(*mtd);
if (!mtd)
return NULL;
mtd->priv = map;
@@ -661,8 +661,7 @@ struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary)
extp->MajorVersion, extp->MinorVersion,
extp->MajorVersion, extp->MinorVersion);
kfree(extp);
- kfree(mtd);
- return NULL;
+ goto free_mtd;
}
printk(KERN_INFO " Amd/Fujitsu Extended Query version %c.%c.\n",
@@ -714,10 +713,8 @@ struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary)
}
cfi_fixup(mtd, cfi_nopri_fixup_table);
- if (!cfi->addr_unlock1 || !cfi->addr_unlock2) {
- kfree(mtd);
- return NULL;
- }
+ if (!cfi->addr_unlock1 || !cfi->addr_unlock2)
+ goto free_mtd;
} /* CFI mode */
else if (cfi->cfi_mode == CFI_MODE_JEDEC) {
@@ -755,6 +752,10 @@ struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary)
map->fldrv = &cfi_amdstd_chipdrv;
return cfi_amdstd_setup(mtd);
+
+free_mtd:
+ kfree(mtd);
+ return NULL;
}
struct mtd_info *cfi_cmdset_0006(struct map_info *map, int primary) __attribute__((alias("cfi_cmdset_0002")));
struct mtd_info *cfi_cmdset_0701(struct map_info *map, int primary) __attribute__((alias("cfi_cmdset_0002")));
@@ -776,9 +777,8 @@ static struct mtd_info *cfi_amdstd_setup(struct mtd_info *mtd)
mtd->size = devsize * cfi->numchips;
mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips;
- mtd->eraseregions = kmalloc_array(mtd->numeraseregions,
- sizeof(struct mtd_erase_region_info),
- GFP_KERNEL);
+ mtd->eraseregions = kmalloc_objs(struct mtd_erase_region_info,
+ mtd->numeraseregions);
if (!mtd->eraseregions)
goto setup_err;
@@ -2819,7 +2819,7 @@ static int __maybe_unused cfi_ppb_unlock(struct mtd_info *mtd, loff_t ofs,
for (i = 0; i < mtd->numeraseregions; i++)
max_sectors += regions[i].numblocks;
- sect = kcalloc(max_sectors, sizeof(struct ppb_lock), GFP_KERNEL);
+ sect = kzalloc_objs(struct ppb_lock, max_sectors);
if (!sect)
return -ENOMEM;
diff --git a/drivers/mtd/chips/cfi_cmdset_0020.c b/drivers/mtd/chips/cfi_cmdset_0020.c
index 5e5266e2c2e1..593ac65a7e2f 100644
--- a/drivers/mtd/chips/cfi_cmdset_0020.c
+++ b/drivers/mtd/chips/cfi_cmdset_0020.c
@@ -172,27 +172,20 @@ static struct mtd_info *cfi_staa_setup(struct map_info *map)
int i,j;
unsigned long devsize = (1<<cfi->cfiq->DevSize) * cfi->interleave;
- mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
+ mtd = kzalloc_obj(*mtd);
//printk(KERN_DEBUG "number of CFI chips: %d\n", cfi->numchips);
-
- if (!mtd) {
- kfree(cfi->cmdset_priv);
- return NULL;
- }
+ if (!mtd)
+ goto free_cmdset_priv;
mtd->priv = map;
mtd->type = MTD_NORFLASH;
mtd->size = devsize * cfi->numchips;
mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips;
- mtd->eraseregions = kmalloc_array(mtd->numeraseregions,
- sizeof(struct mtd_erase_region_info),
- GFP_KERNEL);
- if (!mtd->eraseregions) {
- kfree(cfi->cmdset_priv);
- kfree(mtd);
- return NULL;
- }
+ mtd->eraseregions = kmalloc_objs(struct mtd_erase_region_info,
+ mtd->numeraseregions);
+ if (!mtd->eraseregions)
+ goto free_mtd;
for (i=0; i<cfi->cfiq->NumEraseRegions; i++) {
unsigned long ernum, ersize;
@@ -214,9 +207,7 @@ static struct mtd_info *cfi_staa_setup(struct map_info *map)
/* Argh */
printk(KERN_WARNING "Sum of regions (%lx) != total size of set of interleaved chips (%lx)\n", offset, devsize);
kfree(mtd->eraseregions);
- kfree(cfi->cmdset_priv);
- kfree(mtd);
- return NULL;
+ goto free_mtd;
}
for (i=0; i<mtd->numeraseregions;i++){
@@ -243,6 +234,12 @@ static struct mtd_info *cfi_staa_setup(struct map_info *map)
__module_get(THIS_MODULE);
mtd->name = map->name;
return mtd;
+
+free_mtd:
+ kfree(mtd);
+free_cmdset_priv:
+ kfree(cfi->cmdset_priv);
+ return NULL;
}
diff --git a/drivers/mtd/chips/cfi_probe.c b/drivers/mtd/chips/cfi_probe.c
index a04b6174181c..bdc04a6ec9c7 100644
--- a/drivers/mtd/chips/cfi_probe.c
+++ b/drivers/mtd/chips/cfi_probe.c
@@ -208,7 +208,7 @@ static int __xipram cfi_chip_setup(struct map_info *map,
if (!num_erase_regions)
return 0;
- cfi->cfiq = kmalloc(sizeof(struct cfi_ident) + num_erase_regions * 4, GFP_KERNEL);
+ cfi->cfiq = kmalloc_flex(*cfi->cfiq, EraseRegionInfo, num_erase_regions);
if (!cfi->cfiq)
return 0;
diff --git a/drivers/mtd/chips/gen_probe.c b/drivers/mtd/chips/gen_probe.c
index 9e53fcd7600d..433194a76e2a 100644
--- a/drivers/mtd/chips/gen_probe.c
+++ b/drivers/mtd/chips/gen_probe.c
@@ -134,7 +134,7 @@ static struct cfi_private *genprobe_ident_chips(struct map_info *map, struct chi
* our caller, and copy the appropriate data into them.
*/
- retcfi = kmalloc(struct_size(retcfi, chips, cfi.numchips), GFP_KERNEL);
+ retcfi = kmalloc_flex(*retcfi, chips, cfi.numchips);
if (!retcfi) {
kfree(cfi.cfiq);
diff --git a/drivers/mtd/chips/jedec_probe.c b/drivers/mtd/chips/jedec_probe.c
index 23c32fe584b7..a49ef682dc13 100644
--- a/drivers/mtd/chips/jedec_probe.c
+++ b/drivers/mtd/chips/jedec_probe.c
@@ -1921,7 +1921,7 @@ static inline u32 jedec_read_mfr(struct map_info *map, uint32_t base,
*/
do {
uint32_t ofs = cfi_build_cmd_addr(0 + (bank << 8), map, cfi);
- mask = (1 << (cfi->device_type * 8)) - 1;
+ mask = (1ULL << (cfi->device_type * 8)) - 1;
if (ofs >= map->size)
return 0;
result = map_read(map, base + ofs);
@@ -1937,7 +1937,7 @@ static inline u32 jedec_read_id(struct map_info *map, uint32_t base,
map_word result;
unsigned long mask;
u32 ofs = cfi_build_cmd_addr(1, map, cfi);
- mask = (1 << (cfi->device_type * 8)) -1;
+ mask = (1ULL << (cfi->device_type * 8)) - 1;
result = map_read(map, base + ofs);
return result.x[0] & mask;
}
@@ -1953,7 +1953,7 @@ static void jedec_reset(u32 base, struct map_info *map, struct cfi_private *cfi)
* as they will ignore the writes and don't care what address
* the F0 is written to */
if (cfi->addr_unlock1) {
- pr_debug( "reset unlock called %x %x \n",
+ pr_debug("reset unlock called %x %x\n",
cfi->addr_unlock1,cfi->addr_unlock2);
cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL);
cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, cfi->device_type, NULL);
@@ -1985,7 +1985,7 @@ static int cfi_jedec_setup(struct map_info *map, struct cfi_private *cfi, int in
num_erase_regions = jedec_table[index].nr_regions;
- cfi->cfiq = kmalloc(sizeof(struct cfi_ident) + num_erase_regions * 4, GFP_KERNEL);
+ cfi->cfiq = kmalloc_flex(*cfi->cfiq, EraseRegionInfo, num_erase_regions);
if (!cfi->cfiq) {
//xx printk(KERN_WARNING "%s: kmalloc failed for CFI ident structure\n", map->name);
return 0;
diff --git a/drivers/mtd/chips/map_absent.c b/drivers/mtd/chips/map_absent.c
index fc68557f49c0..1804a44b3eab 100644
--- a/drivers/mtd/chips/map_absent.c
+++ b/drivers/mtd/chips/map_absent.c
@@ -46,7 +46,7 @@ static struct mtd_info *map_absent_probe(struct map_info *map)
{
struct mtd_info *mtd;
- mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
+ mtd = kzalloc_obj(*mtd);
if (!mtd) {
return NULL;
}
diff --git a/drivers/mtd/chips/map_ram.c b/drivers/mtd/chips/map_ram.c
index f9d3e32ef8e9..2dde70e742c5 100644
--- a/drivers/mtd/chips/map_ram.c
+++ b/drivers/mtd/chips/map_ram.c
@@ -57,7 +57,7 @@ static struct mtd_info *map_ram_probe(struct map_info *map)
#endif
/* OK. It seems to be RAM. */
- mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
+ mtd = kzalloc_obj(*mtd);
if (!mtd)
return NULL;
diff --git a/drivers/mtd/chips/map_rom.c b/drivers/mtd/chips/map_rom.c
index 0823b15aaadb..dbc34e886b57 100644
--- a/drivers/mtd/chips/map_rom.c
+++ b/drivers/mtd/chips/map_rom.c
@@ -45,7 +45,7 @@ static struct mtd_info *map_rom_probe(struct map_info *map)
{
struct mtd_info *mtd;
- mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
+ mtd = kzalloc_obj(*mtd);
if (!mtd)
return NULL;
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig
index ff2f9e55ef28..e518dfeee654 100644
--- a/drivers/mtd/devices/Kconfig
+++ b/drivers/mtd/devices/Kconfig
@@ -98,7 +98,7 @@ config MTD_MCHP48L640
config MTD_SPEAR_SMI
tristate "SPEAR MTD NOR Support through SMI controller"
depends on PLAT_SPEAR || COMPILE_TEST
- default y
+ default PLAT_SPEAR
help
This enable SNOR support on SPEAR platforms using SMI controller
@@ -183,6 +183,17 @@ config MTD_POWERNV_FLASH
platforms from Linux. This device abstracts away the
firmware interface for flash access.
+config MTD_INTEL_DG
+ tristate "Intel Discrete Graphics non-volatile memory driver"
+ depends on AUXILIARY_BUS && MTD
+ depends on DRM_I915!=n || DRM_XE!=n || COMPILE_TEST
+ help
+ This provides an MTD device to access Intel Discrete Graphics
+ non-volatile memory.
+
+ To compile this driver as a module, choose M here: the module
+ will be called mtd-intel-dg.
+
comment "Disk-On-Chip Device Drivers"
config MTD_DOCG3
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile
index d11eb2b8b6f8..9fe4ce9cffde 100644
--- a/drivers/mtd/devices/Makefile
+++ b/drivers/mtd/devices/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_MTD_SST25L) += sst25l.o
obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o
obj-$(CONFIG_MTD_ST_SPI_FSM) += st_spi_fsm.o
obj-$(CONFIG_MTD_POWERNV_FLASH) += powernv_flash.o
+obj-$(CONFIG_MTD_INTEL_DG) += mtd_intel_dg.o
CFLAGS_docg3.o += -I$(src)
diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c
index b06c8dd51562..03e80b2c4f5a 100644
--- a/drivers/mtd/devices/block2mtd.c
+++ b/drivers/mtd/devices/block2mtd.c
@@ -271,7 +271,7 @@ static struct block2mtd_dev *add_device(char *devname, int erase_size,
if (!devname)
return NULL;
- dev = kzalloc(sizeof(struct block2mtd_dev), GFP_KERNEL);
+ dev = kzalloc_obj(struct block2mtd_dev);
if (!dev)
return NULL;
diff --git a/drivers/mtd/devices/docg3.c b/drivers/mtd/devices/docg3.c
index c93769c233d9..603fd0efc2ea 100644
--- a/drivers/mtd/devices/docg3.c
+++ b/drivers/mtd/devices/docg3.c
@@ -1810,10 +1810,10 @@ doc_probe_device(struct docg3_cascade *cascade, int floor, struct device *dev)
struct mtd_info *mtd;
ret = -ENOMEM;
- docg3 = kzalloc(sizeof(struct docg3), GFP_KERNEL);
+ docg3 = kzalloc_obj(struct docg3);
if (!docg3)
goto nomem1;
- mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
+ mtd = kzalloc_obj(struct mtd_info);
if (!mtd)
goto nomem2;
mtd->priv = docg3;
@@ -2049,7 +2049,6 @@ err_probe:
static void docg3_release(struct platform_device *pdev)
{
struct docg3_cascade *cascade = platform_get_drvdata(pdev);
- struct docg3 *docg3 = cascade->floors[0]->priv;
int floor;
doc_unregister_sysfs(pdev, cascade);
@@ -2057,7 +2056,7 @@ static void docg3_release(struct platform_device *pdev)
if (cascade->floors[floor])
doc_release_device(cascade->floors[floor]);
- bch_free(docg3->cascade->bch);
+ bch_free(cascade->bch);
}
#ifdef CONFIG_OF
diff --git a/drivers/mtd/devices/docg3.h b/drivers/mtd/devices/docg3.h
index 2c0c5114e4e6..af6ef0ac1f11 100644
--- a/drivers/mtd/devices/docg3.h
+++ b/drivers/mtd/devices/docg3.h
@@ -274,11 +274,11 @@ struct docg3_cascade {
* @cascade: the cascade this device belongs to
* @device_id: number of the cascaded DoCG3 device (0, 1, 2 or 3)
* @if_cfg: if true, reads are on 16bits, else reads are on 8bits
-
* @reliable: if 0, docg3 in normal mode, if 1 docg3 in fast mode, if 2 in
* reliable mode
* Fast mode implies more errors than normal mode.
* Reliable mode implies that page 2*n and 2*n+1 are clones.
+ * @max_block: maximum block number for this device
* @bbt: bad block table cache
* @oob_write_ofs: offset of the MTD where this OOB should belong (ie. in next
* page_write)
diff --git a/drivers/mtd/devices/mchp23k256.c b/drivers/mtd/devices/mchp23k256.c
index cef5f9677d39..66e058df4c32 100644
--- a/drivers/mtd/devices/mchp23k256.c
+++ b/drivers/mtd/devices/mchp23k256.c
@@ -188,7 +188,7 @@ static int mchp23k256_probe(struct spi_device *spi)
data = dev_get_platdata(&spi->dev);
- flash->caps = of_device_get_match_data(&spi->dev);
+ flash->caps = spi_get_device_match_data(spi);
if (!flash->caps)
flash->caps = &mchp23k256_caps;
diff --git a/drivers/mtd/devices/mchp48l640.c b/drivers/mtd/devices/mchp48l640.c
index 7584d0ba9396..4af9208f9690 100644
--- a/drivers/mtd/devices/mchp48l640.c
+++ b/drivers/mtd/devices/mchp48l640.c
@@ -23,6 +23,7 @@
#include <linux/spi/flash.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
+#include <linux/string_choices.h>
struct mchp48_caps {
unsigned int size;
@@ -128,11 +129,11 @@ static int mchp48l640_write_prepare(struct mchp48l640_flash *flash, bool enable)
mutex_unlock(&flash->lock);
if (ret)
- dev_err(&flash->spi->dev, "write %sable failed ret: %d",
- (enable ? "en" : "dis"), ret);
+ dev_err(&flash->spi->dev, "write %s failed ret: %d",
+ str_enable_disable(enable), ret);
- dev_dbg(&flash->spi->dev, "write %sable success ret: %d",
- (enable ? "en" : "dis"), ret);
+ dev_dbg(&flash->spi->dev, "write %s success ret: %d",
+ str_enable_disable(enable), ret);
if (enable)
return mchp48l640_waitforbit(flash, MCHP48L640_STATUS_WEL, true);
diff --git a/drivers/mtd/devices/ms02-nv.c b/drivers/mtd/devices/ms02-nv.c
index 08f76ff839a7..453b3e05feb7 100644
--- a/drivers/mtd/devices/ms02-nv.c
+++ b/drivers/mtd/devices/ms02-nv.c
@@ -117,7 +117,7 @@ static int __init ms02nv_init_one(ulong addr)
int ret = -ENODEV;
/* The module decodes 8MiB of address space. */
- mod_res = kzalloc(sizeof(*mod_res), GFP_KERNEL);
+ mod_res = kzalloc_obj(*mod_res);
if (!mod_res)
return -ENOMEM;
@@ -138,10 +138,10 @@ static int __init ms02nv_init_one(ulong addr)
}
ret = -ENOMEM;
- mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
+ mtd = kzalloc_obj(*mtd);
if (!mtd)
goto err_out_mod_res_rel;
- mp = kzalloc(sizeof(*mp), GFP_KERNEL);
+ mp = kzalloc_obj(*mp);
if (!mp)
goto err_out_mtd;
@@ -149,7 +149,7 @@ static int __init ms02nv_init_one(ulong addr)
mp->resource.module = mod_res;
/* Firmware's diagnostic NVRAM area. */
- diag_res = kzalloc(sizeof(*diag_res), GFP_KERNEL);
+ diag_res = kzalloc_obj(*diag_res);
if (!diag_res)
goto err_out_mp;
@@ -162,7 +162,7 @@ static int __init ms02nv_init_one(ulong addr)
mp->resource.diag_ram = diag_res;
/* User-available general-purpose NVRAM area. */
- user_res = kzalloc(sizeof(*user_res), GFP_KERNEL);
+ user_res = kzalloc_obj(*user_res);
if (!user_res)
goto err_out_diag_res;
@@ -175,7 +175,7 @@ static int __init ms02nv_init_one(ulong addr)
mp->resource.user_ram = user_res;
/* Control and status register. */
- csr_res = kzalloc(sizeof(*csr_res), GFP_KERNEL);
+ csr_res = kzalloc_obj(*csr_res);
if (!csr_res)
goto err_out_user_res;
diff --git a/drivers/mtd/devices/mtd_dataflash.c b/drivers/mtd/devices/mtd_dataflash.c
index ec52277e3dd5..e766258d76f7 100644
--- a/drivers/mtd/devices/mtd_dataflash.c
+++ b/drivers/mtd/devices/mtd_dataflash.c
@@ -627,7 +627,7 @@ static int add_dataflash_otp(struct spi_device *spi, char *name, int nr_pages,
char *otp_tag = "";
int err = 0;
- priv = kzalloc(sizeof *priv, GFP_KERNEL);
+ priv = kzalloc_obj(*priv);
if (!priv)
return -ENOMEM;
diff --git a/drivers/mtd/devices/mtd_intel_dg.c b/drivers/mtd/devices/mtd_intel_dg.c
new file mode 100644
index 000000000000..f2fa8f68d190
--- /dev/null
+++ b/drivers/mtd/devices/mtd_intel_dg.c
@@ -0,0 +1,883 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright(c) 2019-2025, Intel Corporation. All rights reserved.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/intel_dg_nvm_aux.h>
+#include <linux/io.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/partitions.h>
+#include <linux/pm_runtime.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/sizes.h>
+#include <linux/types.h>
+
+#define INTEL_DG_NVM_RPM_TIMEOUT_MS 500
+
+struct intel_dg_nvm {
+ struct kref refcnt;
+ struct mtd_info mtd;
+ struct device *dev;
+ struct mutex lock; /* region access lock */
+ void __iomem *base;
+ void __iomem *base2;
+ bool non_posted_erase;
+
+ size_t size;
+ unsigned int nregions;
+ struct {
+ const char *name;
+ u8 id;
+ u64 offset;
+ u64 size;
+ unsigned int is_readable:1;
+ unsigned int is_writable:1;
+ } regions[] __counted_by(nregions);
+};
+
+#define NVM_TRIGGER_REG 0x00000000
+#define NVM_VALSIG_REG 0x00000010
+#define NVM_ADDRESS_REG 0x00000040
+#define NVM_REGION_ID_REG 0x00000044
+#define NVM_DEBUG_REG 0x00000000
+/*
+ * [15:0]-Erase size = 0x0010 4K 0x0080 32K 0x0100 64K
+ * [23:16]-Reserved
+ * [31:24]-Erase MEM RegionID
+ */
+#define NVM_ERASE_REG 0x00000048
+#define NVM_ACCESS_ERROR_REG 0x00000070
+#define NVM_ADDRESS_ERROR_REG 0x00000074
+
+/* Flash Valid Signature */
+#define NVM_FLVALSIG 0x0FF0A55A
+
+#define NVM_MAP_ADDR_MASK GENMASK(7, 0)
+#define NVM_MAP_ADDR_SHIFT 0x00000004
+
+#define NVM_REGION_ID_DESCRIPTOR 0
+/* Flash Region Base Address */
+#define NVM_FRBA 0x40
+/* Flash Region __n - Flash Descriptor Record */
+#define NVM_FLREG(__n) (NVM_FRBA + ((__n) * 4))
+/* Flash Map 1 Register */
+#define NVM_FLMAP1_REG 0x18
+#define NVM_FLMSTR4_OFFSET 0x00C
+
+#define NVM_ACCESS_ERROR_PCIE_MASK 0x7
+
+#define NVM_FREG_BASE_MASK GENMASK(15, 0)
+#define NVM_FREG_ADDR_MASK GENMASK(31, 16)
+#define NVM_FREG_ADDR_SHIFT 12
+#define NVM_FREG_MIN_REGION_SIZE 0xFFF
+
+#define NVM_NON_POSTED_ERASE_DONE BIT(23)
+#define NVM_NON_POSTED_ERASE_DONE_ITER 3000
+
+static inline void idg_nvm_set_region_id(struct intel_dg_nvm *nvm, u8 region)
+{
+ iowrite32((u32)region, nvm->base + NVM_REGION_ID_REG);
+}
+
+static inline u32 idg_nvm_error(struct intel_dg_nvm *nvm)
+{
+ void __iomem *base = nvm->base;
+
+ u32 reg = ioread32(base + NVM_ACCESS_ERROR_REG) & NVM_ACCESS_ERROR_PCIE_MASK;
+
+ /* reset error bits */
+ if (reg)
+ iowrite32(reg, base + NVM_ACCESS_ERROR_REG);
+
+ return reg;
+}
+
+static inline u32 idg_nvm_read32(struct intel_dg_nvm *nvm, u32 address)
+{
+ void __iomem *base = nvm->base;
+
+ iowrite32(address, base + NVM_ADDRESS_REG);
+
+ return ioread32(base + NVM_TRIGGER_REG);
+}
+
+static inline u64 idg_nvm_read64(struct intel_dg_nvm *nvm, u32 address)
+{
+ void __iomem *base = nvm->base;
+
+ iowrite32(address, base + NVM_ADDRESS_REG);
+
+ return readq(base + NVM_TRIGGER_REG);
+}
+
+static void idg_nvm_write32(struct intel_dg_nvm *nvm, u32 address, u32 data)
+{
+ void __iomem *base = nvm->base;
+
+ iowrite32(address, base + NVM_ADDRESS_REG);
+
+ iowrite32(data, base + NVM_TRIGGER_REG);
+}
+
+static void idg_nvm_write64(struct intel_dg_nvm *nvm, u32 address, u64 data)
+{
+ void __iomem *base = nvm->base;
+
+ iowrite32(address, base + NVM_ADDRESS_REG);
+
+ writeq(data, base + NVM_TRIGGER_REG);
+}
+
+static int idg_nvm_get_access_map(struct intel_dg_nvm *nvm, u32 *access_map)
+{
+ u32 fmstr4_addr;
+ u32 fmstr4;
+ u32 flmap1;
+ u32 fmba;
+
+ idg_nvm_set_region_id(nvm, NVM_REGION_ID_DESCRIPTOR);
+
+ flmap1 = idg_nvm_read32(nvm, NVM_FLMAP1_REG);
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ /* Get Flash Master Baser Address (FMBA) */
+ fmba = (FIELD_GET(NVM_MAP_ADDR_MASK, flmap1) << NVM_MAP_ADDR_SHIFT);
+ fmstr4_addr = fmba + NVM_FLMSTR4_OFFSET;
+
+ fmstr4 = idg_nvm_read32(nvm, fmstr4_addr);
+ if (idg_nvm_error(nvm))
+ return -EIO;
+
+ *access_map = fmstr4;
+ return 0;
+}
+
+/*
+ * Region read/write access encoded in the access map
+ * in the following order from the lower bit:
+ * [3:0] regions 12-15 read state
+ * [7:4] regions 12-15 write state
+ * [19:8] regions 0-11 read state
+ * [31:20] regions 0-11 write state
+ */
+static bool idg_nvm_region_readable(u32 access_map, u8 region)
+{
+ if (region < 12)
+ return access_map & BIT(region + 8); /* [19:8] */
+ else
+ return access_map & BIT(region - 12); /* [3:0] */
+}
+
+static bool idg_nvm_region_writable(u32 access_map, u8 region)
+{
+ if (region < 12)
+ return access_map & BIT(region + 20); /* [31:20] */
+ else
+ return access_map & BIT(region - 8); /* [7:4] */
+}
+
+static int idg_nvm_is_valid(struct intel_dg_nvm *nvm)
+{
+ u32 is_valid;
+
+ idg_nvm_set_region_id(nvm, NVM_REGION_ID_DESCRIPTOR);
+
+ is_valid = idg_nvm_read32(nvm, NVM_VALSIG_REG);
+ if (idg_nvm_error(nvm))
+ return -EIO;
+
+ if (is_valid != NVM_FLVALSIG)
+ return -ENODEV;
+
+ return 0;
+}
+
+static unsigned int idg_nvm_get_region(const struct intel_dg_nvm *nvm, loff_t from)
+{
+ unsigned int i;
+
+ for (i = 0; i < nvm->nregions; i++) {
+ if ((nvm->regions[i].offset + nvm->regions[i].size - 1) >= from &&
+ nvm->regions[i].offset <= from &&
+ nvm->regions[i].size != 0)
+ break;
+ }
+
+ return i;
+}
+
+static ssize_t idg_nvm_rewrite_partial(struct intel_dg_nvm *nvm, loff_t to,
+ loff_t offset, size_t len, const u32 *newdata)
+{
+ u32 data = idg_nvm_read32(nvm, to);
+
+ if (idg_nvm_error(nvm))
+ return -EIO;
+
+ memcpy((u8 *)&data + offset, newdata, len);
+
+ idg_nvm_write32(nvm, to, data);
+ if (idg_nvm_error(nvm))
+ return -EIO;
+
+ return len;
+}
+
+static ssize_t idg_write(struct intel_dg_nvm *nvm, u8 region,
+ loff_t to, size_t len, const unsigned char *buf)
+{
+ size_t len_s = len;
+ size_t to_shift;
+ size_t len8;
+ size_t len4;
+ ssize_t ret;
+ size_t to4;
+ size_t i;
+
+ idg_nvm_set_region_id(nvm, region);
+
+ to4 = ALIGN_DOWN(to, sizeof(u32));
+ to_shift = min(sizeof(u32) - ((size_t)to - to4), len);
+ if (to - to4) {
+ ret = idg_nvm_rewrite_partial(nvm, to4, to - to4, to_shift, (u32 *)&buf[0]);
+ if (ret < 0)
+ return ret;
+
+ buf += to_shift;
+ to += to_shift;
+ len_s -= to_shift;
+ }
+
+ if (!IS_ALIGNED(to, sizeof(u64)) &&
+ ((to ^ (to + len_s)) & GENMASK(31, 10))) {
+ /*
+ * Workaround reads/writes across 1k-aligned addresses
+ * (start u32 before 1k, end u32 after)
+ * as this fails on hardware.
+ */
+ u32 data;
+
+ memcpy(&data, &buf[0], sizeof(u32));
+ idg_nvm_write32(nvm, to, data);
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ buf += sizeof(u32);
+ to += sizeof(u32);
+ len_s -= sizeof(u32);
+ }
+
+ len8 = ALIGN_DOWN(len_s, sizeof(u64));
+ for (i = 0; i < len8; i += sizeof(u64)) {
+ u64 data;
+
+ memcpy(&data, &buf[i], sizeof(u64));
+ idg_nvm_write64(nvm, to + i, data);
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ }
+
+ len4 = len_s - len8;
+ if (len4 >= sizeof(u32)) {
+ u32 data;
+
+ memcpy(&data, &buf[i], sizeof(u32));
+ idg_nvm_write32(nvm, to + i, data);
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ i += sizeof(u32);
+ len4 -= sizeof(u32);
+ }
+
+ if (len4 > 0) {
+ ret = idg_nvm_rewrite_partial(nvm, to + i, 0, len4, (u32 *)&buf[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ return len;
+}
+
+static ssize_t idg_read(struct intel_dg_nvm *nvm, u8 region,
+ loff_t from, size_t len, unsigned char *buf)
+{
+ size_t len_s = len;
+ size_t from_shift;
+ size_t from4;
+ size_t len8;
+ size_t len4;
+ size_t i;
+
+ idg_nvm_set_region_id(nvm, region);
+
+ from4 = ALIGN_DOWN(from, sizeof(u32));
+ from_shift = min(sizeof(u32) - ((size_t)from - from4), len);
+
+ if (from - from4) {
+ u32 data = idg_nvm_read32(nvm, from4);
+
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ memcpy(&buf[0], (u8 *)&data + (from - from4), from_shift);
+ len_s -= from_shift;
+ buf += from_shift;
+ from += from_shift;
+ }
+
+ if (!IS_ALIGNED(from, sizeof(u64)) &&
+ ((from ^ (from + len_s)) & GENMASK(31, 10))) {
+ /*
+ * Workaround reads/writes across 1k-aligned addresses
+ * (start u32 before 1k, end u32 after)
+ * as this fails on hardware.
+ */
+ u32 data = idg_nvm_read32(nvm, from);
+
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ memcpy(&buf[0], &data, sizeof(data));
+ len_s -= sizeof(u32);
+ buf += sizeof(u32);
+ from += sizeof(u32);
+ }
+
+ len8 = ALIGN_DOWN(len_s, sizeof(u64));
+ for (i = 0; i < len8; i += sizeof(u64)) {
+ u64 data = idg_nvm_read64(nvm, from + i);
+
+ if (idg_nvm_error(nvm))
+ return -EIO;
+
+ memcpy(&buf[i], &data, sizeof(data));
+ }
+
+ len4 = len_s - len8;
+ if (len4 >= sizeof(u32)) {
+ u32 data = idg_nvm_read32(nvm, from + i);
+
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ memcpy(&buf[i], &data, sizeof(data));
+ i += sizeof(u32);
+ len4 -= sizeof(u32);
+ }
+
+ if (len4 > 0) {
+ u32 data = idg_nvm_read32(nvm, from + i);
+
+ if (idg_nvm_error(nvm))
+ return -EIO;
+ memcpy(&buf[i], &data, len4);
+ }
+
+ return len;
+}
+
+static ssize_t
+idg_erase(struct intel_dg_nvm *nvm, u8 region, loff_t from, u64 len, u64 *fail_addr)
+{
+ void __iomem *base2 = nvm->base2;
+ void __iomem *base = nvm->base;
+ const u32 block = 0x10;
+ u32 iter = 0;
+ u32 reg;
+ u64 i;
+
+ for (i = 0; i < len; i += SZ_4K) {
+ iowrite32(from + i, base + NVM_ADDRESS_REG);
+ iowrite32(region << 24 | block, base + NVM_ERASE_REG);
+ if (nvm->non_posted_erase) {
+ /* Wait for Erase Done */
+ reg = ioread32(base2 + NVM_DEBUG_REG);
+ while (!(reg & NVM_NON_POSTED_ERASE_DONE) &&
+ ++iter < NVM_NON_POSTED_ERASE_DONE_ITER) {
+ msleep(10);
+ reg = ioread32(base2 + NVM_DEBUG_REG);
+ }
+ if (reg & NVM_NON_POSTED_ERASE_DONE) {
+ /* Clear Erase Done */
+ iowrite32(reg, base2 + NVM_DEBUG_REG);
+ } else {
+ *fail_addr = from + i;
+ return -ETIME;
+ }
+ }
+ /* Since the writes are via sgunit
+ * we cannot do back to back erases.
+ */
+ msleep(50);
+ }
+ return len;
+}
+
+static int intel_dg_nvm_init(struct intel_dg_nvm *nvm, struct device *device,
+ bool non_posted_erase)
+{
+ u32 access_map = 0;
+ unsigned int i, n;
+ int ret;
+
+ nvm->dev = device;
+
+ /* clean error register, previous errors are ignored */
+ idg_nvm_error(nvm);
+
+ ret = idg_nvm_is_valid(nvm);
+ if (ret) {
+ dev_err(device, "The MEM is not valid %d\n", ret);
+ return ret;
+ }
+
+ if (idg_nvm_get_access_map(nvm, &access_map))
+ return -EIO;
+
+ for (i = 0, n = 0; i < nvm->nregions; i++) {
+ u32 address, base, limit, region;
+ u8 id = nvm->regions[i].id;
+
+ address = NVM_FLREG(id);
+ region = idg_nvm_read32(nvm, address);
+
+ base = FIELD_GET(NVM_FREG_BASE_MASK, region) << NVM_FREG_ADDR_SHIFT;
+ limit = (FIELD_GET(NVM_FREG_ADDR_MASK, region) << NVM_FREG_ADDR_SHIFT) |
+ NVM_FREG_MIN_REGION_SIZE;
+
+ dev_dbg(device, "[%d] %s: region: 0x%08X base: 0x%08x limit: 0x%08x\n",
+ id, nvm->regions[i].name, region, base, limit);
+
+ if (base >= limit || (i > 0 && limit == 0)) {
+ dev_dbg(device, "[%d] %s: disabled\n",
+ id, nvm->regions[i].name);
+ nvm->regions[i].is_readable = 0;
+ continue;
+ }
+
+ if (nvm->size < limit)
+ nvm->size = limit;
+
+ nvm->regions[i].offset = base;
+ nvm->regions[i].size = limit - base + 1;
+ /* No write access to descriptor; mask it out*/
+ nvm->regions[i].is_writable = idg_nvm_region_writable(access_map, id);
+
+ nvm->regions[i].is_readable = idg_nvm_region_readable(access_map, id);
+ dev_dbg(device, "Registered, %s id=%d offset=%lld size=%lld rd=%d wr=%d\n",
+ nvm->regions[i].name,
+ nvm->regions[i].id,
+ nvm->regions[i].offset,
+ nvm->regions[i].size,
+ nvm->regions[i].is_readable,
+ nvm->regions[i].is_writable);
+
+ if (nvm->regions[i].is_readable)
+ n++;
+ }
+
+ nvm->non_posted_erase = non_posted_erase;
+
+ dev_dbg(device, "Registered %d regions\n", n);
+ dev_dbg(device, "Non posted erase %d\n", nvm->non_posted_erase);
+
+ /* Need to add 1 to the amount of memory
+ * so it is reported as an even block
+ */
+ nvm->size += 1;
+
+ return n;
+}
+
+static int intel_dg_mtd_erase(struct mtd_info *mtd, struct erase_info *info)
+{
+ struct intel_dg_nvm *nvm = mtd->priv;
+ size_t total_len;
+ unsigned int idx;
+ ssize_t bytes;
+ loff_t from;
+ size_t len;
+ u8 region;
+ u64 addr;
+ int ret;
+
+ if (WARN_ON(!nvm))
+ return -EINVAL;
+
+ if (!IS_ALIGNED(info->addr, SZ_4K) || !IS_ALIGNED(info->len, SZ_4K)) {
+ dev_err(&mtd->dev, "unaligned erase %llx %llx\n",
+ info->addr, info->len);
+ info->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
+ return -EINVAL;
+ }
+
+ total_len = info->len;
+ addr = info->addr;
+
+ ret = pm_runtime_resume_and_get(nvm->dev);
+ if (ret < 0) {
+ dev_err(&mtd->dev, "rpm: get failed %d\n", ret);
+ return ret;
+ }
+
+ ret = 0;
+ guard(mutex)(&nvm->lock);
+
+ while (total_len > 0) {
+ if (!IS_ALIGNED(addr, SZ_4K) || !IS_ALIGNED(total_len, SZ_4K)) {
+ dev_err(&mtd->dev, "unaligned erase %llx %zx\n", addr, total_len);
+ info->fail_addr = addr;
+ ret = -ERANGE;
+ break;
+ }
+
+ idx = idg_nvm_get_region(nvm, addr);
+ if (idx >= nvm->nregions) {
+ dev_err(&mtd->dev, "out of range");
+ info->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
+ ret = -ERANGE;
+ break;
+ }
+
+ from = addr - nvm->regions[idx].offset;
+ region = nvm->regions[idx].id;
+ len = total_len;
+ if (len > nvm->regions[idx].size - from)
+ len = nvm->regions[idx].size - from;
+
+ dev_dbg(&mtd->dev, "erasing region[%d] %s from %llx len %zx\n",
+ region, nvm->regions[idx].name, from, len);
+
+ bytes = idg_erase(nvm, region, from, len, &info->fail_addr);
+ if (bytes < 0) {
+ dev_dbg(&mtd->dev, "erase failed with %zd\n", bytes);
+ info->fail_addr += nvm->regions[idx].offset;
+ ret = bytes;
+ break;
+ }
+
+ addr += len;
+ total_len -= len;
+ }
+
+ pm_runtime_put_autosuspend(nvm->dev);
+ return ret;
+}
+
+static int intel_dg_mtd_read(struct mtd_info *mtd, loff_t from, size_t len,
+ size_t *retlen, u_char *buf)
+{
+ struct intel_dg_nvm *nvm = mtd->priv;
+ unsigned int idx;
+ ssize_t ret;
+ u8 region;
+
+ if (WARN_ON(!nvm))
+ return -EINVAL;
+
+ idx = idg_nvm_get_region(nvm, from);
+
+ dev_dbg(&mtd->dev, "reading region[%d] %s from %lld len %zd\n",
+ nvm->regions[idx].id, nvm->regions[idx].name, from, len);
+
+ if (idx >= nvm->nregions) {
+ dev_err(&mtd->dev, "out of range");
+ return -ERANGE;
+ }
+
+ from -= nvm->regions[idx].offset;
+ region = nvm->regions[idx].id;
+ if (len > nvm->regions[idx].size - from)
+ len = nvm->regions[idx].size - from;
+
+ ret = pm_runtime_resume_and_get(nvm->dev);
+ if (ret < 0) {
+ dev_err(&mtd->dev, "rpm: get failed %zd\n", ret);
+ return ret;
+ }
+
+ guard(mutex)(&nvm->lock);
+
+ ret = idg_read(nvm, region, from, len, buf);
+ if (ret < 0) {
+ dev_dbg(&mtd->dev, "read failed with %zd\n", ret);
+ } else {
+ *retlen = ret;
+ ret = 0;
+ }
+
+ pm_runtime_put_autosuspend(nvm->dev);
+ return ret;
+}
+
+static int intel_dg_mtd_write(struct mtd_info *mtd, loff_t to, size_t len,
+ size_t *retlen, const u_char *buf)
+{
+ struct intel_dg_nvm *nvm = mtd->priv;
+ unsigned int idx;
+ ssize_t ret;
+ u8 region;
+
+ if (WARN_ON(!nvm))
+ return -EINVAL;
+
+ idx = idg_nvm_get_region(nvm, to);
+
+ dev_dbg(&mtd->dev, "writing region[%d] %s to %lld len %zd\n",
+ nvm->regions[idx].id, nvm->regions[idx].name, to, len);
+
+ if (idx >= nvm->nregions) {
+ dev_err(&mtd->dev, "out of range");
+ return -ERANGE;
+ }
+
+ to -= nvm->regions[idx].offset;
+ region = nvm->regions[idx].id;
+ if (len > nvm->regions[idx].size - to)
+ len = nvm->regions[idx].size - to;
+
+ ret = pm_runtime_resume_and_get(nvm->dev);
+ if (ret < 0) {
+ dev_err(&mtd->dev, "rpm: get failed %zd\n", ret);
+ return ret;
+ }
+
+ guard(mutex)(&nvm->lock);
+
+ ret = idg_write(nvm, region, to, len, buf);
+ if (ret < 0) {
+ dev_dbg(&mtd->dev, "write failed with %zd\n", ret);
+ } else {
+ *retlen = ret;
+ ret = 0;
+ }
+
+ pm_runtime_put_autosuspend(nvm->dev);
+ return ret;
+}
+
+static void intel_dg_nvm_release(struct kref *kref)
+{
+ struct intel_dg_nvm *nvm = container_of(kref, struct intel_dg_nvm, refcnt);
+ int i;
+
+ pr_debug("freeing intel_dg nvm\n");
+ for (i = 0; i < nvm->nregions; i++)
+ kfree(nvm->regions[i].name);
+ mutex_destroy(&nvm->lock);
+ kfree(nvm);
+}
+
+static int intel_dg_mtd_get_device(struct mtd_info *mtd)
+{
+ struct mtd_info *master = mtd_get_master(mtd);
+ struct intel_dg_nvm *nvm = master->priv;
+
+ if (WARN_ON(!nvm))
+ return -EINVAL;
+ pr_debug("get mtd %s %d\n", mtd->name, kref_read(&nvm->refcnt));
+ kref_get(&nvm->refcnt);
+
+ return 0;
+}
+
+static void intel_dg_mtd_put_device(struct mtd_info *mtd)
+{
+ struct mtd_info *master = mtd_get_master(mtd);
+ struct intel_dg_nvm *nvm = master->priv;
+
+ if (WARN_ON(!nvm))
+ return;
+ pr_debug("put mtd %s %d\n", mtd->name, kref_read(&nvm->refcnt));
+ kref_put(&nvm->refcnt, intel_dg_nvm_release);
+}
+
+static int intel_dg_nvm_init_mtd(struct intel_dg_nvm *nvm, struct device *device,
+ unsigned int nparts, bool writable_override)
+{
+ struct mtd_partition *parts = NULL;
+ unsigned int i, n;
+ int ret;
+
+ dev_dbg(device, "registering with mtd\n");
+
+ nvm->mtd.owner = THIS_MODULE;
+ nvm->mtd.dev.parent = device;
+ nvm->mtd.flags = MTD_CAP_NORFLASH;
+ nvm->mtd.type = MTD_DATAFLASH;
+ nvm->mtd.priv = nvm;
+ nvm->mtd._write = intel_dg_mtd_write;
+ nvm->mtd._read = intel_dg_mtd_read;
+ nvm->mtd._erase = intel_dg_mtd_erase;
+ nvm->mtd._get_device = intel_dg_mtd_get_device;
+ nvm->mtd._put_device = intel_dg_mtd_put_device;
+ nvm->mtd.writesize = SZ_1; /* 1 byte granularity */
+ nvm->mtd.erasesize = SZ_4K; /* 4K bytes granularity */
+ nvm->mtd.size = nvm->size;
+
+ parts = kzalloc_objs(*parts, nvm->nregions);
+ if (!parts)
+ return -ENOMEM;
+
+ for (i = 0, n = 0; i < nvm->nregions && n < nparts; i++) {
+ if (!nvm->regions[i].is_readable)
+ continue;
+ parts[n].name = nvm->regions[i].name;
+ parts[n].offset = nvm->regions[i].offset;
+ parts[n].size = nvm->regions[i].size;
+ if (!nvm->regions[i].is_writable && !writable_override)
+ parts[n].mask_flags = MTD_WRITEABLE;
+ n++;
+ }
+
+ ret = mtd_device_register(&nvm->mtd, parts, n);
+
+ kfree(parts);
+ return ret;
+}
+
+static int intel_dg_mtd_probe(struct auxiliary_device *aux_dev,
+ const struct auxiliary_device_id *aux_dev_id)
+{
+ struct intel_dg_nvm_dev *invm = auxiliary_dev_to_intel_dg_nvm_dev(aux_dev);
+ struct intel_dg_nvm *nvm;
+ struct device *device;
+ unsigned int nregions;
+ unsigned int i, n;
+ int ret;
+
+ device = &aux_dev->dev;
+
+ /* count available regions */
+ for (nregions = 0, i = 0; i < INTEL_DG_NVM_REGIONS; i++) {
+ if (invm->regions[i].name)
+ nregions++;
+ }
+
+ if (!nregions) {
+ dev_err(device, "no regions defined\n");
+ return -ENODEV;
+ }
+
+ nvm = kzalloc_flex(*nvm, regions, nregions);
+ if (!nvm)
+ return -ENOMEM;
+
+ kref_init(&nvm->refcnt);
+ mutex_init(&nvm->lock);
+ nvm->nregions = nregions;
+
+ for (n = 0, i = 0; i < INTEL_DG_NVM_REGIONS; i++) {
+ if (!invm->regions[i].name)
+ continue;
+
+ char *name = kasprintf(GFP_KERNEL, "%s.%s",
+ dev_name(&aux_dev->dev), invm->regions[i].name);
+ if (!name) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ nvm->regions[n].name = name;
+ nvm->regions[n].id = i;
+ n++;
+ }
+
+ ret = devm_pm_runtime_enable(device);
+ if (ret < 0) {
+ dev_err(device, "rpm: enable failed %d\n", ret);
+ goto err_norpm;
+ }
+
+ pm_runtime_set_autosuspend_delay(device, INTEL_DG_NVM_RPM_TIMEOUT_MS);
+ pm_runtime_use_autosuspend(device);
+
+ ret = pm_runtime_resume_and_get(device);
+ if (ret < 0) {
+ dev_err(device, "rpm: get failed %d\n", ret);
+ goto err_norpm;
+ }
+
+ nvm->base = devm_ioremap_resource(device, &invm->bar);
+ if (IS_ERR(nvm->base)) {
+ ret = PTR_ERR(nvm->base);
+ goto err;
+ }
+
+ if (invm->non_posted_erase) {
+ nvm->base2 = devm_ioremap_resource(device, &invm->bar2);
+ if (IS_ERR(nvm->base2)) {
+ ret = PTR_ERR(nvm->base2);
+ goto err;
+ }
+ }
+
+ ret = intel_dg_nvm_init(nvm, device, invm->non_posted_erase);
+ if (ret < 0) {
+ dev_err(device, "cannot initialize nvm %d\n", ret);
+ goto err;
+ }
+
+ ret = intel_dg_nvm_init_mtd(nvm, device, ret, invm->writable_override);
+ if (ret) {
+ dev_err(device, "failed init mtd %d\n", ret);
+ goto err;
+ }
+
+ dev_set_drvdata(&aux_dev->dev, nvm);
+
+ pm_runtime_put(device);
+ return 0;
+
+err:
+ pm_runtime_put(device);
+err_norpm:
+ kref_put(&nvm->refcnt, intel_dg_nvm_release);
+ return ret;
+}
+
+static void intel_dg_mtd_remove(struct auxiliary_device *aux_dev)
+{
+ struct intel_dg_nvm *nvm = dev_get_drvdata(&aux_dev->dev);
+
+ if (!nvm)
+ return;
+
+ mtd_device_unregister(&nvm->mtd);
+
+ dev_set_drvdata(&aux_dev->dev, NULL);
+
+ kref_put(&nvm->refcnt, intel_dg_nvm_release);
+}
+
+static const struct auxiliary_device_id intel_dg_mtd_id_table[] = {
+ {
+ .name = "i915.nvm",
+ },
+ {
+ .name = "xe.nvm",
+ },
+ {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(auxiliary, intel_dg_mtd_id_table);
+
+static struct auxiliary_driver intel_dg_mtd_driver = {
+ .probe = intel_dg_mtd_probe,
+ .remove = intel_dg_mtd_remove,
+ .driver = {
+ /* auxiliary_driver_register() sets .name to be the modname */
+ },
+ .id_table = intel_dg_mtd_id_table
+};
+module_auxiliary_driver(intel_dg_mtd_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Intel Corporation");
+MODULE_DESCRIPTION("Intel DGFX MTD driver");
diff --git a/drivers/mtd/devices/mtdram.c b/drivers/mtd/devices/mtdram.c
index 1c97fabc4bf9..3fff3cdd9834 100644
--- a/drivers/mtd/devices/mtdram.c
+++ b/drivers/mtd/devices/mtdram.c
@@ -158,7 +158,7 @@ static int __init init_mtdram(void)
return -EINVAL;
/* Allocate some memory */
- mtd_info = kmalloc(sizeof(struct mtd_info), GFP_KERNEL);
+ mtd_info = kmalloc_obj(struct mtd_info);
if (!mtd_info)
return -ENOMEM;
diff --git a/drivers/mtd/devices/phram.c b/drivers/mtd/devices/phram.c
index fd9ec165e61a..b42cadcd76b1 100644
--- a/drivers/mtd/devices/phram.c
+++ b/drivers/mtd/devices/phram.c
@@ -130,7 +130,7 @@ static int register_device(struct platform_device *pdev, const char *name,
struct phram_mtd_list *new;
int ret = -ENOMEM;
- new = kzalloc(sizeof(*new), GFP_KERNEL);
+ new = kzalloc_obj(*new);
if (!new)
goto out0;
diff --git a/drivers/mtd/devices/pmc551.c b/drivers/mtd/devices/pmc551.c
index 6597fc2aad34..dc0ebfeb846e 100644
--- a/drivers/mtd/devices/pmc551.c
+++ b/drivers/mtd/devices/pmc551.c
@@ -715,11 +715,11 @@ static int __init init_pmc551(void)
msize = length;
}
- mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
+ mtd = kzalloc_obj(struct mtd_info);
if (!mtd)
break;
- priv = kzalloc(sizeof(struct mypriv), GFP_KERNEL);
+ priv = kzalloc_obj(struct mypriv);
if (!priv) {
kfree(mtd);
break;
diff --git a/drivers/mtd/devices/slram.c b/drivers/mtd/devices/slram.c
index 8297b366a066..aa38ecdffc97 100644
--- a/drivers/mtd/devices/slram.c
+++ b/drivers/mtd/devices/slram.c
@@ -129,71 +129,79 @@ static int slram_write(struct mtd_info *mtd, loff_t to, size_t len,
static int register_device(char *name, unsigned long start, unsigned long length)
{
slram_mtd_list_t **curmtd;
+ slram_mtd_list_t *new_mtd;
+ struct mtd_info *mtdinfo;
+ slram_priv_t *priv;
+ int ret = -ENOMEM;
curmtd = &slram_mtdlist;
while (*curmtd) {
curmtd = &(*curmtd)->next;
}
- *curmtd = kmalloc(sizeof(slram_mtd_list_t), GFP_KERNEL);
- if (!(*curmtd)) {
+ new_mtd = kmalloc_obj(slram_mtd_list_t);
+ if (!new_mtd) {
E("slram: Cannot allocate new MTD device.\n");
return(-ENOMEM);
}
- (*curmtd)->mtdinfo = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
- (*curmtd)->next = NULL;
-
- if ((*curmtd)->mtdinfo) {
- (*curmtd)->mtdinfo->priv =
- kzalloc(sizeof(slram_priv_t), GFP_KERNEL);
+ new_mtd->next = NULL;
- if (!(*curmtd)->mtdinfo->priv) {
- kfree((*curmtd)->mtdinfo);
- (*curmtd)->mtdinfo = NULL;
- }
+ mtdinfo = kzalloc_obj(struct mtd_info);
+ if (!mtdinfo) {
+ E("slram: Cannot allocate new MTD device.\n");
+ goto err_free_list;
}
+ new_mtd->mtdinfo = mtdinfo;
- if (!(*curmtd)->mtdinfo) {
+ priv = kzalloc_obj(slram_priv_t);
+ if (!priv) {
E("slram: Cannot allocate new MTD device.\n");
- return(-ENOMEM);
+ goto err_free_mtdinfo;
}
+ mtdinfo->priv = priv;
- if (!(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start =
- memremap(start, length,
- MEMREMAP_WB | MEMREMAP_WT | MEMREMAP_WC))) {
+ priv->start = memremap(start, length,
+ MEMREMAP_WB | MEMREMAP_WT | MEMREMAP_WC);
+ if (!priv->start) {
E("slram: memremap failed\n");
- return -EIO;
+ ret = -EIO;
+ goto err_free_priv;
}
- ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end =
- ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start + length;
-
-
- (*curmtd)->mtdinfo->name = name;
- (*curmtd)->mtdinfo->size = length;
- (*curmtd)->mtdinfo->flags = MTD_CAP_RAM;
- (*curmtd)->mtdinfo->_erase = slram_erase;
- (*curmtd)->mtdinfo->_point = slram_point;
- (*curmtd)->mtdinfo->_unpoint = slram_unpoint;
- (*curmtd)->mtdinfo->_read = slram_read;
- (*curmtd)->mtdinfo->_write = slram_write;
- (*curmtd)->mtdinfo->owner = THIS_MODULE;
- (*curmtd)->mtdinfo->type = MTD_RAM;
- (*curmtd)->mtdinfo->erasesize = SLRAM_BLK_SZ;
- (*curmtd)->mtdinfo->writesize = 1;
-
- if (mtd_device_register((*curmtd)->mtdinfo, NULL, 0)) {
+ priv->end = priv->start + length;
+
+ mtdinfo->name = name;
+ mtdinfo->size = length;
+ mtdinfo->flags = MTD_CAP_RAM;
+ mtdinfo->_erase = slram_erase;
+ mtdinfo->_point = slram_point;
+ mtdinfo->_unpoint = slram_unpoint;
+ mtdinfo->_read = slram_read;
+ mtdinfo->_write = slram_write;
+ mtdinfo->owner = THIS_MODULE;
+ mtdinfo->type = MTD_RAM;
+ mtdinfo->erasesize = SLRAM_BLK_SZ;
+ mtdinfo->writesize = 1;
+
+ if (mtd_device_register(mtdinfo, NULL, 0)) {
E("slram: Failed to register new device\n");
- memunmap(((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start);
- kfree((*curmtd)->mtdinfo->priv);
- kfree((*curmtd)->mtdinfo);
- return(-EAGAIN);
+ ret = -EAGAIN;
+ goto err_unmap;
}
+ *curmtd = new_mtd;
T("slram: Registered device %s from %luKiB to %luKiB\n", name,
(start / 1024), ((start + length) / 1024));
- T("slram: Mapped from 0x%p to 0x%p\n",
- ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->start,
- ((slram_priv_t *)(*curmtd)->mtdinfo->priv)->end);
- return(0);
+ T("slram: Mapped from 0x%p to 0x%p\n", priv->start, priv->end);
+ return 0;
+
+err_unmap:
+ memunmap(priv->start);
+err_free_priv:
+ kfree(priv);
+err_free_mtdinfo:
+ kfree(mtdinfo);
+err_free_list:
+ kfree(new_mtd);
+ return ret;
}
static void unregister_devices(void)
diff --git a/drivers/mtd/ftl.c b/drivers/mtd/ftl.c
index 8c22064ead38..fc9185d44d92 100644
--- a/drivers/mtd/ftl.c
+++ b/drivers/mtd/ftl.c
@@ -201,16 +201,13 @@ static int build_maps(partition_t *part)
/* Set up erase unit maps */
part->DataUnits = le16_to_cpu(part->header.NumEraseUnits) -
part->header.NumTransferUnits;
- part->EUNInfo = kmalloc_array(part->DataUnits, sizeof(struct eun_info_t),
- GFP_KERNEL);
+ part->EUNInfo = kmalloc_objs(struct eun_info_t, part->DataUnits);
if (!part->EUNInfo)
goto out;
for (i = 0; i < part->DataUnits; i++)
part->EUNInfo[i].Offset = 0xffffffff;
part->XferInfo =
- kmalloc_array(part->header.NumTransferUnits,
- sizeof(struct xfer_info_t),
- GFP_KERNEL);
+ kmalloc_objs(struct xfer_info_t, part->header.NumTransferUnits);
if (!part->XferInfo)
goto out_EUNInfo;
@@ -263,7 +260,7 @@ static int build_maps(partition_t *part)
/* Set up virtual page map */
blocks = le32_to_cpu(header.FormattedSize) >> header.BlockSize;
- part->VirtualBlockMap = vmalloc(array_size(blocks, sizeof(uint32_t)));
+ part->VirtualBlockMap = vmalloc_array(blocks, sizeof(uint32_t));
if (!part->VirtualBlockMap)
goto out_XferInfo;
@@ -339,12 +336,12 @@ static int erase_xfer(partition_t *part,
/* Is there a free erase slot? Always in MTD. */
- erase=kmalloc(sizeof(struct erase_info), GFP_KERNEL);
+ erase=kmalloc_obj(struct erase_info);
if (!erase)
return -ENOMEM;
erase->addr = xfer->Offset;
- erase->len = 1 << part->header.EraseUnitSize;
+ erase->len = 1ULL << part->header.EraseUnitSize;
ret = mtd_erase(part->mbd.mtd, erase);
if (!ret) {
@@ -1007,7 +1004,7 @@ static void ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
{
partition_t *partition;
- partition = kzalloc(sizeof(partition_t), GFP_KERNEL);
+ partition = kzalloc_obj(partition_t);
if (!partition) {
printk(KERN_WARNING "No memory to scan for FTL on %s\n",
diff --git a/drivers/mtd/hyperbus/hbmc-am654.c b/drivers/mtd/hyperbus/hbmc-am654.c
index 82a1e7b7e4d8..9d31464046b2 100644
--- a/drivers/mtd/hyperbus/hbmc-am654.c
+++ b/drivers/mtd/hyperbus/hbmc-am654.c
@@ -272,5 +272,4 @@ module_platform_driver(am654_hbmc_platform_driver);
MODULE_DESCRIPTION("HBMC driver for AM654 SoC");
MODULE_LICENSE("GPL v2");
-MODULE_ALIAS("platform:hbmc-am654");
MODULE_AUTHOR("Vignesh Raghavendra <vigneshr@ti.com>");
diff --git a/drivers/mtd/inftlcore.c b/drivers/mtd/inftlcore.c
index 9739387cff8c..1fb924a47227 100644
--- a/drivers/mtd/inftlcore.c
+++ b/drivers/mtd/inftlcore.c
@@ -52,7 +52,7 @@ static void inftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
pr_debug("INFTL: add_mtd for %s\n", mtd->name);
- inftl = kzalloc(sizeof(*inftl), GFP_KERNEL);
+ inftl = kzalloc_obj(*inftl);
if (!inftl)
return;
@@ -482,10 +482,11 @@ static inline u16 INFTL_findwriteunit(struct INFTLrecord *inftl, unsigned block)
silly = MAX_LOOPS;
while (thisEUN <= inftl->lastEUN) {
- inftl_read_oob(mtd, (thisEUN * inftl->EraseSize) +
- blockofs, 8, &retlen, (char *)&bci);
-
- status = bci.Status | bci.Status1;
+ if (inftl_read_oob(mtd, (thisEUN * inftl->EraseSize) +
+ blockofs, 8, &retlen, (char *)&bci) < 0)
+ status = SECTOR_IGNORE;
+ else
+ status = bci.Status | bci.Status1;
pr_debug("INFTL: status of block %d in EUN %d is %x\n",
block , writeEUN, status);
diff --git a/drivers/mtd/inftlmount.c b/drivers/mtd/inftlmount.c
index 6276daa296da..87e246a6f488 100644
--- a/drivers/mtd/inftlmount.c
+++ b/drivers/mtd/inftlmount.c
@@ -67,12 +67,12 @@ static int find_boot_record(struct INFTLrecord *inftl)
static int warncount = 5;
if (warncount) {
- printk(KERN_WARNING "INFTL: block read at 0x%x "
+ pr_warn("INFTL: block read at 0x%x "
"of mtd%d failed: %d\n",
block * inftl->EraseSize,
inftl->mbd.mtd->index, ret);
if (!--warncount)
- printk(KERN_WARNING "INFTL: further "
+ pr_warn("INFTL: further "
"failures for this block will "
"not be printed\n");
}
@@ -89,7 +89,7 @@ static int find_boot_record(struct INFTLrecord *inftl)
block * inftl->EraseSize + SECTORSIZE + 8,
8, &retlen,(char *)&h1);
if (ret < 0) {
- printk(KERN_WARNING "INFTL: ANAND header found at "
+ pr_warn("INFTL: ANAND header found at "
"0x%x in mtd%d, but OOB data read failed "
"(err %d)\n", block * inftl->EraseSize,
inftl->mbd.mtd->index, ret);
@@ -107,13 +107,13 @@ static int find_boot_record(struct INFTLrecord *inftl)
mtd_read(mtd, block * inftl->EraseSize + 4096, SECTORSIZE,
&retlen, buf);
if (retlen != SECTORSIZE) {
- printk(KERN_WARNING "INFTL: Unable to read spare "
+ pr_warn("INFTL: Unable to read spare "
"Media Header\n");
return -1;
}
/* Check if this one is the same as the first one we found. */
if (memcmp(mh, buf, sizeof(struct INFTLMediaHeader))) {
- printk(KERN_WARNING "INFTL: Primary and spare Media "
+ pr_warn("INFTL: Primary and spare Media "
"Headers disagree.\n");
return -1;
}
@@ -141,14 +141,14 @@ static int find_boot_record(struct INFTLrecord *inftl)
mh->OsakVersion, mh->PercentUsed);
if (mh->NoOfBDTLPartitions == 0) {
- printk(KERN_WARNING "INFTL: Media Header sanity check "
+ pr_warn("INFTL: Media Header sanity check "
"failed: NoOfBDTLPartitions (%d) == 0, "
"must be at least 1\n", mh->NoOfBDTLPartitions);
return -1;
}
if ((mh->NoOfBDTLPartitions + mh->NoOfBinaryPartitions) > 4) {
- printk(KERN_WARNING "INFTL: Media Header sanity check "
+ pr_warn("INFTL: Media Header sanity check "
"failed: Total Partitions (%d) > 4, "
"BDTL=%d Binary=%d\n", mh->NoOfBDTLPartitions +
mh->NoOfBinaryPartitions,
@@ -158,12 +158,12 @@ static int find_boot_record(struct INFTLrecord *inftl)
}
if (mh->BlockMultiplierBits > 1) {
- printk(KERN_WARNING "INFTL: sorry, we don't support "
+ pr_warn("INFTL: sorry, we don't support "
"UnitSizeFactor 0x%02x\n",
mh->BlockMultiplierBits);
return -1;
} else if (mh->BlockMultiplierBits == 1) {
- printk(KERN_WARNING "INFTL: support for INFTL with "
+ pr_warn("INFTL: support for INFTL with "
"UnitSizeFactor 0x%02x is experimental\n",
mh->BlockMultiplierBits);
inftl->EraseSize = inftl->mbd.mtd->erasesize <<
@@ -207,7 +207,7 @@ static int find_boot_record(struct INFTLrecord *inftl)
mtd_erase(mtd, instr);
}
if ((ip->lastUnit - ip->firstUnit + 1) < ip->virtualUnits) {
- printk(KERN_WARNING "INFTL: Media Header "
+ pr_warn("INFTL: Media Header "
"Partition %d sanity check failed\n"
" firstUnit %d : lastUnit %d > "
"virtualUnits %d\n", i, ip->lastUnit,
@@ -215,7 +215,7 @@ static int find_boot_record(struct INFTLrecord *inftl)
return -1;
}
if (ip->Reserved1 != 0) {
- printk(KERN_WARNING "INFTL: Media Header "
+ pr_warn("INFTL: Media Header "
"Partition %d sanity check failed: "
"Reserved1 %d != 0\n",
i, ip->Reserved1);
@@ -227,7 +227,7 @@ static int find_boot_record(struct INFTLrecord *inftl)
}
if (i >= 4) {
- printk(KERN_WARNING "INFTL: Media Header Partition "
+ pr_warn("INFTL: Media Header Partition "
"sanity check failed:\n No partition "
"marked as Disk Partition\n");
return -1;
@@ -237,7 +237,7 @@ static int find_boot_record(struct INFTLrecord *inftl)
inftl->numvunits = ip->virtualUnits;
if (inftl->numvunits > (inftl->nb_blocks -
inftl->nb_boot_blocks - 2)) {
- printk(KERN_WARNING "INFTL: Media Header sanity check "
+ pr_warn("INFTL: Media Header sanity check "
"failed:\n numvunits (%d) > nb_blocks "
"(%d) - nb_boot_blocks(%d) - 2\n",
inftl->numvunits, inftl->nb_blocks,
@@ -385,7 +385,7 @@ int INFTL_formatblock(struct INFTLrecord *inftl, int block)
ret = mtd_erase(inftl->mbd.mtd, instr);
if (ret) {
- printk(KERN_WARNING "INFTL: error while formatting block %d\n",
+ pr_warn("INFTL: error while formatting block %d\n",
block);
goto fail;
}
@@ -428,13 +428,13 @@ static void format_chain(struct INFTLrecord *inftl, unsigned int first_block)
{
unsigned int block = first_block, block1;
- printk(KERN_WARNING "INFTL: formatting chain at block %d\n",
+ pr_warn("INFTL: formatting chain at block %d\n",
first_block);
for (;;) {
block1 = inftl->PUtable[block];
- printk(KERN_WARNING "INFTL: formatting block %d\n", block);
+ pr_warn("INFTL: formatting block %d\n", block);
if (INFTL_formatblock(inftl, block) < 0) {
/*
* Cannot format !!!! Mark it as Bad Unit,
@@ -539,7 +539,7 @@ int INFTL_mount(struct INFTLrecord *s)
/* Search for INFTL MediaHeader and Spare INFTL Media Header */
if (find_boot_record(s) < 0) {
- printk(KERN_WARNING "INFTL: could not find valid boot record?\n");
+ pr_warn("INFTL: could not find valid boot record?\n");
return -ENXIO;
}
@@ -610,7 +610,7 @@ int INFTL_mount(struct INFTLrecord *s)
/* Check for invalid block */
if (erase_mark != ERASE_MARK) {
- printk(KERN_WARNING "INFTL: corrupt block %d "
+ pr_warn("INFTL: corrupt block %d "
"in chain %d, chain length %d, erase "
"mark 0x%x?\n", block, first_block,
chain_length, erase_mark);
@@ -635,7 +635,7 @@ int INFTL_mount(struct INFTLrecord *s)
((prev_block >= s->nb_blocks) &&
(prev_block != BLOCK_NIL))) {
if (chain_length > 0) {
- printk(KERN_WARNING "INFTL: corrupt "
+ pr_warn("INFTL: corrupt "
"block %d in chain %d?\n",
block, first_block);
do_format_chain++;
@@ -670,7 +670,7 @@ int INFTL_mount(struct INFTLrecord *s)
/* Validate next block before following it... */
if (block > s->lastEUN) {
- printk(KERN_WARNING "INFTL: invalid previous "
+ pr_warn("INFTL: invalid previous "
"block %d in chain %d?\n", block,
first_block);
do_format_chain++;
@@ -714,7 +714,7 @@ int INFTL_mount(struct INFTLrecord *s)
if (s->PUtable[block] == BLOCK_NIL)
break;
if (s->PUtable[block] > s->lastEUN) {
- printk(KERN_WARNING "INFTL: invalid prev %d, "
+ pr_warn("INFTL: invalid prev %d, "
"in virtual chain %d\n",
s->PUtable[block], logical_block);
s->PUtable[block] = BLOCK_NIL;
@@ -757,7 +757,7 @@ int INFTL_mount(struct INFTLrecord *s)
pr_debug("INFTL: pass 3, format unused blocks\n");
for (block = s->firstEUN; block <= s->lastEUN; block++) {
if (s->PUtable[block] == BLOCK_NOTEXPLORED) {
- printk("INFTL: unreferenced block %d, formatting it\n",
+ pr_warn("INFTL: unreferenced block %d, formatting it\n",
block);
if (INFTL_formatblock(s, block) < 0)
s->PUtable[block] = BLOCK_RESERVED;
diff --git a/drivers/mtd/lpddr/lpddr_cmds.c b/drivers/mtd/lpddr/lpddr_cmds.c
index 14e36ae71958..a4167ee5e415 100644
--- a/drivers/mtd/lpddr/lpddr_cmds.c
+++ b/drivers/mtd/lpddr/lpddr_cmds.c
@@ -41,7 +41,7 @@ struct mtd_info *lpddr_cmdset(struct map_info *map)
int numchips;
int i, j;
- mtd = kzalloc(sizeof(*mtd), GFP_KERNEL);
+ mtd = kzalloc_obj(*mtd);
if (!mtd)
return NULL;
mtd->priv = map;
@@ -65,8 +65,7 @@ struct mtd_info *lpddr_cmdset(struct map_info *map)
mtd->erasesize = 1 << lpddr->qinfo->UniformBlockSizeShift;
mtd->writesize = 1 << lpddr->qinfo->BufSizeShift;
- shared = kmalloc_array(lpddr->numchips, sizeof(struct flchip_shared),
- GFP_KERNEL);
+ shared = kmalloc_objs(struct flchip_shared, lpddr->numchips);
if (!shared) {
kfree(mtd);
return NULL;
@@ -79,7 +78,7 @@ struct mtd_info *lpddr_cmdset(struct map_info *map)
mutex_init(&shared[i].lock);
for (j = 0; j < lpddr->qinfo->HWPartsNum; j++) {
*chip = lpddr->chips[i];
- chip->start += j << lpddr->chipshift;
+ chip->start += (unsigned long)j << lpddr->chipshift;
chip->oldstate = chip->state = FL_READY;
chip->priv = &shared[i];
/* those should be reset too since
@@ -142,7 +141,7 @@ static int wait_for_ready(struct map_info *map, struct flchip *chip,
if (dsr & DSR_READY_STATUS)
break;
if (!timeo) {
- printk(KERN_ERR "%s: Flash timeout error state %d \n",
+ printk(KERN_ERR "%s: Flash timeout error state %d\n",
map->name, chip_state);
ret = -ETIME;
break;
@@ -186,7 +185,7 @@ static int wait_for_ready(struct map_info *map, struct flchip *chip,
if (dsr & DSR_ERR) {
/* Clear DSR*/
map_write(map, CMD(~(DSR_ERR)), map->pfow_base + PFOW_DSR);
- printk(KERN_WARNING"%s: Bad status on wait: 0x%x \n",
+ printk(KERN_WARNING"%s: Bad status on wait: 0x%x\n",
map->name, dsr);
print_drs_error(dsr);
ret = -EIO;
@@ -321,7 +320,7 @@ static int chip_ready(struct map_info *map, struct flchip *chip, int mode)
/* Resume and pretend we weren't here. */
put_chip(map, chip);
printk(KERN_ERR "%s: suspend operation failed."
- "State may be wrong \n", map->name);
+ "State may be wrong\n", map->name);
return -EIO;
}
chip->erase_suspended = 1;
@@ -468,7 +467,7 @@ static int do_write_buffer(struct map_info *map, struct flchip *chip,
chip->state = FL_WRITING;
ret = wait_for_ready(map, chip, (1<<lpddr->qinfo->ProgBufferTime));
if (ret) {
- printk(KERN_WARNING"%s Buffer program error: %d at %lx; \n",
+ printk(KERN_WARNING"%s Buffer program error: %d at %lx\n",
map->name, ret, adr);
goto out;
}
@@ -559,7 +558,7 @@ static int lpddr_point(struct mtd_info *mtd, loff_t adr, size_t len,
break;
if ((len + ofs - 1) >> lpddr->chipshift)
- thislen = (1<<lpddr->chipshift) - ofs;
+ thislen = (1UL << lpddr->chipshift) - ofs;
else
thislen = len;
/* get the chip */
@@ -575,7 +574,7 @@ static int lpddr_point(struct mtd_info *mtd, loff_t adr, size_t len,
len -= thislen;
ofs = 0;
- last_end += 1 << lpddr->chipshift;
+ last_end += 1UL << lpddr->chipshift;
chipnum++;
chip = &lpddr->chips[chipnum];
}
@@ -601,7 +600,7 @@ static int lpddr_unpoint (struct mtd_info *mtd, loff_t adr, size_t len)
break;
if ((len + ofs - 1) >> lpddr->chipshift)
- thislen = (1<<lpddr->chipshift) - ofs;
+ thislen = (1UL << lpddr->chipshift) - ofs;
else
thislen = len;
@@ -736,7 +735,7 @@ static int do_xxlock(struct mtd_info *mtd, loff_t adr, uint32_t len, int thunk)
ret = wait_for_ready(map, chip, 1);
if (ret) {
- printk(KERN_ERR "%s: block unlock error status %d \n",
+ printk(KERN_ERR "%s: block unlock error status %d\n",
map->name, ret);
goto out;
}
diff --git a/drivers/mtd/lpddr/qinfo_probe.c b/drivers/mtd/lpddr/qinfo_probe.c
index 137ae5f0a19b..f041913a7112 100644
--- a/drivers/mtd/lpddr/qinfo_probe.c
+++ b/drivers/mtd/lpddr/qinfo_probe.c
@@ -55,7 +55,7 @@ static long lpddr_get_qinforec_pos(struct map_info *map, char *id_str)
return minor | (major << bankwidth);
}
}
- printk(KERN_ERR"%s qinfo id string is wrong! \n", map->name);
+ printk(KERN_ERR"%s qinfo id string is wrong!\n", map->name);
BUG();
return -1;
}
@@ -112,7 +112,7 @@ static int lpddr_pfow_present(struct map_info *map, struct lpddr_private *lpddr)
return 1; /* "PFOW" is found */
out:
- printk(KERN_WARNING"%s: PFOW string at 0x%lx is not found \n",
+ printk(KERN_WARNING"%s: PFOW string at 0x%lx is not found\n",
map->name, map->pfow_base);
return 0;
}
@@ -120,7 +120,7 @@ out:
static int lpddr_chip_setup(struct map_info *map, struct lpddr_private *lpddr)
{
- lpddr->qinfo = kzalloc(sizeof(struct qinfo_chip), GFP_KERNEL);
+ lpddr->qinfo = kzalloc_obj(struct qinfo_chip);
if (!lpddr->qinfo)
return 0;
@@ -167,8 +167,7 @@ static struct lpddr_private *lpddr_probe_chip(struct map_info *map)
lpddr.numchips = 1;
numvirtchips = lpddr.numchips * lpddr.qinfo->HWPartsNum;
- retlpddr = kzalloc(struct_size(retlpddr, chips, numvirtchips),
- GFP_KERNEL);
+ retlpddr = kzalloc_flex(*retlpddr, chips, numvirtchips);
if (!retlpddr)
return NULL;
diff --git a/drivers/mtd/maps/Kconfig b/drivers/mtd/maps/Kconfig
index 8a8b19874e23..f447902d707e 100644
--- a/drivers/mtd/maps/Kconfig
+++ b/drivers/mtd/maps/Kconfig
@@ -75,17 +75,6 @@ config MTD_PHYSMAP_OF
physically into the CPU's memory. The mapping description here is
taken from OF device tree.
-config MTD_PHYSMAP_BT1_ROM
- bool "Baikal-T1 Boot ROMs OF-based physical memory map handling"
- depends on MTD_PHYSMAP_OF
- depends on MIPS_BAIKAL_T1 || COMPILE_TEST
- select MTD_COMPLEX_MAPPINGS
- select MULTIPLEXER
- select MUX_MMIO
- help
- This provides some extra DT physmap parsing for the Baikal-T1
- platforms, some detection and setting up ROMs-specific accessors.
-
config MTD_PHYSMAP_VERSATILE
bool "ARM Versatile OF-based physical memory map handling"
depends on MTD_PHYSMAP_OF
@@ -136,40 +125,6 @@ config MTD_SUN_UFLASH
Sun Microsystems boardsets. This driver will require CFI support
in the kernel, so if you did not enable CFI previously, do that now.
-config MTD_SC520CDP
- tristate "CFI Flash device mapped on AMD SC520 CDP"
- depends on (MELAN || COMPILE_TEST) && MTD_CFI
- help
- The SC520 CDP board has two banks of CFI-compliant chips and one
- Dual-in-line JEDEC chip. This 'mapping' driver supports that
- arrangement, implementing three MTD devices.
-
-config MTD_NETSC520
- tristate "CFI Flash device mapped on AMD NetSc520"
- depends on (MELAN || COMPILE_TEST) && MTD_CFI
- help
- This enables access routines for the flash chips on the AMD NetSc520
- demonstration board. If you have one of these boards and would like
- to use the flash chips on it, say 'Y'.
-
-config MTD_TS5500
- tristate "JEDEC Flash device mapped on Technologic Systems TS-5500"
- depends on TS5500 || COMPILE_TEST
- select MTD_JEDECPROBE
- select MTD_CFI_AMDSTD
- help
- This provides a driver for the on-board flash of the Technologic
- System's TS-5500 board. The 2MB flash is split into 3 partitions
- which are accessed as separate MTD devices.
-
- mtd0 and mtd2 are the two BIOS drives, which use the resident
- flash disk (RFD) flash translation layer.
-
- mtd1 allows you to reprogram your BIOS. BE VERY CAREFUL.
-
- Note that jumper 3 ("Write Enable Drive A") must be set
- otherwise detection won't succeed.
-
config MTD_SBC_GXX
tristate "CFI Flash device mapped on Arcom SBC-GXx boards"
depends on X86 && MTD_CFI_INTELEXT && MTD_COMPLEX_MAPPINGS
@@ -249,12 +204,6 @@ config MTD_TSUNAMI
help
Support for the flash chip on Tsunami TIG bus.
-config MTD_NETtel
- tristate "CFI flash device on SnapGear/SecureEdge"
- depends on X86 && MTD_JEDECPROBE
- help
- Support for flash chips on NETtel/SecureEdge/SnapGear boards.
-
config MTD_LANTIQ
tristate "Lantiq SoC NOR support"
depends on LANTIQ
@@ -300,13 +249,6 @@ config MTD_DC21285
21285 bridge used with Intel's StrongARM processors. More info at
<https://www.intel.com/design/bridge/docs/21285_documentation.htm>.
-config MTD_IMPA7
- tristate "JEDEC Flash device mapped on impA7"
- depends on ARM && MTD_JEDECPROBE
- help
- This enables access to the NOR Flash on the impA7 board of
- implementa GmbH. If you have such a board, say 'Y' here.
-
# This needs CFI or JEDEC, depending on the cards found.
config MTD_PCI
tristate "PCI MTD driver"
diff --git a/drivers/mtd/maps/Makefile b/drivers/mtd/maps/Makefile
index 019f1e92cc41..01745eca1f73 100644
--- a/drivers/mtd/maps/Makefile
+++ b/drivers/mtd/maps/Makefile
@@ -19,7 +19,6 @@ obj-$(CONFIG_MTD_TSUNAMI) += tsunami_flash.o
obj-$(CONFIG_MTD_PXA2XX) += pxa2xx-flash.o
obj-$(CONFIG_MTD_PHYSMAP) += physmap.o
physmap-y := physmap-core.o
-physmap-$(CONFIG_MTD_PHYSMAP_BT1_ROM) += physmap-bt1-rom.o
physmap-$(CONFIG_MTD_PHYSMAP_VERSATILE) += physmap-versatile.o
physmap-$(CONFIG_MTD_PHYSMAP_GEMINI) += physmap-gemini.o
physmap-$(CONFIG_MTD_PHYSMAP_IXP4XX) += physmap-ixp4xx.o
@@ -27,16 +26,11 @@ obj-$(CONFIG_MTD_PISMO) += pismo.o
obj-$(CONFIG_MTD_PCMCIA) += pcmciamtd.o
obj-$(CONFIG_MTD_SA1100) += sa1100-flash.o
obj-$(CONFIG_MTD_SBC_GXX) += sbc_gxx.o
-obj-$(CONFIG_MTD_SC520CDP) += sc520cdp.o
-obj-$(CONFIG_MTD_NETSC520) += netsc520.o
-obj-$(CONFIG_MTD_TS5500) += ts5500_flash.o
obj-$(CONFIG_MTD_SUN_UFLASH) += sun_uflash.o
obj-$(CONFIG_MTD_SCx200_DOCFLASH)+= scx200_docflash.o
obj-$(CONFIG_MTD_SOLUTIONENGINE)+= solutionengine.o
obj-$(CONFIG_MTD_PCI) += pci.o
-obj-$(CONFIG_MTD_IMPA7) += impa7.o
obj-$(CONFIG_MTD_UCLINUX) += uclinux.o
-obj-$(CONFIG_MTD_NETtel) += nettel.o
obj-$(CONFIG_MTD_SCB2_FLASH) += scb2_flash.o
obj-$(CONFIG_MTD_PLATRAM) += plat-ram.o
obj-$(CONFIG_MTD_VMU) += vmu-flash.o
diff --git a/drivers/mtd/maps/amd76xrom.c b/drivers/mtd/maps/amd76xrom.c
index 281fcbaa74e7..457e6ab7f3fb 100644
--- a/drivers/mtd/maps/amd76xrom.c
+++ b/drivers/mtd/maps/amd76xrom.c
@@ -188,7 +188,7 @@ static int amd76xrom_init_one(struct pci_dev *pdev,
int i;
if (!map) {
- map = kmalloc(sizeof(*map), GFP_KERNEL);
+ map = kmalloc_obj(*map);
if (!map)
goto out;
}
@@ -296,12 +296,10 @@ static void amd76xrom_remove_one(struct pci_dev *pdev)
}
static const struct pci_device_id amd76xrom_pci_tbl[] = {
- { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7410,
- PCI_ANY_ID, PCI_ANY_ID, },
- { PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7440,
- PCI_ANY_ID, PCI_ANY_ID, },
- { PCI_VENDOR_ID_AMD, 0x7468 }, /* amd8111 support */
- { 0, }
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7410) },
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7440) },
+ { PCI_DEVICE_SUB(PCI_VENDOR_ID_AMD, 0x7468, 0, 0) }, /* amd8111 support */
+ { }
};
MODULE_DEVICE_TABLE(pci, amd76xrom_pci_tbl);
diff --git a/drivers/mtd/maps/ck804xrom.c b/drivers/mtd/maps/ck804xrom.c
index c0216bc740cc..ef4afa8c1931 100644
--- a/drivers/mtd/maps/ck804xrom.c
+++ b/drivers/mtd/maps/ck804xrom.c
@@ -218,7 +218,7 @@ static int __init ck804xrom_init_one(struct pci_dev *pdev,
int i;
if (!map) {
- map = kmalloc(sizeof(*map), GFP_KERNEL);
+ map = kmalloc_obj(*map);
if (!map)
goto out;
}
@@ -335,7 +335,7 @@ static const struct pci_device_id ck804xrom_pci_tbl[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0365), .driver_data = DEV_MCP55 },
{ PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0366), .driver_data = DEV_MCP55 },
{ PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, 0x0367), .driver_data = DEV_MCP55 },
- { 0, }
+ { }
};
#if 0
diff --git a/drivers/mtd/maps/esb2rom.c b/drivers/mtd/maps/esb2rom.c
index 15d5b76ff504..043bad544414 100644
--- a/drivers/mtd/maps/esb2rom.c
+++ b/drivers/mtd/maps/esb2rom.c
@@ -278,7 +278,7 @@ static int __init esb2rom_init_one(struct pci_dev *pdev,
int i;
if (!map) {
- map = kmalloc(sizeof(*map), GFP_KERNEL);
+ map = kmalloc_obj(*map);
if (!map)
goto out;
}
@@ -385,19 +385,13 @@ static void esb2rom_remove_one(struct pci_dev *pdev)
}
static const struct pci_device_id esb2rom_pci_tbl[] = {
- { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0,
- PCI_ANY_ID, PCI_ANY_ID, },
- { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0,
- PCI_ANY_ID, PCI_ANY_ID, },
- { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0,
- PCI_ANY_ID, PCI_ANY_ID, },
- { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0,
- PCI_ANY_ID, PCI_ANY_ID, },
- { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1,
- PCI_ANY_ID, PCI_ANY_ID, },
- { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB2_0,
- PCI_ANY_ID, PCI_ANY_ID, },
- { 0, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0) },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0) },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0) },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0) },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1) },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB2_0) },
+ { },
};
#if 0
diff --git a/drivers/mtd/maps/ichxrom.c b/drivers/mtd/maps/ichxrom.c
index c8b2793691db..8fecd1820f6e 100644
--- a/drivers/mtd/maps/ichxrom.c
+++ b/drivers/mtd/maps/ichxrom.c
@@ -212,7 +212,7 @@ static int __init ichxrom_init_one(struct pci_dev *pdev,
int i;
if (!map) {
- map = kmalloc(sizeof(*map), GFP_KERNEL);
+ map = kmalloc_obj(*map);
if (!map)
goto out;
}
@@ -323,17 +323,12 @@ static void ichxrom_remove_one(struct pci_dev *pdev)
}
static const struct pci_device_id ichxrom_pci_tbl[] = {
- { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0,
- PCI_ANY_ID, PCI_ANY_ID, },
- { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0,
- PCI_ANY_ID, PCI_ANY_ID, },
- { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0,
- PCI_ANY_ID, PCI_ANY_ID, },
- { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0,
- PCI_ANY_ID, PCI_ANY_ID, },
- { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1,
- PCI_ANY_ID, PCI_ANY_ID, },
- { 0, },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0), },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0), },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0), },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0), },
+ { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ESB_1), },
+ { },
};
#if 0
diff --git a/drivers/mtd/maps/impa7.c b/drivers/mtd/maps/impa7.c
deleted file mode 100644
index b41401852fb7..000000000000
--- a/drivers/mtd/maps/impa7.c
+++ /dev/null
@@ -1,115 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Handle mapping of the NOR flash on implementa A7 boards
- *
- * Copyright 2002 SYSGO Real-Time Solutions GmbH
- */
-
-#include <linux/module.h>
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <asm/io.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/map.h>
-#include <linux/mtd/partitions.h>
-
-#define WINDOW_ADDR0 0x00000000 /* physical properties of flash */
-#define WINDOW_SIZE0 0x00800000
-#define WINDOW_ADDR1 0x10000000 /* physical properties of flash */
-#define WINDOW_SIZE1 0x00800000
-#define NUM_FLASHBANKS 2
-#define BUSWIDTH 4
-
-#define MSG_PREFIX "impA7:" /* prefix for our printk()'s */
-#define MTDID "impa7-%d" /* for mtdparts= partitioning */
-
-static struct mtd_info *impa7_mtd[NUM_FLASHBANKS];
-
-static const char * const rom_probe_types[] = { "jedec_probe", NULL };
-
-static struct map_info impa7_map[NUM_FLASHBANKS] = {
- {
- .name = "impA7 NOR Flash Bank #0",
- .size = WINDOW_SIZE0,
- .bankwidth = BUSWIDTH,
- },
- {
- .name = "impA7 NOR Flash Bank #1",
- .size = WINDOW_SIZE1,
- .bankwidth = BUSWIDTH,
- },
-};
-
-/*
- * MTD partitioning stuff
- */
-static const struct mtd_partition partitions[] =
-{
- {
- .name = "FileSystem",
- .size = 0x800000,
- .offset = 0x00000000
- },
-};
-
-static int __init init_impa7(void)
-{
- const char * const *type;
- int i;
- static struct { u_long addr; u_long size; } pt[NUM_FLASHBANKS] = {
- { WINDOW_ADDR0, WINDOW_SIZE0 },
- { WINDOW_ADDR1, WINDOW_SIZE1 },
- };
- int devicesfound = 0;
-
- for(i=0; i<NUM_FLASHBANKS; i++)
- {
- printk(KERN_NOTICE MSG_PREFIX "probing 0x%08lx at 0x%08lx\n",
- pt[i].size, pt[i].addr);
-
- impa7_map[i].phys = pt[i].addr;
- impa7_map[i].virt = ioremap(pt[i].addr, pt[i].size);
- if (!impa7_map[i].virt) {
- printk(MSG_PREFIX "failed to ioremap\n");
- return -EIO;
- }
- simple_map_init(&impa7_map[i]);
-
- impa7_mtd[i] = NULL;
- type = rom_probe_types;
- for(; !impa7_mtd[i] && *type; type++) {
- impa7_mtd[i] = do_map_probe(*type, &impa7_map[i]);
- }
-
- if (impa7_mtd[i]) {
- impa7_mtd[i]->owner = THIS_MODULE;
- devicesfound++;
- mtd_device_register(impa7_mtd[i], partitions,
- ARRAY_SIZE(partitions));
- } else {
- iounmap((void __iomem *)impa7_map[i].virt);
- }
- }
- return devicesfound == 0 ? -ENXIO : 0;
-}
-
-static void __exit cleanup_impa7(void)
-{
- int i;
- for (i=0; i<NUM_FLASHBANKS; i++) {
- if (impa7_mtd[i]) {
- mtd_device_unregister(impa7_mtd[i]);
- map_destroy(impa7_mtd[i]);
- iounmap((void __iomem *)impa7_map[i].virt);
- impa7_map[i].virt = NULL;
- }
- }
-}
-
-module_init(init_impa7);
-module_exit(cleanup_impa7);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Pavel Bartusek <pba@sysgo.de>");
-MODULE_DESCRIPTION("MTD map driver for implementa impA7");
diff --git a/drivers/mtd/maps/netsc520.c b/drivers/mtd/maps/netsc520.c
deleted file mode 100644
index 0bb651624f05..000000000000
--- a/drivers/mtd/maps/netsc520.c
+++ /dev/null
@@ -1,127 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/* netsc520.c -- MTD map driver for AMD NetSc520 Demonstration Board
- *
- * Copyright (C) 2001 Mark Langsdorf (mark.langsdorf@amd.com)
- * based on sc520cdp.c by Sysgo Real-Time Solutions GmbH
- *
- * The NetSc520 is a demonstration board for the Elan Sc520 processor available
- * from AMD. It has a single back of 16 megs of 32-bit Flash ROM and another
- * 16 megs of SDRAM.
- */
-
-#include <linux/module.h>
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <asm/io.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/map.h>
-#include <linux/mtd/partitions.h>
-
-
-/*
-** The single, 16 megabyte flash bank is divided into four virtual
-** partitions. The first partition is 768 KiB and is intended to
-** store the kernel image loaded by the bootstrap loader. The second
-** partition is 256 KiB and holds the BIOS image. The third
-** partition is 14.5 MiB and is intended for the flash file system
-** image. The last partition is 512 KiB and contains another copy
-** of the BIOS image and the reset vector.
-**
-** Only the third partition should be mounted. The first partition
-** should not be mounted, but it can erased and written to using the
-** MTD character routines. The second and fourth partitions should
-** not be touched - it is possible to corrupt the BIOS image by
-** mounting these partitions, and potentially the board will not be
-** recoverable afterwards.
-*/
-
-/* partition_info gives details on the logical partitions that the split the
- * single flash device into. If the size if zero we use up to the end of the
- * device. */
-static const struct mtd_partition partition_info[] = {
- {
- .name = "NetSc520 boot kernel",
- .offset = 0,
- .size = 0xc0000
- },
- {
- .name = "NetSc520 Low BIOS",
- .offset = 0xc0000,
- .size = 0x40000
- },
- {
- .name = "NetSc520 file system",
- .offset = 0x100000,
- .size = 0xe80000
- },
- {
- .name = "NetSc520 High BIOS",
- .offset = 0xf80000,
- .size = 0x80000
- },
-};
-#define NUM_PARTITIONS ARRAY_SIZE(partition_info)
-
-#define WINDOW_SIZE 0x00100000
-#define WINDOW_ADDR 0x00200000
-
-static struct map_info netsc520_map = {
- .name = "netsc520 Flash Bank",
- .size = WINDOW_SIZE,
- .bankwidth = 4,
- .phys = WINDOW_ADDR,
-};
-
-#define NUM_FLASH_BANKS ARRAY_SIZE(netsc520_map)
-
-static struct mtd_info *mymtd;
-
-static int __init init_netsc520(void)
-{
- printk(KERN_NOTICE "NetSc520 flash device: 0x%Lx at 0x%Lx\n",
- (unsigned long long)netsc520_map.size,
- (unsigned long long)netsc520_map.phys);
- netsc520_map.virt = ioremap(netsc520_map.phys, netsc520_map.size);
-
- if (!netsc520_map.virt) {
- printk("Failed to ioremap\n");
- return -EIO;
- }
-
- simple_map_init(&netsc520_map);
-
- mymtd = do_map_probe("cfi_probe", &netsc520_map);
- if(!mymtd)
- mymtd = do_map_probe("map_ram", &netsc520_map);
- if(!mymtd)
- mymtd = do_map_probe("map_rom", &netsc520_map);
-
- if (!mymtd) {
- iounmap(netsc520_map.virt);
- return -ENXIO;
- }
-
- mymtd->owner = THIS_MODULE;
- mtd_device_register(mymtd, partition_info, NUM_PARTITIONS);
- return 0;
-}
-
-static void __exit cleanup_netsc520(void)
-{
- if (mymtd) {
- mtd_device_unregister(mymtd);
- map_destroy(mymtd);
- }
- if (netsc520_map.virt) {
- iounmap(netsc520_map.virt);
- netsc520_map.virt = NULL;
- }
-}
-
-module_init(init_netsc520);
-module_exit(cleanup_netsc520);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Mark Langsdorf <mark.langsdorf@amd.com>");
-MODULE_DESCRIPTION("MTD map driver for AMD NetSc520 Demonstration Board");
diff --git a/drivers/mtd/maps/nettel.c b/drivers/mtd/maps/nettel.c
deleted file mode 100644
index 7d349874ffeb..000000000000
--- a/drivers/mtd/maps/nettel.c
+++ /dev/null
@@ -1,462 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/****************************************************************************/
-
-/*
- * nettel.c -- mappings for NETtel/SecureEdge/SnapGear (x86) boards.
- *
- * (C) Copyright 2000-2001, Greg Ungerer (gerg@snapgear.com)
- * (C) Copyright 2001-2002, SnapGear (www.snapgear.com)
- */
-
-/****************************************************************************/
-
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/map.h>
-#include <linux/mtd/partitions.h>
-#include <linux/mtd/cfi.h>
-#include <linux/reboot.h>
-#include <linux/err.h>
-#include <linux/kdev_t.h>
-#include <linux/root_dev.h>
-#include <asm/io.h>
-
-/****************************************************************************/
-
-#define INTEL_BUSWIDTH 1
-#define AMD_WINDOW_MAXSIZE 0x00200000
-#define AMD_BUSWIDTH 1
-
-/*
- * PAR masks and shifts, assuming 64K pages.
- */
-#define SC520_PAR_ADDR_MASK 0x00003fff
-#define SC520_PAR_ADDR_SHIFT 16
-#define SC520_PAR_TO_ADDR(par) \
- (((par)&SC520_PAR_ADDR_MASK) << SC520_PAR_ADDR_SHIFT)
-
-#define SC520_PAR_SIZE_MASK 0x01ffc000
-#define SC520_PAR_SIZE_SHIFT 2
-#define SC520_PAR_TO_SIZE(par) \
- ((((par)&SC520_PAR_SIZE_MASK) << SC520_PAR_SIZE_SHIFT) + (64*1024))
-
-#define SC520_PAR(cs, addr, size) \
- ((cs) | \
- ((((size)-(64*1024)) >> SC520_PAR_SIZE_SHIFT) & SC520_PAR_SIZE_MASK) | \
- (((addr) >> SC520_PAR_ADDR_SHIFT) & SC520_PAR_ADDR_MASK))
-
-#define SC520_PAR_BOOTCS 0x8a000000
-#define SC520_PAR_ROMCS1 0xaa000000
-#define SC520_PAR_ROMCS2 0xca000000 /* Cache disabled, 64K page */
-
-static void *nettel_mmcrp = NULL;
-
-#ifdef CONFIG_MTD_CFI_INTELEXT
-static struct mtd_info *intel_mtd;
-#endif
-static struct mtd_info *amd_mtd;
-
-/****************************************************************************/
-
-/****************************************************************************/
-
-#ifdef CONFIG_MTD_CFI_INTELEXT
-static struct map_info nettel_intel_map = {
- .name = "SnapGear Intel",
- .size = 0,
- .bankwidth = INTEL_BUSWIDTH,
-};
-
-static struct mtd_partition nettel_intel_partitions[] = {
- {
- .name = "SnapGear kernel",
- .offset = 0,
- .size = 0x000e0000
- },
- {
- .name = "SnapGear filesystem",
- .offset = 0x00100000,
- },
- {
- .name = "SnapGear config",
- .offset = 0x000e0000,
- .size = 0x00020000
- },
- {
- .name = "SnapGear Intel",
- .offset = 0
- },
- {
- .name = "SnapGear BIOS Config",
- .offset = 0x007e0000,
- .size = 0x00020000
- },
- {
- .name = "SnapGear BIOS",
- .offset = 0x007e0000,
- .size = 0x00020000
- },
-};
-#endif
-
-static struct map_info nettel_amd_map = {
- .name = "SnapGear AMD",
- .size = AMD_WINDOW_MAXSIZE,
- .bankwidth = AMD_BUSWIDTH,
-};
-
-static const struct mtd_partition nettel_amd_partitions[] = {
- {
- .name = "SnapGear BIOS config",
- .offset = 0x000e0000,
- .size = 0x00010000
- },
- {
- .name = "SnapGear BIOS",
- .offset = 0x000f0000,
- .size = 0x00010000
- },
- {
- .name = "SnapGear AMD",
- .offset = 0
- },
- {
- .name = "SnapGear high BIOS",
- .offset = 0x001f0000,
- .size = 0x00010000
- }
-};
-
-#define NUM_AMD_PARTITIONS ARRAY_SIZE(nettel_amd_partitions)
-
-/****************************************************************************/
-
-#ifdef CONFIG_MTD_CFI_INTELEXT
-
-/*
- * Set the Intel flash back to read mode since some old boot
- * loaders don't.
- */
-static int nettel_reboot_notifier(struct notifier_block *nb, unsigned long val, void *v)
-{
- struct cfi_private *cfi = nettel_intel_map.fldrv_priv;
- unsigned long b;
-
- /* Make sure all FLASH chips are put back into read mode */
- for (b = 0; (b < nettel_intel_partitions[3].size); b += 0x100000) {
- cfi_send_gen_cmd(0xff, 0x55, b, &nettel_intel_map, cfi,
- cfi->device_type, NULL);
- }
- return(NOTIFY_OK);
-}
-
-static struct notifier_block nettel_notifier_block = {
- nettel_reboot_notifier, NULL, 0
-};
-
-#endif
-
-/****************************************************************************/
-
-static int __init nettel_init(void)
-{
- volatile unsigned long *amdpar;
- unsigned long amdaddr, maxsize;
- int num_amd_partitions=0;
-#ifdef CONFIG_MTD_CFI_INTELEXT
- volatile unsigned long *intel0par, *intel1par;
- unsigned long orig_bootcspar, orig_romcs1par;
- unsigned long intel0addr, intel0size;
- unsigned long intel1addr, intel1size;
- int intelboot, intel0cs, intel1cs;
- int num_intel_partitions;
-#endif
- int rc = 0;
-
- nettel_mmcrp = (void *) ioremap(0xfffef000, 4096);
- if (nettel_mmcrp == NULL) {
- printk("SNAPGEAR: failed to disable MMCR cache??\n");
- return(-EIO);
- }
-
- /* Set CPU clock to be 33.000MHz */
- *((unsigned char *) (nettel_mmcrp + 0xc64)) = 0x01;
-
- amdpar = (volatile unsigned long *) (nettel_mmcrp + 0xc4);
-
-#ifdef CONFIG_MTD_CFI_INTELEXT
- intelboot = 0;
- intel0cs = SC520_PAR_ROMCS1;
- intel0par = (volatile unsigned long *) (nettel_mmcrp + 0xc0);
- intel1cs = SC520_PAR_ROMCS2;
- intel1par = (volatile unsigned long *) (nettel_mmcrp + 0xbc);
-
- /*
- * Save the CS settings then ensure ROMCS1 and ROMCS2 are off,
- * otherwise they might clash with where we try to map BOOTCS.
- */
- orig_bootcspar = *amdpar;
- orig_romcs1par = *intel0par;
- *intel0par = 0;
- *intel1par = 0;
-#endif
-
- /*
- * The first thing to do is determine if we have a separate
- * boot FLASH device. Typically this is a small (1 to 2MB)
- * AMD FLASH part. It seems that device size is about the
- * only way to tell if this is the case...
- */
- amdaddr = 0x20000000;
- maxsize = AMD_WINDOW_MAXSIZE;
-
- *amdpar = SC520_PAR(SC520_PAR_BOOTCS, amdaddr, maxsize);
- __asm__ ("wbinvd");
-
- nettel_amd_map.phys = amdaddr;
- nettel_amd_map.virt = ioremap(amdaddr, maxsize);
- if (!nettel_amd_map.virt) {
- printk("SNAPGEAR: failed to ioremap() BOOTCS\n");
- iounmap(nettel_mmcrp);
- return(-EIO);
- }
- simple_map_init(&nettel_amd_map);
-
- if ((amd_mtd = do_map_probe("jedec_probe", &nettel_amd_map))) {
- printk(KERN_NOTICE "SNAPGEAR: AMD flash device size = %dK\n",
- (int)(amd_mtd->size>>10));
-
- amd_mtd->owner = THIS_MODULE;
-
- /* The high BIOS partition is only present for 2MB units */
- num_amd_partitions = NUM_AMD_PARTITIONS;
- if (amd_mtd->size < AMD_WINDOW_MAXSIZE)
- num_amd_partitions--;
- /* Don't add the partition until after the primary INTEL's */
-
-#ifdef CONFIG_MTD_CFI_INTELEXT
- /*
- * Map the Intel flash into memory after the AMD
- * It has to start on a multiple of maxsize.
- */
- maxsize = SC520_PAR_TO_SIZE(orig_romcs1par);
- if (maxsize < (32 * 1024 * 1024))
- maxsize = (32 * 1024 * 1024);
- intel0addr = amdaddr + maxsize;
-#endif
- } else {
-#ifdef CONFIG_MTD_CFI_INTELEXT
- /* INTEL boot FLASH */
- intelboot++;
-
- if (!orig_romcs1par) {
- intel0cs = SC520_PAR_BOOTCS;
- intel0par = (volatile unsigned long *)
- (nettel_mmcrp + 0xc4);
- intel1cs = SC520_PAR_ROMCS1;
- intel1par = (volatile unsigned long *)
- (nettel_mmcrp + 0xc0);
-
- intel0addr = SC520_PAR_TO_ADDR(orig_bootcspar);
- maxsize = SC520_PAR_TO_SIZE(orig_bootcspar);
- } else {
- /* Kernel base is on ROMCS1, not BOOTCS */
- intel0cs = SC520_PAR_ROMCS1;
- intel0par = (volatile unsigned long *)
- (nettel_mmcrp + 0xc0);
- intel1cs = SC520_PAR_BOOTCS;
- intel1par = (volatile unsigned long *)
- (nettel_mmcrp + 0xc4);
-
- intel0addr = SC520_PAR_TO_ADDR(orig_romcs1par);
- maxsize = SC520_PAR_TO_SIZE(orig_romcs1par);
- }
-
- /* Destroy useless AMD MTD mapping */
- amd_mtd = NULL;
- iounmap(nettel_amd_map.virt);
- nettel_amd_map.virt = NULL;
-#else
- /* Only AMD flash supported */
- rc = -ENXIO;
- goto out_unmap2;
-#endif
- }
-
-#ifdef CONFIG_MTD_CFI_INTELEXT
- /*
- * We have determined the INTEL FLASH configuration, so lets
- * go ahead and probe for them now.
- */
-
- /* Set PAR to the maximum size */
- if (maxsize < (32 * 1024 * 1024))
- maxsize = (32 * 1024 * 1024);
- *intel0par = SC520_PAR(intel0cs, intel0addr, maxsize);
-
- /* Turn other PAR off so the first probe doesn't find it */
- *intel1par = 0;
-
- /* Probe for the size of the first Intel flash */
- nettel_intel_map.size = maxsize;
- nettel_intel_map.phys = intel0addr;
- nettel_intel_map.virt = ioremap(intel0addr, maxsize);
- if (!nettel_intel_map.virt) {
- printk("SNAPGEAR: failed to ioremap() ROMCS1\n");
- rc = -EIO;
- goto out_unmap2;
- }
- simple_map_init(&nettel_intel_map);
-
- intel_mtd = do_map_probe("cfi_probe", &nettel_intel_map);
- if (!intel_mtd) {
- rc = -ENXIO;
- goto out_unmap1;
- }
-
- /* Set PAR to the detected size */
- intel0size = intel_mtd->size;
- *intel0par = SC520_PAR(intel0cs, intel0addr, intel0size);
-
- /*
- * Map second Intel FLASH right after first. Set its size to the
- * same maxsize used for the first Intel FLASH.
- */
- intel1addr = intel0addr + intel0size;
- *intel1par = SC520_PAR(intel1cs, intel1addr, maxsize);
- __asm__ ("wbinvd");
-
- maxsize += intel0size;
-
- /* Delete the old map and probe again to do both chips */
- map_destroy(intel_mtd);
- intel_mtd = NULL;
- iounmap(nettel_intel_map.virt);
-
- nettel_intel_map.size = maxsize;
- nettel_intel_map.virt = ioremap(intel0addr, maxsize);
- if (!nettel_intel_map.virt) {
- printk("SNAPGEAR: failed to ioremap() ROMCS1/2\n");
- rc = -EIO;
- goto out_unmap2;
- }
-
- intel_mtd = do_map_probe("cfi_probe", &nettel_intel_map);
- if (! intel_mtd) {
- rc = -ENXIO;
- goto out_unmap1;
- }
-
- intel1size = intel_mtd->size - intel0size;
- if (intel1size > 0) {
- *intel1par = SC520_PAR(intel1cs, intel1addr, intel1size);
- __asm__ ("wbinvd");
- } else {
- *intel1par = 0;
- }
-
- printk(KERN_NOTICE "SNAPGEAR: Intel flash device size = %lldKiB\n",
- (unsigned long long)(intel_mtd->size >> 10));
-
- intel_mtd->owner = THIS_MODULE;
-
- num_intel_partitions = ARRAY_SIZE(nettel_intel_partitions);
-
- if (intelboot) {
- /*
- * Adjust offset and size of last boot partition.
- * Must allow for BIOS region at end of FLASH.
- */
- nettel_intel_partitions[1].size = (intel0size + intel1size) -
- (1024*1024 + intel_mtd->erasesize);
- nettel_intel_partitions[3].size = intel0size + intel1size;
- nettel_intel_partitions[4].offset =
- (intel0size + intel1size) - intel_mtd->erasesize;
- nettel_intel_partitions[4].size = intel_mtd->erasesize;
- nettel_intel_partitions[5].offset =
- nettel_intel_partitions[4].offset;
- nettel_intel_partitions[5].size =
- nettel_intel_partitions[4].size;
- } else {
- /* No BIOS regions when AMD boot */
- num_intel_partitions -= 2;
- }
- rc = mtd_device_register(intel_mtd, nettel_intel_partitions,
- num_intel_partitions);
- if (rc)
- goto out_map_destroy;
-#endif
-
- if (amd_mtd) {
- rc = mtd_device_register(amd_mtd, nettel_amd_partitions,
- num_amd_partitions);
- if (rc)
- goto out_mtd_unreg;
- }
-
-#ifdef CONFIG_MTD_CFI_INTELEXT
- register_reboot_notifier(&nettel_notifier_block);
-#endif
-
- return rc;
-
-out_mtd_unreg:
-#ifdef CONFIG_MTD_CFI_INTELEXT
- mtd_device_unregister(intel_mtd);
-out_map_destroy:
- map_destroy(intel_mtd);
-out_unmap1:
- iounmap(nettel_intel_map.virt);
-#endif
-
-out_unmap2:
- iounmap(nettel_mmcrp);
- iounmap(nettel_amd_map.virt);
-
- return rc;
-}
-
-/****************************************************************************/
-
-static void __exit nettel_cleanup(void)
-{
-#ifdef CONFIG_MTD_CFI_INTELEXT
- unregister_reboot_notifier(&nettel_notifier_block);
-#endif
- if (amd_mtd) {
- mtd_device_unregister(amd_mtd);
- map_destroy(amd_mtd);
- }
- if (nettel_mmcrp) {
- iounmap(nettel_mmcrp);
- nettel_mmcrp = NULL;
- }
- if (nettel_amd_map.virt) {
- iounmap(nettel_amd_map.virt);
- nettel_amd_map.virt = NULL;
- }
-#ifdef CONFIG_MTD_CFI_INTELEXT
- if (intel_mtd) {
- mtd_device_unregister(intel_mtd);
- map_destroy(intel_mtd);
- }
- if (nettel_intel_map.virt) {
- iounmap(nettel_intel_map.virt);
- nettel_intel_map.virt = NULL;
- }
-#endif
-}
-
-/****************************************************************************/
-
-module_init(nettel_init);
-module_exit(nettel_cleanup);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Greg Ungerer <gerg@snapgear.com>");
-MODULE_DESCRIPTION("SnapGear/SecureEdge FLASH support");
-
-/****************************************************************************/
diff --git a/drivers/mtd/maps/pci.c b/drivers/mtd/maps/pci.c
index ca00d211e73e..34041cc8f173 100644
--- a/drivers/mtd/maps/pci.c
+++ b/drivers/mtd/maps/pci.c
@@ -227,22 +227,16 @@ static struct mtd_pci_info intel_dc21285_info = {
static const struct pci_device_id mtd_pci_ids[] = {
{
- .vendor = PCI_VENDOR_ID_INTEL,
- .device = 0x530d,
- .subvendor = PCI_ANY_ID,
- .subdevice = PCI_ANY_ID,
+ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x530d),
.class = PCI_CLASS_MEMORY_OTHER << 8,
.class_mask = 0xffff00,
.driver_data = (unsigned long)&intel_iq80310_info,
- },
- {
- .vendor = PCI_VENDOR_ID_DEC,
- .device = PCI_DEVICE_ID_DEC_21285,
- .subvendor = 0, /* DC21285 defaults to 0 on reset */
- .subdevice = 0, /* DC21285 defaults to 0 on reset */
+ }, {
+ /* DC21285 defaults to 0 for .subvendor and .subdevice on reset */
+ PCI_DEVICE_SUB(PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_21285, 0, 0),
.driver_data = (unsigned long)&intel_dc21285_info,
},
- { 0, }
+ { }
};
/*
@@ -264,7 +258,7 @@ static int mtd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
if (err)
goto out;
- map = kmalloc(sizeof(*map), GFP_KERNEL);
+ map = kmalloc_obj(*map);
err = -ENOMEM;
if (!map)
goto release;
diff --git a/drivers/mtd/maps/pcmciamtd.c b/drivers/mtd/maps/pcmciamtd.c
index 2ac79e1cedd9..a2e5a63e5d18 100644
--- a/drivers/mtd/maps/pcmciamtd.c
+++ b/drivers/mtd/maps/pcmciamtd.c
@@ -665,6 +665,7 @@ static void pcmciamtd_detach(struct pcmcia_device *link)
}
pcmciamtd_release(link);
+ kfree(dev);
}
@@ -673,7 +674,7 @@ static int pcmciamtd_probe(struct pcmcia_device *link)
struct pcmciamtd_dev *dev;
/* Create new memory card device */
- dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ dev = kzalloc_obj(*dev);
if (!dev) return -ENOMEM;
pr_debug("dev=0x%p\n", dev);
diff --git a/drivers/mtd/maps/physmap-bt1-rom.c b/drivers/mtd/maps/physmap-bt1-rom.c
deleted file mode 100644
index 60dccc48f99e..000000000000
--- a/drivers/mtd/maps/physmap-bt1-rom.c
+++ /dev/null
@@ -1,125 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only
-/*
- * Copyright (C) 2020 BAIKAL ELECTRONICS, JSC
- *
- * Authors:
- * Serge Semin <Sergey.Semin@baikalelectronics.ru>
- *
- * Baikal-T1 Physically Mapped Internal ROM driver
- */
-#include <linux/bits.h>
-#include <linux/device.h>
-#include <linux/kernel.h>
-#include <linux/mtd/map.h>
-#include <linux/mtd/xip.h>
-#include <linux/mux/consumer.h>
-#include <linux/of.h>
-#include <linux/platform_device.h>
-#include <linux/string.h>
-#include <linux/types.h>
-
-#include "physmap-bt1-rom.h"
-
-/*
- * Baikal-T1 SoC ROMs are only accessible by the dword-aligned instructions.
- * We have to take this into account when implementing the data read-methods.
- * Note there is no need in bothering with endianness, since both Baikal-T1
- * CPU and MMIO are LE.
- */
-static map_word __xipram bt1_rom_map_read(struct map_info *map,
- unsigned long ofs)
-{
- void __iomem *src = map->virt + ofs;
- unsigned int shift;
- map_word ret;
- u32 data;
-
- /* Read data within offset dword. */
- shift = (uintptr_t)src & 0x3;
- data = readl_relaxed(src - shift);
- if (!shift) {
- ret.x[0] = data;
- return ret;
- }
- ret.x[0] = data >> (shift * BITS_PER_BYTE);
-
- /* Read data from the next dword. */
- shift = 4 - shift;
- if (ofs + shift >= map->size)
- return ret;
-
- data = readl_relaxed(src + shift);
- ret.x[0] |= data << (shift * BITS_PER_BYTE);
-
- return ret;
-}
-
-static void __xipram bt1_rom_map_copy_from(struct map_info *map,
- void *to, unsigned long from,
- ssize_t len)
-{
- void __iomem *src = map->virt + from;
- unsigned int shift, chunk;
- u32 data;
-
- if (len <= 0 || from >= map->size)
- return;
-
- /* Make sure we don't go over the map limit. */
- len = min_t(ssize_t, map->size - from, len);
-
- /*
- * Since requested data size can be pretty big we have to implement
- * the copy procedure as optimal as possible. That's why it's split
- * up into the next three stages: unaligned head, aligned body,
- * unaligned tail.
- */
- shift = (uintptr_t)src & 0x3;
- if (shift) {
- chunk = min_t(ssize_t, 4 - shift, len);
- data = readl_relaxed(src - shift);
- memcpy(to, (char *)&data + shift, chunk);
- src += chunk;
- to += chunk;
- len -= chunk;
- }
-
- while (len >= 4) {
- data = readl_relaxed(src);
- memcpy(to, &data, 4);
- src += 4;
- to += 4;
- len -= 4;
- }
-
- if (len) {
- data = readl_relaxed(src);
- memcpy(to, &data, len);
- }
-}
-
-int of_flash_probe_bt1_rom(struct platform_device *pdev,
- struct device_node *np,
- struct map_info *map)
-{
- struct device *dev = &pdev->dev;
-
- /* It's supposed to be read-only MTD. */
- if (!of_device_is_compatible(np, "mtd-rom")) {
- dev_info(dev, "No mtd-rom compatible string\n");
- return 0;
- }
-
- /* Multiplatform guard. */
- if (!of_device_is_compatible(np, "baikal,bt1-int-rom"))
- return 0;
-
- /* Sanity check the device parameters retrieved from DTB. */
- if (map->bankwidth != 4)
- dev_warn(dev, "Bank width is supposed to be 32 bits wide\n");
-
- map->read = bt1_rom_map_read;
- map->copy_from = bt1_rom_map_copy_from;
-
- return 0;
-}
diff --git a/drivers/mtd/maps/physmap-bt1-rom.h b/drivers/mtd/maps/physmap-bt1-rom.h
deleted file mode 100644
index 6782899598a4..000000000000
--- a/drivers/mtd/maps/physmap-bt1-rom.h
+++ /dev/null
@@ -1,17 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
-#include <linux/mtd/map.h>
-#include <linux/of.h>
-
-#ifdef CONFIG_MTD_PHYSMAP_BT1_ROM
-int of_flash_probe_bt1_rom(struct platform_device *pdev,
- struct device_node *np,
- struct map_info *map);
-#else
-static inline
-int of_flash_probe_bt1_rom(struct platform_device *pdev,
- struct device_node *np,
- struct map_info *map)
-{
- return 0;
-}
-#endif
diff --git a/drivers/mtd/maps/physmap-core.c b/drivers/mtd/maps/physmap-core.c
index 2bd7a1af898c..dcda7685fc99 100644
--- a/drivers/mtd/maps/physmap-core.c
+++ b/drivers/mtd/maps/physmap-core.c
@@ -42,7 +42,6 @@
#include <linux/pm_runtime.h>
#include <linux/gpio/consumer.h>
-#include "physmap-bt1-rom.h"
#include "physmap-gemini.h"
#include "physmap-ixp4xx.h"
#include "physmap-versatile.h"
@@ -268,7 +267,7 @@ static const struct of_device_id of_flash_match[] = {
MODULE_DEVICE_TABLE(of, of_flash_match);
static const char * const of_default_part_probes[] = {
- "cmdlinepart", "RedBoot", "ofpart", "ofoldpart", NULL
+ "cmdlinepart", "ofpart", "ofoldpart", "RedBoot", NULL
};
static const char * const *of_get_part_probes(struct platform_device *dev)
@@ -365,10 +364,6 @@ static int physmap_flash_of_init(struct platform_device *dev)
info->maps[i].bankwidth = bankwidth;
info->maps[i].device_node = dp;
- err = of_flash_probe_bt1_rom(dev, dp, &info->maps[i]);
- if (err)
- return err;
-
err = of_flash_probe_gemini(dev, dp, &info->maps[i]);
if (err)
return err;
diff --git a/drivers/mtd/maps/physmap-gemini.c b/drivers/mtd/maps/physmap-gemini.c
index 9d3b4bf84a1a..1c34b4ef77ea 100644
--- a/drivers/mtd/maps/physmap-gemini.c
+++ b/drivers/mtd/maps/physmap-gemini.c
@@ -181,7 +181,7 @@ int of_flash_probe_gemini(struct platform_device *pdev,
dev_err(dev, "no enabled pin control state\n");
gf->disabled_state = pinctrl_lookup_state(gf->p, "disabled");
- if (IS_ERR(gf->enabled_state)) {
+ if (IS_ERR(gf->disabled_state)) {
dev_err(dev, "no disabled pin control state\n");
} else {
ret = pinctrl_select_state(gf->p, gf->disabled_state);
diff --git a/drivers/mtd/maps/pismo.c b/drivers/mtd/maps/pismo.c
index ecf68922da73..b0310fddcaa3 100644
--- a/drivers/mtd/maps/pismo.c
+++ b/drivers/mtd/maps/pismo.c
@@ -218,7 +218,7 @@ static int pismo_probe(struct i2c_client *client)
return -EIO;
}
- pismo = kzalloc(sizeof(*pismo), GFP_KERNEL);
+ pismo = kzalloc_obj(*pismo);
if (!pismo)
return -ENOMEM;
diff --git a/drivers/mtd/maps/plat-ram.c b/drivers/mtd/maps/plat-ram.c
index 1c541eaf477a..816111031cf1 100644
--- a/drivers/mtd/maps/plat-ram.c
+++ b/drivers/mtd/maps/plat-ram.c
@@ -109,7 +109,7 @@ static int platram_probe(struct platform_device *pdev)
pdata = dev_get_platdata(&pdev->dev);
- info = kzalloc(sizeof(*info), GFP_KERNEL);
+ info = kzalloc_obj(*info);
if (info == NULL) {
err = -ENOMEM;
goto exit_error;
diff --git a/drivers/mtd/maps/pxa2xx-flash.c b/drivers/mtd/maps/pxa2xx-flash.c
index f27c25db6778..bc303a9909d4 100644
--- a/drivers/mtd/maps/pxa2xx-flash.c
+++ b/drivers/mtd/maps/pxa2xx-flash.c
@@ -51,7 +51,7 @@ static int pxa2xx_flash_probe(struct platform_device *pdev)
if (!res)
return -ENODEV;
- info = kzalloc(sizeof(struct pxa2xx_flash_info), GFP_KERNEL);
+ info = kzalloc_obj(struct pxa2xx_flash_info);
if (!info)
return -ENOMEM;
diff --git a/drivers/mtd/maps/sa1100-flash.c b/drivers/mtd/maps/sa1100-flash.c
index 6a54a84d0d9c..4506b27a905e 100644
--- a/drivers/mtd/maps/sa1100-flash.c
+++ b/drivers/mtd/maps/sa1100-flash.c
@@ -170,7 +170,7 @@ static struct sa_info *sa1100_setup_mtd(struct platform_device *pdev,
/*
* Allocate the map_info structs in one go.
*/
- info = kzalloc(struct_size(info, subdev, nr), GFP_KERNEL);
+ info = kzalloc_flex(*info, subdev, nr);
if (!info) {
ret = -ENOMEM;
goto out;
@@ -222,7 +222,7 @@ static struct sa_info *sa1100_setup_mtd(struct platform_device *pdev,
} else if (info->num_subdev > 1) {
struct mtd_info **cdev;
- cdev = kmalloc_array(nr, sizeof(*cdev), GFP_KERNEL);
+ cdev = kmalloc_objs(*cdev, nr);
if (!cdev) {
ret = -ENOMEM;
goto err;
diff --git a/drivers/mtd/maps/sc520cdp.c b/drivers/mtd/maps/sc520cdp.c
deleted file mode 100644
index 8ef7aec634c7..000000000000
--- a/drivers/mtd/maps/sc520cdp.c
+++ /dev/null
@@ -1,294 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/* sc520cdp.c -- MTD map driver for AMD SC520 Customer Development Platform
- *
- * Copyright (C) 2001 Sysgo Real-Time Solutions GmbH
- *
- * The SC520CDP is an evaluation board for the Elan SC520 processor available
- * from AMD. It has two banks of 32-bit Flash ROM, each 8 Megabytes in size,
- * and up to 512 KiB of 8-bit DIL Flash ROM.
- * For details see https://www.amd.com/products/epd/desiging/evalboards/18.elansc520/520_cdp_brief/index.html
- */
-
-#include <linux/module.h>
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <asm/io.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/map.h>
-#include <linux/mtd/concat.h>
-
-/*
-** The Embedded Systems BIOS decodes the first FLASH starting at
-** 0x8400000. This is a *terrible* place for it because accessing
-** the flash at this location causes the A22 address line to be high
-** (that's what 0x8400000 binary's ought to be). But this is the highest
-** order address line on the raw flash devices themselves!!
-** This causes the top HALF of the flash to be accessed first. Beyond
-** the physical limits of the flash, the flash chip aliases over (to
-** 0x880000 which causes the bottom half to be accessed. This splits the
-** flash into two and inverts it! If you then try to access this from another
-** program that does NOT do this insanity, then you *will* access the
-** first half of the flash, but not find what you expect there. That
-** stuff is in the *second* half! Similarly, the address used by the
-** BIOS for the second FLASH bank is also quite a bad choice.
-** If REPROGRAM_PAR is defined below (the default), then this driver will
-** choose more useful addresses for the FLASH banks by reprogramming the
-** responsible PARxx registers in the SC520's MMCR region. This will
-** cause the settings to be incompatible with the BIOS's settings, which
-** shouldn't be a problem since you are running Linux, (i.e. the BIOS is
-** not much use anyway). However, if you need to be compatible with
-** the BIOS for some reason, just undefine REPROGRAM_PAR.
-*/
-#define REPROGRAM_PAR
-
-
-
-#ifdef REPROGRAM_PAR
-
-/* These are the addresses we want.. */
-#define WINDOW_ADDR_0 0x08800000
-#define WINDOW_ADDR_1 0x09000000
-#define WINDOW_ADDR_2 0x09800000
-
-/* .. and these are the addresses the BIOS gives us */
-#define WINDOW_ADDR_0_BIOS 0x08400000
-#define WINDOW_ADDR_1_BIOS 0x08c00000
-#define WINDOW_ADDR_2_BIOS 0x09400000
-
-#else
-
-#define WINDOW_ADDR_0 0x08400000
-#define WINDOW_ADDR_1 0x08C00000
-#define WINDOW_ADDR_2 0x09400000
-
-#endif
-
-#define WINDOW_SIZE_0 0x00800000
-#define WINDOW_SIZE_1 0x00800000
-#define WINDOW_SIZE_2 0x00080000
-
-
-static struct map_info sc520cdp_map[] = {
- {
- .name = "SC520CDP Flash Bank #0",
- .size = WINDOW_SIZE_0,
- .bankwidth = 4,
- .phys = WINDOW_ADDR_0
- },
- {
- .name = "SC520CDP Flash Bank #1",
- .size = WINDOW_SIZE_1,
- .bankwidth = 4,
- .phys = WINDOW_ADDR_1
- },
- {
- .name = "SC520CDP DIL Flash",
- .size = WINDOW_SIZE_2,
- .bankwidth = 1,
- .phys = WINDOW_ADDR_2
- },
-};
-
-#define NUM_FLASH_BANKS ARRAY_SIZE(sc520cdp_map)
-
-static struct mtd_info *mymtd[NUM_FLASH_BANKS];
-static struct mtd_info *merged_mtd;
-
-#ifdef REPROGRAM_PAR
-
-/*
-** The SC520 MMCR (memory mapped control register) region resides
-** at 0xFFFEF000. The 16 Programmable Address Region (PAR) registers
-** are at offset 0x88 in the MMCR:
-*/
-#define SC520_MMCR_BASE 0xFFFEF000
-#define SC520_MMCR_EXTENT 0x1000
-#define SC520_PAR(x) ((0x88/sizeof(unsigned long)) + (x))
-#define NUM_SC520_PAR 16 /* total number of PAR registers */
-
-/*
-** The highest three bits in a PAR register determine what target
-** device is controlled by this PAR. Here, only ROMCS? and BOOTCS
-** devices are of interest.
-*/
-#define SC520_PAR_BOOTCS (0x4<<29)
-#define SC520_PAR_ROMCS0 (0x5<<29)
-#define SC520_PAR_ROMCS1 (0x6<<29)
-#define SC520_PAR_TRGDEV (0x7<<29)
-
-/*
-** Bits 28 thru 26 determine some attributes for the
-** region controlled by the PAR. (We only use non-cacheable)
-*/
-#define SC520_PAR_WRPROT (1<<26) /* write protected */
-#define SC520_PAR_NOCACHE (1<<27) /* non-cacheable */
-#define SC520_PAR_NOEXEC (1<<28) /* code execution denied */
-
-
-/*
-** Bit 25 determines the granularity: 4K or 64K
-*/
-#define SC520_PAR_PG_SIZ4 (0<<25)
-#define SC520_PAR_PG_SIZ64 (1<<25)
-
-/*
-** Build a value to be written into a PAR register.
-** We only need ROM entries, 64K page size:
-*/
-#define SC520_PAR_ENTRY(trgdev, address, size) \
- ((trgdev) | SC520_PAR_NOCACHE | SC520_PAR_PG_SIZ64 | \
- (address) >> 16 | (((size) >> 16) - 1) << 14)
-
-struct sc520_par_table
-{
- unsigned long trgdev;
- unsigned long new_par;
- unsigned long default_address;
-};
-
-static const struct sc520_par_table par_table[NUM_FLASH_BANKS] =
-{
- { /* Flash Bank #0: selected by ROMCS0 */
- SC520_PAR_ROMCS0,
- SC520_PAR_ENTRY(SC520_PAR_ROMCS0, WINDOW_ADDR_0, WINDOW_SIZE_0),
- WINDOW_ADDR_0_BIOS
- },
- { /* Flash Bank #1: selected by ROMCS1 */
- SC520_PAR_ROMCS1,
- SC520_PAR_ENTRY(SC520_PAR_ROMCS1, WINDOW_ADDR_1, WINDOW_SIZE_1),
- WINDOW_ADDR_1_BIOS
- },
- { /* DIL (BIOS) Flash: selected by BOOTCS */
- SC520_PAR_BOOTCS,
- SC520_PAR_ENTRY(SC520_PAR_BOOTCS, WINDOW_ADDR_2, WINDOW_SIZE_2),
- WINDOW_ADDR_2_BIOS
- }
-};
-
-
-static void sc520cdp_setup_par(void)
-{
- unsigned long __iomem *mmcr;
- unsigned long mmcr_val;
- int i, j;
-
- /* map in SC520's MMCR area */
- mmcr = ioremap(SC520_MMCR_BASE, SC520_MMCR_EXTENT);
- if(!mmcr) { /* ioremap failed: skip the PAR reprogramming */
- /* force physical address fields to BIOS defaults: */
- for(i = 0; i < NUM_FLASH_BANKS; i++)
- sc520cdp_map[i].phys = par_table[i].default_address;
- return;
- }
-
- /*
- ** Find the PARxx registers that are responsible for activating
- ** ROMCS0, ROMCS1 and BOOTCS. Reprogram each of these with a
- ** new value from the table.
- */
- for(i = 0; i < NUM_FLASH_BANKS; i++) { /* for each par_table entry */
- for(j = 0; j < NUM_SC520_PAR; j++) { /* for each PAR register */
- mmcr_val = readl(&mmcr[SC520_PAR(j)]);
- /* if target device field matches, reprogram the PAR */
- if((mmcr_val & SC520_PAR_TRGDEV) == par_table[i].trgdev)
- {
- writel(par_table[i].new_par, &mmcr[SC520_PAR(j)]);
- break;
- }
- }
- if(j == NUM_SC520_PAR)
- { /* no matching PAR found: try default BIOS address */
- printk(KERN_NOTICE "Could not find PAR responsible for %s\n",
- sc520cdp_map[i].name);
- printk(KERN_NOTICE "Trying default address 0x%lx\n",
- par_table[i].default_address);
- sc520cdp_map[i].phys = par_table[i].default_address;
- }
- }
- iounmap(mmcr);
-}
-#endif
-
-
-static int __init init_sc520cdp(void)
-{
- int i, j, devices_found = 0;
-
-#ifdef REPROGRAM_PAR
- /* reprogram PAR registers so flash appears at the desired addresses */
- sc520cdp_setup_par();
-#endif
-
- for (i = 0; i < NUM_FLASH_BANKS; i++) {
- printk(KERN_NOTICE "SC520 CDP flash device: 0x%Lx at 0x%Lx\n",
- (unsigned long long)sc520cdp_map[i].size,
- (unsigned long long)sc520cdp_map[i].phys);
-
- sc520cdp_map[i].virt = ioremap(sc520cdp_map[i].phys, sc520cdp_map[i].size);
-
- if (!sc520cdp_map[i].virt) {
- printk("Failed to ioremap\n");
- for (j = 0; j < i; j++) {
- if (mymtd[j]) {
- map_destroy(mymtd[j]);
- iounmap(sc520cdp_map[j].virt);
- }
- }
- return -EIO;
- }
-
- simple_map_init(&sc520cdp_map[i]);
-
- mymtd[i] = do_map_probe("cfi_probe", &sc520cdp_map[i]);
- if(!mymtd[i])
- mymtd[i] = do_map_probe("jedec_probe", &sc520cdp_map[i]);
- if(!mymtd[i])
- mymtd[i] = do_map_probe("map_rom", &sc520cdp_map[i]);
-
- if (mymtd[i]) {
- mymtd[i]->owner = THIS_MODULE;
- ++devices_found;
- }
- else {
- iounmap(sc520cdp_map[i].virt);
- }
- }
- if(devices_found >= 2) {
- /* Combine the two flash banks into a single MTD device & register it: */
- merged_mtd = mtd_concat_create(mymtd, 2, "SC520CDP Flash Banks #0 and #1");
- if(merged_mtd)
- mtd_device_register(merged_mtd, NULL, 0);
- }
- if(devices_found == 3) /* register the third (DIL-Flash) device */
- mtd_device_register(mymtd[2], NULL, 0);
- return(devices_found ? 0 : -ENXIO);
-}
-
-static void __exit cleanup_sc520cdp(void)
-{
- int i;
-
- if (merged_mtd) {
- mtd_device_unregister(merged_mtd);
- mtd_concat_destroy(merged_mtd);
- }
- if (mymtd[2])
- mtd_device_unregister(mymtd[2]);
-
- for (i = 0; i < NUM_FLASH_BANKS; i++) {
- if (mymtd[i])
- map_destroy(mymtd[i]);
- if (sc520cdp_map[i].virt) {
- iounmap(sc520cdp_map[i].virt);
- sc520cdp_map[i].virt = NULL;
- }
- }
-}
-
-module_init(init_sc520cdp);
-module_exit(cleanup_sc520cdp);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Sysgo Real-Time Solutions GmbH");
-MODULE_DESCRIPTION("MTD map driver for AMD SC520 Customer Development Platform");
diff --git a/drivers/mtd/maps/scb2_flash.c b/drivers/mtd/maps/scb2_flash.c
index 57303f904bc1..5c7e1dad101b 100644
--- a/drivers/mtd/maps/scb2_flash.c
+++ b/drivers/mtd/maps/scb2_flash.c
@@ -215,13 +215,8 @@ static void scb2_flash_remove(struct pci_dev *dev)
}
static struct pci_device_id scb2_flash_pci_ids[] = {
- {
- .vendor = PCI_VENDOR_ID_SERVERWORKS,
- .device = PCI_DEVICE_ID_SERVERWORKS_CSB5,
- .subvendor = PCI_ANY_ID,
- .subdevice = PCI_ANY_ID
- },
- { 0, }
+ { PCI_DEVICE(PCI_VENDOR_ID_SERVERWORKS, PCI_DEVICE_ID_SERVERWORKS_CSB5) },
+ { }
};
static struct pci_driver scb2_flash_driver = {
diff --git a/drivers/mtd/maps/sun_uflash.c b/drivers/mtd/maps/sun_uflash.c
index ea3aa026b55b..cac780cd23f9 100644
--- a/drivers/mtd/maps/sun_uflash.c
+++ b/drivers/mtd/maps/sun_uflash.c
@@ -61,7 +61,7 @@ static int uflash_devinit(struct platform_device *op, struct device_node *dp)
return -ENODEV;
}
- up = kzalloc(sizeof(struct uflash_dev), GFP_KERNEL);
+ up = kzalloc_obj(struct uflash_dev);
if (!up)
return -ENOMEM;
diff --git a/drivers/mtd/maps/ts5500_flash.c b/drivers/mtd/maps/ts5500_flash.c
deleted file mode 100644
index 70d6e865f555..000000000000
--- a/drivers/mtd/maps/ts5500_flash.c
+++ /dev/null
@@ -1,108 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * ts5500_flash.c -- MTD map driver for Technology Systems TS-5500 board
- *
- * Copyright (C) 2004 Sean Young <sean@mess.org>
- *
- * Note:
- * - In order for detection to work, jumper 3 must be set.
- * - Drive A and B use the resident flash disk (RFD) flash translation layer.
- * - If you have created your own jffs file system and the bios overwrites
- * it during boot, try disabling Drive A: and B: in the boot order.
- */
-
-#include <linux/init.h>
-#include <linux/module.h>
-#include <linux/kernel.h>
-#include <linux/mtd/map.h>
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/partitions.h>
-#include <linux/types.h>
-
-
-#define WINDOW_ADDR 0x09400000
-#define WINDOW_SIZE 0x00200000
-
-static struct map_info ts5500_map = {
- .name = "TS-5500 Flash",
- .size = WINDOW_SIZE,
- .bankwidth = 1,
- .phys = WINDOW_ADDR
-};
-
-static const struct mtd_partition ts5500_partitions[] = {
- {
- .name = "Drive A",
- .offset = 0,
- .size = 0x0e0000
- },
- {
- .name = "BIOS",
- .offset = 0x0e0000,
- .size = 0x020000,
- },
- {
- .name = "Drive B",
- .offset = 0x100000,
- .size = 0x100000
- }
-};
-
-#define NUM_PARTITIONS ARRAY_SIZE(ts5500_partitions)
-
-static struct mtd_info *mymtd;
-
-static int __init init_ts5500_map(void)
-{
- int rc = 0;
-
- ts5500_map.virt = ioremap(ts5500_map.phys, ts5500_map.size);
-
- if (!ts5500_map.virt) {
- printk(KERN_ERR "Failed to ioremap\n");
- rc = -EIO;
- goto err2;
- }
-
- simple_map_init(&ts5500_map);
-
- mymtd = do_map_probe("jedec_probe", &ts5500_map);
- if (!mymtd)
- mymtd = do_map_probe("map_rom", &ts5500_map);
-
- if (!mymtd) {
- rc = -ENXIO;
- goto err1;
- }
-
- mymtd->owner = THIS_MODULE;
- mtd_device_register(mymtd, ts5500_partitions, NUM_PARTITIONS);
-
- return 0;
-
-err1:
- iounmap(ts5500_map.virt);
-err2:
- return rc;
-}
-
-static void __exit cleanup_ts5500_map(void)
-{
- if (mymtd) {
- mtd_device_unregister(mymtd);
- map_destroy(mymtd);
- }
-
- if (ts5500_map.virt) {
- iounmap(ts5500_map.virt);
- ts5500_map.virt = NULL;
- }
-}
-
-module_init(init_ts5500_map);
-module_exit(cleanup_ts5500_map);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Sean Young <sean@mess.org>");
-MODULE_DESCRIPTION("MTD map driver for Technology Systems TS-5500 board");
-
diff --git a/drivers/mtd/maps/vmu-flash.c b/drivers/mtd/maps/vmu-flash.c
index 53019d313db7..10244e6731d0 100644
--- a/drivers/mtd/maps/vmu-flash.c
+++ b/drivers/mtd/maps/vmu-flash.c
@@ -73,7 +73,7 @@ static struct vmu_block *ofs_to_block(unsigned long src_ofs,
if (num > card->parts[partition].numblocks)
goto failed;
- vblock = kmalloc(sizeof(struct vmu_block), GFP_KERNEL);
+ vblock = kmalloc_obj(struct vmu_block);
if (!vblock)
goto failed;
@@ -539,7 +539,7 @@ static void vmu_queryblocks(struct mapleq *mq)
mtd_cur->_sync = vmu_flash_sync;
mtd_cur->writesize = card->blocklen;
- mpart = kmalloc(sizeof(struct mdev_part), GFP_KERNEL);
+ mpart = kmalloc_obj(struct mdev_part);
if (!mpart)
goto fail_mpart;
@@ -547,8 +547,9 @@ static void vmu_queryblocks(struct mapleq *mq)
mpart->partition = card->partition;
mtd_cur->priv = mpart;
mtd_cur->owner = THIS_MODULE;
+ mtd_cur->dev.parent = &mdev->dev;
- pcache = kzalloc(sizeof(struct vmu_cache), GFP_KERNEL);
+ pcache = kzalloc_obj(struct vmu_cache);
if (!pcache)
goto fail_cache_create;
part_cur->pcache = pcache;
@@ -609,7 +610,7 @@ static int vmu_connect(struct maple_device *mdev)
basic_flash_data = be32_to_cpu(mdev->devinfo.function_data[c - 1]);
- card = kmalloc(sizeof(struct memcard), GFP_KERNEL);
+ card = kzalloc_obj(struct memcard);
if (!card) {
error = -ENOMEM;
goto fail_nomem;
@@ -627,15 +628,13 @@ static int vmu_connect(struct maple_device *mdev)
* Not sure there are actually any multi-partition devices in the
* real world, but the hardware supports them, so, so will we
*/
- card->parts = kmalloc_array(card->partitions, sizeof(struct vmupart),
- GFP_KERNEL);
+ card->parts = kzalloc_objs(struct vmupart, card->partitions);
if (!card->parts) {
error = -ENOMEM;
goto fail_partitions;
}
- card->mtd = kmalloc_array(card->partitions, sizeof(struct mtd_info),
- GFP_KERNEL);
+ card->mtd = kzalloc_objs(struct mtd_info, card->partitions);
if (!card->mtd) {
error = -ENOMEM;
goto fail_mtd_info;
diff --git a/drivers/mtd/mtd_blkdevs.c b/drivers/mtd/mtd_blkdevs.c
index 847c11542f02..4d2e7b7774e9 100644
--- a/drivers/mtd/mtd_blkdevs.c
+++ b/drivers/mtd/mtd_blkdevs.c
@@ -246,9 +246,9 @@ unlock:
blktrans_dev_put(dev);
}
-static int blktrans_getgeo(struct block_device *bdev, struct hd_geometry *geo)
+static int blktrans_getgeo(struct gendisk *disk, struct hd_geometry *geo)
{
- struct mtd_blktrans_dev *dev = bdev->bd_disk->private_data;
+ struct mtd_blktrans_dev *dev = disk->private_data;
int ret = -ENXIO;
mutex_lock(&dev->lock);
@@ -324,7 +324,7 @@ int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new)
new->readonly = 1;
ret = -ENOMEM;
- new->tag_set = kzalloc(sizeof(*new->tag_set), GFP_KERNEL);
+ new->tag_set = kzalloc_obj(*new->tag_set);
if (!new->tag_set)
goto out_list_del;
diff --git a/drivers/mtd/mtd_virt_concat.c b/drivers/mtd/mtd_virt_concat.c
new file mode 100644
index 000000000000..da4277ced4d6
--- /dev/null
+++ b/drivers/mtd/mtd_virt_concat.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Virtual concat MTD device driver
+ *
+ * Copyright (C) 2018 Bernhard Frauendienst
+ * Author: Bernhard Frauendienst <kernel@nospam.obeliks.de>
+ */
+
+#include <linux/device.h>
+#include <linux/mtd/mtd.h>
+#include "mtdcore.h"
+#include <linux/mtd/partitions.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/mtd/concat.h>
+
+#define CONCAT_PROP "part-concat-next"
+#define CONCAT_POSTFIX "concat"
+#define MIN_DEV_PER_CONCAT 1
+
+static LIST_HEAD(concat_node_list);
+
+/**
+ * struct mtd_virt_concat_node - components of a concatenation
+ * @head: List handle
+ * @count: Number of nodes
+ * @nodes: Pointer to the nodes (partitions) to concatenate
+ * @concat: Concatenation container
+ */
+struct mtd_virt_concat_node {
+ struct list_head head;
+ unsigned int count;
+ struct mtd_concat *concat;
+ struct device_node *nodes[] __counted_by(count);
+};
+
+/**
+ * mtd_is_part_concat - Check if the device is already part
+ * of a concatenated device
+ * @dev: pointer to 'device_node'
+ *
+ * Return: true if the device is already part of a concatenation,
+ * false otherwise.
+ */
+static bool mtd_is_part_concat(struct device_node *dev)
+{
+ struct mtd_virt_concat_node *item;
+ int idx;
+
+ list_for_each_entry(item, &concat_node_list, head) {
+ for (idx = 0; idx < item->count; idx++) {
+ if (item->nodes[idx] == dev)
+ return true;
+ }
+ }
+ return false;
+}
+
+static void mtd_virt_concat_put_mtd_devices(struct mtd_concat *concat)
+{
+ int i;
+
+ for (i = 0; i < concat->num_subdev; i++)
+ put_mtd_device(concat->subdev[i]);
+}
+
+void mtd_virt_concat_destroy_joins(void)
+{
+ struct mtd_virt_concat_node *item, *tmp;
+ struct mtd_info *mtd;
+
+ list_for_each_entry_safe(item, tmp, &concat_node_list, head) {
+ mtd = &item->concat->mtd;
+ if (item->concat) {
+ mtd_device_unregister(mtd);
+ kfree(mtd->name);
+ mtd_virt_concat_put_mtd_devices(item->concat);
+ mtd_concat_destroy(mtd);
+ }
+ }
+}
+
+/**
+ * mtd_virt_concat_destroy - Destroy the concat that includes the mtd object
+ * @mtd: pointer to 'mtd_info'
+ *
+ * Return: 0 on success, -error otherwise.
+ */
+int mtd_virt_concat_destroy(struct mtd_info *mtd)
+{
+ struct mtd_info *child, *master = mtd_get_master(mtd);
+ struct mtd_virt_concat_node *item, *tmp;
+ struct mtd_concat *concat;
+ int idx, ret = 0;
+ bool is_mtd_found;
+
+ list_for_each_entry_safe(item, tmp, &concat_node_list, head) {
+ is_mtd_found = false;
+
+ /* Find the concat item that hold the mtd device */
+ for (idx = 0; idx < item->count; idx++) {
+ if (item->nodes[idx] == mtd->dev.of_node) {
+ is_mtd_found = true;
+ break;
+ }
+ }
+ if (!is_mtd_found)
+ continue;
+ concat = item->concat;
+
+ /*
+ * Since this concatenated device is being removed, retrieve
+ * all MTD devices that are part of it and register them
+ * individually.
+ */
+ for (idx = 0; idx < concat->num_subdev; idx++) {
+ child = concat->subdev[idx];
+ if (child->dev.of_node != mtd->dev.of_node) {
+ ret = add_mtd_device(child);
+ if (ret)
+ goto out;
+ }
+ }
+ /* Destroy the concat */
+ if (concat->mtd.name) {
+ del_mtd_device(&concat->mtd);
+ kfree(concat->mtd.name);
+ mtd_virt_concat_put_mtd_devices(item->concat);
+ mtd_concat_destroy(&concat->mtd);
+ }
+
+ for (idx = 0; idx < item->count; idx++)
+ of_node_put(item->nodes[idx]);
+
+ kfree(item);
+ }
+ return 0;
+out:
+ mutex_lock(&master->master.partitions_lock);
+ list_del(&child->part.node);
+ mutex_unlock(&master->master.partitions_lock);
+ kfree(mtd->name);
+ kfree(mtd);
+
+ return ret;
+}
+
+/**
+ * mtd_virt_concat_create_item - Create a concat item
+ * @parts: pointer to 'device_node'
+ * @count: number of mtd devices that make up
+ * the concatenated device.
+ *
+ * Return: 0 on success, -error otherwise.
+ */
+static int mtd_virt_concat_create_item(struct device_node *parts,
+ unsigned int count)
+{
+ struct mtd_virt_concat_node *item;
+ struct mtd_concat *concat;
+ int i;
+
+ for (i = 0; i < (count - 1); i++) {
+ if (mtd_is_part_concat(of_parse_phandle(parts, CONCAT_PROP, i)))
+ return 0;
+ }
+
+ item = kzalloc_flex(*item, nodes, count, GFP_KERNEL);
+ if (!item)
+ return -ENOMEM;
+
+ item->count = count;
+
+ /*
+ * The partition in which "part-concat-next" property
+ * is defined is the first device in the list of concat
+ * devices.
+ */
+ item->nodes[0] = parts;
+
+ for (i = 1; i < count; i++)
+ item->nodes[i] = of_parse_phandle(parts, CONCAT_PROP, (i - 1));
+
+ concat = kzalloc_flex(*concat, subdev, count, GFP_KERNEL);
+ if (!concat) {
+ kfree(item);
+ return -ENOMEM;
+ }
+
+ item->concat = concat;
+
+ list_add_tail(&item->head, &concat_node_list);
+
+ return 0;
+}
+
+void mtd_virt_concat_destroy_items(void)
+{
+ struct mtd_virt_concat_node *item, *temp;
+ int i;
+
+ list_for_each_entry_safe(item, temp, &concat_node_list, head) {
+ for (i = 0; i < item->count; i++)
+ of_node_put(item->nodes[i]);
+
+ kfree(item);
+ }
+}
+
+/**
+ * mtd_virt_concat_add - Add a mtd device to the concat list
+ * @mtd: pointer to 'mtd_info'
+ *
+ * Return: true on success, false otherwise.
+ */
+bool mtd_virt_concat_add(struct mtd_info *mtd)
+{
+ struct mtd_virt_concat_node *item;
+ struct mtd_concat *concat;
+ int idx;
+
+ list_for_each_entry(item, &concat_node_list, head) {
+ concat = item->concat;
+ for (idx = 0; idx < item->count; idx++) {
+ if (item->nodes[idx] == mtd->dev.of_node) {
+ concat->subdev[concat->num_subdev++] = mtd;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * mtd_virt_concat_node_create - List all the concatenations found in DT
+ *
+ * Return: 0 on success, -error otherwise.
+ */
+int mtd_virt_concat_node_create(void)
+{
+ struct device_node *parts = NULL;
+ int ret = 0, count = 0;
+
+ /* List all the concatenations found in DT */
+ do {
+ parts = of_find_node_with_property(parts, CONCAT_PROP);
+ if (!of_device_is_available(parts))
+ continue;
+
+ if (mtd_is_part_concat(parts))
+ continue;
+
+ count = of_count_phandle_with_args(parts, CONCAT_PROP, NULL);
+ if (count < MIN_DEV_PER_CONCAT)
+ continue;
+
+ /*
+ * The partition in which "part-concat-next" property is defined
+ * is also part of the concat device, so increament count by 1.
+ */
+ count++;
+
+ ret = mtd_virt_concat_create_item(parts, count);
+ if (ret) {
+ of_node_put(parts);
+ goto destroy_items;
+ }
+ } while (parts);
+
+ return ret;
+
+destroy_items:
+ mtd_virt_concat_destroy_items();
+
+ return ret;
+}
+
+/**
+ * mtd_virt_concat_create_join - Create and register the concatenated
+ * MTD device.
+ *
+ * Return: 0 on success, -error otherwise.
+ */
+int mtd_virt_concat_create_join(void)
+{
+ struct mtd_virt_concat_node *item;
+ struct mtd_concat *concat;
+ struct mtd_info *mtd;
+ ssize_t name_sz;
+ int ret, idx;
+ char *name;
+
+ list_for_each_entry(item, &concat_node_list, head) {
+ concat = item->concat;
+ /*
+ * Check if item->count != concat->num_subdev, it indicates
+ * that the MTD information for all devices included in the
+ * concatenation are not handy, concat MTD device can't be
+ * created hence switch to next concat device.
+ */
+ if (item->count != concat->num_subdev) {
+ continue;
+ } else {
+ /* Calculate the legth of the name of the virtual device */
+ for (idx = 0, name_sz = 0; idx < concat->num_subdev; idx++)
+ name_sz += (strlen(concat->subdev[idx]->name) + 1);
+ name_sz += strlen(CONCAT_POSTFIX);
+ name = kmalloc(name_sz + 1, GFP_KERNEL);
+ if (!name) {
+ mtd_virt_concat_put_mtd_devices(concat);
+ return -ENOMEM;
+ }
+
+ ret = 0;
+ for (idx = 0; idx < concat->num_subdev; idx++) {
+ ret += sprintf((name + ret), "%s-",
+ concat->subdev[idx]->name);
+ }
+ sprintf((name + ret), CONCAT_POSTFIX);
+
+ if (concat->mtd.name) {
+ ret = memcmp(concat->mtd.name, name, name_sz);
+ if (ret == 0) {
+ kfree(name);
+ continue;
+ }
+ }
+ mtd = mtd_concat_create(concat->subdev, concat->num_subdev, name);
+ if (!mtd) {
+ kfree(name);
+ return -ENXIO;
+ }
+ concat->mtd = *mtd;
+ /* Arbitrary set the first device as parent */
+ concat->mtd.dev.parent = concat->subdev[0]->dev.parent;
+ concat->mtd.dev = concat->subdev[0]->dev;
+
+ /* Add the mtd device */
+ ret = add_mtd_device(&concat->mtd);
+ if (ret)
+ goto destroy_concat;
+ }
+ }
+
+ return 0;
+
+destroy_concat:
+ mtd_concat_destroy(mtd);
+
+ return ret;
+}
diff --git a/drivers/mtd/mtdblock.c b/drivers/mtd/mtdblock.c
index 9751416c2a91..c9143711fa39 100644
--- a/drivers/mtd/mtdblock.c
+++ b/drivers/mtd/mtdblock.c
@@ -316,7 +316,7 @@ static int mtdblock_flush(struct mtd_blktrans_dev *dev)
static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
{
- struct mtdblk_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ struct mtdblk_dev *dev = kzalloc_obj(*dev);
if (!dev)
return;
diff --git a/drivers/mtd/mtdblock_ro.c b/drivers/mtd/mtdblock_ro.c
index ef6299af60e4..e564521d6911 100644
--- a/drivers/mtd/mtdblock_ro.c
+++ b/drivers/mtd/mtdblock_ro.c
@@ -36,7 +36,7 @@ static int mtdblock_writesect(struct mtd_blktrans_dev *dev,
static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
{
- struct mtd_blktrans_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ struct mtd_blktrans_dev *dev = kzalloc_obj(*dev);
if (!dev)
return;
diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c
index 8dc4f5c493fc..bf01e6ac7293 100644
--- a/drivers/mtd/mtdchar.c
+++ b/drivers/mtd/mtdchar.c
@@ -72,7 +72,7 @@ static int mtdchar_open(struct inode *inode, struct file *file)
goto out1;
}
- mfi = kzalloc(sizeof(*mfi), GFP_KERNEL);
+ mfi = kzalloc_obj(*mfi);
if (!mfi) {
ret = -ENOMEM;
goto out1;
@@ -599,6 +599,7 @@ mtdchar_write_ioctl(struct mtd_info *mtd, struct mtd_write_req __user *argp)
uint8_t *datbuf = NULL, *oobbuf = NULL;
size_t datbuf_len, oobbuf_len;
int ret = 0;
+ u64 end;
if (copy_from_user(&req, argp, sizeof(req)))
return -EFAULT;
@@ -618,7 +619,7 @@ mtdchar_write_ioctl(struct mtd_info *mtd, struct mtd_write_req __user *argp)
req.len &= 0xffffffff;
req.ooblen &= 0xffffffff;
- if (req.start + req.len > mtd->size)
+ if (check_add_overflow(req.start, req.len, &end) || end > mtd->size)
return -EINVAL;
datbuf_len = min_t(size_t, req.len, mtd->erasesize);
@@ -698,6 +699,7 @@ mtdchar_read_ioctl(struct mtd_info *mtd, struct mtd_read_req __user *argp)
size_t datbuf_len, oobbuf_len;
size_t orig_len, orig_ooblen;
int ret = 0;
+ u64 end;
if (copy_from_user(&req, argp, sizeof(req)))
return -EFAULT;
@@ -724,7 +726,7 @@ mtdchar_read_ioctl(struct mtd_info *mtd, struct mtd_read_req __user *argp)
req.len &= 0xffffffff;
req.ooblen &= 0xffffffff;
- if (req.start + req.len > mtd->size) {
+ if (check_add_overflow(req.start, req.len, &end) || end > mtd->size) {
ret = -EINVAL;
goto out;
}
@@ -921,7 +923,7 @@ static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg)
{
struct erase_info *erase;
- erase=kzalloc(sizeof(struct erase_info),GFP_KERNEL);
+ erase=kzalloc_obj(struct erase_info);
if (!erase)
ret = -ENOMEM;
else {
@@ -1160,7 +1162,7 @@ static int mtdchar_ioctl(struct file *file, u_int cmd, u_long arg)
if (!master->ooblayout)
return -EOPNOTSUPP;
- usrlay = kmalloc(sizeof(*usrlay), GFP_KERNEL);
+ usrlay = kmalloc_obj(*usrlay);
if (!usrlay)
return -ENOMEM;
@@ -1374,27 +1376,12 @@ static unsigned mtdchar_mmap_capabilities(struct file *file)
/*
* set up a mapping for shared memory segments
*/
-static int mtdchar_mmap(struct file *file, struct vm_area_struct *vma)
+static int mtdchar_mmap_prepare(struct vm_area_desc *desc)
{
#ifdef CONFIG_MMU
- struct mtd_file_info *mfi = file->private_data;
- struct mtd_info *mtd = mfi->mtd;
- struct map_info *map = mtd->priv;
-
- /* This is broken because it assumes the MTD device is map-based
- and that mtd->priv is a valid struct map_info. It should be
- replaced with something that uses the mtd_get_unmapped_area()
- operation properly. */
- if (0 /*mtd->type == MTD_RAM || mtd->type == MTD_ROM*/) {
-#ifdef pgprot_noncached
- if (file->f_flags & O_DSYNC || map->phys >= __pa(high_memory))
- vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
-#endif
- return vm_iomap_memory(vma, map->phys, map->size);
- }
return -ENODEV;
#else
- return vma->vm_flags & VM_SHARED ? 0 : -EACCES;
+ return vma_desc_test(desc, VMA_SHARED_BIT) ? 0 : -EACCES;
#endif
}
@@ -1409,7 +1396,7 @@ static const struct file_operations mtd_fops = {
#endif
.open = mtdchar_open,
.release = mtdchar_close,
- .mmap = mtdchar_mmap,
+ .mmap_prepare = mtdchar_mmap_prepare,
#ifndef CONFIG_MMU
.get_unmapped_area = mtdchar_get_unmapped_area,
.mmap_capabilities = mtdchar_mmap_capabilities,
diff --git a/drivers/mtd/mtdconcat.c b/drivers/mtd/mtdconcat.c
index f56f44aa8625..c97167d51fe2 100644
--- a/drivers/mtd/mtdconcat.c
+++ b/drivers/mtd/mtdconcat.c
@@ -21,18 +21,6 @@
#include <asm/div64.h>
/*
- * Our storage structure:
- * Subdev points to an array of pointers to struct mtd_info objects
- * which is allocated along with this structure
- *
- */
-struct mtd_concat {
- struct mtd_info mtd;
- int num_subdev;
- struct mtd_info **subdev;
-};
-
-/*
* how to calculate the size required for the above structure,
* including the pointer array subdev points to:
*/
@@ -416,7 +404,7 @@ static int concat_erase(struct mtd_info *mtd, struct erase_info *instr)
}
/* make a local copy of instr to avoid modifying the caller's struct */
- erase = kmalloc(sizeof (struct erase_info), GFP_KERNEL);
+ erase = kmalloc_obj(struct erase_info);
if (!erase)
return -ENOMEM;
@@ -639,7 +627,6 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
const char *name)
{ /* name for the new device */
int i;
- size_t size;
struct mtd_concat *concat;
struct mtd_info *subdev_master = NULL;
uint32_t max_erasesize, curr_erasesize;
@@ -652,15 +639,13 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
printk(KERN_NOTICE "into device \"%s\"\n", name);
/* allocate the device structure */
- size = SIZEOF_STRUCT_MTD_CONCAT(num_devs);
- concat = kzalloc(size, GFP_KERNEL);
+ concat = kzalloc_flex(*concat, subdev, num_devs, GFP_KERNEL);
if (!concat) {
printk
("memory allocation error while creating concatenated device \"%s\"\n",
name);
return NULL;
}
- concat->subdev = (struct mtd_info **) (concat + 1);
/*
* Set up the new "super" device's MTD object structure, check for
@@ -823,9 +808,7 @@ struct mtd_info *mtd_concat_create(struct mtd_info *subdev[], /* subdevices to c
concat->mtd.erasesize = max_erasesize;
concat->mtd.numeraseregions = num_erase_region;
concat->mtd.eraseregions = erase_region_p =
- kmalloc_array(num_erase_region,
- sizeof(struct mtd_erase_region_info),
- GFP_KERNEL);
+ kmalloc_objs(struct mtd_erase_region_info, num_erase_region);
if (!erase_region_p) {
kfree(concat);
printk
diff --git a/drivers/mtd/mtdcore.c b/drivers/mtd/mtdcore.c
index 724f917f91ba..16629382a787 100644
--- a/drivers/mtd/mtdcore.c
+++ b/drivers/mtd/mtdcore.c
@@ -34,6 +34,7 @@
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
+#include <linux/mtd/concat.h>
#include "mtdcore.h"
@@ -104,6 +105,15 @@ static void mtd_release(struct device *dev)
device_destroy(&mtd_class, index + 1);
}
+/*
+ * No-op device release used in add_mtd_device() error paths.
+ * Prevents mtd_release() from being called via device_release(),
+ * which would free the mtd_info that the caller still manages.
+ */
+static void mtd_dev_release_nop(struct device *dev)
+{
+}
+
static void mtd_device_release(struct kref *kref)
{
struct mtd_info *mtd = container_of(kref, struct mtd_info, refcnt);
@@ -384,14 +394,64 @@ EXPORT_SYMBOL_GPL(mtd_check_expert_analysis_mode);
static struct dentry *dfs_dir_mtd;
+static int mtd_ooblayout_show(struct seq_file *s, void *p,
+ int (*iter)(struct mtd_info *, int section,
+ struct mtd_oob_region *region))
+{
+ struct mtd_info *mtd = s->private;
+ int section;
+
+ for (section = 0;; section++) {
+ struct mtd_oob_region region;
+ int err;
+
+ err = iter(mtd, section, &region);
+ if (err) {
+ if (err == -ERANGE)
+ break;
+
+ return err;
+ }
+
+ seq_printf(s, "%-3d %4u %4u\n", section, region.offset,
+ region.length);
+ }
+
+ return 0;
+}
+
+static int mtd_ooblayout_ecc_show(struct seq_file *s, void *p)
+{
+ return mtd_ooblayout_show(s, p, mtd_ooblayout_ecc);
+}
+DEFINE_SHOW_ATTRIBUTE(mtd_ooblayout_ecc);
+
+static int mtd_ooblayout_free_show(struct seq_file *s, void *p)
+{
+ return mtd_ooblayout_show(s, p, mtd_ooblayout_free);
+}
+DEFINE_SHOW_ATTRIBUTE(mtd_ooblayout_free);
+
static void mtd_debugfs_populate(struct mtd_info *mtd)
{
struct device *dev = &mtd->dev;
+ struct mtd_oob_region region;
if (IS_ERR_OR_NULL(dfs_dir_mtd))
return;
mtd->dbg.dfs_dir = debugfs_create_dir(dev_name(dev), dfs_dir_mtd);
+ if (IS_ERR_OR_NULL(mtd->dbg.dfs_dir))
+ return;
+
+ /* Create ooblayout files only if at least one region is present. */
+ if (mtd_ooblayout_ecc(mtd, 0, &region) == 0)
+ debugfs_create_file("ooblayout_ecc", 0444, mtd->dbg.dfs_dir,
+ mtd, &mtd_ooblayout_ecc_fops);
+
+ if (mtd_ooblayout_free(mtd, 0, &region) == 0)
+ debugfs_create_file("ooblayout_free", 0444, mtd->dbg.dfs_dir,
+ mtd, &mtd_ooblayout_free_fops);
}
#ifndef CONFIG_MMU
@@ -741,15 +801,15 @@ int add_mtd_device(struct mtd_info *mtd)
mtd->dev.type = &mtd_devtype;
mtd->dev.class = &mtd_class;
mtd->dev.devt = MTD_DEVT(i);
- dev_set_name(&mtd->dev, "mtd%d", i);
+ error = dev_set_name(&mtd->dev, "mtd%d", i);
+ if (error)
+ goto fail_devname;
dev_set_drvdata(&mtd->dev, mtd);
mtd_check_of_node(mtd);
of_node_get(mtd_get_of_node(mtd));
error = device_register(&mtd->dev);
- if (error) {
- put_device(&mtd->dev);
+ if (error)
goto fail_added;
- }
/* Add the nvmem provider */
error = mtd_nvmem_add(mtd);
@@ -787,9 +847,18 @@ int add_mtd_device(struct mtd_info *mtd)
return 0;
fail_nvmem_add:
- device_unregister(&mtd->dev);
+ device_del(&mtd->dev);
fail_added:
+ /*
+ * Clear type and set nop release to prevent mtd_release() ->
+ * release_mtd_partition() -> free_partition() from freeing mtd.
+ * The caller handles cleanup on failure.
+ */
+ mtd->dev.type = NULL;
+ mtd->dev.release = mtd_dev_release_nop;
+ put_device(&mtd->dev);
of_node_put(mtd_get_of_node(mtd));
+fail_devname:
idr_remove(&mtd_idr, i);
fail_locked:
mutex_unlock(&mtd_table_mutex);
@@ -1053,7 +1122,7 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
const struct mtd_partition *parts,
int nr_parts)
{
- int ret;
+ int ret, err;
mtd_set_dev_defaults(mtd);
@@ -1067,6 +1136,12 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
goto out;
}
+ if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
+ ret = mtd_virt_concat_node_create();
+ if (ret < 0)
+ goto out;
+ }
+
/* Prefer parsed partitions over driver-provided fallback */
ret = parse_mtd_partitions(mtd, types, parser_data);
if (ret == -EPROBE_DEFER)
@@ -1084,6 +1159,11 @@ int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
if (ret)
goto out;
+ if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
+ ret = mtd_virt_concat_create_join();
+ if (ret < 0)
+ goto out;
+ }
/*
* FIXME: some drivers unfortunately call this function more than once.
* So we have to check if we've already assigned the reboot notifier.
@@ -1105,8 +1185,11 @@ out:
nvmem_unregister(mtd->otp_factory_nvmem);
}
- if (ret && device_is_registered(&mtd->dev))
- del_mtd_device(mtd);
+ if (ret && device_is_registered(&mtd->dev)) {
+ err = del_mtd_device(mtd);
+ if (err)
+ pr_err("Error when deleting MTD device (%d)\n", err);
+ }
return ret;
}
@@ -1130,6 +1213,11 @@ int mtd_device_unregister(struct mtd_info *master)
nvmem_unregister(master->otp_user_nvmem);
nvmem_unregister(master->otp_factory_nvmem);
+ if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
+ err = mtd_virt_concat_destroy(master);
+ if (err)
+ return err;
+ }
err = del_mtd_partitions(master);
if (err)
return err;
@@ -2333,6 +2421,7 @@ EXPORT_SYMBOL_GPL(mtd_block_isbad);
int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs)
{
struct mtd_info *master = mtd_get_master(mtd);
+ loff_t moffs;
int ret;
if (!master->_block_markbad)
@@ -2345,7 +2434,15 @@ int mtd_block_markbad(struct mtd_info *mtd, loff_t ofs)
if (mtd->flags & MTD_SLC_ON_MLC_EMULATION)
ofs = (loff_t)mtd_div_by_eb(ofs, mtd) * master->erasesize;
- ret = master->_block_markbad(master, mtd_get_master_ofs(mtd, ofs));
+ moffs = mtd_get_master_ofs(mtd, ofs);
+
+ if (master->_block_isbad) {
+ ret = master->_block_isbad(master, moffs);
+ if (ret > 0)
+ return 0;
+ }
+
+ ret = master->_block_markbad(master, moffs);
if (ret)
return ret;
@@ -2556,6 +2653,10 @@ err_reg:
static void __exit cleanup_mtd(void)
{
+ if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
+ mtd_virt_concat_destroy_joins();
+ mtd_virt_concat_destroy_items();
+ }
debugfs_remove_recursive(dfs_dir_mtd);
cleanup_mtdchar();
if (proc_mtd)
diff --git a/drivers/mtd/mtdoops.c b/drivers/mtd/mtdoops.c
index 7bf3777e1f13..39df7ce8f55f 100644
--- a/drivers/mtd/mtdoops.c
+++ b/drivers/mtd/mtdoops.c
@@ -356,9 +356,8 @@ static void mtdoops_notify_add(struct mtd_info *mtd)
/* oops_page_used is a bit field */
cxt->oops_page_used =
- vmalloc(array_size(sizeof(unsigned long),
- DIV_ROUND_UP(mtdoops_pages,
- BITS_PER_LONG)));
+ vmalloc_array(DIV_ROUND_UP(mtdoops_pages, BITS_PER_LONG),
+ sizeof(unsigned long));
if (!cxt->oops_page_used) {
pr_err("could not allocate page array\n");
return;
@@ -404,8 +403,7 @@ static struct mtd_notifier mtdoops_notifier = {
static int __init mtdoops_init(void)
{
struct mtdoops_context *cxt = &oops_cxt;
- int mtd_index;
- char *endp;
+ unsigned int mtd_index;
if (strlen(mtddev) == 0) {
pr_err("mtd device (mtddev=name/number) must be supplied\n");
@@ -422,9 +420,9 @@ static int __init mtdoops_init(void)
/* Setup the MTD device to use */
cxt->mtd_index = -1;
- mtd_index = simple_strtoul(mtddev, &endp, 0);
- if (*endp == '\0')
+ if (kstrtouint(mtddev, 0, &mtd_index) == 0) {
cxt->mtd_index = mtd_index;
+ }
cxt->oops_buf = vmalloc(record_size);
if (!cxt->oops_buf)
diff --git a/drivers/mtd/mtdpart.c b/drivers/mtd/mtdpart.c
index 6811a714349d..4b41550fd374 100644
--- a/drivers/mtd/mtdpart.c
+++ b/drivers/mtd/mtdpart.c
@@ -18,6 +18,7 @@
#include <linux/err.h>
#include <linux/of.h>
#include <linux/of_platform.h>
+#include <linux/mtd/concat.h>
#include "mtdcore.h"
@@ -53,7 +54,7 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
u64 tmp;
/* allocate the partition structure */
- child = kzalloc(sizeof(*child), GFP_KERNEL);
+ child = kzalloc_obj(*child);
name = kstrdup(part->name, GFP_KERNEL);
if (!name || !child) {
printk(KERN_ERR"memory allocation error while creating partitions for \"%s\"\n",
@@ -117,6 +118,9 @@ static struct mtd_info *allocate_partition(struct mtd_info *parent,
part->name, parent_size - child->part.offset,
child->part.size);
/* register to preserve ordering */
+ child->part.offset = 0;
+ child->part.size = 0;
+ child->erasesize = parent->erasesize;
goto out_register;
}
}
@@ -263,6 +267,11 @@ int mtd_add_partition(struct mtd_info *parent, const char *name,
if (length <= 0)
return -EINVAL;
+ if (offset < 0 || offset >= (long long)parent_size)
+ return -EINVAL;
+
+ if ((u64)offset + (u64)length > parent_size)
+ return -EINVAL;
memset(&part, 0, sizeof(part));
part.name = name;
part.size = length;
@@ -409,6 +418,11 @@ int add_mtd_partitions(struct mtd_info *parent,
goto err_del_partitions;
}
+ if (IS_REACHABLE(CONFIG_MTD_VIRT_CONCAT)) {
+ if (mtd_virt_concat_add(child))
+ continue;
+ }
+
mutex_lock(&master->master.partitions_lock);
list_add_tail(&child->part.node, &parent->partitions);
mutex_unlock(&master->master.partitions_lock);
@@ -425,9 +439,12 @@ int add_mtd_partitions(struct mtd_info *parent,
mtd_add_partition_attrs(child);
- /* Look for subpartitions */
+ /* Look for subpartitions (skip if no maching parser found) */
ret = parse_mtd_partitions(child, parts[i].types, NULL);
- if (ret < 0) {
+ if (ret < 0 && ret == -ENOENT) {
+ pr_debug("Skip parsing subpartitions: %d\n", ret);
+ continue;
+ } else if (ret < 0) {
pr_err("Failed to parse subpartitions: %d\n", ret);
goto err_del_partitions;
}
@@ -690,10 +707,9 @@ int parse_mtd_partitions(struct mtd_info *master, const char *const *types,
parser = mtd_part_parser_get(*types);
if (!parser && !request_module("%s", *types))
parser = mtd_part_parser_get(*types);
- pr_debug("%s: got parser %s\n", master->name,
- parser ? parser->name : NULL);
if (!parser)
continue;
+ pr_debug("%s: got parser %s\n", master->name, parser->name);
ret = mtd_part_do_parse(parser, master, &pparts, data);
if (ret <= 0)
mtd_part_parser_put(parser);
diff --git a/drivers/mtd/mtdpstore.c b/drivers/mtd/mtdpstore.c
index 7ac8ac901306..9cf3872e37ae 100644
--- a/drivers/mtd/mtdpstore.c
+++ b/drivers/mtd/mtdpstore.c
@@ -417,11 +417,14 @@ static void mtdpstore_notify_add(struct mtd_info *mtd)
}
longcnt = BITS_TO_LONGS(div_u64(mtd->size, info->kmsg_size));
- cxt->rmmap = kcalloc(longcnt, sizeof(long), GFP_KERNEL);
- cxt->usedmap = kcalloc(longcnt, sizeof(long), GFP_KERNEL);
+ cxt->rmmap = devm_kcalloc(&mtd->dev, longcnt, sizeof(long), GFP_KERNEL);
+ cxt->usedmap = devm_kcalloc(&mtd->dev, longcnt, sizeof(long), GFP_KERNEL);
longcnt = BITS_TO_LONGS(div_u64(mtd->size, mtd->erasesize));
- cxt->badmap = kcalloc(longcnt, sizeof(long), GFP_KERNEL);
+ cxt->badmap = devm_kcalloc(&mtd->dev, longcnt, sizeof(long), GFP_KERNEL);
+
+ if (!cxt->rmmap || !cxt->usedmap || !cxt->badmap)
+ return;
/* just support dmesg right now */
cxt->dev.flags = PSTORE_FLAGS_DMESG;
@@ -527,9 +530,6 @@ static void mtdpstore_notify_remove(struct mtd_info *mtd)
mtdpstore_flush_removed(cxt);
unregister_pstore_device(&cxt->dev);
- kfree(cxt->badmap);
- kfree(cxt->usedmap);
- kfree(cxt->rmmap);
cxt->mtd = NULL;
cxt->index = -1;
}
diff --git a/drivers/mtd/mtdsuper.c b/drivers/mtd/mtdsuper.c
index b7e3763c47f0..c709ff7aa6b5 100644
--- a/drivers/mtd/mtdsuper.c
+++ b/drivers/mtd/mtdsuper.c
@@ -132,12 +132,12 @@ int get_tree_mtd(struct fs_context *fc,
} else if (isdigit(fc->source[3])) {
/* mount by MTD device number name */
- char *endptr;
+ unsigned int mtdnr_val;
- mtdnr = simple_strtoul(fc->source + 3, &endptr, 0);
- if (!*endptr) {
+ if (kstrtouint(fc->source + 3, 0, &mtdnr_val) == 0) {
+ mtdnr = mtdnr_val;
/* It was a valid number */
- pr_debug("MTDSB: mtd%%d, mtdnr %d\n", mtdnr);
+ pr_debug("MTDSB: mtd%%d, mtdnr %u\n", mtdnr_val);
return mtd_get_sb_by_nr(fc, mtdnr, fill_super);
}
}
diff --git a/drivers/mtd/mtdswap.c b/drivers/mtd/mtdswap.c
index 680366616da2..f33f753f0a9f 100644
--- a/drivers/mtd/mtdswap.c
+++ b/drivers/mtd/mtdswap.c
@@ -125,6 +125,7 @@ struct mtdswap_dev {
char *page_buf;
char *oob_buf;
+ struct dentry *debugfs_stats;
};
struct mtdswap_oobdata {
@@ -1262,7 +1263,8 @@ static int mtdswap_add_debugfs(struct mtdswap_dev *d)
if (IS_ERR_OR_NULL(root))
return -1;
- debugfs_create_file("mtdswap_stats", S_IRUSR, root, d, &mtdswap_fops);
+ d->debugfs_stats = debugfs_create_file("mtdswap_stats", 0400, root,
+ d, &mtdswap_fops);
return 0;
}
@@ -1285,11 +1287,11 @@ static int mtdswap_init(struct mtdswap_dev *d, unsigned int eblocks,
for (i = 0; i < MTDSWAP_TREE_CNT; i++)
d->trees[i].root = RB_ROOT;
- d->page_data = vmalloc(array_size(pages, sizeof(int)));
+ d->page_data = vmalloc_array(pages, sizeof(int));
if (!d->page_data)
goto page_data_fail;
- d->revmap = vmalloc(array_size(blocks, sizeof(int)));
+ d->revmap = vmalloc_array(blocks, sizeof(int));
if (!d->revmap)
goto revmap_fail;
@@ -1413,11 +1415,11 @@ static void mtdswap_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
"%u spare, %u bad blocks\n",
MTDSWAP_PREFIX, part, swap_size / 1024, spare_cnt, bad_blocks);
- d = kzalloc(sizeof(struct mtdswap_dev), GFP_KERNEL);
+ d = kzalloc_obj(struct mtdswap_dev);
if (!d)
return;
- mbd_dev = kzalloc(sizeof(struct mtd_blktrans_dev), GFP_KERNEL);
+ mbd_dev = kzalloc_obj(struct mtd_blktrans_dev);
if (!mbd_dev) {
kfree(d);
return;
@@ -1463,6 +1465,7 @@ static void mtdswap_remove_dev(struct mtd_blktrans_dev *dev)
{
struct mtdswap_dev *d = MTDSWAP_MBD_TO_MTDSWAP(dev);
+ debugfs_remove(d->debugfs_stats);
del_mtd_blktrans_dev(dev);
mtdswap_cleanup(d);
kfree(d);
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 5b0c2c95f10c..1e57c8de8578 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -61,6 +61,14 @@ config MTD_NAND_ECC_MEDIATEK
help
This enables support for the hardware ECC engine from Mediatek.
+config MTD_NAND_ECC_REALTEK
+ tristate "Realtek RTL93xx hardware ECC engine"
+ depends on HAS_IOMEM && HAS_DMA
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
+ select MTD_NAND_ECC
+ help
+ This enables support for the hardware ECC engine from Realtek.
+
endmenu
endmenu
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index da1586a36574..2e0e56267718 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -3,6 +3,8 @@
nandcore-objs := core.o bbt.o
obj-$(CONFIG_MTD_NAND_CORE) += nandcore.o
obj-$(CONFIG_MTD_NAND_ECC_MEDIATEK) += ecc-mtk.o
+obj-$(CONFIG_MTD_NAND_ECC_REALTEK) += ecc-realtek.o
+obj-$(CONFIG_SPI_QPIC_SNAND) += qpic_common.o
obj-$(CONFIG_MTD_NAND_QCOM) += qpic_common.o
obj-y += onenand/
obj-y += raw/
diff --git a/drivers/mtd/nand/core.c b/drivers/mtd/nand/core.c
index 7737b1a4a177..3e76d127715f 100644
--- a/drivers/mtd/nand/core.c
+++ b/drivers/mtd/nand/core.c
@@ -13,6 +13,137 @@
#include <linux/mtd/nand.h>
/**
+ * nand_check_erased_buf - check if a buffer contains (almost) only 0xff data
+ * @buf: buffer to test
+ * @len: buffer length
+ * @bitflips_threshold: maximum number of bitflips
+ *
+ * Check if a buffer contains only 0xff, which means the underlying region
+ * has been erased and is ready to be programmed.
+ * The bitflips_threshold specify the maximum number of bitflips before
+ * considering the region is not erased.
+ * Note: The logic of this function has been extracted from the memweight
+ * implementation, except that nand_check_erased_buf function exit before
+ * testing the whole buffer if the number of bitflips exceed the
+ * bitflips_threshold value.
+ *
+ * Returns a positive number of bitflips less than or equal to
+ * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
+ * threshold.
+ */
+static int nand_check_erased_buf(void *buf, int len, int bitflips_threshold)
+{
+ const unsigned char *bitmap = buf;
+ int bitflips = 0;
+ int weight;
+
+ for (; len && ((uintptr_t)bitmap) % sizeof(long);
+ len--, bitmap++) {
+ weight = hweight8(*bitmap);
+ bitflips += BITS_PER_BYTE - weight;
+ if (unlikely(bitflips > bitflips_threshold))
+ return -EBADMSG;
+ }
+
+ for (; len >= sizeof(long);
+ len -= sizeof(long), bitmap += sizeof(long)) {
+ unsigned long d = *((unsigned long *)bitmap);
+ if (d == ~0UL)
+ continue;
+ weight = hweight_long(d);
+ bitflips += BITS_PER_LONG - weight;
+ if (unlikely(bitflips > bitflips_threshold))
+ return -EBADMSG;
+ }
+
+ for (; len > 0; len--, bitmap++) {
+ weight = hweight8(*bitmap);
+ bitflips += BITS_PER_BYTE - weight;
+ if (unlikely(bitflips > bitflips_threshold))
+ return -EBADMSG;
+ }
+
+ return bitflips;
+}
+
+/**
+ * nand_check_erased_ecc_chunk - check if an ECC chunk contains (almost) only
+ * 0xff data
+ * @data: data buffer to test
+ * @datalen: data length
+ * @ecc: ECC buffer
+ * @ecclen: ECC length
+ * @extraoob: extra OOB buffer
+ * @extraooblen: extra OOB length
+ * @bitflips_threshold: maximum number of bitflips
+ *
+ * Check if a data buffer and its associated ECC and OOB data contains only
+ * 0xff pattern, which means the underlying region has been erased and is
+ * ready to be programmed.
+ * The bitflips_threshold specify the maximum number of bitflips before
+ * considering the region as not erased.
+ *
+ * Note:
+ * 1/ ECC algorithms are working on pre-defined block sizes which are usually
+ * different from the NAND page size. When fixing bitflips, ECC engines will
+ * report the number of errors per chunk, and the NAND core infrastructure
+ * expect you to return the maximum number of bitflips for the whole page.
+ * This is why you should always use this function on a single chunk and
+ * not on the whole page. After checking each chunk you should update your
+ * max_bitflips value accordingly.
+ * 2/ When checking for bitflips in erased pages you should not only check
+ * the payload data but also their associated ECC data, because a user might
+ * have programmed almost all bits to 1 but a few. In this case, we
+ * shouldn't consider the chunk as erased, and checking ECC bytes prevent
+ * this case.
+ * 3/ The extraoob argument is optional, and should be used if some of your OOB
+ * data are protected by the ECC engine.
+ * It could also be used if you support subpages and want to attach some
+ * extra OOB data to an ECC chunk.
+ *
+ * Returns a positive number of bitflips less than or equal to
+ * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
+ * threshold. In case of success, the passed buffers are filled with 0xff.
+ */
+int nand_check_erased_ecc_chunk(void *data, int datalen,
+ void *ecc, int ecclen,
+ void *extraoob, int extraooblen,
+ int bitflips_threshold)
+{
+ int data_bitflips = 0, ecc_bitflips = 0, extraoob_bitflips = 0;
+
+ data_bitflips = nand_check_erased_buf(data, datalen,
+ bitflips_threshold);
+ if (data_bitflips < 0)
+ return data_bitflips;
+
+ bitflips_threshold -= data_bitflips;
+
+ ecc_bitflips = nand_check_erased_buf(ecc, ecclen, bitflips_threshold);
+ if (ecc_bitflips < 0)
+ return ecc_bitflips;
+
+ bitflips_threshold -= ecc_bitflips;
+
+ extraoob_bitflips = nand_check_erased_buf(extraoob, extraooblen,
+ bitflips_threshold);
+ if (extraoob_bitflips < 0)
+ return extraoob_bitflips;
+
+ if (data_bitflips)
+ memset(data, 0xff, datalen);
+
+ if (ecc_bitflips)
+ memset(ecc, 0xff, ecclen);
+
+ if (extraoob_bitflips)
+ memset(extraoob, 0xff, extraooblen);
+
+ return data_bitflips + ecc_bitflips + extraoob_bitflips;
+}
+EXPORT_SYMBOL(nand_check_erased_ecc_chunk);
+
+/**
* nanddev_isbad() - Check if a block is bad
* @nand: NAND device
* @pos: position pointing to the block we want to check
diff --git a/drivers/mtd/nand/ecc-mtk.c b/drivers/mtd/nand/ecc-mtk.c
index c75bb8b80cc1..66f0985ef7cd 100644
--- a/drivers/mtd/nand/ecc-mtk.c
+++ b/drivers/mtd/nand/ecc-mtk.c
@@ -123,8 +123,8 @@ static int mt7622_ecc_regs[] = {
[ECC_DECIRQ_STA] = 0x144,
};
-static inline void mtk_ecc_wait_idle(struct mtk_ecc *ecc,
- enum mtk_ecc_operation op)
+static inline int mtk_ecc_wait_idle(struct mtk_ecc *ecc,
+ enum mtk_ecc_operation op)
{
struct device *dev = ecc->dev;
u32 val;
@@ -136,6 +136,8 @@ static inline void mtk_ecc_wait_idle(struct mtk_ecc *ecc,
if (ret)
dev_warn(dev, "%s NOT idle\n",
op == ECC_ENCODE ? "encoder" : "decoder");
+
+ return ret;
}
static irqreturn_t mtk_ecc_irq(int irq, void *id)
@@ -265,6 +267,7 @@ static struct mtk_ecc *mtk_ecc_get(struct device_node *np)
{
struct platform_device *pdev;
struct mtk_ecc *ecc;
+ int ret;
pdev = of_find_device_by_node(np);
if (!pdev)
@@ -276,7 +279,12 @@ static struct mtk_ecc *mtk_ecc_get(struct device_node *np)
return ERR_PTR(-EPROBE_DEFER);
}
- clk_prepare_enable(ecc->clk);
+ ret = clk_prepare_enable(ecc->clk);
+ if (ret) {
+ put_device(&pdev->dev);
+ return ERR_PTR(ret);
+ }
+
mtk_ecc_hw_init(ecc);
return ecc;
@@ -312,7 +320,11 @@ int mtk_ecc_enable(struct mtk_ecc *ecc, struct mtk_ecc_config *config)
return ret;
}
- mtk_ecc_wait_idle(ecc, op);
+ ret = mtk_ecc_wait_idle(ecc, op);
+ if (ret) {
+ mutex_unlock(&ecc->lock);
+ return ret;
+ }
ret = mtk_ecc_config(ecc, config);
if (ret) {
@@ -412,7 +424,9 @@ int mtk_ecc_encode(struct mtk_ecc *ecc, struct mtk_ecc_config *config,
if (ret)
goto timeout;
- mtk_ecc_wait_idle(ecc, ECC_ENCODE);
+ ret = mtk_ecc_wait_idle(ecc, ECC_ENCODE);
+ if (ret)
+ goto timeout;
/* Program ECC bytes to OOB: per sector oob = FDM + ECC + SPARE */
len = (config->strength * ecc->caps->parity_bits + 7) >> 3;
diff --git a/drivers/mtd/nand/ecc-mxic.c b/drivers/mtd/nand/ecc-mxic.c
index 56b56f726b99..60cdcb4175ef 100644
--- a/drivers/mtd/nand/ecc-mxic.c
+++ b/drivers/mtd/nand/ecc-mxic.c
@@ -322,14 +322,14 @@ static int mxic_ecc_init_ctx(struct nand_device *nand, struct device *dev)
sg_init_table(ctx->sg, 2);
/* Configuration dump and sanity checks */
- dev_err(dev, "DPE version number: %d\n",
+ dev_dbg(dev, "DPE version number: %d\n",
readl(mxic->regs + DP_VER) >> DP_VER_OFFSET);
- dev_err(dev, "Chunk size: %d\n", readl(mxic->regs + CHUNK_SIZE));
- dev_err(dev, "Main size: %d\n", readl(mxic->regs + MAIN_SIZE));
- dev_err(dev, "Spare size: %d\n", SPARE_SZ(spare_reg));
- dev_err(dev, "Rsv size: %ld\n", RSV_SZ(spare_reg));
- dev_err(dev, "Parity size: %d\n", ctx->parity_sz);
- dev_err(dev, "Meta size: %d\n", ctx->meta_sz);
+ dev_dbg(dev, "Chunk size: %d\n", readl(mxic->regs + CHUNK_SIZE));
+ dev_dbg(dev, "Main size: %d\n", readl(mxic->regs + MAIN_SIZE));
+ dev_dbg(dev, "Spare size: %d\n", SPARE_SZ(spare_reg));
+ dev_dbg(dev, "Rsv size: %ld\n", RSV_SZ(spare_reg));
+ dev_dbg(dev, "Parity size: %d\n", ctx->parity_sz);
+ dev_dbg(dev, "Meta size: %d\n", ctx->meta_sz);
if ((ctx->meta_sz + ctx->parity_sz + RSV_SZ(spare_reg)) !=
SPARE_SZ(spare_reg)) {
@@ -614,7 +614,7 @@ static int mxic_ecc_finish_io_req_external(struct nand_device *nand,
{
struct mxic_ecc_engine *mxic = nand_to_mxic(nand);
struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand);
- int nents, step, ret;
+ int nents, step, ret = 0;
if (req->mode == MTD_OPS_RAW)
return 0;
diff --git a/drivers/mtd/nand/ecc-realtek.c b/drivers/mtd/nand/ecc-realtek.c
new file mode 100644
index 000000000000..7d003fd72027
--- /dev/null
+++ b/drivers/mtd/nand/ecc-realtek.c
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for Realtek hardware ECC engine in RTL93xx SoCs
+ */
+
+#include <linux/bitfield.h>
+#include <linux/dma-mapping.h>
+#include <linux/mtd/nand.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/*
+ * The Realtek ECC engine has two operation modes.
+ *
+ * - BCH6 : Generate 10 ECC bytes from 512 data bytes plus 6 free bytes
+ * - BCH12 : Generate 20 ECC bytes from 512 data bytes plus 6 free bytes
+ *
+ * It can run for arbitrary NAND flash chips with different block and OOB sizes. Currently there
+ * are a few known devices in the wild that make use of this ECC engine
+ * (Linksys LGS328C, LGS352C & Netlink HG323DAC). To keep compatibility with vendor firmware,
+ * new modes can only be added when new data layouts have been analyzed. For now allow BCH6 on
+ * flash with 2048 byte blocks and at least 64 bytes oob. Some vendors make use of
+ * 128 bytes OOB NAND chips (e.g. Macronix MX35LF1G24AD) but only use BCH6 and thus the first
+ * 64 bytes of the OOB area. In this case the engine leaves any extra bytes unused.
+ *
+ * This driver aligns with kernel ECC naming conventions. Neverthless a short notice on the
+ * Realtek naming conventions for the different structures in the OOB area.
+ *
+ * - BBI : Bad block indicator. The first two bytes of OOB. Protected by ECC!
+ * - tag : 6 User/free bytes. First tag "contains" 2 bytes BBI. Protected by ECC!
+ * - syndrome : ECC/parity bytes
+ *
+ * Altogether this gives currently the following block layout.
+ *
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
+ * | 512 | 512 | 512 | 512 | 2 | 4 | 6 | 6 | 6 | 10 | 10 | 10 | 10 |
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
+ * | data | data | data | data | BBI | free | free | free | free | ECC | ECC | ECC | ECC |
+ * +------+------+------+------+-----+------+------+------+------+-----+-----+-----+-----+
+ */
+
+#define RTL_ECC_ALLOWED_PAGE_SIZE 2048
+#define RTL_ECC_ALLOWED_MIN_OOB_SIZE 64
+#define RTL_ECC_ALLOWED_STRENGTH 6
+
+#define RTL_ECC_BLOCK_SIZE 512
+#define RTL_ECC_FREE_SIZE 6
+#define RTL_ECC_PARITY_SIZE_BCH6 10
+#define RTL_ECC_PARITY_SIZE_BCH12 20
+
+/*
+ * The engine is fed with two DMA regions. One for data (always 512 bytes) and one for free bytes
+ * and parity (either 16 bytes for BCH6 or 26 bytes for BCH12). Start and length of each must be
+ * aligned to a multiple of 4.
+ */
+
+#define RTL_ECC_DMA_FREE_PARITY_SIZE ALIGN(RTL_ECC_FREE_SIZE + RTL_ECC_PARITY_SIZE_BCH12, 4)
+#define RTL_ECC_DMA_SIZE (RTL_ECC_BLOCK_SIZE + RTL_ECC_DMA_FREE_PARITY_SIZE)
+
+#define RTL_ECC_CFG 0x00
+#define RTL_ECC_BCH6 0
+#define RTL_ECC_BCH12 BIT(28)
+#define RTL_ECC_DMA_PRECISE BIT(12)
+#define RTL_ECC_BURST_128 GENMASK(1, 0)
+#define RTL_ECC_DMA_TRIGGER 0x08
+#define RTL_ECC_OP_DECODE 0
+#define RTL_ECC_OP_ENCODE BIT(0)
+#define RTL_ECC_DMA_START 0x0c
+#define RTL_ECC_DMA_TAG 0x10
+#define RTL_ECC_STATUS 0x14
+#define RTL_ECC_CORR_COUNT GENMASK(19, 12)
+#define RTL_ECC_RESULT BIT(8)
+#define RTL_ECC_ALL_ONE BIT(4)
+#define RTL_ECC_OP_STATUS BIT(0)
+
+struct rtl_ecc_engine {
+ struct device *dev;
+ struct nand_ecc_engine engine;
+ struct mutex lock;
+ char *buf;
+ dma_addr_t buf_dma;
+ struct regmap *regmap;
+};
+
+struct rtl_ecc_ctx {
+ struct rtl_ecc_engine * rtlc;
+ struct nand_ecc_req_tweak_ctx req_ctx;
+ int steps;
+ int bch_mode;
+ int strength;
+ int parity_size;
+};
+
+static const struct regmap_config rtl_ecc_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static inline void *nand_to_ctx(struct nand_device *nand)
+{
+ return nand->ecc.ctx.priv;
+}
+
+static inline struct rtl_ecc_engine *nand_to_rtlc(struct nand_device *nand)
+{
+ struct nand_ecc_engine *eng = nand->ecc.engine;
+
+ return container_of(eng, struct rtl_ecc_engine, engine);
+}
+
+static int rtl_ecc_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ struct nand_device *nand = mtd_to_nanddev(mtd);
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+
+ if (section < 0 || section >= ctx->steps)
+ return -ERANGE;
+
+ oobregion->offset = ctx->steps * RTL_ECC_FREE_SIZE + section * ctx->parity_size;
+ oobregion->length = ctx->parity_size;
+
+ return 0;
+}
+
+static int rtl_ecc_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ struct nand_device *nand = mtd_to_nanddev(mtd);
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+ int bbm;
+
+ if (section < 0 || section >= ctx->steps)
+ return -ERANGE;
+
+ /* reserve 2 BBM bytes in first block */
+ bbm = section ? 0 : 2;
+ oobregion->offset = section * RTL_ECC_FREE_SIZE + bbm;
+ oobregion->length = RTL_ECC_FREE_SIZE - bbm;
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops rtl_ecc_ooblayout_ops = {
+ .ecc = rtl_ecc_ooblayout_ecc,
+ .free = rtl_ecc_ooblayout_free,
+};
+
+static void rtl_ecc_kick_engine(struct rtl_ecc_ctx *ctx, int operation)
+{
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
+
+ regmap_write(rtlc->regmap, RTL_ECC_CFG,
+ ctx->bch_mode | RTL_ECC_BURST_128 | RTL_ECC_DMA_PRECISE);
+
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_START, rtlc->buf_dma);
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_TAG, rtlc->buf_dma + RTL_ECC_BLOCK_SIZE);
+ regmap_write(rtlc->regmap, RTL_ECC_DMA_TRIGGER, operation);
+}
+
+static int rtl_ecc_wait_for_engine(struct rtl_ecc_ctx *ctx)
+{
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
+ int ret, status, bitflips;
+ bool all_one;
+
+ /*
+ * The ECC engine needs 6-8 us to encode/decode a BCH6 syndrome for 512 bytes of data
+ * and 6 free bytes. In case the NAND area has been erased and all data and oob is
+ * set to 0xff, decoding takes 30us (reason unknown). Although the engine can trigger
+ * interrupts when finished, use active polling for now. 12 us maximum wait time has
+ * proven to be a good tradeoff between performance and overhead.
+ */
+
+ ret = regmap_read_poll_timeout(rtlc->regmap, RTL_ECC_STATUS, status,
+ !(status & RTL_ECC_OP_STATUS), 12, 1000000);
+ if (ret)
+ return ret;
+
+ ret = FIELD_GET(RTL_ECC_RESULT, status);
+ all_one = FIELD_GET(RTL_ECC_ALL_ONE, status);
+ bitflips = FIELD_GET(RTL_ECC_CORR_COUNT, status);
+
+ /* For erased blocks (all bits one) error status can be ignored */
+ if (all_one)
+ ret = 0;
+
+ return ret ? -EBADMSG : bitflips;
+}
+
+static int rtl_ecc_run_engine(struct rtl_ecc_ctx *ctx, char *data, char *free,
+ char *parity, int operation)
+{
+ struct rtl_ecc_engine *rtlc = ctx->rtlc;
+ char *buf_parity = rtlc->buf + RTL_ECC_BLOCK_SIZE + RTL_ECC_FREE_SIZE;
+ char *buf_free = rtlc->buf + RTL_ECC_BLOCK_SIZE;
+ char *buf_data = rtlc->buf;
+ int ret;
+
+ mutex_lock(&rtlc->lock);
+
+ memcpy(buf_data, data, RTL_ECC_BLOCK_SIZE);
+ memcpy(buf_free, free, RTL_ECC_FREE_SIZE);
+ memcpy(buf_parity, parity, ctx->parity_size);
+
+ dma_sync_single_for_device(rtlc->dev, rtlc->buf_dma, RTL_ECC_DMA_SIZE, DMA_TO_DEVICE);
+ rtl_ecc_kick_engine(ctx, operation);
+ ret = rtl_ecc_wait_for_engine(ctx);
+ dma_sync_single_for_cpu(rtlc->dev, rtlc->buf_dma, RTL_ECC_DMA_SIZE, DMA_FROM_DEVICE);
+
+ if (ret >= 0) {
+ memcpy(data, buf_data, RTL_ECC_BLOCK_SIZE);
+ memcpy(free, buf_free, RTL_ECC_FREE_SIZE);
+ memcpy(parity, buf_parity, ctx->parity_size);
+ }
+
+ mutex_unlock(&rtlc->lock);
+
+ return ret;
+}
+
+static int rtl_ecc_prepare_io_req(struct nand_device *nand, struct nand_page_io_req *req)
+{
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+ char *data, *free, *parity;
+ int ret = 0;
+
+ if (req->mode == MTD_OPS_RAW)
+ return 0;
+
+ nand_ecc_tweak_req(&ctx->req_ctx, req);
+
+ if (req->type == NAND_PAGE_READ)
+ return 0;
+
+ free = req->oobbuf.in;
+ data = req->databuf.in;
+ parity = req->oobbuf.in + ctx->steps * RTL_ECC_FREE_SIZE;
+
+ for (int i = 0; i < ctx->steps; i++) {
+ ret |= rtl_ecc_run_engine(ctx, data, free, parity, RTL_ECC_OP_ENCODE);
+
+ free += RTL_ECC_FREE_SIZE;
+ data += RTL_ECC_BLOCK_SIZE;
+ parity += ctx->parity_size;
+ }
+
+ if (unlikely(ret))
+ dev_dbg(rtlc->dev, "ECC calculation failed\n");
+
+ return ret ? -EBADMSG : 0;
+}
+
+static int rtl_ecc_finish_io_req(struct nand_device *nand, struct nand_page_io_req *req)
+{
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
+ char *data, *free, *parity;
+ bool failure = false;
+ int bitflips = 0;
+
+ if (req->mode == MTD_OPS_RAW)
+ return 0;
+
+ if (req->type == NAND_PAGE_WRITE) {
+ nand_ecc_restore_req(&ctx->req_ctx, req);
+ return 0;
+ }
+
+ free = req->oobbuf.in;
+ data = req->databuf.in;
+ parity = req->oobbuf.in + ctx->steps * RTL_ECC_FREE_SIZE;
+
+ for (int i = 0 ; i < ctx->steps; i++) {
+ int ret = rtl_ecc_run_engine(ctx, data, free, parity, RTL_ECC_OP_DECODE);
+
+ if (unlikely(ret < 0))
+ /* ECC totally fails for bitflips in erased blocks */
+ ret = nand_check_erased_ecc_chunk(data, RTL_ECC_BLOCK_SIZE,
+ parity, ctx->parity_size,
+ free, RTL_ECC_FREE_SIZE,
+ ctx->strength);
+ if (unlikely(ret < 0)) {
+ failure = true;
+ mtd->ecc_stats.failed++;
+ } else {
+ mtd->ecc_stats.corrected += ret;
+ bitflips = max_t(unsigned int, bitflips, ret);
+ }
+
+ free += RTL_ECC_FREE_SIZE;
+ data += RTL_ECC_BLOCK_SIZE;
+ parity += ctx->parity_size;
+ }
+
+ nand_ecc_restore_req(&ctx->req_ctx, req);
+
+ if (unlikely(failure))
+ dev_dbg(rtlc->dev, "ECC correction failed\n");
+ else if (unlikely(bitflips > 2))
+ dev_dbg(rtlc->dev, "%d bitflips detected\n", bitflips);
+
+ return failure ? -EBADMSG : bitflips;
+}
+
+static int rtl_ecc_check_support(struct nand_device *nand)
+{
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
+ struct device *dev = nand->ecc.engine->dev;
+
+ if (mtd->oobsize < RTL_ECC_ALLOWED_MIN_OOB_SIZE ||
+ mtd->writesize != RTL_ECC_ALLOWED_PAGE_SIZE) {
+ dev_err(dev, "only flash geometry data=%d, oob>=%d supported\n",
+ RTL_ECC_ALLOWED_PAGE_SIZE, RTL_ECC_ALLOWED_MIN_OOB_SIZE);
+ return -EINVAL;
+ }
+
+ if (nand->ecc.user_conf.algo != NAND_ECC_ALGO_BCH ||
+ nand->ecc.user_conf.strength != RTL_ECC_ALLOWED_STRENGTH ||
+ nand->ecc.user_conf.placement != NAND_ECC_PLACEMENT_OOB ||
+ nand->ecc.user_conf.step_size != RTL_ECC_BLOCK_SIZE) {
+ dev_err(dev, "only algo=bch, strength=%d, placement=oob, step=%d supported\n",
+ RTL_ECC_ALLOWED_STRENGTH, RTL_ECC_BLOCK_SIZE);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rtl_ecc_init_ctx(struct nand_device *nand)
+{
+ struct nand_ecc_props *conf = &nand->ecc.ctx.conf;
+ struct rtl_ecc_engine *rtlc = nand_to_rtlc(nand);
+ struct mtd_info *mtd = nanddev_to_mtd(nand);
+ int strength = nand->ecc.user_conf.strength;
+ struct device *dev = nand->ecc.engine->dev;
+ struct rtl_ecc_ctx *ctx;
+ int ret;
+
+ ret = rtl_ecc_check_support(nand);
+ if (ret)
+ return ret;
+
+ ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ nand->ecc.ctx.priv = ctx;
+ mtd_set_ooblayout(mtd, &rtl_ecc_ooblayout_ops);
+
+ conf->algo = NAND_ECC_ALGO_BCH;
+ conf->strength = strength;
+ conf->step_size = RTL_ECC_BLOCK_SIZE;
+ conf->engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST;
+
+ ctx->rtlc = rtlc;
+ ctx->steps = mtd->writesize / RTL_ECC_BLOCK_SIZE;
+ ctx->strength = strength;
+ ctx->bch_mode = strength == 6 ? RTL_ECC_BCH6 : RTL_ECC_BCH12;
+ ctx->parity_size = strength == 6 ? RTL_ECC_PARITY_SIZE_BCH6 : RTL_ECC_PARITY_SIZE_BCH12;
+
+ ret = nand_ecc_init_req_tweaking(&ctx->req_ctx, nand);
+ if (ret)
+ return ret;
+
+ dev_dbg(dev, "using bch%d with geometry data=%dx%d, free=%dx6, parity=%dx%d",
+ conf->strength, ctx->steps, conf->step_size,
+ ctx->steps, ctx->steps, ctx->parity_size);
+
+ return 0;
+}
+
+static void rtl_ecc_cleanup_ctx(struct nand_device *nand)
+{
+ struct rtl_ecc_ctx *ctx = nand_to_ctx(nand);
+
+ if (ctx)
+ nand_ecc_cleanup_req_tweaking(&ctx->req_ctx);
+}
+
+static const struct nand_ecc_engine_ops rtl_ecc_engine_ops = {
+ .init_ctx = rtl_ecc_init_ctx,
+ .cleanup_ctx = rtl_ecc_cleanup_ctx,
+ .prepare_io_req = rtl_ecc_prepare_io_req,
+ .finish_io_req = rtl_ecc_finish_io_req,
+};
+
+static int rtl_ecc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct rtl_ecc_engine *rtlc;
+ void __iomem *base;
+ int ret;
+
+ rtlc = devm_kzalloc(dev, sizeof(*rtlc), GFP_KERNEL);
+ if (!rtlc)
+ return -ENOMEM;
+
+ base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ ret = devm_mutex_init(dev, &rtlc->lock);
+ if (ret)
+ return ret;
+
+ rtlc->regmap = devm_regmap_init_mmio(dev, base, &rtl_ecc_regmap_config);
+ if (IS_ERR(rtlc->regmap))
+ return PTR_ERR(rtlc->regmap);
+
+ /*
+ * Focus on simplicity and use a preallocated DMA buffer for data exchange with the
+ * engine. For now make it a noncoherent memory model as invalidating/flushing caches
+ * is faster than reading/writing uncached memory on the known architectures.
+ */
+
+ rtlc->buf = dma_alloc_noncoherent(dev, RTL_ECC_DMA_SIZE, &rtlc->buf_dma,
+ DMA_BIDIRECTIONAL, GFP_KERNEL);
+ if (!rtlc->buf)
+ return -ENOMEM;
+
+ rtlc->dev = dev;
+ rtlc->engine.dev = dev;
+ rtlc->engine.ops = &rtl_ecc_engine_ops;
+ rtlc->engine.integration = NAND_ECC_ENGINE_INTEGRATION_EXTERNAL;
+
+ nand_ecc_register_on_host_hw_engine(&rtlc->engine);
+
+ platform_set_drvdata(pdev, rtlc);
+
+ return 0;
+}
+
+static void rtl_ecc_remove(struct platform_device *pdev)
+{
+ struct rtl_ecc_engine *rtlc = platform_get_drvdata(pdev);
+
+ nand_ecc_unregister_on_host_hw_engine(&rtlc->engine);
+ dma_free_noncoherent(rtlc->dev, RTL_ECC_DMA_SIZE, rtlc->buf, rtlc->buf_dma,
+ DMA_BIDIRECTIONAL);
+}
+
+static const struct of_device_id rtl_ecc_of_ids[] = {
+ {
+ .compatible = "realtek,rtl9301-ecc",
+ },
+ { /* sentinel */ },
+};
+
+static struct platform_driver rtl_ecc_driver = {
+ .driver = {
+ .name = "rtl-nand-ecc-engine",
+ .of_match_table = rtl_ecc_of_ids,
+ },
+ .probe = rtl_ecc_probe,
+ .remove = rtl_ecc_remove,
+};
+module_platform_driver(rtl_ecc_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Markus Stockhausen <markus.stockhausen@gmx.de>");
+MODULE_DESCRIPTION("Realtek NAND hardware ECC controller");
diff --git a/drivers/mtd/nand/ecc-sw-bch.c b/drivers/mtd/nand/ecc-sw-bch.c
index 0d9310dd6f52..26d5f3f10964 100644
--- a/drivers/mtd/nand/ecc-sw-bch.c
+++ b/drivers/mtd/nand/ecc-sw-bch.c
@@ -227,7 +227,7 @@ int nand_ecc_sw_bch_init_ctx(struct nand_device *nand)
return -EINVAL;
}
- engine_conf = kzalloc(sizeof(*engine_conf), GFP_KERNEL);
+ engine_conf = kzalloc_obj(*engine_conf);
if (!engine_conf)
return -ENOMEM;
diff --git a/drivers/mtd/nand/ecc-sw-hamming.c b/drivers/mtd/nand/ecc-sw-hamming.c
index f2d0effad9d2..460acc1029c3 100644
--- a/drivers/mtd/nand/ecc-sw-hamming.c
+++ b/drivers/mtd/nand/ecc-sw-hamming.c
@@ -8,7 +8,7 @@
*
* Completely replaces the previous ECC implementation which was written by:
* Steven J. Hill (sjhill@realitydiluted.com)
- * Thomas Gleixner (tglx@linutronix.de)
+ * Thomas Gleixner (tglx@kernel.org)
*
* Information on how this algorithm works and how it was developed
* can be found in Documentation/driver-api/mtd/nand_ecc.rst
@@ -496,7 +496,7 @@ int nand_ecc_sw_hamming_init_ctx(struct nand_device *nand)
if (conf->step_size != 256 && conf->step_size != 512)
conf->step_size = 256;
- engine_conf = kzalloc(sizeof(*engine_conf), GFP_KERNEL);
+ engine_conf = kzalloc_obj(*engine_conf);
if (!engine_conf)
return -ENOMEM;
diff --git a/drivers/mtd/nand/ecc.c b/drivers/mtd/nand/ecc.c
index 8f996e8d61b8..6ccdff3fc913 100644
--- a/drivers/mtd/nand/ecc.c
+++ b/drivers/mtd/nand/ecc.c
@@ -552,7 +552,7 @@ void nand_ecc_tweak_req(struct nand_ecc_req_tweak_ctx *ctx,
memset(tweak->oobbuf.in, 0xFF, ctx->oob_buffer_size);
}
- /* Copy the data that must be writen in the bounce buffers, if needed */
+ /* Copy the data that must be written in the bounce buffers, if needed */
if (orig->type == NAND_PAGE_WRITE) {
if (ctx->bounce_data)
memcpy((void *)tweak->databuf.out + orig->dataoffs,
diff --git a/drivers/mtd/nand/onenand/generic.c b/drivers/mtd/nand/onenand/generic.c
index 4e6fd1c34484..3191904ced14 100644
--- a/drivers/mtd/nand/onenand/generic.c
+++ b/drivers/mtd/nand/onenand/generic.c
@@ -37,7 +37,7 @@ static int generic_onenand_probe(struct platform_device *pdev)
unsigned long size = resource_size(res);
int err;
- info = kzalloc(sizeof(struct onenand_info), GFP_KERNEL);
+ info = kzalloc_obj(struct onenand_info);
if (!info)
return -ENOMEM;
diff --git a/drivers/mtd/nand/onenand/onenand_base.c b/drivers/mtd/nand/onenand/onenand_base.c
index 0dc2ea4fc857..d08aeac86f9f 100644
--- a/drivers/mtd/nand/onenand/onenand_base.c
+++ b/drivers/mtd/nand/onenand/onenand_base.c
@@ -3728,9 +3728,8 @@ static int onenand_probe(struct mtd_info *mtd)
/* Maximum possible erase regions */
mtd->numeraseregions = this->dies << 1;
mtd->eraseregions =
- kcalloc(this->dies << 1,
- sizeof(struct mtd_erase_region_info),
- GFP_KERNEL);
+ kzalloc_objs(struct mtd_erase_region_info,
+ this->dies << 1);
if (!mtd->eraseregions)
return -ENOMEM;
}
diff --git a/drivers/mtd/nand/onenand/onenand_bbt.c b/drivers/mtd/nand/onenand/onenand_bbt.c
index d7fe35bc45cb..012163fb6add 100644
--- a/drivers/mtd/nand/onenand/onenand_bbt.c
+++ b/drivers/mtd/nand/onenand/onenand_bbt.c
@@ -231,7 +231,7 @@ int onenand_default_bbt(struct mtd_info *mtd)
struct onenand_chip *this = mtd->priv;
struct bbm_info *bbm;
- this->bbm = kzalloc(sizeof(struct bbm_info), GFP_KERNEL);
+ this->bbm = kzalloc_obj(struct bbm_info);
if (!this->bbm)
return -ENOMEM;
diff --git a/drivers/mtd/nand/onenand/onenand_omap2.c b/drivers/mtd/nand/onenand/onenand_omap2.c
index f9a386b69050..0793251ada3b 100644
--- a/drivers/mtd/nand/onenand/onenand_omap2.c
+++ b/drivers/mtd/nand/onenand/onenand_omap2.c
@@ -603,7 +603,6 @@ static struct platform_driver omap2_onenand_driver = {
module_platform_driver(omap2_onenand_driver);
-MODULE_ALIAS("platform:" DRIVER_NAME);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jarkko Lavinen <jarkko.lavinen@nokia.com>");
MODULE_DESCRIPTION("Glue layer for OneNAND flash on OMAP2 / OMAP3");
diff --git a/drivers/mtd/nand/onenand/onenand_samsung.c b/drivers/mtd/nand/onenand/onenand_samsung.c
index f37a6138e461..b7b7758ce4d8 100644
--- a/drivers/mtd/nand/onenand/onenand_samsung.c
+++ b/drivers/mtd/nand/onenand/onenand_samsung.c
@@ -554,6 +554,9 @@ static int s5pc110_dma_poll(dma_addr_t dst, dma_addr_t src, size_t count, int di
} while (!(status & S5PC110_DMA_TRANS_STATUS_TD) &&
time_before(jiffies, timeout));
+ if (!(status & S5PC110_DMA_TRANS_STATUS_TD))
+ return -ETIMEDOUT;
+
writel(S5PC110_DMA_TRANS_CMD_TDC, base + S5PC110_DMA_TRANS_CMD);
return 0;
@@ -608,7 +611,9 @@ static int s5pc110_dma_irq(dma_addr_t dst, dma_addr_t src, size_t count, int dir
writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD);
- wait_for_completion_timeout(&onenand->complete, msecs_to_jiffies(20));
+ if (!wait_for_completion_timeout(&onenand->complete,
+ msecs_to_jiffies(20)))
+ return -ETIMEDOUT;
return 0;
}
@@ -906,7 +911,7 @@ static int s3c_onenand_probe(struct platform_device *pdev)
err = devm_request_irq(&pdev->dev, r->start,
s5pc110_onenand_irq,
IRQF_SHARED, "onenand",
- &onenand);
+ onenand);
if (err) {
dev_err(&pdev->dev, "failed to get irq\n");
return err;
diff --git a/drivers/mtd/nand/qpic_common.c b/drivers/mtd/nand/qpic_common.c
index e0ed25b5afea..4f3e4dd766da 100644
--- a/drivers/mtd/nand/qpic_common.c
+++ b/drivers/mtd/nand/qpic_common.c
@@ -57,14 +57,15 @@ qcom_alloc_bam_transaction(struct qcom_nand_controller *nandc)
bam_txn_buf += sizeof(*bam_txn);
bam_txn->bam_ce = bam_txn_buf;
- bam_txn_buf +=
- sizeof(*bam_txn->bam_ce) * QPIC_PER_CW_CMD_ELEMENTS * num_cw;
+ bam_txn->bam_ce_nitems = QPIC_PER_CW_CMD_ELEMENTS * num_cw;
+ bam_txn_buf += sizeof(*bam_txn->bam_ce) * bam_txn->bam_ce_nitems;
bam_txn->cmd_sgl = bam_txn_buf;
- bam_txn_buf +=
- sizeof(*bam_txn->cmd_sgl) * QPIC_PER_CW_CMD_SGL * num_cw;
+ bam_txn->cmd_sgl_nitems = QPIC_PER_CW_CMD_SGL * num_cw;
+ bam_txn_buf += sizeof(*bam_txn->cmd_sgl) * bam_txn->cmd_sgl_nitems;
bam_txn->data_sgl = bam_txn_buf;
+ bam_txn->data_sgl_nitems = QPIC_PER_CW_DATA_SGL * num_cw;
init_completion(&bam_txn->txn_done);
@@ -88,10 +89,8 @@ void qcom_clear_bam_transaction(struct qcom_nand_controller *nandc)
memset(&bam_txn->bam_positions, 0, sizeof(bam_txn->bam_positions));
bam_txn->last_data_desc = NULL;
- sg_init_table(bam_txn->cmd_sgl, nandc->max_cwperpage *
- QPIC_PER_CW_CMD_SGL);
- sg_init_table(bam_txn->data_sgl, nandc->max_cwperpage *
- QPIC_PER_CW_DATA_SGL);
+ sg_init_table(bam_txn->cmd_sgl, bam_txn->cmd_sgl_nitems);
+ sg_init_table(bam_txn->data_sgl, bam_txn->data_sgl_nitems);
reinit_completion(&bam_txn->txn_done);
}
@@ -157,7 +156,7 @@ int qcom_prepare_bam_async_desc(struct qcom_nand_controller *nandc,
enum dma_transfer_direction dir_eng;
struct dma_async_tx_descriptor *dma_desc;
- desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+ desc = kzalloc_obj(*desc);
if (!desc)
return -ENOMEM;
@@ -236,21 +235,26 @@ int qcom_prep_bam_dma_desc_cmd(struct qcom_nand_controller *nandc, bool read,
int i, ret;
struct bam_cmd_element *bam_ce_buffer;
struct bam_transaction *bam_txn = nandc->bam_txn;
+ u32 offset;
+
+ if (bam_txn->bam_ce_pos + size > bam_txn->bam_ce_nitems) {
+ dev_err(nandc->dev, "BAM %s array is full\n", "CE");
+ return -EINVAL;
+ }
bam_ce_buffer = &bam_txn->bam_ce[bam_txn->bam_ce_pos];
/* fill the command desc */
for (i = 0; i < size; i++) {
+ offset = nandc->props->bam_offset + reg_off + 4 * i;
if (read)
bam_prep_ce(&bam_ce_buffer[i],
- nandc_reg_phys(nandc, reg_off + 4 * i),
- BAM_READ_COMMAND,
+ offset, BAM_READ_COMMAND,
reg_buf_dma_addr(nandc,
(__le32 *)vaddr + i));
else
bam_prep_ce_le32(&bam_ce_buffer[i],
- nandc_reg_phys(nandc, reg_off + 4 * i),
- BAM_WRITE_COMMAND,
+ offset, BAM_WRITE_COMMAND,
*((__le32 *)vaddr + i));
}
@@ -258,6 +262,12 @@ int qcom_prep_bam_dma_desc_cmd(struct qcom_nand_controller *nandc, bool read,
/* use the separate sgl after this command */
if (flags & NAND_BAM_NEXT_SGL) {
+ if (bam_txn->cmd_sgl_pos >= bam_txn->cmd_sgl_nitems) {
+ dev_err(nandc->dev, "BAM %s array is full\n",
+ "CMD sgl");
+ return -EINVAL;
+ }
+
bam_ce_buffer = &bam_txn->bam_ce[bam_txn->bam_ce_start];
bam_ce_size = (bam_txn->bam_ce_pos -
bam_txn->bam_ce_start) *
@@ -297,10 +307,20 @@ int qcom_prep_bam_dma_desc_data(struct qcom_nand_controller *nandc, bool read,
struct bam_transaction *bam_txn = nandc->bam_txn;
if (read) {
+ if (bam_txn->rx_sgl_pos >= bam_txn->data_sgl_nitems) {
+ dev_err(nandc->dev, "BAM %s array is full\n", "RX sgl");
+ return -EINVAL;
+ }
+
sg_set_buf(&bam_txn->data_sgl[bam_txn->rx_sgl_pos],
vaddr, size);
bam_txn->rx_sgl_pos++;
} else {
+ if (bam_txn->tx_sgl_pos >= bam_txn->data_sgl_nitems) {
+ dev_err(nandc->dev, "BAM %s array is full\n", "TX sgl");
+ return -EINVAL;
+ }
+
sg_set_buf(&bam_txn->data_sgl[bam_txn->tx_sgl_pos],
vaddr, size);
bam_txn->tx_sgl_pos++;
@@ -344,7 +364,7 @@ int qcom_prep_adm_dma_desc(struct qcom_nand_controller *nandc, bool read,
struct scatterlist *sgl;
int ret;
- desc = kzalloc(sizeof(*desc), GFP_KERNEL);
+ desc = kzalloc_obj(*desc);
if (!desc)
return -ENOMEM;
diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index b8035df8f732..1f4053e531fd 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -34,7 +34,7 @@ config MTD_NAND_DENALI_DT
config MTD_NAND_AMS_DELTA
tristate "Amstrad E3 NAND controller"
depends on MACH_AMS_DELTA || COMPILE_TEST
- default y
+ default MACH_AMS_DELTA
help
Support for NAND flash on Amstrad E3 (Delta).
@@ -71,38 +71,13 @@ config MTD_NAND_AU1550
config MTD_NAND_NDFC
tristate "IBM/MCC 4xx NAND controller"
- depends on 4xx
+ depends on 44x || COMPILE_TEST
+ depends on OF
select MTD_NAND_ECC_SW_HAMMING
select MTD_NAND_ECC_SW_HAMMING_SMC
help
NDFC Nand Flash Controllers are integrated in IBM/AMCC's 4xx SoCs
-config MTD_NAND_S3C2410
- tristate "Samsung S3C NAND controller"
- depends on ARCH_S3C64XX
- help
- This enables the NAND flash controller on the S3C24xx and S3C64xx
- SoCs
-
- No board specific support is done by this driver, each board
- must advertise a platform_device for the driver to attach.
-
-config MTD_NAND_S3C2410_DEBUG
- bool "Samsung S3C NAND controller debug"
- depends on MTD_NAND_S3C2410
- help
- Enable debugging of the S3C NAND driver
-
-config MTD_NAND_S3C2410_CLKSTOP
- bool "Samsung S3C NAND IDLE clock stop"
- depends on MTD_NAND_S3C2410
- default n
- help
- Stop the clock to the NAND controller when there is no chip
- selected to save power. This will mean there is a small delay
- when the is NAND chip selected or released, but will save
- approximately 5mA of power when there is nothing happening.
-
config MTD_NAND_SHARPSL
tristate "Sharp SL Series (C7xx + others) NAND controller"
depends on ARCH_PXA || COMPILE_TEST
@@ -330,7 +305,7 @@ config MTD_NAND_HISI504
Enables support for NAND controller on Hisilicon SoC Hip04.
config MTD_NAND_QCOM
- tristate "QCOM NAND controller"
+ tristate "Qualcomm NAND controller"
depends on ARCH_QCOM || COMPILE_TEST
depends on HAS_IOMEM
help
@@ -462,6 +437,13 @@ config MTD_NAND_NUVOTON_MA35
Enables support for the NAND controller found on
the Nuvoton MA35 series SoCs.
+config MTD_NAND_LOONGSON
+ tristate "Loongson NAND controller"
+ depends on LOONGSON1_APB_DMA || LOONGSON2_APB_DMA || COMPILE_TEST
+ select REGMAP_MMIO
+ help
+ Enables support for NAND controller on Loongson family chips.
+
comment "Misc"
config MTD_SM_COMMON
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index 99e79c448847..619760138d32 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -9,7 +9,6 @@ obj-$(CONFIG_MTD_NAND_DENALI) += denali.o
obj-$(CONFIG_MTD_NAND_DENALI_PCI) += denali_pci.o
obj-$(CONFIG_MTD_NAND_DENALI_DT) += denali_dt.o
obj-$(CONFIG_MTD_NAND_AU1550) += au1550nd.o
-obj-$(CONFIG_MTD_NAND_S3C2410) += s3c2410.o
obj-$(CONFIG_MTD_NAND_DAVINCI) += davinci_nand.o
obj-$(CONFIG_MTD_NAND_DISKONCHIP) += diskonchip.o
obj-$(CONFIG_MTD_NAND_FSMC) += fsmc_nand.o
@@ -59,6 +58,7 @@ obj-$(CONFIG_MTD_NAND_ROCKCHIP) += rockchip-nand-controller.o
obj-$(CONFIG_MTD_NAND_PL35X) += pl35x-nand-controller.o
obj-$(CONFIG_MTD_NAND_RENESAS) += renesas-nand-controller.o
obj-$(CONFIG_MTD_NAND_NUVOTON_MA35) += nuvoton-ma35d1-nand-controller.o
+obj-$(CONFIG_MTD_NAND_LOONGSON) += loongson-nand-controller.o
nand-objs := nand_base.o nand_legacy.o nand_bbt.o nand_timings.o nand_ids.o
nand-objs += nand_onfi.o
diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c
index dedcca87defc..e7fdf532c5fe 100644
--- a/drivers/mtd/nand/raw/atmel/nand-controller.c
+++ b/drivers/mtd/nand/raw/atmel/nand-controller.c
@@ -373,7 +373,7 @@ static int atmel_nand_dma_transfer(struct atmel_nand_controller *nc,
dma_cookie_t cookie;
buf_dma = dma_map_single(nc->dev, buf, len, dir);
- if (dma_mapping_error(nc->dev, dev_dma)) {
+ if (dma_mapping_error(nc->dev, buf_dma)) {
dev_err(nc->dev,
"Failed to prepare a buffer for DMA access\n");
goto err;
@@ -1240,7 +1240,7 @@ static int atmel_smc_nand_prepare_smcconf(struct atmel_nand *nand,
const struct nand_interface_config *conf,
struct atmel_smc_cs_conf *smcconf)
{
- u32 ncycles, totalcycles, timeps, mckperiodps;
+ u32 ncycles, totalcycles, timeps, mckperiodps, pulse;
struct atmel_nand_controller *nc;
int ret;
@@ -1366,11 +1366,16 @@ static int atmel_smc_nand_prepare_smcconf(struct atmel_nand *nand,
ATMEL_SMC_MODE_TDFMODE_OPTIMIZED;
/*
- * Read pulse timing directly matches tRP:
+ * Read pulse timing would directly match tRP,
+ * but some NAND flash chips (S34ML01G2 and W29N02KVxxAF)
+ * do not work properly in timing mode 3.
+ * The workaround is to extend the SMC NRD pulse to meet tREA
+ * timing.
*
- * NRD_PULSE = tRP
+ * NRD_PULSE = max(tRP, tREA)
*/
- ncycles = DIV_ROUND_UP(conf->timings.sdr.tRP_min, mckperiodps);
+ pulse = max(conf->timings.sdr.tRP_min, conf->timings.sdr.tREA_max);
+ ncycles = DIV_ROUND_UP(pulse, mckperiodps);
totalcycles += ncycles;
ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NRD_SHIFT,
ncycles);
@@ -1378,13 +1383,23 @@ static int atmel_smc_nand_prepare_smcconf(struct atmel_nand *nand,
return ret;
/*
+ * Read setup timing depends on the operation done on the NAND:
+ *
+ * NRD_SETUP = max(tAR, tCLR)
+ */
+ timeps = max(conf->timings.sdr.tAR_min, conf->timings.sdr.tCLR_min);
+ ncycles = DIV_ROUND_UP(timeps, mckperiodps);
+ totalcycles += ncycles;
+ ret = atmel_smc_cs_conf_set_setup(smcconf, ATMEL_SMC_NRD_SHIFT, ncycles);
+ if (ret)
+ return ret;
+
+ /*
* The read cycle timing is directly matching tRC, but is also
* dependent on the setup and hold timings we calculated earlier,
* which gives:
*
- * NRD_CYCLE = max(tRC, NRD_PULSE + NRD_HOLD)
- *
- * NRD_SETUP is always 0.
+ * NRD_CYCLE = max(tRC, NRD_SETUP + NRD_PULSE + NRD_HOLD)
*/
ncycles = DIV_ROUND_UP(conf->timings.sdr.tRC_min, mckperiodps);
ncycles = max(totalcycles, ncycles);
@@ -1848,7 +1863,7 @@ atmel_nand_controller_legacy_add_nands(struct atmel_nand_controller *nc)
static int atmel_nand_controller_add_nands(struct atmel_nand_controller *nc)
{
- struct device_node *np, *nand_np;
+ struct device_node *np;
struct device *dev = nc->dev;
int ret, reg_cells;
u32 val;
@@ -1875,7 +1890,7 @@ static int atmel_nand_controller_add_nands(struct atmel_nand_controller *nc)
reg_cells += val;
- for_each_child_of_node(np, nand_np) {
+ for_each_child_of_node_scoped(np, nand_np) {
struct atmel_nand *nand;
nand = atmel_nand_create(nc, nand_np, reg_cells);
@@ -2289,10 +2304,8 @@ atmel_hsmc_nand_controller_init(struct atmel_hsmc_nand_controller *nc)
nc->sram.pool = of_gen_pool_get(nc->base.dev->of_node,
"atmel,nfc-sram", 0);
- if (!nc->sram.pool) {
- dev_err(nc->base.dev, "Missing SRAM\n");
- return -ENOMEM;
- }
+ if (!nc->sram.pool)
+ return dev_err_probe(nc->base.dev, -EPROBE_DEFER, "Missing SRAM\n");
nc->sram.virt = (void __iomem *)gen_pool_dma_alloc(nc->sram.pool,
ATMEL_NFC_SRAM_SIZE,
diff --git a/drivers/mtd/nand/raw/atmel/pmecc.c b/drivers/mtd/nand/raw/atmel/pmecc.c
index 3c7dee1be21d..1d0e93e4edb1 100644
--- a/drivers/mtd/nand/raw/atmel/pmecc.c
+++ b/drivers/mtd/nand/raw/atmel/pmecc.c
@@ -143,6 +143,7 @@ struct atmel_pmecc_caps {
int nstrengths;
int el_offset;
bool correct_erased_chunks;
+ bool clk_ctrl;
};
struct atmel_pmecc {
@@ -843,6 +844,10 @@ static struct atmel_pmecc *atmel_pmecc_create(struct platform_device *pdev,
if (IS_ERR(pmecc->regs.errloc))
return ERR_CAST(pmecc->regs.errloc);
+ /* pmecc data setup time */
+ if (caps->clk_ctrl)
+ writel(PMECC_CLK_133MHZ, pmecc->regs.base + ATMEL_PMECC_CLK);
+
/* Disable all interrupts before registering the PMECC handler. */
writel(0xffffffff, pmecc->regs.base + ATMEL_PMECC_IDR);
atmel_pmecc_reset(pmecc);
@@ -896,6 +901,7 @@ static struct atmel_pmecc_caps at91sam9g45_caps = {
.strengths = atmel_pmecc_strengths,
.nstrengths = 5,
.el_offset = 0x8c,
+ .clk_ctrl = true,
};
static struct atmel_pmecc_caps sama5d4_caps = {
@@ -1004,4 +1010,3 @@ module_platform_driver(atmel_pmecc_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Boris Brezillon <boris.brezillon@free-electrons.com>");
MODULE_DESCRIPTION("PMECC engine driver");
-MODULE_ALIAS("platform:atmel_pmecc");
diff --git a/drivers/mtd/nand/raw/au1550nd.c b/drivers/mtd/nand/raw/au1550nd.c
index 04d64724c400..f1e99197ab84 100644
--- a/drivers/mtd/nand/raw/au1550nd.c
+++ b/drivers/mtd/nand/raw/au1550nd.c
@@ -266,7 +266,7 @@ static int au1550nd_probe(struct platform_device *pdev)
return -ENODEV;
}
- ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ ctx = kzalloc_obj(*ctx);
if (!ctx)
return -ENOMEM;
diff --git a/drivers/mtd/nand/raw/bcm47xxnflash/ops_bcm4706.c b/drivers/mtd/nand/raw/bcm47xxnflash/ops_bcm4706.c
index 6487dfc64258..e532c3535b16 100644
--- a/drivers/mtd/nand/raw/bcm47xxnflash/ops_bcm4706.c
+++ b/drivers/mtd/nand/raw/bcm47xxnflash/ops_bcm4706.c
@@ -171,6 +171,7 @@ static void bcm47xxnflash_ops_bcm4706_cmd_ctrl(struct nand_chip *nand_chip,
{
struct bcm47xxnflash *b47n = nand_get_controller_data(nand_chip);
u32 code = 0;
+ int rc;
if (cmd == NAND_CMD_NONE)
return;
@@ -182,7 +183,9 @@ static void bcm47xxnflash_ops_bcm4706_cmd_ctrl(struct nand_chip *nand_chip,
if (cmd != NAND_CMD_RESET)
code |= NCTL_CSA;
- bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc, code);
+ rc = bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc, code);
+ if (rc)
+ pr_err("ctl_cmd didn't work with error %d\n", rc);
}
/* Default nand_select_chip calls cmd_ctrl, which is not used in BCM4706 */
diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.c b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
index fea5b6119956..5b9dadd5405e 100644
--- a/drivers/mtd/nand/raw/brcmnand/brcmnand.c
+++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c
@@ -29,6 +29,7 @@
#include <linux/static_key.h>
#include <linux/list.h>
#include <linux/log2.h>
+#include <linux/string_choices.h>
#include "brcmnand.h"
@@ -65,6 +66,7 @@ module_param(wp_on, int, 0444);
#define CMD_PARAMETER_READ 0x0e
#define CMD_PARAMETER_CHANGE_COL 0x0f
#define CMD_LOW_LEVEL_OP 0x10
+#define CMD_NOT_SUPPORTED 0xff
struct brcm_nand_dma_desc {
u32 next_desc;
@@ -101,7 +103,7 @@ struct brcm_nand_dma_desc {
#define BRCMNAND_MIN_DEVSIZE (4ULL * 1024 * 1024)
#define NAND_CTRL_RDY (INTFC_CTLR_READY | INTFC_FLASH_READY)
-#define NAND_POLL_STATUS_TIMEOUT_MS 100
+#define NAND_POLL_STATUS_TIMEOUT_MS 500
#define EDU_CMD_WRITE 0x00
#define EDU_CMD_READ 0x01
@@ -199,6 +201,30 @@ static const u16 flash_dma_regs_v4[] = {
[FLASH_DMA_CURRENT_DESC_EXT] = 0x34,
};
+/* Native command conversion for legacy controllers (< v5.0) */
+static const u8 native_cmd_conv[] = {
+ [NAND_CMD_READ0] = CMD_NOT_SUPPORTED,
+ [NAND_CMD_READ1] = CMD_NOT_SUPPORTED,
+ [NAND_CMD_RNDOUT] = CMD_PARAMETER_CHANGE_COL,
+ [NAND_CMD_PAGEPROG] = CMD_NOT_SUPPORTED,
+ [NAND_CMD_READOOB] = CMD_NOT_SUPPORTED,
+ [NAND_CMD_ERASE1] = CMD_BLOCK_ERASE,
+ [NAND_CMD_STATUS] = CMD_NOT_SUPPORTED,
+ [NAND_CMD_SEQIN] = CMD_NOT_SUPPORTED,
+ [NAND_CMD_RNDIN] = CMD_NOT_SUPPORTED,
+ [NAND_CMD_READID] = CMD_DEVICE_ID_READ,
+ [NAND_CMD_ERASE2] = CMD_NULL,
+ [NAND_CMD_PARAM] = CMD_PARAMETER_READ,
+ [NAND_CMD_GET_FEATURES] = CMD_NOT_SUPPORTED,
+ [NAND_CMD_SET_FEATURES] = CMD_NOT_SUPPORTED,
+ [NAND_CMD_RESET] = CMD_NOT_SUPPORTED,
+ [NAND_CMD_READSTART] = CMD_NOT_SUPPORTED,
+ [NAND_CMD_READCACHESEQ] = CMD_NOT_SUPPORTED,
+ [NAND_CMD_READCACHEEND] = CMD_NOT_SUPPORTED,
+ [NAND_CMD_RNDOUTSTART] = CMD_NULL,
+ [NAND_CMD_CACHEDPROG] = CMD_NOT_SUPPORTED,
+};
+
/* Controller feature flags */
enum {
BRCMNAND_HAS_1K_SECTORS = BIT(0),
@@ -237,6 +263,12 @@ struct brcmnand_controller {
/* List of NAND hosts (one for each chip-select) */
struct list_head host_list;
+ /* Functions to be called from exec_op */
+ int (*check_instr)(struct nand_chip *chip,
+ const struct nand_operation *op);
+ int (*exec_instr)(struct nand_chip *chip,
+ const struct nand_operation *op);
+
/* EDU info, per-transaction */
const u16 *edu_offsets;
void __iomem *edu_base;
@@ -310,9 +342,6 @@ struct brcmnand_host {
struct platform_device *pdev;
int cs;
- unsigned int last_cmd;
- unsigned int last_byte;
- u64 last_addr;
struct brcmnand_cfg hwcfg;
struct brcmnand_controller *ctrl;
};
@@ -331,6 +360,7 @@ enum brcmnand_reg {
BRCMNAND_CORR_THRESHOLD_EXT,
BRCMNAND_UNCORR_COUNT,
BRCMNAND_CORR_COUNT,
+ BRCMNAND_READ_ERROR_COUNT,
BRCMNAND_CORR_EXT_ADDR,
BRCMNAND_CORR_ADDR,
BRCMNAND_UNCORR_EXT_ADDR,
@@ -361,6 +391,7 @@ static const u16 brcmnand_regs_v21[] = {
[BRCMNAND_CORR_THRESHOLD_EXT] = 0,
[BRCMNAND_UNCORR_COUNT] = 0,
[BRCMNAND_CORR_COUNT] = 0,
+ [BRCMNAND_READ_ERROR_COUNT] = 0,
[BRCMNAND_CORR_EXT_ADDR] = 0x60,
[BRCMNAND_CORR_ADDR] = 0x64,
[BRCMNAND_UNCORR_EXT_ADDR] = 0x68,
@@ -391,6 +422,7 @@ static const u16 brcmnand_regs_v33[] = {
[BRCMNAND_CORR_THRESHOLD_EXT] = 0,
[BRCMNAND_UNCORR_COUNT] = 0,
[BRCMNAND_CORR_COUNT] = 0,
+ [BRCMNAND_READ_ERROR_COUNT] = 0x80,
[BRCMNAND_CORR_EXT_ADDR] = 0x70,
[BRCMNAND_CORR_ADDR] = 0x74,
[BRCMNAND_UNCORR_EXT_ADDR] = 0x78,
@@ -421,6 +453,7 @@ static const u16 brcmnand_regs_v50[] = {
[BRCMNAND_CORR_THRESHOLD_EXT] = 0,
[BRCMNAND_UNCORR_COUNT] = 0,
[BRCMNAND_CORR_COUNT] = 0,
+ [BRCMNAND_READ_ERROR_COUNT] = 0x80,
[BRCMNAND_CORR_EXT_ADDR] = 0x70,
[BRCMNAND_CORR_ADDR] = 0x74,
[BRCMNAND_UNCORR_EXT_ADDR] = 0x78,
@@ -451,6 +484,7 @@ static const u16 brcmnand_regs_v60[] = {
[BRCMNAND_CORR_THRESHOLD_EXT] = 0xc4,
[BRCMNAND_UNCORR_COUNT] = 0xfc,
[BRCMNAND_CORR_COUNT] = 0x100,
+ [BRCMNAND_READ_ERROR_COUNT] = 0x104,
[BRCMNAND_CORR_EXT_ADDR] = 0x10c,
[BRCMNAND_CORR_ADDR] = 0x110,
[BRCMNAND_UNCORR_EXT_ADDR] = 0x114,
@@ -481,6 +515,7 @@ static const u16 brcmnand_regs_v71[] = {
[BRCMNAND_CORR_THRESHOLD_EXT] = 0xe0,
[BRCMNAND_UNCORR_COUNT] = 0xfc,
[BRCMNAND_CORR_COUNT] = 0x100,
+ [BRCMNAND_READ_ERROR_COUNT] = 0x104,
[BRCMNAND_CORR_EXT_ADDR] = 0x10c,
[BRCMNAND_CORR_ADDR] = 0x110,
[BRCMNAND_UNCORR_EXT_ADDR] = 0x114,
@@ -511,6 +546,7 @@ static const u16 brcmnand_regs_v72[] = {
[BRCMNAND_CORR_THRESHOLD_EXT] = 0xe0,
[BRCMNAND_UNCORR_COUNT] = 0xfc,
[BRCMNAND_CORR_COUNT] = 0x100,
+ [BRCMNAND_READ_ERROR_COUNT] = 0x104,
[BRCMNAND_CORR_EXT_ADDR] = 0x10c,
[BRCMNAND_CORR_ADDR] = 0x110,
[BRCMNAND_UNCORR_EXT_ADDR] = 0x114,
@@ -931,11 +967,11 @@ static inline u16 brcmnand_cs_offset(struct brcmnand_controller *ctrl, int cs,
return offs_cs0 + cs * ctrl->reg_spacing + cs_offs;
}
-static inline u32 brcmnand_count_corrected(struct brcmnand_controller *ctrl)
+static inline u32 brcmnand_corr_total(struct brcmnand_controller *ctrl)
{
- if (ctrl->nand_version < 0x0600)
- return 1;
- return brcmnand_read_reg(ctrl, BRCMNAND_CORR_COUNT);
+ if (ctrl->nand_version < 0x400)
+ return 0;
+ return brcmnand_read_reg(ctrl, BRCMNAND_READ_ERROR_COUNT);
}
static void brcmnand_wr_corr_thresh(struct brcmnand_host *host, u8 val)
@@ -1434,7 +1470,7 @@ static void brcmnand_wp(struct mtd_info *mtd, int wp)
int ret;
if (old_wp != wp) {
- dev_dbg(ctrl->dev, "WP %s\n", wp ? "on" : "off");
+ dev_dbg(ctrl->dev, "WP %s\n", str_on_off(wp));
old_wp = wp;
}
@@ -1464,7 +1500,7 @@ static void brcmnand_wp(struct mtd_info *mtd, int wp)
if (ret)
dev_err_ratelimited(&host->pdev->dev,
"nand #WP expected %s\n",
- wp ? "on" : "off");
+ str_on_off(wp));
}
}
@@ -1841,8 +1877,8 @@ static int brcmnand_edu_trans(struct brcmnand_host *host, u64 addr, u32 *buf,
unsigned int trans = len >> FC_SHIFT;
dma_addr_t pa;
- dev_dbg(ctrl->dev, "EDU %s %p:%p\n", ((edu_cmd == EDU_CMD_READ) ?
- "read" : "write"), buf, oob);
+ dev_dbg(ctrl->dev, "EDU %s %p:%p\n",
+ str_read_write(edu_cmd == EDU_CMD_READ), buf, oob);
pa = dma_map_single(ctrl->dev, buf, len, dir);
if (dma_mapping_error(ctrl->dev, pa)) {
@@ -2038,15 +2074,20 @@ static int brcmnand_dma_trans(struct brcmnand_host *host, u64 addr, u32 *buf,
*/
static int brcmnand_read_by_pio(struct mtd_info *mtd, struct nand_chip *chip,
u64 addr, unsigned int trans, u32 *buf,
- u8 *oob, u64 *err_addr)
+ u8 *oob, u64 *err_addr, unsigned int *corr)
{
struct brcmnand_host *host = nand_get_controller_data(chip);
struct brcmnand_controller *ctrl = host->ctrl;
int i, ret = 0;
+ unsigned int prev_corr;
+
+ if (corr)
+ *corr = 0;
brcmnand_clear_ecc_addr(ctrl);
for (i = 0; i < trans; i++, addr += FC_BYTES) {
+ prev_corr = brcmnand_corr_total(ctrl);
brcmnand_set_cmd_addr(mtd, addr);
/* SPARE_AREA_READ does not use ECC, so just use PAGE_READ */
brcmnand_send_cmd(host, CMD_PAGE_READ);
@@ -2071,13 +2112,16 @@ static int brcmnand_read_by_pio(struct mtd_info *mtd, struct nand_chip *chip,
if (*err_addr)
ret = -EBADMSG;
- }
+ else {
+ *err_addr = brcmnand_get_correcc_addr(ctrl);
- if (!ret) {
- *err_addr = brcmnand_get_correcc_addr(ctrl);
+ if (*err_addr) {
+ ret = -EUCLEAN;
- if (*err_addr)
- ret = -EUCLEAN;
+ if (corr && (brcmnand_corr_total(ctrl) - prev_corr) > *corr)
+ *corr = brcmnand_corr_total(ctrl) - prev_corr;
+ }
+ }
}
}
@@ -2145,6 +2189,8 @@ static int brcmnand_read(struct mtd_info *mtd, struct nand_chip *chip,
int err;
bool retry = true;
bool edu_err = false;
+ unsigned int corrected = 0; /* max corrected bits per subpage */
+ unsigned int prev_tot = brcmnand_corr_total(ctrl);
dev_dbg(ctrl->dev, "read %llx -> %p\n", (unsigned long long)addr, buf);
@@ -2172,9 +2218,11 @@ try_dmaread:
memset(oob, 0x99, mtd->oobsize);
err = brcmnand_read_by_pio(mtd, chip, addr, trans, buf,
- oob, &err_addr);
+ oob, &err_addr, &corrected);
}
+ mtd->ecc_stats.corrected += brcmnand_corr_total(ctrl) - prev_tot;
+
if (mtd_is_eccerr(err)) {
/*
* On controller version and 7.0, 7.1 , DMA read after a
@@ -2212,16 +2260,20 @@ try_dmaread:
}
if (mtd_is_bitflip(err)) {
- unsigned int corrected = brcmnand_count_corrected(ctrl);
-
/* in case of EDU correctable error we read again using PIO */
if (edu_err)
err = brcmnand_read_by_pio(mtd, chip, addr, trans, buf,
- oob, &err_addr);
+ oob, &err_addr, &corrected);
dev_dbg(ctrl->dev, "corrected error at 0x%llx\n",
(unsigned long long)err_addr);
- mtd->ecc_stats.corrected += corrected;
+ /*
+ * if flipped bits accumulator is not supported but we detected
+ * a correction, increase stat by 1 to match previous behavior.
+ */
+ if (brcmnand_corr_total(ctrl) == prev_tot)
+ mtd->ecc_stats.corrected++;
+
/* Always exceed the software-imposed threshold */
return max(mtd->bitflip_threshold, corrected);
}
@@ -2233,14 +2285,11 @@ static int brcmnand_read_page(struct nand_chip *chip, uint8_t *buf,
int oob_required, int page)
{
struct mtd_info *mtd = nand_to_mtd(chip);
- struct brcmnand_host *host = nand_get_controller_data(chip);
u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL;
u64 addr = (u64)page << chip->page_shift;
- host->last_addr = addr;
-
- return brcmnand_read(mtd, chip, host->last_addr,
- mtd->writesize >> FC_SHIFT, (u32 *)buf, oob);
+ return brcmnand_read(mtd, chip, addr, mtd->writesize >> FC_SHIFT,
+ (u32 *)buf, oob);
}
static int brcmnand_read_page_raw(struct nand_chip *chip, uint8_t *buf,
@@ -2252,11 +2301,9 @@ static int brcmnand_read_page_raw(struct nand_chip *chip, uint8_t *buf,
int ret;
u64 addr = (u64)page << chip->page_shift;
- host->last_addr = addr;
-
brcmnand_set_ecc_enabled(host, 0);
- ret = brcmnand_read(mtd, chip, host->last_addr,
- mtd->writesize >> FC_SHIFT, (u32 *)buf, oob);
+ ret = brcmnand_read(mtd, chip, addr, mtd->writesize >> FC_SHIFT,
+ (u32 *)buf, oob);
brcmnand_set_ecc_enabled(host, 1);
return ret;
}
@@ -2303,14 +2350,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 (mtd->oops_panic_write)
+ if (mtd->oops_panic_write) {
/* switch to interrupt polling and PIO mode */
disable_ctrl_irqs(ctrl);
-
- if (use_dma(ctrl) && (has_edu(ctrl) || !oob) && flash_dma_buf_ok(buf)) {
+ } else if (use_dma(ctrl) && (has_edu(ctrl) || !oob) && flash_dma_buf_ok(buf)) {
if (ctrl->dma_trans(host, addr, (u32 *)buf, oob, mtd->writesize,
CMD_PROGRAM_PAGE))
-
ret = -EIO;
goto out;
@@ -2363,13 +2408,10 @@ static int brcmnand_write_page(struct nand_chip *chip, const uint8_t *buf,
int oob_required, int page)
{
struct mtd_info *mtd = nand_to_mtd(chip);
- struct brcmnand_host *host = nand_get_controller_data(chip);
void *oob = oob_required ? chip->oob_poi : NULL;
u64 addr = (u64)page << chip->page_shift;
- host->last_addr = addr;
-
- return brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
+ return brcmnand_write(mtd, chip, addr, (const u32 *)buf, oob);
}
static int brcmnand_write_page_raw(struct nand_chip *chip, const uint8_t *buf,
@@ -2381,9 +2423,8 @@ static int brcmnand_write_page_raw(struct nand_chip *chip, const uint8_t *buf,
u64 addr = (u64)page << chip->page_shift;
int ret = 0;
- host->last_addr = addr;
brcmnand_set_ecc_enabled(host, 0);
- ret = brcmnand_write(mtd, chip, host->last_addr, (const u32 *)buf, oob);
+ ret = brcmnand_write(mtd, chip, addr, (const u32 *)buf, oob);
brcmnand_set_ecc_enabled(host, 1);
return ret;
@@ -2490,18 +2531,190 @@ static int brcmnand_op_is_reset(const struct nand_operation *op)
return 0;
}
+static int brcmnand_check_instructions(struct nand_chip *chip,
+ const struct nand_operation *op)
+{
+ return 0;
+}
+
+static int brcmnand_exec_instructions(struct nand_chip *chip,
+ const struct nand_operation *op)
+{
+ struct brcmnand_host *host = nand_get_controller_data(chip);
+ unsigned int i;
+ int ret = 0;
+
+ for (i = 0; i < op->ninstrs; i++) {
+ ret = brcmnand_exec_instr(host, i, op);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+static int brcmnand_check_instructions_legacy(struct nand_chip *chip,
+ const struct nand_operation *op)
+{
+ const struct nand_op_instr *instr;
+ unsigned int i;
+ u8 cmd;
+
+ for (i = 0; i < op->ninstrs; i++) {
+ instr = &op->instrs[i];
+
+ switch (instr->type) {
+ case NAND_OP_CMD_INSTR:
+ cmd = native_cmd_conv[instr->ctx.cmd.opcode];
+ if (cmd == CMD_NOT_SUPPORTED)
+ return -EOPNOTSUPP;
+ break;
+ case NAND_OP_ADDR_INSTR:
+ case NAND_OP_DATA_IN_INSTR:
+ case NAND_OP_WAITRDY_INSTR:
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
+ return 0;
+}
+
+static int brcmnand_exec_instructions_legacy(struct nand_chip *chip,
+ const struct nand_operation *op)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct brcmnand_host *host = nand_get_controller_data(chip);
+ struct brcmnand_controller *ctrl = host->ctrl;
+ const struct nand_op_instr *instr;
+ unsigned int i, j;
+ u8 cmd = CMD_NULL, last_cmd = CMD_NULL;
+ int ret = 0;
+ u64 last_addr;
+
+ for (i = 0; i < op->ninstrs; i++) {
+ instr = &op->instrs[i];
+
+ if (instr->type == NAND_OP_CMD_INSTR) {
+ cmd = native_cmd_conv[instr->ctx.cmd.opcode];
+ if (cmd == CMD_NOT_SUPPORTED) {
+ dev_err(ctrl->dev, "unsupported cmd=%d\n",
+ instr->ctx.cmd.opcode);
+ ret = -EOPNOTSUPP;
+ break;
+ }
+ } else if (instr->type == NAND_OP_ADDR_INSTR) {
+ u64 addr = 0;
+
+ if (cmd == CMD_NULL)
+ continue;
+
+ if (instr->ctx.addr.naddrs > 8) {
+ dev_err(ctrl->dev, "unsupported naddrs=%u\n",
+ instr->ctx.addr.naddrs);
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ for (j = 0; j < instr->ctx.addr.naddrs; j++)
+ addr |= (instr->ctx.addr.addrs[j]) << (j << 3);
+
+ if (cmd == CMD_BLOCK_ERASE)
+ addr <<= chip->page_shift;
+ else if (cmd == CMD_PARAMETER_CHANGE_COL)
+ addr &= ~((u64)(FC_BYTES - 1));
+
+ brcmnand_set_cmd_addr(mtd, addr);
+ brcmnand_send_cmd(host, cmd);
+ last_addr = addr;
+ last_cmd = cmd;
+ cmd = CMD_NULL;
+ brcmnand_waitfunc(chip);
+
+ if (last_cmd == CMD_PARAMETER_READ ||
+ last_cmd == CMD_PARAMETER_CHANGE_COL) {
+ /* Copy flash cache word-wise */
+ u32 *flash_cache = (u32 *)ctrl->flash_cache;
+
+ brcmnand_soc_data_bus_prepare(ctrl->soc, true);
+
+ /*
+ * Must cache the FLASH_CACHE now, since changes in
+ * SECTOR_SIZE_1K may invalidate it
+ */
+ for (j = 0; j < FC_WORDS; j++)
+ /*
+ * Flash cache is big endian for parameter pages, at
+ * least on STB SoCs
+ */
+ flash_cache[j] = be32_to_cpu(brcmnand_read_fc(ctrl, j));
+
+ brcmnand_soc_data_bus_unprepare(ctrl->soc, true);
+ }
+ } else if (instr->type == NAND_OP_DATA_IN_INSTR) {
+ u8 *in = instr->ctx.data.buf.in;
+
+ if (last_cmd == CMD_DEVICE_ID_READ) {
+ u32 val;
+
+ if (instr->ctx.data.len > 8) {
+ dev_err(ctrl->dev, "unsupported len=%u\n",
+ instr->ctx.data.len);
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ for (j = 0; j < instr->ctx.data.len; j++) {
+ if (j == 0)
+ val = brcmnand_read_reg(ctrl, BRCMNAND_ID);
+ else if (j == 4)
+ val = brcmnand_read_reg(ctrl, BRCMNAND_ID_EXT);
+
+ in[j] = (val >> (24 - ((j % 4) << 3))) & 0xff;
+ }
+ } else if (last_cmd == CMD_PARAMETER_READ ||
+ last_cmd == CMD_PARAMETER_CHANGE_COL) {
+ u64 addr;
+ u32 offs;
+
+ for (j = 0; j < instr->ctx.data.len; j++) {
+ addr = last_addr + j;
+ offs = addr & (FC_BYTES - 1);
+
+ if (j > 0 && offs == 0)
+ nand_change_read_column_op(chip, addr, NULL, 0,
+ false);
+
+ in[j] = ctrl->flash_cache[offs];
+ }
+ }
+ } else if (instr->type == NAND_OP_WAITRDY_INSTR) {
+ ret = bcmnand_ctrl_poll_status(host, NAND_CTRL_RDY, NAND_CTRL_RDY, 0);
+ if (ret)
+ break;
+ } else {
+ dev_err(ctrl->dev, "unsupported instruction type: %d\n", instr->type);
+ ret = -EOPNOTSUPP;
+ break;
+ }
+ }
+
+ return ret;
+}
+
static int brcmnand_exec_op(struct nand_chip *chip,
const struct nand_operation *op,
bool check_only)
{
struct brcmnand_host *host = nand_get_controller_data(chip);
+ struct brcmnand_controller *ctrl = host->ctrl;
struct mtd_info *mtd = nand_to_mtd(chip);
u8 *status;
- unsigned int i;
int ret = 0;
if (check_only)
- return 0;
+ return ctrl->check_instr(chip, op);
if (brcmnand_op_is_status(op)) {
status = op->instrs[1].ctx.data.buf.in;
@@ -2525,11 +2738,7 @@ static int brcmnand_exec_op(struct nand_chip *chip,
if (op->deassert_wp)
brcmnand_wp(mtd, 0);
- for (i = 0; i < op->ninstrs; i++) {
- ret = brcmnand_exec_instr(host, i, op);
- if (ret)
- break;
- }
+ ret = ctrl->exec_instr(chip, op);
if (op->deassert_wp)
brcmnand_wp(mtd, 1);
@@ -3008,7 +3217,7 @@ static int brcmnand_resume(struct device *dev)
brcmnand_save_restore_cs_config(host, 1);
/* Reset the chip, required by some chips after power-up */
- nand_reset_op(chip);
+ nand_reset(chip, 0);
}
return 0;
@@ -3087,7 +3296,7 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
{
struct brcmnand_platform_data *pd = dev_get_platdata(&pdev->dev);
struct device *dev = &pdev->dev;
- struct device_node *dn = dev->of_node, *child;
+ struct device_node *dn = dev->of_node;
struct brcmnand_controller *ctrl;
struct brcmnand_host *host;
struct resource *res;
@@ -3142,6 +3351,15 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
if (ret)
goto err;
+ /* Only v5.0+ controllers have low level ops support */
+ if (ctrl->nand_version >= 0x0500) {
+ ctrl->check_instr = brcmnand_check_instructions;
+ ctrl->exec_instr = brcmnand_exec_instructions;
+ } else {
+ ctrl->check_instr = brcmnand_check_instructions_legacy;
+ ctrl->exec_instr = brcmnand_exec_instructions_legacy;
+ }
+
/*
* Most chips have this cache at a fixed offset within 'nand' block.
* Some must specify this region separately.
@@ -3266,12 +3484,11 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
}
}
- for_each_available_child_of_node(dn, child) {
+ for_each_available_child_of_node_scoped(dn, child) {
if (of_device_is_compatible(child, "brcm,nandcs")) {
host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
if (!host) {
- of_node_put(child);
ret = -ENOMEM;
goto err;
}
@@ -3289,10 +3506,9 @@ int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc)
ret = brcmnand_init_cs(host, NULL);
if (ret) {
- if (ret == -EPROBE_DEFER) {
- of_node_put(child);
+ if (ret == -EPROBE_DEFER)
goto err;
- }
+
devm_kfree(dev, host);
continue; /* Try all chip-selects */
}
diff --git a/drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c b/drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c
index 950923d977b7..8ed64613fda5 100644
--- a/drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c
+++ b/drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c
@@ -5,7 +5,6 @@
#include <linux/device.h>
#include <linux/module.h>
-#include <linux/mod_devicetable.h>
#include <linux/platform_device.h>
#include "brcmnand.h"
diff --git a/drivers/mtd/nand/raw/cadence-nand-controller.c b/drivers/mtd/nand/raw/cadence-nand-controller.c
index 6667eea95597..d53b35a8b3cb 100644
--- a/drivers/mtd/nand/raw/cadence-nand-controller.c
+++ b/drivers/mtd/nand/raw/cadence-nand-controller.c
@@ -199,6 +199,7 @@
/* Common settings. */
#define COMMON_SET 0x1008
+#define OPR_MODE_NVDDR BIT(0)
/* 16 bit device connected to the NAND Flash interface. */
#define COMMON_SET_DEVICE_16BIT BIT(8)
@@ -211,12 +212,20 @@
#define SKIP_BYTES_OFFSET_VALUE GENMASK(23, 0)
/* Timings configuration. */
+#define TOGGLE_TIMINGS_0 0x1014
+#define TOGGLE_TIMINGS_1 0x1018
+
#define ASYNC_TOGGLE_TIMINGS 0x101c
#define ASYNC_TOGGLE_TIMINGS_TRH GENMASK(28, 24)
#define ASYNC_TOGGLE_TIMINGS_TRP GENMASK(20, 16)
#define ASYNC_TOGGLE_TIMINGS_TWH GENMASK(12, 8)
#define ASYNC_TOGGLE_TIMINGS_TWP GENMASK(4, 0)
+#define SYNC_TIMINGS 0x1020
+#define SYNC_TCKWR GENMASK(21, 16)
+#define SYNC_TWRCK GENMASK(13, 8)
+#define SYNC_TCAD GENMASK(5, 0)
+
#define TIMINGS0 0x1024
#define TIMINGS0_TADL GENMASK(31, 24)
#define TIMINGS0_TCCS GENMASK(23, 16)
@@ -226,6 +235,7 @@
#define TIMINGS1 0x1028
#define TIMINGS1_TRHZ GENMASK(31, 24)
#define TIMINGS1_TWB GENMASK(23, 16)
+#define TIMINGS1_TCWAW GENMASK(15, 8)
#define TIMINGS1_TVDLY GENMASK(7, 0)
#define TIMINGS2 0x102c
@@ -243,14 +253,23 @@
/* Register controlling DQ related timing. */
#define PHY_DQ_TIMING 0x2000
+#define PHY_DQ_TIMING_OE_END GENMASK(2, 0)
+#define PHY_DQ_TIMING_OE_START GENMASK(6, 4)
+#define PHY_DQ_TIMING_TSEL_END GENMASK(11, 8)
+#define PHY_DQ_TIMING_TSEL_START GENMASK(15, 12)
+
/* Register controlling DSQ related timing. */
#define PHY_DQS_TIMING 0x2004
#define PHY_DQS_TIMING_DQS_SEL_OE_END GENMASK(3, 0)
+#define PHY_DQS_TIMING_DQS_SEL_OE_START GENMASK(7, 4)
+#define PHY_DQS_TIMING_DQS_SEL_TSEL_END GENMASK(11, 8)
#define PHY_DQS_TIMING_PHONY_DQS_SEL BIT(16)
#define PHY_DQS_TIMING_USE_PHONY_DQS BIT(20)
/* Register controlling the gate and loopback control related timing. */
#define PHY_GATE_LPBK_CTRL 0x2008
+#define PHY_GATE_LPBK_CTRL_GATE_CFG GENMASK(3, 0)
+#define PHY_GATE_LPBK_CTRL_GATE_CFG_CLOSE GENMASK(5, 4)
#define PHY_GATE_LPBK_CTRL_RDS GENMASK(24, 19)
/* Register holds the control for the master DLL logic. */
@@ -260,6 +279,12 @@
/* Register holds the control for the slave DLL logic. */
#define PHY_DLL_SLAVE_CTRL 0x2010
+/* Register controls the DQS related timing. */
+#define PHY_IE_TIMING 0x2014
+#define PHY_IE_TIMING_DQS_IE_START GENMASK(10, 8)
+#define PHY_IE_TIMING_DQ_IE_START GENMASK(18, 16)
+#define PHY_IE_TIMING_IE_ALWAYS_ON BIT(20)
+
/* This register handles the global control settings for the PHY. */
#define PHY_CTRL 0x2080
#define PHY_CTRL_SDR_DQS BIT(14)
@@ -375,15 +400,41 @@
#define BCH_MAX_NUM_CORR_CAPS 8
#define BCH_MAX_NUM_SECTOR_SIZES 2
+/* NVDDR mode specific parameters and register values based on cadence specs */
+#define NVDDR_PHY_RD_DELAY 29
+#define NVDDR_PHY_RD_DELAY_MAX 31
+#define NVDDR_GATE_CFG_OPT 14
+#define NVDDR_GATE_CFG_STD 7
+#define NVDDR_GATE_CFG_MAX 15
+#define NVDDR_DATA_SEL_OE_START 1
+#define NVDDR_DATA_SEL_OE_START_MAX 7
+#define NVDDR_DATA_SEL_OE_END 6
+#define NVDDR_DATA_SEL_OE_END_MIN 4
+#define NVDDR_DATA_SEL_OE_END_MAX 15
+#define NVDDR_RS_HIGH_WAIT_CNT 7
+#define NVDDR_RS_IDLE_CNT 7
+#define NVDDR_TCWAW_DELAY 250000
+#define NVDDR_TVDLY_DELAY 500000
+#define NVDDR_TOGGLE_TIMINGS_0 0x00000301
+#define NVDDR_TOGGLE_TIMINGS_1 0x0a060102
+#define NVDDR_ASYNC_TOGGLE_TIMINGS 0
+#define NVDDR_PHY_CTRL 0x00004000
+#define NVDDR_PHY_TSEL 0
+#define NVDDR_PHY_DLL_MASTER_CTRL 0x00140004
+#define NVDDR_PHY_DLL_SLAVE_CTRL 0x00003c3c
+
struct cadence_nand_timings {
u32 async_toggle_timings;
+ u32 sync_timings;
u32 timings0;
u32 timings1;
u32 timings2;
u32 dll_phy_ctrl;
u32 phy_ctrl;
+ u32 phy_dq_timing;
u32 phy_dqs_timing;
u32 phy_gate_lpbk_ctrl;
+ u32 phy_ie_timing;
};
/* Command DMA descriptor. */
@@ -1015,7 +1066,7 @@ static int cadence_nand_cdma_send(struct cdns_nand_ctrl *cdns_ctrl,
}
/* Send SDMA command and wait for finish. */
-static u32
+static int
cadence_nand_cdma_send_and_wait(struct cdns_nand_ctrl *cdns_ctrl,
u8 thread)
{
@@ -2345,11 +2396,9 @@ static inline u32 calc_tdvw(u32 trp_cnt, u32 clk_period, u32 trhoh_min,
return (trp_cnt + 1) * clk_period + trhoh_min - trea_max;
}
-static int
-cadence_nand_setup_interface(struct nand_chip *chip, int chipnr,
- const struct nand_interface_config *conf)
+static int cadence_nand_setup_sdr_interface(struct nand_chip *chip,
+ const struct nand_sdr_timings *sdr)
{
- const struct nand_sdr_timings *sdr;
struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller);
struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip);
struct cadence_nand_timings *t = &cdns_chip->timings;
@@ -2370,13 +2419,8 @@ cadence_nand_setup_interface(struct nand_chip *chip, int chipnr,
u32 dll_phy_dqs_timing = 0, phony_dqs_timing = 0, rd_del_sel = 0;
u32 sampling_point;
- sdr = nand_get_sdr_timings(conf);
- if (IS_ERR(sdr))
- return PTR_ERR(sdr);
-
memset(t, 0, sizeof(*t));
/* Sampling point calculation. */
-
if (cdns_ctrl->caps2.is_phy_type_dll)
phony_dqs_mod = 2;
else
@@ -2633,10 +2677,221 @@ cadence_nand_setup_interface(struct nand_chip *chip, int chipnr,
PHY_DLL_MASTER_CTRL_BYPASS_MODE);
dev_dbg(cdns_ctrl->dev, "PHY_DLL_SLAVE_CTRL_REG_SDR\t%x\n", 0);
}
+ return 0;
+}
+
+static int
+cadence_nand_setup_nvddr_interface(struct nand_chip *chip,
+ const struct nand_nvddr_timings *nvddr)
+{
+ struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller);
+ struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip);
+ struct cadence_nand_timings *t = &cdns_chip->timings;
+ u32 board_delay = cdns_ctrl->board_delay;
+ u32 clk_period = DIV_ROUND_DOWN_ULL(1000000000000ULL,
+ cdns_ctrl->nf_clk_rate);
+ u32 ddr_clk_ctrl_period = clk_period * 2;
+ u32 if_skew = cdns_ctrl->caps1->if_skew;
+ u32 tceh_cnt, tcs_cnt, tadl_cnt, tccs_cnt;
+ u32 twrck_cnt, tcad_cnt, tckwr_cnt = 0;
+ u32 tfeat_cnt, trhz_cnt, tvdly_cnt, tcwaw_cnt;
+ u32 trhw_cnt, twb_cnt, twhr_cnt;
+ u32 oe_start, oe_end, oe_end_dqsd;
+ u32 rd_del_sel = 0;
+ u32 dqs_driven_by_device, dqs_toogle_by_device, gate_open_delay;
+ u32 dll_phy_gate_open_delay, gate_close_delay, ie_start;
+ u32 dll_phy_rd_delay;
+ u32 reg;
+
+ memset(t, 0, sizeof(*t));
+ twrck_cnt = calc_cycl(nvddr->tWRCK_min, ddr_clk_ctrl_period);
+ tcad_cnt = calc_cycl(nvddr->tCAD_min, ddr_clk_ctrl_period);
+
+ reg = FIELD_PREP(SYNC_TWRCK, twrck_cnt);
+ reg |= FIELD_PREP(SYNC_TCAD, tcad_cnt);
+ t->sync_timings = reg;
+ dev_dbg(cdns_ctrl->dev, "SYNC_TIMINGS_NVDDR\t%08x\n", reg);
+
+ tadl_cnt = calc_cycl((nvddr->tADL_min + if_skew), ddr_clk_ctrl_period);
+ tccs_cnt = calc_cycl((nvddr->tCCS_min + if_skew), ddr_clk_ctrl_period);
+ twhr_cnt = calc_cycl((nvddr->tWHR_min + if_skew), ddr_clk_ctrl_period);
+ trhw_cnt = calc_cycl((nvddr->tRHW_min + if_skew), ddr_clk_ctrl_period);
+ reg = FIELD_PREP(TIMINGS0_TADL, tadl_cnt);
+ reg |= FIELD_PREP(TIMINGS0_TCCS, tccs_cnt);
+ reg |= FIELD_PREP(TIMINGS0_TWHR, twhr_cnt);
+ reg |= FIELD_PREP(TIMINGS0_TRHW, trhw_cnt);
+ t->timings0 = reg;
+ dev_dbg(cdns_ctrl->dev, "TIMINGS0_NVDDR\t%08x\n", reg);
+
+ twb_cnt = calc_cycl((nvddr->tWB_max + board_delay),
+ ddr_clk_ctrl_period);
+ /*
+ * Because of the two stage syncflop the value must be increased by 3
+ * first value is related with sync, second value is related
+ * with output if delay.
+ */
+ twb_cnt = twb_cnt + 3 + 5;
+ tvdly_cnt = calc_cycl(NVDDR_TVDLY_DELAY + if_skew, ddr_clk_ctrl_period);
+ tcwaw_cnt = calc_cycl(NVDDR_TCWAW_DELAY, ddr_clk_ctrl_period);
+ trhz_cnt = 1;
+ reg = FIELD_PREP(TIMINGS1_TWB, twb_cnt);
+ reg |= FIELD_PREP(TIMINGS1_TVDLY, tvdly_cnt);
+ reg |= FIELD_PREP(TIMINGS1_TRHZ, trhz_cnt);
+ reg |= FIELD_PREP(TIMINGS1_TCWAW, tcwaw_cnt);
+ t->timings1 = reg;
+ dev_dbg(cdns_ctrl->dev, "TIMINGS1_NVDDR\t%08x\n", reg);
+
+ tfeat_cnt = calc_cycl(nvddr->tFEAT_max, ddr_clk_ctrl_period);
+ if (tfeat_cnt < twb_cnt)
+ tfeat_cnt = twb_cnt;
+
+ tceh_cnt = calc_cycl(nvddr->tCEH_min, ddr_clk_ctrl_period);
+ tcs_cnt = calc_cycl((nvddr->tCS_min + if_skew), ddr_clk_ctrl_period);
+ reg = FIELD_PREP(TIMINGS2_TFEAT, tfeat_cnt);
+ reg |= FIELD_PREP(TIMINGS2_CS_HOLD_TIME, tceh_cnt);
+ reg |= FIELD_PREP(TIMINGS2_CS_SETUP_TIME, tcs_cnt);
+ t->timings2 = reg;
+ dev_dbg(cdns_ctrl->dev, "TIMINGS2_NVDDR\t%08x\n", reg);
+
+ reg = FIELD_PREP(DLL_PHY_CTRL_RS_HIGH_WAIT_CNT, NVDDR_RS_HIGH_WAIT_CNT);
+ reg |= FIELD_PREP(DLL_PHY_CTRL_RS_IDLE_CNT, NVDDR_RS_IDLE_CNT);
+ t->dll_phy_ctrl = reg;
+ dev_dbg(cdns_ctrl->dev, "DLL_PHY_CTRL_NVDDR\t%08x\n", reg);
+
+ reg = PHY_CTRL_SDR_DQS;
+ t->phy_ctrl = reg;
+ dev_dbg(cdns_ctrl->dev, "PHY_CTRL_REG_NVDDR\t%08x\n", reg);
+
+ dqs_driven_by_device = (nvddr->tDQSD_max + board_delay) / 1000 +
+ if_skew;
+ dqs_toogle_by_device = (nvddr->tDQSCK_max + board_delay) / 1000 -
+ if_skew;
+ gate_open_delay = dqs_toogle_by_device / (clk_period / 1000);
+ if (dqs_toogle_by_device > clk_period / 1000) {
+ if (gate_open_delay > NVDDR_GATE_CFG_OPT)
+ dll_phy_gate_open_delay = NVDDR_GATE_CFG_MAX;
+ else
+ dll_phy_gate_open_delay = gate_open_delay + 1;
+ gate_close_delay = 0;
+ } else {
+ twrck_cnt = calc_cycl(dqs_driven_by_device * 1000, clk_period);
+ dll_phy_gate_open_delay = 1;
+ gate_close_delay = 0;
+
+ reg = FIELD_PREP(SYNC_TCKWR, tckwr_cnt);
+ reg |= FIELD_PREP(SYNC_TWRCK, twrck_cnt);
+ reg |= FIELD_PREP(SYNC_TCAD, tcad_cnt);
+ t->sync_timings = reg;
+ dev_dbg(cdns_ctrl->dev, "SYNC_TIMINGS_NVDDR\t%08x\n", reg);
+ }
+ if (dll_phy_gate_open_delay > NVDDR_GATE_CFG_STD)
+ ie_start = NVDDR_GATE_CFG_STD;
+ else
+ ie_start = dll_phy_gate_open_delay;
+
+ dll_phy_rd_delay = ((nvddr->tDQSCK_max + board_delay) +
+ (clk_period / 2)) / clk_period;
+ if (dll_phy_rd_delay <= NVDDR_PHY_RD_DELAY)
+ rd_del_sel = dll_phy_rd_delay + 2;
+ else
+ rd_del_sel = NVDDR_PHY_RD_DELAY_MAX;
+
+ reg = FIELD_PREP(PHY_GATE_LPBK_CTRL_GATE_CFG, dll_phy_gate_open_delay);
+ reg |= FIELD_PREP(PHY_GATE_LPBK_CTRL_GATE_CFG_CLOSE, gate_close_delay);
+ reg |= FIELD_PREP(PHY_GATE_LPBK_CTRL_RDS, rd_del_sel);
+ t->phy_gate_lpbk_ctrl = reg;
+ dev_dbg(cdns_ctrl->dev, "PHY_GATE_LPBK_CTRL_REG_NVDDR\t%08x\n", reg);
+
+ oe_end_dqsd = ((nvddr->tDQSD_max / 1000) / ((clk_period / 2) / 1000))
+ + NVDDR_DATA_SEL_OE_END_MIN;
+ oe_end = (NVDDR_DATA_SEL_OE_END_MIN + oe_end_dqsd) / 2;
+ if (oe_end > NVDDR_DATA_SEL_OE_END_MAX)
+ oe_end = NVDDR_DATA_SEL_OE_END_MAX;
+
+ oe_start = ((nvddr->tDQSHZ_max / 1000) / ((clk_period / 2) / 1000)) + 1;
+ if (oe_start > NVDDR_DATA_SEL_OE_START_MAX)
+ oe_start = NVDDR_DATA_SEL_OE_START_MAX;
+
+ reg = FIELD_PREP(PHY_DQ_TIMING_OE_END, NVDDR_DATA_SEL_OE_END);
+ reg |= FIELD_PREP(PHY_DQ_TIMING_OE_START, NVDDR_DATA_SEL_OE_START);
+ reg |= FIELD_PREP(PHY_DQ_TIMING_TSEL_END, NVDDR_DATA_SEL_OE_END);
+ reg |= FIELD_PREP(PHY_DQ_TIMING_TSEL_START, NVDDR_DATA_SEL_OE_START);
+ t->phy_dq_timing = reg;
+ dev_dbg(cdns_ctrl->dev, "PHY_DQ_TIMING_REG_NVDDR\t%08x\n", reg);
+
+ reg = FIELD_PREP(PHY_DQS_TIMING_DQS_SEL_OE_END, oe_end);
+ reg |= FIELD_PREP(PHY_DQS_TIMING_DQS_SEL_OE_START, oe_start);
+ reg |= FIELD_PREP(PHY_DQS_TIMING_DQS_SEL_TSEL_END, oe_end);
+ t->phy_dqs_timing = reg;
+ dev_dbg(cdns_ctrl->dev, "PHY_DQS_TIMING_REG_NVDDR\t%08x\n", reg);
+
+ reg = FIELD_PREP(PHY_IE_TIMING_DQS_IE_START, ie_start);
+ reg |= FIELD_PREP(PHY_IE_TIMING_DQ_IE_START, ie_start);
+ reg |= FIELD_PREP(PHY_IE_TIMING_IE_ALWAYS_ON, 0);
+ t->phy_ie_timing = reg;
+ dev_dbg(cdns_ctrl->dev, "PHY_IE_TIMING_REG_NVDDR\t%08x\n", reg);
+
+ reg = readl_relaxed(cdns_ctrl->reg + DLL_PHY_CTRL);
+ reg &= ~(DLL_PHY_CTRL_DLL_RST_N |
+ DLL_PHY_CTRL_EXTENDED_RD_MODE |
+ DLL_PHY_CTRL_EXTENDED_WR_MODE);
+ writel_relaxed(reg, cdns_ctrl->reg + DLL_PHY_CTRL);
+ writel_relaxed(OPR_MODE_NVDDR, cdns_ctrl->reg + COMMON_SET);
+ writel_relaxed(NVDDR_TOGGLE_TIMINGS_0,
+ cdns_ctrl->reg + TOGGLE_TIMINGS_0);
+ writel_relaxed(NVDDR_TOGGLE_TIMINGS_1,
+ cdns_ctrl->reg + TOGGLE_TIMINGS_1);
+ writel_relaxed(NVDDR_ASYNC_TOGGLE_TIMINGS,
+ cdns_ctrl->reg + ASYNC_TOGGLE_TIMINGS);
+ writel_relaxed(t->sync_timings, cdns_ctrl->reg + SYNC_TIMINGS);
+ writel_relaxed(t->timings0, cdns_ctrl->reg + TIMINGS0);
+ writel_relaxed(t->timings1, cdns_ctrl->reg + TIMINGS1);
+ writel_relaxed(t->timings2, cdns_ctrl->reg + TIMINGS2);
+ writel_relaxed(t->dll_phy_ctrl, cdns_ctrl->reg + DLL_PHY_CTRL);
+ writel_relaxed(t->phy_ctrl, cdns_ctrl->reg + PHY_CTRL);
+ writel_relaxed(NVDDR_PHY_TSEL, cdns_ctrl->reg + PHY_TSEL);
+ writel_relaxed(t->phy_dq_timing, cdns_ctrl->reg + PHY_DQ_TIMING);
+ writel_relaxed(t->phy_dqs_timing, cdns_ctrl->reg + PHY_DQS_TIMING);
+ writel_relaxed(t->phy_gate_lpbk_ctrl,
+ cdns_ctrl->reg + PHY_GATE_LPBK_CTRL);
+ writel_relaxed(NVDDR_PHY_DLL_MASTER_CTRL,
+ cdns_ctrl->reg + PHY_DLL_MASTER_CTRL);
+ writel_relaxed(NVDDR_PHY_DLL_SLAVE_CTRL,
+ cdns_ctrl->reg + PHY_DLL_SLAVE_CTRL);
+ writel_relaxed(t->phy_ie_timing, cdns_ctrl->reg + PHY_IE_TIMING);
+ writel_relaxed((reg | DLL_PHY_CTRL_DLL_RST_N),
+ cdns_ctrl->reg + DLL_PHY_CTRL);
return 0;
}
+static int
+cadence_nand_setup_interface(struct nand_chip *chip, int chipnr,
+ const struct nand_interface_config *conf)
+{
+ int ret = 0;
+
+ if (chipnr < 0)
+ return ret;
+
+ if (nand_interface_is_sdr(conf)) {
+ const struct nand_sdr_timings *sdr = nand_get_sdr_timings(conf);
+
+ if (IS_ERR(sdr))
+ return PTR_ERR(sdr);
+
+ ret = cadence_nand_setup_sdr_interface(chip, sdr);
+ } else {
+ const struct nand_nvddr_timings *nvddr = nand_get_nvddr_timings(conf);
+
+ if (IS_ERR(nvddr))
+ return PTR_ERR(nvddr);
+
+ ret = cadence_nand_setup_nvddr_interface(chip, nvddr);
+ }
+ return ret;
+}
+
static int cadence_nand_attach_chip(struct nand_chip *chip)
{
struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller);
@@ -2871,14 +3126,14 @@ cadence_nand_irq_cleanup(int irqnum, struct cdns_nand_ctrl *cdns_ctrl)
static int cadence_nand_init(struct cdns_nand_ctrl *cdns_ctrl)
{
dma_cap_mask_t mask;
- struct dma_device *dma_dev = cdns_ctrl->dmac->device;
+ struct dma_device *dma_dev;
int ret;
cdns_ctrl->cdma_desc = dma_alloc_coherent(cdns_ctrl->dev,
sizeof(*cdns_ctrl->cdma_desc),
&cdns_ctrl->dma_cdma_desc,
GFP_KERNEL);
- if (!cdns_ctrl->dma_cdma_desc)
+ if (!cdns_ctrl->cdma_desc)
return -ENOMEM;
cdns_ctrl->buf_size = SZ_16K;
@@ -2915,6 +3170,7 @@ static int cadence_nand_init(struct cdns_nand_ctrl *cdns_ctrl)
}
}
+ dma_dev = cdns_ctrl->dmac->device;
cdns_ctrl->io.iova_dma = dma_map_resource(dma_dev->dev, cdns_ctrl->io.dma,
cdns_ctrl->io.size,
DMA_BIDIRECTIONAL, 0);
diff --git a/drivers/mtd/nand/raw/cafe_nand.c b/drivers/mtd/nand/raw/cafe_nand.c
index 66385c4fb994..f3117b031cd2 100644
--- a/drivers/mtd/nand/raw/cafe_nand.c
+++ b/drivers/mtd/nand/raw/cafe_nand.c
@@ -678,7 +678,7 @@ static int cafe_nand_probe(struct pci_dev *pdev,
pci_set_master(pdev);
- cafe = kzalloc(sizeof(*cafe), GFP_KERNEL);
+ cafe = kzalloc_obj(*cafe);
if (!cafe) {
err = -ENOMEM;
goto out_disable_device;
@@ -830,16 +830,16 @@ static void cafe_nand_remove(struct pci_dev *pdev)
}
static const struct pci_device_id cafe_nand_tbl[] = {
- { PCI_VENDOR_ID_MARVELL, PCI_DEVICE_ID_MARVELL_88ALP01_NAND,
- PCI_ANY_ID, PCI_ANY_ID },
+ { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, PCI_DEVICE_ID_MARVELL_88ALP01_NAND) },
{ }
};
MODULE_DEVICE_TABLE(pci, cafe_nand_tbl);
-static int cafe_nand_resume(struct pci_dev *pdev)
+static int cafe_nand_resume(struct device *dev)
{
uint32_t ctrl;
+ struct pci_dev *pdev = to_pci_dev(dev);
struct mtd_info *mtd = pci_get_drvdata(pdev);
struct nand_chip *chip = mtd_to_nand(mtd);
struct cafe_priv *cafe = nand_get_controller_data(chip);
@@ -877,12 +877,14 @@ static int cafe_nand_resume(struct pci_dev *pdev)
return 0;
}
+static DEFINE_SIMPLE_DEV_PM_OPS(cafe_nand_ops, NULL, cafe_nand_resume);
+
static struct pci_driver cafe_nand_pci_driver = {
.name = "CAFÉ NAND",
.id_table = cafe_nand_tbl,
.probe = cafe_nand_probe,
.remove = cafe_nand_remove,
- .resume = cafe_nand_resume,
+ .driver.pm = &cafe_nand_ops,
};
module_pci_driver(cafe_nand_pci_driver);
diff --git a/drivers/mtd/nand/raw/cs553x_nand.c b/drivers/mtd/nand/raw/cs553x_nand.c
index 341318024a19..0b872b1e3b04 100644
--- a/drivers/mtd/nand/raw/cs553x_nand.c
+++ b/drivers/mtd/nand/raw/cs553x_nand.c
@@ -273,7 +273,7 @@ static int __init cs553x_init_one(int cs, int mmio, unsigned long adr)
}
/* Allocate memory for MTD device structure and private data */
- controller = kzalloc(sizeof(*controller), GFP_KERNEL);
+ controller = kzalloc_obj(*controller);
if (!controller) {
err = -ENOMEM;
goto out;
@@ -351,20 +351,20 @@ static int __init cs553x_init(void)
return -ENXIO;
/* If it doesn't have the CS553[56], abort */
- rdmsrl(MSR_DIVIL_GLD_CAP, val);
+ rdmsrq(MSR_DIVIL_GLD_CAP, val);
val &= ~0xFFULL;
if (val != CAP_CS5535 && val != CAP_CS5536)
return -ENXIO;
/* If it doesn't have the NAND controller enabled, abort */
- rdmsrl(MSR_DIVIL_BALL_OPTS, val);
+ rdmsrq(MSR_DIVIL_BALL_OPTS, val);
if (val & PIN_OPT_IDE) {
pr_info("CS553x NAND controller: Flash I/O not enabled in MSR_DIVIL_BALL_OPTS.\n");
return -ENXIO;
}
for (i = 0; i < NR_CS553X_CONTROLLERS; i++) {
- rdmsrl(MSR_DIVIL_LBAR_FLSH0 + i, val);
+ rdmsrq(MSR_DIVIL_LBAR_FLSH0 + i, val);
if ((val & (FLSH_LBAR_EN|FLSH_NOR_NAND)) == (FLSH_LBAR_EN|FLSH_NOR_NAND))
err = cs553x_init_one(i, !!(val & FLSH_MEM_IO), val & 0xFFFFFFFF);
diff --git a/drivers/mtd/nand/raw/denali_dt.c b/drivers/mtd/nand/raw/denali_dt.c
index e0dd59bba4bd..8c822eae72e7 100644
--- a/drivers/mtd/nand/raw/denali_dt.c
+++ b/drivers/mtd/nand/raw/denali_dt.c
@@ -115,7 +115,6 @@ static int denali_dt_probe(struct platform_device *pdev)
struct denali_dt *dt;
const struct denali_dt_data *data;
struct denali_controller *denali;
- struct device_node *np;
int ret;
dt = devm_kzalloc(dev, sizeof(*dt), GFP_KERNEL);
@@ -192,12 +191,10 @@ static int denali_dt_probe(struct platform_device *pdev)
if (ret)
goto out_assert_rst;
- for_each_child_of_node(dev->of_node, np) {
+ for_each_child_of_node_scoped(dev->of_node, np) {
ret = denali_dt_chip_init(denali, np);
- if (ret) {
- of_node_put(np);
+ if (ret)
goto out_remove_denali;
- }
}
platform_set_drvdata(pdev, dt);
diff --git a/drivers/mtd/nand/raw/denali_pci.c b/drivers/mtd/nand/raw/denali_pci.c
index e22094e39546..f53df1b32dda 100644
--- a/drivers/mtd/nand/raw/denali_pci.c
+++ b/drivers/mtd/nand/raw/denali_pci.c
@@ -19,8 +19,8 @@
/* List of platforms this NAND controller has be integrated into */
static const struct pci_device_id denali_pci_ids[] = {
- { PCI_VDEVICE(INTEL, 0x0701), INTEL_CE4100 },
- { PCI_VDEVICE(INTEL, 0x0809), INTEL_MRST },
+ { PCI_VDEVICE(INTEL, 0x0701), .driver_data = INTEL_CE4100 },
+ { PCI_VDEVICE(INTEL, 0x0809), .driver_data = INTEL_MRST },
{ /* end: all zeroes */ }
};
MODULE_DEVICE_TABLE(pci, denali_pci_ids);
@@ -68,7 +68,7 @@ static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
denali->clk_rate = 50000000; /* 50 MHz */
denali->clk_x_rate = 200000000; /* 200 MHz */
- ret = pci_request_regions(dev, DENALI_NAND_NAME);
+ ret = pcim_request_all_regions(dev, DENALI_NAND_NAME);
if (ret) {
dev_err(&dev->dev, "Spectra: Unable to request memory regions\n");
return ret;
@@ -77,20 +77,18 @@ static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
denali->reg = devm_ioremap(denali->dev, csr_base, csr_len);
if (!denali->reg) {
dev_err(&dev->dev, "Spectra: Unable to remap memory region\n");
- ret = -ENOMEM;
- goto regions_release;
+ return -ENOMEM;
}
denali->host = devm_ioremap(denali->dev, mem_base, mem_len);
if (!denali->host) {
dev_err(&dev->dev, "Spectra: ioremap failed!");
- ret = -ENOMEM;
- goto regions_release;
+ return -ENOMEM;
}
ret = denali_init(denali);
if (ret)
- goto regions_release;
+ return ret;
nsels = denali->nbanks;
@@ -118,8 +116,6 @@ static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
out_remove_denali:
denali_remove(denali);
-regions_release:
- pci_release_regions(dev);
return ret;
}
@@ -127,7 +123,6 @@ static void denali_pci_remove(struct pci_dev *dev)
{
struct denali_controller *denali = pci_get_drvdata(dev);
- pci_release_regions(dev);
denali_remove(denali);
}
diff --git a/drivers/mtd/nand/raw/diskonchip.c b/drivers/mtd/nand/raw/diskonchip.c
index 70d6c2250f32..540b6baf8bb1 100644
--- a/drivers/mtd/nand/raw/diskonchip.c
+++ b/drivers/mtd/nand/raw/diskonchip.c
@@ -11,7 +11,7 @@
* Error correction code lifted from the old docecc code
* Author: Fabrice Bellard (fabrice.bellard@netgem.com)
* Copyright (C) 2000 Netgem S.A.
- * converted to the generic Reed-Solomon library by Thomas Gleixner <tglx@linutronix.de>
+ * converted to the generic Reed-Solomon library by Thomas Gleixner <tglx@kernel.org>
*
* Interface to generic NAND code for M-Systems DiskOnChip devices
*/
diff --git a/drivers/mtd/nand/raw/fsl_elbc_nand.c b/drivers/mtd/nand/raw/fsl_elbc_nand.c
index 03dbe37df021..c655d27bc4cc 100644
--- a/drivers/mtd/nand/raw/fsl_elbc_nand.c
+++ b/drivers/mtd/nand/raw/fsl_elbc_nand.c
@@ -895,13 +895,13 @@ static int fsl_elbc_nand_probe(struct platform_device *pdev)
return -ENODEV;
}
- priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ priv = kzalloc_obj(*priv);
if (!priv)
return -ENOMEM;
mutex_lock(&fsl_elbc_nand_mutex);
if (!fsl_lbc_ctrl_dev->nand) {
- elbc_fcm_ctrl = kzalloc(sizeof(*elbc_fcm_ctrl), GFP_KERNEL);
+ elbc_fcm_ctrl = kzalloc_obj(*elbc_fcm_ctrl);
if (!elbc_fcm_ctrl) {
mutex_unlock(&fsl_elbc_nand_mutex);
ret = -ENOMEM;
diff --git a/drivers/mtd/nand/raw/fsl_ifc_nand.c b/drivers/mtd/nand/raw/fsl_ifc_nand.c
index 7be95d0be248..a88ac2cfaccd 100644
--- a/drivers/mtd/nand/raw/fsl_ifc_nand.c
+++ b/drivers/mtd/nand/raw/fsl_ifc_nand.c
@@ -7,6 +7,7 @@
* Author: Dipen Dudhat <Dipen.Dudhat@freescale.com>
*/
+#include <linux/cleanup.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/types.h>
@@ -683,8 +684,15 @@ static int fsl_ifc_read_page(struct nand_chip *chip, uint8_t *buf,
return check_erased_page(chip, buf);
}
- if (ctrl->nand_stat != IFC_NAND_EVTER_STAT_OPC)
+ if (!ctrl->nand_stat) {
mtd->ecc_stats.failed++;
+ return -ETIMEDOUT;
+ }
+
+ if (ctrl->nand_stat != IFC_NAND_EVTER_STAT_OPC) {
+ mtd->ecc_stats.failed++;
+ return -EIO;
+ }
return nctrl->max_bitflips;
}
@@ -863,7 +871,14 @@ static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv)
/* Fill in fsl_ifc_mtd structure */
mtd->dev.parent = priv->dev;
- nand_set_flash_node(chip, priv->dev->of_node);
+
+ struct device_node *np __free(device_node) =
+ of_get_next_child_with_prefix(priv->dev->of_node, NULL, "nand");
+
+ if (np)
+ nand_set_flash_node(chip, np);
+ else
+ nand_set_flash_node(chip, priv->dev->of_node);
/* fill in nand_chip structure */
/* set up function call table */
@@ -1018,7 +1033,7 @@ static int fsl_ifc_nand_probe(struct platform_device *dev)
mutex_lock(&fsl_ifc_nand_mutex);
if (!fsl_ifc_ctrl_dev->nand) {
- ifc_nand_ctrl = kzalloc(sizeof(*ifc_nand_ctrl), GFP_KERNEL);
+ ifc_nand_ctrl = kzalloc_obj(*ifc_nand_ctrl);
if (!ifc_nand_ctrl) {
mutex_unlock(&fsl_ifc_nand_mutex);
return -ENOMEM;
diff --git a/drivers/mtd/nand/raw/fsmc_nand.c b/drivers/mtd/nand/raw/fsmc_nand.c
index d579d5dd60d6..b13b2b0c3f30 100644
--- a/drivers/mtd/nand/raw/fsmc_nand.c
+++ b/drivers/mtd/nand/raw/fsmc_nand.c
@@ -503,6 +503,8 @@ static int dma_xfer(struct fsmc_nand_data *host, void *buffer, int len,
dma_dev = chan->device;
dma_addr = dma_map_single(dma_dev->dev, buffer, len, direction);
+ if (dma_mapping_error(dma_dev->dev, dma_addr))
+ return -EINVAL;
if (direction == DMA_TO_DEVICE) {
dma_src = dma_addr;
@@ -874,10 +876,14 @@ static int fsmc_nand_probe_config_dt(struct platform_device *pdev,
if (!of_property_read_u32(np, "bank-width", &val)) {
if (val == 2) {
nand->options |= NAND_BUSWIDTH_16;
- } else if (val != 1) {
+ } else if (val == 1) {
+ nand->options |= NAND_BUSWIDTH_AUTO;
+ } else {
dev_err(&pdev->dev, "invalid bank-width %u\n", val);
return -EINVAL;
}
+ } else {
+ nand->options |= NAND_BUSWIDTH_AUTO;
}
if (of_property_read_bool(np, "nand-skip-bbtscan"))
diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
index d76802944453..c1f766cb225a 100644
--- a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
+++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c
@@ -5,6 +5,7 @@
* Copyright (C) 2010-2015 Freescale Semiconductor, Inc.
* Copyright (C) 2008 Embedded Alley Solutions, Inc.
*/
+#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/slab.h>
@@ -17,6 +18,7 @@
#include <linux/pm_runtime.h>
#include <linux/pinctrl/consumer.h>
#include <linux/dma/mxs-dma.h>
+#include <linux/string_choices.h>
#include "gpmi-nand.h"
#include "gpmi-regs.h"
#include "bch-regs.h"
@@ -144,6 +146,9 @@ err_clk:
return ret;
}
+#define gpmi_enable_clk(x) __gpmi_enable_clk(x, true)
+#define gpmi_disable_clk(x) __gpmi_enable_clk(x, false)
+
static int gpmi_init(struct gpmi_nand_data *this)
{
struct resources *r = &this->resources;
@@ -187,7 +192,6 @@ static int gpmi_init(struct gpmi_nand_data *this)
r->gpmi_regs + HW_GPMI_CTRL1_SET);
err_out:
- pm_runtime_mark_last_busy(this->dev);
pm_runtime_put_autosuspend(this->dev);
return ret;
}
@@ -757,7 +761,6 @@ static int bch_set_geometry(struct gpmi_nand_data *this)
ret = 0;
err_out:
- pm_runtime_mark_last_busy(this->dev);
pm_runtime_put_autosuspend(this->dev);
return ret;
@@ -2319,8 +2322,8 @@ static int gpmi_nand_attach_chip(struct nand_chip *chip)
"fsl,no-blockmark-swap"))
this->swap_block_mark = false;
}
- dev_dbg(this->dev, "Blockmark swapping %sabled\n",
- this->swap_block_mark ? "en" : "dis");
+ dev_dbg(this->dev, "Blockmark swapping %s\n",
+ str_enabled_disabled(this->swap_block_mark));
ret = gpmi_init_last(this);
if (ret)
@@ -2663,7 +2666,6 @@ unmap:
this->bch = false;
out_pm:
- pm_runtime_mark_last_busy(this->dev);
pm_runtime_put_autosuspend(this->dev);
return ret;
@@ -2687,7 +2689,15 @@ static int gpmi_nand_init(struct gpmi_nand_data *this)
/* init the nand_chip{}, we don't support a 16-bit NAND Flash bus. */
nand_set_controller_data(chip, this);
- nand_set_flash_node(chip, this->pdev->dev.of_node);
+
+ struct device_node *np __free(device_node) =
+ of_get_next_child_with_prefix(this->pdev->dev.of_node, NULL, "nand");
+
+ if (np)
+ nand_set_flash_node(chip, np);
+ else
+ nand_set_flash_node(chip, this->pdev->dev.of_node);
+
chip->legacy.block_markbad = gpmi_block_markbad;
chip->badblock_pattern = &gpmi_bbt_descr;
chip->options |= NAND_NO_SUBPAGE_WRITE;
@@ -2764,6 +2774,11 @@ static int gpmi_nand_probe(struct platform_device *pdev)
pm_runtime_enable(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, 500);
pm_runtime_use_autosuspend(&pdev->dev);
+#ifndef CONFIG_PM
+ ret = gpmi_enable_clk(this);
+ if (ret)
+ goto exit_acquire_resources;
+#endif
ret = gpmi_init(this);
if (ret)
@@ -2799,6 +2814,9 @@ static void gpmi_nand_remove(struct platform_device *pdev)
release_resources(this);
pm_runtime_dont_use_autosuspend(&pdev->dev);
pm_runtime_disable(&pdev->dev);
+#ifndef CONFIG_PM
+ gpmi_disable_clk(this);
+#endif
}
static int gpmi_pm_suspend(struct device *dev)
@@ -2845,9 +2863,6 @@ static int gpmi_pm_resume(struct device *dev)
return 0;
}
-#define gpmi_enable_clk(x) __gpmi_enable_clk(x, true)
-#define gpmi_disable_clk(x) __gpmi_enable_clk(x, false)
-
static int gpmi_runtime_suspend(struct device *dev)
{
struct gpmi_nand_data *this = dev_get_drvdata(dev);
diff --git a/drivers/mtd/nand/raw/ingenic/ingenic_ecc.c b/drivers/mtd/nand/raw/ingenic/ingenic_ecc.c
index 525c34c281b6..beb033705cf3 100644
--- a/drivers/mtd/nand/raw/ingenic/ingenic_ecc.c
+++ b/drivers/mtd/nand/raw/ingenic/ingenic_ecc.c
@@ -67,6 +67,7 @@ static struct ingenic_ecc *ingenic_ecc_get(struct device_node *np)
{
struct platform_device *pdev;
struct ingenic_ecc *ecc;
+ int ret;
pdev = of_find_device_by_node(np);
if (!pdev)
@@ -78,7 +79,11 @@ static struct ingenic_ecc *ingenic_ecc_get(struct device_node *np)
}
ecc = platform_get_drvdata(pdev);
- clk_prepare_enable(ecc->clk);
+ ret = clk_prepare_enable(ecc->clk);
+ if (ret) {
+ put_device(&pdev->dev);
+ return ERR_PTR(ret);
+ }
return ecc;
}
diff --git a/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c b/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c
index 47dc3efcee92..f1e2c82936b3 100644
--- a/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c
+++ b/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c
@@ -438,7 +438,6 @@ static int ingenic_nand_init_chips(struct ingenic_nfc *nfc,
struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct device_node *np;
int i = 0;
int ret;
int num_chips = of_get_child_count(dev->of_node);
@@ -449,11 +448,10 @@ static int ingenic_nand_init_chips(struct ingenic_nfc *nfc,
return -EINVAL;
}
- for_each_child_of_node(dev->of_node, np) {
+ for_each_child_of_node_scoped(dev->of_node, np) {
ret = ingenic_nand_init_chip(pdev, nfc, np, i);
if (ret) {
ingenic_nand_cleanup_chips(nfc);
- of_node_put(np);
return ret;
}
diff --git a/drivers/mtd/nand/raw/loongson-nand-controller.c b/drivers/mtd/nand/raw/loongson-nand-controller.c
new file mode 100644
index 000000000000..8490412d5be1
--- /dev/null
+++ b/drivers/mtd/nand/raw/loongson-nand-controller.c
@@ -0,0 +1,1024 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * NAND Controller Driver for Loongson family chips
+ *
+ * Copyright (C) 2015-2025 Keguang Zhang <keguang.zhang@gmail.com>
+ * Copyright (C) 2025 Binbin Zhou <zhoubinbin@loongson.cn>
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/iopoll.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/rawnand.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/sizes.h>
+
+/* Loongson NAND Controller Registers */
+#define LOONGSON_NAND_CMD 0x0
+#define LOONGSON_NAND_ADDR1 0x4
+#define LOONGSON_NAND_ADDR2 0x8
+#define LOONGSON_NAND_TIMING 0xc
+#define LOONGSON_NAND_IDL 0x10
+#define LOONGSON_NAND_IDH_STATUS 0x14
+#define LOONGSON_NAND_PARAM 0x18
+#define LOONGSON_NAND_OP_NUM 0x1c
+#define LOONGSON_NAND_CS_RDY_MAP 0x20
+
+/* Bitfields of nand command register */
+#define LOONGSON_NAND_CMD_OP_DONE BIT(10)
+#define LOONGSON_NAND_CMD_OP_SPARE BIT(9)
+#define LOONGSON_NAND_CMD_OP_MAIN BIT(8)
+#define LOONGSON_NAND_CMD_STATUS BIT(7)
+#define LOONGSON_NAND_CMD_RESET BIT(6)
+#define LOONGSON_NAND_CMD_READID BIT(5)
+#define LOONGSON_NAND_CMD_BLOCKS_ERASE BIT(4)
+#define LOONGSON_NAND_CMD_ERASE BIT(3)
+#define LOONGSON_NAND_CMD_WRITE BIT(2)
+#define LOONGSON_NAND_CMD_READ BIT(1)
+#define LOONGSON_NAND_CMD_VALID BIT(0)
+
+/* Bitfields of nand cs/rdy map register */
+#define LOONGSON_NAND_MAP_CS1_SEL GENMASK(11, 8)
+#define LOONGSON_NAND_MAP_RDY1_SEL GENMASK(15, 12)
+#define LOONGSON_NAND_MAP_CS2_SEL GENMASK(19, 16)
+#define LOONGSON_NAND_MAP_RDY2_SEL GENMASK(23, 20)
+#define LOONGSON_NAND_MAP_CS3_SEL GENMASK(27, 24)
+#define LOONGSON_NAND_MAP_RDY3_SEL GENMASK(31, 28)
+
+#define LOONGSON_NAND_CS_SEL0 BIT(0)
+#define LOONGSON_NAND_CS_SEL1 BIT(1)
+#define LOONGSON_NAND_CS_SEL2 BIT(2)
+#define LOONGSON_NAND_CS_SEL3 BIT(3)
+#define LOONGSON_NAND_CS_RDY0 BIT(0)
+#define LOONGSON_NAND_CS_RDY1 BIT(1)
+#define LOONGSON_NAND_CS_RDY2 BIT(2)
+#define LOONGSON_NAND_CS_RDY3 BIT(3)
+
+/* Bitfields of nand timing register */
+#define LOONGSON_NAND_WAIT_CYCLE_MASK GENMASK(7, 0)
+#define LOONGSON_NAND_HOLD_CYCLE_MASK GENMASK(15, 8)
+
+/* Bitfields of nand parameter register */
+#define LOONGSON_NAND_CELL_SIZE_MASK GENMASK(11, 8)
+
+#define LOONGSON_NAND_COL_ADDR_CYC 2U
+#define LOONGSON_NAND_MAX_ADDR_CYC 5U
+
+#define LOONGSON_NAND_READ_ID_SLEEP_US 1000
+#define LOONGSON_NAND_READ_ID_TIMEOUT_US 5000
+
+#define BITS_PER_WORD (4 * BITS_PER_BYTE)
+
+/* Loongson-2K1000 NAND DMA routing register */
+#define LS2K1000_NAND_DMA_MASK GENMASK(2, 0)
+#define LS2K1000_DMA0_CONF 0x0
+#define LS2K1000_DMA1_CONF 0x1
+#define LS2K1000_DMA2_CONF 0x2
+#define LS2K1000_DMA3_CONF 0x3
+#define LS2K1000_DMA4_CONF 0x4
+
+struct loongson_nand_host;
+
+struct loongson_nand_op {
+ char addrs[LOONGSON_NAND_MAX_ADDR_CYC];
+ unsigned int naddrs;
+ unsigned int addrs_offset;
+ unsigned int aligned_offset;
+ unsigned int cmd_reg;
+ unsigned int row_start;
+ unsigned int rdy_timeout_ms;
+ unsigned int orig_len;
+ bool is_readid;
+ bool is_erase;
+ bool is_write;
+ bool is_read;
+ bool is_change_column;
+ size_t len;
+ char *buf;
+};
+
+struct loongson_nand_data {
+ unsigned int max_id_cycle;
+ unsigned int id_cycle_field;
+ unsigned int status_field;
+ unsigned int op_scope_field;
+ unsigned int hold_cycle;
+ unsigned int wait_cycle;
+ unsigned int nand_cs;
+ unsigned int dma_bits;
+ int (*dma_config)(struct device *dev);
+ void (*set_addr)(struct loongson_nand_host *host, struct loongson_nand_op *op);
+};
+
+struct loongson_nand_host {
+ struct device *dev;
+ struct nand_chip chip;
+ struct nand_controller controller;
+ const struct loongson_nand_data *data;
+ unsigned int addr_cs_field;
+ void __iomem *reg_base;
+ struct regmap *regmap;
+ /* DMA Engine stuff */
+ dma_addr_t dma_base;
+ struct dma_chan *dma_chan;
+ dma_cookie_t dma_cookie;
+ struct completion dma_complete;
+};
+
+static const struct regmap_config loongson_nand_regmap_config = {
+ .reg_bits = 32,
+ .val_bits = 32,
+ .reg_stride = 4,
+};
+
+static int loongson_nand_op_cmd_mapping(struct nand_chip *chip, struct loongson_nand_op *op,
+ u8 opcode)
+{
+ struct loongson_nand_host *host = nand_get_controller_data(chip);
+
+ op->row_start = chip->page_shift + 1;
+
+ /* The controller abstracts the following NAND operations. */
+ switch (opcode) {
+ case NAND_CMD_STATUS:
+ op->cmd_reg = LOONGSON_NAND_CMD_STATUS;
+ break;
+ case NAND_CMD_RESET:
+ op->cmd_reg = LOONGSON_NAND_CMD_RESET;
+ break;
+ case NAND_CMD_READID:
+ op->is_readid = true;
+ op->cmd_reg = LOONGSON_NAND_CMD_READID;
+ break;
+ case NAND_CMD_ERASE1:
+ op->is_erase = true;
+ op->addrs_offset = LOONGSON_NAND_COL_ADDR_CYC;
+ break;
+ case NAND_CMD_ERASE2:
+ if (!op->is_erase)
+ return -EOPNOTSUPP;
+ /* During erasing, row_start differs from the default value. */
+ op->row_start = chip->page_shift;
+ op->cmd_reg = LOONGSON_NAND_CMD_ERASE;
+ break;
+ case NAND_CMD_SEQIN:
+ op->is_write = true;
+ break;
+ case NAND_CMD_PAGEPROG:
+ if (!op->is_write)
+ return -EOPNOTSUPP;
+ op->cmd_reg = LOONGSON_NAND_CMD_WRITE;
+ break;
+ case NAND_CMD_READ0:
+ op->is_read = true;
+ break;
+ case NAND_CMD_READSTART:
+ if (!op->is_read)
+ return -EOPNOTSUPP;
+ op->cmd_reg = LOONGSON_NAND_CMD_READ;
+ break;
+ case NAND_CMD_RNDOUT:
+ op->is_change_column = true;
+ break;
+ case NAND_CMD_RNDOUTSTART:
+ if (!op->is_change_column)
+ return -EOPNOTSUPP;
+ op->cmd_reg = LOONGSON_NAND_CMD_READ;
+ break;
+ default:
+ dev_dbg(host->dev, "unsupported opcode: %u\n", opcode);
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int loongson_nand_parse_instructions(struct nand_chip *chip, const struct nand_subop *subop,
+ struct loongson_nand_op *op)
+{
+ unsigned int op_id;
+ int ret;
+
+ for (op_id = 0; op_id < subop->ninstrs; op_id++) {
+ const struct nand_op_instr *instr = &subop->instrs[op_id];
+ unsigned int offset, naddrs;
+ const u8 *addrs;
+
+ switch (instr->type) {
+ case NAND_OP_CMD_INSTR:
+ ret = loongson_nand_op_cmd_mapping(chip, op, instr->ctx.cmd.opcode);
+ if (ret < 0)
+ return ret;
+
+ break;
+ case NAND_OP_ADDR_INSTR:
+ naddrs = nand_subop_get_num_addr_cyc(subop, op_id);
+ if (naddrs > LOONGSON_NAND_MAX_ADDR_CYC)
+ return -EOPNOTSUPP;
+ op->naddrs = naddrs;
+ offset = nand_subop_get_addr_start_off(subop, op_id);
+ addrs = &instr->ctx.addr.addrs[offset];
+ memcpy(op->addrs + op->addrs_offset, addrs, naddrs);
+ break;
+ case NAND_OP_DATA_IN_INSTR:
+ case NAND_OP_DATA_OUT_INSTR:
+ offset = nand_subop_get_data_start_off(subop, op_id);
+ op->orig_len = nand_subop_get_data_len(subop, op_id);
+ if (instr->type == NAND_OP_DATA_IN_INSTR)
+ op->buf = instr->ctx.data.buf.in + offset;
+ else if (instr->type == NAND_OP_DATA_OUT_INSTR)
+ op->buf = (void *)instr->ctx.data.buf.out + offset;
+
+ break;
+ case NAND_OP_WAITRDY_INSTR:
+ op->rdy_timeout_ms = instr->ctx.waitrdy.timeout_ms;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static void loongson_nand_set_addr_cs(struct loongson_nand_host *host)
+{
+ struct nand_chip *chip = &host->chip;
+ struct mtd_info *mtd = nand_to_mtd(chip);
+
+ if (!host->data->nand_cs)
+ return;
+
+ /*
+ * The Manufacturer/Chip ID read operation precedes attach_chip, at which point
+ * information such as NAND chip selection and capacity is unknown. As a
+ * workaround, we use 128MB cellsize (2KB pagesize) as a fallback.
+ */
+ if (!mtd->writesize)
+ host->addr_cs_field = GENMASK(17, 16);
+
+ regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR2, host->addr_cs_field,
+ host->data->nand_cs << __ffs(host->addr_cs_field));
+}
+
+static void ls1b_nand_set_addr(struct loongson_nand_host *host, struct loongson_nand_op *op)
+{
+ struct nand_chip *chip = &host->chip;
+ int i;
+
+ for (i = 0; i < LOONGSON_NAND_MAX_ADDR_CYC; i++) {
+ int shift, mask, val;
+
+ if (i < LOONGSON_NAND_COL_ADDR_CYC) {
+ shift = i * BITS_PER_BYTE;
+ mask = (u32)0xff << shift;
+ mask &= GENMASK(chip->page_shift, 0);
+ val = (u32)op->addrs[i] << shift;
+ regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR1, mask, val);
+ } else if (!op->is_change_column) {
+ shift = op->row_start + (i - LOONGSON_NAND_COL_ADDR_CYC) * BITS_PER_BYTE;
+ mask = (u32)0xff << shift;
+ val = (u32)op->addrs[i] << shift;
+ regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR1, mask, val);
+
+ if (i == 4) {
+ mask = (u32)0xff >> (BITS_PER_WORD - shift);
+ val = (u32)op->addrs[i] >> (BITS_PER_WORD - shift);
+ regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR2, mask, val);
+ }
+ }
+ }
+}
+
+static void ls1c_nand_set_addr(struct loongson_nand_host *host, struct loongson_nand_op *op)
+{
+ int i;
+
+ for (i = 0; i < LOONGSON_NAND_MAX_ADDR_CYC; i++) {
+ int shift, mask, val;
+
+ if (i < LOONGSON_NAND_COL_ADDR_CYC) {
+ shift = i * BITS_PER_BYTE;
+ mask = (u32)0xff << shift;
+ val = (u32)op->addrs[i] << shift;
+ regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR1, mask, val);
+ } else if (!op->is_change_column) {
+ shift = (i - LOONGSON_NAND_COL_ADDR_CYC) * BITS_PER_BYTE;
+ mask = (u32)0xff << shift;
+ val = (u32)op->addrs[i] << shift;
+ regmap_update_bits(host->regmap, LOONGSON_NAND_ADDR2, mask, val);
+ }
+ }
+
+ loongson_nand_set_addr_cs(host);
+}
+
+static void loongson_nand_trigger_op(struct loongson_nand_host *host, struct loongson_nand_op *op)
+{
+ struct nand_chip *chip = &host->chip;
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ int col0 = op->addrs[0];
+ short col;
+
+ if (!IS_ALIGNED(col0, chip->buf_align)) {
+ col0 = ALIGN_DOWN(op->addrs[0], chip->buf_align);
+ op->aligned_offset = op->addrs[0] - col0;
+ op->addrs[0] = col0;
+ }
+
+ if (host->data->set_addr)
+ host->data->set_addr(host, op);
+
+ /* set operation length */
+ if (op->is_write || op->is_read || op->is_change_column)
+ op->len = ALIGN(op->orig_len + op->aligned_offset, chip->buf_align);
+ else if (op->is_erase)
+ op->len = 1;
+ else
+ op->len = op->orig_len;
+
+ writel(op->len, host->reg_base + LOONGSON_NAND_OP_NUM);
+
+ /* set operation area and scope */
+ col = op->addrs[1] << BITS_PER_BYTE | op->addrs[0];
+ if (op->orig_len && !op->is_readid) {
+ unsigned int op_scope = 0;
+
+ if (col < mtd->writesize) {
+ op->cmd_reg |= LOONGSON_NAND_CMD_OP_MAIN;
+ op_scope = mtd->writesize;
+ }
+
+ op->cmd_reg |= LOONGSON_NAND_CMD_OP_SPARE;
+ op_scope += mtd->oobsize;
+
+ op_scope <<= __ffs(host->data->op_scope_field);
+ regmap_update_bits(host->regmap, LOONGSON_NAND_PARAM,
+ host->data->op_scope_field, op_scope);
+ }
+
+ /* set command */
+ writel(op->cmd_reg, host->reg_base + LOONGSON_NAND_CMD);
+
+ /* trigger operation */
+ regmap_write_bits(host->regmap, LOONGSON_NAND_CMD, LOONGSON_NAND_CMD_VALID,
+ LOONGSON_NAND_CMD_VALID);
+}
+
+static int loongson_nand_wait_for_op_done(struct loongson_nand_host *host,
+ struct loongson_nand_op *op)
+{
+ unsigned int val;
+ int ret = 0;
+
+ if (op->rdy_timeout_ms) {
+ ret = regmap_read_poll_timeout(host->regmap, LOONGSON_NAND_CMD,
+ val, val & LOONGSON_NAND_CMD_OP_DONE,
+ 0, op->rdy_timeout_ms * MSEC_PER_SEC);
+ if (ret)
+ dev_err(host->dev, "operation failed\n");
+ }
+
+ return ret;
+}
+
+static void loongson_nand_dma_callback(void *data)
+{
+ struct loongson_nand_host *host = (struct loongson_nand_host *)data;
+ struct dma_chan *chan = host->dma_chan;
+ struct device *dev = chan->device->dev;
+ enum dma_status status;
+
+ status = dmaengine_tx_status(chan, host->dma_cookie, NULL);
+ if (likely(status == DMA_COMPLETE)) {
+ dev_dbg(dev, "DMA complete with cookie=%d\n", host->dma_cookie);
+ complete(&host->dma_complete);
+ } else {
+ dev_err(dev, "DMA error with cookie=%d\n", host->dma_cookie);
+ }
+}
+
+static int loongson_nand_dma_transfer(struct loongson_nand_host *host, struct loongson_nand_op *op)
+{
+ struct nand_chip *chip = &host->chip;
+ struct dma_chan *chan = host->dma_chan;
+ struct device *dev = chan->device->dev;
+ struct dma_async_tx_descriptor *desc;
+ enum dma_data_direction data_dir = op->is_write ? DMA_TO_DEVICE : DMA_FROM_DEVICE;
+ enum dma_transfer_direction xfer_dir = op->is_write ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM;
+ void *buf = op->buf;
+ char *dma_buf = NULL;
+ dma_addr_t dma_addr;
+ int ret;
+
+ if (IS_ALIGNED((uintptr_t)buf, chip->buf_align) &&
+ IS_ALIGNED(op->orig_len, chip->buf_align)) {
+ dma_addr = dma_map_single(dev, buf, op->orig_len, data_dir);
+ if (dma_mapping_error(dev, dma_addr)) {
+ dev_err(dev, "failed to map DMA buffer\n");
+ return -ENXIO;
+ }
+ } else if (!op->is_write) {
+ dma_buf = dma_alloc_coherent(dev, op->len, &dma_addr, GFP_KERNEL);
+ if (!dma_buf)
+ return -ENOMEM;
+ } else {
+ dev_err(dev, "subpage writing not supported\n");
+ return -EOPNOTSUPP;
+ }
+
+ desc = dmaengine_prep_slave_single(chan, dma_addr, op->len, xfer_dir, DMA_PREP_INTERRUPT);
+ if (!desc) {
+ dev_err(dev, "failed to prepare DMA descriptor\n");
+ ret = -ENOMEM;
+ goto err;
+ }
+ desc->callback = loongson_nand_dma_callback;
+ desc->callback_param = host;
+
+ host->dma_cookie = dmaengine_submit(desc);
+ ret = dma_submit_error(host->dma_cookie);
+ if (ret) {
+ dev_err(dev, "failed to submit DMA descriptor\n");
+ goto err;
+ }
+
+ dev_dbg(dev, "issue DMA with cookie=%d\n", host->dma_cookie);
+ dma_async_issue_pending(chan);
+
+ if (!wait_for_completion_timeout(&host->dma_complete, msecs_to_jiffies(1000))) {
+ dmaengine_terminate_sync(chan);
+ reinit_completion(&host->dma_complete);
+ ret = -ETIMEDOUT;
+ goto err;
+ }
+
+ if (dma_buf)
+ memcpy(buf, dma_buf + op->aligned_offset, op->orig_len);
+err:
+ if (dma_buf)
+ dma_free_coherent(dev, op->len, dma_buf, dma_addr);
+ else
+ dma_unmap_single(dev, dma_addr, op->orig_len, data_dir);
+
+ return ret;
+}
+
+static int loongson_nand_data_type_exec(struct nand_chip *chip, const struct nand_subop *subop)
+{
+ struct loongson_nand_host *host = nand_get_controller_data(chip);
+ struct loongson_nand_op op = {};
+ int ret;
+
+ ret = loongson_nand_parse_instructions(chip, subop, &op);
+ if (ret)
+ return ret;
+
+ loongson_nand_trigger_op(host, &op);
+
+ ret = loongson_nand_dma_transfer(host, &op);
+ if (ret)
+ return ret;
+
+ return loongson_nand_wait_for_op_done(host, &op);
+}
+
+static int loongson_nand_misc_type_exec(struct nand_chip *chip, const struct nand_subop *subop,
+ struct loongson_nand_op *op)
+{
+ struct loongson_nand_host *host = nand_get_controller_data(chip);
+ int ret;
+
+ ret = loongson_nand_parse_instructions(chip, subop, op);
+ if (ret)
+ return ret;
+
+ loongson_nand_trigger_op(host, op);
+
+ return loongson_nand_wait_for_op_done(host, op);
+}
+
+static int loongson_nand_zerolen_type_exec(struct nand_chip *chip, const struct nand_subop *subop)
+{
+ struct loongson_nand_op op = {};
+
+ return loongson_nand_misc_type_exec(chip, subop, &op);
+}
+
+static int loongson_nand_read_id_type_exec(struct nand_chip *chip, const struct nand_subop *subop)
+{
+ struct loongson_nand_host *host = nand_get_controller_data(chip);
+ struct loongson_nand_op op = {};
+ int i, ret;
+ union {
+ char ids[6];
+ struct {
+ int idl;
+ u16 idh;
+ };
+ } nand_id;
+
+ ret = loongson_nand_misc_type_exec(chip, subop, &op);
+ if (ret)
+ return ret;
+
+ ret = regmap_read_poll_timeout(host->regmap, LOONGSON_NAND_IDL, nand_id.idl, nand_id.idl,
+ LOONGSON_NAND_READ_ID_SLEEP_US,
+ LOONGSON_NAND_READ_ID_TIMEOUT_US);
+ if (ret)
+ return ret;
+
+ nand_id.idh = readw(host->reg_base + LOONGSON_NAND_IDH_STATUS);
+
+ for (i = 0; i < min(host->data->max_id_cycle, op.orig_len); i++)
+ op.buf[i] = nand_id.ids[host->data->max_id_cycle - 1 - i];
+
+ return ret;
+}
+
+static int loongson_nand_read_status_type_exec(struct nand_chip *chip,
+ const struct nand_subop *subop)
+{
+ struct loongson_nand_host *host = nand_get_controller_data(chip);
+ struct loongson_nand_op op = {};
+ int val, ret;
+
+ ret = loongson_nand_misc_type_exec(chip, subop, &op);
+ if (ret)
+ return ret;
+
+ val = readl(host->reg_base + LOONGSON_NAND_IDH_STATUS);
+ val &= ~host->data->status_field;
+ op.buf[0] = val << ffs(host->data->status_field);
+
+ return ret;
+}
+
+static const struct nand_op_parser loongson_nand_op_parser = NAND_OP_PARSER(
+ NAND_OP_PARSER_PATTERN(
+ loongson_nand_read_id_type_exec,
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_ADDR_ELEM(false, LOONGSON_NAND_MAX_ADDR_CYC),
+ NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 8)),
+ NAND_OP_PARSER_PATTERN(
+ loongson_nand_read_status_type_exec,
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 1)),
+ NAND_OP_PARSER_PATTERN(
+ loongson_nand_zerolen_type_exec,
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)),
+ NAND_OP_PARSER_PATTERN(
+ loongson_nand_zerolen_type_exec,
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_ADDR_ELEM(false, LOONGSON_NAND_MAX_ADDR_CYC),
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)),
+ NAND_OP_PARSER_PATTERN(
+ loongson_nand_data_type_exec,
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_ADDR_ELEM(false, LOONGSON_NAND_MAX_ADDR_CYC),
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_WAITRDY_ELEM(true),
+ NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 0)),
+ NAND_OP_PARSER_PATTERN(
+ loongson_nand_data_type_exec,
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_ADDR_ELEM(false, LOONGSON_NAND_MAX_ADDR_CYC),
+ NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 0),
+ NAND_OP_PARSER_PAT_CMD_ELEM(false),
+ NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)),
+ );
+
+static int loongson_nand_is_valid_cmd(u8 opcode)
+{
+ if (opcode == NAND_CMD_STATUS || opcode == NAND_CMD_RESET || opcode == NAND_CMD_READID)
+ return 0;
+
+ return -EOPNOTSUPP;
+}
+
+static int loongson_nand_is_valid_cmd_seq(u8 opcode1, u8 opcode2)
+{
+ if (opcode1 == NAND_CMD_RNDOUT && opcode2 == NAND_CMD_RNDOUTSTART)
+ return 0;
+
+ if (opcode1 == NAND_CMD_READ0 && opcode2 == NAND_CMD_READSTART)
+ return 0;
+
+ if (opcode1 == NAND_CMD_ERASE1 && opcode2 == NAND_CMD_ERASE2)
+ return 0;
+
+ if (opcode1 == NAND_CMD_SEQIN && opcode2 == NAND_CMD_PAGEPROG)
+ return 0;
+
+ return -EOPNOTSUPP;
+}
+
+static int loongson_nand_check_op(struct nand_chip *chip, const struct nand_operation *op)
+{
+ const struct nand_op_instr *instr1 = NULL, *instr2 = NULL;
+ int op_id;
+
+ for (op_id = 0; op_id < op->ninstrs; op_id++) {
+ const struct nand_op_instr *instr = &op->instrs[op_id];
+
+ if (instr->type == NAND_OP_CMD_INSTR) {
+ if (!instr1)
+ instr1 = instr;
+ else if (!instr2)
+ instr2 = instr;
+ else
+ break;
+ }
+ }
+
+ if (!instr1)
+ return -EOPNOTSUPP;
+
+ if (!instr2)
+ return loongson_nand_is_valid_cmd(instr1->ctx.cmd.opcode);
+
+ return loongson_nand_is_valid_cmd_seq(instr1->ctx.cmd.opcode, instr2->ctx.cmd.opcode);
+}
+
+static int loongson_nand_exec_op(struct nand_chip *chip, const struct nand_operation *op,
+ bool check_only)
+{
+ if (check_only)
+ return loongson_nand_check_op(chip, op);
+
+ return nand_op_parser_exec_op(chip, &loongson_nand_op_parser, op, check_only);
+}
+
+static int loongson_nand_get_chip_capacity(struct nand_chip *chip)
+{
+ struct loongson_nand_host *host = nand_get_controller_data(chip);
+ u64 chipsize = nanddev_target_size(&chip->base);
+ struct mtd_info *mtd = nand_to_mtd(chip);
+
+ switch (mtd->writesize) {
+ case SZ_512:
+ switch (chipsize) {
+ case SZ_8M:
+ host->addr_cs_field = GENMASK(15, 14);
+ return 0x9;
+ case SZ_16M:
+ host->addr_cs_field = GENMASK(16, 15);
+ return 0xa;
+ case SZ_32M:
+ host->addr_cs_field = GENMASK(17, 16);
+ return 0xb;
+ case SZ_64M:
+ host->addr_cs_field = GENMASK(18, 17);
+ return 0xc;
+ case SZ_128M:
+ host->addr_cs_field = GENMASK(19, 18);
+ return 0xd;
+ }
+ break;
+ case SZ_2K:
+ switch (chipsize) {
+ case SZ_128M:
+ host->addr_cs_field = GENMASK(17, 16);
+ return 0x0;
+ case SZ_256M:
+ host->addr_cs_field = GENMASK(18, 17);
+ return 0x1;
+ case SZ_512M:
+ host->addr_cs_field = GENMASK(19, 18);
+ return 0x2;
+ case SZ_1G:
+ host->addr_cs_field = GENMASK(20, 19);
+ return 0x3;
+ }
+ break;
+ case SZ_4K:
+ if (chipsize == SZ_2G) {
+ host->addr_cs_field = GENMASK(20, 19);
+ return 0x4;
+ }
+ break;
+ case SZ_8K:
+ switch (chipsize) {
+ case SZ_4G:
+ host->addr_cs_field = GENMASK(20, 19);
+ return 0x5;
+ case SZ_8G:
+ host->addr_cs_field = GENMASK(21, 20);
+ return 0x6;
+ case SZ_16G:
+ host->addr_cs_field = GENMASK(22, 21);
+ return 0x7;
+ }
+ break;
+ }
+
+ dev_err(host->dev, "Unsupported chip size: %llu MB with page size %u B\n",
+ chipsize, mtd->writesize);
+ return -EINVAL;
+}
+
+static int loongson_nand_attach_chip(struct nand_chip *chip)
+{
+ struct loongson_nand_host *host = nand_get_controller_data(chip);
+ int cell_size = loongson_nand_get_chip_capacity(chip);
+
+ if (cell_size < 0)
+ return cell_size;
+
+ switch (chip->ecc.engine_type) {
+ case NAND_ECC_ENGINE_TYPE_NONE:
+ break;
+ case NAND_ECC_ENGINE_TYPE_SOFT:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* set cell size */
+ regmap_update_bits(host->regmap, LOONGSON_NAND_PARAM, LOONGSON_NAND_CELL_SIZE_MASK,
+ FIELD_PREP(LOONGSON_NAND_CELL_SIZE_MASK, cell_size));
+
+ regmap_update_bits(host->regmap, LOONGSON_NAND_TIMING, LOONGSON_NAND_HOLD_CYCLE_MASK,
+ FIELD_PREP(LOONGSON_NAND_HOLD_CYCLE_MASK, host->data->hold_cycle));
+
+ regmap_update_bits(host->regmap, LOONGSON_NAND_TIMING, LOONGSON_NAND_WAIT_CYCLE_MASK,
+ FIELD_PREP(LOONGSON_NAND_WAIT_CYCLE_MASK, host->data->wait_cycle));
+
+ chip->ecc.read_page_raw = nand_monolithic_read_page_raw;
+ chip->ecc.write_page_raw = nand_monolithic_write_page_raw;
+
+ return 0;
+}
+
+static const struct nand_controller_ops loongson_nand_controller_ops = {
+ .exec_op = loongson_nand_exec_op,
+ .attach_chip = loongson_nand_attach_chip,
+};
+
+static void loongson_nand_controller_cleanup(struct loongson_nand_host *host)
+{
+ if (host->dma_chan)
+ dma_release_channel(host->dma_chan);
+}
+
+static int ls2k1000_nand_apbdma_config(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ void __iomem *regs;
+ int val;
+
+ regs = devm_platform_ioremap_resource_byname(pdev, "dma-config");
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ val = readl(regs);
+ val |= FIELD_PREP(LS2K1000_NAND_DMA_MASK, LS2K1000_DMA0_CONF);
+ writel(val, regs);
+
+ return 0;
+}
+
+static int loongson_nand_controller_init(struct loongson_nand_host *host)
+{
+ struct device *dev = host->dev;
+ struct dma_chan *chan;
+ struct dma_slave_config cfg = {};
+ int ret, val;
+
+ host->regmap = devm_regmap_init_mmio(dev, host->reg_base, &loongson_nand_regmap_config);
+ if (IS_ERR(host->regmap))
+ return dev_err_probe(dev, PTR_ERR(host->regmap), "failed to init regmap\n");
+
+ if (host->data->id_cycle_field)
+ regmap_update_bits(host->regmap, LOONGSON_NAND_PARAM, host->data->id_cycle_field,
+ host->data->max_id_cycle << __ffs(host->data->id_cycle_field));
+
+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(host->data->dma_bits));
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to set DMA mask\n");
+
+ val = FIELD_PREP(LOONGSON_NAND_MAP_CS1_SEL, LOONGSON_NAND_CS_SEL1) |
+ FIELD_PREP(LOONGSON_NAND_MAP_RDY1_SEL, LOONGSON_NAND_CS_RDY1) |
+ FIELD_PREP(LOONGSON_NAND_MAP_CS2_SEL, LOONGSON_NAND_CS_SEL2) |
+ FIELD_PREP(LOONGSON_NAND_MAP_RDY2_SEL, LOONGSON_NAND_CS_RDY2) |
+ FIELD_PREP(LOONGSON_NAND_MAP_CS3_SEL, LOONGSON_NAND_CS_SEL3) |
+ FIELD_PREP(LOONGSON_NAND_MAP_RDY3_SEL, LOONGSON_NAND_CS_RDY3);
+
+ regmap_write(host->regmap, LOONGSON_NAND_CS_RDY_MAP, val);
+
+ if (host->data->dma_config) {
+ ret = host->data->dma_config(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to config DMA routing\n");
+ }
+
+ chan = dma_request_chan(dev, "rxtx");
+ if (IS_ERR(chan))
+ return dev_err_probe(dev, PTR_ERR(chan), "failed to request DMA channel\n");
+ host->dma_chan = chan;
+
+ cfg.src_addr = host->dma_base;
+ cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ cfg.dst_addr = host->dma_base;
+ cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ ret = dmaengine_slave_config(host->dma_chan, &cfg);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to config DMA channel\n");
+
+ init_completion(&host->dma_complete);
+
+ return 0;
+}
+
+static int loongson_nand_chip_init(struct loongson_nand_host *host)
+{
+ struct device *dev = host->dev;
+ int nchips = of_get_child_count(dev->of_node);
+ struct device_node *chip_np;
+ struct nand_chip *chip = &host->chip;
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ int ret;
+
+ if (nchips != 1)
+ return dev_err_probe(dev, -EINVAL, "Currently one NAND chip supported\n");
+
+ chip_np = of_get_next_child(dev->of_node, NULL);
+ if (!chip_np)
+ return dev_err_probe(dev, -ENODEV, "failed to get child node for NAND chip\n");
+
+ nand_set_flash_node(chip, chip_np);
+ of_node_put(chip_np);
+ if (!mtd->name)
+ return dev_err_probe(dev, -EINVAL, "Missing MTD label\n");
+
+ nand_set_controller_data(chip, host);
+ chip->controller = &host->controller;
+ chip->options = NAND_NO_SUBPAGE_WRITE | NAND_USES_DMA | NAND_BROKEN_XD;
+ chip->buf_align = 16;
+ mtd->dev.parent = dev;
+ mtd->owner = THIS_MODULE;
+
+ ret = nand_scan(chip, 1);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to scan NAND chip\n");
+
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret) {
+ nand_cleanup(chip);
+ return dev_err_probe(dev, ret, "failed to register MTD device\n");
+ }
+
+ return 0;
+}
+
+static int loongson_nand_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct loongson_nand_data *data;
+ struct loongson_nand_host *host;
+ struct resource *res;
+ int ret;
+
+ data = of_device_get_match_data(dev);
+ if (!data)
+ return -ENODEV;
+
+ host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
+ if (!host)
+ return -ENOMEM;
+
+ host->reg_base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(host->reg_base))
+ return PTR_ERR(host->reg_base);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand-dma");
+ if (!res)
+ return dev_err_probe(dev, -EINVAL, "Missing 'nand-dma' in reg-names property\n");
+
+ host->dma_base = dma_map_resource(dev, res->start, resource_size(res),
+ DMA_BIDIRECTIONAL, 0);
+ if (dma_mapping_error(dev, host->dma_base))
+ return -ENXIO;
+
+ host->dev = dev;
+ host->data = data;
+ host->controller.ops = &loongson_nand_controller_ops;
+
+ nand_controller_init(&host->controller);
+
+ ret = loongson_nand_controller_init(host);
+ if (ret)
+ goto err;
+
+ ret = loongson_nand_chip_init(host);
+ if (ret)
+ goto err;
+
+ platform_set_drvdata(pdev, host);
+
+ return 0;
+err:
+ loongson_nand_controller_cleanup(host);
+
+ return ret;
+}
+
+static void loongson_nand_remove(struct platform_device *pdev)
+{
+ struct loongson_nand_host *host = platform_get_drvdata(pdev);
+ struct nand_chip *chip = &host->chip;
+ int ret;
+
+ ret = mtd_device_unregister(nand_to_mtd(chip));
+ WARN_ON(ret);
+ nand_cleanup(chip);
+ loongson_nand_controller_cleanup(host);
+}
+
+static const struct loongson_nand_data ls1b_nand_data = {
+ .max_id_cycle = 5,
+ .status_field = GENMASK(15, 8),
+ .hold_cycle = 0x2,
+ .wait_cycle = 0xc,
+ .dma_bits = 32,
+ .set_addr = ls1b_nand_set_addr,
+};
+
+static const struct loongson_nand_data ls1c_nand_data = {
+ .max_id_cycle = 6,
+ .id_cycle_field = GENMASK(14, 12),
+ .status_field = GENMASK(23, 16),
+ .op_scope_field = GENMASK(29, 16),
+ .hold_cycle = 0x2,
+ .wait_cycle = 0xc,
+ .dma_bits = 32,
+ .set_addr = ls1c_nand_set_addr,
+};
+
+static const struct loongson_nand_data ls2k0500_nand_data = {
+ .max_id_cycle = 6,
+ .id_cycle_field = GENMASK(14, 12),
+ .status_field = GENMASK(23, 16),
+ .op_scope_field = GENMASK(29, 16),
+ .hold_cycle = 0x4,
+ .wait_cycle = 0x12,
+ .dma_bits = 64,
+ .set_addr = ls1c_nand_set_addr,
+};
+
+static const struct loongson_nand_data ls2k1000_nand_data = {
+ .max_id_cycle = 6,
+ .id_cycle_field = GENMASK(14, 12),
+ .status_field = GENMASK(23, 16),
+ .op_scope_field = GENMASK(29, 16),
+ .hold_cycle = 0x4,
+ .wait_cycle = 0x12,
+ .nand_cs = 0x2,
+ .dma_bits = 64,
+ .dma_config = ls2k1000_nand_apbdma_config,
+ .set_addr = ls1c_nand_set_addr,
+};
+
+static const struct of_device_id loongson_nand_match[] = {
+ {
+ .compatible = "loongson,ls1b-nand-controller",
+ .data = &ls1b_nand_data,
+ },
+ {
+ .compatible = "loongson,ls1c-nand-controller",
+ .data = &ls1c_nand_data,
+ },
+ {
+ .compatible = "loongson,ls2k0500-nand-controller",
+ .data = &ls2k0500_nand_data,
+ },
+ {
+ .compatible = "loongson,ls2k1000-nand-controller",
+ .data = &ls2k1000_nand_data,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, loongson_nand_match);
+
+static struct platform_driver loongson_nand_driver = {
+ .probe = loongson_nand_probe,
+ .remove = loongson_nand_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = loongson_nand_match,
+ },
+};
+
+module_platform_driver(loongson_nand_driver);
+
+MODULE_AUTHOR("Keguang Zhang <keguang.zhang@gmail.com>");
+MODULE_AUTHOR("Binbin Zhou <zhoubinbin@loongson.cn>");
+MODULE_DESCRIPTION("Loongson NAND Controller Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/nand/raw/lpc32xx_mlc.c b/drivers/mtd/nand/raw/lpc32xx_mlc.c
index 19b13ae536d4..8f6a89d9ba83 100644
--- a/drivers/mtd/nand/raw/lpc32xx_mlc.c
+++ b/drivers/mtd/nand/raw/lpc32xx_mlc.c
@@ -396,6 +396,7 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len,
struct lpc32xx_nand_host *host = nand_get_controller_data(chip);
struct dma_async_tx_descriptor *desc;
int flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
+ unsigned long time_left;
int res;
sg_init_one(&host->sgl, mem, len);
@@ -410,6 +411,7 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len,
flags);
if (!desc) {
dev_err(mtd->dev.parent, "Failed to prepare slave sg\n");
+ res = -ENXIO;
goto out1;
}
@@ -420,7 +422,13 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len,
dmaengine_submit(desc);
dma_async_issue_pending(host->dma_chan);
- wait_for_completion_timeout(&host->comp_dma, msecs_to_jiffies(1000));
+ time_left = wait_for_completion_timeout(&host->comp_dma,
+ msecs_to_jiffies(1000));
+ if (!time_left) {
+ dmaengine_terminate_sync(host->dma_chan);
+ res = -ETIMEDOUT;
+ goto out1;
+ }
dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1,
DMA_BIDIRECTIONAL);
@@ -428,7 +436,7 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len,
out1:
dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1,
DMA_BIDIRECTIONAL);
- return -ENXIO;
+ return res;
}
static int lpc32xx_read_page(struct nand_chip *chip, uint8_t *buf,
diff --git a/drivers/mtd/nand/raw/lpc32xx_slc.c b/drivers/mtd/nand/raw/lpc32xx_slc.c
index b54d76547ffb..10c8080207f4 100644
--- a/drivers/mtd/nand/raw/lpc32xx_slc.c
+++ b/drivers/mtd/nand/raw/lpc32xx_slc.c
@@ -430,6 +430,7 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, dma_addr_t dma,
struct dma_async_tx_descriptor *desc;
int flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
int res;
+ unsigned long time_left;
host->dma_slave_config.direction = dir;
host->dma_slave_config.src_addr = dma;
@@ -467,12 +468,19 @@ static int lpc32xx_xmit_dma(struct mtd_info *mtd, dma_addr_t dma,
dmaengine_submit(desc);
dma_async_issue_pending(host->dma_chan);
- wait_for_completion_timeout(&host->comp, msecs_to_jiffies(1000));
+ time_left = wait_for_completion_timeout(&host->comp,
+ msecs_to_jiffies(1000));
+ if (!time_left) {
+ dmaengine_terminate_sync(host->dma_chan);
+ res = -ETIMEDOUT;
+ } else {
+ res = 0;
+ }
dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1,
DMA_BIDIRECTIONAL);
- return 0;
+ return res;
out1:
dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1,
DMA_BIDIRECTIONAL);
@@ -854,7 +862,7 @@ static int lpc32xx_nand_probe(struct platform_device *pdev)
}
/* Start with WP disabled, if available */
- host->wp_gpio = gpiod_get_optional(&pdev->dev, NULL, GPIOD_OUT_LOW);
+ host->wp_gpio = devm_gpiod_get_optional(&pdev->dev, NULL, GPIOD_OUT_LOW);
res = PTR_ERR_OR_ZERO(host->wp_gpio);
if (res) {
if (res != -EPROBE_DEFER)
diff --git a/drivers/mtd/nand/raw/marvell_nand.c b/drivers/mtd/nand/raw/marvell_nand.c
index 303b3016a070..38b7eb5b992c 100644
--- a/drivers/mtd/nand/raw/marvell_nand.c
+++ b/drivers/mtd/nand/raw/marvell_nand.c
@@ -290,13 +290,16 @@ static const struct marvell_hw_ecc_layout marvell_nfc_layouts[] = {
MARVELL_LAYOUT( 2048, 512, 4, 1, 1, 2048, 32, 30, 0, 0, 0),
MARVELL_LAYOUT( 2048, 512, 8, 2, 1, 1024, 0, 30,1024,32, 30),
MARVELL_LAYOUT( 2048, 512, 8, 2, 1, 1024, 0, 30,1024,64, 30),
- MARVELL_LAYOUT( 2048, 512, 16, 4, 4, 512, 0, 30, 0, 32, 30),
+ MARVELL_LAYOUT( 2048, 512, 12, 3, 2, 704, 0, 30,640, 0, 30),
+ MARVELL_LAYOUT( 2048, 512, 16, 5, 4, 512, 0, 30, 0, 32, 30),
MARVELL_LAYOUT( 4096, 512, 4, 2, 2, 2048, 32, 30, 0, 0, 0),
- MARVELL_LAYOUT( 4096, 512, 8, 4, 4, 1024, 0, 30, 0, 64, 30),
- MARVELL_LAYOUT( 4096, 512, 16, 8, 8, 512, 0, 30, 0, 32, 30),
+ MARVELL_LAYOUT( 4096, 512, 8, 5, 4, 1024, 0, 30, 0, 64, 30),
+ MARVELL_LAYOUT( 4096, 512, 12, 6, 5, 704, 0, 30,576, 32, 30),
+ MARVELL_LAYOUT( 4096, 512, 16, 9, 8, 512, 0, 30, 0, 32, 30),
MARVELL_LAYOUT( 8192, 512, 4, 4, 4, 2048, 0, 30, 0, 0, 0),
- MARVELL_LAYOUT( 8192, 512, 8, 8, 8, 1024, 0, 30, 0, 160, 30),
- MARVELL_LAYOUT( 8192, 512, 16, 16, 16, 512, 0, 30, 0, 32, 30),
+ MARVELL_LAYOUT( 8192, 512, 8, 9, 8, 1024, 0, 30, 0, 160, 30),
+ MARVELL_LAYOUT( 8192, 512, 12, 12, 11, 704, 0, 30,448, 64, 30),
+ MARVELL_LAYOUT( 8192, 512, 16, 17, 16, 512, 0, 30, 0, 32, 30),
};
/**
diff --git a/drivers/mtd/nand/raw/mxc_nand.c b/drivers/mtd/nand/raw/mxc_nand.c
index 8c56b685bf91..4d8b92e7e672 100644
--- a/drivers/mtd/nand/raw/mxc_nand.c
+++ b/drivers/mtd/nand/raw/mxc_nand.c
@@ -4,6 +4,7 @@
* Copyright 2008 Sascha Hauer, kernel@pengutronix.de
*/
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/init.h>
@@ -1714,7 +1715,14 @@ static int mxcnd_probe(struct platform_device *pdev)
this->legacy.chip_delay = 5;
nand_set_controller_data(this, host);
- nand_set_flash_node(this, pdev->dev.of_node);
+
+ struct device_node *np __free(device_node) =
+ of_get_next_child_with_prefix(pdev->dev.of_node, NULL, "nand");
+
+ if (np)
+ nand_set_flash_node(this, np);
+ else
+ nand_set_flash_node(this, pdev->dev.of_node);
host->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(host->clk))
diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c
index 53e16d39af4b..a5b278ab9384 100644
--- a/drivers/mtd/nand/raw/nand_base.c
+++ b/drivers/mtd/nand/raw/nand_base.c
@@ -8,7 +8,7 @@
* http://www.linux-mtd.infradead.org/doc/nand.html
*
* Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com)
- * 2002-2006 Thomas Gleixner (tglx@linutronix.de)
+ * 2002-2006 Thomas Gleixner (tglx@kernel.org)
*
* Credits:
* David Woodhouse for adding multichip support
@@ -43,6 +43,7 @@
#include <linux/mtd/partitions.h>
#include <linux/of.h>
#include <linux/gpio/consumer.h>
+#include <linux/cleanup.h>
#include "internals.h"
@@ -174,7 +175,7 @@ void nand_select_target(struct nand_chip *chip, unsigned int cs)
* cs should always lie between 0 and nanddev_ntargets(), when that's
* not the case it's a bug and the caller should be fixed.
*/
- if (WARN_ON(cs > nanddev_ntargets(&chip->base)))
+ if (WARN_ON(cs >= nanddev_ntargets(&chip->base)))
return;
chip->cur_cs = cs;
@@ -1062,7 +1063,7 @@ static int nand_choose_interface_config(struct nand_chip *chip)
if (!nand_controller_can_setup_interface(chip))
return 0;
- iface = kzalloc(sizeof(*iface), GFP_KERNEL);
+ iface = kzalloc_obj(*iface);
if (!iface)
return -ENOMEM;
@@ -1215,32 +1216,32 @@ static int nand_lp_exec_read_page_op(struct nand_chip *chip, unsigned int page,
return nand_exec_op(chip, &op);
}
-static unsigned int rawnand_last_page_of_lun(unsigned int pages_per_lun, unsigned int lun)
+static unsigned int rawnand_last_page_of_block(unsigned int ppb, unsigned int block)
{
- /* lun is expected to be very small */
- return (lun * pages_per_lun) + pages_per_lun - 1;
+ /* block is expected to be very small */
+ return (block * ppb) + ppb - 1;
}
static void rawnand_cap_cont_reads(struct nand_chip *chip)
{
struct nand_memory_organization *memorg;
- unsigned int ppl, first_lun, last_lun;
+ unsigned int ppb, first_block, last_block;
memorg = nanddev_get_memorg(&chip->base);
- ppl = memorg->pages_per_eraseblock * memorg->eraseblocks_per_lun;
- first_lun = chip->cont_read.first_page / ppl;
- last_lun = chip->cont_read.last_page / ppl;
+ ppb = memorg->pages_per_eraseblock;
+ first_block = chip->cont_read.first_page / ppb;
+ last_block = chip->cont_read.last_page / ppb;
- /* Prevent sequential cache reads across LUN boundaries */
- if (first_lun != last_lun)
- chip->cont_read.pause_page = rawnand_last_page_of_lun(ppl, first_lun);
+ /* Prevent sequential cache reads across block boundaries */
+ if (first_block != last_block)
+ chip->cont_read.pause_page = rawnand_last_page_of_block(ppb, first_block);
else
chip->cont_read.pause_page = chip->cont_read.last_page;
if (chip->cont_read.first_page == chip->cont_read.pause_page) {
chip->cont_read.first_page++;
chip->cont_read.pause_page = min(chip->cont_read.last_page,
- rawnand_last_page_of_lun(ppl, first_lun + 1));
+ rawnand_last_page_of_block(ppb, first_block + 1));
}
if (chip->cont_read.first_page >= chip->cont_read.last_page)
@@ -1833,7 +1834,7 @@ int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf,
/* READ_ID data bytes are received twice in NV-DDR mode */
if (len && nand_interface_is_nvddr(conf)) {
- ddrbuf = kzalloc(len * 2, GFP_KERNEL);
+ ddrbuf = kcalloc(2, len, GFP_KERNEL);
if (!ddrbuf)
return -ENOMEM;
@@ -2203,7 +2204,7 @@ int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len,
* twice.
*/
if (force_8bit && nand_interface_is_nvddr(conf)) {
- ddrbuf = kzalloc(len * 2, GFP_KERNEL);
+ ddrbuf = kcalloc(2, len, GFP_KERNEL);
if (!ddrbuf)
return -ENOMEM;
@@ -2784,137 +2785,6 @@ int nand_set_features(struct nand_chip *chip, int addr,
}
/**
- * nand_check_erased_buf - check if a buffer contains (almost) only 0xff data
- * @buf: buffer to test
- * @len: buffer length
- * @bitflips_threshold: maximum number of bitflips
- *
- * Check if a buffer contains only 0xff, which means the underlying region
- * has been erased and is ready to be programmed.
- * The bitflips_threshold specify the maximum number of bitflips before
- * considering the region is not erased.
- * Note: The logic of this function has been extracted from the memweight
- * implementation, except that nand_check_erased_buf function exit before
- * testing the whole buffer if the number of bitflips exceed the
- * bitflips_threshold value.
- *
- * Returns a positive number of bitflips less than or equal to
- * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
- * threshold.
- */
-static int nand_check_erased_buf(void *buf, int len, int bitflips_threshold)
-{
- const unsigned char *bitmap = buf;
- int bitflips = 0;
- int weight;
-
- for (; len && ((uintptr_t)bitmap) % sizeof(long);
- len--, bitmap++) {
- weight = hweight8(*bitmap);
- bitflips += BITS_PER_BYTE - weight;
- if (unlikely(bitflips > bitflips_threshold))
- return -EBADMSG;
- }
-
- for (; len >= sizeof(long);
- len -= sizeof(long), bitmap += sizeof(long)) {
- unsigned long d = *((unsigned long *)bitmap);
- if (d == ~0UL)
- continue;
- weight = hweight_long(d);
- bitflips += BITS_PER_LONG - weight;
- if (unlikely(bitflips > bitflips_threshold))
- return -EBADMSG;
- }
-
- for (; len > 0; len--, bitmap++) {
- weight = hweight8(*bitmap);
- bitflips += BITS_PER_BYTE - weight;
- if (unlikely(bitflips > bitflips_threshold))
- return -EBADMSG;
- }
-
- return bitflips;
-}
-
-/**
- * nand_check_erased_ecc_chunk - check if an ECC chunk contains (almost) only
- * 0xff data
- * @data: data buffer to test
- * @datalen: data length
- * @ecc: ECC buffer
- * @ecclen: ECC length
- * @extraoob: extra OOB buffer
- * @extraooblen: extra OOB length
- * @bitflips_threshold: maximum number of bitflips
- *
- * Check if a data buffer and its associated ECC and OOB data contains only
- * 0xff pattern, which means the underlying region has been erased and is
- * ready to be programmed.
- * The bitflips_threshold specify the maximum number of bitflips before
- * considering the region as not erased.
- *
- * Note:
- * 1/ ECC algorithms are working on pre-defined block sizes which are usually
- * different from the NAND page size. When fixing bitflips, ECC engines will
- * report the number of errors per chunk, and the NAND core infrastructure
- * expect you to return the maximum number of bitflips for the whole page.
- * This is why you should always use this function on a single chunk and
- * not on the whole page. After checking each chunk you should update your
- * max_bitflips value accordingly.
- * 2/ When checking for bitflips in erased pages you should not only check
- * the payload data but also their associated ECC data, because a user might
- * have programmed almost all bits to 1 but a few. In this case, we
- * shouldn't consider the chunk as erased, and checking ECC bytes prevent
- * this case.
- * 3/ The extraoob argument is optional, and should be used if some of your OOB
- * data are protected by the ECC engine.
- * It could also be used if you support subpages and want to attach some
- * extra OOB data to an ECC chunk.
- *
- * Returns a positive number of bitflips less than or equal to
- * bitflips_threshold, or -ERROR_CODE for bitflips in excess of the
- * threshold. In case of success, the passed buffers are filled with 0xff.
- */
-int nand_check_erased_ecc_chunk(void *data, int datalen,
- void *ecc, int ecclen,
- void *extraoob, int extraooblen,
- int bitflips_threshold)
-{
- int data_bitflips = 0, ecc_bitflips = 0, extraoob_bitflips = 0;
-
- data_bitflips = nand_check_erased_buf(data, datalen,
- bitflips_threshold);
- if (data_bitflips < 0)
- return data_bitflips;
-
- bitflips_threshold -= data_bitflips;
-
- ecc_bitflips = nand_check_erased_buf(ecc, ecclen, bitflips_threshold);
- if (ecc_bitflips < 0)
- return ecc_bitflips;
-
- bitflips_threshold -= ecc_bitflips;
-
- extraoob_bitflips = nand_check_erased_buf(extraoob, extraooblen,
- bitflips_threshold);
- if (extraoob_bitflips < 0)
- return extraoob_bitflips;
-
- if (data_bitflips)
- memset(data, 0xff, datalen);
-
- if (ecc_bitflips)
- memset(ecc, 0xff, ecclen);
-
- if (extraoob_bitflips)
- memset(extraoob, 0xff, extraooblen);
-
- return data_bitflips + ecc_bitflips + extraoob_bitflips;
-}
-EXPORT_SYMBOL(nand_check_erased_ecc_chunk);
-
-/**
* nand_read_page_raw_notsupp - dummy read raw page function
* @chip: nand chip info structure
* @buf: buffer to store read data
@@ -4835,16 +4705,16 @@ 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->ops.resume)
- chip->ops.resume(chip);
- chip->suspended = 0;
- } else {
- pr_err("%s called for a chip which is not in suspended state\n",
- __func__);
+ scoped_guard(mutex, &chip->lock) {
+ if (chip->suspended) {
+ if (chip->ops.resume)
+ chip->ops.resume(chip);
+ chip->suspended = 0;
+ } else {
+ pr_err("%s called for a chip which is not in suspended state\n",
+ __func__);
+ }
}
- mutex_unlock(&chip->lock);
wake_up_all(&chip->resume_wq);
}
@@ -4868,11 +4738,16 @@ static void nand_shutdown(struct mtd_info *mtd)
static int nand_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
{
struct nand_chip *chip = mtd_to_nand(mtd);
+ int ret;
if (!chip->ops.lock_area)
return -ENOTSUPP;
- return chip->ops.lock_area(chip, ofs, len);
+ nand_get_device(chip);
+ ret = chip->ops.lock_area(chip, ofs, len);
+ nand_release_device(chip);
+
+ return ret;
}
/**
@@ -4884,11 +4759,16 @@ static int nand_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
static int nand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len)
{
struct nand_chip *chip = mtd_to_nand(mtd);
+ int ret;
if (!chip->ops.unlock_area)
return -ENOTSUPP;
- return chip->ops.unlock_area(chip, ofs, len);
+ nand_get_device(chip);
+ ret = chip->ops.unlock_area(chip, ofs, len);
+ nand_release_device(chip);
+
+ return ret;
}
/* Set default functions */
@@ -5560,8 +5440,8 @@ static int of_get_nand_secure_regions(struct nand_chip *chip)
return nr_elem;
chip->nr_secure_regions = nr_elem / 2;
- chip->secure_regions = kcalloc(chip->nr_secure_regions, sizeof(*chip->secure_regions),
- GFP_KERNEL);
+ chip->secure_regions = kzalloc_objs(*chip->secure_regions,
+ chip->nr_secure_regions);
if (!chip->secure_regions)
return -ENOMEM;
@@ -6469,11 +6349,14 @@ static int nand_scan_tail(struct nand_chip *chip)
ecc->steps = mtd->writesize / ecc->size;
if (!base->ecc.ctx.nsteps)
base->ecc.ctx.nsteps = ecc->steps;
- if (ecc->steps * ecc->size != mtd->writesize) {
- WARN(1, "Invalid ECC parameters\n");
- ret = -EINVAL;
- goto err_nand_manuf_cleanup;
- }
+
+ /*
+ * Validity check: Warn if ECC parameters are not compatible with page size.
+ * Due to the custom handling of ECC blocks in certain controllers the check
+ * may result in an expected failure.
+ */
+ if (ecc->steps * ecc->size != mtd->writesize)
+ pr_warn("ECC parameters may be invalid in reference to underlying NAND chip\n");
if (!ecc->total) {
ecc->total = ecc->steps * ecc->bytes;
@@ -6722,5 +6605,5 @@ EXPORT_SYMBOL_GPL(nand_cleanup);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Steven J. Hill <sjhill@realitydiluted.com>");
-MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>");
+MODULE_AUTHOR("Thomas Gleixner <tglx@kernel.org>");
MODULE_DESCRIPTION("Generic NAND flash driver code");
diff --git a/drivers/mtd/nand/raw/nand_bbt.c b/drivers/mtd/nand/raw/nand_bbt.c
index a8fba5f39f59..b0e7592a2ab7 100644
--- a/drivers/mtd/nand/raw/nand_bbt.c
+++ b/drivers/mtd/nand/raw/nand_bbt.c
@@ -3,7 +3,7 @@
* Overview:
* Bad block table support for the NAND driver
*
- * Copyright © 2004 Thomas Gleixner (tglx@linutronix.de)
+ * Copyright © 2004 Thomas Gleixner (tglx@kernel.org)
*
* Description:
*
@@ -1375,7 +1375,7 @@ static int nand_create_badblock_pattern(struct nand_chip *this)
pr_warn("Bad block pattern already allocated; not replacing\n");
return -EINVAL;
}
- bd = kzalloc(sizeof(*bd), GFP_KERNEL);
+ bd = kzalloc_obj(*bd);
if (!bd)
return -ENOMEM;
bd->options = this->bbt_options & BADBLOCK_SCAN_MASK;
diff --git a/drivers/mtd/nand/raw/nand_hynix.c b/drivers/mtd/nand/raw/nand_hynix.c
index c02e50608816..0510ceff591d 100644
--- a/drivers/mtd/nand/raw/nand_hynix.c
+++ b/drivers/mtd/nand/raw/nand_hynix.c
@@ -377,9 +377,9 @@ static int hynix_nand_rr_init(struct nand_chip *chip)
/*
* We only support read-retry for 1xnm NANDs, and those NANDs all
- * expose a valid JEDEC ID.
+ * expose a valid JEDEC ID. SLC NANDs don't require read-retry.
*/
- if (valid_jedecid) {
+ if (valid_jedecid && nanddev_bits_per_cell(&chip->base) > 1) {
u8 nand_tech = chip->id.data[5] >> 4;
/* 1xnm technology */
@@ -705,7 +705,7 @@ static int hynix_nand_init(struct nand_chip *chip)
else
chip->options |= NAND_BBM_FIRSTPAGE | NAND_BBM_SECONDPAGE;
- hynix = kzalloc(sizeof(*hynix), GFP_KERNEL);
+ hynix = kzalloc_obj(*hynix);
if (!hynix)
return -ENOMEM;
diff --git a/drivers/mtd/nand/raw/nand_ids.c b/drivers/mtd/nand/raw/nand_ids.c
index 650351c62af6..62a8cf86d9e2 100644
--- a/drivers/mtd/nand/raw/nand_ids.c
+++ b/drivers/mtd/nand/raw/nand_ids.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * Copyright (C) 2002 Thomas Gleixner (tglx@linutronix.de)
+ * Copyright (C) 2002 Thomas Gleixner (tglx@kernel.org)
*/
#include <linux/sizes.h>
diff --git a/drivers/mtd/nand/raw/nand_jedec.c b/drivers/mtd/nand/raw/nand_jedec.c
index b3cc8f360529..b523bf65746b 100644
--- a/drivers/mtd/nand/raw/nand_jedec.c
+++ b/drivers/mtd/nand/raw/nand_jedec.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com)
- * 2002-2006 Thomas Gleixner (tglx@linutronix.de)
+ * 2002-2006 Thomas Gleixner (tglx@kernel.org)
*
* Credits:
* David Woodhouse for adding multichip support
@@ -42,7 +42,7 @@ int nand_jedec_detect(struct nand_chip *chip)
return 0;
/* JEDEC chip: allocate a buffer to hold its parameter page */
- p = kzalloc(sizeof(*p), GFP_KERNEL);
+ p = kzalloc_obj(*p);
if (!p)
return -ENOMEM;
diff --git a/drivers/mtd/nand/raw/nand_legacy.c b/drivers/mtd/nand/raw/nand_legacy.c
index 743792edf98d..97700f80d5b8 100644
--- a/drivers/mtd/nand/raw/nand_legacy.c
+++ b/drivers/mtd/nand/raw/nand_legacy.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com)
- * 2002-2006 Thomas Gleixner (tglx@linutronix.de)
+ * 2002-2006 Thomas Gleixner (tglx@kernel.org)
*
* Credits:
* David Woodhouse for adding multichip support
diff --git a/drivers/mtd/nand/raw/nand_micron.c b/drivers/mtd/nand/raw/nand_micron.c
index c0192881906b..8807b8aade41 100644
--- a/drivers/mtd/nand/raw/nand_micron.c
+++ b/drivers/mtd/nand/raw/nand_micron.c
@@ -484,7 +484,7 @@ static int micron_nand_init(struct nand_chip *chip)
int ondie;
int ret;
- micron = kzalloc(sizeof(*micron), GFP_KERNEL);
+ micron = kzalloc_obj(*micron);
if (!micron)
return -ENOMEM;
diff --git a/drivers/mtd/nand/raw/nand_onfi.c b/drivers/mtd/nand/raw/nand_onfi.c
index 861975e44b55..cd3ad373883e 100644
--- a/drivers/mtd/nand/raw/nand_onfi.c
+++ b/drivers/mtd/nand/raw/nand_onfi.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com)
- * 2002-2006 Thomas Gleixner (tglx@linutronix.de)
+ * 2002-2006 Thomas Gleixner (tglx@kernel.org)
*
* Credits:
* David Woodhouse for adding multichip support
@@ -306,7 +306,7 @@ int nand_onfi_detect(struct nand_chip *chip)
if (le16_to_cpu(p->opt_cmd) & ONFI_OPT_CMD_READ_CACHE)
chip->parameters.supports_read_cache = true;
- onfi = kzalloc(sizeof(*onfi), GFP_KERNEL);
+ onfi = kzalloc_obj(*onfi);
if (!onfi) {
ret = -ENOMEM;
goto free_model;
diff --git a/drivers/mtd/nand/raw/nandsim.c b/drivers/mtd/nand/raw/nandsim.c
index df48b7d01d16..fe968037f75a 100644
--- a/drivers/mtd/nand/raw/nandsim.c
+++ b/drivers/mtd/nand/raw/nandsim.c
@@ -552,9 +552,8 @@ static int __init ns_alloc_device(struct nandsim *ns)
err = -EINVAL;
goto err_close_filp;
}
- ns->pages_written =
- vzalloc(array_size(sizeof(unsigned long),
- BITS_TO_LONGS(ns->geom.pgnum)));
+ ns->pages_written = vcalloc(BITS_TO_LONGS(ns->geom.pgnum),
+ sizeof(unsigned long));
if (!ns->pages_written) {
NS_ERR("alloc_device: unable to allocate pages written array\n");
err = -ENOMEM;
@@ -578,7 +577,7 @@ err_close_filp:
return err;
}
- ns->pages = vmalloc(array_size(sizeof(union ns_mem), ns->geom.pgnum));
+ ns->pages = vmalloc_array(ns->geom.pgnum, sizeof(union ns_mem));
if (!ns->pages) {
NS_ERR("alloc_device: unable to allocate page array\n");
return -ENOMEM;
@@ -852,7 +851,7 @@ static int ns_parse_weakblocks(void)
}
if (*w == ',')
w += 1;
- wb = kzalloc(sizeof(*wb), GFP_KERNEL);
+ wb = kzalloc_obj(*wb);
if (!wb) {
NS_ERR("unable to allocate memory.\n");
return -ENOMEM;
@@ -903,7 +902,7 @@ static int ns_parse_weakpages(void)
}
if (*w == ',')
w += 1;
- wp = kzalloc(sizeof(*wp), GFP_KERNEL);
+ wp = kzalloc_obj(*wp);
if (!wp) {
NS_ERR("unable to allocate memory.\n");
return -ENOMEM;
@@ -954,7 +953,7 @@ static int ns_parse_gravepages(void)
}
if (*g == ',')
g += 1;
- gp = kzalloc(sizeof(*gp), GFP_KERNEL);
+ gp = kzalloc_obj(*gp);
if (!gp) {
NS_ERR("unable to allocate memory.\n");
return -ENOMEM;
@@ -2269,7 +2268,7 @@ static int __init ns_init_module(void)
return -EINVAL;
}
- ns = kzalloc(sizeof(struct nandsim), GFP_KERNEL);
+ ns = kzalloc_obj(struct nandsim);
if (!ns) {
NS_ERR("unable to allocate core structures.\n");
return -ENOMEM;
diff --git a/drivers/mtd/nand/raw/ndfc.c b/drivers/mtd/nand/raw/ndfc.c
index 13365128194d..a48274297d3b 100644
--- a/drivers/mtd/nand/raw/ndfc.c
+++ b/drivers/mtd/nand/raw/ndfc.c
@@ -44,13 +44,13 @@ static void ndfc_select_chip(struct nand_chip *nchip, int chip)
uint32_t ccr;
struct ndfc_controller *ndfc = nand_get_controller_data(nchip);
- ccr = in_be32(ndfc->ndfcbase + NDFC_CCR);
+ ccr = ioread32be(ndfc->ndfcbase + NDFC_CCR);
if (chip >= 0) {
ccr &= ~NDFC_CCR_BS_MASK;
ccr |= NDFC_CCR_BS(chip + ndfc->chip_select);
} else
ccr |= NDFC_CCR_RESET_CE;
- out_be32(ndfc->ndfcbase + NDFC_CCR, ccr);
+ iowrite32be(ccr, ndfc->ndfcbase + NDFC_CCR);
}
static void ndfc_hwcontrol(struct nand_chip *chip, int cmd, unsigned int ctrl)
@@ -70,7 +70,7 @@ static int ndfc_ready(struct nand_chip *chip)
{
struct ndfc_controller *ndfc = nand_get_controller_data(chip);
- return in_be32(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY;
+ return ioread32be(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY;
}
static void ndfc_enable_hwecc(struct nand_chip *chip, int mode)
@@ -78,9 +78,9 @@ static void ndfc_enable_hwecc(struct nand_chip *chip, int mode)
uint32_t ccr;
struct ndfc_controller *ndfc = nand_get_controller_data(chip);
- ccr = in_be32(ndfc->ndfcbase + NDFC_CCR);
+ ccr = ioread32be(ndfc->ndfcbase + NDFC_CCR);
ccr |= NDFC_CCR_RESET_ECC;
- out_be32(ndfc->ndfcbase + NDFC_CCR, ccr);
+ iowrite32be(ccr, ndfc->ndfcbase + NDFC_CCR);
wmb();
}
@@ -92,7 +92,7 @@ static int ndfc_calculate_ecc(struct nand_chip *chip,
uint8_t *p = (uint8_t *)&ecc;
wmb();
- ecc = in_be32(ndfc->ndfcbase + NDFC_ECC);
+ ecc = ioread32be(ndfc->ndfcbase + NDFC_ECC);
/* The NDFC uses Smart Media (SMC) bytes order */
ecc_code[0] = p[1];
ecc_code[1] = p[2];
@@ -114,7 +114,7 @@ static void ndfc_read_buf(struct nand_chip *chip, uint8_t *buf, int len)
uint32_t *p = (uint32_t *) buf;
for(;len > 0; len -= 4)
- *p++ = in_be32(ndfc->ndfcbase + NDFC_DATA);
+ *p++ = ioread32be(ndfc->ndfcbase + NDFC_DATA);
}
static void ndfc_write_buf(struct nand_chip *chip, const uint8_t *buf, int len)
@@ -123,7 +123,7 @@ static void ndfc_write_buf(struct nand_chip *chip, const uint8_t *buf, int len)
uint32_t *p = (uint32_t *) buf;
for(;len > 0; len -= 4)
- out_be32(ndfc->ndfcbase + NDFC_DATA, *p++);
+ iowrite32be(*p++, ndfc->ndfcbase + NDFC_DATA);
}
/*
@@ -188,7 +188,7 @@ static int ndfc_probe(struct platform_device *ofdev)
const __be32 *reg;
u32 ccr;
u32 cs;
- int err, len;
+ int err, len = 0;
/* Read the reg property to get the chip select */
reg = of_get_property(ofdev->dev.of_node, "reg", &len);
@@ -223,13 +223,13 @@ static int ndfc_probe(struct platform_device *ofdev)
if (reg)
ccr |= be32_to_cpup(reg);
- out_be32(ndfc->ndfcbase + NDFC_CCR, ccr);
+ iowrite32be(ccr, ndfc->ndfcbase + NDFC_CCR);
/* Set the bank settings if given */
reg = of_get_property(ofdev->dev.of_node, "bank-settings", NULL);
if (reg) {
int offset = NDFC_BCFG0 + (ndfc->chip_select << 2);
- out_be32(ndfc->ndfcbase + offset, be32_to_cpup(reg));
+ iowrite32be(be32_to_cpup(reg), ndfc->ndfcbase + offset);
}
err = ndfc_chip_init(ndfc, ofdev->dev.of_node);
@@ -272,5 +272,5 @@ static struct platform_driver ndfc_driver = {
module_platform_driver(ndfc_driver);
MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Thomas Gleixner <tglx@linutronix.de>");
+MODULE_AUTHOR("Thomas Gleixner <tglx@kernel.org>");
MODULE_DESCRIPTION("OF Platform driver for NDFC");
diff --git a/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c b/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c
index c23b537948d5..1a285cd8fad6 100644
--- a/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c
+++ b/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c
@@ -935,10 +935,10 @@ static void ma35_chips_cleanup(struct ma35_nand_info *nand)
static int ma35_nand_chips_init(struct device *dev, struct ma35_nand_info *nand)
{
- struct device_node *np = dev->of_node, *nand_np;
+ struct device_node *np = dev->of_node;
int ret;
- for_each_child_of_node(np, nand_np) {
+ for_each_child_of_node_scoped(np, nand_np) {
ret = ma35_nand_chip_init(dev, nand, nand_np);
if (ret) {
ma35_chips_cleanup(nand);
diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c
index b8af3a3533fc..39e297486721 100644
--- a/drivers/mtd/nand/raw/omap2.c
+++ b/drivers/mtd/nand/raw/omap2.c
@@ -1979,7 +1979,7 @@ static int omap_nand_attach_chip(struct nand_chip *chip)
err = rawnand_sw_bch_init(chip);
if (err) {
dev_err(dev, "Unable to use BCH library\n");
- return err;
+ goto err_put_elm_dev;
}
break;
@@ -2016,7 +2016,7 @@ static int omap_nand_attach_chip(struct nand_chip *chip)
err = rawnand_sw_bch_init(chip);
if (err) {
dev_err(dev, "unable to use BCH library\n");
- return err;
+ goto err_put_elm_dev;
}
break;
@@ -2054,7 +2054,8 @@ static int omap_nand_attach_chip(struct nand_chip *chip)
break;
default:
dev_err(dev, "Invalid or unsupported ECC scheme\n");
- return -EINVAL;
+ err = -EINVAL;
+ goto err_put_elm_dev;
}
if (elm_bch_strength >= 0) {
@@ -2073,7 +2074,7 @@ static int omap_nand_attach_chip(struct nand_chip *chip)
info->nsteps_per_eccpg, chip->ecc.size,
chip->ecc.bytes);
if (err < 0)
- return err;
+ goto err_put_elm_dev;
}
/* Check if NAND device's OOB is enough to store ECC signatures */
@@ -2083,10 +2084,24 @@ static int omap_nand_attach_chip(struct nand_chip *chip)
dev_err(dev,
"Not enough OOB bytes: required = %d, available=%d\n",
min_oobbytes, mtd->oobsize);
- return -EINVAL;
+ err = -EINVAL;
+ goto err_put_elm_dev;
}
return 0;
+
+err_put_elm_dev:
+ put_device(info->elm_dev);
+
+ return err;
+}
+
+static void omap_nand_detach_chip(struct nand_chip *chip)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct omap_nand_info *info = mtd_to_omap(mtd);
+
+ put_device(info->elm_dev);
}
static void omap_nand_data_in(struct nand_chip *chip, void *buf,
@@ -2187,6 +2202,7 @@ static int omap_nand_exec_op(struct nand_chip *chip,
static const struct nand_controller_ops omap_nand_controller_ops = {
.attach_chip = omap_nand_attach_chip,
+ .detach_chip = omap_nand_detach_chip,
.exec_op = omap_nand_exec_op,
};
@@ -2316,6 +2332,5 @@ static struct platform_driver omap_nand_driver = {
module_platform_driver(omap_nand_driver);
-MODULE_ALIAS("platform:" DRIVER_NAME);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Glue layer for NAND flash on TI OMAP boards");
diff --git a/drivers/mtd/nand/raw/pasemi_nand.c b/drivers/mtd/nand/raw/pasemi_nand.c
index 0b1f7670660e..09409b703d93 100644
--- a/drivers/mtd/nand/raw/pasemi_nand.c
+++ b/drivers/mtd/nand/raw/pasemi_nand.c
@@ -113,7 +113,7 @@ static int pasemi_nand_probe(struct platform_device *ofdev)
dev_dbg(dev, "pasemi_nand at %pR\n", &res);
/* Allocate memory for MTD device structure and private data */
- ddata = kzalloc(sizeof(*ddata), GFP_KERNEL);
+ ddata = kzalloc_obj(*ddata);
if (!ddata) {
err = -ENOMEM;
goto out;
diff --git a/drivers/mtd/nand/raw/pl35x-nand-controller.c b/drivers/mtd/nand/raw/pl35x-nand-controller.c
index 09440ed4652e..06f8f1e14b9c 100644
--- a/drivers/mtd/nand/raw/pl35x-nand-controller.c
+++ b/drivers/mtd/nand/raw/pl35x-nand-controller.c
@@ -862,6 +862,9 @@ static int pl35x_nfc_setup_interface(struct nand_chip *chip, int cs,
PL35X_SMC_NAND_TAR_CYCLES(tmgs.t_ar) |
PL35X_SMC_NAND_TRR_CYCLES(tmgs.t_rr);
+ writel(plnand->timings, nfc->conf_regs + PL35X_SMC_CYCLES);
+ pl35x_smc_update_regs(nfc);
+
return 0;
}
@@ -970,14 +973,18 @@ static int pl35x_nand_attach_chip(struct nand_chip *chip)
switch (chip->ecc.engine_type) {
case NAND_ECC_ENGINE_TYPE_ON_DIE:
+ dev_dbg(nfc->dev, "Using on-die ECC\n");
/* Keep these legacy BBT descriptors for ON_DIE situations */
chip->bbt_td = &bbt_main_descr;
chip->bbt_md = &bbt_mirror_descr;
fallthrough;
case NAND_ECC_ENGINE_TYPE_NONE:
case NAND_ECC_ENGINE_TYPE_SOFT:
+ dev_dbg(nfc->dev, "Using software ECC (Hamming 1-bit/512B)\n");
+ chip->ecc.write_page_raw = nand_monolithic_write_page_raw;
break;
case NAND_ECC_ENGINE_TYPE_ON_HOST:
+ dev_dbg(nfc->dev, "Using hardware ECC\n");
ret = pl35x_nand_init_hw_ecc_controller(nfc, chip);
if (ret)
return ret;
@@ -1137,7 +1144,7 @@ static int pl35x_nand_probe(struct platform_device *pdev)
struct device *smc_dev = pdev->dev.parent;
struct amba_device *smc_amba = to_amba_device(smc_dev);
struct pl35x_nandc *nfc;
- u32 ret;
+ int ret;
nfc = devm_kzalloc(&pdev->dev, sizeof(*nfc), GFP_KERNEL);
if (!nfc)
@@ -1148,7 +1155,7 @@ static int pl35x_nand_probe(struct platform_device *pdev)
nfc->controller.ops = &pl35x_nandc_ops;
INIT_LIST_HEAD(&nfc->chips);
- nfc->conf_regs = devm_ioremap_resource(&smc_amba->dev, &smc_amba->res);
+ nfc->conf_regs = devm_ioremap_resource(nfc->dev, &smc_amba->res);
if (IS_ERR(nfc->conf_regs))
return PTR_ERR(nfc->conf_regs);
@@ -1193,6 +1200,5 @@ static struct platform_driver pl35x_nandc_driver = {
module_platform_driver(pl35x_nandc_driver);
MODULE_AUTHOR("Xilinx, Inc.");
-MODULE_ALIAS("platform:" PL35X_NANDC_DRIVER_NAME);
MODULE_DESCRIPTION("ARM PL35X NAND controller driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/mtd/nand/raw/plat_nand.c b/drivers/mtd/nand/raw/plat_nand.c
index 0bcd455328ef..fe31551bcf5f 100644
--- a/drivers/mtd/nand/raw/plat_nand.c
+++ b/drivers/mtd/nand/raw/plat_nand.c
@@ -6,6 +6,7 @@
*/
#include <linux/err.h>
+#include <linux/gpio/consumer.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
@@ -17,6 +18,7 @@ struct plat_nand_data {
struct nand_controller controller;
struct nand_chip chip;
void __iomem *io_base;
+ struct gpio_desc *ready_gpio;
};
static int plat_nand_attach_chip(struct nand_chip *chip)
@@ -32,6 +34,14 @@ static const struct nand_controller_ops plat_nand_ops = {
.attach_chip = plat_nand_attach_chip,
};
+/* Resources and device for NAND */
+static int plat_nand_gpio_dev_ready(struct nand_chip *chip)
+{
+ struct plat_nand_data *data = nand_get_controller_data(chip);
+
+ return gpiod_get_value(data->ready_gpio);
+}
+
/*
* Probe for the NAND device.
*/
@@ -41,6 +51,7 @@ static int plat_nand_probe(struct platform_device *pdev)
struct plat_nand_data *data;
struct mtd_info *mtd;
const char **part_types;
+ struct nand_chip *chip;
int err = 0;
if (!pdata) {
@@ -59,9 +70,17 @@ static int plat_nand_probe(struct platform_device *pdev)
if (!data)
return -ENOMEM;
+ data->ready_gpio = devm_gpiod_get_optional(&pdev->dev, "ready",
+ GPIOD_IN);
+ if (IS_ERR(data->ready_gpio))
+ return dev_err_probe(&pdev->dev, PTR_ERR(data->ready_gpio),
+ "could not get READY GPIO\n");
+
data->controller.ops = &plat_nand_ops;
nand_controller_init(&data->controller);
data->chip.controller = &data->controller;
+ chip = &data->chip;
+ nand_set_controller_data(chip, data);
data->io_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(data->io_base))
@@ -74,7 +93,10 @@ static int plat_nand_probe(struct platform_device *pdev)
data->chip.legacy.IO_ADDR_R = data->io_base;
data->chip.legacy.IO_ADDR_W = data->io_base;
data->chip.legacy.cmd_ctrl = pdata->ctrl.cmd_ctrl;
- data->chip.legacy.dev_ready = pdata->ctrl.dev_ready;
+ if (data->ready_gpio)
+ data->chip.legacy.dev_ready = plat_nand_gpio_dev_ready;
+ else
+ data->chip.legacy.dev_ready = pdata->ctrl.dev_ready;
data->chip.legacy.select_chip = pdata->ctrl.select_chip;
data->chip.legacy.write_buf = pdata->ctrl.write_buf;
data->chip.legacy.read_buf = pdata->ctrl.read_buf;
diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c
index 6720b547892b..4b80ce084d9a 100644
--- a/drivers/mtd/nand/raw/qcom_nandc.c
+++ b/drivers/mtd/nand/raw/qcom_nandc.c
@@ -128,8 +128,8 @@ static struct qcom_nand_host *to_qcom_nand_host(struct nand_chip *chip)
static struct qcom_nand_controller *
get_qcom_nand_controller(struct nand_chip *chip)
{
- return (struct qcom_nand_controller *)
- ((u8 *)chip->controller - sizeof(struct qcom_nand_controller));
+ return container_of(chip->controller, struct qcom_nand_controller,
+ controller);
}
static u32 nandc_read(struct qcom_nand_controller *nandc, int offset)
@@ -165,9 +165,9 @@ static void nandc_set_read_loc_first(struct nand_chip *chip,
{
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
__le32 locreg_val;
- u32 val = (((cw_offset) << READ_LOCATION_OFFSET) |
- ((read_size) << READ_LOCATION_SIZE) |
- ((is_last_read_loc) << READ_LOCATION_LAST));
+ u32 val = FIELD_PREP(READ_LOCATION_OFFSET_MASK, cw_offset) |
+ FIELD_PREP(READ_LOCATION_SIZE_MASK, read_size) |
+ FIELD_PREP(READ_LOCATION_LAST_MASK, is_last_read_loc);
locreg_val = cpu_to_le32(val);
@@ -197,9 +197,9 @@ static void nandc_set_read_loc_last(struct nand_chip *chip,
{
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
__le32 locreg_val;
- u32 val = (((cw_offset) << READ_LOCATION_OFFSET) |
- ((read_size) << READ_LOCATION_SIZE) |
- ((is_last_read_loc) << READ_LOCATION_LAST));
+ u32 val = FIELD_PREP(READ_LOCATION_OFFSET_MASK, cw_offset) |
+ FIELD_PREP(READ_LOCATION_SIZE_MASK, read_size) |
+ FIELD_PREP(READ_LOCATION_LAST_MASK, is_last_read_loc);
locreg_val = cpu_to_le32(val);
@@ -271,14 +271,14 @@ static void update_rw_regs(struct qcom_nand_host *host, int num_cw, bool read, i
}
if (host->use_ecc) {
- cfg0 = cpu_to_le32((host->cfg0 & ~(7U << CW_PER_PAGE)) |
- (num_cw - 1) << CW_PER_PAGE);
+ cfg0 = cpu_to_le32((host->cfg0 & ~CW_PER_PAGE_MASK) |
+ FIELD_PREP(CW_PER_PAGE_MASK, (num_cw - 1)));
cfg1 = cpu_to_le32(host->cfg1);
ecc_bch_cfg = cpu_to_le32(host->ecc_bch_cfg);
} else {
- cfg0 = cpu_to_le32((host->cfg0_raw & ~(7U << CW_PER_PAGE)) |
- (num_cw - 1) << CW_PER_PAGE);
+ cfg0 = cpu_to_le32((host->cfg0_raw & ~CW_PER_PAGE_MASK) |
+ FIELD_PREP(CW_PER_PAGE_MASK, (num_cw - 1)));
cfg1 = cpu_to_le32(host->cfg1_raw);
ecc_bch_cfg = cpu_to_le32(ECC_CFG_ECC_DISABLE);
@@ -882,12 +882,12 @@ static void qcom_nandc_codeword_fixup(struct qcom_nand_host *host, int page)
host->bbm_size - host->cw_data;
host->cfg0 &= ~(SPARE_SIZE_BYTES_MASK | UD_SIZE_BYTES_MASK);
- host->cfg0 |= host->spare_bytes << SPARE_SIZE_BYTES |
- host->cw_data << UD_SIZE_BYTES;
+ host->cfg0 |= FIELD_PREP(SPARE_SIZE_BYTES_MASK, host->spare_bytes) |
+ FIELD_PREP(UD_SIZE_BYTES_MASK, host->cw_data);
host->ecc_bch_cfg &= ~ECC_NUM_DATA_BYTES_MASK;
- host->ecc_bch_cfg |= host->cw_data << ECC_NUM_DATA_BYTES;
- host->ecc_buf_cfg = (host->cw_data - 1) << NUM_STEPS;
+ host->ecc_bch_cfg |= FIELD_PREP(ECC_NUM_DATA_BYTES_MASK, host->cw_data);
+ host->ecc_buf_cfg = FIELD_PREP(NUM_STEPS_MASK, host->cw_data - 1);
}
/* implements ecc->read_page() */
@@ -1379,7 +1379,7 @@ static int qcom_nand_attach_chip(struct nand_chip *chip)
struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip);
int cwperpage, bad_block_byte, ret;
bool wide_bus;
- int ecc_mode = 1;
+ int ecc_mode = ECC_MODE_8BIT;
/* controller only supports 512 bytes data steps */
ecc->size = NANDC_STEP_SIZE;
@@ -1400,7 +1400,7 @@ static int qcom_nand_attach_chip(struct nand_chip *chip)
if (ecc->strength >= 8) {
/* 8 bit ECC defaults to BCH ECC on all platforms */
host->bch_enabled = true;
- ecc_mode = 1;
+ ecc_mode = ECC_MODE_8BIT;
if (wide_bus) {
host->ecc_bytes_hw = 14;
@@ -1420,7 +1420,7 @@ static int qcom_nand_attach_chip(struct nand_chip *chip)
if (nandc->props->ecc_modes & ECC_BCH_4BIT) {
/* BCH */
host->bch_enabled = true;
- ecc_mode = 0;
+ ecc_mode = ECC_MODE_4BIT;
if (wide_bus) {
host->ecc_bytes_hw = 8;
@@ -1531,7 +1531,7 @@ static int qcom_nand_attach_chip(struct nand_chip *chip)
FIELD_PREP(ECC_PARITY_SIZE_BYTES_BCH_MASK, host->ecc_bytes_hw);
if (!nandc->props->qpic_version2)
- host->ecc_buf_cfg = 0x203 << NUM_STEPS;
+ host->ecc_buf_cfg = FIELD_PREP(NUM_STEPS_MASK, 0x203);
host->clrflashstatus = FS_READY_BSY_N;
host->clrreadstatus = 0xc0;
@@ -1817,7 +1817,7 @@ static int qcom_misc_cmd_type_exec(struct nand_chip *chip, const struct nand_sub
q_op.cmd_reg |= cpu_to_le32(PAGE_ACC | LAST_PAGE);
nandc->regs->addr0 = q_op.addr1_reg;
nandc->regs->addr1 = q_op.addr2_reg;
- nandc->regs->cfg0 = cpu_to_le32(host->cfg0_raw & ~(7 << CW_PER_PAGE));
+ nandc->regs->cfg0 = cpu_to_le32(host->cfg0_raw & ~CW_PER_PAGE_MASK);
nandc->regs->cfg1 = cpu_to_le32(host->cfg1_raw);
instrs = 3;
} else if (q_op.cmd_reg != cpu_to_le32(OP_RESET_DEVICE)) {
@@ -1863,7 +1863,12 @@ static int qcom_param_page_type_exec(struct nand_chip *chip, const struct nand_
const struct nand_op_instr *instr = NULL;
unsigned int op_id = 0;
unsigned int len = 0;
- int ret;
+ int ret, reg_base;
+
+ reg_base = NAND_READ_LOCATION_0;
+
+ if (nandc->props->qpic_version2)
+ reg_base = NAND_READ_LOCATION_LAST_CW_0;
ret = qcom_parse_instructions(chip, subop, &q_op);
if (ret)
@@ -1900,8 +1905,8 @@ static int qcom_param_page_type_exec(struct nand_chip *chip, const struct nand_
/* configure CMD1 and VLD for ONFI param probing in QPIC v1 */
if (!nandc->props->qpic_version2) {
nandc->regs->vld = cpu_to_le32((nandc->vld & ~READ_START_VLD));
- nandc->regs->cmd1 = cpu_to_le32((nandc->cmd1 & ~(0xFF << READ_ADDR))
- | NAND_CMD_PARAM << READ_ADDR);
+ nandc->regs->cmd1 = cpu_to_le32((nandc->cmd1 & ~READ_ADDR_MASK) |
+ FIELD_PREP(READ_ADDR_MASK, NAND_CMD_PARAM));
}
nandc->regs->exec = cpu_to_le32(1);
@@ -1915,14 +1920,17 @@ static int qcom_param_page_type_exec(struct nand_chip *chip, const struct nand_
op_id = q_op.data_instr_idx;
len = nand_subop_get_data_len(subop, op_id);
- nandc_set_read_loc(chip, 0, 0, 0, len, 1);
+ if (nandc->props->qpic_version2)
+ nandc_set_read_loc_last(chip, reg_base, 0, len, 1);
+ else
+ nandc_set_read_loc_first(chip, reg_base, 0, len, 1);
if (!nandc->props->qpic_version2) {
qcom_write_reg_dma(nandc, &nandc->regs->vld, NAND_DEV_CMD_VLD, 1, 0);
qcom_write_reg_dma(nandc, &nandc->regs->cmd1, NAND_DEV_CMD1, 1, NAND_BAM_NEXT_SGL);
}
- nandc->buf_count = len;
+ nandc->buf_count = 512;
memset(nandc->data_buffer, 0xff, nandc->buf_count);
config_nand_single_cw_page_read(chip, false, 0);
@@ -2026,8 +2034,8 @@ static int qcom_nandc_setup(struct qcom_nand_controller *nandc)
{
u32 nand_ctrl;
- nand_controller_init(nandc->controller);
- nandc->controller->ops = &qcom_nandc_ops;
+ nand_controller_init(&nandc->controller);
+ nandc->controller.ops = &qcom_nandc_ops;
/* kill onenand */
if (!nandc->props->nandc_part_of_qpic)
@@ -2167,7 +2175,7 @@ static int qcom_nand_host_init_and_register(struct qcom_nand_controller *nandc,
chip->legacy.block_bad = qcom_nandc_block_bad;
chip->legacy.block_markbad = qcom_nandc_block_markbad;
- chip->controller = nandc->controller;
+ chip->controller = &nandc->controller;
chip->options |= NAND_NO_SUBPAGE_WRITE | NAND_USES_DMA |
NAND_SKIP_BBTSCAN;
@@ -2198,16 +2206,14 @@ err:
static int qcom_probe_nand_devices(struct qcom_nand_controller *nandc)
{
struct device *dev = nandc->dev;
- struct device_node *dn = dev->of_node, *child;
+ struct device_node *dn = dev->of_node;
struct qcom_nand_host *host;
int ret = -ENODEV;
- for_each_available_child_of_node(dn, child) {
+ for_each_available_child_of_node_scoped(dn, child) {
host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL);
- if (!host) {
- of_node_put(child);
+ if (!host)
return -ENOMEM;
- }
ret = qcom_nand_host_init_and_register(nandc, host, child);
if (ret) {
@@ -2250,21 +2256,17 @@ static int qcom_nandc_parse_dt(struct platform_device *pdev)
static int qcom_nandc_probe(struct platform_device *pdev)
{
struct qcom_nand_controller *nandc;
- struct nand_controller *controller;
const void *dev_data;
struct device *dev = &pdev->dev;
struct resource *res;
int ret;
- nandc = devm_kzalloc(&pdev->dev, sizeof(*nandc) + sizeof(*controller),
- GFP_KERNEL);
+ nandc = devm_kzalloc(&pdev->dev, sizeof(*nandc), GFP_KERNEL);
if (!nandc)
return -ENOMEM;
- controller = (struct nand_controller *)&nandc[1];
platform_set_drvdata(pdev, nandc);
nandc->dev = dev;
- nandc->controller = controller;
dev_data = of_device_get_match_data(dev);
if (!dev_data) {
@@ -2360,6 +2362,7 @@ static const struct qcom_nandc_props ipq806x_nandc_props = {
.supports_bam = false,
.use_codeword_fixup = true,
.dev_cmd_reg_start = 0x0,
+ .bam_offset = 0x30000,
};
static const struct qcom_nandc_props ipq4019_nandc_props = {
@@ -2367,6 +2370,7 @@ static const struct qcom_nandc_props ipq4019_nandc_props = {
.supports_bam = true,
.nandc_part_of_qpic = true,
.dev_cmd_reg_start = 0x0,
+ .bam_offset = 0x30000,
};
static const struct qcom_nandc_props ipq8074_nandc_props = {
@@ -2374,6 +2378,7 @@ static const struct qcom_nandc_props ipq8074_nandc_props = {
.supports_bam = true,
.nandc_part_of_qpic = true,
.dev_cmd_reg_start = 0x7000,
+ .bam_offset = 0x30000,
};
static const struct qcom_nandc_props sdx55_nandc_props = {
@@ -2382,6 +2387,7 @@ static const struct qcom_nandc_props sdx55_nandc_props = {
.nandc_part_of_qpic = true,
.qpic_version2 = true,
.dev_cmd_reg_start = 0x7000,
+ .bam_offset = 0x30000,
};
/*
diff --git a/drivers/mtd/nand/raw/r852.c b/drivers/mtd/nand/raw/r852.c
index b07c2f8b4035..92c47d351c27 100644
--- a/drivers/mtd/nand/raw/r852.c
+++ b/drivers/mtd/nand/raw/r852.c
@@ -387,6 +387,9 @@ static int r852_wait(struct nand_chip *chip)
static int r852_ready(struct nand_chip *chip)
{
struct r852_device *dev = r852_get_dev(nand_to_mtd(chip));
+ if (dev->card_unstable)
+ return 0;
+
return !(r852_read_reg(dev, R852_CARD_STA) & R852_CARD_STA_BUSY);
}
@@ -864,7 +867,7 @@ static int r852_probe(struct pci_dev *pci_dev, const struct pci_device_id *id)
error = -ENOMEM;
/* init nand chip, but register it only on card insert */
- chip = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);
+ chip = kzalloc_obj(struct nand_chip);
if (!chip)
goto error4;
@@ -880,7 +883,7 @@ static int r852_probe(struct pci_dev *pci_dev, const struct pci_device_id *id)
chip->legacy.write_buf = r852_write_buf;
/* init our device structure */
- dev = kzalloc(sizeof(struct r852_device), GFP_KERNEL);
+ dev = kzalloc_obj(struct r852_device);
if (!dev)
goto error5;
@@ -1067,8 +1070,8 @@ static int r852_resume(struct device *device)
static const struct pci_device_id r852_pci_id_tbl[] = {
- { PCI_VDEVICE(RICOH, 0x0852), },
- { },
+ { PCI_VDEVICE(RICOH, 0x0852) },
+ { }
};
MODULE_DEVICE_TABLE(pci, r852_pci_id_tbl);
diff --git a/drivers/mtd/nand/raw/renesas-nand-controller.c b/drivers/mtd/nand/raw/renesas-nand-controller.c
index 44f6603736d1..201dd62b9990 100644
--- a/drivers/mtd/nand/raw/renesas-nand-controller.c
+++ b/drivers/mtd/nand/raw/renesas-nand-controller.c
@@ -426,6 +426,9 @@ static int rnandc_read_page_hw_ecc(struct nand_chip *chip, u8 *buf,
/* Configure DMA */
dma_addr = dma_map_single(rnandc->dev, rnandc->buf, mtd->writesize,
DMA_FROM_DEVICE);
+ if (dma_mapping_error(rnandc->dev, dma_addr))
+ return -ENOMEM;
+
writel(dma_addr, rnandc->regs + DMA_ADDR_LOW_REG);
writel(mtd->writesize, rnandc->regs + DMA_CNT_REG);
writel(DMA_TLVL_MAX, rnandc->regs + DMA_TLVL_REG);
@@ -606,6 +609,9 @@ static int rnandc_write_page_hw_ecc(struct nand_chip *chip, const u8 *buf,
/* Configure DMA */
dma_addr = dma_map_single(rnandc->dev, (void *)rnandc->buf, mtd->writesize,
DMA_TO_DEVICE);
+ if (dma_mapping_error(rnandc->dev, dma_addr))
+ return -ENOMEM;
+
writel(dma_addr, rnandc->regs + DMA_ADDR_LOW_REG);
writel(mtd->writesize, rnandc->regs + DMA_CNT_REG);
writel(DMA_TLVL_MAX, rnandc->regs + DMA_TLVL_REG);
@@ -1330,7 +1336,10 @@ static int rnandc_probe(struct platform_device *pdev)
if (IS_ERR(rnandc->regs))
return PTR_ERR(rnandc->regs);
- devm_pm_runtime_enable(&pdev->dev);
+ ret = devm_pm_runtime_enable(&pdev->dev);
+ if (ret)
+ return ret;
+
ret = pm_runtime_resume_and_get(&pdev->dev);
if (ret < 0)
return ret;
diff --git a/drivers/mtd/nand/raw/rockchip-nand-controller.c b/drivers/mtd/nand/raw/rockchip-nand-controller.c
index 63e7b9e39a5a..9444ba02696d 100644
--- a/drivers/mtd/nand/raw/rockchip-nand-controller.c
+++ b/drivers/mtd/nand/raw/rockchip-nand-controller.c
@@ -656,9 +656,16 @@ static int rk_nfc_write_page_hwecc(struct nand_chip *chip, const u8 *buf,
dma_data = dma_map_single(nfc->dev, (void *)nfc->page_buf,
mtd->writesize, DMA_TO_DEVICE);
+ if (dma_mapping_error(nfc->dev, dma_data))
+ return -ENOMEM;
+
dma_oob = dma_map_single(nfc->dev, nfc->oob_buf,
ecc->steps * oob_step,
DMA_TO_DEVICE);
+ if (dma_mapping_error(nfc->dev, dma_oob)) {
+ dma_unmap_single(nfc->dev, dma_data, mtd->writesize, DMA_TO_DEVICE);
+ return -ENOMEM;
+ }
reinit_completion(&nfc->done);
writel(INT_DMA, nfc->regs + nfc->cfg->int_en_off);
@@ -772,9 +779,17 @@ static int rk_nfc_read_page_hwecc(struct nand_chip *chip, u8 *buf, int oob_on,
dma_data = dma_map_single(nfc->dev, nfc->page_buf,
mtd->writesize,
DMA_FROM_DEVICE);
+ if (dma_mapping_error(nfc->dev, dma_data))
+ return -ENOMEM;
+
dma_oob = dma_map_single(nfc->dev, nfc->oob_buf,
ecc->steps * oob_step,
DMA_FROM_DEVICE);
+ if (dma_mapping_error(nfc->dev, dma_oob)) {
+ dma_unmap_single(nfc->dev, dma_data, mtd->writesize,
+ DMA_FROM_DEVICE);
+ return -ENOMEM;
+ }
/*
* The first blocks (4, 8 or 16 depending on the device)
@@ -1490,4 +1505,3 @@ module_platform_driver(rk_nfc_driver);
MODULE_LICENSE("Dual MIT/GPL");
MODULE_AUTHOR("Yifeng Zhao <yifeng.zhao@rock-chips.com>");
MODULE_DESCRIPTION("Rockchip Nand Flash Controller Driver");
-MODULE_ALIAS("platform:rockchip-nand-controller");
diff --git a/drivers/mtd/nand/raw/s3c2410.c b/drivers/mtd/nand/raw/s3c2410.c
deleted file mode 100644
index 229f2e87d56e..000000000000
--- a/drivers/mtd/nand/raw/s3c2410.c
+++ /dev/null
@@ -1,1230 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * Copyright © 2004-2008 Simtec Electronics
- * http://armlinux.simtec.co.uk/
- * Ben Dooks <ben@simtec.co.uk>
- *
- * Samsung S3C2410/S3C2440/S3C2412 NAND driver
-*/
-
-#define pr_fmt(fmt) "nand-s3c2410: " fmt
-
-#ifdef CONFIG_MTD_NAND_S3C2410_DEBUG
-#define DEBUG
-#endif
-
-#include <linux/module.h>
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/string.h>
-#include <linux/io.h>
-#include <linux/ioport.h>
-#include <linux/platform_device.h>
-#include <linux/delay.h>
-#include <linux/err.h>
-#include <linux/slab.h>
-#include <linux/clk.h>
-#include <linux/cpufreq.h>
-#include <linux/of.h>
-
-#include <linux/mtd/mtd.h>
-#include <linux/mtd/rawnand.h>
-#include <linux/mtd/partitions.h>
-
-#include <linux/platform_data/mtd-nand-s3c2410.h>
-
-#define S3C2410_NFREG(x) (x)
-
-#define S3C2410_NFCONF S3C2410_NFREG(0x00)
-#define S3C2410_NFCMD S3C2410_NFREG(0x04)
-#define S3C2410_NFADDR S3C2410_NFREG(0x08)
-#define S3C2410_NFDATA S3C2410_NFREG(0x0C)
-#define S3C2410_NFSTAT S3C2410_NFREG(0x10)
-#define S3C2410_NFECC S3C2410_NFREG(0x14)
-#define S3C2440_NFCONT S3C2410_NFREG(0x04)
-#define S3C2440_NFCMD S3C2410_NFREG(0x08)
-#define S3C2440_NFADDR S3C2410_NFREG(0x0C)
-#define S3C2440_NFDATA S3C2410_NFREG(0x10)
-#define S3C2440_NFSTAT S3C2410_NFREG(0x20)
-#define S3C2440_NFMECC0 S3C2410_NFREG(0x2C)
-#define S3C2412_NFSTAT S3C2410_NFREG(0x28)
-#define S3C2412_NFMECC0 S3C2410_NFREG(0x34)
-#define S3C2410_NFCONF_EN (1<<15)
-#define S3C2410_NFCONF_INITECC (1<<12)
-#define S3C2410_NFCONF_nFCE (1<<11)
-#define S3C2410_NFCONF_TACLS(x) ((x)<<8)
-#define S3C2410_NFCONF_TWRPH0(x) ((x)<<4)
-#define S3C2410_NFCONF_TWRPH1(x) ((x)<<0)
-#define S3C2410_NFSTAT_BUSY (1<<0)
-#define S3C2440_NFCONF_TACLS(x) ((x)<<12)
-#define S3C2440_NFCONF_TWRPH0(x) ((x)<<8)
-#define S3C2440_NFCONF_TWRPH1(x) ((x)<<4)
-#define S3C2440_NFCONT_INITECC (1<<4)
-#define S3C2440_NFCONT_nFCE (1<<1)
-#define S3C2440_NFCONT_ENABLE (1<<0)
-#define S3C2440_NFSTAT_READY (1<<0)
-#define S3C2412_NFCONF_NANDBOOT (1<<31)
-#define S3C2412_NFCONT_INIT_MAIN_ECC (1<<5)
-#define S3C2412_NFCONT_nFCE0 (1<<1)
-#define S3C2412_NFSTAT_READY (1<<0)
-
-/* new oob placement block for use with hardware ecc generation
- */
-static int s3c2410_ooblayout_ecc(struct mtd_info *mtd, int section,
- struct mtd_oob_region *oobregion)
-{
- if (section)
- return -ERANGE;
-
- oobregion->offset = 0;
- oobregion->length = 3;
-
- return 0;
-}
-
-static int s3c2410_ooblayout_free(struct mtd_info *mtd, int section,
- struct mtd_oob_region *oobregion)
-{
- if (section)
- return -ERANGE;
-
- oobregion->offset = 8;
- oobregion->length = 8;
-
- return 0;
-}
-
-static const struct mtd_ooblayout_ops s3c2410_ooblayout_ops = {
- .ecc = s3c2410_ooblayout_ecc,
- .free = s3c2410_ooblayout_free,
-};
-
-/* controller and mtd information */
-
-struct s3c2410_nand_info;
-
-/**
- * struct s3c2410_nand_mtd - driver MTD structure
- * @chip: The NAND chip information.
- * @set: The platform information supplied for this set of NAND chips.
- * @info: Link back to the hardware information.
-*/
-struct s3c2410_nand_mtd {
- struct nand_chip chip;
- struct s3c2410_nand_set *set;
- struct s3c2410_nand_info *info;
-};
-
-enum s3c_cpu_type {
- TYPE_S3C2410,
- TYPE_S3C2412,
- TYPE_S3C2440,
-};
-
-enum s3c_nand_clk_state {
- CLOCK_DISABLE = 0,
- CLOCK_ENABLE,
- CLOCK_SUSPEND,
-};
-
-/* overview of the s3c2410 nand state */
-
-/**
- * struct s3c2410_nand_info - NAND controller state.
- * @controller: Base controller structure.
- * @mtds: An array of MTD instances on this controller.
- * @platform: The platform data for this board.
- * @device: The platform device we bound to.
- * @clk: The clock resource for this controller.
- * @regs: The area mapped for the hardware registers.
- * @sel_reg: Pointer to the register controlling the NAND selection.
- * @sel_bit: The bit in @sel_reg to select the NAND chip.
- * @mtd_count: The number of MTDs created from this controller.
- * @save_sel: The contents of @sel_reg to be saved over suspend.
- * @clk_rate: The clock rate from @clk.
- * @clk_state: The current clock state.
- * @cpu_type: The exact type of this controller.
- */
-struct s3c2410_nand_info {
- /* mtd info */
- struct nand_controller controller;
- struct s3c2410_nand_mtd *mtds;
- struct s3c2410_platform_nand *platform;
-
- /* device info */
- struct device *device;
- struct clk *clk;
- void __iomem *regs;
- void __iomem *sel_reg;
- int sel_bit;
- int mtd_count;
- unsigned long save_sel;
- unsigned long clk_rate;
- enum s3c_nand_clk_state clk_state;
-
- enum s3c_cpu_type cpu_type;
-};
-
-struct s3c24XX_nand_devtype_data {
- enum s3c_cpu_type type;
-};
-
-static const struct s3c24XX_nand_devtype_data s3c2410_nand_devtype_data = {
- .type = TYPE_S3C2410,
-};
-
-static const struct s3c24XX_nand_devtype_data s3c2412_nand_devtype_data = {
- .type = TYPE_S3C2412,
-};
-
-static const struct s3c24XX_nand_devtype_data s3c2440_nand_devtype_data = {
- .type = TYPE_S3C2440,
-};
-
-/* conversion functions */
-
-static struct s3c2410_nand_mtd *s3c2410_nand_mtd_toours(struct mtd_info *mtd)
-{
- return container_of(mtd_to_nand(mtd), struct s3c2410_nand_mtd,
- chip);
-}
-
-static struct s3c2410_nand_info *s3c2410_nand_mtd_toinfo(struct mtd_info *mtd)
-{
- return s3c2410_nand_mtd_toours(mtd)->info;
-}
-
-static struct s3c2410_nand_info *to_nand_info(struct platform_device *dev)
-{
- return platform_get_drvdata(dev);
-}
-
-static struct s3c2410_platform_nand *to_nand_plat(struct platform_device *dev)
-{
- return dev_get_platdata(&dev->dev);
-}
-
-static inline int allow_clk_suspend(struct s3c2410_nand_info *info)
-{
-#ifdef CONFIG_MTD_NAND_S3C2410_CLKSTOP
- return 1;
-#else
- return 0;
-#endif
-}
-
-/**
- * s3c2410_nand_clk_set_state - Enable, disable or suspend NAND clock.
- * @info: The controller instance.
- * @new_state: State to which clock should be set.
- */
-static void s3c2410_nand_clk_set_state(struct s3c2410_nand_info *info,
- enum s3c_nand_clk_state new_state)
-{
- if (!allow_clk_suspend(info) && new_state == CLOCK_SUSPEND)
- return;
-
- if (info->clk_state == CLOCK_ENABLE) {
- if (new_state != CLOCK_ENABLE)
- clk_disable_unprepare(info->clk);
- } else {
- if (new_state == CLOCK_ENABLE)
- clk_prepare_enable(info->clk);
- }
-
- info->clk_state = new_state;
-}
-
-/* timing calculations */
-
-#define NS_IN_KHZ 1000000
-
-/**
- * s3c_nand_calc_rate - calculate timing data.
- * @wanted: The cycle time in nanoseconds.
- * @clk: The clock rate in kHz.
- * @max: The maximum divider value.
- *
- * Calculate the timing value from the given parameters.
- */
-static int s3c_nand_calc_rate(int wanted, unsigned long clk, int max)
-{
- int result;
-
- result = DIV_ROUND_UP((wanted * clk), NS_IN_KHZ);
-
- pr_debug("result %d from %ld, %d\n", result, clk, wanted);
-
- if (result > max) {
- pr_err("%d ns is too big for current clock rate %ld\n",
- wanted, clk);
- return -1;
- }
-
- if (result < 1)
- result = 1;
-
- return result;
-}
-
-#define to_ns(ticks, clk) (((ticks) * NS_IN_KHZ) / (unsigned int)(clk))
-
-/* controller setup */
-
-/**
- * s3c2410_nand_setrate - setup controller timing information.
- * @info: The controller instance.
- *
- * Given the information supplied by the platform, calculate and set
- * the necessary timing registers in the hardware to generate the
- * necessary timing cycles to the hardware.
- */
-static int s3c2410_nand_setrate(struct s3c2410_nand_info *info)
-{
- struct s3c2410_platform_nand *plat = info->platform;
- int tacls_max = (info->cpu_type == TYPE_S3C2412) ? 8 : 4;
- int tacls, twrph0, twrph1;
- unsigned long clkrate = clk_get_rate(info->clk);
- unsigned long set, cfg, mask;
- unsigned long flags;
-
- /* calculate the timing information for the controller */
-
- info->clk_rate = clkrate;
- clkrate /= 1000; /* turn clock into kHz for ease of use */
-
- if (plat != NULL) {
- tacls = s3c_nand_calc_rate(plat->tacls, clkrate, tacls_max);
- twrph0 = s3c_nand_calc_rate(plat->twrph0, clkrate, 8);
- twrph1 = s3c_nand_calc_rate(plat->twrph1, clkrate, 8);
- } else {
- /* default timings */
- tacls = tacls_max;
- twrph0 = 8;
- twrph1 = 8;
- }
-
- if (tacls < 0 || twrph0 < 0 || twrph1 < 0) {
- dev_err(info->device, "cannot get suitable timings\n");
- return -EINVAL;
- }
-
- dev_info(info->device, "Tacls=%d, %dns Twrph0=%d %dns, Twrph1=%d %dns\n",
- tacls, to_ns(tacls, clkrate), twrph0, to_ns(twrph0, clkrate),
- twrph1, to_ns(twrph1, clkrate));
-
- switch (info->cpu_type) {
- case TYPE_S3C2410:
- mask = (S3C2410_NFCONF_TACLS(3) |
- S3C2410_NFCONF_TWRPH0(7) |
- S3C2410_NFCONF_TWRPH1(7));
- set = S3C2410_NFCONF_EN;
- set |= S3C2410_NFCONF_TACLS(tacls - 1);
- set |= S3C2410_NFCONF_TWRPH0(twrph0 - 1);
- set |= S3C2410_NFCONF_TWRPH1(twrph1 - 1);
- break;
-
- case TYPE_S3C2440:
- case TYPE_S3C2412:
- mask = (S3C2440_NFCONF_TACLS(tacls_max - 1) |
- S3C2440_NFCONF_TWRPH0(7) |
- S3C2440_NFCONF_TWRPH1(7));
-
- set = S3C2440_NFCONF_TACLS(tacls - 1);
- set |= S3C2440_NFCONF_TWRPH0(twrph0 - 1);
- set |= S3C2440_NFCONF_TWRPH1(twrph1 - 1);
- break;
-
- default:
- BUG();
- }
-
- local_irq_save(flags);
-
- cfg = readl(info->regs + S3C2410_NFCONF);
- cfg &= ~mask;
- cfg |= set;
- writel(cfg, info->regs + S3C2410_NFCONF);
-
- local_irq_restore(flags);
-
- dev_dbg(info->device, "NF_CONF is 0x%lx\n", cfg);
-
- return 0;
-}
-
-/**
- * s3c2410_nand_inithw - basic hardware initialisation
- * @info: The hardware state.
- *
- * Do the basic initialisation of the hardware, using s3c2410_nand_setrate()
- * to setup the hardware access speeds and set the controller to be enabled.
-*/
-static int s3c2410_nand_inithw(struct s3c2410_nand_info *info)
-{
- int ret;
-
- ret = s3c2410_nand_setrate(info);
- if (ret < 0)
- return ret;
-
- switch (info->cpu_type) {
- case TYPE_S3C2410:
- default:
- break;
-
- case TYPE_S3C2440:
- case TYPE_S3C2412:
- /* enable the controller and de-assert nFCE */
-
- writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT);
- }
-
- return 0;
-}
-
-/**
- * s3c2410_nand_select_chip - select the given nand chip
- * @this: NAND chip object.
- * @chip: The chip number.
- *
- * This is called by the MTD layer to either select a given chip for the
- * @mtd instance, or to indicate that the access has finished and the
- * chip can be de-selected.
- *
- * The routine ensures that the nFCE line is correctly setup, and any
- * platform specific selection code is called to route nFCE to the specific
- * chip.
- */
-static void s3c2410_nand_select_chip(struct nand_chip *this, int chip)
-{
- struct s3c2410_nand_info *info;
- struct s3c2410_nand_mtd *nmtd;
- unsigned long cur;
-
- nmtd = nand_get_controller_data(this);
- info = nmtd->info;
-
- if (chip != -1)
- s3c2410_nand_clk_set_state(info, CLOCK_ENABLE);
-
- cur = readl(info->sel_reg);
-
- if (chip == -1) {
- cur |= info->sel_bit;
- } else {
- if (nmtd->set != NULL && chip > nmtd->set->nr_chips) {
- dev_err(info->device, "invalid chip %d\n", chip);
- return;
- }
-
- if (info->platform != NULL) {
- if (info->platform->select_chip != NULL)
- (info->platform->select_chip) (nmtd->set, chip);
- }
-
- cur &= ~info->sel_bit;
- }
-
- writel(cur, info->sel_reg);
-
- if (chip == -1)
- s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND);
-}
-
-/* s3c2410_nand_hwcontrol
- *
- * Issue command and address cycles to the chip
-*/
-
-static void s3c2410_nand_hwcontrol(struct nand_chip *chip, int cmd,
- unsigned int ctrl)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-
- if (cmd == NAND_CMD_NONE)
- return;
-
- if (ctrl & NAND_CLE)
- writeb(cmd, info->regs + S3C2410_NFCMD);
- else
- writeb(cmd, info->regs + S3C2410_NFADDR);
-}
-
-/* command and control functions */
-
-static void s3c2440_nand_hwcontrol(struct nand_chip *chip, int cmd,
- unsigned int ctrl)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-
- if (cmd == NAND_CMD_NONE)
- return;
-
- if (ctrl & NAND_CLE)
- writeb(cmd, info->regs + S3C2440_NFCMD);
- else
- writeb(cmd, info->regs + S3C2440_NFADDR);
-}
-
-/* s3c2410_nand_devready()
- *
- * returns 0 if the nand is busy, 1 if it is ready
-*/
-
-static int s3c2410_nand_devready(struct nand_chip *chip)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
- return readb(info->regs + S3C2410_NFSTAT) & S3C2410_NFSTAT_BUSY;
-}
-
-static int s3c2440_nand_devready(struct nand_chip *chip)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
- return readb(info->regs + S3C2440_NFSTAT) & S3C2440_NFSTAT_READY;
-}
-
-static int s3c2412_nand_devready(struct nand_chip *chip)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
- return readb(info->regs + S3C2412_NFSTAT) & S3C2412_NFSTAT_READY;
-}
-
-/* ECC handling functions */
-
-static int s3c2410_nand_correct_data(struct nand_chip *chip, u_char *dat,
- u_char *read_ecc, u_char *calc_ecc)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
- unsigned int diff0, diff1, diff2;
- unsigned int bit, byte;
-
- pr_debug("%s(%p,%p,%p,%p)\n", __func__, mtd, dat, read_ecc, calc_ecc);
-
- diff0 = read_ecc[0] ^ calc_ecc[0];
- diff1 = read_ecc[1] ^ calc_ecc[1];
- diff2 = read_ecc[2] ^ calc_ecc[2];
-
- pr_debug("%s: rd %*phN calc %*phN diff %02x%02x%02x\n",
- __func__, 3, read_ecc, 3, calc_ecc,
- diff0, diff1, diff2);
-
- if (diff0 == 0 && diff1 == 0 && diff2 == 0)
- return 0; /* ECC is ok */
-
- /* sometimes people do not think about using the ECC, so check
- * to see if we have an 0xff,0xff,0xff read ECC and then ignore
- * the error, on the assumption that this is an un-eccd page.
- */
- if (read_ecc[0] == 0xff && read_ecc[1] == 0xff && read_ecc[2] == 0xff
- && info->platform->ignore_unset_ecc)
- return 0;
-
- /* Can we correct this ECC (ie, one row and column change).
- * Note, this is similar to the 256 error code on smartmedia */
-
- if (((diff0 ^ (diff0 >> 1)) & 0x55) == 0x55 &&
- ((diff1 ^ (diff1 >> 1)) & 0x55) == 0x55 &&
- ((diff2 ^ (diff2 >> 1)) & 0x55) == 0x55) {
- /* calculate the bit position of the error */
-
- bit = ((diff2 >> 3) & 1) |
- ((diff2 >> 4) & 2) |
- ((diff2 >> 5) & 4);
-
- /* calculate the byte position of the error */
-
- byte = ((diff2 << 7) & 0x100) |
- ((diff1 << 0) & 0x80) |
- ((diff1 << 1) & 0x40) |
- ((diff1 << 2) & 0x20) |
- ((diff1 << 3) & 0x10) |
- ((diff0 >> 4) & 0x08) |
- ((diff0 >> 3) & 0x04) |
- ((diff0 >> 2) & 0x02) |
- ((diff0 >> 1) & 0x01);
-
- dev_dbg(info->device, "correcting error bit %d, byte %d\n",
- bit, byte);
-
- dat[byte] ^= (1 << bit);
- return 1;
- }
-
- /* if there is only one bit difference in the ECC, then
- * one of only a row or column parity has changed, which
- * means the error is most probably in the ECC itself */
-
- diff0 |= (diff1 << 8);
- diff0 |= (diff2 << 16);
-
- /* equal to "(diff0 & ~(1 << __ffs(diff0)))" */
- if ((diff0 & (diff0 - 1)) == 0)
- return 1;
-
- return -1;
-}
-
-/* ECC functions
- *
- * These allow the s3c2410 and s3c2440 to use the controller's ECC
- * generator block to ECC the data as it passes through]
-*/
-
-static void s3c2410_nand_enable_hwecc(struct nand_chip *chip, int mode)
-{
- struct s3c2410_nand_info *info;
- unsigned long ctrl;
-
- info = s3c2410_nand_mtd_toinfo(nand_to_mtd(chip));
- ctrl = readl(info->regs + S3C2410_NFCONF);
- ctrl |= S3C2410_NFCONF_INITECC;
- writel(ctrl, info->regs + S3C2410_NFCONF);
-}
-
-static void s3c2412_nand_enable_hwecc(struct nand_chip *chip, int mode)
-{
- struct s3c2410_nand_info *info;
- unsigned long ctrl;
-
- info = s3c2410_nand_mtd_toinfo(nand_to_mtd(chip));
- ctrl = readl(info->regs + S3C2440_NFCONT);
- writel(ctrl | S3C2412_NFCONT_INIT_MAIN_ECC,
- info->regs + S3C2440_NFCONT);
-}
-
-static void s3c2440_nand_enable_hwecc(struct nand_chip *chip, int mode)
-{
- struct s3c2410_nand_info *info;
- unsigned long ctrl;
-
- info = s3c2410_nand_mtd_toinfo(nand_to_mtd(chip));
- ctrl = readl(info->regs + S3C2440_NFCONT);
- writel(ctrl | S3C2440_NFCONT_INITECC, info->regs + S3C2440_NFCONT);
-}
-
-static int s3c2410_nand_calculate_ecc(struct nand_chip *chip,
- const u_char *dat, u_char *ecc_code)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-
- ecc_code[0] = readb(info->regs + S3C2410_NFECC + 0);
- ecc_code[1] = readb(info->regs + S3C2410_NFECC + 1);
- ecc_code[2] = readb(info->regs + S3C2410_NFECC + 2);
-
- pr_debug("%s: returning ecc %*phN\n", __func__, 3, ecc_code);
-
- return 0;
-}
-
-static int s3c2412_nand_calculate_ecc(struct nand_chip *chip,
- const u_char *dat, u_char *ecc_code)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
- unsigned long ecc = readl(info->regs + S3C2412_NFMECC0);
-
- ecc_code[0] = ecc;
- ecc_code[1] = ecc >> 8;
- ecc_code[2] = ecc >> 16;
-
- pr_debug("%s: returning ecc %*phN\n", __func__, 3, ecc_code);
-
- return 0;
-}
-
-static int s3c2440_nand_calculate_ecc(struct nand_chip *chip,
- const u_char *dat, u_char *ecc_code)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
- unsigned long ecc = readl(info->regs + S3C2440_NFMECC0);
-
- ecc_code[0] = ecc;
- ecc_code[1] = ecc >> 8;
- ecc_code[2] = ecc >> 16;
-
- pr_debug("%s: returning ecc %06lx\n", __func__, ecc & 0xffffff);
-
- return 0;
-}
-
-/* over-ride the standard functions for a little more speed. We can
- * use read/write block to move the data buffers to/from the controller
-*/
-
-static void s3c2410_nand_read_buf(struct nand_chip *this, u_char *buf, int len)
-{
- readsb(this->legacy.IO_ADDR_R, buf, len);
-}
-
-static void s3c2440_nand_read_buf(struct nand_chip *this, u_char *buf, int len)
-{
- struct mtd_info *mtd = nand_to_mtd(this);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-
- readsl(info->regs + S3C2440_NFDATA, buf, len >> 2);
-
- /* cleanup if we've got less than a word to do */
- if (len & 3) {
- buf += len & ~3;
-
- for (; len & 3; len--)
- *buf++ = readb(info->regs + S3C2440_NFDATA);
- }
-}
-
-static void s3c2410_nand_write_buf(struct nand_chip *this, const u_char *buf,
- int len)
-{
- writesb(this->legacy.IO_ADDR_W, buf, len);
-}
-
-static void s3c2440_nand_write_buf(struct nand_chip *this, const u_char *buf,
- int len)
-{
- struct mtd_info *mtd = nand_to_mtd(this);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-
- writesl(info->regs + S3C2440_NFDATA, buf, len >> 2);
-
- /* cleanup any fractional write */
- if (len & 3) {
- buf += len & ~3;
-
- for (; len & 3; len--, buf++)
- writeb(*buf, info->regs + S3C2440_NFDATA);
- }
-}
-
-/* device management functions */
-
-static void s3c24xx_nand_remove(struct platform_device *pdev)
-{
- struct s3c2410_nand_info *info = to_nand_info(pdev);
-
- if (info == NULL)
- return;
-
- /* Release all our mtds and their partitions, then go through
- * freeing the resources used
- */
-
- if (info->mtds != NULL) {
- struct s3c2410_nand_mtd *ptr = info->mtds;
- int mtdno;
-
- for (mtdno = 0; mtdno < info->mtd_count; mtdno++, ptr++) {
- pr_debug("releasing mtd %d (%p)\n", mtdno, ptr);
- WARN_ON(mtd_device_unregister(nand_to_mtd(&ptr->chip)));
- nand_cleanup(&ptr->chip);
- }
- }
-
- /* free the common resources */
-
- if (!IS_ERR(info->clk))
- s3c2410_nand_clk_set_state(info, CLOCK_DISABLE);
-}
-
-static int s3c2410_nand_add_partition(struct s3c2410_nand_info *info,
- struct s3c2410_nand_mtd *mtd,
- struct s3c2410_nand_set *set)
-{
- if (set) {
- struct mtd_info *mtdinfo = nand_to_mtd(&mtd->chip);
-
- mtdinfo->name = set->name;
-
- return mtd_device_register(mtdinfo, set->partitions,
- set->nr_partitions);
- }
-
- return -ENODEV;
-}
-
-static int s3c2410_nand_setup_interface(struct nand_chip *chip, int csline,
- const struct nand_interface_config *conf)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
- struct s3c2410_platform_nand *pdata = info->platform;
- const struct nand_sdr_timings *timings;
- int tacls;
-
- timings = nand_get_sdr_timings(conf);
- if (IS_ERR(timings))
- return -ENOTSUPP;
-
- tacls = timings->tCLS_min - timings->tWP_min;
- if (tacls < 0)
- tacls = 0;
-
- pdata->tacls = DIV_ROUND_UP(tacls, 1000);
- pdata->twrph0 = DIV_ROUND_UP(timings->tWP_min, 1000);
- pdata->twrph1 = DIV_ROUND_UP(timings->tCLH_min, 1000);
-
- return s3c2410_nand_setrate(info);
-}
-
-/**
- * s3c2410_nand_init_chip - initialise a single instance of an chip
- * @info: The base NAND controller the chip is on.
- * @nmtd: The new controller MTD instance to fill in.
- * @set: The information passed from the board specific platform data.
- *
- * Initialise the given @nmtd from the information in @info and @set. This
- * readies the structure for use with the MTD layer functions by ensuring
- * all pointers are setup and the necessary control routines selected.
- */
-static void s3c2410_nand_init_chip(struct s3c2410_nand_info *info,
- struct s3c2410_nand_mtd *nmtd,
- struct s3c2410_nand_set *set)
-{
- struct device_node *np = info->device->of_node;
- struct nand_chip *chip = &nmtd->chip;
- void __iomem *regs = info->regs;
-
- nand_set_flash_node(chip, set->of_node);
-
- chip->legacy.write_buf = s3c2410_nand_write_buf;
- chip->legacy.read_buf = s3c2410_nand_read_buf;
- chip->legacy.select_chip = s3c2410_nand_select_chip;
- chip->legacy.chip_delay = 50;
- nand_set_controller_data(chip, nmtd);
- chip->options = set->options;
- chip->controller = &info->controller;
-
- /*
- * let's keep behavior unchanged for legacy boards booting via pdata and
- * auto-detect timings only when booting with a device tree.
- */
- if (!np)
- chip->options |= NAND_KEEP_TIMINGS;
-
- switch (info->cpu_type) {
- case TYPE_S3C2410:
- chip->legacy.IO_ADDR_W = regs + S3C2410_NFDATA;
- info->sel_reg = regs + S3C2410_NFCONF;
- info->sel_bit = S3C2410_NFCONF_nFCE;
- chip->legacy.cmd_ctrl = s3c2410_nand_hwcontrol;
- chip->legacy.dev_ready = s3c2410_nand_devready;
- break;
-
- case TYPE_S3C2440:
- chip->legacy.IO_ADDR_W = regs + S3C2440_NFDATA;
- info->sel_reg = regs + S3C2440_NFCONT;
- info->sel_bit = S3C2440_NFCONT_nFCE;
- chip->legacy.cmd_ctrl = s3c2440_nand_hwcontrol;
- chip->legacy.dev_ready = s3c2440_nand_devready;
- chip->legacy.read_buf = s3c2440_nand_read_buf;
- chip->legacy.write_buf = s3c2440_nand_write_buf;
- break;
-
- case TYPE_S3C2412:
- chip->legacy.IO_ADDR_W = regs + S3C2440_NFDATA;
- info->sel_reg = regs + S3C2440_NFCONT;
- info->sel_bit = S3C2412_NFCONT_nFCE0;
- chip->legacy.cmd_ctrl = s3c2440_nand_hwcontrol;
- chip->legacy.dev_ready = s3c2412_nand_devready;
-
- if (readl(regs + S3C2410_NFCONF) & S3C2412_NFCONF_NANDBOOT)
- dev_info(info->device, "System booted from NAND\n");
-
- break;
- }
-
- chip->legacy.IO_ADDR_R = chip->legacy.IO_ADDR_W;
-
- nmtd->info = info;
- nmtd->set = set;
-
- chip->ecc.engine_type = info->platform->engine_type;
-
- /*
- * If you use u-boot BBT creation code, specifying this flag will
- * let the kernel fish out the BBT from the NAND.
- */
- if (set->flash_bbt)
- chip->bbt_options |= NAND_BBT_USE_FLASH;
-}
-
-/**
- * s3c2410_nand_attach_chip - Init the ECC engine after NAND scan
- * @chip: The NAND chip
- *
- * This hook is called by the core after the identification of the NAND chip,
- * once the relevant per-chip information is up to date.. This call ensure that
- * we update the internal state accordingly.
- *
- * The internal state is currently limited to the ECC state information.
-*/
-static int s3c2410_nand_attach_chip(struct nand_chip *chip)
-{
- struct mtd_info *mtd = nand_to_mtd(chip);
- struct s3c2410_nand_info *info = s3c2410_nand_mtd_toinfo(mtd);
-
- switch (chip->ecc.engine_type) {
-
- case NAND_ECC_ENGINE_TYPE_NONE:
- dev_info(info->device, "ECC disabled\n");
- break;
-
- case NAND_ECC_ENGINE_TYPE_SOFT:
- /*
- * This driver expects Hamming based ECC when engine_type is set
- * to NAND_ECC_ENGINE_TYPE_SOFT. Force ecc.algo to
- * NAND_ECC_ALGO_HAMMING to avoid adding an extra ecc_algo field
- * to s3c2410_platform_nand.
- */
- chip->ecc.algo = NAND_ECC_ALGO_HAMMING;
- dev_info(info->device, "soft ECC\n");
- break;
-
- case NAND_ECC_ENGINE_TYPE_ON_HOST:
- chip->ecc.calculate = s3c2410_nand_calculate_ecc;
- chip->ecc.correct = s3c2410_nand_correct_data;
- chip->ecc.strength = 1;
-
- switch (info->cpu_type) {
- case TYPE_S3C2410:
- chip->ecc.hwctl = s3c2410_nand_enable_hwecc;
- chip->ecc.calculate = s3c2410_nand_calculate_ecc;
- break;
-
- case TYPE_S3C2412:
- chip->ecc.hwctl = s3c2412_nand_enable_hwecc;
- chip->ecc.calculate = s3c2412_nand_calculate_ecc;
- break;
-
- case TYPE_S3C2440:
- chip->ecc.hwctl = s3c2440_nand_enable_hwecc;
- chip->ecc.calculate = s3c2440_nand_calculate_ecc;
- break;
- }
-
- dev_dbg(info->device, "chip %p => page shift %d\n",
- chip, chip->page_shift);
-
- /* change the behaviour depending on whether we are using
- * the large or small page nand device */
- if (chip->page_shift > 10) {
- chip->ecc.size = 256;
- chip->ecc.bytes = 3;
- } else {
- chip->ecc.size = 512;
- chip->ecc.bytes = 3;
- mtd_set_ooblayout(nand_to_mtd(chip),
- &s3c2410_ooblayout_ops);
- }
-
- dev_info(info->device, "hardware ECC\n");
- break;
-
- default:
- dev_err(info->device, "invalid ECC mode!\n");
- return -EINVAL;
- }
-
- if (chip->bbt_options & NAND_BBT_USE_FLASH)
- chip->options |= NAND_SKIP_BBTSCAN;
-
- return 0;
-}
-
-static const struct nand_controller_ops s3c24xx_nand_controller_ops = {
- .attach_chip = s3c2410_nand_attach_chip,
- .setup_interface = s3c2410_nand_setup_interface,
-};
-
-static const struct of_device_id s3c24xx_nand_dt_ids[] = {
- {
- .compatible = "samsung,s3c2410-nand",
- .data = &s3c2410_nand_devtype_data,
- }, {
- /* also compatible with s3c6400 */
- .compatible = "samsung,s3c2412-nand",
- .data = &s3c2412_nand_devtype_data,
- }, {
- .compatible = "samsung,s3c2440-nand",
- .data = &s3c2440_nand_devtype_data,
- },
- { /* sentinel */ }
-};
-MODULE_DEVICE_TABLE(of, s3c24xx_nand_dt_ids);
-
-static int s3c24xx_nand_probe_dt(struct platform_device *pdev)
-{
- const struct s3c24XX_nand_devtype_data *devtype_data;
- struct s3c2410_platform_nand *pdata;
- struct s3c2410_nand_info *info = platform_get_drvdata(pdev);
- struct device_node *np = pdev->dev.of_node, *child;
- struct s3c2410_nand_set *sets;
-
- devtype_data = of_device_get_match_data(&pdev->dev);
- if (!devtype_data)
- return -ENODEV;
-
- info->cpu_type = devtype_data->type;
-
- pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
- if (!pdata)
- return -ENOMEM;
-
- pdev->dev.platform_data = pdata;
-
- pdata->nr_sets = of_get_child_count(np);
- if (!pdata->nr_sets)
- return 0;
-
- sets = devm_kcalloc(&pdev->dev, pdata->nr_sets, sizeof(*sets),
- GFP_KERNEL);
- if (!sets)
- return -ENOMEM;
-
- pdata->sets = sets;
-
- for_each_available_child_of_node(np, child) {
- sets->name = (char *)child->name;
- sets->of_node = child;
- sets->nr_chips = 1;
-
- of_node_get(child);
-
- sets++;
- }
-
- return 0;
-}
-
-static int s3c24xx_nand_probe_pdata(struct platform_device *pdev)
-{
- struct s3c2410_nand_info *info = platform_get_drvdata(pdev);
-
- info->cpu_type = platform_get_device_id(pdev)->driver_data;
-
- return 0;
-}
-
-/* s3c24xx_nand_probe
- *
- * called by device layer when it finds a device matching
- * one our driver can handled. This code checks to see if
- * it can allocate all necessary resources then calls the
- * nand layer to look for devices
-*/
-static int s3c24xx_nand_probe(struct platform_device *pdev)
-{
- struct s3c2410_platform_nand *plat;
- struct s3c2410_nand_info *info;
- struct s3c2410_nand_mtd *nmtd;
- struct s3c2410_nand_set *sets;
- struct resource *res;
- int err = 0;
- int size;
- int nr_sets;
- int setno;
-
- info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
- if (info == NULL) {
- err = -ENOMEM;
- goto exit_error;
- }
-
- platform_set_drvdata(pdev, info);
-
- nand_controller_init(&info->controller);
- info->controller.ops = &s3c24xx_nand_controller_ops;
-
- /* get the clock source and enable it */
-
- info->clk = devm_clk_get(&pdev->dev, "nand");
- if (IS_ERR(info->clk)) {
- dev_err(&pdev->dev, "failed to get clock\n");
- err = -ENOENT;
- goto exit_error;
- }
-
- s3c2410_nand_clk_set_state(info, CLOCK_ENABLE);
-
- if (pdev->dev.of_node)
- err = s3c24xx_nand_probe_dt(pdev);
- else
- err = s3c24xx_nand_probe_pdata(pdev);
-
- if (err)
- goto exit_error;
-
- plat = to_nand_plat(pdev);
-
- /* allocate and map the resource */
-
- /* currently we assume we have the one resource */
- res = pdev->resource;
- size = resource_size(res);
-
- info->device = &pdev->dev;
- info->platform = plat;
-
- info->regs = devm_ioremap_resource(&pdev->dev, res);
- if (IS_ERR(info->regs)) {
- err = PTR_ERR(info->regs);
- goto exit_error;
- }
-
- dev_dbg(&pdev->dev, "mapped registers at %p\n", info->regs);
-
- if (!plat->sets || plat->nr_sets < 1) {
- err = -EINVAL;
- goto exit_error;
- }
-
- sets = plat->sets;
- nr_sets = plat->nr_sets;
-
- info->mtd_count = nr_sets;
-
- /* allocate our information */
-
- size = nr_sets * sizeof(*info->mtds);
- info->mtds = devm_kzalloc(&pdev->dev, size, GFP_KERNEL);
- if (info->mtds == NULL) {
- err = -ENOMEM;
- goto exit_error;
- }
-
- /* initialise all possible chips */
-
- nmtd = info->mtds;
-
- for (setno = 0; setno < nr_sets; setno++, nmtd++, sets++) {
- struct mtd_info *mtd = nand_to_mtd(&nmtd->chip);
-
- pr_debug("initialising set %d (%p, info %p)\n",
- setno, nmtd, info);
-
- mtd->dev.parent = &pdev->dev;
- s3c2410_nand_init_chip(info, nmtd, sets);
-
- err = nand_scan(&nmtd->chip, sets ? sets->nr_chips : 1);
- if (err)
- goto exit_error;
-
- s3c2410_nand_add_partition(info, nmtd, sets);
- }
-
- /* initialise the hardware */
- err = s3c2410_nand_inithw(info);
- if (err != 0)
- goto exit_error;
-
- if (allow_clk_suspend(info)) {
- dev_info(&pdev->dev, "clock idle support enabled\n");
- s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND);
- }
-
- return 0;
-
- exit_error:
- s3c24xx_nand_remove(pdev);
-
- if (err == 0)
- err = -EINVAL;
- return err;
-}
-
-/* PM Support */
-#ifdef CONFIG_PM
-
-static int s3c24xx_nand_suspend(struct platform_device *dev, pm_message_t pm)
-{
- struct s3c2410_nand_info *info = platform_get_drvdata(dev);
-
- if (info) {
- info->save_sel = readl(info->sel_reg);
-
- /* For the moment, we must ensure nFCE is high during
- * the time we are suspended. This really should be
- * handled by suspending the MTDs we are using, but
- * that is currently not the case. */
-
- writel(info->save_sel | info->sel_bit, info->sel_reg);
-
- s3c2410_nand_clk_set_state(info, CLOCK_DISABLE);
- }
-
- return 0;
-}
-
-static int s3c24xx_nand_resume(struct platform_device *dev)
-{
- struct s3c2410_nand_info *info = platform_get_drvdata(dev);
- unsigned long sel;
-
- if (info) {
- s3c2410_nand_clk_set_state(info, CLOCK_ENABLE);
- s3c2410_nand_inithw(info);
-
- /* Restore the state of the nFCE line. */
-
- sel = readl(info->sel_reg);
- sel &= ~info->sel_bit;
- sel |= info->save_sel & info->sel_bit;
- writel(sel, info->sel_reg);
-
- s3c2410_nand_clk_set_state(info, CLOCK_SUSPEND);
- }
-
- return 0;
-}
-
-#else
-#define s3c24xx_nand_suspend NULL
-#define s3c24xx_nand_resume NULL
-#endif
-
-/* driver device registration */
-
-static const struct platform_device_id s3c24xx_driver_ids[] = {
- {
- .name = "s3c2410-nand",
- .driver_data = TYPE_S3C2410,
- }, {
- .name = "s3c2440-nand",
- .driver_data = TYPE_S3C2440,
- }, {
- .name = "s3c2412-nand",
- .driver_data = TYPE_S3C2412,
- }, {
- .name = "s3c6400-nand",
- .driver_data = TYPE_S3C2412, /* compatible with 2412 */
- },
- { }
-};
-
-MODULE_DEVICE_TABLE(platform, s3c24xx_driver_ids);
-
-static struct platform_driver s3c24xx_nand_driver = {
- .probe = s3c24xx_nand_probe,
- .remove = s3c24xx_nand_remove,
- .suspend = s3c24xx_nand_suspend,
- .resume = s3c24xx_nand_resume,
- .id_table = s3c24xx_driver_ids,
- .driver = {
- .name = "s3c24xx-nand",
- .of_match_table = s3c24xx_nand_dt_ids,
- },
-};
-
-module_platform_driver(s3c24xx_nand_driver);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
-MODULE_DESCRIPTION("S3C24XX MTD NAND driver");
diff --git a/drivers/mtd/nand/raw/sharpsl.c b/drivers/mtd/nand/raw/sharpsl.c
index 142e93b200a3..4154ab74f169 100644
--- a/drivers/mtd/nand/raw/sharpsl.c
+++ b/drivers/mtd/nand/raw/sharpsl.c
@@ -132,7 +132,7 @@ static int sharpsl_nand_probe(struct platform_device *pdev)
}
/* Allocate memory for MTD device structure and private data */
- sharpsl = kzalloc(sizeof(struct sharpsl_nand), GFP_KERNEL);
+ sharpsl = kzalloc_obj(struct sharpsl_nand);
if (!sharpsl)
return -ENOMEM;
diff --git a/drivers/mtd/nand/raw/stm32_fmc2_nand.c b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
index a960403081f1..c08d6b176372 100644
--- a/drivers/mtd/nand/raw/stm32_fmc2_nand.c
+++ b/drivers/mtd/nand/raw/stm32_fmc2_nand.c
@@ -272,6 +272,7 @@ struct stm32_fmc2_nfc {
struct sg_table dma_data_sg;
struct sg_table dma_ecc_sg;
u8 *ecc_buf;
+ dma_addr_t dma_ecc_addr;
int dma_ecc_len;
u32 tx_dma_max_burst;
u32 rx_dma_max_burst;
@@ -902,17 +903,10 @@ static int stm32_fmc2_nfc_xfer(struct nand_chip *chip, const u8 *buf,
if (!write_data && !raw) {
/* Configure DMA ECC status */
- p = nfc->ecc_buf;
for_each_sg(nfc->dma_ecc_sg.sgl, sg, eccsteps, s) {
- sg_set_buf(sg, p, nfc->dma_ecc_len);
- p += nfc->dma_ecc_len;
- }
-
- ret = dma_map_sg(nfc->dev, nfc->dma_ecc_sg.sgl,
- eccsteps, dma_data_dir);
- if (!ret) {
- ret = -EIO;
- goto err_unmap_data;
+ sg_dma_address(sg) = nfc->dma_ecc_addr +
+ s * nfc->dma_ecc_len;
+ sg_dma_len(sg) = nfc->dma_ecc_len;
}
desc_ecc = dmaengine_prep_slave_sg(nfc->dma_ecc_ch,
@@ -921,7 +915,7 @@ static int stm32_fmc2_nfc_xfer(struct nand_chip *chip, const u8 *buf,
DMA_PREP_INTERRUPT);
if (!desc_ecc) {
ret = -ENOMEM;
- goto err_unmap_ecc;
+ goto err_unmap_data;
}
reinit_completion(&nfc->dma_ecc_complete);
@@ -929,7 +923,7 @@ static int stm32_fmc2_nfc_xfer(struct nand_chip *chip, const u8 *buf,
desc_ecc->callback_param = &nfc->dma_ecc_complete;
ret = dma_submit_error(dmaengine_submit(desc_ecc));
if (ret)
- goto err_unmap_ecc;
+ goto err_unmap_data;
dma_async_issue_pending(nfc->dma_ecc_ch);
}
@@ -949,7 +943,7 @@ static int stm32_fmc2_nfc_xfer(struct nand_chip *chip, const u8 *buf,
if (!write_data && !raw)
dmaengine_terminate_all(nfc->dma_ecc_ch);
ret = -ETIMEDOUT;
- goto err_unmap_ecc;
+ goto err_unmap_data;
}
/* Wait DMA data transfer completion */
@@ -969,11 +963,6 @@ static int stm32_fmc2_nfc_xfer(struct nand_chip *chip, const u8 *buf,
}
}
-err_unmap_ecc:
- if (!write_data && !raw)
- dma_unmap_sg(nfc->dev, nfc->dma_ecc_sg.sgl,
- eccsteps, dma_data_dir);
-
err_unmap_data:
dma_unmap_sg(nfc->dev, nfc->dma_data_sg.sgl, eccsteps, dma_data_dir);
@@ -996,9 +985,21 @@ static int stm32_fmc2_nfc_seq_write(struct nand_chip *chip, const u8 *buf,
/* Write oob */
if (oob_required) {
- ret = nand_change_write_column_op(chip, mtd->writesize,
- chip->oob_poi, mtd->oobsize,
- false);
+ unsigned int offset_in_page = mtd->writesize;
+ const void *buf = chip->oob_poi;
+ unsigned int len = mtd->oobsize;
+
+ if (!raw) {
+ struct mtd_oob_region oob_free;
+
+ mtd_ooblayout_free(mtd, 0, &oob_free);
+ offset_in_page += oob_free.offset;
+ buf += oob_free.offset;
+ len = oob_free.length;
+ }
+
+ ret = nand_change_write_column_op(chip, offset_in_page,
+ buf, len, false);
if (ret)
return ret;
}
@@ -1610,7 +1611,8 @@ static int stm32_fmc2_nfc_dma_setup(struct stm32_fmc2_nfc *nfc)
return ret;
/* Allocate a buffer to store ECC status registers */
- nfc->ecc_buf = devm_kzalloc(nfc->dev, FMC2_MAX_ECC_BUF_LEN, GFP_KERNEL);
+ nfc->ecc_buf = dmam_alloc_coherent(nfc->dev, FMC2_MAX_ECC_BUF_LEN,
+ &nfc->dma_ecc_addr, GFP_KERNEL);
if (!nfc->ecc_buf)
return -ENOMEM;
@@ -2156,7 +2158,6 @@ static struct platform_driver stm32_fmc2_nfc_driver = {
};
module_platform_driver(stm32_fmc2_nfc_driver);
-MODULE_ALIAS("platform:stm32_fmc2_nfc");
MODULE_AUTHOR("Christophe Kerello <christophe.kerello@st.com>");
MODULE_DESCRIPTION("STMicroelectronics STM32 FMC2 NFC driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/mtd/nand/raw/sunxi_nand.c b/drivers/mtd/nand/raw/sunxi_nand.c
index fab371e3e9b7..02647565c8ba 100644
--- a/drivers/mtd/nand/raw/sunxi_nand.c
+++ b/drivers/mtd/nand/raw/sunxi_nand.c
@@ -45,13 +45,40 @@
#define NFC_REG_A23_IO_DATA 0x0300
#define NFC_REG_ECC_CTL 0x0034
#define NFC_REG_ECC_ST 0x0038
-#define NFC_REG_DEBUG 0x003C
-#define NFC_REG_ECC_ERR_CNT(x) ((0x0040 + (x)) & ~0x3)
-#define NFC_REG_USER_DATA(x) (0x0050 + ((x) * 4))
-#define NFC_REG_SPARE_AREA 0x00A0
-#define NFC_REG_PAT_ID 0x00A4
+#define NFC_REG_H6_PAT_FOUND 0x003C
+#define NFC_REG_A10_ECC_ERR_CNT 0x0040
+#define NFC_REG_H6_ECC_ERR_CNT 0x0050
+#define NFC_REG_ECC_ERR_CNT(nfc, x) ((nfc->caps->reg_ecc_err_cnt + (x)) & ~0x3)
+#define NFC_REG_H6_RDATA_CTL 0x0044
+#define NFC_REG_H6_RDATA_0 0x0048
+#define NFC_REG_H6_RDATA_1 0x004C
+#define NFC_REG_A10_USER_DATA 0x0050
+#define NFC_REG_H6_USER_DATA 0x0080
+#define NFC_REG_USER_DATA(nfc, x) (nfc->caps->reg_user_data + ((x) * 4))
+#define NFC_REG_H6_USER_DATA_LEN 0x0070
+/* A USER_DATA_LEN register can hold the length of 8 USER_DATA registers */
+#define NFC_REG_USER_DATA_LEN_CAPACITY 8
+#define NFC_REG_USER_DATA_LEN(nfc, step) \
+ (nfc->caps->reg_user_data_len + \
+ ((step) / NFC_REG_USER_DATA_LEN_CAPACITY) * 4)
+#define NFC_REG_SPARE_AREA(nfc) (nfc->caps->reg_spare_area)
+#define NFC_REG_A10_SPARE_AREA 0x00A0
+#define NFC_REG_PAT_ID(nfc) (nfc->caps->reg_pat_id)
+#define NFC_REG_A10_PAT_ID 0x00A4
#define NFC_REG_MDMA_ADDR 0x00C0
#define NFC_REG_MDMA_CNT 0x00C4
+#define NFC_REG_H6_EFNAND_STATUS 0x0110
+#define NFC_REG_H6_SPARE_AREA 0x0114
+#define NFC_REG_H6_PAT_ID 0x0118
+#define NFC_REG_H6_DDR2_SPEC_CTL 0x011C
+#define NFC_REG_H6_NDMA_MODE_CTL 0x0120
+#define NFC_REG_H6_MDMA_DLBA_REG 0x0200
+#define NFC_REG_H6_MDMA_STA 0x0204
+#define NFC_REG_H6_MDMA_INT_MAS 0x0208
+#define NFC_REG_H6_MDMA_DESC_ADDR 0x020C
+#define NFC_REG_H6_MDMA_BUF_ADDR 0x0210
+#define NFC_REG_H6_MDMA_CNT 0x0214
+
#define NFC_RAM0_BASE 0x0400
#define NFC_RAM1_BASE 0x0800
@@ -63,6 +90,7 @@
#define NFC_BUS_WIDTH_16 (1 << 2)
#define NFC_RB_SEL_MSK BIT(3)
#define NFC_RB_SEL(x) ((x) << 3)
+/* CE_SEL BIT 27 is meant to be used for GPIO chipselect */
#define NFC_CE_SEL_MSK GENMASK(26, 24)
#define NFC_CE_SEL(x) ((x) << 24)
#define NFC_CE_CTL BIT(6)
@@ -81,6 +109,9 @@
#define NFC_STA BIT(4)
#define NFC_NATCH_INT_FLAG BIT(5)
#define NFC_RB_STATE(x) BIT(x + 8)
+#define NFC_RB_STATE_MSK GENMASK(11, 8)
+#define NDFC_RDATA_STA_1 BIT(12)
+#define NDFC_RDATA_STA_0 BIT(13)
/* define bit use in NFC_INT */
#define NFC_B2R_INT_ENABLE BIT(0)
@@ -92,6 +123,7 @@
/* define bit use in NFC_TIMING_CTL */
#define NFC_TIMING_CTL_EDO BIT(8)
+#define NFC_TIMING_CTL_E_EDO BIT(9)
/* define NFC_TIMING_CFG register layout */
#define NFC_TIMING_CFG(tWB, tADL, tWHR, tRHW, tCAD) \
@@ -99,9 +131,15 @@
(((tWHR) & 0x3) << 4) | (((tRHW) & 0x3) << 6) | \
(((tCAD) & 0x7) << 8))
+#define NFC_TIMING_CFG2(tCDQSS, tSC, tCLHZ, tCSS, tWC) \
+ ((((tCDQSS) & 0x1) << 11) | (((tSC) & 0x3) << 12) | \
+ (((tCLHZ) & 0x3) << 14) | (((tCSS) & 0x3) << 16) | \
+ (((tWC) & 0x3) << 18))
+
/* define bit use in NFC_CMD */
#define NFC_CMD_LOW_BYTE_MSK GENMASK(7, 0)
-#define NFC_CMD_HIGH_BYTE_MSK GENMASK(15, 8)
+#define NFC_CMD_HIGH_BYTE_MSK GENMASK(15, 8) /* 15-10 reserved on H6 */
+#define NFC_CMD_ADR_NUM_MSK GENMASK(9, 8)
#define NFC_CMD(x) (x)
#define NFC_ADR_NUM_MSK GENMASK(18, 16)
#define NFC_ADR_NUM(x) (((x) - 1) << 16)
@@ -114,6 +152,7 @@
#define NFC_SEQ BIT(25)
#define NFC_DATA_SWAP_METHOD BIT(26)
#define NFC_ROW_AUTO_INC BIT(27)
+#define NFC_H6_SEND_RND_CMD2 BIT(27)
#define NFC_SEND_CMD3 BIT(28)
#define NFC_SEND_CMD4 BIT(29)
#define NFC_CMD_TYPE_MSK GENMASK(31, 30)
@@ -125,6 +164,7 @@
#define NFC_READ_CMD_MSK GENMASK(7, 0)
#define NFC_RND_READ_CMD0_MSK GENMASK(15, 8)
#define NFC_RND_READ_CMD1_MSK GENMASK(23, 16)
+#define NFC_RND_READ_CMD2_MSK GENMASK(31, 24)
/* define bit use in NFC_WCMD_SET */
#define NFC_PROGRAM_CMD_MSK GENMASK(7, 0)
@@ -138,25 +178,45 @@
#define NFC_ECC_EXCEPTION BIT(4)
#define NFC_ECC_BLOCK_SIZE_MSK BIT(5)
#define NFC_ECC_BLOCK_512 BIT(5)
-#define NFC_RANDOM_EN BIT(9)
-#define NFC_RANDOM_DIRECTION BIT(10)
-#define NFC_ECC_MODE_MSK GENMASK(15, 12)
-#define NFC_ECC_MODE(x) ((x) << 12)
+#define NFC_RANDOM_EN(nfc) (nfc->caps->random_en_mask)
+#define NFC_RANDOM_DIRECTION(nfc) (nfc->caps->random_dir_mask)
+#define NFC_ECC_MODE_MSK(nfc) (nfc->caps->ecc_mode_mask)
+#define NFC_ECC_MODE(nfc, x) field_prep(NFC_ECC_MODE_MSK(nfc), (x))
+/* RANDOM_PAGE_SIZE: 0: ECC block size 1: page size */
+#define NFC_A23_RANDOM_PAGE_SIZE BIT(11)
+#define NFC_H6_RANDOM_PAGE_SIZE BIT(7)
#define NFC_RANDOM_SEED_MSK GENMASK(30, 16)
#define NFC_RANDOM_SEED(x) ((x) << 16)
/* define bit use in NFC_ECC_ST */
#define NFC_ECC_ERR(x) BIT(x)
-#define NFC_ECC_ERR_MSK GENMASK(15, 0)
-#define NFC_ECC_PAT_FOUND(x) BIT(x + 16)
+#define NFC_ECC_ERR_MSK(nfc) (nfc->caps->ecc_err_mask)
+
+/*
+ * define bit use in NFC_REG_PAT_FOUND
+ * For A10/A23, NFC_REG_PAT_FOUND == NFC_ECC_ST register
+ */
+#define NFC_ECC_PAT_FOUND_MSK(nfc) (nfc->caps->pat_found_mask)
+
#define NFC_ECC_ERR_CNT(b, x) (((x) >> (((b) % 4) * 8)) & 0xff)
-#define NFC_DEFAULT_TIMEOUT_MS 1000
+#define NFC_USER_DATA_LEN_MSK(step) \
+ (0xf << (((step) % NFC_REG_USER_DATA_LEN_CAPACITY) * 4))
-#define NFC_SRAM_SIZE 1024
+#define NFC_DEFAULT_TIMEOUT_MS 1000
#define NFC_MAX_CS 7
+/*
+ * On A10/A23, this is the size of the NDFC User Data Register, containing the
+ * mandatory user data bytes preceding the ECC for each ECC step.
+ * Thus, for each ECC step, we need the ECC bytes + USER_DATA_SZ.
+ *
+ * On H6/H616, this size became configurable, from 0 bytes to 32, via the
+ * USER_DATA_LEN registers.
+ */
+#define USER_DATA_SZ 4
+
/**
* struct sunxi_nand_chip_sel - stores information related to NAND Chip Select
*
@@ -188,6 +248,7 @@ struct sunxi_nand_hw_ecc {
* @timing_ctl: TIMING_CTL register value for this NAND chip
* @nsels: number of CS lines required by the NAND chip
* @sels: array of CS lines descriptions
+ * @user_data_bytes: array of user data lengths for all ECC steps
*/
struct sunxi_nand_chip {
struct list_head node;
@@ -196,6 +257,7 @@ struct sunxi_nand_chip {
unsigned long clk_rate;
u32 timing_cfg;
u32 timing_ctl;
+ u8 *user_data_bytes;
int nsels;
struct sunxi_nand_chip_sel sels[] __counted_by(nsels);
};
@@ -211,13 +273,60 @@ static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand)
*
* @has_mdma: Use mbus dma mode, otherwise general dma
* through MBUS on A23/A33 needs extra configuration.
+ * @has_ecc_block_512: If the ECC can handle 512B or only 1024B chunks
+ * @has_ecc_clk: If the controller needs an ECC clock.
+ * @has_mbus_clk: If the controller needs a mbus clock.
+ * @legacy_max_strength:If the maximize strength function was off by 2 bytes
+ * NB: this should not be used in new controllers
* @reg_io_data: I/O data register
+ * @reg_ecc_err_cnt: ECC error counter register
+ * @reg_user_data: User data register
+ * @reg_user_data_len: User data length register
+ * @reg_spare_area: Spare Area Register
+ * @reg_pat_id: Pattern ID Register
+ * @reg_pat_found: Data Pattern Status Register
+ * @random_en_mask: RANDOM_EN mask in NFC_ECC_CTL register
+ * @random_dir_mask: RANDOM_DIRECTION mask in NFC_ECC_CTL register
+ * @ecc_mode_mask: ECC_MODE mask in NFC_ECC_CTL register
+ * @ecc_err_mask: NFC_ECC_ERR mask in NFC_ECC_ST register
+ * @pat_found_mask: ECC_PAT_FOUND mask in NFC_REG_PAT_FOUND register
* @dma_maxburst: DMA maxburst
+ * @ecc_strengths: Available ECC strengths array
+ * @nstrengths: Size of @ecc_strengths
+ * @max_ecc_steps: Maximum supported steps for ECC, this is also the
+ * number of user data registers
+ * @user_data_len_tab: Table of lengths supported by USER_DATA_LEN register
+ * The table index is the value to set in NFC_USER_DATA_LEN
+ * registers, and the corresponding value is the number of
+ * bytes to write
+ * @nuser_data_tab: Size of @user_data_len_tab
+ * @sram_size: Size of the NAND controller SRAM
*/
struct sunxi_nfc_caps {
bool has_mdma;
+ bool has_ecc_block_512;
+ bool has_ecc_clk;
+ bool has_mbus_clk;
+ bool legacy_max_strength;
unsigned int reg_io_data;
+ unsigned int reg_ecc_err_cnt;
+ unsigned int reg_user_data;
+ unsigned int reg_user_data_len;
+ unsigned int reg_spare_area;
+ unsigned int reg_pat_id;
+ unsigned int reg_pat_found;
+ unsigned int random_en_mask;
+ unsigned int random_dir_mask;
+ unsigned int ecc_mode_mask;
+ unsigned int ecc_err_mask;
+ unsigned int pat_found_mask;
unsigned int dma_maxburst;
+ const u8 *ecc_strengths;
+ unsigned int nstrengths;
+ const u8 *user_data_len_tab;
+ unsigned int nuser_data_tab;
+ unsigned int max_ecc_steps;
+ int sram_size;
};
/**
@@ -228,6 +337,8 @@ struct sunxi_nfc_caps {
* @regs: NAND controller registers
* @ahb_clk: NAND controller AHB clock
* @mod_clk: NAND controller mod clock
+ * @ecc_clk: NAND controller ECC clock
+ * @mbus_clk: NAND controller MBUS clock
* @reset: NAND controller reset line
* @assigned_cs: bitmask describing already assigned CS lines
* @clk_rate: NAND controller current clock rate
@@ -243,6 +354,8 @@ struct sunxi_nfc {
void __iomem *regs;
struct clk *ahb_clk;
struct clk *mod_clk;
+ struct clk *ecc_clk;
+ struct clk *mbus_clk;
struct reset_control *reset;
unsigned long assigned_cs;
unsigned long clk_rate;
@@ -431,7 +544,7 @@ static void sunxi_nfc_select_chip(struct nand_chip *nand, unsigned int cs)
if (sel->rb >= 0)
ctl |= NFC_RB_SEL(sel->rb);
- writel(mtd->writesize, nfc->regs + NFC_REG_SPARE_AREA);
+ writel(mtd->writesize, nfc->regs + NFC_REG_SPARE_AREA(nfc));
if (nfc->clk_rate != sunxi_nand->clk_rate) {
clk_set_rate(nfc->mod_clk, sunxi_nand->clk_rate);
@@ -455,7 +568,7 @@ static void sunxi_nfc_read_buf(struct nand_chip *nand, uint8_t *buf, int len)
while (len > offs) {
bool poll = false;
- cnt = min(len - offs, NFC_SRAM_SIZE);
+ cnt = min(len - offs, nfc->caps->sram_size);
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
@@ -493,7 +606,7 @@ static void sunxi_nfc_write_buf(struct nand_chip *nand, const uint8_t *buf,
while (len > offs) {
bool poll = false;
- cnt = min(len - offs, NFC_SRAM_SIZE);
+ cnt = min(len - offs, nfc->caps->sram_size);
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
@@ -623,13 +736,12 @@ static void sunxi_nfc_randomizer_config(struct nand_chip *nand, int page,
bool ecc)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
- u32 ecc_ctl = readl(nfc->regs + NFC_REG_ECC_CTL);
+ u32 ecc_ctl;
u16 state;
if (!(nand->options & NAND_NEED_SCRAMBLING))
return;
- ecc_ctl = readl(nfc->regs + NFC_REG_ECC_CTL);
state = sunxi_nfc_randomizer_state(nand, page, ecc);
ecc_ctl = readl(nfc->regs + NFC_REG_ECC_CTL) & ~NFC_RANDOM_SEED_MSK;
writel(ecc_ctl | NFC_RANDOM_SEED(state), nfc->regs + NFC_REG_ECC_CTL);
@@ -642,7 +754,7 @@ static void sunxi_nfc_randomizer_enable(struct nand_chip *nand)
if (!(nand->options & NAND_NEED_SCRAMBLING))
return;
- writel(readl(nfc->regs + NFC_REG_ECC_CTL) | NFC_RANDOM_EN,
+ writel(readl(nfc->regs + NFC_REG_ECC_CTL) | NFC_RANDOM_EN(nfc),
nfc->regs + NFC_REG_ECC_CTL);
}
@@ -653,7 +765,7 @@ static void sunxi_nfc_randomizer_disable(struct nand_chip *nand)
if (!(nand->options & NAND_NEED_SCRAMBLING))
return;
- writel(readl(nfc->regs + NFC_REG_ECC_CTL) & ~NFC_RANDOM_EN,
+ writel(readl(nfc->regs + NFC_REG_ECC_CTL) & ~NFC_RANDOM_EN(nfc),
nfc->regs + NFC_REG_ECC_CTL);
}
@@ -712,35 +824,148 @@ static inline u32 sunxi_nfc_buf_to_user_data(const u8 *buf)
return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
}
+static u8 sunxi_nfc_user_data_sz(struct sunxi_nand_chip *sunxi_nand, int step)
+{
+ if (!sunxi_nand->user_data_bytes)
+ return USER_DATA_SZ;
+
+ return sunxi_nand->user_data_bytes[step];
+}
+
static void sunxi_nfc_hw_ecc_get_prot_oob_bytes(struct nand_chip *nand, u8 *oob,
- int step, bool bbm, int page)
+ int step, bool bbm, int page,
+ unsigned int user_data_sz)
{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ u32 user_data;
- sunxi_nfc_user_data_to_buf(readl(nfc->regs + NFC_REG_USER_DATA(step)),
- oob);
+ if (!nfc->caps->reg_user_data_len) {
+ /*
+ * For A10, the user data for step n is in the nth
+ * REG_USER_DATA
+ */
+ user_data = readl(nfc->regs + NFC_REG_USER_DATA(nfc, step));
+ sunxi_nfc_user_data_to_buf(user_data, oob);
+ } else {
+ /*
+ * For H6 NAND controller, the user data for all steps is
+ * contained in 32 user data registers, but not at a specific
+ * offset for each step, they are just concatenated.
+ */
+ unsigned int user_data_off = 0;
+ unsigned int reg_off;
+ u8 *ptr = oob;
+ unsigned int i;
+
+ for (i = 0; i < step; i++)
+ user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+ user_data_off /= 4;
+ for (i = 0; i < user_data_sz / 4; i++, ptr += 4) {
+ reg_off = NFC_REG_USER_DATA(nfc, user_data_off + i);
+ user_data = readl(nfc->regs + reg_off);
+ sunxi_nfc_user_data_to_buf(user_data, ptr);
+ }
+ }
/* De-randomize the Bad Block Marker. */
if (bbm && (nand->options & NAND_NEED_SCRAMBLING))
sunxi_nfc_randomize_bbm(nand, page, oob);
}
+/*
+ * On H6/H6 the user_data length has to be set in specific registers
+ * before writing.
+ */
+static void sunxi_nfc_reset_user_data_len(struct sunxi_nfc *nfc)
+{
+ int loop_step = NFC_REG_USER_DATA_LEN_CAPACITY;
+
+ /* not all SoCs have this register */
+ if (!nfc->caps->reg_user_data_len)
+ return;
+
+ for (int i = 0; i < nfc->caps->max_ecc_steps; i += loop_step)
+ writel(0, nfc->regs + NFC_REG_USER_DATA_LEN(nfc, i));
+}
+
+static void sunxi_nfc_set_user_data_len(struct sunxi_nfc *nfc,
+ int len, int step)
+{
+ bool found = false;
+ u32 val;
+ int i;
+
+ /* not all SoCs have this register */
+ if (!nfc->caps->reg_user_data_len)
+ return;
+
+ for (i = 0; i < nfc->caps->nuser_data_tab; i++) {
+ if (len == nfc->caps->user_data_len_tab[i]) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ dev_warn(nfc->dev,
+ "Unsupported length for user data reg: %d\n", len);
+ return;
+ }
+
+ val = readl(nfc->regs + NFC_REG_USER_DATA_LEN(nfc, step));
+
+ val &= ~NFC_USER_DATA_LEN_MSK(step);
+ val |= field_prep(NFC_USER_DATA_LEN_MSK(step), i);
+ writel(val, nfc->regs + NFC_REG_USER_DATA_LEN(nfc, step));
+}
+
static void sunxi_nfc_hw_ecc_set_prot_oob_bytes(struct nand_chip *nand,
const u8 *oob, int step,
bool bbm, int page)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
- u8 user_data[4];
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
+ u8 *user_data = NULL;
/* Randomize the Bad Block Marker. */
if (bbm && (nand->options & NAND_NEED_SCRAMBLING)) {
- memcpy(user_data, oob, sizeof(user_data));
+ user_data = kmalloc(user_data_sz, GFP_KERNEL);
+ memcpy(user_data, oob, user_data_sz);
sunxi_nfc_randomize_bbm(nand, page, user_data);
oob = user_data;
}
- writel(sunxi_nfc_buf_to_user_data(oob),
- nfc->regs + NFC_REG_USER_DATA(step));
+ if (!nfc->caps->reg_user_data_len) {
+ /*
+ * For A10, the user data for step n is in the nth
+ * REG_USER_DATA
+ */
+ writel(sunxi_nfc_buf_to_user_data(oob),
+ nfc->regs + NFC_REG_USER_DATA(nfc, step));
+ } else {
+ /*
+ * For H6 NAND controller, the user data for all steps is
+ * contained in 32 user data registers, but not at a specific
+ * offset for each step, they are just concatenated.
+ */
+ unsigned int user_data_off = 0;
+ const u8 *ptr = oob;
+ unsigned int i;
+
+ for (i = 0; i < step; i++)
+ user_data_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+ user_data_off /= 4;
+ for (i = 0; i < user_data_sz / 4; i++, ptr += 4) {
+ writel(sunxi_nfc_buf_to_user_data(ptr),
+ nfc->regs + NFC_REG_USER_DATA(nfc, user_data_off + i));
+ }
+ }
+
+ kfree(user_data);
}
static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,
@@ -757,9 +982,12 @@ static void sunxi_nfc_hw_ecc_update_stats(struct nand_chip *nand,
}
static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
- int step, u32 status, bool *erased)
+ int step, u32 status, u32 pattern_found,
+ bool *erased)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
struct nand_ecc_ctrl *ecc = &nand->ecc;
u32 tmp;
@@ -768,10 +996,10 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
if (status & NFC_ECC_ERR(step))
return -EBADMSG;
- if (status & NFC_ECC_PAT_FOUND(step)) {
+ if (pattern_found & BIT(step)) {
u8 pattern;
- if (unlikely(!(readl(nfc->regs + NFC_REG_PAT_ID) & 0x1))) {
+ if (unlikely(!(readl(nfc->regs + NFC_REG_PAT_ID(nfc)) & 0x1))) {
pattern = 0x0;
} else {
pattern = 0xff;
@@ -782,12 +1010,12 @@ static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob,
memset(data, pattern, ecc->size);
if (oob)
- memset(oob, pattern, ecc->bytes + 4);
+ memset(oob, pattern, ecc->bytes + user_data_sz);
return 0;
}
- tmp = readl(nfc->regs + NFC_REG_ECC_ERR_CNT(step));
+ tmp = readl(nfc->regs + NFC_REG_ECC_ERR_CNT(nfc, step));
return NFC_ECC_ERR_CNT(step, tmp);
}
@@ -797,13 +1025,19 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
u8 *oob, int oob_off,
int *cur_off,
unsigned int *max_bitflips,
- bool bbm, bool oob_required, int page)
+ int step, bool oob_required, int page)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int raw_mode = 0;
+ u32 pattern_found;
+ bool bbm = !step;
bool erased;
int ret;
+ /* From the controller point of view, we are at step 0 */
+ const int nfc_step = 0;
if (*cur_off != data_off)
nand_change_read_column_op(nand, data_off, NULL, 0, false);
@@ -817,6 +1051,8 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
if (ret)
return ret;
+ sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
+ sunxi_nfc_randomizer_config(nand, page, false);
sunxi_nfc_randomizer_enable(nand);
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | NFC_ECC_OP,
nfc->regs + NFC_REG_CMD);
@@ -826,11 +1062,14 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
if (ret)
return ret;
- *cur_off = oob_off + ecc->bytes + 4;
+ *cur_off = oob_off + ecc->bytes + user_data_sz;
- ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL, 0,
- readl(nfc->regs + NFC_REG_ECC_ST),
- &erased);
+ pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
+ pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
+
+ ret = sunxi_nfc_hw_ecc_correct(nand, data, oob_required ? oob : NULL,
+ nfc_step, readl(nfc->regs + NFC_REG_ECC_ST),
+ pattern_found, &erased);
if (erased)
return 1;
@@ -846,11 +1085,11 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE,
ecc->size);
- nand_change_read_column_op(nand, oob_off, oob, ecc->bytes + 4,
- false);
+ nand_change_read_column_op(nand, oob_off, oob,
+ ecc->bytes + user_data_sz, false);
- ret = nand_check_erased_ecc_chunk(data, ecc->size,
- oob, ecc->bytes + 4,
+ ret = nand_check_erased_ecc_chunk(data, ecc->size, oob,
+ ecc->bytes + user_data_sz,
NULL, 0, ecc->strength);
if (ret >= 0)
raw_mode = 1;
@@ -860,11 +1099,11 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
if (oob_required) {
nand_change_read_column_op(nand, oob_off, NULL, 0,
false);
- sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + 4,
+ sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + user_data_sz,
true, page);
- sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, 0,
- bbm, page);
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step,
+ bbm, page, user_data_sz);
}
}
@@ -873,21 +1112,50 @@ static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand,
return raw_mode;
}
+/*
+ * Returns the offset of the OOB for each step.
+ * (it includes the user data before the ECC data.)
+ */
+static int sunxi_get_oob_offset(struct sunxi_nand_chip *sunxi_nand,
+ struct nand_ecc_ctrl *ecc, int step)
+{
+ int ecc_off = step * ecc->bytes;
+ int i;
+
+ for (i = 0; i < step; i++)
+ ecc_off += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+ return ecc_off;
+}
+
+/*
+ * Returns the offset of the ECC for each step.
+ * So, it's the same as sunxi_get_oob_offset(),
+ * but it skips the next user data.
+ */
+static int sunxi_get_ecc_offset(struct sunxi_nand_chip *sunxi_nand,
+ struct nand_ecc_ctrl *ecc, int step)
+{
+ return sunxi_get_oob_offset(sunxi_nand, ecc, step) +
+ sunxi_nfc_user_data_sz(sunxi_nand, step);
+}
+
static void sunxi_nfc_hw_ecc_read_extra_oob(struct nand_chip *nand,
u8 *oob, int *cur_off,
bool randomize, int page)
{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
- int offset = ((ecc->bytes + 4) * ecc->steps);
+ int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
int len = mtd->oobsize - offset;
if (len <= 0)
return;
- if (!cur_off || *cur_off != offset)
- nand_change_read_column_op(nand, mtd->writesize, NULL, 0,
- false);
+ if (!cur_off || *cur_off != (offset + mtd->writesize))
+ nand_change_read_column_op(nand, mtd->writesize + offset,
+ NULL, 0, false);
if (!randomize)
sunxi_nfc_read_buf(nand, oob + offset, len);
@@ -904,13 +1172,14 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
int nchunks)
{
bool randomized = nand->options & NAND_NEED_SCRAMBLING;
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
unsigned int max_bitflips = 0;
int ret, i, raw_mode = 0;
struct scatterlist sg;
- u32 status, wait;
+ u32 status, pattern_found, wait;
ret = sunxi_nfc_wait_cmd_fifo_empty(nfc);
if (ret)
@@ -922,6 +1191,9 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
return ret;
sunxi_nfc_hw_ecc_enable(nand);
+ sunxi_nfc_reset_user_data_len(nfc);
+ for (i = 0; i < nchunks; i++)
+ sunxi_nfc_set_user_data_len(nfc, sunxi_nfc_user_data_sz(sunxi_nand, i), i);
sunxi_nfc_randomizer_config(nand, page, false);
sunxi_nfc_randomizer_enable(nand);
@@ -951,17 +1223,21 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
return ret;
status = readl(nfc->regs + NFC_REG_ECC_ST);
+ pattern_found = readl(nfc->regs + nfc->caps->reg_pat_found);
+ pattern_found = field_get(NFC_ECC_PAT_FOUND_MSK(nfc), pattern_found);
for (i = 0; i < nchunks; i++) {
int data_off = i * ecc->size;
- int oob_off = i * (ecc->bytes + 4);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
u8 *data = buf + data_off;
u8 *oob = nand->oob_poi + oob_off;
bool erased;
ret = sunxi_nfc_hw_ecc_correct(nand, randomized ? data : NULL,
oob_required ? oob : NULL,
- i, status, &erased);
+ i, status, pattern_found,
+ &erased);
/* ECC errors are handled in the second loop. */
if (ret < 0)
@@ -971,10 +1247,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
/* TODO: use DMA to retrieve OOB */
nand_change_read_column_op(nand,
mtd->writesize + oob_off,
- oob, ecc->bytes + 4, false);
+ oob, ecc->bytes + user_data_sz, false);
- sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i,
- !i, page);
+ sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !i,
+ page, user_data_sz);
}
if (erased)
@@ -983,10 +1259,11 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, ret);
}
- if (status & NFC_ECC_ERR_MSK) {
+ if (status & NFC_ECC_ERR_MSK(nfc)) {
for (i = 0; i < nchunks; i++) {
int data_off = i * ecc->size;
- int oob_off = i * (ecc->bytes + 4);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
u8 *data = buf + data_off;
u8 *oob = nand->oob_poi + oob_off;
@@ -1006,10 +1283,10 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
/* TODO: use DMA to retrieve OOB */
nand_change_read_column_op(nand,
mtd->writesize + oob_off,
- oob, ecc->bytes + 4, false);
+ oob, ecc->bytes + user_data_sz, false);
- ret = nand_check_erased_ecc_chunk(data, ecc->size,
- oob, ecc->bytes + 4,
+ ret = nand_check_erased_ecc_chunk(data, ecc->size, oob,
+ ecc->bytes + user_data_sz,
NULL, 0,
ecc->strength);
if (ret >= 0)
@@ -1030,12 +1307,17 @@ static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf
static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
const u8 *data, int data_off,
const u8 *oob, int oob_off,
- int *cur_off, bool bbm,
+ int *cur_off, int step,
int page)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, step);
struct nand_ecc_ctrl *ecc = &nand->ecc;
+ bool bbm = !step;
int ret;
+ /* From the controller point of view, we are at step 0 */
+ const int nfc_step = 0;
if (data_off != *cur_off)
nand_change_write_column_op(nand, data_off, NULL, 0, false);
@@ -1049,8 +1331,10 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
if (ret)
return ret;
+ sunxi_nfc_randomizer_config(nand, page, false);
sunxi_nfc_randomizer_enable(nand);
- sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, 0, bbm, page);
+ sunxi_nfc_set_user_data_len(nfc, user_data_sz, nfc_step);
+ sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, nfc_step, bbm, page);
writel(NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD |
NFC_ACCESS_DIR | NFC_ECC_OP,
@@ -1061,7 +1345,7 @@ static int sunxi_nfc_hw_ecc_write_chunk(struct nand_chip *nand,
if (ret)
return ret;
- *cur_off = oob_off + ecc->bytes + 4;
+ *cur_off = oob_off + ecc->bytes + user_data_sz;
return 0;
}
@@ -1071,8 +1355,9 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand,
int page)
{
struct mtd_info *mtd = nand_to_mtd(nand);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
- int offset = ((ecc->bytes + 4) * ecc->steps);
+ int offset = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps);
int len = mtd->oobsize - offset;
if (len <= 0)
@@ -1091,6 +1376,8 @@ static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand,
static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
int oob_required, int page)
{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
unsigned int max_bitflips = 0;
@@ -1103,16 +1390,17 @@ static int sunxi_nfc_hw_ecc_read_page(struct nand_chip *nand, uint8_t *buf,
sunxi_nfc_hw_ecc_enable(nand);
+ sunxi_nfc_reset_user_data_len(nfc);
for (i = 0; i < ecc->steps; i++) {
int data_off = i * ecc->size;
- int oob_off = i * (ecc->bytes + 4);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
u8 *data = buf + data_off;
u8 *oob = nand->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off, oob,
oob_off + mtd->writesize,
&cur_off, &max_bitflips,
- !i, oob_required, page);
+ i, oob_required, page);
if (ret < 0)
return ret;
else if (ret)
@@ -1150,6 +1438,8 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
u32 data_offs, u32 readlen,
u8 *bufpoi, int page)
{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int ret, i, cur_off = 0;
@@ -1161,17 +1451,18 @@ static int sunxi_nfc_hw_ecc_read_subpage(struct nand_chip *nand,
sunxi_nfc_hw_ecc_enable(nand);
+ sunxi_nfc_reset_user_data_len(nfc);
for (i = data_offs / ecc->size;
i < DIV_ROUND_UP(data_offs + readlen, ecc->size); i++) {
int data_off = i * ecc->size;
- int oob_off = i * (ecc->bytes + 4);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
u8 *data = bufpoi + data_off;
u8 *oob = nand->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_read_chunk(nand, data, data_off,
oob,
oob_off + mtd->writesize,
- &cur_off, &max_bitflips, !i,
+ &cur_off, &max_bitflips, i,
false, page);
if (ret < 0)
return ret;
@@ -1206,6 +1497,8 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand,
const uint8_t *buf, int oob_required,
int page)
{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int ret, i, cur_off = 0;
@@ -1216,15 +1509,16 @@ static int sunxi_nfc_hw_ecc_write_page(struct nand_chip *nand,
sunxi_nfc_hw_ecc_enable(nand);
+ sunxi_nfc_reset_user_data_len(nfc);
for (i = 0; i < ecc->steps; i++) {
int data_off = i * ecc->size;
- int oob_off = i * (ecc->bytes + 4);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
const u8 *data = buf + data_off;
const u8 *oob = nand->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob,
oob_off + mtd->writesize,
- &cur_off, !i, page);
+ &cur_off, i, page);
if (ret)
return ret;
}
@@ -1243,6 +1537,8 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand,
const u8 *buf, int oob_required,
int page)
{
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
int ret, i, cur_off = 0;
@@ -1253,16 +1549,17 @@ static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand,
sunxi_nfc_hw_ecc_enable(nand);
+ sunxi_nfc_reset_user_data_len(nfc);
for (i = data_offs / ecc->size;
i < DIV_ROUND_UP(data_offs + data_len, ecc->size); i++) {
int data_off = i * ecc->size;
- int oob_off = i * (ecc->bytes + 4);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
const u8 *data = buf + data_off;
const u8 *oob = nand->oob_poi + oob_off;
ret = sunxi_nfc_hw_ecc_write_chunk(nand, data, data_off, oob,
oob_off + mtd->writesize,
- &cur_off, !i, page);
+ &cur_off, i, page);
if (ret)
return ret;
}
@@ -1278,6 +1575,7 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
int page)
{
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct nand_ecc_ctrl *ecc = &nand->ecc;
struct scatterlist sg;
u32 wait;
@@ -1294,10 +1592,14 @@ static int sunxi_nfc_hw_ecc_write_page_dma(struct nand_chip *nand,
if (ret)
goto pio_fallback;
+ sunxi_nfc_reset_user_data_len(nfc);
for (i = 0; i < ecc->steps; i++) {
- const u8 *oob = nand->oob_poi + (i * (ecc->bytes + 4));
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, i);
+ int oob_off = sunxi_get_oob_offset(sunxi_nand, ecc, i);
+ const u8 *oob = nand->oob_poi + oob_off;
sunxi_nfc_hw_ecc_set_prot_oob_bytes(nand, oob, i, !i, page);
+ sunxi_nfc_set_user_data_len(nfc, user_data_sz, i);
}
nand_prog_page_begin_op(nand, page, 0, NULL, 0);
@@ -1561,11 +1863,12 @@ static int sunxi_nand_ooblayout_ecc(struct mtd_info *mtd, int section,
{
struct nand_chip *nand = mtd_to_nand(mtd);
struct nand_ecc_ctrl *ecc = &nand->ecc;
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
if (section >= ecc->steps)
return -ERANGE;
- oobregion->offset = section * (ecc->bytes + 4) + 4;
+ oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section);
oobregion->length = ecc->bytes;
return 0;
@@ -1576,35 +1879,30 @@ static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section,
{
struct nand_chip *nand = mtd_to_nand(mtd);
struct nand_ecc_ctrl *ecc = &nand->ecc;
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ unsigned int user_data_sz = sunxi_nfc_user_data_sz(sunxi_nand, section);
- if (section > ecc->steps)
+ /*
+ * The controller does not provide access to OOB bytes
+ * past the end of the ECC data.
+ */
+ if (section >= ecc->steps)
return -ERANGE;
/*
* The first 2 bytes are used for BB markers, hence we
- * only have 2 bytes available in the first user data
+ * only have user_data_sz - 2 bytes available in the first user data
* section.
*/
- if (!section && ecc->engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) {
+ if (section == 0) {
oobregion->offset = 2;
- oobregion->length = 2;
+ oobregion->length = user_data_sz - 2;
return 0;
}
- /*
- * The controller does not provide access to OOB bytes
- * past the end of the ECC data.
- */
- if (section == ecc->steps && ecc->engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST)
- return -ERANGE;
-
- oobregion->offset = section * (ecc->bytes + 4);
-
- if (section < ecc->steps)
- oobregion->length = 4;
- else
- oobregion->length = mtd->oobsize - oobregion->offset;
+ oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section);
+ oobregion->length = user_data_sz;
return 0;
}
@@ -1614,29 +1912,96 @@ static const struct mtd_ooblayout_ops sunxi_nand_ooblayout_ops = {
.free = sunxi_nand_ooblayout_free,
};
+static void sunxi_nand_detach_chip(struct nand_chip *nand)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+
+ devm_kfree(nfc->dev, sunxi_nand->user_data_bytes);
+ sunxi_nand->user_data_bytes = NULL;
+}
+
+static int sunxi_nfc_maximize_user_data(struct nand_chip *nand, uint32_t oobsize,
+ int ecc_bytes, int nsectors)
+{
+ struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
+ struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ const struct sunxi_nfc_caps *c = nfc->caps;
+ int remaining_bytes = oobsize - (ecc_bytes * nsectors);
+ int i, step;
+
+ sunxi_nand->user_data_bytes = devm_kzalloc(nfc->dev, nsectors,
+ GFP_KERNEL);
+ if (!sunxi_nand->user_data_bytes)
+ return -ENOMEM;
+
+ for (step = 0; (step < nsectors) && (remaining_bytes > 0); step++) {
+ for (i = 0; i < c->nuser_data_tab; i++) {
+ if (c->user_data_len_tab[i] > remaining_bytes)
+ break;
+ sunxi_nand->user_data_bytes[step] = c->user_data_len_tab[i];
+ }
+ remaining_bytes -= sunxi_nand->user_data_bytes[step];
+ if (sunxi_nand->user_data_bytes[step] == 0)
+ break;
+ }
+
+ return 0;
+}
+
static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
struct nand_ecc_ctrl *ecc,
struct device_node *np)
{
- static const u8 strengths[] = { 16, 24, 28, 32, 40, 48, 56, 60, 64 };
struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand);
struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller);
+ const u8 *strengths = nfc->caps->ecc_strengths;
struct mtd_info *mtd = nand_to_mtd(nand);
struct nand_device *nanddev = mtd_to_nanddev(mtd);
+ int total_user_data_sz = 0;
int nsectors;
+ int ecc_mode;
int i;
if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) {
- int bytes;
+ int bytes = mtd->oobsize;
ecc->size = 1024;
nsectors = mtd->writesize / ecc->size;
- /* Reserve 2 bytes for the BBM */
- bytes = (mtd->oobsize - 2) / nsectors;
+ if (!nfc->caps->reg_user_data_len) {
+ /*
+ * If there's a fixed user data length, subtract it before
+ * computing the max ECC strength
+ */
+
+ for (i = 0; i < nsectors; i++)
+ total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i);
- /* 4 non-ECC bytes are added before each ECC bytes section */
- bytes -= 4;
+ /*
+ * The 2 BBM bytes should not be removed from the grand total,
+ * because they are part of the USER_DATA_SZ.
+ * But we can't modify that for older platform since it may
+ * result in a stronger ECC at the end, and break the
+ * compatibility.
+ */
+ if (nfc->caps->legacy_max_strength)
+ bytes -= 2;
+
+ bytes -= total_user_data_sz;
+ } else {
+ /*
+ * remove at least the BBM size before computing the
+ * max ECC
+ */
+ bytes -= 2;
+ }
+
+ /*
+ * Once all user data has been subtracted, the rest can be used
+ * for ECC bytes
+ */
+ bytes /= nsectors;
/* and bytes has to be even. */
if (bytes % 2)
@@ -1644,7 +2009,7 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
ecc->strength = bytes * 8 / fls(8 * ecc->size);
- for (i = 0; i < ARRAY_SIZE(strengths); i++) {
+ for (i = 0; i < nfc->caps->nstrengths; i++) {
if (strengths[i] > ecc->strength)
break;
}
@@ -1665,18 +2030,18 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
}
/* Add ECC info retrieval from DT */
- for (i = 0; i < ARRAY_SIZE(strengths); i++) {
- if (ecc->strength <= strengths[i]) {
+ for (ecc_mode = 0; ecc_mode < nfc->caps->nstrengths; ecc_mode++) {
+ if (ecc->strength <= strengths[ecc_mode]) {
/*
* Update ecc->strength value with the actual strength
* that will be used by the ECC engine.
*/
- ecc->strength = strengths[i];
+ ecc->strength = strengths[ecc_mode];
break;
}
}
- if (i >= ARRAY_SIZE(strengths)) {
+ if (ecc_mode >= nfc->caps->nstrengths) {
dev_err(nfc->dev, "unsupported strength\n");
return -ENOTSUPP;
}
@@ -1689,7 +2054,19 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
nsectors = mtd->writesize / ecc->size;
- if (mtd->oobsize < ((ecc->bytes + 4) * nsectors))
+ /*
+ * The rationale for variable data length is to prioritize maximum ECC
+ * strength, and then use the remaining space for user data.
+ */
+ if (nfc->caps->reg_user_data_len)
+ sunxi_nfc_maximize_user_data(nand, mtd->oobsize, ecc->bytes,
+ nsectors);
+
+ if (total_user_data_sz == 0)
+ for (i = 0; i < nsectors; i++)
+ total_user_data_sz += sunxi_nfc_user_data_sz(sunxi_nand, i);
+
+ if (mtd->oobsize < (ecc->bytes * nsectors + total_user_data_sz))
return -EINVAL;
ecc->read_oob = sunxi_nfc_hw_ecc_read_oob;
@@ -1712,11 +2089,17 @@ static int sunxi_nand_hw_ecc_ctrl_init(struct nand_chip *nand,
ecc->read_oob_raw = nand_read_oob_std;
ecc->write_oob_raw = nand_write_oob_std;
- sunxi_nand->ecc.ecc_ctl = NFC_ECC_MODE(i) | NFC_ECC_EXCEPTION |
+ sunxi_nand->ecc.ecc_ctl = NFC_ECC_MODE(nfc, ecc_mode) | NFC_ECC_EXCEPTION |
NFC_ECC_PIPELINE | NFC_ECC_EN;
- if (ecc->size == 512)
- sunxi_nand->ecc.ecc_ctl |= NFC_ECC_BLOCK_512;
+ if (ecc->size == 512) {
+ if (nfc->caps->has_ecc_block_512) {
+ sunxi_nand->ecc.ecc_ctl |= NFC_ECC_BLOCK_512;
+ } else {
+ dev_err(nfc->dev, "512B ECC block not supported\n");
+ return -EOPNOTSUPP;
+ }
+ }
return 0;
}
@@ -1805,7 +2188,7 @@ static int sunxi_nfc_exec_subop(struct nand_chip *nand,
case NAND_OP_DATA_OUT_INSTR:
start = nand_subop_get_data_start_off(subop, i);
remaining = nand_subop_get_data_len(subop, i);
- cnt = min_t(u32, remaining, NFC_SRAM_SIZE);
+ cnt = min_t(u32, remaining, nfc->caps->sram_size);
cmd |= NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD;
if (instr->type == NAND_OP_DATA_OUT_INSTR) {
@@ -1913,6 +2296,7 @@ static int sunxi_nfc_exec_op(struct nand_chip *nand,
static const struct nand_controller_ops sunxi_nand_controller_ops = {
.attach_chip = sunxi_nand_attach_chip,
+ .detach_chip = sunxi_nand_detach_chip,
.setup_interface = sunxi_nfc_setup_interface,
.exec_op = sunxi_nfc_exec_op,
};
@@ -2092,6 +2476,10 @@ static int sunxi_nfc_probe(struct platform_device *pdev)
if (irq < 0)
return irq;
+ nfc->caps = of_device_get_match_data(dev);
+ if (!nfc->caps)
+ return -EINVAL;
+
nfc->ahb_clk = devm_clk_get_enabled(dev, "ahb");
if (IS_ERR(nfc->ahb_clk)) {
dev_err(dev, "failed to retrieve ahb clk\n");
@@ -2104,6 +2492,22 @@ static int sunxi_nfc_probe(struct platform_device *pdev)
return PTR_ERR(nfc->mod_clk);
}
+ if (nfc->caps->has_ecc_clk) {
+ nfc->ecc_clk = devm_clk_get_enabled(dev, "ecc");
+ if (IS_ERR(nfc->ecc_clk)) {
+ dev_err(dev, "failed to retrieve ecc clk\n");
+ return PTR_ERR(nfc->ecc_clk);
+ }
+ }
+
+ if (nfc->caps->has_mbus_clk) {
+ nfc->mbus_clk = devm_clk_get_enabled(dev, "mbus");
+ if (IS_ERR(nfc->mbus_clk)) {
+ dev_err(dev, "failed to retrieve mbus clk\n");
+ return PTR_ERR(nfc->mbus_clk);
+ }
+ }
+
nfc->reset = devm_reset_control_get_optional_exclusive(dev, "ahb");
if (IS_ERR(nfc->reset))
return PTR_ERR(nfc->reset);
@@ -2114,12 +2518,6 @@ static int sunxi_nfc_probe(struct platform_device *pdev)
return ret;
}
- nfc->caps = of_device_get_match_data(&pdev->dev);
- if (!nfc->caps) {
- ret = -EINVAL;
- goto out_ahb_reset_reassert;
- }
-
ret = sunxi_nfc_rst(nfc);
if (ret)
goto out_ahb_reset_reassert;
@@ -2166,15 +2564,83 @@ static void sunxi_nfc_remove(struct platform_device *pdev)
dma_release_channel(nfc->dmac);
}
+static const u8 sunxi_ecc_strengths_a10[] = {
+ 16, 24, 28, 32, 40, 48, 56, 60, 64
+};
+
+static const u8 sunxi_ecc_strengths_h6[] = {
+ 16, 24, 28, 32, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, 80
+};
+
+static const u8 sunxi_user_data_len_h6[] = {
+ 0, 4, 8, 12, 16, 20, 24, 28, 32
+};
+
static const struct sunxi_nfc_caps sunxi_nfc_a10_caps = {
+ .has_ecc_block_512 = true,
+ .legacy_max_strength = true,
.reg_io_data = NFC_REG_A10_IO_DATA,
+ .reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT,
+ .reg_user_data = NFC_REG_A10_USER_DATA,
+ .reg_spare_area = NFC_REG_A10_SPARE_AREA,
+ .reg_pat_id = NFC_REG_A10_PAT_ID,
+ .reg_pat_found = NFC_REG_ECC_ST,
+ .random_en_mask = BIT(9),
+ .random_dir_mask = BIT(10),
+ .ecc_mode_mask = GENMASK(15, 12),
+ .ecc_err_mask = GENMASK(15, 0),
+ .pat_found_mask = GENMASK(31, 16),
.dma_maxburst = 4,
+ .ecc_strengths = sunxi_ecc_strengths_a10,
+ .nstrengths = ARRAY_SIZE(sunxi_ecc_strengths_a10),
+ .max_ecc_steps = 16,
+ .sram_size = 1024,
};
static const struct sunxi_nfc_caps sunxi_nfc_a23_caps = {
.has_mdma = true,
+ .has_ecc_block_512 = true,
+ .legacy_max_strength = true,
+ .reg_io_data = NFC_REG_A23_IO_DATA,
+ .reg_ecc_err_cnt = NFC_REG_A10_ECC_ERR_CNT,
+ .reg_user_data = NFC_REG_A10_USER_DATA,
+ .reg_spare_area = NFC_REG_A10_SPARE_AREA,
+ .reg_pat_id = NFC_REG_A10_PAT_ID,
+ .reg_pat_found = NFC_REG_ECC_ST,
+ .random_en_mask = BIT(9),
+ .random_dir_mask = BIT(10),
+ .ecc_mode_mask = GENMASK(15, 12),
+ .ecc_err_mask = GENMASK(15, 0),
+ .pat_found_mask = GENMASK(31, 16),
+ .dma_maxburst = 8,
+ .ecc_strengths = sunxi_ecc_strengths_a10,
+ .nstrengths = ARRAY_SIZE(sunxi_ecc_strengths_a10),
+ .max_ecc_steps = 16,
+ .sram_size = 1024,
+};
+
+static const struct sunxi_nfc_caps sunxi_nfc_h616_caps = {
+ .has_ecc_clk = true,
+ .has_mbus_clk = true,
.reg_io_data = NFC_REG_A23_IO_DATA,
+ .reg_ecc_err_cnt = NFC_REG_H6_ECC_ERR_CNT,
+ .reg_user_data = NFC_REG_H6_USER_DATA,
+ .reg_user_data_len = NFC_REG_H6_USER_DATA_LEN,
+ .reg_spare_area = NFC_REG_H6_SPARE_AREA,
+ .reg_pat_id = NFC_REG_H6_PAT_ID,
+ .reg_pat_found = NFC_REG_H6_PAT_FOUND,
+ .random_en_mask = BIT(5),
+ .random_dir_mask = BIT(6),
+ .ecc_mode_mask = GENMASK(15, 8),
+ .ecc_err_mask = GENMASK(31, 0),
+ .pat_found_mask = GENMASK(31, 0),
.dma_maxburst = 8,
+ .ecc_strengths = sunxi_ecc_strengths_h6,
+ .nstrengths = ARRAY_SIZE(sunxi_ecc_strengths_h6),
+ .user_data_len_tab = sunxi_user_data_len_h6,
+ .nuser_data_tab = ARRAY_SIZE(sunxi_user_data_len_h6),
+ .max_ecc_steps = 32,
+ .sram_size = 8192,
};
static const struct of_device_id sunxi_nfc_ids[] = {
@@ -2186,6 +2652,10 @@ static const struct of_device_id sunxi_nfc_ids[] = {
.compatible = "allwinner,sun8i-a23-nand-controller",
.data = &sunxi_nfc_a23_caps,
},
+ {
+ .compatible = "allwinner,sun50i-h616-nand-controller",
+ .data = &sunxi_nfc_h616_caps,
+ },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, sunxi_nfc_ids);
@@ -2203,4 +2673,3 @@ module_platform_driver(sunxi_nfc_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Boris BREZILLON");
MODULE_DESCRIPTION("Allwinner NAND Flash Controller driver");
-MODULE_ALIAS("platform:sunxi_nand");
diff --git a/drivers/mtd/nand/raw/txx9ndfmc.c b/drivers/mtd/nand/raw/txx9ndfmc.c
index 907fb5de4269..4cc6e91dbc23 100644
--- a/drivers/mtd/nand/raw/txx9ndfmc.c
+++ b/drivers/mtd/nand/raw/txx9ndfmc.c
@@ -319,8 +319,7 @@ static int txx9ndfmc_probe(struct platform_device *dev)
if (!(plat->ch_mask & (1 << i)))
continue;
- txx9_priv = kzalloc(sizeof(struct txx9ndfmc_priv),
- GFP_KERNEL);
+ txx9_priv = kzalloc_obj(struct txx9ndfmc_priv);
if (!txx9_priv)
continue;
chip = &txx9_priv->chip;
diff --git a/drivers/mtd/nand/raw/vf610_nfc.c b/drivers/mtd/nand/raw/vf610_nfc.c
index 4b5ba3187853..9940681810cf 100644
--- a/drivers/mtd/nand/raw/vf610_nfc.c
+++ b/drivers/mtd/nand/raw/vf610_nfc.c
@@ -810,7 +810,6 @@ static int vf610_nfc_probe(struct platform_device *pdev)
struct vf610_nfc *nfc;
struct mtd_info *mtd;
struct nand_chip *chip;
- struct device_node *child;
int err;
int irq;
@@ -840,17 +839,16 @@ static int vf610_nfc_probe(struct platform_device *pdev)
return PTR_ERR(nfc->clk);
}
- nfc->variant = (enum vf610_nfc_variant)device_get_match_data(&pdev->dev);
+ nfc->variant = (unsigned long)device_get_match_data(&pdev->dev);
if (!nfc->variant)
return -ENODEV;
- for_each_available_child_of_node(nfc->dev->of_node, child) {
+ for_each_available_child_of_node_scoped(nfc->dev->of_node, child) {
if (of_device_is_compatible(child, "fsl,vf610-nfc-nandcs")) {
if (nand_get_flash_node(chip)) {
dev_err(nfc->dev,
"Only one NAND chip supported!\n");
- of_node_put(child);
return -EINVAL;
}
diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile
index 1e61ab21893a..a47bd22cd309 100644
--- a/drivers/mtd/nand/spi/Makefile
+++ b/drivers/mtd/nand/spi/Makefile
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
-spinand-objs := core.o alliancememory.o ato.o esmt.o foresee.o gigadevice.o macronix.o
-spinand-objs += micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o
+spinand-objs := core.o otp.o
+spinand-objs += alliancememory.o ato.o dosilicon.o esmt.o fmsh.o foresee.o gigadevice.o
+spinand-objs += macronix.o micron.o paragon.o skyhigh.o toshiba.o winbond.o xtx.o
obj-$(CONFIG_MTD_SPI_NAND) += spinand.o
diff --git a/drivers/mtd/nand/spi/alliancememory.c b/drivers/mtd/nand/spi/alliancememory.c
index 6046c73f8424..9e97c40955c9 100644
--- a/drivers/mtd/nand/spi/alliancememory.c
+++ b/drivers/mtd/nand/spi/alliancememory.c
@@ -17,20 +17,20 @@
#define AM_STATUS_ECC_MAX_CORRECTED (3 << 4)
static SPINAND_OP_VARIANTS(read_cache_variants,
- SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
- SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
- SPINAND_PROG_LOAD(true, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
- SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
- SPINAND_PROG_LOAD(false, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static int am_get_eccsize(struct mtd_info *mtd)
{
diff --git a/drivers/mtd/nand/spi/ato.c b/drivers/mtd/nand/spi/ato.c
index bb5298911137..45d38ce0736c 100644
--- a/drivers/mtd/nand/spi/ato.c
+++ b/drivers/mtd/nand/spi/ato.c
@@ -14,17 +14,17 @@
static SPINAND_OP_VARIANTS(read_cache_variants,
- SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
- SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
- SPINAND_PROG_LOAD(true, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
- SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
- SPINAND_PROG_LOAD(false, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static int ato25d1ga_ooblayout_ecc(struct mtd_info *mtd, int section,
diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c
index da4713692674..74bb5ee83b31 100644
--- a/drivers/mtd/nand/spi/core.c
+++ b/drivers/mtd/nand/spi/core.c
@@ -20,10 +20,111 @@
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
-static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
+static struct spi_mem_op
+spinand_fill_reset_op(struct spinand_device *spinand)
{
- struct spi_mem_op op = SPINAND_GET_FEATURE_OP(reg,
- spinand->scratchbuf);
+ return spinand->op_templates->reset;
+}
+
+static struct spi_mem_op
+spinand_fill_readid_op(struct spinand_device *spinand,
+ u8 naddr, u8 ndummy, void *buf, unsigned int len)
+{
+ struct spi_mem_op op = spinand->op_templates->readid;
+
+ op.addr.nbytes = naddr;
+ op.dummy.nbytes = ndummy;
+ op.data.buf.in = buf;
+ op.data.nbytes = len;
+
+ return op;
+}
+
+struct spi_mem_op
+spinand_fill_wr_en_op(struct spinand_device *spinand)
+{
+ return spinand->op_templates->wr_en;
+}
+
+static __maybe_unused struct spi_mem_op
+spinand_fill_wr_dis_op(struct spinand_device *spinand)
+{
+ return spinand->op_templates->wr_dis;
+}
+
+struct spi_mem_op
+spinand_fill_set_feature_op(struct spinand_device *spinand, u64 reg, const void *valptr)
+{
+ struct spi_mem_op op = spinand->op_templates->set_feature;
+
+ if (op.cmd.dtr && op.cmd.buswidth == 8)
+ reg |= reg << 8;
+
+ op.addr.val = reg;
+ op.data.buf.out = valptr;
+
+ return op;
+}
+
+struct spi_mem_op
+spinand_fill_get_feature_op(struct spinand_device *spinand, u64 reg, void *valptr)
+{
+ struct spi_mem_op op = spinand->op_templates->get_feature;
+
+ if (op.cmd.dtr && op.cmd.buswidth == 8)
+ reg |= reg << 8;
+
+ op.addr.val = reg;
+ op.data.buf.in = valptr;
+
+ return op;
+}
+
+static struct spi_mem_op
+spinand_fill_blk_erase_op(struct spinand_device *spinand, u64 addr)
+{
+ struct spi_mem_op op = spinand->op_templates->blk_erase;
+
+ op.addr.val = addr;
+
+ return op;
+}
+
+static struct spi_mem_op
+spinand_fill_page_read_op(struct spinand_device *spinand, u64 addr)
+{
+ struct spi_mem_op op = spinand->op_templates->page_read;
+
+ op.addr.val = addr;
+
+ return op;
+}
+
+static struct spi_mem_op
+spinand_fill_page_read_packed_op(struct spinand_device *spinand, u64 addr)
+{
+ struct spi_mem_op op = spinand->op_templates->page_read;
+
+ op.cmd.opcode |= addr >> 16;
+ op.addr.val = addr & 0xFFFF;
+
+ return op;
+}
+
+struct spi_mem_op
+spinand_fill_prog_exec_op(struct spinand_device *spinand, u64 addr)
+{
+ struct spi_mem_op op = spinand->op_templates->prog_exec;
+
+ op.addr.val = addr;
+
+ return op;
+}
+
+int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
+{
+ struct spi_mem_op op = SPINAND_OP(spinand, get_feature,
+ reg, spinand->scratchbuf);
int ret;
ret = spi_mem_exec_op(spinand->spimem, &op);
@@ -36,8 +137,8 @@ static int spinand_read_reg_op(struct spinand_device *spinand, u8 reg, u8 *val)
int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val)
{
- struct spi_mem_op op = SPINAND_SET_FEATURE_OP(reg,
- spinand->scratchbuf);
+ struct spi_mem_op op = SPINAND_OP(spinand, set_feature,
+ reg, spinand->scratchbuf);
*spinand->scratchbuf = val;
return spi_mem_exec_op(spinand->spimem, &op);
@@ -177,18 +278,9 @@ static int spinand_init_cfg_cache(struct spinand_device *spinand)
return 0;
}
-static int spinand_init_quad_enable(struct spinand_device *spinand)
+static int spinand_init_quad_enable(struct spinand_device *spinand,
+ bool enable)
{
- bool enable = false;
-
- if (!(spinand->flags & SPINAND_HAS_QE_BIT))
- return 0;
-
- if (spinand->op_templates.read_cache->data.buswidth == 4 ||
- spinand->op_templates.write_cache->data.buswidth == 4 ||
- spinand->op_templates.update_cache->data.buswidth == 4)
- enable = true;
-
return spinand_upd_cfg(spinand, CFG_QUAD_ENABLE,
enable ? CFG_QUAD_ENABLE : 0);
}
@@ -269,7 +361,7 @@ static int spinand_ondie_ecc_init_ctx(struct nand_device *nand)
nand->ecc.ctx.conf.step_size = nand->ecc.requirements.step_size;
nand->ecc.ctx.conf.strength = nand->ecc.requirements.strength;
- engine_conf = kzalloc(sizeof(*engine_conf), GFP_KERNEL);
+ engine_conf = kzalloc_obj(*engine_conf);
if (!engine_conf)
return -ENOMEM;
@@ -360,9 +452,9 @@ static void spinand_ondie_ecc_save_status(struct nand_device *nand, u8 status)
engine_conf->status = status;
}
-static int spinand_write_enable_op(struct spinand_device *spinand)
+int spinand_write_enable_op(struct spinand_device *spinand)
{
- struct spi_mem_op op = SPINAND_WR_EN_DIS_OP(true);
+ struct spi_mem_op op = SPINAND_OP(spinand, wr_en);
return spi_mem_exec_op(spinand->spimem, &op);
}
@@ -372,7 +464,10 @@ static int spinand_load_page_op(struct spinand_device *spinand,
{
struct nand_device *nand = spinand_to_nand(spinand);
unsigned int row = nanddev_pos_to_row(nand, &req->pos);
- struct spi_mem_op op = SPINAND_PAGE_READ_OP(row);
+ bool packed = spinand->flags & SPINAND_ODTR_PACKED_PAGE_READ;
+ struct spi_mem_op op = packed ?
+ SPINAND_OP(spinand, page_read_packed, row) :
+ SPINAND_OP(spinand, page_read, row);
return spi_mem_exec_op(spinand->spimem, &op);
}
@@ -406,10 +501,18 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand,
}
}
- if (req->mode == MTD_OPS_RAW)
- rdesc = spinand->dirmaps[req->pos.plane].rdesc;
+ rdesc = spinand->dirmaps[req->pos.plane].rdesc;
+
+ if (spinand->op_templates->cont_read_cache && req->continuous)
+ rdesc->info.op_tmpl = &rdesc->info.secondary_op_tmpl;
else
- rdesc = spinand->dirmaps[req->pos.plane].rdesc_ecc;
+ rdesc->info.op_tmpl = &rdesc->info.primary_op_tmpl;
+
+ if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED &&
+ req->mode != MTD_OPS_RAW)
+ rdesc->info.op_tmpl->data.ecc = true;
+ else
+ rdesc->info.op_tmpl->data.ecc = false;
if (spinand->flags & SPINAND_HAS_READ_PLANE_SELECT_BIT)
column |= req->pos.plane << fls(nanddev_page_size(nand));
@@ -430,8 +533,16 @@ static int spinand_read_from_cache_op(struct spinand_device *spinand,
* Dirmap accesses are allowed to toggle the CS.
* Toggling the CS during a continuous read is forbidden.
*/
- if (nbytes && req->continuous)
- return -EIO;
+ if (nbytes && req->continuous) {
+ /*
+ * Spi controller with broken support of continuous
+ * reading was detected. Disable future use of
+ * continuous reading and return -EAGAIN to retry
+ * reading within regular mode.
+ */
+ spinand->cont_read_possible = false;
+ return -EAGAIN;
+ }
}
if (req->datalen)
@@ -490,10 +601,13 @@ static int spinand_write_to_cache_op(struct spinand_device *spinand,
req->ooblen);
}
- if (req->mode == MTD_OPS_RAW)
- wdesc = spinand->dirmaps[req->pos.plane].wdesc;
+ wdesc = spinand->dirmaps[req->pos.plane].wdesc;
+
+ if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED &&
+ req->mode != MTD_OPS_RAW)
+ wdesc->info.op_tmpl->data.ecc = true;
else
- wdesc = spinand->dirmaps[req->pos.plane].wdesc_ecc;
+ wdesc->info.op_tmpl->data.ecc = false;
if (spinand->flags & SPINAND_HAS_PROG_PLANE_SELECT_BIT)
column |= req->pos.plane << fls(nanddev_page_size(nand));
@@ -519,7 +633,7 @@ static int spinand_program_op(struct spinand_device *spinand,
{
struct nand_device *nand = spinand_to_nand(spinand);
unsigned int row = nanddev_pos_to_row(nand, &req->pos);
- struct spi_mem_op op = SPINAND_PROG_EXEC_OP(row);
+ struct spi_mem_op op = SPINAND_OP(spinand, prog_exec, row);
return spi_mem_exec_op(spinand->spimem, &op);
}
@@ -529,18 +643,28 @@ static int spinand_erase_op(struct spinand_device *spinand,
{
struct nand_device *nand = spinand_to_nand(spinand);
unsigned int row = nanddev_pos_to_row(nand, pos);
- struct spi_mem_op op = SPINAND_BLK_ERASE_OP(row);
+ struct spi_mem_op op = SPINAND_OP(spinand, blk_erase, row);
return spi_mem_exec_op(spinand->spimem, &op);
}
-static int spinand_wait(struct spinand_device *spinand,
- unsigned long initial_delay_us,
- unsigned long poll_delay_us,
- u8 *s)
+/**
+ * spinand_wait() - Poll memory device status
+ * @spinand: the spinand device
+ * @initial_delay_us: delay in us before starting to poll
+ * @poll_delay_us: time to sleep between reads in us
+ * @s: the pointer to variable to store the value of REG_STATUS
+ *
+ * This function polls a status register (REG_STATUS) and returns when
+ * the STATUS_READY bit is 0 or when the timeout has expired.
+ *
+ * Return: 0 on success, a negative error code otherwise.
+ */
+int spinand_wait(struct spinand_device *spinand, unsigned long initial_delay_us,
+ unsigned long poll_delay_us, u8 *s)
{
- struct spi_mem_op op = SPINAND_GET_FEATURE_OP(REG_STATUS,
- spinand->scratchbuf);
+ struct spi_mem_op op = SPINAND_OP(spinand, get_feature,
+ REG_STATUS, spinand->scratchbuf);
u8 status;
int ret;
@@ -573,8 +697,8 @@ out:
static int spinand_read_id_op(struct spinand_device *spinand, u8 naddr,
u8 ndummy, u8 *buf)
{
- struct spi_mem_op op = SPINAND_READID_OP(
- naddr, ndummy, spinand->scratchbuf, SPINAND_MAX_ID_LEN);
+ struct spi_mem_op op = SPINAND_OP(spinand, readid,
+ naddr, ndummy, spinand->scratchbuf, SPINAND_MAX_ID_LEN);
int ret;
ret = spi_mem_exec_op(spinand->spimem, &op);
@@ -586,7 +710,7 @@ static int spinand_read_id_op(struct spinand_device *spinand, u8 naddr,
static int spinand_reset_op(struct spinand_device *spinand)
{
- struct spi_mem_op op = SPINAND_RESET_OP;
+ struct spi_mem_op op = SPINAND_OP(spinand, reset);
int ret;
ret = spi_mem_exec_op(spinand->spimem, &op);
@@ -604,8 +728,16 @@ static int spinand_lock_block(struct spinand_device *spinand, u8 lock)
return spinand_write_reg_op(spinand, REG_BLOCK_LOCK, lock);
}
-static int spinand_read_page(struct spinand_device *spinand,
- const struct nand_page_io_req *req)
+/**
+ * spinand_read_page() - Read a page
+ * @spinand: the spinand device
+ * @req: the I/O request
+ *
+ * Return: 0 or a positive number of bitflips corrected on success.
+ * A negative error code otherwise.
+ */
+int spinand_read_page(struct spinand_device *spinand,
+ const struct nand_page_io_req *req)
{
struct nand_device *nand = spinand_to_nand(spinand);
u8 status;
@@ -635,8 +767,16 @@ static int spinand_read_page(struct spinand_device *spinand,
return nand_ecc_finish_io_req(nand, (struct nand_page_io_req *)req);
}
-static int spinand_write_page(struct spinand_device *spinand,
- const struct nand_page_io_req *req)
+/**
+ * spinand_write_page() - Write a page
+ * @spinand: the spinand device
+ * @req: the I/O request
+ *
+ * Return: 0 or a positive number of bitflips corrected on success.
+ * A negative error code otherwise.
+ */
+int spinand_write_page(struct spinand_device *spinand,
+ const struct nand_page_io_req *req)
{
struct nand_device *nand = spinand_to_nand(spinand);
u8 status;
@@ -662,7 +802,10 @@ static int spinand_write_page(struct spinand_device *spinand,
SPINAND_WRITE_INITIAL_DELAY_US,
SPINAND_WRITE_POLL_DELAY_US,
&status);
- if (!ret && (status & STATUS_PROG_FAILED))
+ if (ret)
+ return ret;
+
+ if (status & STATUS_PROG_FAILED)
return -EIO;
return nand_ecc_finish_io_req(nand, (struct nand_page_io_req *)req);
@@ -674,10 +817,14 @@ static int spinand_mtd_regular_page_read(struct mtd_info *mtd, loff_t from,
{
struct spinand_device *spinand = mtd_to_spinand(mtd);
struct nand_device *nand = mtd_to_nanddev(mtd);
+ struct mtd_ecc_stats old_stats;
struct nand_io_iter iter;
bool disable_ecc = false;
bool ecc_failed = false;
- int ret;
+ unsigned int retry_mode = 0;
+ int ret = 0;
+
+ old_stats = mtd->ecc_stats;
if (ops->mode == MTD_OPS_RAW || !mtd->ooblayout)
disable_ecc = true;
@@ -690,18 +837,43 @@ static int spinand_mtd_regular_page_read(struct mtd_info *mtd, loff_t from,
if (ret)
break;
+read_retry:
ret = spinand_read_page(spinand, &iter.req);
if (ret < 0 && ret != -EBADMSG)
break;
- if (ret == -EBADMSG)
+ if (ret == -EBADMSG && spinand->set_read_retry) {
+ if (spinand->read_retries && (++retry_mode <= spinand->read_retries)) {
+ ret = spinand->set_read_retry(spinand, retry_mode);
+ if (ret < 0) {
+ spinand->set_read_retry(spinand, 0);
+ return ret;
+ }
+
+ /* Reset ecc_stats; retry */
+ mtd->ecc_stats = old_stats;
+ goto read_retry;
+ } else {
+ /* No more retry modes; real failure */
+ ecc_failed = true;
+ }
+ } else if (ret == -EBADMSG) {
ecc_failed = true;
- else
+ } else {
*max_bitflips = max_t(unsigned int, *max_bitflips, ret);
+ }
ret = 0;
ops->retlen += iter.req.datalen;
ops->oobretlen += iter.req.ooblen;
+
+ /* Reset to retry mode 0 */
+ if (retry_mode) {
+ retry_mode = 0;
+ ret = spinand->set_read_retry(spinand, retry_mode);
+ if (ret < 0)
+ return ret;
+ }
}
if (ecc_failed && !ret)
@@ -731,6 +903,12 @@ static int spinand_mtd_continuous_page_read(struct mtd_info *mtd, loff_t from,
* Each data read must be a multiple of 4-bytes and full pages should be read;
* otherwise, the data output might get out of sequence from one read command
* to another.
+ *
+ * Continuous reads never cross LUN boundaries. Some devices don't
+ * support crossing planes boundaries. Some devices don't even support
+ * crossing blocks boundaries. The common case being to read through UBI,
+ * we will very rarely read two consequent blocks or more, so let's only enable
+ * continuous reads when reading within the same erase block.
*/
nanddev_io_for_each_block(nand, NAND_PAGE_READ, from, ops, &iter) {
ret = spinand_select_target(spinand, iter.req.pos.target);
@@ -787,12 +965,22 @@ static void spinand_cont_read_init(struct spinand_device *spinand)
{
struct nand_device *nand = spinand_to_nand(spinand);
enum nand_ecc_engine_type engine_type = nand->ecc.ctx.conf.engine_type;
+ struct spi_controller *ctlr = spinand->spimem->spi->controller;
/* OOBs cannot be retrieved so external/on-host ECC engine won't work */
- if (spinand->set_cont_read &&
- (engine_type == NAND_ECC_ENGINE_TYPE_ON_DIE ||
- engine_type == NAND_ECC_ENGINE_TYPE_NONE)) {
- spinand->cont_read_possible = true;
+ if (spinand->set_cont_read) {
+ /*
+ * Ensure continuous read is disabled on probe.
+ * Some devices retain this state across soft reset,
+ * which leaves the OOB area inaccessible and results
+ * in false positive returns from spinand_isbad().
+ */
+ spinand_cont_read_enable(spinand, false);
+
+ if ((engine_type == NAND_ECC_ENGINE_TYPE_ON_DIE ||
+ engine_type == NAND_ECC_ENGINE_TYPE_NONE) &&
+ !spi_mem_controller_is_capable(ctlr, no_cs_assertion))
+ spinand->cont_read_possible = true;
}
}
@@ -813,19 +1001,6 @@ static bool spinand_use_cont_read(struct mtd_info *mtd, loff_t from,
nanddev_offs_to_pos(nand, from, &start_pos);
nanddev_offs_to_pos(nand, from + ops->len - 1, &end_pos);
- /*
- * Continuous reads never cross LUN boundaries. Some devices don't
- * support crossing planes boundaries. Some devices don't even support
- * crossing blocks boundaries. The common case being to read through UBI,
- * we will very rarely read two consequent blocks or more, so it is safer
- * and easier (can be improved) to only enable continuous reads when
- * reading within the same erase block.
- */
- if (start_pos.target != end_pos.target ||
- start_pos.plane != end_pos.plane ||
- start_pos.eraseblock != end_pos.eraseblock)
- return false;
-
return start_pos.page < end_pos.page;
}
@@ -841,10 +1016,19 @@ static int spinand_mtd_read(struct mtd_info *mtd, loff_t from,
old_stats = mtd->ecc_stats;
- if (spinand_use_cont_read(mtd, from, ops))
+ if (spinand_use_cont_read(mtd, from, ops)) {
ret = spinand_mtd_continuous_page_read(mtd, from, ops, &max_bitflips);
- else
+ if (ret == -EAGAIN && !spinand->cont_read_possible) {
+ /*
+ * Spi controller with broken support of continuous
+ * reading was detected (see spinand_read_from_cache_op()),
+ * repeat reading in regular mode.
+ */
+ ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips);
+ }
+ } else {
ret = spinand_mtd_regular_page_read(mtd, from, ops, &max_bitflips);
+ }
if (ops->stats) {
ops->stats->uncorrectable_errors +=
@@ -1035,62 +1219,90 @@ static int spinand_mtd_block_isreserved(struct mtd_info *mtd, loff_t offs)
return ret;
}
-static int spinand_create_dirmap(struct spinand_device *spinand,
- unsigned int plane)
+static struct spi_mem_dirmap_desc *spinand_create_rdesc(
+ struct spinand_device *spinand,
+ struct spi_mem_dirmap_info *info)
{
struct nand_device *nand = spinand_to_nand(spinand);
- struct spi_mem_dirmap_info info = {
- .length = nanddev_page_size(nand) +
- nanddev_per_page_oobsize(nand),
- };
- struct spi_mem_dirmap_desc *desc;
+ struct spi_mem_dirmap_desc *desc = NULL;
- if (spinand->cont_read_possible)
- info.length = nanddev_eraseblock_size(nand);
+ if (spinand->cont_read_possible) {
+ /*
+ * spi controller may return an error if info->length is
+ * too large
+ */
+ info->length = nanddev_eraseblock_size(nand);
+ desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+ spinand->spimem, info);
+ }
- /* The plane number is passed in MSB just above the column address */
- info.offset = plane << fls(nand->memorg.pagesize);
+ if (IS_ERR_OR_NULL(desc)) {
+ /*
+ * continuous reading is not supported by flash or
+ * its spi controller, use regular reading
+ */
+ spinand->cont_read_possible = false;
+ memset(&info->secondary_op_tmpl, 0, sizeof(info->secondary_op_tmpl));
- info.op_tmpl = *spinand->op_templates.update_cache;
- desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
- spinand->spimem, &info);
- if (IS_ERR(desc))
- return PTR_ERR(desc);
+ info->length = nanddev_page_size(nand) +
+ nanddev_per_page_oobsize(nand);
+ desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
+ spinand->spimem, info);
+ }
- spinand->dirmaps[plane].wdesc = desc;
+ return desc;
+}
- info.op_tmpl = *spinand->op_templates.read_cache;
- desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
- spinand->spimem, &info);
- if (IS_ERR(desc))
- return PTR_ERR(desc);
+static int spinand_create_dirmap(struct spinand_device *spinand,
+ unsigned int plane)
+{
+ struct nand_device *nand = spinand_to_nand(spinand);
+ struct spi_mem_dirmap_info info = { 0 };
+ struct spi_mem_dirmap_desc *desc;
+ bool enable_ecc = false, secondary_op = false;
- spinand->dirmaps[plane].rdesc = desc;
+ if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED)
+ enable_ecc = true;
- if (nand->ecc.engine->integration != NAND_ECC_ENGINE_INTEGRATION_PIPELINED) {
- spinand->dirmaps[plane].wdesc_ecc = spinand->dirmaps[plane].wdesc;
- spinand->dirmaps[plane].rdesc_ecc = spinand->dirmaps[plane].rdesc;
+ if (spinand->cont_read_possible && spinand->op_templates->cont_read_cache)
+ secondary_op = true;
- return 0;
+ /*
+ * Continuous read implies that only the main data is retrieved, backed
+ * by an on-die ECC engine. It is not possible to use a pipelind ECC
+ * engine with continuous read.
+ */
+ if (enable_ecc && secondary_op) {
+ secondary_op = false;
+ spinand->cont_read_possible = false;
}
- info.op_tmpl = *spinand->op_templates.update_cache;
- info.op_tmpl.data.ecc = true;
+ /* The plane number is passed in MSB just above the column address */
+ info.offset = plane << fls(nand->memorg.pagesize);
+
+ /* Write descriptor */
+ info.length = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
+ info.primary_op_tmpl = *spinand->op_templates->update_cache;
+ info.primary_op_tmpl.data.ecc = enable_ecc;
desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
spinand->spimem, &info);
if (IS_ERR(desc))
return PTR_ERR(desc);
- spinand->dirmaps[plane].wdesc_ecc = desc;
+ spinand->dirmaps[plane].wdesc = desc;
- info.op_tmpl = *spinand->op_templates.read_cache;
- info.op_tmpl.data.ecc = true;
- desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev,
- spinand->spimem, &info);
+ /* Read descriptor */
+ info.primary_op_tmpl = *spinand->op_templates->read_cache;
+ info.primary_op_tmpl.data.ecc = enable_ecc;
+ if (secondary_op) {
+ info.secondary_op_tmpl = *spinand->op_templates->cont_read_cache;
+ info.secondary_op_tmpl.data.ecc = enable_ecc;
+ }
+ desc = spinand_create_rdesc(spinand, &info);
if (IS_ERR(desc))
return PTR_ERR(desc);
- spinand->dirmaps[plane].rdesc_ecc = desc;
+ spinand->dirmaps[plane].rdesc = desc;
return 0;
}
@@ -1116,6 +1328,22 @@ static int spinand_create_dirmaps(struct spinand_device *spinand)
return 0;
}
+static int spinand_randomizer_init(struct spinand_device *spinand)
+{
+ struct device_node *np = spinand->spimem->spi->dev.of_node;
+ u32 rand_val;
+ int ret;
+
+ if (!spinand->set_randomizer)
+ return 0;
+
+ ret = of_property_read_u32(np, "nand-randomizer", &rand_val);
+ if (ret)
+ return 0;
+
+ return spinand->set_randomizer(spinand, rand_val);
+}
+
static const struct nand_ops spinand_ops = {
.erase = spinand_erase,
.markbad = spinand_markbad,
@@ -1125,7 +1353,10 @@ static const struct nand_ops spinand_ops = {
static const struct spinand_manufacturer *spinand_manufacturers[] = {
&alliancememory_spinand_manufacturer,
&ato_spinand_manufacturer,
+ &dosilicon_spinand_manufacturer,
+ &esmt_8c_spinand_manufacturer,
&esmt_c8_spinand_manufacturer,
+ &fmsh_spinand_manufacturer,
&foresee_spinand_manufacturer,
&gigadevice_spinand_manufacturer,
&macronix_spinand_manufacturer,
@@ -1195,8 +1426,13 @@ static int spinand_id_detect(struct spinand_device *spinand)
static int spinand_manufacturer_init(struct spinand_device *spinand)
{
- if (spinand->manufacturer->ops->init)
- return spinand->manufacturer->ops->init(spinand);
+ int ret;
+
+ if (spinand->manufacturer->ops->init) {
+ ret = spinand->manufacturer->ops->init(spinand);
+ if (ret)
+ return ret;
+ }
return 0;
}
@@ -1208,8 +1444,105 @@ static void spinand_manufacturer_cleanup(struct spinand_device *spinand)
return spinand->manufacturer->ops->cleanup(spinand);
}
+bool spinand_op_is_odtr(const struct spi_mem_op *op)
+{
+ return op->cmd.dtr && op->cmd.buswidth == 8;
+}
+
+static void spinand_init_ssdr_templates(struct spinand_device *spinand)
+{
+ struct spinand_mem_ops *tmpl = &spinand->ssdr_op_templates;
+
+ tmpl->reset = (struct spi_mem_op)SPINAND_RESET_1S_0_0_OP;
+ tmpl->readid = (struct spi_mem_op)SPINAND_READID_1S_1S_1S_OP(0, 0, NULL, 0);
+ tmpl->wr_en = (struct spi_mem_op)SPINAND_WR_EN_1S_0_0_OP;
+ tmpl->wr_dis = (struct spi_mem_op)SPINAND_WR_DIS_1S_0_0_OP;
+ tmpl->set_feature = (struct spi_mem_op)SPINAND_SET_FEATURE_1S_1S_1S_OP(0, NULL);
+ tmpl->get_feature = (struct spi_mem_op)SPINAND_GET_FEATURE_1S_1S_1S_OP(0, NULL);
+ tmpl->blk_erase = (struct spi_mem_op)SPINAND_BLK_ERASE_1S_1S_0_OP(0);
+ tmpl->page_read = (struct spi_mem_op)SPINAND_PAGE_READ_1S_1S_0_OP(0);
+ tmpl->prog_exec = (struct spi_mem_op)SPINAND_PROG_EXEC_1S_1S_0_OP(0);
+ spinand->op_templates = &spinand->ssdr_op_templates;
+ spinand->bus_iface = SSDR;
+}
+
+static int spinand_support_vendor_ops(struct spinand_device *spinand,
+ const struct spinand_info *info,
+ enum spinand_bus_interface iface)
+{
+ int i;
+
+ if (!info->vendor_ops)
+ return 0;
+ /*
+ * The vendor ops array is only used in order to verify this chip and all its memory
+ * operations are supported. If we see patterns emerging, we could ideally name these
+ * operations and define them at the SPI NAND core level instead.
+ * For now, this only serves as a sanity check.
+ */
+ for (i = 0; i < info->vendor_ops->nops; i++) {
+ const struct spi_mem_op *op = &info->vendor_ops->ops[i];
+
+ if ((iface == SSDR && spinand_op_is_odtr(op)) ||
+ (iface == ODTR && !spinand_op_is_odtr(op)))
+ continue;
+
+ if (!spi_mem_supports_op(spinand->spimem, op))
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int spinand_init_odtr_instruction_set(struct spinand_device *spinand)
+{
+ struct spinand_mem_ops *tmpl = &spinand->odtr_op_templates;
+
+ tmpl->reset = (struct spi_mem_op)SPINAND_RESET_8D_0_0_OP;
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->reset))
+ return -EOPNOTSUPP;
+
+ tmpl->readid = (struct spi_mem_op)SPINAND_READID_8D_8D_8D_OP(0, 0, NULL, 0);
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->readid))
+ return -EOPNOTSUPP;
+
+ tmpl->wr_en = (struct spi_mem_op)SPINAND_WR_EN_8D_0_0_OP;
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->wr_en))
+ return -EOPNOTSUPP;
+
+ tmpl->wr_dis = (struct spi_mem_op)SPINAND_WR_DIS_8D_0_0_OP;
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->wr_dis))
+ return -EOPNOTSUPP;
+
+ tmpl->set_feature = (struct spi_mem_op)SPINAND_SET_FEATURE_8D_8D_8D_OP(0, NULL);
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->set_feature))
+ return -EOPNOTSUPP;
+
+ tmpl->get_feature = (struct spi_mem_op)SPINAND_GET_FEATURE_8D_8D_8D_OP(0, NULL);
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->get_feature))
+ return -EOPNOTSUPP;
+
+ tmpl->blk_erase = (struct spi_mem_op)SPINAND_BLK_ERASE_8D_8D_0_OP(0);
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->blk_erase))
+ return -EOPNOTSUPP;
+
+ if (spinand->flags & SPINAND_ODTR_PACKED_PAGE_READ)
+ tmpl->page_read = (struct spi_mem_op)SPINAND_PAGE_READ_PACKED_8D_8D_0_OP(0);
+ else
+ tmpl->page_read = (struct spi_mem_op)SPINAND_PAGE_READ_8D_8D_0_OP(0);
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->page_read)) {
+ return -EOPNOTSUPP;
+ }
+
+ tmpl->prog_exec = (struct spi_mem_op)SPINAND_PROG_EXEC_8D_8D_0_OP(0);
+ if (!spi_mem_supports_op(spinand->spimem, &tmpl->prog_exec))
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
static const struct spi_mem_op *
-spinand_select_op_variant(struct spinand_device *spinand,
+spinand_select_op_variant(struct spinand_device *spinand, enum spinand_bus_interface iface,
const struct spinand_op_variants *variants)
{
struct nand_device *nand = spinand_to_nand(spinand);
@@ -1223,6 +1556,10 @@ spinand_select_op_variant(struct spinand_device *spinand,
unsigned int nbytes;
int ret;
+ if ((iface == SSDR && spinand_op_is_odtr(&op)) ||
+ (iface == ODTR && !spinand_op_is_odtr(&op)))
+ continue;
+
nbytes = nanddev_per_page_oobsize(nand) +
nanddev_page_size(nand);
@@ -1239,7 +1576,7 @@ spinand_select_op_variant(struct spinand_device *spinand,
nbytes -= op.data.nbytes;
- op_duration_ns += spi_mem_calc_op_duration(&op);
+ op_duration_ns += spi_mem_calc_op_duration(spinand->spimem, &op);
}
if (!nbytes && op_duration_ns < best_op_duration_ns) {
@@ -1274,6 +1611,7 @@ int spinand_match_and_init(struct spinand_device *spinand,
u8 *id = spinand->id.data;
struct nand_device *nand = spinand_to_nand(spinand);
unsigned int i;
+ int ret;
for (i = 0; i < table_size; i++) {
const struct spinand_info *info = &table[i];
@@ -1291,30 +1629,103 @@ int spinand_match_and_init(struct spinand_device *spinand,
spinand->flags = table[i].flags;
spinand->id.len = 1 + table[i].devid.len;
spinand->select_target = table[i].select_target;
+ spinand->configure_chip = table[i].configure_chip;
spinand->set_cont_read = table[i].set_cont_read;
+ spinand->fact_otp = &table[i].fact_otp;
+ spinand->user_otp = &table[i].user_otp;
+ spinand->read_retries = table[i].read_retries;
+ spinand->set_read_retry = table[i].set_read_retry;
+ spinand->set_randomizer = table[i].set_randomizer;
- op = spinand_select_op_variant(spinand,
+ /* I/O variants selection with single-spi SDR commands */
+
+ op = spinand_select_op_variant(spinand, SSDR,
info->op_variants.read_cache);
if (!op)
- return -ENOTSUPP;
+ return -EOPNOTSUPP;
- spinand->op_templates.read_cache = op;
+ spinand->ssdr_op_templates.read_cache = op;
- op = spinand_select_op_variant(spinand,
+ op = spinand_select_op_variant(spinand, SSDR,
info->op_variants.write_cache);
if (!op)
- return -ENOTSUPP;
+ return -EOPNOTSUPP;
+
+ spinand->ssdr_op_templates.write_cache = op;
+
+ op = spinand_select_op_variant(spinand, SSDR,
+ info->op_variants.update_cache);
+ if (!op)
+ return -EOPNOTSUPP;
+
+ spinand->ssdr_op_templates.update_cache = op;
- spinand->op_templates.write_cache = op;
+ ret = spinand_support_vendor_ops(spinand, info, SSDR);
+ if (ret)
+ return ret;
+
+ if (info->op_variants.cont_read_cache) {
+ op = spinand_select_op_variant(spinand, SSDR,
+ info->op_variants.cont_read_cache);
+ if (op) {
+ const struct spi_mem_op *read_op;
+
+ read_op = spinand->ssdr_op_templates.read_cache;
+
+ /*
+ * Sometimes the fastest continuous read variant may not
+ * be supported. In this case, prefer to use the fastest
+ * read from cache variant and disable continuous reads.
+ */
+ if (read_op->cmd.buswidth > op->cmd.buswidth ||
+ (read_op->cmd.dtr && !op->cmd.dtr) ||
+ read_op->addr.buswidth > op->addr.buswidth ||
+ (read_op->addr.dtr && !op->addr.dtr) ||
+ read_op->data.buswidth > op->data.buswidth ||
+ (read_op->data.dtr && !op->data.dtr))
+ spinand->cont_read_possible = false;
+ else
+ spinand->ssdr_op_templates.cont_read_cache = op;
+ } else {
+ spinand->cont_read_possible = false;
+ }
+ }
+
+ /* I/O variants selection with octo-spi DDR commands (optional) */
+
+ ret = spinand_init_odtr_instruction_set(spinand);
+ if (ret)
+ return 0;
+
+ ret = spinand_support_vendor_ops(spinand, info, ODTR);
+ if (ret)
+ return 0;
- op = spinand_select_op_variant(spinand,
+ op = spinand_select_op_variant(spinand, ODTR,
+ info->op_variants.read_cache);
+ spinand->odtr_op_templates.read_cache = op;
+
+ op = spinand_select_op_variant(spinand, ODTR,
+ info->op_variants.write_cache);
+ spinand->odtr_op_templates.write_cache = op;
+
+ op = spinand_select_op_variant(spinand, ODTR,
info->op_variants.update_cache);
- spinand->op_templates.update_cache = op;
+ spinand->odtr_op_templates.update_cache = op;
+
+ if (info->op_variants.cont_read_cache) {
+ op = spinand_select_op_variant(spinand, ODTR,
+ info->op_variants.cont_read_cache);
+ if (op)
+ spinand->odtr_op_templates.cont_read_cache = op;
+ else
+ spinand->cont_read_possible = false;
+ }
return 0;
}
- return -ENOTSUPP;
+ return -EOPNOTSUPP;
}
static int spinand_detect(struct spinand_device *spinand)
@@ -1350,6 +1761,56 @@ static int spinand_detect(struct spinand_device *spinand)
return 0;
}
+static int spinand_configure_chip(struct spinand_device *spinand)
+{
+ bool odtr = false, quad_enable = false;
+ int ret;
+
+ if (spinand->odtr_op_templates.read_cache &&
+ spinand->odtr_op_templates.write_cache &&
+ spinand->odtr_op_templates.update_cache)
+ odtr = true;
+
+ if (odtr) {
+ if (!spinand->configure_chip)
+ goto try_ssdr;
+
+ /* ODTR bus interface configuration happens here */
+ ret = spinand->configure_chip(spinand, ODTR);
+ if (ret) {
+ spinand->odtr_op_templates.read_cache = NULL;
+ spinand->odtr_op_templates.write_cache = NULL;
+ spinand->odtr_op_templates.update_cache = NULL;
+ goto try_ssdr;
+ }
+
+ spinand->op_templates = &spinand->odtr_op_templates;
+ spinand->bus_iface = ODTR;
+
+ return 0;
+ }
+
+try_ssdr:
+ if (spinand->flags & SPINAND_HAS_QE_BIT) {
+ if (spinand->ssdr_op_templates.read_cache->data.buswidth == 4 ||
+ spinand->ssdr_op_templates.write_cache->data.buswidth == 4 ||
+ spinand->ssdr_op_templates.update_cache->data.buswidth == 4)
+ quad_enable = true;
+ }
+
+ ret = spinand_init_quad_enable(spinand, quad_enable);
+ if (ret)
+ return ret;
+
+ if (spinand->configure_chip) {
+ ret = spinand->configure_chip(spinand, SSDR);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
static int spinand_init_flash(struct spinand_device *spinand)
{
struct device *dev = &spinand->spimem->spi->dev;
@@ -1360,10 +1821,6 @@ static int spinand_init_flash(struct spinand_device *spinand)
if (ret)
return ret;
- ret = spinand_init_quad_enable(spinand);
- if (ret)
- return ret;
-
ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0);
if (ret)
return ret;
@@ -1376,19 +1833,25 @@ static int spinand_init_flash(struct spinand_device *spinand)
return ret;
}
+ ret = spinand_configure_chip(spinand);
+ if (ret)
+ goto manuf_cleanup;
+
/* After power up, all blocks are locked, so unlock them here. */
for (i = 0; i < nand->memorg.ntargets; i++) {
ret = spinand_select_target(spinand, i);
if (ret)
- break;
+ goto manuf_cleanup;
ret = spinand_lock_block(spinand, BL_ALL_UNLOCKED);
if (ret)
- break;
+ goto manuf_cleanup;
}
- if (ret)
- spinand_manufacturer_cleanup(spinand);
+ return 0;
+
+manuf_cleanup:
+ spinand_manufacturer_cleanup(spinand);
return ret;
}
@@ -1409,6 +1872,32 @@ static void spinand_mtd_resume(struct mtd_info *mtd)
spinand_ecc_enable(spinand, false);
}
+static int spinand_mtd_suspend(struct mtd_info *mtd)
+{
+ struct spinand_device *spinand = mtd_to_spinand(mtd);
+ int ret;
+
+ /*
+ * Return to SSDR interface in the suspend path to make sure the
+ * reset operation is correctly processed upon resume.
+ *
+ * Note: Once back in SSDR mode, every operation but the page helpers
+ * (dirmap based I/O accessors) will work. Page accesses would require
+ * destroying and recreating the dirmaps twice to work, which would be
+ * impacting for no reason, as this is just a transitional state.
+ */
+ if (spinand->bus_iface == ODTR) {
+ ret = spinand->configure_chip(spinand, SSDR);
+ if (ret)
+ return ret;
+
+ spinand->op_templates = &spinand->ssdr_op_templates;
+ spinand->bus_iface = SSDR;
+ }
+
+ return 0;
+}
+
static int spinand_init(struct spinand_device *spinand)
{
struct device *dev = &spinand->spimem->spi->dev;
@@ -1424,6 +1913,8 @@ static int spinand_init(struct spinand_device *spinand)
if (!spinand->scratchbuf)
return -ENOMEM;
+ spinand_init_ssdr_templates(spinand);
+
ret = spinand_detect(spinand);
if (ret)
goto err_free_bufs;
@@ -1468,6 +1959,9 @@ static int spinand_init(struct spinand_device *spinand)
* ECC initialization must have happened previously.
*/
spinand_cont_read_init(spinand);
+ ret = spinand_randomizer_init(spinand);
+ if (ret)
+ goto err_cleanup_nanddev;
mtd->_read_oob = spinand_mtd_read;
mtd->_write_oob = spinand_mtd_write;
@@ -1476,8 +1970,15 @@ static int spinand_init(struct spinand_device *spinand)
mtd->_block_isreserved = spinand_mtd_block_isreserved;
mtd->_erase = spinand_mtd_erase;
mtd->_max_bad_blocks = nanddev_mtd_max_bad_blocks;
+ mtd->_suspend = spinand_mtd_suspend;
mtd->_resume = spinand_mtd_resume;
+ if (spinand_user_otp_size(spinand) || spinand_fact_otp_size(spinand)) {
+ ret = spinand_set_mtd_otp_ops(spinand);
+ if (ret)
+ goto err_cleanup_ecc_engine;
+ }
+
if (nand->ecc.engine) {
ret = mtd_ooblayout_count_freebytes(mtd);
if (ret < 0)
@@ -1520,6 +2021,7 @@ static void spinand_cleanup(struct spinand_device *spinand)
{
struct nand_device *nand = spinand_to_nand(spinand);
+ nanddev_ecc_engine_cleanup(nand);
nanddev_cleanup(nand);
spinand_manufacturer_cleanup(spinand);
kfree(spinand->databuf);
diff --git a/drivers/mtd/nand/spi/dosilicon.c b/drivers/mtd/nand/spi/dosilicon.c
new file mode 100644
index 000000000000..f99899866ceb
--- /dev/null
+++ b/drivers/mtd/nand/spi/dosilicon.c
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Author: Ahmed Naseef <naseefkm@gmail.com>
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/mtd/spinand.h>
+
+#define SPINAND_MFR_DOSILICON 0xE5
+
+static SPINAND_OP_VARIANTS(read_cache_variants,
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
+
+static SPINAND_OP_VARIANTS(write_cache_variants,
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
+
+static SPINAND_OP_VARIANTS(update_cache_variants,
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
+
+static int ds35xx_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section > 3)
+ return -ERANGE;
+
+ region->offset = 8 + (section * 16);
+ region->length = 8;
+
+ return 0;
+}
+
+static int ds35xx_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section > 3)
+ return -ERANGE;
+
+ if (section == 0) {
+ /* reserve 2 bytes for the BBM */
+ region->offset = 2;
+ region->length = 6;
+ } else {
+ region->offset = section * 16;
+ region->length = 8;
+ }
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops ds35xx_ooblayout = {
+ .ecc = ds35xx_ooblayout_ecc,
+ .free = ds35xx_ooblayout_free,
+};
+
+static const struct spinand_info dosilicon_spinand_table[] = {
+ SPINAND_INFO("DS35Q1GA",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x71),
+ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(4, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&ds35xx_ooblayout, NULL)),
+ SPINAND_INFO("DS35M1GA",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x21),
+ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(4, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&ds35xx_ooblayout, NULL)),
+};
+
+static const struct spinand_manufacturer_ops dosilicon_spinand_manuf_ops = {
+};
+
+const struct spinand_manufacturer dosilicon_spinand_manufacturer = {
+ .id = SPINAND_MFR_DOSILICON,
+ .name = "Dosilicon",
+ .chips = dosilicon_spinand_table,
+ .nchips = ARRAY_SIZE(dosilicon_spinand_table),
+ .ops = &dosilicon_spinand_manuf_ops,
+};
diff --git a/drivers/mtd/nand/spi/esmt.c b/drivers/mtd/nand/spi/esmt.c
index 323a20901fc9..8607c2fdedcf 100644
--- a/drivers/mtd/nand/spi/esmt.c
+++ b/drivers/mtd/nand/spi/esmt.c
@@ -8,23 +8,29 @@
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mtd/spinand.h>
+#include <linux/spi/spi-mem.h>
/* ESMT uses GigaDevice 0xc8 JECDEC ID on some SPI NANDs */
#define SPINAND_MFR_ESMT_C8 0xc8
+#define SPINAND_MFR_ESMT_8C 0x8c
+
+#define ESMT_F50L1G41LB_CFG_OTP_PROTECT BIT(7)
+#define ESMT_F50L1G41LB_CFG_OTP_LOCK \
+ (CFG_OTP_ENABLE | ESMT_F50L1G41LB_CFG_OTP_PROTECT)
static SPINAND_OP_VARIANTS(read_cache_variants,
- SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
- SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
- SPINAND_PROG_LOAD(true, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
- SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
- SPINAND_PROG_LOAD(false, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
/*
* OOB spare area map (64 bytes)
@@ -102,6 +108,98 @@ static const struct mtd_ooblayout_ops f50l1g41lb_ooblayout = {
.free = f50l1g41lb_ooblayout_free,
};
+static int f50l1g41lb_otp_info(struct spinand_device *spinand, size_t len,
+ struct otp_info *buf, size_t *retlen, bool user)
+{
+ if (len < sizeof(*buf))
+ return -EINVAL;
+
+ buf->locked = 0;
+ buf->start = 0;
+ buf->length = user ? spinand_user_otp_size(spinand) :
+ spinand_fact_otp_size(spinand);
+
+ *retlen = sizeof(*buf);
+ return 0;
+}
+
+static int f50l1g41lb_fact_otp_info(struct spinand_device *spinand, size_t len,
+ struct otp_info *buf, size_t *retlen)
+{
+ return f50l1g41lb_otp_info(spinand, len, buf, retlen, false);
+}
+
+static int f50l1g41lb_user_otp_info(struct spinand_device *spinand, size_t len,
+ struct otp_info *buf, size_t *retlen)
+{
+ return f50l1g41lb_otp_info(spinand, len, buf, retlen, true);
+}
+
+static int f50l1g41lb_otp_lock(struct spinand_device *spinand, loff_t from,
+ size_t len)
+{
+ struct spi_mem_op write_op = SPINAND_OP(spinand, wr_en);
+ struct spi_mem_op exec_op = SPINAND_OP(spinand, prog_exec, 0);
+ u8 status;
+ int ret;
+
+ ret = spinand_upd_cfg(spinand, ESMT_F50L1G41LB_CFG_OTP_LOCK,
+ ESMT_F50L1G41LB_CFG_OTP_LOCK);
+ if (!ret)
+ return ret;
+
+ ret = spi_mem_exec_op(spinand->spimem, &write_op);
+ if (!ret)
+ goto out;
+
+ ret = spi_mem_exec_op(spinand->spimem, &exec_op);
+ if (!ret)
+ goto out;
+
+ ret = spinand_wait(spinand,
+ SPINAND_WRITE_INITIAL_DELAY_US,
+ SPINAND_WRITE_POLL_DELAY_US,
+ &status);
+ if (!ret && (status & STATUS_PROG_FAILED))
+ ret = -EIO;
+
+out:
+ if (spinand_upd_cfg(spinand, ESMT_F50L1G41LB_CFG_OTP_LOCK, 0)) {
+ dev_warn(&spinand_to_mtd(spinand)->dev,
+ "Can not disable OTP mode\n");
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static const struct spinand_user_otp_ops f50l1g41lb_user_otp_ops = {
+ .info = f50l1g41lb_user_otp_info,
+ .lock = f50l1g41lb_otp_lock,
+ .read = spinand_user_otp_read,
+ .write = spinand_user_otp_write,
+};
+
+static const struct spinand_fact_otp_ops f50l1g41lb_fact_otp_ops = {
+ .info = f50l1g41lb_fact_otp_info,
+ .read = spinand_fact_otp_read,
+};
+
+
+static const struct spinand_info esmt_8c_spinand_table[] = {
+ SPINAND_INFO("F50L1G41LC",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x2C),
+ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(1, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ 0,
+ SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL),
+ SPINAND_USER_OTP_INFO(28, 2, &f50l1g41lb_user_otp_ops),
+ SPINAND_FACT_OTP_INFO(2, 0, &f50l1g41lb_fact_otp_ops)),
+};
+
static const struct spinand_info esmt_c8_spinand_table[] = {
SPINAND_INFO("F50L1G41LB",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x01, 0x7f,
@@ -112,7 +210,9 @@ static const struct spinand_info esmt_c8_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
0,
- SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL)),
+ SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL),
+ SPINAND_USER_OTP_INFO(28, 2, &f50l1g41lb_user_otp_ops),
+ SPINAND_FACT_OTP_INFO(2, 0, &f50l1g41lb_fact_otp_ops)),
SPINAND_INFO("F50D1G41LB",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x11, 0x7f,
0x7f, 0x7f),
@@ -122,7 +222,9 @@ static const struct spinand_info esmt_c8_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
0,
- SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL)),
+ SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL),
+ SPINAND_USER_OTP_INFO(28, 2, &f50l1g41lb_user_otp_ops),
+ SPINAND_FACT_OTP_INFO(2, 0, &f50l1g41lb_fact_otp_ops)),
SPINAND_INFO("F50D2G41KA",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x51, 0x7f,
0x7f, 0x7f),
@@ -138,6 +240,14 @@ static const struct spinand_info esmt_c8_spinand_table[] = {
static const struct spinand_manufacturer_ops esmt_spinand_manuf_ops = {
};
+const struct spinand_manufacturer esmt_8c_spinand_manufacturer = {
+ .id = SPINAND_MFR_ESMT_8C,
+ .name = "ESMT",
+ .chips = esmt_8c_spinand_table,
+ .nchips = ARRAY_SIZE(esmt_8c_spinand_table),
+ .ops = &esmt_spinand_manuf_ops,
+};
+
const struct spinand_manufacturer esmt_c8_spinand_manufacturer = {
.id = SPINAND_MFR_ESMT_C8,
.name = "ESMT",
diff --git a/drivers/mtd/nand/spi/fmsh.c b/drivers/mtd/nand/spi/fmsh.c
new file mode 100644
index 000000000000..f417955f7d1c
--- /dev/null
+++ b/drivers/mtd/nand/spi/fmsh.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2020-2021 Rockchip Electronics Co., Ltd.
+ *
+ * Author: Dingqiang Lin <jon.lin@rock-chips.com>
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/mtd/spinand.h>
+
+#define FM25S01BI3_STATUS_ECC_MASK (7 << 4)
+ #define FM25S01BI3_STATUS_ECC_NO_BITFLIPS (0 << 4)
+ #define FM25S01BI3_STATUS_ECC_1_3_BITFLIPS (1 << 4)
+ #define FM25S01BI3_STATUS_ECC_UNCOR_ERROR (2 << 4)
+ #define FM25S01BI3_STATUS_ECC_4_6_BITFLIPS (3 << 4)
+ #define FM25S01BI3_STATUS_ECC_7_8_BITFLIPS (5 << 4)
+
+#define SPINAND_MFR_FMSH 0xA1
+
+static SPINAND_OP_VARIANTS(read_cache_variants,
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
+
+static SPINAND_OP_VARIANTS(write_cache_variants,
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
+
+static SPINAND_OP_VARIANTS(update_cache_variants,
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
+
+static int fm25s01a_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ return -ERANGE;
+}
+
+static int fm25s01a_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section)
+ return -ERANGE;
+
+ region->offset = 2;
+ region->length = 62;
+
+ return 0;
+}
+
+static int fm25s01bi3_ecc_get_status(struct spinand_device *spinand,
+ u8 status)
+{
+ switch (status & FM25S01BI3_STATUS_ECC_MASK) {
+ case FM25S01BI3_STATUS_ECC_NO_BITFLIPS:
+ return 0;
+
+ case FM25S01BI3_STATUS_ECC_UNCOR_ERROR:
+ return -EBADMSG;
+
+ case FM25S01BI3_STATUS_ECC_1_3_BITFLIPS:
+ return 3;
+
+ case FM25S01BI3_STATUS_ECC_4_6_BITFLIPS:
+ return 6;
+
+ case FM25S01BI3_STATUS_ECC_7_8_BITFLIPS:
+ return 8;
+
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int fm25s01bi3_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section)
+ return -ERANGE;
+
+ region->offset = 64;
+ region->length = 64;
+
+ return 0;
+}
+
+static int fm25s01bi3_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section > 3)
+ return -ERANGE;
+
+ region->offset = (16 * section) + 4;
+ region->length = 12;
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops fm25s01a_ooblayout = {
+ .ecc = fm25s01a_ooblayout_ecc,
+ .free = fm25s01a_ooblayout_free,
+};
+
+static const struct mtd_ooblayout_ops fm25s01bi3_ooblayout = {
+ .ecc = fm25s01bi3_ooblayout_ecc,
+ .free = fm25s01bi3_ooblayout_free,
+};
+
+static const struct spinand_info fmsh_spinand_table[] = {
+ SPINAND_INFO("FM25S01A",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE4),
+ NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(1, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants,
+ &write_cache_variants,
+ &update_cache_variants),
+ 0,
+ SPINAND_ECCINFO(&fm25s01a_ooblayout, NULL)),
+ SPINAND_INFO("FM25S01BI3",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xd4),
+ 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),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&fm25s01bi3_ooblayout,
+ fm25s01bi3_ecc_get_status)),
+};
+
+static const struct spinand_manufacturer_ops fmsh_spinand_manuf_ops = {
+};
+
+const struct spinand_manufacturer fmsh_spinand_manufacturer = {
+ .id = SPINAND_MFR_FMSH,
+ .name = "Fudan Micro",
+ .chips = fmsh_spinand_table,
+ .nchips = ARRAY_SIZE(fmsh_spinand_table),
+ .ops = &fmsh_spinand_manuf_ops,
+};
diff --git a/drivers/mtd/nand/spi/foresee.c b/drivers/mtd/nand/spi/foresee.c
index ecd5f6bffa33..5ff18316092c 100644
--- a/drivers/mtd/nand/spi/foresee.c
+++ b/drivers/mtd/nand/spi/foresee.c
@@ -11,19 +11,24 @@
#define SPINAND_MFR_FORESEE 0xCD
+#define F35SQB002G_STATUS_ECC_MASK (7 << 4)
+#define F35SQB002G_STATUS_ECC_NO_BITFLIPS (0 << 4)
+#define F35SQB002G_STATUS_ECC_1_3_BITFLIPS (1 << 4)
+#define F35SQB002G_STATUS_ECC_UNCOR_ERROR (7 << 4)
+
static SPINAND_OP_VARIANTS(read_cache_variants,
- SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
- SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
- SPINAND_PROG_LOAD(true, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
- SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
- SPINAND_PROG_LOAD(false, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static int f35sqa002g_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
@@ -70,6 +75,25 @@ static int f35sqa002g_ecc_get_status(struct spinand_device *spinand, u8 status)
return -EBADMSG;
}
+static int f35sqb002g_ecc_get_status(struct spinand_device *spinand, u8 status)
+{
+ switch (status & F35SQB002G_STATUS_ECC_MASK) {
+ case F35SQB002G_STATUS_ECC_NO_BITFLIPS:
+ return 0;
+
+ case F35SQB002G_STATUS_ECC_1_3_BITFLIPS:
+ return 3;
+
+ case F35SQB002G_STATUS_ECC_UNCOR_ERROR:
+ return -EBADMSG;
+
+ default: /* (2 << 4) through (6 << 4) are 4-8 corrected errors */
+ return ((status & F35SQB002G_STATUS_ECC_MASK) >> 4) + 2;
+ }
+
+ return -EINVAL;
+}
+
static const struct spinand_info foresee_spinand_table[] = {
SPINAND_INFO("F35SQA002G",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x72, 0x72),
@@ -91,6 +115,16 @@ static const struct spinand_info foresee_spinand_table[] = {
SPINAND_HAS_QE_BIT,
SPINAND_ECCINFO(&f35sqa002g_ooblayout,
f35sqa002g_ecc_get_status)),
+ SPINAND_INFO("F35SQB002G",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x52, 0x52),
+ 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),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&f35sqa002g_ooblayout,
+ f35sqb002g_ecc_get_status)),
};
static const struct spinand_manufacturer_ops foresee_spinand_manuf_ops = {
diff --git a/drivers/mtd/nand/spi/gigadevice.c b/drivers/mtd/nand/spi/gigadevice.c
index d620bb02a20a..a6d6d6e0cc37 100644
--- a/drivers/mtd/nand/spi/gigadevice.c
+++ b/drivers/mtd/nand/spi/gigadevice.c
@@ -4,6 +4,7 @@
* Chuanhong Guo <gch981213@gmail.com>
*/
+#include <linux/bitfield.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mtd/spinand.h>
@@ -23,45 +24,125 @@
#define GD5FXGQ4UXFXXG_STATUS_ECC_1_3_BITFLIPS (1 << 4)
#define GD5FXGQ4UXFXXG_STATUS_ECC_UNCOR_ERROR (7 << 4)
+/* Feature bit definitions */
+#define GD_FEATURE_NR BIT(3) /* Normal Read(1=normal,0=continuous) */
+#define GD_FEATURE_CRDC BIT(2) /* Continuous Read Dummy */
+
+/* ECC status extraction helpers */
+#define GD_ECCSR_LAST_PAGE(eccsr) FIELD_GET(GENMASK(3, 0), eccsr)
+#define GD_ECCSR_ACCUMULATED(eccsr) FIELD_GET(GENMASK(7, 4), eccsr)
+
+struct gigadevice_priv {
+ bool continuous_read;
+};
+
static SPINAND_OP_VARIANTS(read_cache_variants,
- SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(read_cache_variants_f,
- SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X4_OP_3A(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP_3A(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP_3A(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP_3A(0, 0, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_3A_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_3A_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_3A_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_3A_1S_1S_1S_OP(0, 0, NULL, 0, 0));
static SPINAND_OP_VARIANTS(read_cache_variants_1gq5,
- SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(read_cache_variants_2gq5,
- SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 4, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 2, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 4, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 2, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
- SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
- SPINAND_PROG_LOAD(true, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
- SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
- SPINAND_PROG_LOAD(false, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
+
+static int gd5fxgm9_get_eccsr(struct spinand_device *spinand, u8 *eccsr)
+{
+ struct gigadevice_priv *priv = spinand->priv;
+ struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0x7c, 1),
+ SPI_MEM_OP_NO_ADDR,
+ SPI_MEM_OP_DUMMY(1, 1),
+ SPI_MEM_OP_DATA_IN(1, eccsr, 1));
+ int ret;
+
+ ret = spi_mem_exec_op(spinand->spimem, &op);
+ if (ret)
+ return ret;
+
+ if (priv->continuous_read)
+ *eccsr = GD_ECCSR_ACCUMULATED(*eccsr);
+ else
+ *eccsr = GD_ECCSR_LAST_PAGE(*eccsr);
+
+ return 0;
+}
+
+static int gd5fxgm9_ecc_get_status(struct spinand_device *spinand, u8 status)
+{
+ struct nand_device *nand = spinand_to_nand(spinand);
+ u8 eccsr;
+ int ret;
+
+ switch (status & STATUS_ECC_MASK) {
+ case STATUS_ECC_NO_BITFLIPS:
+ return 0;
+
+ case GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS:
+ ret = gd5fxgm9_get_eccsr(spinand, spinand->scratchbuf);
+ if (ret)
+ return nanddev_get_ecc_conf(nand)->strength;
+
+ eccsr = *spinand->scratchbuf;
+ if (WARN_ON(!eccsr || eccsr > nanddev_get_ecc_conf(nand)->strength))
+ return nanddev_get_ecc_conf(nand)->strength;
+
+ return eccsr;
+
+ case GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS:
+ return 8;
+
+ case STATUS_ECC_UNCOR_ERROR:
+ return -EBADMSG;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static int gd5fxgm9_set_continuous_read(struct spinand_device *spinand, bool enable)
+{
+ struct gigadevice_priv *priv = spinand->priv;
+ int ret;
+
+ ret = spinand_upd_cfg(spinand, GD_FEATURE_NR,
+ enable ? 0 : GD_FEATURE_NR);
+ if (ret)
+ return ret;
+
+ priv->continuous_read = enable;
+
+ return 0;
+}
static int gd5fxgq4xa_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
@@ -185,8 +266,8 @@ static int gd5fxgq4uexxg_ecc_get_status(struct spinand_device *spinand,
u8 status)
{
u8 status2;
- struct spi_mem_op op = SPINAND_GET_FEATURE_OP(GD5FXGQXXEXXG_REG_STATUS2,
- spinand->scratchbuf);
+ struct spi_mem_op op = SPINAND_OP(spinand, get_feature,
+ GD5FXGQXXEXXG_REG_STATUS2, spinand->scratchbuf);
int ret;
switch (status & STATUS_ECC_MASK) {
@@ -228,8 +309,8 @@ static int gd5fxgq5xexxg_ecc_get_status(struct spinand_device *spinand,
u8 status)
{
u8 status2;
- struct spi_mem_op op = SPINAND_GET_FEATURE_OP(GD5FXGQXXEXXG_REG_STATUS2,
- spinand->scratchbuf);
+ struct spi_mem_op op = SPINAND_OP(spinand, get_feature,
+ GD5FXGQXXEXXG_REG_STATUS2, spinand->scratchbuf);
int ret;
switch (status & STATUS_ECC_MASK) {
@@ -533,9 +614,51 @@ static const struct spinand_info gigadevice_spinand_table[] = {
SPINAND_HAS_QE_BIT,
SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
gd5fxgq4uexxg_ecc_get_status)),
+ SPINAND_INFO("GD5F1GM9UExxG",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x91, 0x01),
+ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(8, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
+ &write_cache_variants,
+ &update_cache_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+ gd5fxgm9_ecc_get_status),
+ SPINAND_CONT_READ(gd5fxgm9_set_continuous_read)),
+ SPINAND_INFO("GD5F1GM9RExxG",
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x81, 0x01),
+ NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
+ NAND_ECCREQ(8, 512),
+ SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5,
+ &write_cache_variants,
+ &update_cache_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout,
+ gd5fxgm9_ecc_get_status),
+ SPINAND_CONT_READ(gd5fxgm9_set_continuous_read)),
};
+static int gd5fxgm9_spinand_init(struct spinand_device *spinand)
+{
+ struct gigadevice_priv *priv;
+
+ priv = kzalloc_obj(*priv);
+ if (!priv)
+ return -ENOMEM;
+
+ spinand->priv = priv;
+
+ return 0;
+}
+
+static void gd5fxgm9_spinand_cleanup(struct spinand_device *spinand)
+{
+ kfree(spinand->priv);
+}
+
static const struct spinand_manufacturer_ops gigadevice_spinand_manuf_ops = {
+ .init = gd5fxgm9_spinand_init,
+ .cleanup = gd5fxgm9_spinand_cleanup,
};
const struct spinand_manufacturer gigadevice_spinand_manufacturer = {
diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c
index 3dc4d63d6832..7dfcc34e9b72 100644
--- a/drivers/mtd/nand/spi/macronix.c
+++ b/drivers/mtd/nand/spi/macronix.c
@@ -14,6 +14,10 @@
#define MACRONIX_ECCSR_BF_LAST_PAGE(eccsr) FIELD_GET(GENMASK(3, 0), eccsr)
#define MACRONIX_ECCSR_BF_ACCUMULATED_PAGES(eccsr) FIELD_GET(GENMASK(7, 4), eccsr)
#define MACRONIX_CFG_CONT_READ BIT(2)
+#define MACRONIX_CFG_RANDOMIZER_EN BIT(1)
+#define MACRONIX_FEATURE_ADDR_RANDOMIZER 0x10
+#define MACRONIX_FEATURE_ADDR_READ_RETRY 0x70
+#define MACRONIX_NUM_READ_RETRY_MODES 5
#define STATUS_ECC_HAS_BITFLIPS_THRESHOLD (3 << 4)
@@ -26,18 +30,35 @@ struct macronix_priv {
};
static SPINAND_OP_VARIANTS(read_cache_variants,
- SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
- SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
- SPINAND_PROG_LOAD(false, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
- SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
- SPINAND_PROG_LOAD(false, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
+
+#define SPINAND_MACRONIX_READ_ECCSR_1S_0_1S(buf) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0x7c, 1), \
+ SPI_MEM_OP_NO_ADDR, \
+ SPI_MEM_OP_DUMMY(1, 1), \
+ SPI_MEM_OP_DATA_IN(1, buf, 1))
+
+static SPINAND_OP_VARIANTS(macronix_ops,
+ SPINAND_MACRONIX_READ_ECCSR_1S_0_1S(NULL));
+
+static struct spi_mem_op
+spinand_fill_macronix_read_eccsr_op(struct spinand_device *spinand, void *valptr)
+{
+ WARN_ON_ONCE(spinand->bus_iface != SSDR);
+
+ return (struct spi_mem_op)SPINAND_MACRONIX_READ_ECCSR_1S_0_1S(valptr);
+}
static int mx35lfxge4ab_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
@@ -65,12 +86,10 @@ static const struct mtd_ooblayout_ops mx35lfxge4ab_ooblayout = {
static int macronix_get_eccsr(struct spinand_device *spinand, u8 *eccsr)
{
struct macronix_priv *priv = spinand->priv;
- struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0x7c, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_DUMMY(1, 1),
- SPI_MEM_OP_DATA_IN(1, eccsr, 1));
+ struct spi_mem_op op = SPINAND_OP(spinand, macronix_read_eccsr, eccsr);
+ int ret;
- int ret = spi_mem_exec_op(spinand->spimem, &op);
+ ret = spi_mem_exec_op(spinand->spimem, &op);
if (ret)
return ret;
@@ -136,6 +155,29 @@ static int macronix_set_cont_read(struct spinand_device *spinand, bool enable)
return 0;
}
+/**
+ * macronix_set_read_retry - Set the retry mode
+ * @spinand: SPI NAND device
+ * @retry_mode: Specify which retry mode to set
+ *
+ * Return: 0 on success, a negative error code otherwise.
+ */
+static int macronix_set_read_retry(struct spinand_device *spinand,
+ unsigned int retry_mode)
+{
+ struct spi_mem_op op = SPINAND_OP(spinand, set_feature,
+ MACRONIX_FEATURE_ADDR_READ_RETRY, spinand->scratchbuf);
+
+ *spinand->scratchbuf = retry_mode;
+ return spi_mem_exec_op(spinand->spimem, &op);
+}
+
+static int macronix_set_randomizer(struct spinand_device *spinand, bool enable)
+{
+ return spinand_write_reg_op(spinand, MACRONIX_FEATURE_ADDR_RANDOMIZER,
+ enable ? MACRONIX_CFG_RANDOMIZER_EN : 0);
+}
+
static const struct spinand_info macronix_spinand_table[] = {
SPINAND_INFO("MX35LF1GE4AB",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x12),
@@ -145,6 +187,7 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status)),
SPINAND_INFO("MX35LF2GE4AB",
@@ -166,9 +209,12 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
- SPINAND_CONT_READ(macronix_set_cont_read)),
+ SPINAND_CONT_READ(macronix_set_cont_read),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry)),
SPINAND_INFO("MX35LF4GE4AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x37, 0x03),
NAND_MEMORG(1, 4096, 128, 64, 2048, 40, 1, 1, 1),
@@ -177,9 +223,12 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
- SPINAND_CONT_READ(macronix_set_cont_read)),
+ SPINAND_CONT_READ(macronix_set_cont_read),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry)),
SPINAND_INFO("MX35LF1G24AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14, 0x03),
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
@@ -188,7 +237,10 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
- SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)),
+ SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry),
+ SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35LF2G24AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x24, 0x03),
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1),
@@ -198,7 +250,10 @@ static const struct spinand_info macronix_spinand_table[] = {
&update_cache_variants),
SPINAND_HAS_QE_BIT |
SPINAND_HAS_PROG_PLANE_SELECT_BIT,
- SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)),
+ SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry),
+ SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35LF2G24AD-Z4I8",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x64, 0x03),
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
@@ -207,7 +262,10 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
- SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)),
+ SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry),
+ SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35LF4G24AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35, 0x03),
NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 2, 1, 1),
@@ -217,7 +275,10 @@ static const struct spinand_info macronix_spinand_table[] = {
&update_cache_variants),
SPINAND_HAS_QE_BIT |
SPINAND_HAS_PROG_PLANE_SELECT_BIT,
- SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)),
+ SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry),
+ SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35LF4G24AD-Z4I8",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x75, 0x03),
NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
@@ -226,7 +287,10 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
- SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)),
+ SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry),
+ SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX31LF1GE4BC",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x1e),
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
@@ -235,6 +299,7 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status)),
SPINAND_INFO("MX31UF1GE4BC",
@@ -245,6 +310,7 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status)),
@@ -258,6 +324,7 @@ static const struct spinand_info macronix_spinand_table[] = {
SPINAND_HAS_QE_BIT |
SPINAND_HAS_PROG_PLANE_SELECT_BIT |
SPINAND_HAS_READ_PLANE_SELECT_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status)),
SPINAND_INFO("MX35UF4G24AD",
@@ -269,8 +336,12 @@ static const struct spinand_info macronix_spinand_table[] = {
&update_cache_variants),
SPINAND_HAS_QE_BIT |
SPINAND_HAS_PROG_PLANE_SELECT_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
- macronix_ecc_get_status)),
+ macronix_ecc_get_status),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry),
+ SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35UF4G24AD-Z4I8",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xf5, 0x03),
NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
@@ -279,8 +350,12 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
- macronix_ecc_get_status)),
+ macronix_ecc_get_status),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry),
+ SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35UF4GE4AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb7, 0x03),
NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1),
@@ -289,9 +364,12 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
- SPINAND_CONT_READ(macronix_set_cont_read)),
+ SPINAND_CONT_READ(macronix_set_cont_read),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry)),
SPINAND_INFO("MX35UF2G14AC",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa0),
NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1),
@@ -302,6 +380,7 @@ static const struct spinand_info macronix_spinand_table[] = {
SPINAND_HAS_QE_BIT |
SPINAND_HAS_PROG_PLANE_SELECT_BIT |
SPINAND_HAS_READ_PLANE_SELECT_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status)),
SPINAND_INFO("MX35UF2G24AD",
@@ -313,8 +392,12 @@ static const struct spinand_info macronix_spinand_table[] = {
&update_cache_variants),
SPINAND_HAS_QE_BIT |
SPINAND_HAS_PROG_PLANE_SELECT_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
- macronix_ecc_get_status)),
+ macronix_ecc_get_status),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry),
+ SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35UF2G24AD-Z4I8",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe4, 0x03),
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
@@ -323,8 +406,12 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
- macronix_ecc_get_status)),
+ macronix_ecc_get_status),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry),
+ SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35UF2GE4AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa6, 0x03),
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
@@ -333,9 +420,12 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
- SPINAND_CONT_READ(macronix_set_cont_read)),
+ SPINAND_CONT_READ(macronix_set_cont_read),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry)),
SPINAND_INFO("MX35UF2GE4AC",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa2, 0x01),
NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1),
@@ -344,6 +434,7 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
SPINAND_CONT_READ(macronix_set_cont_read)),
@@ -355,6 +446,7 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status)),
SPINAND_INFO("MX35UF1G24AD",
@@ -365,8 +457,12 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
- macronix_ecc_get_status)),
+ macronix_ecc_get_status),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry),
+ SPINAND_RANDOMIZER(macronix_set_randomizer)),
SPINAND_INFO("MX35UF1GE4AD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x96, 0x03),
NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1),
@@ -375,9 +471,12 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
- SPINAND_CONT_READ(macronix_set_cont_read)),
+ SPINAND_CONT_READ(macronix_set_cont_read),
+ SPINAND_READ_RETRY(MACRONIX_NUM_READ_RETRY_MODES,
+ macronix_set_read_retry)),
SPINAND_INFO("MX35UF1GE4AC",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x92, 0x01),
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
@@ -386,6 +485,7 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status),
SPINAND_CONT_READ(macronix_set_cont_read)),
@@ -397,6 +497,7 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status)),
SPINAND_INFO("MX3UF2GE4BC",
@@ -407,6 +508,7 @@ static const struct spinand_info macronix_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
SPINAND_HAS_QE_BIT,
+ SPINAND_INFO_VENDOR_OPS(&macronix_ops),
SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout,
macronix_ecc_get_status)),
};
@@ -415,7 +517,7 @@ static int macronix_spinand_init(struct spinand_device *spinand)
{
struct macronix_priv *priv;
- priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ priv = kzalloc_obj(*priv);
if (!priv)
return -ENOMEM;
diff --git a/drivers/mtd/nand/spi/micron.c b/drivers/mtd/nand/spi/micron.c
index ad0bb9755a09..36f6cbbd7462 100644
--- a/drivers/mtd/nand/spi/micron.c
+++ b/drivers/mtd/nand/spi/micron.c
@@ -9,6 +9,8 @@
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mtd/spinand.h>
+#include <linux/spi/spi-mem.h>
+#include <linux/string.h>
#define SPINAND_MFR_MICRON 0x2c
@@ -28,34 +30,38 @@
#define MICRON_SELECT_DIE(x) ((x) << 6)
+#define MICRON_MT29F2G01ABAGD_CFG_OTP_STATE BIT(7)
+#define MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK \
+ (CFG_OTP_ENABLE | MICRON_MT29F2G01ABAGD_CFG_OTP_STATE)
+
static SPINAND_OP_VARIANTS(quadio_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),
- SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(x4_write_cache_variants,
- SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
- SPINAND_PROG_LOAD(true, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(x4_update_cache_variants,
- SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
- SPINAND_PROG_LOAD(false, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
/* Micron MT29F2G01AAAED Device */
static SPINAND_OP_VARIANTS(x4_read_cache_variants,
- SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(x1_write_cache_variants,
- SPINAND_PROG_LOAD(true, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(x1_update_cache_variants,
- SPINAND_PROG_LOAD(false, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static int micron_8_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
@@ -131,8 +137,8 @@ static const struct mtd_ooblayout_ops micron_4_ooblayout = {
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);
+ struct spi_mem_op op = SPINAND_OP(spinand, set_feature,
+ MICRON_DIE_SELECT_REG, spinand->scratchbuf);
if (target > 1)
return -EINVAL;
@@ -168,6 +174,131 @@ static int micron_8_ecc_get_status(struct spinand_device *spinand,
return -EINVAL;
}
+static int mt29f2g01abagd_otp_is_locked(struct spinand_device *spinand)
+{
+ size_t bufsize = spinand_otp_page_size(spinand);
+ size_t retlen;
+ u8 *buf;
+ int ret;
+
+ buf = kmalloc(bufsize, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = spinand_upd_cfg(spinand,
+ MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK,
+ MICRON_MT29F2G01ABAGD_CFG_OTP_STATE);
+ if (ret)
+ goto free_buf;
+
+ ret = spinand_user_otp_read(spinand, 0, bufsize, &retlen, buf);
+
+ if (spinand_upd_cfg(spinand, MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK,
+ 0)) {
+ dev_warn(&spinand_to_mtd(spinand)->dev,
+ "Can not disable OTP mode\n");
+ ret = -EIO;
+ }
+
+ if (ret)
+ goto free_buf;
+
+ /* If all zeros, then the OTP area is locked. */
+ if (mem_is_zero(buf, bufsize))
+ ret = 1;
+
+free_buf:
+ kfree(buf);
+ return ret;
+}
+
+static int mt29f2g01abagd_otp_info(struct spinand_device *spinand, size_t len,
+ struct otp_info *buf, size_t *retlen,
+ bool user)
+{
+ int locked;
+
+ if (len < sizeof(*buf))
+ return -EINVAL;
+
+ locked = mt29f2g01abagd_otp_is_locked(spinand);
+ if (locked < 0)
+ return locked;
+
+ buf->locked = locked;
+ buf->start = 0;
+ buf->length = user ? spinand_user_otp_size(spinand) :
+ spinand_fact_otp_size(spinand);
+
+ *retlen = sizeof(*buf);
+ return 0;
+}
+
+static int mt29f2g01abagd_fact_otp_info(struct spinand_device *spinand,
+ size_t len, struct otp_info *buf,
+ size_t *retlen)
+{
+ return mt29f2g01abagd_otp_info(spinand, len, buf, retlen, false);
+}
+
+static int mt29f2g01abagd_user_otp_info(struct spinand_device *spinand,
+ size_t len, struct otp_info *buf,
+ size_t *retlen)
+{
+ return mt29f2g01abagd_otp_info(spinand, len, buf, retlen, true);
+}
+
+static int mt29f2g01abagd_otp_lock(struct spinand_device *spinand, loff_t from,
+ size_t len)
+{
+ struct spi_mem_op write_op = SPINAND_OP(spinand, wr_en);
+ struct spi_mem_op exec_op = SPINAND_OP(spinand, prog_exec, 0);
+ u8 status;
+ int ret;
+
+ ret = spinand_upd_cfg(spinand,
+ MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK,
+ MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK);
+ if (!ret)
+ return ret;
+
+ ret = spi_mem_exec_op(spinand->spimem, &write_op);
+ if (!ret)
+ goto out;
+
+ ret = spi_mem_exec_op(spinand->spimem, &exec_op);
+ if (!ret)
+ goto out;
+
+ ret = spinand_wait(spinand,
+ SPINAND_WRITE_INITIAL_DELAY_US,
+ SPINAND_WRITE_POLL_DELAY_US,
+ &status);
+ if (!ret && (status & STATUS_PROG_FAILED))
+ ret = -EIO;
+
+out:
+ if (spinand_upd_cfg(spinand, MICRON_MT29F2G01ABAGD_CFG_OTP_LOCK, 0)) {
+ dev_warn(&spinand_to_mtd(spinand)->dev,
+ "Can not disable OTP mode\n");
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static const struct spinand_user_otp_ops mt29f2g01abagd_user_otp_ops = {
+ .info = mt29f2g01abagd_user_otp_info,
+ .lock = mt29f2g01abagd_otp_lock,
+ .read = spinand_user_otp_read,
+ .write = spinand_user_otp_write,
+};
+
+static const struct spinand_fact_otp_ops mt29f2g01abagd_fact_otp_ops = {
+ .info = mt29f2g01abagd_fact_otp_info,
+ .read = spinand_fact_otp_read,
+};
+
static const struct spinand_info micron_spinand_table[] = {
/* M79A 2Gb 3.3V */
SPINAND_INFO("MT29F2G01ABAGD",
@@ -179,7 +310,9 @@ static const struct spinand_info micron_spinand_table[] = {
&x4_update_cache_variants),
0,
SPINAND_ECCINFO(&micron_8_ooblayout,
- micron_8_ecc_get_status)),
+ micron_8_ecc_get_status),
+ SPINAND_USER_OTP_INFO(12, 2, &mt29f2g01abagd_user_otp_ops),
+ SPINAND_FACT_OTP_INFO(2, 0, &mt29f2g01abagd_fact_otp_ops)),
/* M79A 2Gb 1.8V */
SPINAND_INFO("MT29F2G01ABBGD",
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x25),
diff --git a/drivers/mtd/nand/spi/otp.c b/drivers/mtd/nand/spi/otp.c
new file mode 100644
index 000000000000..ce41bca86ea9
--- /dev/null
+++ b/drivers/mtd/nand/spi/otp.c
@@ -0,0 +1,362 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025, SaluteDevices. All Rights Reserved.
+ *
+ * Author: Martin Kurbanov <mmkurbanov@salutedevices.com>
+ */
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/spinand.h>
+
+/**
+ * spinand_otp_page_size() - Get SPI-NAND OTP page size
+ * @spinand: the spinand device
+ *
+ * Return: the OTP page size.
+ */
+size_t spinand_otp_page_size(struct spinand_device *spinand)
+{
+ struct nand_device *nand = spinand_to_nand(spinand);
+
+ return nanddev_page_size(nand) + nanddev_per_page_oobsize(nand);
+}
+
+static size_t spinand_otp_size(struct spinand_device *spinand,
+ const struct spinand_otp_layout *layout)
+{
+ return layout->npages * spinand_otp_page_size(spinand);
+}
+
+/**
+ * spinand_fact_otp_size() - Get SPI-NAND factory OTP area size
+ * @spinand: the spinand device
+ *
+ * Return: the OTP size.
+ */
+size_t spinand_fact_otp_size(struct spinand_device *spinand)
+{
+ return spinand_otp_size(spinand, &spinand->fact_otp->layout);
+}
+
+/**
+ * spinand_user_otp_size() - Get SPI-NAND user OTP area size
+ * @spinand: the spinand device
+ *
+ * Return: the OTP size.
+ */
+size_t spinand_user_otp_size(struct spinand_device *spinand)
+{
+ return spinand_otp_size(spinand, &spinand->user_otp->layout);
+}
+
+static int spinand_otp_check_bounds(struct spinand_device *spinand, loff_t ofs,
+ size_t len,
+ const struct spinand_otp_layout *layout)
+{
+ if (ofs < 0 || ofs + len > spinand_otp_size(spinand, layout))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int spinand_user_otp_check_bounds(struct spinand_device *spinand,
+ loff_t ofs, size_t len)
+{
+ return spinand_otp_check_bounds(spinand, ofs, len,
+ &spinand->user_otp->layout);
+}
+
+static int spinand_otp_rw(struct spinand_device *spinand, loff_t ofs,
+ size_t len, size_t *retlen, u8 *buf, bool is_write,
+ const struct spinand_otp_layout *layout)
+{
+ struct nand_page_io_req req = {};
+ unsigned long long page;
+ size_t copied = 0;
+ size_t otp_pagesize = spinand_otp_page_size(spinand);
+ int ret;
+
+ if (!len)
+ return 0;
+
+ ret = spinand_otp_check_bounds(spinand, ofs, len, layout);
+ if (ret)
+ return ret;
+
+ ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, CFG_OTP_ENABLE);
+ if (ret)
+ return ret;
+
+ page = ofs;
+ req.dataoffs = do_div(page, otp_pagesize);
+ req.pos.page = page + layout->start_page;
+ req.type = is_write ? NAND_PAGE_WRITE : NAND_PAGE_READ;
+ req.mode = MTD_OPS_RAW;
+ req.databuf.in = buf;
+
+ while (copied < len) {
+ req.datalen = min_t(unsigned int,
+ otp_pagesize - req.dataoffs,
+ len - copied);
+
+ if (is_write)
+ ret = spinand_write_page(spinand, &req);
+ else
+ ret = spinand_read_page(spinand, &req);
+
+ if (ret < 0)
+ break;
+
+ req.databuf.in += req.datalen;
+ req.pos.page++;
+ req.dataoffs = 0;
+ copied += req.datalen;
+ }
+
+ *retlen = copied;
+
+ if (spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0)) {
+ dev_warn(&spinand_to_mtd(spinand)->dev,
+ "Can not disable OTP mode\n");
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+/**
+ * spinand_fact_otp_read() - Read from OTP area
+ * @spinand: the spinand device
+ * @ofs: the offset to read
+ * @len: the number of data bytes to read
+ * @retlen: the pointer to variable to store the number of read bytes
+ * @buf: the buffer to store the read data
+ *
+ * Return: 0 on success, an error code otherwise.
+ */
+int spinand_fact_otp_read(struct spinand_device *spinand, loff_t ofs,
+ size_t len, size_t *retlen, u8 *buf)
+{
+ return spinand_otp_rw(spinand, ofs, len, retlen, buf, false,
+ &spinand->fact_otp->layout);
+}
+
+/**
+ * spinand_user_otp_read() - Read from OTP area
+ * @spinand: the spinand device
+ * @ofs: the offset to read
+ * @len: the number of data bytes to read
+ * @retlen: the pointer to variable to store the number of read bytes
+ * @buf: the buffer to store the read data
+ *
+ * Return: 0 on success, an error code otherwise.
+ */
+int spinand_user_otp_read(struct spinand_device *spinand, loff_t ofs,
+ size_t len, size_t *retlen, u8 *buf)
+{
+ return spinand_otp_rw(spinand, ofs, len, retlen, buf, false,
+ &spinand->user_otp->layout);
+}
+
+/**
+ * spinand_user_otp_write() - Write to OTP area
+ * @spinand: the spinand device
+ * @ofs: the offset to write to
+ * @len: the number of bytes to write
+ * @retlen: the pointer to variable to store the number of written bytes
+ * @buf: the buffer with data to write
+ *
+ * Return: 0 on success, an error code otherwise.
+ */
+int spinand_user_otp_write(struct spinand_device *spinand, loff_t ofs,
+ size_t len, size_t *retlen, const u8 *buf)
+{
+ return spinand_otp_rw(spinand, ofs, len, retlen, (u8 *)buf, true,
+ &spinand->user_otp->layout);
+}
+
+static int spinand_mtd_otp_info(struct mtd_info *mtd, size_t len,
+ size_t *retlen, struct otp_info *buf,
+ bool is_fact)
+{
+ struct spinand_device *spinand = mtd_to_spinand(mtd);
+ int ret;
+
+ *retlen = 0;
+
+ mutex_lock(&spinand->lock);
+
+ if (is_fact)
+ ret = spinand->fact_otp->ops->info(spinand, len, buf, retlen);
+ else
+ ret = spinand->user_otp->ops->info(spinand, len, buf, retlen);
+
+ mutex_unlock(&spinand->lock);
+
+ return ret;
+}
+
+static int spinand_mtd_fact_otp_info(struct mtd_info *mtd, size_t len,
+ size_t *retlen, struct otp_info *buf)
+{
+ return spinand_mtd_otp_info(mtd, len, retlen, buf, true);
+}
+
+static int spinand_mtd_user_otp_info(struct mtd_info *mtd, size_t len,
+ size_t *retlen, struct otp_info *buf)
+{
+ return spinand_mtd_otp_info(mtd, len, retlen, buf, false);
+}
+
+static int spinand_mtd_otp_read(struct mtd_info *mtd, loff_t ofs, size_t len,
+ size_t *retlen, u8 *buf, bool is_fact)
+{
+ struct spinand_device *spinand = mtd_to_spinand(mtd);
+ int ret;
+
+ *retlen = 0;
+
+ if (!len)
+ return 0;
+
+ ret = spinand_otp_check_bounds(spinand, ofs, len,
+ is_fact ? &spinand->fact_otp->layout :
+ &spinand->user_otp->layout);
+ if (ret)
+ return ret;
+
+ mutex_lock(&spinand->lock);
+
+ if (is_fact)
+ ret = spinand->fact_otp->ops->read(spinand, ofs, len, retlen,
+ buf);
+ else
+ ret = spinand->user_otp->ops->read(spinand, ofs, len, retlen,
+ buf);
+
+ mutex_unlock(&spinand->lock);
+
+ return ret;
+}
+
+static int spinand_mtd_fact_otp_read(struct mtd_info *mtd, loff_t ofs,
+ size_t len, size_t *retlen, u8 *buf)
+{
+ return spinand_mtd_otp_read(mtd, ofs, len, retlen, buf, true);
+}
+
+static int spinand_mtd_user_otp_read(struct mtd_info *mtd, loff_t ofs,
+ size_t len, size_t *retlen, u8 *buf)
+{
+ return spinand_mtd_otp_read(mtd, ofs, len, retlen, buf, false);
+}
+
+static int spinand_mtd_user_otp_write(struct mtd_info *mtd, loff_t ofs,
+ size_t len, size_t *retlen, const u8 *buf)
+{
+ struct spinand_device *spinand = mtd_to_spinand(mtd);
+ const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
+ int ret;
+
+ *retlen = 0;
+
+ if (!len)
+ return 0;
+
+ ret = spinand_user_otp_check_bounds(spinand, ofs, len);
+ if (ret)
+ return ret;
+
+ mutex_lock(&spinand->lock);
+ ret = ops->write(spinand, ofs, len, retlen, buf);
+ mutex_unlock(&spinand->lock);
+
+ return ret;
+}
+
+static int spinand_mtd_user_otp_erase(struct mtd_info *mtd, loff_t ofs,
+ size_t len)
+{
+ struct spinand_device *spinand = mtd_to_spinand(mtd);
+ const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
+ int ret;
+
+ if (!len)
+ return 0;
+
+ ret = spinand_user_otp_check_bounds(spinand, ofs, len);
+ if (ret)
+ return ret;
+
+ mutex_lock(&spinand->lock);
+ ret = ops->erase(spinand, ofs, len);
+ mutex_unlock(&spinand->lock);
+
+ return ret;
+}
+
+static int spinand_mtd_user_otp_lock(struct mtd_info *mtd, loff_t ofs,
+ size_t len)
+{
+ struct spinand_device *spinand = mtd_to_spinand(mtd);
+ const struct spinand_user_otp_ops *ops = spinand->user_otp->ops;
+ int ret;
+
+ if (!len)
+ return 0;
+
+ ret = spinand_user_otp_check_bounds(spinand, ofs, len);
+ if (ret)
+ return ret;
+
+ mutex_lock(&spinand->lock);
+ ret = ops->lock(spinand, ofs, len);
+ mutex_unlock(&spinand->lock);
+
+ return ret;
+}
+
+/**
+ * spinand_set_mtd_otp_ops() - Setup OTP methods
+ * @spinand: the spinand device
+ *
+ * Setup OTP methods.
+ *
+ * Return: 0 on success, a negative error code otherwise.
+ */
+int spinand_set_mtd_otp_ops(struct spinand_device *spinand)
+{
+ struct mtd_info *mtd = spinand_to_mtd(spinand);
+ const struct spinand_fact_otp_ops *fact_ops = spinand->fact_otp->ops;
+ const struct spinand_user_otp_ops *user_ops = spinand->user_otp->ops;
+
+ if (!user_ops && !fact_ops)
+ return -EINVAL;
+
+ if (user_ops) {
+ if (user_ops->info)
+ mtd->_get_user_prot_info = spinand_mtd_user_otp_info;
+
+ if (user_ops->read)
+ mtd->_read_user_prot_reg = spinand_mtd_user_otp_read;
+
+ if (user_ops->write)
+ mtd->_write_user_prot_reg = spinand_mtd_user_otp_write;
+
+ if (user_ops->lock)
+ mtd->_lock_user_prot_reg = spinand_mtd_user_otp_lock;
+
+ if (user_ops->erase)
+ mtd->_erase_user_prot_reg = spinand_mtd_user_otp_erase;
+ }
+
+ if (fact_ops) {
+ if (fact_ops->info)
+ mtd->_get_fact_prot_info = spinand_mtd_fact_otp_info;
+
+ if (fact_ops->read)
+ mtd->_read_fact_prot_reg = spinand_mtd_fact_otp_read;
+ }
+
+ return 0;
+}
diff --git a/drivers/mtd/nand/spi/paragon.c b/drivers/mtd/nand/spi/paragon.c
index 6e7cc6995380..73bd124273a5 100644
--- a/drivers/mtd/nand/spi/paragon.c
+++ b/drivers/mtd/nand/spi/paragon.c
@@ -22,20 +22,20 @@
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),
- SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
- SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
- SPINAND_PROG_LOAD(true, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
- SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
- SPINAND_PROG_LOAD(false, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static int pn26g0xa_ooblayout_ecc(struct mtd_info *mtd, int section,
diff --git a/drivers/mtd/nand/spi/skyhigh.c b/drivers/mtd/nand/spi/skyhigh.c
index 961df0d74984..bf9ce163e6a7 100644
--- a/drivers/mtd/nand/spi/skyhigh.c
+++ b/drivers/mtd/nand/spi/skyhigh.c
@@ -17,20 +17,20 @@
#define SKYHIGH_CONFIG_PROTECT_EN BIT(1)
static SPINAND_OP_VARIANTS(read_cache_variants,
- SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 4, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 2, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 4, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 2, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
- SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
- SPINAND_PROG_LOAD(true, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
- SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
- SPINAND_PROG_LOAD(false, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static int skyhigh_spinand_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
diff --git a/drivers/mtd/nand/spi/toshiba.c b/drivers/mtd/nand/spi/toshiba.c
index 2e2106b2705f..ef649162ee68 100644
--- a/drivers/mtd/nand/spi/toshiba.c
+++ b/drivers/mtd/nand/spi/toshiba.c
@@ -15,28 +15,28 @@
#define TOSH_STATUS_ECC_HAS_BITFLIPS_T (3 << 4)
static SPINAND_OP_VARIANTS(read_cache_variants,
- SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_x4_variants,
- SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
- SPINAND_PROG_LOAD(true, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(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));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(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));
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
- SPINAND_PROG_LOAD(false, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static int tx58cxgxsxraix_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
@@ -73,7 +73,8 @@ static int tx58cxgxsxraix_ecc_get_status(struct spinand_device *spinand,
{
struct nand_device *nand = spinand_to_nand(spinand);
u8 mbf = 0;
- struct spi_mem_op op = SPINAND_GET_FEATURE_OP(0x30, spinand->scratchbuf);
+ struct spi_mem_op op = SPINAND_OP(spinand, get_feature,
+ 0x30, spinand->scratchbuf);
switch (status & STATUS_ECC_MASK) {
case STATUS_ECC_NO_BITFLIPS:
diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c
index 8394a1b1fb0c..9b78c1e6cbc9 100644
--- a/drivers/mtd/nand/spi/winbond.c
+++ b/drivers/mtd/nand/spi/winbond.c
@@ -11,46 +11,254 @@
#include <linux/kernel.h>
#include <linux/mtd/spinand.h>
#include <linux/units.h>
+#include <linux/delay.h>
#define SPINAND_MFR_WINBOND 0xEF
+#define WINBOND_CFG_HFREQ BIT(0)
#define WINBOND_CFG_BUF_READ BIT(3)
#define W25N04KV_STATUS_ECC_5_8_BITFLIPS (3 << 4)
+#define W25W35NXXJW_STATUS_ECC_MULT_UNCOR (3 << 4)
+
+#define W25N0XJW_SR4 0xD0
+#define W25N0XJW_SR4_HS BIT(2)
+
+#define W35N01JW_VCR_IO_MODE_REG 0x00
+#define W35N01JW_VCR_IO_MODE_SINGLE_SDR 0xFF
+#define W35N01JW_VCR_IO_MODE_OCTAL_SDR 0xDF
+#define W35N01JW_VCR_IO_MODE_OCTAL_DDR_DS 0xE7
+#define W35N01JW_VCR_IO_MODE_OCTAL_DDR 0xC7
+#define W35N01JW_VCR_DUMMY_CLOCK_REG 0x01
+
+/*
+ * Winbond chips ignore the address bytes during continuous reads, and
+ * because the dummy cycles are enough they indicate dropping the
+ * address cycles from the continuous read from cache variants. This is
+ * very poorly supported by SPI controller drivers which are "wired" to
+ * always at least provide the column. Keep using address cycles, but
+ * reduce the number of dummy cycles accordingly.
+ */
+#define WINBOND_CONT_READ_FROM_CACHE_FAST_1S_1S_1S_OP(ndummy, buf, len, freq) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0x0b, 1), \
+ SPI_MEM_OP_ADDR(1, 0, 1), \
+ SPI_MEM_OP_DUMMY(ndummy - 1, 1), \
+ SPI_MEM_OP_DATA_IN(len, buf, 1), \
+ SPI_MEM_OP_MAX_FREQ(freq))
+
+#define WINBOND_CONT_READ_FROM_CACHE_1S_1D_1D_OP(ndummy, buf, len, freq) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0x0d, 1), \
+ SPI_MEM_DTR_OP_ADDR(2, 0, 1), \
+ SPI_MEM_DTR_OP_DUMMY(ndummy, 1), \
+ SPI_MEM_DTR_OP_DATA_IN(len, buf, 1), \
+ SPI_MEM_OP_MAX_FREQ(freq))
+
+#define WINBOND_CONT_READ_FROM_CACHE_1S_1S_2S_OP(ndummy, buf, len, freq) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0x3b, 1), \
+ SPI_MEM_OP_ADDR(1, 0, 1), \
+ SPI_MEM_OP_DUMMY(ndummy - 1, 1), \
+ SPI_MEM_OP_DATA_IN(len, buf, 2), \
+ SPI_MEM_OP_MAX_FREQ(freq))
+
+#define WINBOND_CONT_READ_FROM_CACHE_1S_2S_2S_OP(ndummy, buf, len, freq) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0xbb, 1), \
+ SPI_MEM_OP_ADDR(1, 0, 2), \
+ SPI_MEM_OP_DUMMY(ndummy - 1, 2), \
+ SPI_MEM_OP_DATA_IN(len, buf, 2), \
+ SPI_MEM_OP_MAX_FREQ(freq))
+
+#define WINBOND_CONT_READ_FROM_CACHE_1S_2D_2D_OP(ndummy, buf, len, freq) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0xbd, 1), \
+ SPI_MEM_DTR_OP_ADDR(1, 0, 2), \
+ SPI_MEM_DTR_OP_DUMMY(ndummy - 1, 2), \
+ SPI_MEM_DTR_OP_DATA_IN(len, buf, 2), \
+ SPI_MEM_OP_MAX_FREQ(freq))
+
+#define WINBOND_CONT_READ_FROM_CACHE_1S_1S_4S_OP(ndummy, buf, len, freq) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0x6b, 1), \
+ SPI_MEM_OP_ADDR(1, 0, 1), \
+ SPI_MEM_OP_DUMMY(ndummy - 1, 1), \
+ SPI_MEM_OP_DATA_IN(len, buf, 4), \
+ SPI_MEM_OP_MAX_FREQ(freq))
+
+#define WINBOND_CONT_READ_FROM_CACHE_1S_1D_4D_OP(ndummy, buf, len, freq) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0x6d, 1), \
+ SPI_MEM_DTR_OP_ADDR(1, 0, 1), \
+ SPI_MEM_DTR_OP_DUMMY(ndummy - 1, 1), \
+ SPI_MEM_DTR_OP_DATA_IN(len, buf, 4), \
+ SPI_MEM_OP_MAX_FREQ(freq))
+
+#define WINBOND_CONT_READ_FROM_CACHE_1S_4S_4S_OP(ndummy, buf, len, freq) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0xeb, 1), \
+ SPI_MEM_OP_ADDR(1, 0, 4), \
+ SPI_MEM_OP_DUMMY(ndummy - 1, 4), \
+ SPI_MEM_OP_DATA_IN(len, buf, 4), \
+ SPI_MEM_OP_MAX_FREQ(freq))
+
+#define WINBOND_CONT_READ_FROM_CACHE_1S_4D_4D_OP(ndummy, buf, len, freq) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0xed, 1), \
+ SPI_MEM_DTR_OP_ADDR(1, 0, 4), \
+ SPI_MEM_DTR_OP_DUMMY(ndummy - 1, 4), \
+ SPI_MEM_DTR_OP_DATA_IN(len, buf, 4), \
+ SPI_MEM_OP_MAX_FREQ(freq))
+
+#define WINBOND_CONT_READ_FROM_CACHE_1S_1S_8S_OP(ndummy, buf, len, freq) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0x8b, 1), \
+ SPI_MEM_OP_ADDR(1, 0, 1), \
+ SPI_MEM_OP_DUMMY(ndummy - 1, 1), \
+ SPI_MEM_OP_DATA_IN(len, buf, 8), \
+ SPI_MEM_OP_MAX_FREQ(freq))
+
+#define WINBOND_CONT_READ_FROM_CACHE_1S_1D_8D_OP(ndummy, buf, len, freq) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0x9d, 1), \
+ SPI_MEM_DTR_OP_ADDR(1, 0, 1), \
+ SPI_MEM_DTR_OP_DUMMY(ndummy - 1, 1), \
+ SPI_MEM_DTR_OP_DATA_IN(len, buf, 8), \
+ SPI_MEM_OP_MAX_FREQ(freq))
+
+#define WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(ndummy, buf, len, freq) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0xcb, 1), \
+ SPI_MEM_OP_ADDR(1, 0, 8), \
+ SPI_MEM_OP_DUMMY(ndummy - 1, 8), \
+ SPI_MEM_OP_DATA_IN(len, buf, 8), \
+ SPI_MEM_OP_MAX_FREQ(freq))
+
+#define WINBOND_CONT_READ_FROM_CACHE_8D_8D_8D_OP(ndummy, buf, len, freq) \
+ SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x9d, 8), \
+ SPI_MEM_DTR_OP_ADDR(2, 0, 8), \
+ SPI_MEM_DTR_OP_DUMMY(ndummy - 2, 8), \
+ SPI_MEM_DTR_OP_DATA_IN(len, buf, 8), \
+ SPI_MEM_OP_MAX_FREQ(freq))
/*
* "X2" in the core is equivalent to "dual output" in the datasheets,
* "X4" in the core is equivalent to "quad output" in the datasheets.
+ * Quad and octal capable chips feature an absolute maximum frequency of 166MHz.
*/
-static SPINAND_OP_VARIANTS(read_cache_dtr_variants,
- SPINAND_PAGE_READ_FROM_CACHE_QUADIO_DTR_OP(0, 8, NULL, 0, 80 * HZ_PER_MHZ),
- SPINAND_PAGE_READ_FROM_CACHE_X4_DTR_OP(0, 2, NULL, 0, 80 * HZ_PER_MHZ),
- SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 2, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_DUALIO_DTR_OP(0, 4, NULL, 0, 80 * HZ_PER_MHZ),
- SPINAND_PAGE_READ_FROM_CACHE_X2_DTR_OP(0, 2, NULL, 0, 80 * HZ_PER_MHZ),
- SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_DTR_OP(0, 2, NULL, 0, 80 * HZ_PER_MHZ),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0, 54 * HZ_PER_MHZ));
+static SPINAND_OP_VARIANTS(read_cache_octal_variants,
+ SPINAND_PAGE_READ_FROM_CACHE_8D_8D_8D_OP(0, 24, NULL, 0, 120 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_8D_8D_8D_OP(0, 16, NULL, 0, 86 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1D_8D_OP(0, 3, NULL, 0, 120 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1D_8D_OP(0, 2, NULL, 0, 105 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 20, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 16, NULL, 0, 162 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 12, NULL, 0, 124 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_8S_8S_OP(0, 8, NULL, 0, 86 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_8S_OP(0, 2, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_8S_OP(0, 1, NULL, 0, 133 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
+
+static SPINAND_OP_VARIANTS(cont_read_cache_octal_variants,
+ WINBOND_CONT_READ_FROM_CACHE_8D_8D_8D_OP(24, NULL, 0, 120 * HZ_PER_MHZ),
+ WINBOND_CONT_READ_FROM_CACHE_8D_8D_8D_OP(16, NULL, 0, 86 * HZ_PER_MHZ),
+ WINBOND_CONT_READ_FROM_CACHE_1S_1D_8D_OP(3, NULL, 0, 120 * HZ_PER_MHZ),
+ WINBOND_CONT_READ_FROM_CACHE_1S_1D_8D_OP(2, NULL, 0, 105 * HZ_PER_MHZ),
+ WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(20, NULL, 0, 0),
+ WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(16, NULL, 0, 162 * HZ_PER_MHZ),
+ WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(12, NULL, 0, 124 * HZ_PER_MHZ),
+ WINBOND_CONT_READ_FROM_CACHE_1S_8S_8S_OP(8, NULL, 0, 86 * HZ_PER_MHZ),
+ WINBOND_CONT_READ_FROM_CACHE_1S_1S_8S_OP(2, NULL, 0, 0),
+ WINBOND_CONT_READ_FROM_CACHE_1S_1S_8S_OP(1, NULL, 0, 133 * HZ_PER_MHZ),
+ WINBOND_CONT_READ_FROM_CACHE_FAST_1S_1S_1S_OP(1, NULL, 0, 0));
+
+static SPINAND_OP_VARIANTS(write_cache_octal_variants,
+ SPINAND_PROG_LOAD_8D_8D_8D_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_8S_8S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_8S_OP(0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
+
+static SPINAND_OP_VARIANTS(update_cache_octal_variants,
+ SPINAND_PROG_LOAD_8D_8D_8D_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_8S_8S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
+
+static SPINAND_OP_VARIANTS(read_cache_dual_quad_dtr_variants,
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4D_4D_OP(0, 8, NULL, 0, 80 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1D_4D_OP(0, 2, NULL, 0, 80 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 4, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 104 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2D_2D_OP(0, 4, NULL, 0, 80 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1D_2D_OP(0, 2, NULL, 0, 80 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 2, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 104 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1D_1D_OP(0, 2, NULL, 0, 80 * HZ_PER_MHZ),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 54 * HZ_PER_MHZ));
+
+static SPINAND_OP_VARIANTS(cont_read_cache_dual_quad_dtr_variants,
+ WINBOND_CONT_READ_FROM_CACHE_1S_4D_4D_OP(11, NULL, 0, 80 * HZ_PER_MHZ),
+ WINBOND_CONT_READ_FROM_CACHE_1S_1D_4D_OP(5, NULL, 0, 80 * HZ_PER_MHZ),
+ WINBOND_CONT_READ_FROM_CACHE_1S_4S_4S_OP(7, NULL, 0, 0),
+ WINBOND_CONT_READ_FROM_CACHE_1S_4S_4S_OP(6, NULL, 0, 104 * HZ_PER_MHZ),
+ WINBOND_CONT_READ_FROM_CACHE_1S_1S_4S_OP(4, NULL, 0, 0),
+ WINBOND_CONT_READ_FROM_CACHE_1S_2D_2D_OP(6, NULL, 0, 80 * HZ_PER_MHZ),
+ /* The 1S_1D_2D variant would require 4.5 dummy bytes, this is not possible */
+ WINBOND_CONT_READ_FROM_CACHE_1S_2S_2S_OP(5, NULL, 0, 0),
+ WINBOND_CONT_READ_FROM_CACHE_1S_2S_2S_OP(4, NULL, 0, 104 * HZ_PER_MHZ),
+ WINBOND_CONT_READ_FROM_CACHE_1S_1S_2S_OP(4, NULL, 0, 0),
+ /* The 1S_1D_1D variant would require 4.5 dummy bytes, this is not possible */
+ WINBOND_CONT_READ_FROM_CACHE_FAST_1S_1S_1S_OP(4, NULL, 0, 0));
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),
- SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 2, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
- SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
- SPINAND_PROG_LOAD(true, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
- SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
- SPINAND_PROG_LOAD(false, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
+
+#define SPINAND_WINBOND_WRITE_VCR_1S_1S_1S(reg, buf) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0x81, 1), \
+ SPI_MEM_OP_ADDR(3, reg, 1), \
+ SPI_MEM_OP_NO_DUMMY, \
+ SPI_MEM_OP_DATA_OUT(1, buf, 1))
+
+#define SPINAND_WINBOND_WRITE_VCR_8D_8D_8D(reg, buf) \
+ SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x81, 8), \
+ SPI_MEM_DTR_OP_ADDR(4, reg << 8, 8), \
+ SPI_MEM_OP_NO_DUMMY, \
+ SPI_MEM_DTR_OP_DATA_OUT(2, buf, 8))
+
+static SPINAND_OP_VARIANTS(winbond_w35_ops,
+ SPINAND_WINBOND_WRITE_VCR_1S_1S_1S(0, NULL),
+ SPINAND_WINBOND_WRITE_VCR_8D_8D_8D(0, NULL));
+
+static struct spi_mem_op
+spinand_fill_winbond_write_vcr_op(struct spinand_device *spinand, u8 reg, void *valptr)
+{
+ return (spinand->bus_iface == SSDR) ?
+ (struct spi_mem_op)SPINAND_WINBOND_WRITE_VCR_1S_1S_1S(reg, valptr) :
+ (struct spi_mem_op)SPINAND_WINBOND_WRITE_VCR_8D_8D_8D(reg, valptr);
+}
+
+#define SPINAND_WINBOND_SELECT_TARGET_1S_0_1S(buf) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(0xc2, 1), \
+ SPI_MEM_OP_NO_ADDR, \
+ SPI_MEM_OP_NO_DUMMY, \
+ SPI_MEM_OP_DATA_OUT(1, buf, 1))
+
+static SPINAND_OP_VARIANTS(winbond_w25_ops,
+ SPINAND_WINBOND_SELECT_TARGET_1S_0_1S(NULL));
+
+static struct spi_mem_op
+spinand_fill_winbond_select_target_op(struct spinand_device *spinand, void *valptr)
+{
+ WARN_ON_ONCE(spinand->bus_iface != SSDR);
+
+ return (struct spi_mem_op)SPINAND_WINBOND_SELECT_TARGET_1S_0_1S(valptr);
+}
static int w25m02gv_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
@@ -84,12 +292,8 @@ static const struct mtd_ooblayout_ops w25m02gv_ooblayout = {
static int w25m02gv_select_target(struct spinand_device *spinand,
unsigned int target)
{
- struct spi_mem_op op = SPI_MEM_OP(SPI_MEM_OP_CMD(0xc2, 1),
- SPI_MEM_OP_NO_ADDR,
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_DATA_OUT(1,
- spinand->scratchbuf,
- 1));
+ struct spi_mem_op op = SPINAND_OP(spinand, winbond_select_target,
+ spinand->scratchbuf);
*spinand->scratchbuf = target;
return spi_mem_exec_op(spinand->spimem, &op);
@@ -141,12 +345,83 @@ static const struct mtd_ooblayout_ops w25n02kv_ooblayout = {
.free = w25n02kv_ooblayout_free,
};
+static int w25n01jw_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section > 3)
+ return -ERANGE;
+
+ region->offset = (16 * section) + 12;
+ region->length = 4;
+
+ return 0;
+}
+
+static int w25n01jw_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section > 3)
+ return -ERANGE;
+
+ region->offset = (16 * section);
+ region->length = 12;
+
+ /* Extract BBM */
+ if (!section) {
+ region->offset += 2;
+ region->length -= 2;
+ }
+
+ return 0;
+}
+
+static int w35n01jw_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section > 7)
+ return -ERANGE;
+
+ region->offset = (16 * section) + 12;
+ region->length = 4;
+
+ return 0;
+}
+
+static int w35n01jw_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *region)
+{
+ if (section > 7)
+ return -ERANGE;
+
+ region->offset = 16 * section;
+ region->length = 12;
+
+ /* Extract BBM */
+ if (!section) {
+ region->offset += 2;
+ region->length -= 2;
+ }
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops w25n01jw_ooblayout = {
+ .ecc = w25n01jw_ooblayout_ecc,
+ .free = w25n01jw_ooblayout_free,
+};
+
+static const struct mtd_ooblayout_ops w35n01jw_ooblayout = {
+ .ecc = w35n01jw_ooblayout_ecc,
+ .free = w35n01jw_ooblayout_free,
+};
+
static int w25n02kv_ecc_get_status(struct spinand_device *spinand,
u8 status)
{
struct nand_device *nand = spinand_to_nand(spinand);
u8 mbf = 0;
- struct spi_mem_op op = SPINAND_GET_FEATURE_OP(0x30, spinand->scratchbuf);
+ struct spi_mem_op op = SPINAND_OP(spinand, get_feature,
+ 0x30, spinand->scratchbuf);
switch (status & STATUS_ECC_MASK) {
case STATUS_ECC_NO_BITFLIPS:
@@ -179,6 +454,182 @@ static int w25n02kv_ecc_get_status(struct spinand_device *spinand,
return -EINVAL;
}
+static int w25w35nxxjw_ecc_get_status(struct spinand_device *spinand, u8 status)
+{
+ switch (status & STATUS_ECC_MASK) {
+ case STATUS_ECC_NO_BITFLIPS:
+ return 0;
+
+ case STATUS_ECC_HAS_BITFLIPS:
+ return 1;
+
+ case STATUS_ECC_UNCOR_ERROR:
+ case W25W35NXXJW_STATUS_ECC_MULT_UNCOR:
+ return -EBADMSG;
+
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int w25n0xjw_set_sr4_hs(struct spinand_device *spinand, bool enable)
+{
+ int ret;
+ u8 sr4;
+
+ ret = spinand_read_reg_op(spinand, W25N0XJW_SR4, &sr4);
+ if (ret)
+ return ret;
+
+ if (enable)
+ sr4 |= W25N0XJW_SR4_HS;
+ else
+ sr4 &= ~W25N0XJW_SR4_HS;
+
+ return spinand_write_reg_op(spinand, W25N0XJW_SR4, sr4);
+}
+
+/*
+ * SDR dual and quad I/O operations over 104MHz require the HS bit to
+ * enable a few more dummy cycles.
+ */
+static bool w25n0xjw_op_needs_hs(const struct spi_mem_op *op)
+{
+ if (op->cmd.dtr || op->addr.dtr || op->dummy.dtr || op->data.dtr)
+ return false;
+ else if (op->cmd.buswidth != 1 || op->addr.buswidth == 1)
+ return false;
+ else if (op->max_freq && op->max_freq <= 104 * HZ_PER_MHZ)
+ return false;
+
+ return true;
+}
+
+static int w25n0xjw_hs_cfg(struct spinand_device *spinand,
+ enum spinand_bus_interface iface)
+{
+ const struct spi_mem_op *op;
+
+ if (iface != SSDR)
+ return -EOPNOTSUPP;
+
+ /*
+ * At this stage, we do not yet know the continuous read template, nor
+ * if there is going to be one. Let's assume the continuous read
+ * template will be selected with the same heuristics as the buffered
+ * read variant, as there cannot be a HS configuration mismatch between
+ * them.
+ */
+ op = spinand->op_templates->read_cache;
+
+ return w25n0xjw_set_sr4_hs(spinand, w25n0xjw_op_needs_hs(op));
+}
+
+static int w25n0xjw_set_cont_read(struct spinand_device *spinand, bool enable)
+{
+ u8 mask = enable ? 0 : WINBOND_CFG_BUF_READ;
+
+ return spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ, mask);
+}
+
+static int w35n0xjw_write_vcr(struct spinand_device *spinand, u8 reg, u8 val)
+{
+ struct spi_mem_op op = SPINAND_OP(spinand, winbond_write_vcr,
+ reg, spinand->scratchbuf);
+ int ret;
+
+ *spinand->scratchbuf = val;
+
+ ret = spinand_write_enable_op(spinand);
+ if (ret)
+ return ret;
+
+ ret = spi_mem_exec_op(spinand->spimem, &op);
+ if (ret)
+ return ret;
+
+ /*
+ * Write VCR operation doesn't set the busy bit in SR, which means we
+ * cannot perform a status poll. Minimum time of 50ns is needed to
+ * complete the write.
+ */
+ ndelay(50);
+
+ return 0;
+}
+
+static int w35n0xjw_vcr_cfg(struct spinand_device *spinand,
+ enum spinand_bus_interface iface)
+{
+ const struct spi_mem_op *ref_op;
+ unsigned int dummy_cycles;
+ bool dtr, single;
+ u8 io_mode;
+ int ret;
+
+ switch (iface) {
+ case SSDR:
+ ref_op = spinand->ssdr_op_templates.read_cache;
+ break;
+ case ODTR:
+ ref_op = spinand->odtr_op_templates.read_cache;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ dummy_cycles = ((ref_op->dummy.nbytes * 8) / ref_op->dummy.buswidth) /
+ (ref_op->dummy.dtr ? 2 : 1);
+ switch (dummy_cycles) {
+ case 8:
+ case 12:
+ case 16:
+ case 20:
+ case 24:
+ case 28:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = w35n0xjw_write_vcr(spinand, W35N01JW_VCR_DUMMY_CLOCK_REG, dummy_cycles);
+ if (ret)
+ return ret;
+
+ single = (ref_op->cmd.buswidth == 1 &&
+ ref_op->addr.buswidth == 1 &&
+ ref_op->data.buswidth == 1);
+ dtr = (ref_op->cmd.dtr && ref_op->addr.dtr && ref_op->data.dtr);
+ if (single && !dtr)
+ io_mode = W35N01JW_VCR_IO_MODE_SINGLE_SDR;
+ else if (!single && !dtr)
+ io_mode = W35N01JW_VCR_IO_MODE_OCTAL_SDR;
+ else if (!single && dtr)
+ io_mode = W35N01JW_VCR_IO_MODE_OCTAL_DDR;
+ else
+ return -EINVAL;
+
+ ret = w35n0xjw_write_vcr(spinand, W35N01JW_VCR_IO_MODE_REG, io_mode);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int w35n0xjw_set_cont_read(struct spinand_device *spinand, bool enable)
+{
+ const struct spi_mem_op *cont_op = spinand->op_templates->cont_read_cache;
+ u8 mask = enable ? 0 : WINBOND_CFG_BUF_READ;
+
+ if (cont_op && enable && spinand_op_is_odtr(cont_op) &&
+ cont_op->max_freq >= 90 * HZ_PER_MHZ)
+ mask |= WINBOND_CFG_HFREQ;
+
+ return spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ | WINBOND_CFG_HFREQ, mask);
+}
+
static const struct spinand_info winbond_spinand_table[] = {
/* 512M-bit densities */
SPINAND_INFO("W25N512GW", /* 1.8V */
@@ -213,11 +664,14 @@ static const struct spinand_info winbond_spinand_table[] = {
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xbc, 0x21),
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1),
NAND_ECCREQ(1, 512),
- SPINAND_INFO_OP_VARIANTS(&read_cache_dtr_variants,
- &write_cache_variants,
- &update_cache_variants),
- 0,
- SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)),
+ SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_dual_quad_dtr_variants,
+ &write_cache_variants,
+ &update_cache_variants,
+ &cont_read_cache_dual_quad_dtr_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&w25n01jw_ooblayout, w25w35nxxjw_ecc_get_status),
+ SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg),
+ SPINAND_CONT_READ(w25n0xjw_set_cont_read)),
SPINAND_INFO("W25N01KV", /* 3.3V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xae, 0x21),
NAND_MEMORG(1, 2048, 96, 64, 1024, 20, 1, 1, 1),
@@ -227,6 +681,19 @@ static const struct spinand_info winbond_spinand_table[] = {
&update_cache_variants),
0,
SPINAND_ECCINFO(&w25n01kv_ooblayout, w25n02kv_ecc_get_status)),
+ SPINAND_INFO("W35N01JW", /* 1.8V */
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdc, 0x21),
+ NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 1, 1),
+ NAND_ECCREQ(1, 512),
+ SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_octal_variants,
+ &write_cache_octal_variants,
+ &update_cache_octal_variants,
+ &cont_read_cache_octal_variants),
+ 0,
+ SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops),
+ SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status),
+ SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg),
+ SPINAND_CONT_READ(w35n0xjw_set_cont_read)),
/* 2G-bit densities */
SPINAND_INFO("W25M02GV", /* 2x1G-bit 3.3V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xab, 0x21),
@@ -236,17 +703,21 @@ static const struct spinand_info winbond_spinand_table[] = {
&write_cache_variants,
&update_cache_variants),
0,
+ SPINAND_INFO_VENDOR_OPS(&winbond_w25_ops),
SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL),
SPINAND_SELECT_TARGET(w25m02gv_select_target)),
SPINAND_INFO("W25N02JW", /* high-speed 1.8V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xbf, 0x22),
NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 2, 1),
NAND_ECCREQ(1, 512),
- SPINAND_INFO_OP_VARIANTS(&read_cache_dtr_variants,
- &write_cache_variants,
- &update_cache_variants),
- 0,
- SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)),
+ SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_dual_quad_dtr_variants,
+ &write_cache_variants,
+ &update_cache_variants,
+ &cont_read_cache_dual_quad_dtr_variants),
+ SPINAND_HAS_QE_BIT,
+ SPINAND_ECCINFO(&w25m02gv_ooblayout, w25w35nxxjw_ecc_get_status),
+ SPINAND_CONFIGURE_CHIP(w25n0xjw_hs_cfg),
+ SPINAND_CONT_READ(w25n0xjw_set_cont_read)),
SPINAND_INFO("W25N02KV", /* 3.3V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x22),
NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1),
@@ -265,6 +736,19 @@ static const struct spinand_info winbond_spinand_table[] = {
&update_cache_variants),
0,
SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
+ SPINAND_INFO("W35N02JW", /* 1.8V */
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x22),
+ NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 2, 1),
+ NAND_ECCREQ(1, 512),
+ SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_octal_variants,
+ &write_cache_octal_variants,
+ &update_cache_octal_variants,
+ &cont_read_cache_octal_variants),
+ SPINAND_ODTR_PACKED_PAGE_READ,
+ SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops),
+ SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status),
+ SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg),
+ SPINAND_CONT_READ(w35n0xjw_set_cont_read)),
/* 4G-bit densities */
SPINAND_INFO("W25N04KV", /* 3.3V */
SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x23),
@@ -284,6 +768,19 @@ static const struct spinand_info winbond_spinand_table[] = {
&update_cache_variants),
0,
SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)),
+ SPINAND_INFO("W35N04JW", /* 1.8V */
+ SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xdf, 0x23),
+ NAND_MEMORG(1, 4096, 128, 64, 512, 10, 1, 4, 1),
+ NAND_ECCREQ(1, 512),
+ SPINAND_INFO_OP_VARIANTS_WITH_CONT(&read_cache_octal_variants,
+ &write_cache_octal_variants,
+ &update_cache_octal_variants,
+ &cont_read_cache_octal_variants),
+ SPINAND_ODTR_PACKED_PAGE_READ,
+ SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops),
+ SPINAND_ECCINFO(&w35n01jw_ooblayout, w25w35nxxjw_ecc_get_status),
+ SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg),
+ SPINAND_CONT_READ(w35n0xjw_set_cont_read)),
};
static int winbond_spinand_init(struct spinand_device *spinand)
diff --git a/drivers/mtd/nand/spi/xtx.c b/drivers/mtd/nand/spi/xtx.c
index 3f539ca0de86..5915b37b47f5 100644
--- a/drivers/mtd/nand/spi/xtx.c
+++ b/drivers/mtd/nand/spi/xtx.c
@@ -23,20 +23,20 @@
#define XT26XXXD_STATUS_ECC_UNCOR_ERROR (2)
static SPINAND_OP_VARIANTS(read_cache_variants,
- SPINAND_PAGE_READ_FROM_CACHE_QUADIO_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X4_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_DUALIO_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_X2_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_FAST_OP(0, 1, NULL, 0),
- SPINAND_PAGE_READ_FROM_CACHE_OP(0, 1, NULL, 0));
+ SPINAND_PAGE_READ_FROM_CACHE_1S_4S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_4S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_2S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_2S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_FAST_1S_1S_1S_OP(0, 1, NULL, 0, 0),
+ SPINAND_PAGE_READ_FROM_CACHE_1S_1S_1S_OP(0, 1, NULL, 0, 0));
static SPINAND_OP_VARIANTS(write_cache_variants,
- SPINAND_PROG_LOAD_X4(true, 0, NULL, 0),
- SPINAND_PROG_LOAD(true, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(true, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(true, 0, NULL, 0));
static SPINAND_OP_VARIANTS(update_cache_variants,
- SPINAND_PROG_LOAD_X4(false, 0, NULL, 0),
- SPINAND_PROG_LOAD(false, 0, NULL, 0));
+ SPINAND_PROG_LOAD_1S_1S_4S_OP(false, 0, NULL, 0),
+ SPINAND_PROG_LOAD_1S_1S_1S_OP(false, 0, NULL, 0));
static int xt26g0xa_ooblayout_ecc(struct mtd_info *mtd, int section,
struct mtd_oob_region *region)
diff --git a/drivers/mtd/nftlcore.c b/drivers/mtd/nftlcore.c
index 64d319e959b2..6b29b9c7a6a6 100644
--- a/drivers/mtd/nftlcore.c
+++ b/drivers/mtd/nftlcore.c
@@ -45,7 +45,7 @@ static void nftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
pr_debug("NFTL: add_mtd for %s\n", mtd->name);
- nftl = kzalloc(sizeof(struct NFTLrecord), GFP_KERNEL);
+ nftl = kzalloc_obj(struct NFTLrecord);
if (!nftl)
return;
@@ -228,6 +228,25 @@ static u16 NFTL_findfreeblock(struct NFTLrecord *nftl, int desperate )
return BLOCK_NIL;
}
+static noinline_for_stack void NFTL_move_block(struct mtd_info *mtd, loff_t src, loff_t dst)
+{
+ unsigned char movebuf[512];
+ struct nftl_oob oob;
+ size_t retlen;
+ int ret;
+
+ ret = mtd_read(mtd, src, 512, &retlen, movebuf);
+ if (ret < 0 && !mtd_is_bitflip(ret)) {
+ ret = mtd_read(mtd, src, 512, &retlen, movebuf);
+ if (ret != -EIO)
+ printk("Error went away on retry.\n");
+ }
+ memset(&oob, 0xff, sizeof(struct nftl_oob));
+ oob.b.Status = oob.b.Status1 = SECTOR_USED;
+
+ nftl_write(mtd, dst, 512, &retlen, movebuf, (char *)&oob);
+}
+
static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned pendingblock )
{
struct mtd_info *mtd = nftl->mbd.mtd;
@@ -389,9 +408,6 @@ static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned p
*/
pr_debug("Folding chain %d into unit %d\n", thisVUC, targetEUN);
for (block = 0; block < nftl->EraseSize / 512 ; block++) {
- unsigned char movebuf[512];
- int ret;
-
/* If it's in the target EUN already, or if it's pending write, do nothing */
if (BlockMap[block] == targetEUN ||
(pendingblock == (thisVUC * (nftl->EraseSize / 512) + block))) {
@@ -403,25 +419,8 @@ static u16 NFTL_foldchain (struct NFTLrecord *nftl, unsigned thisVUC, unsigned p
if (BlockMap[block] == BLOCK_NIL)
continue;
- ret = mtd_read(mtd,
- (nftl->EraseSize * BlockMap[block]) + (block * 512),
- 512,
- &retlen,
- movebuf);
- if (ret < 0 && !mtd_is_bitflip(ret)) {
- ret = mtd_read(mtd,
- (nftl->EraseSize * BlockMap[block]) + (block * 512),
- 512,
- &retlen,
- movebuf);
- if (ret != -EIO)
- printk("Error went away on retry.\n");
- }
- memset(&oob, 0xff, sizeof(struct nftl_oob));
- oob.b.Status = oob.b.Status1 = SECTOR_USED;
-
- nftl_write(nftl->mbd.mtd, (nftl->EraseSize * targetEUN) +
- (block * 512), 512, &retlen, movebuf, (char *)&oob);
+ NFTL_move_block(mtd, (nftl->EraseSize * BlockMap[block]) + (block * 512),
+ (nftl->EraseSize * targetEUN) + (block * 512));
}
/* add the header so that it is now a valid chain */
diff --git a/drivers/mtd/parsers/bcm47xxpart.c b/drivers/mtd/parsers/bcm47xxpart.c
index 49c8e7f27f21..a412c0e7bd4e 100644
--- a/drivers/mtd/parsers/bcm47xxpart.c
+++ b/drivers/mtd/parsers/bcm47xxpart.c
@@ -106,8 +106,7 @@ static int bcm47xxpart_parse(struct mtd_info *master,
blocksize = 0x1000;
/* Alloc */
- parts = kcalloc(BCM47XXPART_MAX_PARTS, sizeof(struct mtd_partition),
- GFP_KERNEL);
+ parts = kzalloc_objs(struct mtd_partition, BCM47XXPART_MAX_PARTS);
if (!parts)
return -ENOMEM;
diff --git a/drivers/mtd/parsers/brcm_u-boot.c b/drivers/mtd/parsers/brcm_u-boot.c
index 984f98923446..cd78e432f1de 100644
--- a/drivers/mtd/parsers/brcm_u-boot.c
+++ b/drivers/mtd/parsers/brcm_u-boot.c
@@ -37,7 +37,7 @@ static int brcm_u_boot_parse(struct mtd_info *mtd,
int err;
int i = 0;
- parts = kcalloc(BRCM_U_BOOT_MAX_PARTS, sizeof(*parts), GFP_KERNEL);
+ parts = kzalloc_objs(*parts, BRCM_U_BOOT_MAX_PARTS);
if (!parts)
return -ENOMEM;
diff --git a/drivers/mtd/parsers/cmdlinepart.c b/drivers/mtd/parsers/cmdlinepart.c
index 504e5fa2b45b..4caf1b3804f2 100644
--- a/drivers/mtd/parsers/cmdlinepart.c
+++ b/drivers/mtd/parsers/cmdlinepart.c
@@ -50,9 +50,9 @@
struct cmdline_mtd_partition {
struct cmdline_mtd_partition *next;
- char *mtd_id;
int num_parts;
struct mtd_partition *parts;
+ char mtd_id[];
};
/* mtdpart_setup() parses into here */
@@ -289,7 +289,6 @@ static int mtdpart_setup_real(char *s)
/* enter results */
this_mtd->parts = parts;
this_mtd->num_parts = num_parts;
- this_mtd->mtd_id = (char*)(this_mtd + 1);
strscpy(this_mtd->mtd_id, mtd_id, mtd_id_len + 1);
/* link into chain */
diff --git a/drivers/mtd/parsers/ofpart_bcm4908.h b/drivers/mtd/parsers/ofpart_bcm4908.h
index 80f8c086641f..f96dcd5275a7 100644
--- a/drivers/mtd/parsers/ofpart_bcm4908.h
+++ b/drivers/mtd/parsers/ofpart_bcm4908.h
@@ -4,12 +4,6 @@
#ifdef CONFIG_MTD_OF_PARTS_BCM4908
int bcm4908_partitions_post_parse(struct mtd_info *mtd, struct mtd_partition *parts, int nr_parts);
-#else
-static inline int bcm4908_partitions_post_parse(struct mtd_info *mtd, struct mtd_partition *parts,
- int nr_parts)
-{
- return -EOPNOTSUPP;
-}
#endif
#endif
diff --git a/drivers/mtd/parsers/ofpart_core.c b/drivers/mtd/parsers/ofpart_core.c
index abfa68798918..262c4221d23f 100644
--- a/drivers/mtd/parsers/ofpart_core.c
+++ b/drivers/mtd/parsers/ofpart_core.c
@@ -23,13 +23,17 @@ struct fixed_partitions_quirks {
int (*post_parse)(struct mtd_info *mtd, struct mtd_partition *parts, int nr_parts);
};
+#ifdef CONFIG_MTD_OF_PARTS_BCM4908
static struct fixed_partitions_quirks bcm4908_partitions_quirks = {
.post_parse = bcm4908_partitions_post_parse,
};
+#endif
+#ifdef CONFIG_MTD_OF_PARTS_LINKSYS_NS
static struct fixed_partitions_quirks linksys_ns_partitions_quirks = {
.post_parse = linksys_ns_partitions_post_parse,
};
+#endif
static const struct of_device_id parse_ofpart_match_table[];
@@ -71,12 +75,13 @@ static int parse_fixed_partitions(struct mtd_info *master,
dedicated = false;
}
} else { /* Partition */
- ofpart_node = mtd_node;
+ ofpart_node = of_node_get(mtd_node);
}
of_id = of_match_node(parse_ofpart_match_table, ofpart_node);
if (dedicated && !of_id) {
/* The 'partitions' subnode might be used by another parser */
+ of_node_put(ofpart_node);
return 0;
}
@@ -91,12 +96,18 @@ static int parse_fixed_partitions(struct mtd_info *master,
nr_parts++;
}
- if (nr_parts == 0)
+ if (nr_parts == 0) {
+ if (dedicated)
+ of_node_put(ofpart_node);
return 0;
+ }
- parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL);
- if (!parts)
+ parts = kzalloc_objs(*parts, nr_parts);
+ if (!parts) {
+ if (dedicated)
+ of_node_put(ofpart_node);
return -ENOMEM;
+ }
i = 0;
for_each_child_of_node(ofpart_node, pp) {
@@ -175,15 +186,20 @@ static int parse_fixed_partitions(struct mtd_info *master,
if (quirks && quirks->post_parse)
quirks->post_parse(master, parts, nr_parts);
+ if (dedicated)
+ of_node_put(ofpart_node);
+
*pparts = parts;
return nr_parts;
ofpart_fail:
pr_err("%s: error parsing ofpart partition %pOF (%pOF)\n",
master->name, pp, mtd_node);
+ of_node_put(pp);
ret = -EINVAL;
ofpart_none:
- of_node_put(pp);
+ if (dedicated)
+ of_node_put(ofpart_node);
kfree(parts);
return ret;
}
@@ -192,8 +208,12 @@ static const struct of_device_id parse_ofpart_match_table[] = {
/* Generic */
{ .compatible = "fixed-partitions" },
/* Customized */
+#ifdef CONFIG_MTD_OF_PARTS_BCM4908
{ .compatible = "brcm,bcm4908-partitions", .data = &bcm4908_partitions_quirks, },
+#endif
+#ifdef CONFIG_MTD_OF_PARTS_LINKSYS_NS
{ .compatible = "linksys,ns-partitions", .data = &linksys_ns_partitions_quirks, },
+#endif
{},
};
MODULE_DEVICE_TABLE(of, parse_ofpart_match_table);
@@ -229,7 +249,7 @@ static int parse_ofoldpart_partitions(struct mtd_info *master,
nr_parts = plen / sizeof(part[0]);
- parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL);
+ parts = kzalloc_objs(*parts, nr_parts);
if (!parts)
return -ENOMEM;
diff --git a/drivers/mtd/parsers/ofpart_linksys_ns.h b/drivers/mtd/parsers/ofpart_linksys_ns.h
index 730c46812ebf..6537aa37c0aa 100644
--- a/drivers/mtd/parsers/ofpart_linksys_ns.h
+++ b/drivers/mtd/parsers/ofpart_linksys_ns.h
@@ -6,13 +6,6 @@
int linksys_ns_partitions_post_parse(struct mtd_info *mtd,
struct mtd_partition *parts,
int nr_parts);
-#else
-static inline int linksys_ns_partitions_post_parse(struct mtd_info *mtd,
- struct mtd_partition *parts,
- int nr_parts)
-{
- return -EOPNOTSUPP;
-}
#endif
#endif
diff --git a/drivers/mtd/parsers/parser_trx.c b/drivers/mtd/parsers/parser_trx.c
index 4814cf218e17..cf3bc9735238 100644
--- a/drivers/mtd/parsers/parser_trx.c
+++ b/drivers/mtd/parsers/parser_trx.c
@@ -65,8 +65,7 @@ static int parser_trx_parse(struct mtd_info *mtd,
if (err != 0 && err != -EINVAL)
pr_err("failed to parse \"brcm,trx-magic\" DT attribute, using default: %d\n", err);
- parts = kcalloc(TRX_PARSER_MAX_PARTS, sizeof(struct mtd_partition),
- GFP_KERNEL);
+ parts = kzalloc_objs(struct mtd_partition, TRX_PARSER_MAX_PARTS);
if (!parts)
return -ENOMEM;
diff --git a/drivers/mtd/parsers/qcomsmempart.c b/drivers/mtd/parsers/qcomsmempart.c
index 4311b89d8df0..d4fdc46a0073 100644
--- a/drivers/mtd/parsers/qcomsmempart.c
+++ b/drivers/mtd/parsers/qcomsmempart.c
@@ -123,7 +123,7 @@ static int parse_qcomsmem_part(struct mtd_info *mtd,
numparts++;
}
- parts = kcalloc(numparts, sizeof(*parts), GFP_KERNEL);
+ parts = kzalloc_objs(*parts, numparts);
if (!parts)
return -ENOMEM;
diff --git a/drivers/mtd/parsers/redboot.c b/drivers/mtd/parsers/redboot.c
index 3b55b676ca6b..bf162c44eafe 100644
--- a/drivers/mtd/parsers/redboot.c
+++ b/drivers/mtd/parsers/redboot.c
@@ -203,7 +203,7 @@ nogood:
if (!redboot_checksum(&buf[i]))
break;
- new_fl = kmalloc(sizeof(struct fis_list), GFP_KERNEL);
+ new_fl = kmalloc_obj(struct fis_list);
namelen += strlen(buf[i].name) + 1;
if (!new_fl) {
ret = -ENOMEM;
@@ -270,9 +270,9 @@ nogood:
strcpy(names, fl->img->name);
#ifdef CONFIG_MTD_REDBOOT_PARTS_READONLY
- if (!memcmp(names, "RedBoot", 8) ||
- !memcmp(names, "RedBoot config", 15) ||
- !memcmp(names, "FIS directory", 14)) {
+ if (!strcmp(names, "RedBoot") ||
+ !strcmp(names, "RedBoot config") ||
+ !strcmp(names, "FIS directory")) {
parts[i].mask_flags = MTD_WRITEABLE;
}
#endif
diff --git a/drivers/mtd/parsers/scpart.c b/drivers/mtd/parsers/scpart.c
index 6e5e11c37078..ad6e9d5c3daa 100644
--- a/drivers/mtd/parsers/scpart.c
+++ b/drivers/mtd/parsers/scpart.c
@@ -80,7 +80,7 @@ static int scpart_scan_partmap(struct mtd_info *master, loff_t partmap_offs,
if (cnt > 0) {
int bytes = cnt * sizeof(*pdesc);
- pdesc = kcalloc(cnt, sizeof(*pdesc), GFP_KERNEL);
+ pdesc = kzalloc_objs(*pdesc, cnt);
if (!pdesc) {
res = -ENOMEM;
goto free;
@@ -171,8 +171,7 @@ static int scpart_parse(struct mtd_info *master,
goto free;
}
- parts = kcalloc(of_get_child_count(ofpart_node), sizeof(*parts),
- GFP_KERNEL);
+ parts = kzalloc_objs(*parts, of_get_child_count(ofpart_node));
if (!parts) {
res = -ENOMEM;
goto free;
diff --git a/drivers/mtd/parsers/sharpslpart.c b/drivers/mtd/parsers/sharpslpart.c
index 671a61845bd5..3833b887c667 100644
--- a/drivers/mtd/parsers/sharpslpart.c
+++ b/drivers/mtd/parsers/sharpslpart.c
@@ -362,9 +362,8 @@ static int sharpsl_parse_mtd_partitions(struct mtd_info *master,
return err;
}
- sharpsl_nand_parts = kcalloc(SHARPSL_NAND_PARTS,
- sizeof(*sharpsl_nand_parts),
- GFP_KERNEL);
+ sharpsl_nand_parts = kzalloc_objs(*sharpsl_nand_parts,
+ SHARPSL_NAND_PARTS);
if (!sharpsl_nand_parts)
return -ENOMEM;
diff --git a/drivers/mtd/parsers/tplink_safeloader.c b/drivers/mtd/parsers/tplink_safeloader.c
index e358a029dc70..b770f41ef874 100644
--- a/drivers/mtd/parsers/tplink_safeloader.c
+++ b/drivers/mtd/parsers/tplink_safeloader.c
@@ -82,7 +82,7 @@ static int mtd_parser_tplink_safeloader_parse(struct mtd_info *mtd,
int idx;
int err;
- parts = kcalloc(TPLINK_SAFELOADER_MAX_PARTS, sizeof(*parts), GFP_KERNEL);
+ parts = kzalloc_objs(*parts, TPLINK_SAFELOADER_MAX_PARTS);
if (!parts) {
err = -ENOMEM;
goto err_out;
@@ -116,6 +116,7 @@ static int mtd_parser_tplink_safeloader_parse(struct mtd_info *mtd,
return idx;
err_free:
+ kfree(buf);
for (idx -= 1; idx >= 0; idx--)
kfree(parts[idx].name);
err_free_parts:
diff --git a/drivers/mtd/rfd_ftl.c b/drivers/mtd/rfd_ftl.c
index c546f8c5f24d..9f7efdab6e90 100644
--- a/drivers/mtd/rfd_ftl.c
+++ b/drivers/mtd/rfd_ftl.c
@@ -185,13 +185,12 @@ static int scan_header(struct partition *part)
if (!part->header_cache)
goto err;
- part->blocks = kcalloc(part->total_blocks, sizeof(struct block),
- GFP_KERNEL);
+ part->blocks = kzalloc_objs(struct block, part->total_blocks);
if (!part->blocks)
goto err;
- part->sector_map = vmalloc(array_size(sizeof(u_long),
- part->sector_count));
+ part->sector_map = vmalloc_array(part->sector_count,
+ sizeof(u_long));
if (!part->sector_map)
goto err;
@@ -270,7 +269,7 @@ static int erase_block(struct partition *part, int block)
struct erase_info *erase;
int rc;
- erase = kmalloc(sizeof(struct erase_info), GFP_KERNEL);
+ erase = kmalloc_obj(struct erase_info);
if (!erase)
return -ENOMEM;
@@ -752,7 +751,7 @@ static void rfd_ftl_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
mtd->size > UINT_MAX)
return;
- part = kzalloc(sizeof(struct partition), GFP_KERNEL);
+ part = kzalloc_obj(struct partition);
if (!part)
return;
diff --git a/drivers/mtd/sm_ftl.c b/drivers/mtd/sm_ftl.c
index b5b3c4c44a94..fb0f97fb94f0 100644
--- a/drivers/mtd/sm_ftl.c
+++ b/drivers/mtd/sm_ftl.c
@@ -44,8 +44,7 @@ static ssize_t sm_attr_show(struct device *dev, struct device_attribute *attr,
struct sm_sysfs_attribute *sm_attr =
container_of(attr, struct sm_sysfs_attribute, dev_attr);
- strncpy(buf, sm_attr->data, sm_attr->len);
- return sm_attr->len;
+ return sysfs_emit(buf, "%.*s", sm_attr->len, sm_attr->data);
}
@@ -65,7 +64,7 @@ static struct attribute_group *sm_create_sysfs_attributes(struct sm_ftl *ftl)
/* Initialize sysfs attributes */
vendor_attribute =
- kzalloc(sizeof(struct sm_sysfs_attribute), GFP_KERNEL);
+ kzalloc_obj(struct sm_sysfs_attribute);
if (!vendor_attribute)
goto error2;
@@ -79,14 +78,13 @@ static struct attribute_group *sm_create_sysfs_attributes(struct sm_ftl *ftl)
/* Create array of pointers to the attributes */
- attributes = kcalloc(NUM_ATTRIBUTES + 1, sizeof(struct attribute *),
- GFP_KERNEL);
+ attributes = kzalloc_objs(struct attribute *, NUM_ATTRIBUTES + 1);
if (!attributes)
goto error3;
attributes[0] = &vendor_attribute->dev_attr.attr;
/* Finally create the attribute group */
- attr_group = kzalloc(sizeof(struct attribute_group), GFP_KERNEL);
+ attr_group = kzalloc_obj(struct attribute_group);
if (!attr_group)
goto error4;
attr_group->attrs = attributes;
@@ -157,7 +155,7 @@ static int sm_read_lba(struct sm_oob *oob)
if (!memcmp(oob, erased_pattern, SM_OOB_SIZE))
return -1;
- /* Now check is both copies of the LBA differ too much */
+ /* Now check if both copies of the LBA differ too much */
lba_test = *(uint16_t *)oob->lba_copy1 ^ *(uint16_t*)oob->lba_copy2;
if (lba_test && !is_power_of_2(lba_test))
return -2;
@@ -993,7 +991,7 @@ restart:
/* flush timer, runs a second after last write */
static void sm_cache_flush_timer(struct timer_list *t)
{
- struct sm_ftl *ftl = from_timer(ftl, t, timer);
+ struct sm_ftl *ftl = timer_container_of(ftl, t, timer);
queue_work(cache_flush_workqueue, &ftl->flush_work);
}
@@ -1067,7 +1065,7 @@ static int sm_write(struct mtd_blktrans_dev *dev,
sm_break_offset(ftl, sec_no << 9, &zone_num, &block, &boffset);
/* No need in flush thread running now */
- del_timer(&ftl->timer);
+ timer_delete(&ftl->timer);
mutex_lock(&ftl->mutex);
zone = sm_get_zone(ftl, zone_num);
@@ -1111,7 +1109,7 @@ static void sm_release(struct mtd_blktrans_dev *dev)
{
struct sm_ftl *ftl = dev->priv;
- del_timer_sync(&ftl->timer);
+ timer_delete_sync(&ftl->timer);
cancel_work_sync(&ftl->flush_work);
mutex_lock(&ftl->mutex);
sm_cache_flush(ftl);
@@ -1135,7 +1133,7 @@ static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
struct sm_ftl *ftl;
/* Allocate & initialize our private structure */
- ftl = kzalloc(sizeof(struct sm_ftl), GFP_KERNEL);
+ ftl = kzalloc_flex(*ftl, cis_buffer, SM_SECTOR_SIZE);
if (!ftl)
goto error1;
@@ -1147,34 +1145,28 @@ static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
/* Read media information */
if (sm_get_media_info(ftl, mtd)) {
dbg("found unsupported mtd device, aborting");
- goto error2;
+ goto error1;
}
- /* Allocate temporary CIS buffer for read retry support */
- ftl->cis_buffer = kzalloc(SM_SECTOR_SIZE, GFP_KERNEL);
- if (!ftl->cis_buffer)
- goto error2;
-
/* Allocate zone array, it will be initialized on demand */
- ftl->zones = kcalloc(ftl->zone_count, sizeof(struct ftl_zone),
- GFP_KERNEL);
+ ftl->zones = kzalloc_objs(struct ftl_zone, ftl->zone_count);
if (!ftl->zones)
- goto error3;
+ goto error2;
/* Allocate the cache*/
ftl->cache_data = kzalloc(ftl->block_size, GFP_KERNEL);
if (!ftl->cache_data)
- goto error4;
+ goto error3;
sm_cache_init(ftl);
/* Allocate upper layer structure and initialize it */
- trans = kzalloc(sizeof(struct mtd_blktrans_dev), GFP_KERNEL);
+ trans = kzalloc_obj(struct mtd_blktrans_dev);
if (!trans)
- goto error5;
+ goto error4;
ftl->trans = trans;
trans->priv = ftl;
@@ -1187,12 +1179,12 @@ static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
if (sm_find_cis(ftl)) {
dbg("CIS not found on mtd device, aborting");
- goto error6;
+ goto error5;
}
ftl->disk_attributes = sm_create_sysfs_attributes(ftl);
if (!ftl->disk_attributes)
- goto error6;
+ goto error5;
trans->disk_attributes = ftl->disk_attributes;
sm_printk("Found %d MiB xD/SmartMedia FTL on mtd%d",
@@ -1209,17 +1201,15 @@ static void sm_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
/* Register device*/
if (add_mtd_blktrans_dev(trans)) {
dbg("error in mtdblktrans layer");
- goto error6;
+ goto error5;
}
return;
-error6:
- kfree(trans);
error5:
- kfree(ftl->cache_data);
+ kfree(trans);
error4:
- kfree(ftl->zones);
+ kfree(ftl->cache_data);
error3:
- kfree(ftl->cis_buffer);
+ kfree(ftl->zones);
error2:
kfree(ftl);
error1:
@@ -1245,7 +1235,6 @@ static void sm_remove_dev(struct mtd_blktrans_dev *dev)
}
sm_delete_sysfs_attributes(ftl);
- kfree(ftl->cis_buffer);
kfree(ftl->zones);
kfree(ftl->cache_data);
kfree(ftl);
diff --git a/drivers/mtd/sm_ftl.h b/drivers/mtd/sm_ftl.h
index 6aed8b60de16..1a1a54ce836a 100644
--- a/drivers/mtd/sm_ftl.h
+++ b/drivers/mtd/sm_ftl.h
@@ -39,7 +39,6 @@ struct sm_ftl {
int cis_block; /* CIS block location */
int cis_boffset; /* CIS offset in the block */
int cis_page_offset; /* CIS offset in the page */
- void *cis_buffer; /* tmp buffer for cis reads */
/* Cache */
int cache_block; /* block number of cached block */
@@ -56,6 +55,7 @@ struct sm_ftl {
int cylinders;
struct attribute_group *disk_attributes;
+ u8 cis_buffer[]; /* tmp buffer for cis reads */
};
struct chs_entry {
diff --git a/drivers/mtd/spi-nor/Kconfig b/drivers/mtd/spi-nor/Kconfig
index 24cd25de2b8b..fd05a24d64a9 100644
--- a/drivers/mtd/spi-nor/Kconfig
+++ b/drivers/mtd/spi-nor/Kconfig
@@ -1,7 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
menuconfig MTD_SPI_NOR
tristate "SPI NOR device support"
- depends on MTD
depends on MTD && SPI_MASTER
select SPI_MEM
help
diff --git a/drivers/mtd/spi-nor/controllers/hisi-sfc.c b/drivers/mtd/spi-nor/controllers/hisi-sfc.c
index db948da2c4c5..6897ced2d57b 100644
--- a/drivers/mtd/spi-nor/controllers/hisi-sfc.c
+++ b/drivers/mtd/spi-nor/controllers/hisi-sfc.c
@@ -394,19 +394,15 @@ static void hisi_spi_nor_unregister_all(struct hifmc_host *host)
static int hisi_spi_nor_register_all(struct hifmc_host *host)
{
struct device *dev = host->dev;
- struct device_node *np;
int ret;
- for_each_available_child_of_node(dev->of_node, np) {
+ for_each_available_child_of_node_scoped(dev->of_node, np) {
ret = hisi_spi_nor_register(np, host);
- if (ret) {
- of_node_put(np);
+ if (ret)
goto fail;
- }
if (host->num_chip == HIFMC_MAX_CHIP_NUM) {
dev_warn(dev, "Flash device number exceeds the maximum chipselect number\n");
- of_node_put(np);
break;
}
}
diff --git a/drivers/mtd/spi-nor/core.c b/drivers/mtd/spi-nor/core.c
index 19eb98bd6821..ccf4396cdcd0 100644
--- a/drivers/mtd/spi-nor/core.c
+++ b/drivers/mtd/spi-nor/core.c
@@ -7,16 +7,17 @@
* Copyright (C) 2014, Freescale Semiconductor, Inc.
*/
-#include <linux/err.h>
-#include <linux/errno.h>
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/errno.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/spi-nor.h>
#include <linux/mutex.h>
-#include <linux/of_platform.h>
+#include <linux/of.h>
#include <linux/regulator/consumer.h>
#include <linux/sched/task_stack.h>
#include <linux/sizes.h>
@@ -639,32 +640,26 @@ static bool spi_nor_use_parallel_locking(struct spi_nor *nor)
static int spi_nor_rww_start_rdst(struct spi_nor *nor)
{
struct spi_nor_rww *rww = &nor->rww;
- int ret = -EAGAIN;
- mutex_lock(&nor->lock);
+ guard(mutex)(&nor->lock);
if (rww->ongoing_io || rww->ongoing_rd)
- goto busy;
+ return -EAGAIN;
rww->ongoing_io = true;
rww->ongoing_rd = true;
- ret = 0;
-busy:
- mutex_unlock(&nor->lock);
- return ret;
+ return 0;
}
static void spi_nor_rww_end_rdst(struct spi_nor *nor)
{
struct spi_nor_rww *rww = &nor->rww;
- mutex_lock(&nor->lock);
+ guard(mutex)(&nor->lock);
rww->ongoing_io = false;
rww->ongoing_rd = false;
-
- mutex_unlock(&nor->lock);
}
static int spi_nor_lock_rdst(struct spi_nor *nor)
@@ -874,8 +869,8 @@ static int spi_nor_write_16bit_sr_and_check(struct spi_nor *nor, u8 sr1)
ret = spi_nor_read_cr(nor, &sr_cr[1]);
if (ret)
return ret;
- } else if (spi_nor_get_protocol_width(nor->read_proto) == 4 &&
- spi_nor_get_protocol_width(nor->write_proto) == 4 &&
+ } else if ((spi_nor_get_protocol_width(nor->read_proto) == 4 ||
+ spi_nor_get_protocol_width(nor->write_proto) == 4) &&
nor->params->quad_enable) {
/*
* If the Status Register 2 Read command (35h) is not
@@ -982,6 +977,54 @@ int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr)
}
/**
+ * spi_nor_write_16bit_sr_cr_and_check() - Write the Status Register 1 and the
+ * Configuration Register in one shot. Ensure that the bytes written in both
+ * registers match the received value.
+ * @nor: pointer to a 'struct spi_nor'.
+ * @regs: two-byte array with values to be written to the status and
+ * configuration registers.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int spi_nor_write_16bit_sr_cr_and_check(struct spi_nor *nor, const u8 *regs)
+{
+ u8 written_regs[2];
+ int ret;
+
+ written_regs[0] = regs[0];
+ written_regs[1] = regs[1];
+ nor->bouncebuf[0] = regs[0];
+ nor->bouncebuf[1] = regs[1];
+
+ ret = spi_nor_write_sr(nor, nor->bouncebuf, 2);
+ if (ret)
+ return ret;
+
+ ret = spi_nor_read_sr(nor, &nor->bouncebuf[0]);
+ if (ret)
+ return ret;
+
+ if (written_regs[0] != nor->bouncebuf[0]) {
+ dev_dbg(nor->dev, "SR: Read back test failed\n");
+ return -EIO;
+ }
+
+ if (nor->flags & SNOR_F_NO_READ_CR)
+ return 0;
+
+ ret = spi_nor_read_cr(nor, &nor->bouncebuf[1]);
+ if (ret)
+ return ret;
+
+ if (written_regs[1] != nor->bouncebuf[1]) {
+ dev_dbg(nor->dev, "CR: read back test failed\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/**
* spi_nor_write_sr_and_check() - Write the Status Register 1 and ensure that
* the byte written match the received value without affecting other bits in the
* Status Register 1 and 2.
@@ -999,6 +1042,23 @@ int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1)
}
/**
+ * spi_nor_write_sr_cr_and_check() - Write the Status Register 1 and ensure that
+ * the byte written match the received value. Same for the Control Register if
+ * available.
+ * @nor: pointer to a 'struct spi_nor'.
+ * @regs: byte array to be written to the registers.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs)
+{
+ if (nor->flags & SNOR_F_HAS_16BIT_SR)
+ return spi_nor_write_16bit_sr_cr_and_check(nor, regs);
+
+ return spi_nor_write_sr1_and_check(nor, regs[0]);
+}
+
+/**
* spi_nor_write_sr2() - Write the Status Register 2 using the
* SPINOR_OP_WRSR2 (3eh) command.
* @nor: pointer to 'struct spi_nor'.
@@ -1212,26 +1272,21 @@ static void spi_nor_offset_to_banks(u64 bank_size, loff_t start, size_t len,
static bool spi_nor_rww_start_io(struct spi_nor *nor)
{
struct spi_nor_rww *rww = &nor->rww;
- bool start = false;
- mutex_lock(&nor->lock);
+ guard(mutex)(&nor->lock);
if (rww->ongoing_io)
- goto busy;
+ return false;
rww->ongoing_io = true;
- start = true;
-busy:
- mutex_unlock(&nor->lock);
- return start;
+ return true;
}
static void spi_nor_rww_end_io(struct spi_nor *nor)
{
- mutex_lock(&nor->lock);
+ guard(mutex)(&nor->lock);
nor->rww.ongoing_io = false;
- mutex_unlock(&nor->lock);
}
static int spi_nor_lock_device(struct spi_nor *nor)
@@ -1254,32 +1309,27 @@ static void spi_nor_unlock_device(struct spi_nor *nor)
static bool spi_nor_rww_start_exclusive(struct spi_nor *nor)
{
struct spi_nor_rww *rww = &nor->rww;
- bool start = false;
mutex_lock(&nor->lock);
if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe)
- goto busy;
+ return false;
rww->ongoing_io = true;
rww->ongoing_rd = true;
rww->ongoing_pe = true;
- start = true;
-busy:
- mutex_unlock(&nor->lock);
- return start;
+ return true;
}
static void spi_nor_rww_end_exclusive(struct spi_nor *nor)
{
struct spi_nor_rww *rww = &nor->rww;
- mutex_lock(&nor->lock);
+ guard(mutex)(&nor->lock);
rww->ongoing_io = false;
rww->ongoing_rd = false;
rww->ongoing_pe = false;
- mutex_unlock(&nor->lock);
}
int spi_nor_prep_and_lock(struct spi_nor *nor)
@@ -1316,30 +1366,26 @@ static bool spi_nor_rww_start_pe(struct spi_nor *nor, loff_t start, size_t len)
{
struct spi_nor_rww *rww = &nor->rww;
unsigned int used_banks = 0;
- bool started = false;
u8 first, last;
int bank;
- mutex_lock(&nor->lock);
+ guard(mutex)(&nor->lock);
if (rww->ongoing_io || rww->ongoing_rd || rww->ongoing_pe)
- goto busy;
+ return false;
spi_nor_offset_to_banks(nor->params->bank_size, start, len, &first, &last);
for (bank = first; bank <= last; bank++) {
if (rww->used_banks & BIT(bank))
- goto busy;
+ return false;
used_banks |= BIT(bank);
}
rww->used_banks |= used_banks;
rww->ongoing_pe = true;
- started = true;
-busy:
- mutex_unlock(&nor->lock);
- return started;
+ return true;
}
static void spi_nor_rww_end_pe(struct spi_nor *nor, loff_t start, size_t len)
@@ -1348,15 +1394,13 @@ static void spi_nor_rww_end_pe(struct spi_nor *nor, loff_t start, size_t len)
u8 first, last;
int bank;
- mutex_lock(&nor->lock);
+ guard(mutex)(&nor->lock);
spi_nor_offset_to_banks(nor->params->bank_size, start, len, &first, &last);
for (bank = first; bank <= last; bank++)
rww->used_banks &= ~BIT(bank);
rww->ongoing_pe = false;
-
- mutex_unlock(&nor->lock);
}
static int spi_nor_prep_and_lock_pe(struct spi_nor *nor, loff_t start, size_t len)
@@ -1393,19 +1437,18 @@ static bool spi_nor_rww_start_rd(struct spi_nor *nor, loff_t start, size_t len)
{
struct spi_nor_rww *rww = &nor->rww;
unsigned int used_banks = 0;
- bool started = false;
u8 first, last;
int bank;
- mutex_lock(&nor->lock);
+ guard(mutex)(&nor->lock);
if (rww->ongoing_io || rww->ongoing_rd)
- goto busy;
+ return false;
spi_nor_offset_to_banks(nor->params->bank_size, start, len, &first, &last);
for (bank = first; bank <= last; bank++) {
if (rww->used_banks & BIT(bank))
- goto busy;
+ return false;
used_banks |= BIT(bank);
}
@@ -1413,11 +1456,8 @@ static bool spi_nor_rww_start_rd(struct spi_nor *nor, loff_t start, size_t len)
rww->used_banks |= used_banks;
rww->ongoing_io = true;
rww->ongoing_rd = true;
- started = true;
-busy:
- mutex_unlock(&nor->lock);
- return started;
+ return true;
}
static void spi_nor_rww_end_rd(struct spi_nor *nor, loff_t start, size_t len)
@@ -1426,7 +1466,7 @@ static void spi_nor_rww_end_rd(struct spi_nor *nor, loff_t start, size_t len)
u8 first, last;
int bank;
- mutex_lock(&nor->lock);
+ guard(mutex)(&nor->lock);
spi_nor_offset_to_banks(nor->params->bank_size, start, len, &first, &last);
for (bank = first; bank <= last; bank++)
@@ -1434,8 +1474,6 @@ static void spi_nor_rww_end_rd(struct spi_nor *nor, loff_t start, size_t len)
rww->ongoing_io = false;
rww->ongoing_rd = false;
-
- mutex_unlock(&nor->lock);
}
static int spi_nor_prep_and_lock_rd(struct spi_nor *nor, loff_t start, size_t len)
@@ -1580,7 +1618,7 @@ spi_nor_init_erase_cmd(const struct spi_nor_erase_region *region,
{
struct spi_nor_erase_command *cmd;
- cmd = kmalloc(sizeof(*cmd), GFP_KERNEL);
+ cmd = kmalloc_obj(*cmd);
if (!cmd)
return ERR_PTR(-ENOMEM);
@@ -2041,6 +2079,76 @@ static const struct flash_info *spi_nor_detect(struct spi_nor *nor)
return info;
}
+/*
+ * On Octal DTR capable flashes, reads cannot start or end at an odd
+ * address in Octal DTR mode. Extra bytes need to be read at the start
+ * or end to make sure both the start address and length remain even.
+ */
+static int spi_nor_octal_dtr_read(struct spi_nor *nor, loff_t from, size_t len,
+ u_char *buf)
+{
+ u_char *tmp_buf;
+ size_t tmp_len;
+ loff_t start, end;
+ int ret, bytes_read;
+
+ if (IS_ALIGNED(from, 2) && IS_ALIGNED(len, 2))
+ return spi_nor_read_data(nor, from, len, buf);
+ else if (IS_ALIGNED(from, 2) && len > PAGE_SIZE)
+ return spi_nor_read_data(nor, from, round_down(len, PAGE_SIZE),
+ buf);
+
+ tmp_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+ if (!tmp_buf)
+ return -ENOMEM;
+
+ start = round_down(from, 2);
+ end = round_up(from + len, 2);
+
+ /*
+ * Avoid allocating too much memory. The requested read length might be
+ * quite large. Allocating a buffer just as large (slightly bigger, in
+ * fact) would put unnecessary memory pressure on the system.
+ *
+ * For example if the read is from 3 to 1M, then this will read from 2
+ * to 4098. The reads from 4098 to 1M will then not need a temporary
+ * buffer so they can proceed as normal.
+ */
+ tmp_len = min_t(size_t, end - start, PAGE_SIZE);
+
+ ret = spi_nor_read_data(nor, start, tmp_len, tmp_buf);
+ if (ret == 0) {
+ ret = -EIO;
+ goto out;
+ }
+ if (ret < 0)
+ goto out;
+
+ /*
+ * More bytes are read than actually requested, but that number can't be
+ * reported to the calling function or it will confuse its calculations.
+ * Calculate how many of the _requested_ bytes were read.
+ */
+ bytes_read = ret;
+
+ if (from != start)
+ ret -= from - start;
+
+ /*
+ * Only account for extra bytes at the end if they were actually read.
+ * For example, if the total length was truncated because of temporary
+ * buffer size limit then the adjustment for the extra bytes at the end
+ * is not needed.
+ */
+ if (start + bytes_read == end)
+ ret -= end - (from + len);
+
+ memcpy(buf, tmp_buf + (from - start), ret);
+out:
+ kfree(tmp_buf);
+ return ret;
+}
+
static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf)
{
@@ -2058,7 +2166,11 @@ static int spi_nor_read(struct mtd_info *mtd, loff_t from, size_t len,
while (len) {
loff_t addr = from;
- ret = spi_nor_read_data(nor, addr, len, buf);
+ if (nor->read_proto == SNOR_PROTO_8_8_8_DTR)
+ ret = spi_nor_octal_dtr_read(nor, addr, len, buf);
+ else
+ ret = spi_nor_read_data(nor, addr, len, buf);
+
if (ret == 0) {
/* We shouldn't see 0-length reads */
ret = -EIO;
@@ -2082,6 +2194,68 @@ read_err:
}
/*
+ * On Octal DTR capable flashes, writes cannot start or end at an odd address
+ * in Octal DTR mode. Extra 0xff bytes need to be appended or prepended to
+ * make sure the start address and end address are even. 0xff is used because
+ * on NOR flashes a program operation can only flip bits from 1 to 0, not the
+ * other way round. 0 to 1 flip needs to happen via erases.
+ */
+static int spi_nor_octal_dtr_write(struct spi_nor *nor, loff_t to, size_t len,
+ const u8 *buf)
+{
+ u8 *tmp_buf;
+ size_t bytes_written;
+ loff_t start, end;
+ int ret;
+
+ if (IS_ALIGNED(to, 2) && IS_ALIGNED(len, 2))
+ return spi_nor_write_data(nor, to, len, buf);
+
+ tmp_buf = kmalloc(nor->params->page_size, GFP_KERNEL);
+ if (!tmp_buf)
+ return -ENOMEM;
+
+ memset(tmp_buf, 0xff, nor->params->page_size);
+
+ start = round_down(to, 2);
+ end = round_up(to + len, 2);
+
+ memcpy(tmp_buf + (to - start), buf, len);
+
+ ret = spi_nor_write_data(nor, start, end - start, tmp_buf);
+ if (ret == 0) {
+ ret = -EIO;
+ goto out;
+ }
+ if (ret < 0)
+ goto out;
+
+ /*
+ * More bytes are written than actually requested, but that number can't
+ * be reported to the calling function or it will confuse its
+ * calculations. Calculate how many of the _requested_ bytes were
+ * written.
+ */
+ bytes_written = ret;
+
+ if (to != start)
+ ret -= to - start;
+
+ /*
+ * Only account for extra bytes at the end if they were actually
+ * written. For example, if for some reason the controller could only
+ * complete a partial write then the adjustment for the extra bytes at
+ * the end is not needed.
+ */
+ if (start + bytes_written == end)
+ ret -= end - (to + len);
+
+out:
+ kfree(tmp_buf);
+ return ret;
+}
+
+/*
* Write an address range to the nor chip. Data must be written in
* FLASH_PAGESIZE chunks. The address range may be any size provided
* it is within the physical boundaries.
@@ -2117,7 +2291,12 @@ static int spi_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
goto write_err;
}
- ret = spi_nor_write_data(nor, addr, page_remain, buf + i);
+ if (nor->write_proto == SNOR_PROTO_8_8_8_DTR)
+ ret = spi_nor_octal_dtr_write(nor, addr, page_remain,
+ buf + i);
+ else
+ ret = spi_nor_write_data(nor, addr, page_remain,
+ buf + i);
spi_nor_unlock_device(nor);
if (ret < 0)
goto write_err;
@@ -2231,15 +2410,15 @@ int spi_nor_hwcaps_pp2cmd(u32 hwcaps)
}
/**
- * spi_nor_spimem_check_op - check if the operation is supported
- * by controller
+ * spi_nor_spimem_check_read_pp_op - check if a read or a page program operation is
+ * supported by controller
*@nor: pointer to a 'struct spi_nor'
*@op: pointer to op template to be checked
*
* Returns 0 if operation is supported, -EOPNOTSUPP otherwise.
*/
-static int spi_nor_spimem_check_op(struct spi_nor *nor,
- struct spi_mem_op *op)
+static int spi_nor_spimem_check_read_pp_op(struct spi_nor *nor,
+ struct spi_mem_op *op)
{
/*
* First test with 4 address bytes. The opcode itself might
@@ -2279,10 +2458,10 @@ static int spi_nor_spimem_check_readop(struct spi_nor *nor,
/* convert the dummy cycles to the number of bytes */
op.dummy.nbytes = (read->num_mode_clocks + read->num_wait_states) *
op.dummy.buswidth / 8;
- if (spi_nor_protocol_is_dtr(nor->read_proto))
+ if (spi_nor_protocol_is_dtr(read->proto))
op.dummy.nbytes *= 2;
- return spi_nor_spimem_check_op(nor, &op);
+ return spi_nor_spimem_check_read_pp_op(nor, &op);
}
/**
@@ -2300,7 +2479,7 @@ static int spi_nor_spimem_check_pp(struct spi_nor *nor,
spi_nor_spimem_setup_op(nor, &op, pp->proto);
- return spi_nor_spimem_check_op(nor, &op);
+ return spi_nor_spimem_check_read_pp_op(nor, &op);
}
/**
@@ -2345,6 +2524,16 @@ spi_nor_spimem_adjust_hwcaps(struct spi_nor *nor, u32 *hwcaps)
&params->page_programs[ppidx]))
*hwcaps &= ~BIT(cap);
}
+
+ /* Some SPI controllers might not support CR read opcode. */
+ if (!(nor->flags & SNOR_F_NO_READ_CR)) {
+ struct spi_mem_op op = SPI_NOR_RDCR_OP(nor->bouncebuf);
+
+ spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
+
+ if (!spi_mem_supports_op(nor->spimem, &op))
+ nor->flags |= SNOR_F_NO_READ_CR;
+ }
}
/**
@@ -2785,6 +2974,9 @@ static void spi_nor_init_flags(struct spi_nor *nor)
nor->flags |= SNOR_F_HAS_SR_BP3_BIT6;
}
+ if (flags & SPI_NOR_HAS_CMP)
+ nor->flags |= SNOR_F_HAS_SR2_CMP_BIT6;
+
if (flags & SPI_NOR_RWW && nor->params->n_banks > 1 &&
!nor->controller_ops)
nor->flags |= SNOR_F_RWW;
@@ -3137,10 +3329,12 @@ static int spi_nor_init(struct spi_nor *nor)
* protection bits are volatile. The latter is indicated by
* SNOR_F_SWP_IS_VOLATILE.
*/
+ spi_nor_cache_sr_lock_bits(nor, NULL);
if (IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE) ||
(IS_ENABLED(CONFIG_MTD_SPI_NOR_SWP_DISABLE_ON_VOLATILE) &&
- nor->flags & SNOR_F_SWP_IS_VOLATILE))
+ nor->flags & SNOR_F_SWP_IS_VOLATILE)) {
spi_nor_try_unlock_all(nor);
+ }
if (nor->addr_nbytes == 4 &&
nor->read_proto != SNOR_PROTO_8_8_8_DTR &&
@@ -3517,14 +3711,15 @@ EXPORT_SYMBOL_GPL(spi_nor_scan);
static int spi_nor_create_read_dirmap(struct spi_nor *nor)
{
struct spi_mem_dirmap_info info = {
- .op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0),
- SPI_MEM_OP_ADDR(nor->addr_nbytes, 0, 0),
- SPI_MEM_OP_DUMMY(nor->read_dummy, 0),
- SPI_MEM_OP_DATA_IN(0, NULL, 0)),
+ .op_tmpl = &info.primary_op_tmpl,
+ .primary_op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->read_opcode, 0),
+ SPI_MEM_OP_ADDR(nor->addr_nbytes, 0, 0),
+ SPI_MEM_OP_DUMMY(nor->read_dummy, 0),
+ SPI_MEM_OP_DATA_IN(0, NULL, 0)),
.offset = 0,
.length = nor->params->size,
};
- struct spi_mem_op *op = &info.op_tmpl;
+ struct spi_mem_op *op = info.op_tmpl;
spi_nor_spimem_setup_op(nor, op, nor->read_proto);
@@ -3548,14 +3743,15 @@ static int spi_nor_create_read_dirmap(struct spi_nor *nor)
static int spi_nor_create_write_dirmap(struct spi_nor *nor)
{
struct spi_mem_dirmap_info info = {
- .op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 0),
- SPI_MEM_OP_ADDR(nor->addr_nbytes, 0, 0),
- SPI_MEM_OP_NO_DUMMY,
- SPI_MEM_OP_DATA_OUT(0, NULL, 0)),
+ .op_tmpl = &info.primary_op_tmpl,
+ .primary_op_tmpl = SPI_MEM_OP(SPI_MEM_OP_CMD(nor->program_opcode, 0),
+ SPI_MEM_OP_ADDR(nor->addr_nbytes, 0, 0),
+ SPI_MEM_OP_NO_DUMMY,
+ SPI_MEM_OP_DATA_OUT(0, NULL, 0)),
.offset = 0,
.length = nor->params->size,
};
- struct spi_mem_op *op = &info.op_tmpl;
+ struct spi_mem_op *op = info.op_tmpl;
if (nor->program_opcode == SPINOR_OP_AAI_WP && nor->sst_write_second)
op->addr.nbytes = 0;
diff --git a/drivers/mtd/spi-nor/core.h b/drivers/mtd/spi-nor/core.h
index ceff412f7d65..ba2d1a862c9d 100644
--- a/drivers/mtd/spi-nor/core.h
+++ b/drivers/mtd/spi-nor/core.h
@@ -141,6 +141,7 @@ enum spi_nor_option_flags {
SNOR_F_ECC = BIT(15),
SNOR_F_NO_WP = BIT(16),
SNOR_F_SWAP16 = BIT(17),
+ SNOR_F_HAS_SR2_CMP_BIT6 = BIT(18),
};
struct spi_nor_read_command {
@@ -279,9 +280,17 @@ struct spi_nor_erase_map {
/**
* 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
+ * @lock: lock a region of the SPI NOR, never locks more than what is
+ * requested, ie. may lock less.
+ * @unlock: unlock a region of the SPI NOR, may unlock more than what is
+ * requested.
+ * @is_locked: check if a region of the SPI NOR is completely locked, returns
+ * false otherwise. This feedback may be misleading because users
+ * may get an "unlocked" status even though a subpart of the region
+ * is effectively locked.
+ *
+ * If in doubt during development, check-out the debugfs output which tries to
+ * be more user friendly.
*/
struct spi_nor_locking_ops {
int (*lock)(struct spi_nor *nor, loff_t ofs, u64 len);
@@ -409,7 +418,11 @@ struct spi_nor_flash_parameter {
* 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
+ * @smpt_read_dummy: called during SMPT table is being parsed. Used to fix the
+ * number of dummy cycles in read register ops.
+ * @smpt_map_id: called after map ID in SMPT table has been determined for the
+ * case the map ID is wrong and needs to be fixed.
+ * @post_sfdp: called after SFDP has been parsed (is not 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
@@ -426,6 +439,8 @@ struct spi_nor_fixups {
int (*post_bfpt)(struct spi_nor *nor,
const struct sfdp_parameter_header *bfpt_header,
const struct sfdp_bfpt *bfpt);
+ void (*smpt_read_dummy)(const struct spi_nor *nor, u8 *read_dummy);
+ void (*smpt_map_id)(const struct spi_nor *nor, u8 *map_id);
int (*post_sfdp)(struct spi_nor *nor);
int (*late_init)(struct spi_nor *nor);
};
@@ -477,6 +492,8 @@ struct spi_nor_id {
* SPI_NOR_NO_ERASE: no erase command needed.
* SPI_NOR_QUAD_PP: flash supports Quad Input Page Program.
* SPI_NOR_RWW: flash supports reads while write.
+ * SPI_NOR_HAS_CMP: flash SR2 has complement (CMP) protect bit. Must
+ * be used with SPI_NOR_HAS_LOCK.
*
* @no_sfdp_flags: flags that indicate support that can be discovered via SFDP.
* Used when SFDP tables are not defined in the flash. These
@@ -525,6 +542,7 @@ struct flash_info {
#define SPI_NOR_NO_ERASE BIT(6)
#define SPI_NOR_QUAD_PP BIT(8)
#define SPI_NOR_RWW BIT(9)
+#define SPI_NOR_HAS_CMP BIT(10)
u8 no_sfdp_flags;
#define SPI_NOR_SKIP_SFDP BIT(0)
@@ -626,6 +644,7 @@ int spi_nor_read_cr(struct spi_nor *nor, u8 *cr);
int spi_nor_write_sr(struct spi_nor *nor, const u8 *sr, size_t len);
int spi_nor_write_sr_and_check(struct spi_nor *nor, u8 sr1);
int spi_nor_write_16bit_cr_and_check(struct spi_nor *nor, u8 cr);
+int spi_nor_write_sr_cr_and_check(struct spi_nor *nor, const u8 *regs);
ssize_t spi_nor_read_data(struct spi_nor *nor, loff_t from, size_t len,
u8 *buf);
@@ -666,7 +685,9 @@ int spi_nor_post_bfpt_fixups(struct spi_nor *nor,
const struct sfdp_bfpt *bfpt);
void spi_nor_init_default_locking_ops(struct spi_nor *nor);
+bool spi_nor_has_default_locking_ops(struct spi_nor *nor);
void spi_nor_try_unlock_all(struct spi_nor *nor);
+void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr);
void spi_nor_set_mtd_locking_ops(struct spi_nor *nor);
void spi_nor_set_mtd_otp_ops(struct spi_nor *nor);
@@ -699,6 +720,10 @@ static inline bool spi_nor_needs_sfdp(const struct spi_nor *nor)
return !nor->info->size;
}
+u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor);
+void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs, u64 *len);
+bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, const u8 *sr);
+
#ifdef CONFIG_DEBUG_FS
void spi_nor_debugfs_register(struct spi_nor *nor);
void spi_nor_debugfs_shutdown(void);
diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c
index fa6956144d2e..288e2866daed 100644
--- a/drivers/mtd/spi-nor/debugfs.c
+++ b/drivers/mtd/spi-nor/debugfs.c
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
+#include <linux/array_size.h>
#include <linux/debugfs.h>
+#include <linux/math64.h>
#include <linux/mtd/spi-nor.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi-mem.h>
@@ -28,6 +30,8 @@ static const char *const snor_f_names[] = {
SNOR_F_NAME(RWW),
SNOR_F_NAME(ECC),
SNOR_F_NAME(NO_WP),
+ SNOR_F_NAME(SWAP16),
+ SNOR_F_NAME(HAS_SR2_CMP_BIT6),
};
#undef SNOR_F_NAME
@@ -76,11 +80,14 @@ static void spi_nor_print_flags(struct seq_file *s, unsigned long flags,
static int spi_nor_params_show(struct seq_file *s, void *data)
{
struct spi_nor *nor = s->private;
+ u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor);
struct spi_nor_flash_parameter *params = nor->params;
struct spi_nor_erase_map *erase_map = &params->erase_map;
struct spi_nor_erase_region *region = erase_map->regions;
const struct flash_info *info = nor->info;
char buf[16], *str;
+ loff_t lock_start;
+ u64 lock_length;
unsigned int i;
seq_printf(s, "name\t\t%s\n", info->name);
@@ -92,7 +99,8 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
seq_printf(s, "address nbytes\t%u\n", nor->addr_nbytes);
seq_puts(s, "flags\t\t");
- spi_nor_print_flags(s, nor->flags, snor_f_names, sizeof(snor_f_names));
+ spi_nor_print_flags(s, nor->flags, snor_f_names,
+ ARRAY_SIZE(snor_f_names));
seq_puts(s, "\n");
seq_puts(s, "\nopcodes\n");
@@ -138,12 +146,12 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
if (!(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
string_get_size(params->size, 1, STRING_UNITS_2, buf, sizeof(buf));
- seq_printf(s, " %02x (%s)\n", nor->params->die_erase_opcode, buf);
+ seq_printf(s, " %02x (%s)\n", params->die_erase_opcode, buf);
}
seq_puts(s, "\nsector map\n");
seq_puts(s, " region (in hex) | erase mask | overlaid\n");
- seq_puts(s, " ------------------+------------+----------\n");
+ seq_puts(s, " ------------------+------------+---------\n");
for (i = 0; i < erase_map->n_regions; i++) {
u64 start = region[i].offset;
u64 end = start + region[i].size - 1;
@@ -158,10 +166,69 @@ static int spi_nor_params_show(struct seq_file *s, void *data)
region[i].overlaid ? "yes" : "no");
}
+ if (!spi_nor_has_default_locking_ops(nor))
+ return 0;
+
+ seq_puts(s, "\nlocked sectors\n");
+ seq_puts(s, " region (in hex) | status | #sectors\n");
+ seq_puts(s, " ------------------+----------+---------\n");
+
+ spi_nor_get_locked_range_sr(nor, nor->dfs_sr_cache, &lock_start, &lock_length);
+ if (!lock_length || lock_length == params->size) {
+ seq_printf(s, " %08llx-%08llx | %s | %llu\n", 0ULL, params->size - 1,
+ lock_length ? " locked" : "unlocked",
+ div_u64(params->size, min_prot_len));
+ } else if (!lock_start) {
+ seq_printf(s, " %08llx-%08llx | %s | %llu\n", 0ULL, lock_length - 1,
+ " locked", div_u64(lock_length, min_prot_len));
+ seq_printf(s, " %08llx-%08llx | %s | %llu\n", lock_length, params->size - 1,
+ "unlocked", div_u64(params->size - lock_length, min_prot_len));
+ } else {
+ seq_printf(s, " %08llx-%08llx | %s | %llu\n", 0ULL, lock_start - 1,
+ "unlocked", div_u64(lock_start, min_prot_len));
+ seq_printf(s, " %08llx-%08llx | %s | %llu\n", lock_start, params->size - 1,
+ " locked", div_u64(lock_length, min_prot_len));
+ }
+
return 0;
}
DEFINE_SHOW_ATTRIBUTE(spi_nor_params);
+static int spi_nor_locked_sectors_map_show(struct seq_file *s, void *data)
+{
+ struct spi_nor *nor = s->private;
+ struct spi_nor_flash_parameter *params = nor->params;
+ u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor);
+ unsigned int sector = 0;
+ u64 offset = 0;
+ bool locked;
+ int i;
+
+ seq_printf(s, "Locked sectors map (x: locked, .: unlocked, unit: %lldkiB)\n",
+ min_prot_len / 1024);
+ while (offset < params->size) {
+ seq_printf(s, " 0x%08llx (#%5d): ", offset, sector);
+ for (i = 0; i < 64 && offset < params->size; i++) {
+ locked = spi_nor_is_locked_sr(nor, offset, min_prot_len,
+ nor->dfs_sr_cache);
+ if (locked)
+ seq_puts(s, "x");
+ else
+ seq_puts(s, ".");
+
+ if (((i + 1) % 16) == 0)
+ seq_puts(s, " ");
+
+ offset += min_prot_len;
+ sector++;
+ }
+ seq_puts(s, "\n");
+ }
+
+ return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(spi_nor_locked_sectors_map);
+
static void spi_nor_print_read_cmd(struct seq_file *s, u32 cap,
struct spi_nor_read_command *cmd)
{
@@ -247,6 +314,9 @@ void spi_nor_debugfs_register(struct spi_nor *nor)
debugfs_create_file("params", 0444, d, nor, &spi_nor_params_fops);
debugfs_create_file("capabilities", 0444, d, nor,
&spi_nor_capabilities_fops);
+ if (spi_nor_has_default_locking_ops(nor))
+ debugfs_create_file("locked-sectors-map", 0444, d, nor,
+ &spi_nor_locked_sectors_map_fops);
}
void spi_nor_debugfs_shutdown(void)
diff --git a/drivers/mtd/spi-nor/macronix.c b/drivers/mtd/spi-nor/macronix.c
index 99936fd25d43..e97f5cbd9aad 100644
--- a/drivers/mtd/spi-nor/macronix.c
+++ b/drivers/mtd/spi-nor/macronix.c
@@ -45,8 +45,55 @@ mx25l25635_post_bfpt_fixups(struct spi_nor *nor,
return 0;
}
+static int
+macronix_qpp4b_post_sfdp_fixups(struct spi_nor *nor)
+{
+ /* PP_1_1_4_4B is supported but missing in 4BAIT. */
+ struct spi_nor_flash_parameter *params = nor->params;
+
+ params->hwcaps.mask |= SNOR_HWCAPS_PP_1_1_4;
+ spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP_1_1_4],
+ SPINOR_OP_PP_1_1_4_4B, SNOR_PROTO_1_1_4);
+
+ return 0;
+}
+
+static int
+mx25l3255e_late_init_fixups(struct spi_nor *nor)
+{
+ struct spi_nor_flash_parameter *params = nor->params;
+
+ /*
+ * SFDP of MX25L3255E is JESD216, which does not include the Quad
+ * Enable bit Requirement in BFPT. As a result, during BFPT parsing,
+ * the quad_enable method is not set to spi_nor_sr1_bit6_quad_enable.
+ * Therefore, it is necessary to correct this setting by late_init.
+ */
+ params->quad_enable = spi_nor_sr1_bit6_quad_enable;
+
+ /*
+ * In addition, MX25L3255E also supports 1-4-4 page program in 3-byte
+ * address mode. However, since the 3-byte address 1-4-4 page program
+ * is not defined in SFDP, it needs to be configured in late_init.
+ */
+ params->hwcaps.mask |= SNOR_HWCAPS_PP_1_4_4;
+ spi_nor_set_pp_settings(&params->page_programs[SNOR_CMD_PP_1_4_4],
+ SPINOR_OP_PP_1_4_4, SNOR_PROTO_1_4_4);
+
+ return 0;
+}
+
static const struct spi_nor_fixups mx25l25635_fixups = {
.post_bfpt = mx25l25635_post_bfpt_fixups,
+ .post_sfdp = macronix_qpp4b_post_sfdp_fixups,
+};
+
+static const struct spi_nor_fixups macronix_qpp4b_fixups = {
+ .post_sfdp = macronix_qpp4b_post_sfdp_fixups,
+};
+
+static const struct spi_nor_fixups mx25l3255e_fixups = {
+ .late_init = mx25l3255e_late_init_fixups,
};
static const struct flash_info macronix_nor_parts[] = {
@@ -70,10 +117,8 @@ static const struct flash_info macronix_nor_parts[] = {
.name = "mx25l8005",
.size = SZ_1M,
}, {
+ /* MX25L1606E */
.id = SNOR_ID(0xc2, 0x20, 0x15),
- .name = "mx25l1606e",
- .size = SZ_2M,
- .no_sfdp_flags = SECT_4K,
}, {
.id = SNOR_ID(0xc2, 0x20, 0x16),
.name = "mx25l3205d",
@@ -85,28 +130,26 @@ static const struct flash_info macronix_nor_parts[] = {
.size = SZ_8M,
.no_sfdp_flags = SECT_4K,
}, {
+ /* MX25L12805D */
.id = SNOR_ID(0xc2, 0x20, 0x18),
- .name = "mx25l12805d",
- .size = SZ_16M,
.flags = SPI_NOR_HAS_LOCK | SPI_NOR_4BIT_BP,
- .no_sfdp_flags = SECT_4K,
}, {
+ /* MX25L25635E, MX25L25645G */
.id = SNOR_ID(0xc2, 0x20, 0x19),
- .name = "mx25l25635e",
- .size = SZ_32M,
- .no_sfdp_flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
.fixups = &mx25l25635_fixups
}, {
+ /* MX66L51235F */
.id = SNOR_ID(0xc2, 0x20, 0x1a),
- .name = "mx66l51235f",
- .size = SZ_64M,
- .no_sfdp_flags = SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
.fixup_flags = SPI_NOR_4B_OPCODES,
+ .fixups = &macronix_qpp4b_fixups,
}, {
+ /* MX66L1G45G */
.id = SNOR_ID(0xc2, 0x20, 0x1b),
- .name = "mx66l1g45g",
- .size = SZ_128M,
- .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
+ .fixups = &macronix_qpp4b_fixups,
+ }, {
+ /* MX66L2G45G */
+ .id = SNOR_ID(0xc2, 0x20, 0x1c),
+ .fixups = &macronix_qpp4b_fixups,
}, {
.id = SNOR_ID(0xc2, 0x23, 0x14),
.name = "mx25v8035f",
@@ -143,23 +186,17 @@ static const struct flash_info macronix_nor_parts[] = {
.size = SZ_16M,
.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
}, {
+ /* MX25U51245G */
.id = SNOR_ID(0xc2, 0x25, 0x3a),
- .name = "mx25u51245g",
- .size = SZ_64M,
- .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
- .fixup_flags = SPI_NOR_4B_OPCODES,
+ .fixups = &macronix_qpp4b_fixups,
}, {
- .id = SNOR_ID(0xc2, 0x25, 0x3a),
- .name = "mx66u51235f",
- .size = SZ_64M,
- .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
- .fixup_flags = SPI_NOR_4B_OPCODES,
+ /* MX66U1G45G */
+ .id = SNOR_ID(0xc2, 0x25, 0x3b),
+ .fixups = &macronix_qpp4b_fixups,
}, {
+ /* MX66U2G45G */
.id = SNOR_ID(0xc2, 0x25, 0x3c),
- .name = "mx66u2g45g",
- .size = SZ_256M,
- .no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
- .fixup_flags = SPI_NOR_4B_OPCODES,
+ .fixups = &macronix_qpp4b_fixups,
}, {
.id = SNOR_ID(0xc2, 0x26, 0x18),
.name = "mx25l12855e",
@@ -184,15 +221,14 @@ static const struct flash_info macronix_nor_parts[] = {
.size = SZ_4M,
.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
}, {
+ /* MX25UW51245G */
.id = SNOR_ID(0xc2, 0x81, 0x3a),
- .name = "mx25uw51245g",
.n_banks = 4,
.flags = SPI_NOR_RWW,
}, {
+ /* MX25L3255E */
.id = SNOR_ID(0xc2, 0x9e, 0x16),
- .name = "mx25l3255e",
- .size = SZ_4M,
- .no_sfdp_flags = SECT_4K,
+ .fixups = &mx25l3255e_fixups,
},
/*
* This spares us of adding new flash entries for flashes that can be
diff --git a/drivers/mtd/spi-nor/micron-st.c b/drivers/mtd/spi-nor/micron-st.c
index e6bab2d00c92..c75b0a1cd567 100644
--- a/drivers/mtd/spi-nor/micron-st.c
+++ b/drivers/mtd/spi-nor/micron-st.c
@@ -127,9 +127,36 @@ static int micron_st_nor_set_octal_dtr(struct spi_nor *nor, bool enable)
micron_st_nor_octal_dtr_dis(nor);
}
-static void mt35xu512aba_default_init(struct spi_nor *nor)
+static int micron_st_nor_four_die_late_init(struct spi_nor *nor)
{
- nor->params->set_octal_dtr = micron_st_nor_set_octal_dtr;
+ struct spi_nor_flash_parameter *params = nor->params;
+
+ params->die_erase_opcode = SPINOR_OP_MT_DIE_ERASE;
+ params->n_dice = 4;
+
+ /*
+ * Unfortunately the die erase opcode does not have a 4-byte opcode
+ * correspondent for these flashes. The SFDP 4BAIT table fails to
+ * consider the die erase too. We're forced to enter in the 4 byte
+ * address mode in order to benefit of the die erase.
+ */
+ return spi_nor_set_4byte_addr_mode(nor, true);
+}
+
+static int micron_st_nor_two_die_late_init(struct spi_nor *nor)
+{
+ struct spi_nor_flash_parameter *params = nor->params;
+
+ params->die_erase_opcode = SPINOR_OP_MT_DIE_ERASE;
+ params->n_dice = 2;
+
+ /*
+ * Unfortunately the die erase opcode does not have a 4-byte opcode
+ * correspondent for these flashes. The SFDP 4BAIT table fails to
+ * consider the die erase too. We're forced to enter in the 4 byte
+ * address mode in order to benefit of the die erase.
+ */
+ return spi_nor_set_4byte_addr_mode(nor, true);
}
static int mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor)
@@ -140,6 +167,16 @@ static int mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor)
0, 20, SPINOR_OP_MT_DTR_RD,
SNOR_PROTO_8_8_8_DTR);
+ /*
+ * Some batches of mt35xu512aba do not contain the OCT DTR command
+ * information, but do support OCT DTR mode. Add the settings for
+ * SNOR_CMD_PP_8_8_8_DTR here. This also makes sure the flash can switch
+ * to OCT DTR mode.
+ */
+ nor->params->hwcaps.mask |= SNOR_HWCAPS_PP_8_8_8_DTR;
+ spi_nor_set_pp_settings(&nor->params->page_programs[SNOR_CMD_PP_8_8_8_DTR],
+ SPINOR_OP_PP_4B, SNOR_PROTO_8_8_8_DTR);
+
nor->cmd_ext_type = SPI_NOR_EXT_REPEAT;
nor->params->rdsr_dummy = 8;
nor->params->rdsr_addr_nbytes = 0;
@@ -155,29 +192,36 @@ static int mt35xu512aba_post_sfdp_fixup(struct spi_nor *nor)
}
static const struct spi_nor_fixups mt35xu512aba_fixups = {
- .default_init = mt35xu512aba_default_init,
.post_sfdp = mt35xu512aba_post_sfdp_fixup,
};
+static const struct spi_nor_fixups mt35_two_die_fixups = {
+ .post_sfdp = mt35xu512aba_post_sfdp_fixup,
+ .late_init = micron_st_nor_two_die_late_init,
+};
+
static const struct flash_info micron_nor_parts[] = {
{
+ /* MT35XU512ABA */
.id = SNOR_ID(0x2c, 0x5b, 0x1a),
- .name = "mt35xu512aba",
- .sector_size = SZ_128K,
- .size = SZ_64M,
- .no_sfdp_flags = SECT_4K | SPI_NOR_OCTAL_READ |
- SPI_NOR_OCTAL_DTR_READ | SPI_NOR_OCTAL_DTR_PP,
.mfr_flags = USE_FSR,
- .fixup_flags = SPI_NOR_4B_OPCODES | SPI_NOR_IO_MODE_EN_VOLATILE,
+ .fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE,
.fixups = &mt35xu512aba_fixups,
}, {
+ /* MT35XU01GBBA */
+ .id = SNOR_ID(0x2c, 0x5b, 0x1b),
+ .mfr_flags = USE_FSR,
+ .fixup_flags = SPI_NOR_IO_MODE_EN_VOLATILE,
+ .fixups = &mt35_two_die_fixups,
+ }, {
.id = SNOR_ID(0x2c, 0x5b, 0x1c),
.name = "mt35xu02g",
.sector_size = SZ_128K,
.size = SZ_256M,
.no_sfdp_flags = SECT_4K | SPI_NOR_OCTAL_READ,
.mfr_flags = USE_FSR,
- .fixup_flags = SPI_NOR_4B_OPCODES,
+ .fixup_flags = SPI_NOR_4B_OPCODES | SPI_NOR_IO_MODE_EN_VOLATILE,
+ .fixups = &mt35_two_die_fixups,
},
};
@@ -189,52 +233,20 @@ static int mt25qu512a_post_bfpt_fixup(struct spi_nor *nor,
return 0;
}
-static struct spi_nor_fixups mt25qu512a_fixups = {
+static const struct spi_nor_fixups mt25qu512a_fixups = {
.post_bfpt = mt25qu512a_post_bfpt_fixup,
};
-static int st_nor_four_die_late_init(struct spi_nor *nor)
-{
- struct spi_nor_flash_parameter *params = nor->params;
-
- params->die_erase_opcode = SPINOR_OP_MT_DIE_ERASE;
- params->n_dice = 4;
-
- /*
- * Unfortunately the die erase opcode does not have a 4-byte opcode
- * correspondent for these flashes. The SFDP 4BAIT table fails to
- * consider the die erase too. We're forced to enter in the 4 byte
- * address mode in order to benefit of the die erase.
- */
- return spi_nor_set_4byte_addr_mode(nor, true);
-}
-
-static int st_nor_two_die_late_init(struct spi_nor *nor)
-{
- struct spi_nor_flash_parameter *params = nor->params;
-
- params->die_erase_opcode = SPINOR_OP_MT_DIE_ERASE;
- params->n_dice = 2;
-
- /*
- * Unfortunately the die erase opcode does not have a 4-byte opcode
- * correspondent for these flashes. The SFDP 4BAIT table fails to
- * consider the die erase too. We're forced to enter in the 4 byte
- * address mode in order to benefit of the die erase.
- */
- return spi_nor_set_4byte_addr_mode(nor, true);
-}
-
-static struct spi_nor_fixups n25q00_fixups = {
- .late_init = st_nor_four_die_late_init,
+static const struct spi_nor_fixups n25q00_fixups = {
+ .late_init = micron_st_nor_four_die_late_init,
};
-static struct spi_nor_fixups mt25q01_fixups = {
- .late_init = st_nor_two_die_late_init,
+static const struct spi_nor_fixups mt25q01_fixups = {
+ .late_init = micron_st_nor_two_die_late_init,
};
-static struct spi_nor_fixups mt25q02_fixups = {
- .late_init = st_nor_four_die_late_init,
+static const struct spi_nor_fixups mt25q02_fixups = {
+ .late_init = micron_st_nor_four_die_late_init,
};
static const struct flash_info st_nor_parts[] = {
@@ -635,6 +647,8 @@ static int micron_st_nor_late_init(struct spi_nor *nor)
if (!params->set_4byte_addr_mode)
params->set_4byte_addr_mode = spi_nor_set_4byte_addr_mode_wren_en4b_ex4b;
+ params->set_octal_dtr = micron_st_nor_set_octal_dtr;
+
return 0;
}
diff --git a/drivers/mtd/spi-nor/otp.c b/drivers/mtd/spi-nor/otp.c
index 9a729aa3452d..7d0b145d78d8 100644
--- a/drivers/mtd/spi-nor/otp.c
+++ b/drivers/mtd/spi-nor/otp.c
@@ -6,6 +6,7 @@
*/
#include <linux/log2.h>
+#include <linux/math64.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/spi-nor.h>
diff --git a/drivers/mtd/spi-nor/sfdp.c b/drivers/mtd/spi-nor/sfdp.c
index 21727f9a4ac6..4600983cb579 100644
--- a/drivers/mtd/spi-nor/sfdp.c
+++ b/drivers/mtd/spi-nor/sfdp.c
@@ -699,6 +699,17 @@ static u8 spi_nor_smpt_addr_nbytes(const struct spi_nor *nor, const u32 settings
}
}
+static void spi_nor_smpt_read_dummy_fixups(const struct spi_nor *nor,
+ u8 *read_dummy)
+{
+ if (nor->manufacturer && nor->manufacturer->fixups &&
+ nor->manufacturer->fixups->smpt_read_dummy)
+ nor->manufacturer->fixups->smpt_read_dummy(nor, read_dummy);
+
+ if (nor->info->fixups && nor->info->fixups->smpt_read_dummy)
+ nor->info->fixups->smpt_read_dummy(nor, read_dummy);
+}
+
/**
* spi_nor_smpt_read_dummy() - return the configuration detection command read
* latency, in clock cycles.
@@ -711,11 +722,24 @@ static u8 spi_nor_smpt_read_dummy(const struct spi_nor *nor, const u32 settings)
{
u8 read_dummy = SMPT_CMD_READ_DUMMY(settings);
- if (read_dummy == SMPT_CMD_READ_DUMMY_IS_VARIABLE)
- return nor->read_dummy;
+ if (read_dummy == SMPT_CMD_READ_DUMMY_IS_VARIABLE) {
+ read_dummy = nor->read_dummy;
+ spi_nor_smpt_read_dummy_fixups(nor, &read_dummy);
+ }
+
return read_dummy;
}
+static void spi_nor_smpt_map_id_fixups(const struct spi_nor *nor, u8 *map_id)
+{
+ if (nor->manufacturer && nor->manufacturer->fixups &&
+ nor->manufacturer->fixups->smpt_map_id)
+ nor->manufacturer->fixups->smpt_map_id(nor, map_id);
+
+ if (nor->info->fixups && nor->info->fixups->smpt_map_id)
+ nor->info->fixups->smpt_map_id(nor, map_id);
+}
+
/**
* spi_nor_get_map_in_use() - get the configuration map in use
* @nor: pointer to a 'struct spi_nor'
@@ -736,7 +760,7 @@ static const u32 *spi_nor_get_map_in_use(struct spi_nor *nor, const u32 *smpt,
u8 read_data_mask, map_id;
/* Use a kmalloc'ed bounce buffer to guarantee it is DMA-able. */
- buf = kmalloc(sizeof(*buf), GFP_KERNEL);
+ buf = kmalloc_obj(*buf);
if (!buf)
return ERR_PTR(-ENOMEM);
@@ -769,6 +793,8 @@ static const u32 *spi_nor_get_map_in_use(struct spi_nor *nor, const u32 *smpt,
map_id = map_id << 1 | !!(*buf & read_data_mask);
}
+ spi_nor_smpt_map_id_fixups(nor, &map_id);
+
/*
* If command descriptors are provided, they always precede map
* descriptors in the table. There is no need to start the iteration
diff --git a/drivers/mtd/spi-nor/spansion.c b/drivers/mtd/spi-nor/spansion.c
index bf08dbf5e742..65227d989de1 100644
--- a/drivers/mtd/spi-nor/spansion.c
+++ b/drivers/mtd/spi-nor/spansion.c
@@ -17,6 +17,7 @@
#define SPINOR_OP_CLSR 0x30 /* Clear status register 1 */
#define SPINOR_OP_CLPEF 0x82 /* Clear program/erase failure flags */
+#define SPINOR_OP_CYPRESS_EX4B 0xB8 /* Exit 4-byte address mode */
#define SPINOR_OP_CYPRESS_DIE_ERASE 0x61 /* Chip (die) erase */
#define SPINOR_OP_RD_ANY_REG 0x65 /* Read any register */
#define SPINOR_OP_WR_ANY_REG 0x71 /* Write any register */
@@ -58,6 +59,13 @@
SPI_MEM_OP_DUMMY(ndummy, 0), \
SPI_MEM_OP_DATA_IN(1, buf, 0))
+#define CYPRESS_NOR_EN4B_EX4B_OP(enable) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(enable ? SPINOR_OP_EN4B : \
+ SPINOR_OP_CYPRESS_EX4B, 0), \
+ SPI_MEM_OP_NO_ADDR, \
+ SPI_MEM_OP_NO_DUMMY, \
+ SPI_MEM_OP_NO_DATA)
+
#define SPANSION_OP(opcode) \
SPI_MEM_OP(SPI_MEM_OP_CMD(opcode, 0), \
SPI_MEM_OP_NO_ADDR, \
@@ -356,6 +364,20 @@ static int cypress_nor_quad_enable_volatile(struct spi_nor *nor)
return 0;
}
+static int cypress_nor_set_4byte_addr_mode(struct spi_nor *nor, bool enable)
+{
+ int ret;
+ struct spi_mem_op op = CYPRESS_NOR_EN4B_EX4B_OP(enable);
+
+ spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
+
+ ret = spi_mem_exec_op(nor->spimem, &op);
+ if (ret)
+ dev_dbg(nor->dev, "error %d setting 4-byte mode\n", ret);
+
+ return ret;
+}
+
/**
* cypress_nor_determine_addr_mode_by_sr1() - Determine current address mode
* (3 or 4-byte) by querying status
@@ -526,6 +548,9 @@ s25fs256t_post_bfpt_fixup(struct spi_nor *nor,
struct spi_mem_op op;
int ret;
+ /* Assign 4-byte address mode method that is not determined in BFPT */
+ nor->params->set_4byte_addr_mode = cypress_nor_set_4byte_addr_mode;
+
ret = cypress_nor_set_addr_mode_nbytes(nor);
if (ret)
return ret;
@@ -578,7 +603,7 @@ static int s25fs256t_late_init(struct spi_nor *nor)
return 0;
}
-static struct spi_nor_fixups s25fs256t_fixups = {
+static const struct spi_nor_fixups s25fs256t_fixups = {
.post_bfpt = s25fs256t_post_bfpt_fixup,
.post_sfdp = s25fs256t_post_sfdp_fixup,
.late_init = s25fs256t_late_init,
@@ -591,6 +616,9 @@ s25hx_t_post_bfpt_fixup(struct spi_nor *nor,
{
int ret;
+ /* Assign 4-byte address mode method that is not determined in BFPT */
+ nor->params->set_4byte_addr_mode = cypress_nor_set_4byte_addr_mode;
+
ret = cypress_nor_set_addr_mode_nbytes(nor);
if (ret)
return ret;
@@ -646,11 +674,13 @@ static int s25hx_t_late_init(struct spi_nor *nor)
params->ready = cypress_nor_sr_ready_and_clear;
cypress_nor_ecc_init(nor);
- params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE;
+ if (params->n_dice > 1)
+ params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE;
+
return 0;
}
-static struct spi_nor_fixups s25hx_t_fixups = {
+static const struct spi_nor_fixups s25hx_t_fixups = {
.post_bfpt = s25hx_t_post_bfpt_fixup,
.post_sfdp = s25hx_t_post_sfdp_fixup,
.late_init = s25hx_t_late_init,
@@ -718,6 +748,9 @@ static int s28hx_t_post_bfpt_fixup(struct spi_nor *nor,
const struct sfdp_parameter_header *bfpt_header,
const struct sfdp_bfpt *bfpt)
{
+ /* Assign 4-byte address mode method that is not determined in BFPT */
+ nor->params->set_4byte_addr_mode = cypress_nor_set_4byte_addr_mode;
+
return cypress_nor_set_addr_mode_nbytes(nor);
}
@@ -729,6 +762,9 @@ static int s28hx_t_late_init(struct spi_nor *nor)
params->ready = cypress_nor_sr_ready_and_clear;
cypress_nor_ecc_init(nor);
+ if (params->n_dice > 1)
+ params->die_erase_opcode = SPINOR_OP_CYPRESS_DIE_ERASE;
+
return 0;
}
@@ -754,8 +790,46 @@ s25fs_s_nor_post_bfpt_fixups(struct spi_nor *nor,
return 0;
}
+static void s25fs_s_nor_smpt_read_dummy(const struct spi_nor *nor,
+ u8 *read_dummy)
+{
+ /*
+ * The configuration detection dwords in S25FS-S SMPT has 65h as
+ * command instruction and 'variable' as configuration detection command
+ * latency. Set 8 dummy cycles as it is factory default for 65h (read
+ * any register) op.
+ */
+ *read_dummy = 8;
+}
+
+static void s25fs_s_nor_smpt_map_id_dummy(const struct spi_nor *nor, u8 *map_id)
+{
+ /*
+ * The S25FS512S chip supports:
+ * - Hybrid sector option which has physical set of eight 4-KB sectors
+ * and one 224-KB sector at the top or bottom of address space with
+ * all remaining sectors of 256-KB
+ * - Uniform sector option which has uniform 256-KB sectors
+ *
+ * On the other hand, the datasheet rev.O Table 71 on page 153 JEDEC
+ * Sector Map Parameter Dword-6 Config. Detect-3 does use CR3NV[1] to
+ * discern 64-KB(CR3NV[1]=0) and 256-KB(CR3NV[1]=1) uniform sectors
+ * device configuration. And in section 7.5.5.1 Configuration Register 3
+ * Non-volatile (CR3NV) page 61, the CR3NV[1] is RFU Reserved for Future
+ * Use and set to 0, which means 64-KB uniform. Since the device does
+ * not support 64-KB uniform sectors in any configuration, parsing SMPT
+ * table cannot find a valid sector map entry and fails. Fix this up by
+ * setting SMPT by overwriting the CR3NV[1] value to 1, as the table
+ * expects.
+ */
+ if (nor->params->size == SZ_64M)
+ *map_id |= BIT(0);
+}
+
static const struct spi_nor_fixups s25fs_s_nor_fixups = {
.post_bfpt = s25fs_s_nor_post_bfpt_fixups,
+ .smpt_read_dummy = s25fs_s_nor_smpt_read_dummy,
+ .smpt_map_id = s25fs_s_nor_smpt_map_id_dummy,
};
static const struct flash_info spansion_nor_parts[] = {
diff --git a/drivers/mtd/spi-nor/sst.c b/drivers/mtd/spi-nor/sst.c
index 175211fe6a5e..db02c14ba16f 100644
--- a/drivers/mtd/spi-nor/sst.c
+++ b/drivers/mtd/spi-nor/sst.c
@@ -203,6 +203,8 @@ static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
/* Start write from odd address. */
if (to % 2) {
+ bool needs_write_enable = (len > 1);
+
/* write one byte. */
ret = sst_nor_write_data(nor, to, 1, buf);
if (ret < 0)
@@ -210,6 +212,17 @@ static int sst_nor_write(struct mtd_info *mtd, loff_t to, size_t len,
to++;
actual++;
+
+ /*
+ * Byte program clears the write enable latch. If more
+ * data needs to be written using the AAI sequence,
+ * re-enable writes.
+ */
+ if (needs_write_enable) {
+ ret = spi_nor_write_enable(nor);
+ if (ret)
+ goto out;
+ }
}
/* Write out most of the data here. */
diff --git a/drivers/mtd/spi-nor/swp.c b/drivers/mtd/spi-nor/swp.c
index e48c3cff247a..235070b215d1 100644
--- a/drivers/mtd/spi-nor/swp.c
+++ b/drivers/mtd/spi-nor/swp.c
@@ -5,6 +5,7 @@
* Copyright (C) 2005, Intec Automation Inc.
* Copyright (C) 2014, Freescale Semiconductor, Inc.
*/
+#include <linux/math64.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/spi-nor.h>
@@ -27,11 +28,22 @@ static u8 spi_nor_get_sr_tb_mask(struct spi_nor *nor)
{
if (nor->flags & SNOR_F_HAS_SR_TB_BIT6)
return SR_TB_BIT6;
- else
+ else if (nor->flags & SNOR_F_HAS_SR_TB)
return SR_TB_BIT5;
+ else
+ return 0;
}
-static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
+static u8 spi_nor_get_sr_cmp_mask(struct spi_nor *nor)
+{
+ if (!(nor->flags & SNOR_F_NO_READ_CR) &&
+ nor->flags & SNOR_F_HAS_SR2_CMP_BIT6)
+ return SR2_CMP_BIT6;
+ else
+ return 0;
+}
+
+u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
{
unsigned int bp_slots, bp_slots_needed;
/*
@@ -52,14 +64,16 @@ static u64 spi_nor_get_min_prot_length_sr(struct spi_nor *nor)
return sector_size;
}
-static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs,
- u64 *len)
+void spi_nor_get_locked_range_sr(struct spi_nor *nor, const u8 *sr, loff_t *ofs,
+ u64 *len)
{
- struct mtd_info *mtd = &nor->mtd;
u64 min_prot_len;
- u8 mask = spi_nor_get_sr_bp_mask(nor);
+ u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
- u8 bp, val = sr & mask;
+ u8 cmp_mask = spi_nor_get_sr_cmp_mask(nor);
+ u8 bp, val = sr[0] & bp_mask;
+ bool tb = (nor->flags & SNOR_F_HAS_SR_TB) ? sr[0] & tb_mask : 0;
+ bool cmp = sr[1] & cmp_mask;
if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3_BIT6)
val = (val & ~SR_BP3_BIT6) | SR_BP3;
@@ -67,22 +81,37 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs,
bp = val >> SR_BP_SHIFT;
if (!bp) {
- /* No protection */
- *ofs = 0;
- *len = 0;
+ if (!cmp) {
+ /* No protection */
+ *ofs = 0;
+ *len = 0;
+ } else {
+ /* Full protection */
+ *ofs = 0;
+ *len = nor->params->size;
+ }
return;
}
min_prot_len = spi_nor_get_min_prot_length_sr(nor);
*len = min_prot_len << (bp - 1);
+ if (*len > nor->params->size)
+ *len = nor->params->size;
- if (*len > mtd->size)
- *len = mtd->size;
+ if (cmp)
+ *len = nor->params->size - *len;
- if (nor->flags & SNOR_F_HAS_SR_TB && sr & tb_mask)
- *ofs = 0;
- else
- *ofs = mtd->size - *len;
+ if (!cmp) {
+ if (tb)
+ *ofs = 0;
+ else
+ *ofs = nor->params->size - *len;
+ } else {
+ if (tb)
+ *ofs = nor->params->size - *len;
+ else
+ *ofs = 0;
+ }
}
/*
@@ -90,7 +119,7 @@ static void spi_nor_get_locked_range_sr(struct spi_nor *nor, u8 sr, loff_t *ofs,
* (if @locked is false); false otherwise.
*/
static bool spi_nor_check_lock_status_sr(struct spi_nor *nor, loff_t ofs,
- u64 len, u8 sr, bool locked)
+ u64 len, const u8 *sr, bool locked)
{
loff_t lock_offs, lock_offs_max, offs_max;
u64 lock_len;
@@ -111,27 +140,102 @@ static bool spi_nor_check_lock_status_sr(struct spi_nor *nor, loff_t ofs,
return (ofs >= lock_offs_max) || (offs_max <= lock_offs);
}
-static bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, u8 sr)
+bool spi_nor_is_locked_sr(struct spi_nor *nor, loff_t ofs, u64 len, const u8 *sr)
{
return spi_nor_check_lock_status_sr(nor, ofs, len, sr, true);
}
static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, u64 len,
- u8 sr)
+ const u8 *sr)
{
return spi_nor_check_lock_status_sr(nor, ofs, len, sr, false);
}
+static int spi_nor_sr_set_bp_mask(struct spi_nor *nor, u8 *sr, u8 pow)
+{
+ u8 mask = spi_nor_get_sr_bp_mask(nor);
+ u8 val = pow << SR_BP_SHIFT;
+
+ if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3)
+ val = (val & ~SR_BP3) | SR_BP3_BIT6;
+
+ if (val & ~mask)
+ return -EINVAL;
+
+ sr[0] |= val;
+
+ return 0;
+}
+
+static int spi_nor_build_sr(struct spi_nor *nor, const u8 *old_sr, u8 *new_sr,
+ u8 pow, bool use_top, bool cmp)
+{
+ u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
+ u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
+ u8 cmp_mask = spi_nor_get_sr_cmp_mask(nor);
+ int ret;
+
+ new_sr[0] = old_sr[0] & ~bp_mask & ~tb_mask;
+ new_sr[1] = old_sr[1] & ~cmp_mask;
+
+ /* Build BP field */
+ ret = spi_nor_sr_set_bp_mask(nor, &new_sr[0], pow);
+ if (ret)
+ return ret;
+
+ /* Build TB field */
+ if ((!cmp && !use_top) || (cmp && use_top))
+ new_sr[0] |= tb_mask;
+
+ /* Build CMP field */
+ if (cmp)
+ new_sr[1] |= cmp_mask;
+
+ return 0;
+}
+
+/*
+ * Keep a local cache containing all lock-related bits for debugfs use only.
+ * This way, debugfs never needs to access the flash directly.
+ */
+void spi_nor_cache_sr_lock_bits(struct spi_nor *nor, u8 *sr)
+{
+ u8 bp_mask = spi_nor_get_sr_bp_mask(nor);
+ u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
+ u8 cmp_mask = spi_nor_get_sr_cmp_mask(nor);
+ u8 sr_cr[2] = {};
+
+
+ if (!sr) {
+ if (spi_nor_read_sr(nor, nor->bouncebuf))
+ return;
+
+ sr_cr[0] = nor->bouncebuf[0];
+
+ if (!(nor->flags & SNOR_F_NO_READ_CR)) {
+ if (spi_nor_read_cr(nor, nor->bouncebuf))
+ return;
+ }
+
+ sr_cr[1] = nor->bouncebuf[0];
+ sr = sr_cr;
+ }
+
+ nor->dfs_sr_cache[0] = sr[0] & (bp_mask | tb_mask | SR_SRWD);
+ nor->dfs_sr_cache[1] = sr[1] & cmp_mask;
+}
+
/*
* Lock a region of the flash. Compatible with ST Micro and similar flash.
* Supports the block protection bits BP{0,1,2}/BP{0,1,2,3} in the status
* register
* (SR). Does not support these features found in newer SR bitfields:
* - SEC: sector/block protect - only handle SEC=0 (block protect)
- * - CMP: complement protect - only support CMP=0 (range is not complemented)
*
* Support for the following is provided conditionally for some flash:
* - TB: top/bottom protect
+ * - CMP: complement protect (BP and TP describe the unlocked part, while
+ * the reminder is locked)
*
* Sample table portion for 8MB flash (Winbond w25q64fw):
*
@@ -157,21 +261,31 @@ static bool spi_nor_is_unlocked_sr(struct spi_nor *nor, loff_t ofs, u64 len,
*/
static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
{
- struct mtd_info *mtd = &nor->mtd;
- u64 min_prot_len;
- int ret, status_old, status_new;
- u8 mask = spi_nor_get_sr_bp_mask(nor);
- u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
- u8 pow, val;
+ u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor);
+ u8 status_old[2] = {}, status_new[2] = {}, status_new_cmp[2] = {};
+ u8 *best_status_new = status_new;
+ loff_t ofs_old, ofs_new, ofs_new_cmp;
+ u64 len_old, len_new, len_new_cmp;
loff_t lock_len;
- bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
+ bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB,
+ can_be_cmp = spi_nor_get_sr_cmp_mask(nor);
bool use_top;
+ int ret;
+ u8 pow;
ret = spi_nor_read_sr(nor, nor->bouncebuf);
if (ret)
return ret;
- status_old = nor->bouncebuf[0];
+ status_old[0] = nor->bouncebuf[0];
+
+ if (!(nor->flags & SNOR_F_NO_READ_CR)) {
+ ret = spi_nor_read_cr(nor, nor->bouncebuf);
+ if (ret)
+ return ret;
+
+ status_old[1] = nor->bouncebuf[0];
+ }
/* If nothing in our range is unlocked, we don't need to do anything */
if (spi_nor_is_locked_sr(nor, ofs, len, status_old))
@@ -182,7 +296,7 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
can_be_bottom = false;
/* If anything above us is unlocked, we can't use 'top' protection */
- if (!spi_nor_is_locked_sr(nor, ofs + len, mtd->size - (ofs + len),
+ if (!spi_nor_is_locked_sr(nor, ofs + len, nor->params->size - (ofs + len),
status_old))
can_be_top = false;
@@ -194,50 +308,86 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
/* lock_len: length of region that should end up locked */
if (use_top)
- lock_len = mtd->size - ofs;
+ lock_len = nor->params->size - ofs;
else
lock_len = ofs + len;
- if (lock_len == mtd->size) {
- val = mask;
- } else {
- min_prot_len = spi_nor_get_min_prot_length_sr(nor);
+ if (lock_len == nor->params->size)
+ pow = (nor->flags & SNOR_F_HAS_4BIT_BP) ? GENMASK(3, 0) : GENMASK(2, 0);
+ else
pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
- val = pow << SR_BP_SHIFT;
-
- if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3)
- val = (val & ~SR_BP3) | SR_BP3_BIT6;
- if (val & ~mask)
- return -EINVAL;
+ ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top, false);
+ if (ret)
+ return ret;
- /* Don't "lock" with no region! */
- if (!(val & mask))
- return -EINVAL;
+ /*
+ * In case the region asked is not fully met, maybe we can try with the
+ * complement feature
+ */
+ spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new);
+ if (can_be_cmp && len_new != lock_len) {
+ pow = ilog2(nor->params->size - lock_len) - ilog2(min_prot_len) + 1;
+ ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true);
+ if (ret)
+ return ret;
+
+ /*
+ * ilog2() "floors" the result, which means in some cases we may have to
+ * manually reduce the scope when the complement feature is used.
+ * The uAPI is to never lock more than what is requested, but less is accepted.
+ * Make sure we are not covering a too wide range, reduce it otherwise.
+ */
+ spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp);
+ if (len_new_cmp > lock_len) {
+ pow++;
+ ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true);
+ if (ret)
+ return ret;
+ }
+
+ /* Pick the CMP configuration if we cover a closer range */
+ spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new);
+ spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp);
+ if (len_new_cmp <= lock_len &&
+ (lock_len - len_new_cmp) < (lock_len - len_new))
+ best_status_new = status_new_cmp;
}
- status_new = (status_old & ~mask & ~tb_mask) | val;
-
/*
* Disallow further writes if WP# pin is neither left floating nor
* wrongly tied to GND (that includes internal pull-downs).
* WP# pin hard strapped to GND can be a valid use case.
*/
if (!(nor->flags & SNOR_F_NO_WP))
- status_new |= SR_SRWD;
+ best_status_new[0] |= SR_SRWD;
+
+ spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old);
+ spi_nor_get_locked_range_sr(nor, best_status_new, &ofs_new, &len_new);
- if (!use_top)
- status_new |= tb_mask;
+ /* Don't "lock" with no region! */
+ if (!len_new)
+ return -EINVAL;
/* Don't bother if they're the same */
- if (status_new == status_old)
+ if (best_status_new[0] == status_old[0] && best_status_new[1] == status_old[1])
return 0;
/* Only modify protection if it will not unlock other areas */
- if ((status_new & mask) < (status_old & mask))
+ if (len_old &&
+ (ofs_old < ofs_new || (ofs_new + len_new) < (ofs_old + len_old)))
return -EINVAL;
- return spi_nor_write_sr_and_check(nor, status_new);
+ if (nor->flags & SNOR_F_NO_READ_CR)
+ ret = spi_nor_write_sr_and_check(nor, best_status_new[0]);
+ else
+ ret = spi_nor_write_sr_cr_and_check(nor, best_status_new);
+ if (ret)
+ return ret;
+
+ spi_nor_cache_sr_lock_bits(nor, best_status_new);
+
+ return 0;
}
/*
@@ -247,21 +397,31 @@ static int spi_nor_sr_lock(struct spi_nor *nor, loff_t ofs, u64 len)
*/
static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
{
- struct mtd_info *mtd = &nor->mtd;
- u64 min_prot_len;
- int ret, status_old, status_new;
- u8 mask = spi_nor_get_sr_bp_mask(nor);
- u8 tb_mask = spi_nor_get_sr_tb_mask(nor);
- u8 pow, val;
+ u64 min_prot_len = spi_nor_get_min_prot_length_sr(nor);
+ u8 status_old[2] = {}, status_new[2] = {}, status_new_cmp[2] = {};
+ u8 *best_status_new = status_new;
+ loff_t ofs_old, ofs_new, ofs_new_cmp;
+ u64 len_old, len_new, len_new_cmp;
loff_t lock_len;
- bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB;
+ bool can_be_top = true, can_be_bottom = nor->flags & SNOR_F_HAS_SR_TB,
+ can_be_cmp = spi_nor_get_sr_cmp_mask(nor);
bool use_top;
+ int ret;
+ u8 pow;
ret = spi_nor_read_sr(nor, nor->bouncebuf);
if (ret)
return ret;
- status_old = nor->bouncebuf[0];
+ status_old[0] = nor->bouncebuf[0];
+
+ if (!(nor->flags & SNOR_F_NO_READ_CR)) {
+ ret = spi_nor_read_cr(nor, nor->bouncebuf);
+ if (ret)
+ return ret;
+
+ status_old[1] = nor->bouncebuf[0];
+ }
/* If nothing in our range is locked, we don't need to do anything */
if (spi_nor_is_unlocked_sr(nor, ofs, len, status_old))
@@ -272,7 +432,7 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
can_be_top = false;
/* If anything above us is locked, we can't use 'bottom' protection */
- if (!spi_nor_is_unlocked_sr(nor, ofs + len, mtd->size - (ofs + len),
+ if (!spi_nor_is_unlocked_sr(nor, ofs + len, nor->params->size - (ofs + len),
status_old))
can_be_bottom = false;
@@ -282,45 +442,86 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
/* Prefer top, if both are valid */
use_top = can_be_top;
- /* lock_len: length of region that should remain locked */
- if (use_top)
- lock_len = mtd->size - (ofs + len);
+ /*
+ * lock_len: length of region that should remain locked.
+ *
+ * When can_be_top and can_be_bottom booleans are true, both adjacent
+ * regions are unlocked, thus the entire flash can be unlocked.
+ */
+ if (can_be_top && can_be_bottom)
+ lock_len = 0;
+ else if (use_top)
+ lock_len = nor->params->size - (ofs + len);
else
lock_len = ofs;
- if (lock_len == 0) {
- val = 0; /* fully unlocked */
- } else {
- min_prot_len = spi_nor_get_min_prot_length_sr(nor);
+ if (lock_len == 0)
+ pow = 0; /* fully unlocked */
+ else
pow = ilog2(lock_len) - ilog2(min_prot_len) + 1;
- val = pow << SR_BP_SHIFT;
- if (nor->flags & SNOR_F_HAS_SR_BP3_BIT6 && val & SR_BP3)
- val = (val & ~SR_BP3) | SR_BP3_BIT6;
+ ret = spi_nor_build_sr(nor, status_old, status_new, pow, use_top, false);
+ if (ret)
+ return ret;
- /* Some power-of-two sizes are not supported */
- if (val & ~mask)
- return -EINVAL;
+ /*
+ * In case the region asked is not fully met, maybe we can try with the
+ * complement feature
+ */
+ spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new);
+ if (can_be_cmp && len_new != lock_len) {
+ pow = ilog2(nor->params->size - lock_len) - ilog2(min_prot_len) + 1;
+ ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true);
+ if (ret)
+ return ret;
+
+ /*
+ * ilog2() "floors" the result, which means in some cases we may have to
+ * manually reduce the scope when the complement feature is used.
+ * The uAPI is to never unlock more than what is requested, but less is accepted.
+ * Make sure we are not covering a too small range, increase it otherwise.
+ */
+ spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp);
+ if (len_new_cmp < lock_len) {
+ pow--;
+ ret = spi_nor_build_sr(nor, status_old, status_new_cmp, pow, use_top, true);
+ if (ret)
+ return ret;
+ }
+
+ /* Pick the CMP configuration if we cover a closer range */
+ spi_nor_get_locked_range_sr(nor, status_new, &ofs_new, &len_new);
+ spi_nor_get_locked_range_sr(nor, status_new_cmp, &ofs_new_cmp, &len_new_cmp);
+ if (len_new_cmp <= lock_len &&
+ (lock_len - len_new_cmp) < (lock_len - len_new))
+ best_status_new = status_new_cmp;
}
- status_new = (status_old & ~mask & ~tb_mask) | val;
-
/* Don't protect status register if we're fully unlocked */
if (lock_len == 0)
- status_new &= ~SR_SRWD;
-
- if (!use_top)
- status_new |= tb_mask;
+ best_status_new[0] &= ~SR_SRWD;
/* Don't bother if they're the same */
- if (status_new == status_old)
+ if (best_status_new[0] == status_old[0] && best_status_new[1] == status_old[1])
return 0;
/* Only modify protection if it will not lock other areas */
- if ((status_new & mask) > (status_old & mask))
+ spi_nor_get_locked_range_sr(nor, status_old, &ofs_old, &len_old);
+ spi_nor_get_locked_range_sr(nor, best_status_new, &ofs_new, &len_new);
+ if (len_old && len_new &&
+ (ofs_new < ofs_old || (ofs_old + len_old) < (ofs_new + len_new)))
return -EINVAL;
- return spi_nor_write_sr_and_check(nor, status_new);
+ if (nor->flags & SNOR_F_NO_READ_CR)
+ ret = spi_nor_write_sr_and_check(nor, best_status_new[0]);
+ else
+ ret = spi_nor_write_sr_cr_and_check(nor, best_status_new);
+ if (ret)
+ return ret;
+
+ spi_nor_cache_sr_lock_bits(nor, best_status_new);
+
+ return 0;
}
/*
@@ -332,13 +533,24 @@ static int spi_nor_sr_unlock(struct spi_nor *nor, loff_t ofs, u64 len)
*/
static int spi_nor_sr_is_locked(struct spi_nor *nor, loff_t ofs, u64 len)
{
+ u8 sr_cr[2] = {};
int ret;
ret = spi_nor_read_sr(nor, nor->bouncebuf);
if (ret)
return ret;
- return spi_nor_is_locked_sr(nor, ofs, len, nor->bouncebuf[0]);
+ sr_cr[0] = nor->bouncebuf[0];
+
+ if (!(nor->flags & SNOR_F_NO_READ_CR)) {
+ ret = spi_nor_read_cr(nor, nor->bouncebuf);
+ if (ret)
+ return ret;
+
+ sr_cr[1] = nor->bouncebuf[0];
+ }
+
+ return spi_nor_is_locked_sr(nor, ofs, len, sr_cr);
}
static const struct spi_nor_locking_ops spi_nor_sr_locking_ops = {
@@ -352,6 +564,11 @@ void spi_nor_init_default_locking_ops(struct spi_nor *nor)
nor->params->locking_ops = &spi_nor_sr_locking_ops;
}
+bool spi_nor_has_default_locking_ops(struct spi_nor *nor)
+{
+ return nor->params->locking_ops == &spi_nor_sr_locking_ops;
+}
+
static int spi_nor_lock(struct mtd_info *mtd, loff_t ofs, u64 len)
{
struct spi_nor *nor = mtd_to_spi_nor(mtd);
diff --git a/drivers/mtd/spi-nor/sysfs.c b/drivers/mtd/spi-nor/sysfs.c
index 4f12ff755df0..643513ee891b 100644
--- a/drivers/mtd/spi-nor/sysfs.c
+++ b/drivers/mtd/spi-nor/sysfs.c
@@ -104,7 +104,7 @@ static const struct attribute_group spi_nor_sysfs_group = {
.is_visible = spi_nor_sysfs_is_visible,
.is_bin_visible = spi_nor_sysfs_is_bin_visible,
.attrs = spi_nor_sysfs_entries,
- .bin_attrs_new = spi_nor_sysfs_bin_entries,
+ .bin_attrs = spi_nor_sysfs_bin_entries,
};
const struct attribute_group *spi_nor_sysfs_groups[] = {
diff --git a/drivers/mtd/spi-nor/winbond.c b/drivers/mtd/spi-nor/winbond.c
index 8d0a00d69e12..8ebdbcec0b3f 100644
--- a/drivers/mtd/spi-nor/winbond.c
+++ b/drivers/mtd/spi-nor/winbond.c
@@ -10,6 +10,7 @@
#define WINBOND_NOR_OP_RDEAR 0xc8 /* Read Extended Address Register */
#define WINBOND_NOR_OP_WREAR 0xc5 /* Write Extended Address Register */
+#define WINBOND_NOR_OP_SELDIE 0xc2 /* Select active die */
#define WINBOND_NOR_WREAR_OP(buf) \
SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_WREAR, 0), \
@@ -17,6 +18,12 @@
SPI_MEM_OP_NO_DUMMY, \
SPI_MEM_OP_DATA_OUT(1, buf, 0))
+#define WINBOND_NOR_SELDIE_OP(buf) \
+ SPI_MEM_OP(SPI_MEM_OP_CMD(WINBOND_NOR_OP_SELDIE, 0), \
+ SPI_MEM_OP_NO_ADDR, \
+ SPI_MEM_OP_NO_DUMMY, \
+ SPI_MEM_OP_DATA_OUT(1, buf, 0))
+
static int
w25q128_post_bfpt_fixups(struct spi_nor *nor,
const struct sfdp_parameter_header *bfpt_header,
@@ -66,6 +73,99 @@ static const struct spi_nor_fixups w25q256_fixups = {
.post_bfpt = w25q256_post_bfpt_fixups,
};
+static int
+winbond_rdcr_post_bfpt_fixup(struct spi_nor *nor,
+ const struct sfdp_parameter_header *bfpt_header,
+ const struct sfdp_bfpt *bfpt)
+{
+ /*
+ * W25H02NW, unlike its W25H512NW nor W25H01NW cousins, improperly sets
+ * the QE BFPT configuration bits, indicating a non readable CR. This is
+ * both incorrect and impractical, as the chip features a CMP bit for its
+ * locking scheme that lays in the Control Register, and needs to be read.
+ */
+ nor->flags &= ~SNOR_F_NO_READ_CR;
+
+ return 0;
+}
+
+static const struct spi_nor_fixups winbond_rdcr_fixup = {
+ .post_bfpt = winbond_rdcr_post_bfpt_fixup,
+};
+
+/**
+ * winbond_nor_select_die() - Set active die.
+ * @nor: pointer to 'struct spi_nor'.
+ * @die: die to set active.
+ *
+ * Certain Winbond chips feature more than a single die. This is mostly hidden
+ * to the user, except that some chips may experience time deviation when
+ * modifying the status bits between dies, which in some corner cases may
+ * produce problematic races. Being able to explicitly select a die to check its
+ * state in this case may be useful.
+ *
+ * Return: 0 on success, -errno otherwise.
+ */
+static int winbond_nor_select_die(struct spi_nor *nor, u8 die)
+{
+ int ret;
+
+ nor->bouncebuf[0] = die;
+
+ if (nor->spimem) {
+ struct spi_mem_op op = WINBOND_NOR_SELDIE_OP(nor->bouncebuf);
+
+ spi_nor_spimem_setup_op(nor, &op, nor->reg_proto);
+
+ ret = spi_mem_exec_op(nor->spimem, &op);
+ } else {
+ ret = spi_nor_controller_ops_write_reg(nor,
+ WINBOND_NOR_OP_SELDIE,
+ nor->bouncebuf, 1);
+ }
+
+ if (ret)
+ dev_dbg(nor->dev, "error %d selecting die %d\n", ret, die);
+
+ return ret;
+}
+
+static int winbond_nor_multi_die_ready(struct spi_nor *nor)
+{
+ int ret, i;
+
+ for (i = 0; i < nor->params->n_dice; i++) {
+ ret = winbond_nor_select_die(nor, i);
+ if (ret)
+ return ret;
+
+ ret = spi_nor_sr_ready(nor);
+ if (ret <= 0)
+ return ret;
+ }
+
+ return 1;
+}
+
+static int
+winbond_nor_multi_die_post_sfdp_fixups(struct spi_nor *nor)
+{
+ /*
+ * SFDP supports dice numbers, but this information is only available in
+ * optional additional tables which are not provided by these chips.
+ * Dice number has an impact though, because these devices need extra
+ * care when reading the busy bit.
+ */
+ nor->params->n_dice = nor->params->size / SZ_64M;
+ nor->params->ready = winbond_nor_multi_die_ready;
+
+ return 0;
+}
+
+static const struct spi_nor_fixups winbond_nor_multi_die_fixups = {
+ .post_sfdp = winbond_nor_multi_die_post_sfdp_fixups,
+};
+
static const struct flash_info winbond_nor_parts[] = {
{
.id = SNOR_ID(0xef, 0x30, 0x10),
@@ -147,6 +247,10 @@ static const struct flash_info winbond_nor_parts[] = {
.size = SZ_64M,
.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
}, {
+ /* W25Q01JV */
+ .id = SNOR_ID(0xef, 0x40, 0x21),
+ .fixups = &winbond_nor_multi_die_fixups,
+ }, {
.id = SNOR_ID(0xef, 0x50, 0x12),
.name = "w25q20bw",
.size = SZ_256K,
@@ -190,6 +294,7 @@ static const struct flash_info winbond_nor_parts[] = {
.id = SNOR_ID(0xef, 0x60, 0x19),
.name = "w25q256jw",
.size = SZ_32M,
+ .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
}, {
.id = SNOR_ID(0xef, 0x60, 0x20),
@@ -211,6 +316,7 @@ static const struct flash_info winbond_nor_parts[] = {
.id = SNOR_ID(0xef, 0x70, 0x17),
.name = "w25q64jvm",
.size = SZ_8M,
+ .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
.no_sfdp_flags = SECT_4K,
}, {
.id = SNOR_ID(0xef, 0x70, 0x18),
@@ -222,6 +328,10 @@ static const struct flash_info winbond_nor_parts[] = {
.id = SNOR_ID(0xef, 0x70, 0x19),
.name = "w25q256jvm",
}, {
+ /* W25Q02JV */
+ .id = SNOR_ID(0xef, 0x70, 0x22),
+ .fixups = &winbond_nor_multi_die_fixups,
+ }, {
.id = SNOR_ID(0xef, 0x71, 0x19),
.name = "w25m512jv",
.size = SZ_64M,
@@ -249,12 +359,45 @@ static const struct flash_info winbond_nor_parts[] = {
.id = SNOR_ID(0xef, 0x80, 0x19),
.name = "w25q256jwm",
.size = SZ_32M,
- .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB,
+ .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 | SPI_NOR_4BIT_BP,
.no_sfdp_flags = SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ,
}, {
.id = SNOR_ID(0xef, 0x80, 0x20),
.name = "w25q512nwm",
.otp = SNOR_OTP(256, 3, 0x1000, 0x1000),
+ }, {
+ /* W25Q01NWxxIQ */
+ .id = SNOR_ID(0xef, 0x60, 0x21),
+ .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+ SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+ .fixups = &winbond_rdcr_fixup,
+ }, {
+ /* W25Q01NWxxIM */
+ .id = SNOR_ID(0xef, 0x80, 0x21),
+ .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+ SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+ }, {
+ /* W25Q02NWxxIM */
+ .id = SNOR_ID(0xef, 0x80, 0x22),
+ .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+ SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+ .fixups = &winbond_rdcr_fixup,
+ }, {
+ /* W25H512NWxxAM */
+ .id = SNOR_ID(0xef, 0xa0, 0x20),
+ .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+ SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+ }, {
+ /* W25H01NWxxAM */
+ .id = SNOR_ID(0xef, 0xa0, 0x21),
+ .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+ SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+ }, {
+ /* W25H02NWxxAM */
+ .id = SNOR_ID(0xef, 0xa0, 0x22),
+ .flags = SPI_NOR_HAS_LOCK | SPI_NOR_HAS_TB | SPI_NOR_TB_SR_BIT6 |
+ SPI_NOR_4BIT_BP | SPI_NOR_HAS_CMP,
+ .fixups = &winbond_rdcr_fixup,
},
};
diff --git a/drivers/mtd/ssfdc.c b/drivers/mtd/ssfdc.c
index 46c01fa2ec46..4a5225699a54 100644
--- a/drivers/mtd/ssfdc.c
+++ b/drivers/mtd/ssfdc.c
@@ -295,7 +295,7 @@ static void ssfdcr_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd)
if (cis_sector == -1)
return;
- ssfdc = kzalloc(sizeof(*ssfdc), GFP_KERNEL);
+ ssfdc = kzalloc_obj(*ssfdc);
if (!ssfdc)
return;
diff --git a/drivers/mtd/tests/stresstest.c b/drivers/mtd/tests/stresstest.c
index 8062098930d6..944f960a4bbf 100644
--- a/drivers/mtd/tests/stresstest.c
+++ b/drivers/mtd/tests/stresstest.c
@@ -178,7 +178,7 @@ static int __init mtd_stresstest_init(void)
err = -ENOMEM;
readbuf = vmalloc(bufsize);
writebuf = vmalloc(bufsize);
- offsets = kmalloc_array(ebcnt, sizeof(int), GFP_KERNEL);
+ offsets = kmalloc_objs(int, ebcnt);
if (!readbuf || !writebuf || !offsets)
goto out;
for (i = 0; i < ebcnt; i++)
diff --git a/drivers/mtd/ubi/attach.c b/drivers/mtd/ubi/attach.c
index adc47b87b38a..0fa115cbf3ad 100644
--- a/drivers/mtd/ubi/attach.c
+++ b/drivers/mtd/ubi/attach.c
@@ -131,7 +131,7 @@ static struct ubi_ainf_volume *find_or_add_av(struct ubi_attach_info *ai,
return NULL;
/* The volume is absent - add it */
- av = kzalloc(sizeof(*av), GFP_KERNEL);
+ av = kzalloc_obj(*av);
if (!av)
return ERR_PTR(-ENOMEM);
@@ -1451,7 +1451,7 @@ static struct ubi_attach_info *alloc_ai(const char *slab_name)
{
struct ubi_attach_info *ai;
- ai = kzalloc(sizeof(struct ubi_attach_info), GFP_KERNEL);
+ ai = kzalloc_obj(struct ubi_attach_info);
if (!ai)
return ai;
@@ -1600,7 +1600,7 @@ int ubi_attach(struct ubi_device *ubi, int force_scan)
err = ubi_read_volume_table(ubi, ai);
if (err)
- goto out_ai;
+ goto out_fm;
err = ubi_wl_init(ubi, ai);
if (err)
@@ -1642,6 +1642,8 @@ out_wl:
out_vtbl:
ubi_free_all_volumes(ubi);
vfree(ubi->vtbl);
+out_fm:
+ ubi_free_fastmap(ubi);
out_ai:
destroy_ai(ai);
return err;
diff --git a/drivers/mtd/ubi/block.c b/drivers/mtd/ubi/block.c
index 2836905f0152..29c0d6941a81 100644
--- a/drivers/mtd/ubi/block.c
+++ b/drivers/mtd/ubi/block.c
@@ -199,7 +199,7 @@ static blk_status_t ubiblock_read(struct request *req)
* and ubi_read_sg() will check that limit.
*/
ubi_sgl_init(&pdu->usgl);
- blk_rq_map_sg(req->q, req, pdu->usgl.sg);
+ blk_rq_map_sg(req, pdu->usgl.sg);
while (bytes_left) {
/*
@@ -282,12 +282,12 @@ static void ubiblock_release(struct gendisk *gd)
mutex_unlock(&dev->dev_mutex);
}
-static int ubiblock_getgeo(struct block_device *bdev, struct hd_geometry *geo)
+static int ubiblock_getgeo(struct gendisk *disk, struct hd_geometry *geo)
{
/* Some tools might require this information */
geo->heads = 1;
geo->cylinders = 1;
- geo->sectors = get_capacity(bdev->bd_disk);
+ geo->sectors = get_capacity(disk);
geo->start = 0;
return 0;
}
@@ -312,7 +312,7 @@ static blk_status_t ubiblock_queue_rq(struct blk_mq_hw_ctx *hctx,
static int ubiblock_init_request(struct blk_mq_tag_set *set,
struct request *req, unsigned int hctx_idx,
- unsigned int numa_node)
+ int numa_node)
{
struct ubiblock_pdu *pdu = blk_mq_rq_to_pdu(req);
@@ -368,7 +368,7 @@ int ubiblock_create(struct ubi_volume_info *vi)
goto out_unlock;
}
- dev = kzalloc(sizeof(struct ubiblock), GFP_KERNEL);
+ dev = kzalloc_obj(struct ubiblock);
if (!dev) {
ret = -ENOMEM;
goto out_unlock;
diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c
index ef6a22f372f9..674ad87809df 100644
--- a/drivers/mtd/ubi/build.c
+++ b/drivers/mtd/ubi/build.c
@@ -930,7 +930,7 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num,
}
}
- ubi = kzalloc(sizeof(struct ubi_device), GFP_KERNEL);
+ ubi = kzalloc_obj(struct ubi_device);
if (!ubi)
return -ENOMEM;
diff --git a/drivers/mtd/ubi/cdev.c b/drivers/mtd/ubi/cdev.c
index b700a0efaa93..fd39030dbf89 100644
--- a/drivers/mtd/ubi/cdev.c
+++ b/drivers/mtd/ubi/cdev.c
@@ -727,7 +727,7 @@ static int rename_volumes(struct ubi_device *ubi,
int name_len = req->ents[i].name_len;
const char *name = req->ents[i].name;
- re = kzalloc(sizeof(struct ubi_rename_entry), GFP_KERNEL);
+ re = kzalloc_obj(struct ubi_rename_entry);
if (!re) {
err = -ENOMEM;
goto out_free;
@@ -801,7 +801,7 @@ static int rename_volumes(struct ubi_device *ubi,
goto out_free;
}
- re1 = kzalloc(sizeof(struct ubi_rename_entry), GFP_KERNEL);
+ re1 = kzalloc_obj(struct ubi_rename_entry);
if (!re1) {
err = -ENOMEM;
ubi_close_volume(desc);
@@ -1007,7 +1007,7 @@ static long ubi_cdev_ioctl(struct file *file, unsigned int cmd,
struct ubi_rnvol_req *req;
dbg_gen("re-name volumes");
- req = kmalloc(sizeof(struct ubi_rnvol_req), GFP_KERNEL);
+ req = kmalloc_obj(struct ubi_rnvol_req);
if (!req) {
err = -ENOMEM;
break;
diff --git a/drivers/mtd/ubi/eba.c b/drivers/mtd/ubi/eba.c
index c7ba7a15c9f7..f0c549e62c55 100644
--- a/drivers/mtd/ubi/eba.c
+++ b/drivers/mtd/ubi/eba.c
@@ -124,12 +124,11 @@ struct ubi_eba_table *ubi_eba_create_table(struct ubi_volume *vol,
int err = -ENOMEM;
int i;
- tbl = kzalloc(sizeof(*tbl), GFP_KERNEL);
+ tbl = kzalloc_obj(*tbl);
if (!tbl)
return ERR_PTR(-ENOMEM);
- tbl->entries = kmalloc_array(nentries, sizeof(*tbl->entries),
- GFP_KERNEL);
+ tbl->entries = kmalloc_objs(*tbl->entries, nentries);
if (!tbl->entries)
goto err;
@@ -248,7 +247,7 @@ static struct ubi_ltree_entry *ltree_add_entry(struct ubi_device *ubi,
{
struct ubi_ltree_entry *le, *le1, *le_free;
- le = kmalloc(sizeof(struct ubi_ltree_entry), GFP_NOFS);
+ le = kmalloc_obj(struct ubi_ltree_entry, GFP_NOFS);
if (!le)
return ERR_PTR(-ENOMEM);
@@ -1536,11 +1535,11 @@ int self_check_eba(struct ubi_device *ubi, struct ubi_attach_info *ai_fastmap,
num_volumes = ubi->vtbl_slots + UBI_INT_VOL_COUNT;
- scan_eba = kmalloc_array(num_volumes, sizeof(*scan_eba), GFP_KERNEL);
+ scan_eba = kmalloc_objs(*scan_eba, num_volumes);
if (!scan_eba)
return -ENOMEM;
- fm_eba = kmalloc_array(num_volumes, sizeof(*fm_eba), GFP_KERNEL);
+ fm_eba = kmalloc_objs(*fm_eba, num_volumes);
if (!fm_eba) {
kfree(scan_eba);
return -ENOMEM;
@@ -1551,17 +1550,13 @@ int self_check_eba(struct ubi_device *ubi, struct ubi_attach_info *ai_fastmap,
if (!vol)
continue;
- scan_eba[i] = kmalloc_array(vol->reserved_pebs,
- sizeof(**scan_eba),
- GFP_KERNEL);
+ scan_eba[i] = kmalloc_objs(**scan_eba, vol->reserved_pebs);
if (!scan_eba[i]) {
ret = -ENOMEM;
goto out_free;
}
- fm_eba[i] = kmalloc_array(vol->reserved_pebs,
- sizeof(**fm_eba),
- GFP_KERNEL);
+ fm_eba[i] = kmalloc_objs(**fm_eba, vol->reserved_pebs);
if (!fm_eba[i]) {
ret = -ENOMEM;
kfree(scan_eba[i]);
diff --git a/drivers/mtd/ubi/fastmap-wl.c b/drivers/mtd/ubi/fastmap-wl.c
index 9bdb6525f128..a0f750411f9d 100644
--- a/drivers/mtd/ubi/fastmap-wl.c
+++ b/drivers/mtd/ubi/fastmap-wl.c
@@ -466,7 +466,7 @@ int ubi_ensure_anchor_pebs(struct ubi_device *ubi)
ubi->wl_scheduled = 1;
spin_unlock(&ubi->wl_lock);
- wrk = kmalloc(sizeof(struct ubi_work), GFP_NOFS);
+ wrk = kmalloc_obj(struct ubi_work, GFP_NOFS);
if (!wrk) {
spin_lock(&ubi->wl_lock);
ubi->wl_scheduled = 0;
@@ -530,8 +530,6 @@ int ubi_is_erase_work(struct ubi_work *wrk)
static void ubi_fastmap_close(struct ubi_device *ubi)
{
- int i;
-
return_unused_pool_pebs(ubi, &ubi->fm_pool);
return_unused_pool_pebs(ubi, &ubi->fm_wl_pool);
@@ -540,11 +538,7 @@ static void ubi_fastmap_close(struct ubi_device *ubi)
ubi->fm_anchor = NULL;
}
- if (ubi->fm) {
- for (i = 0; i < ubi->fm->used_blocks; i++)
- kfree(ubi->fm->e[i]);
- }
- kfree(ubi->fm);
+ ubi_free_fastmap(ubi);
}
/**
diff --git a/drivers/mtd/ubi/fastmap.c b/drivers/mtd/ubi/fastmap.c
index 9a4940874be5..3bce1b4d8464 100644
--- a/drivers/mtd/ubi/fastmap.c
+++ b/drivers/mtd/ubi/fastmap.c
@@ -889,13 +889,13 @@ int ubi_scan_fastmap(struct ubi_device *ubi, struct ubi_attach_info *ai,
down_write(&ubi->fm_protect);
memset(ubi->fm_buf, 0, ubi->fm_size);
- fmsb = kmalloc(sizeof(*fmsb), GFP_KERNEL);
+ fmsb = kmalloc_obj(*fmsb);
if (!fmsb) {
ret = -ENOMEM;
goto out;
}
- fm = kzalloc(sizeof(*fm), GFP_KERNEL);
+ fm = kzalloc_obj(*fm);
if (!fm) {
ret = -ENOMEM;
kfree(fmsb);
@@ -1416,7 +1416,7 @@ static int invalidate_fastmap(struct ubi_device *ubi)
ubi->fm = NULL;
ret = -ENOMEM;
- fm = kzalloc(sizeof(*fm), GFP_NOFS);
+ fm = kzalloc_obj(*fm, GFP_NOFS);
if (!fm)
goto out;
@@ -1501,7 +1501,7 @@ int ubi_update_fastmap(struct ubi_device *ubi)
return 0;
}
- new_fm = kzalloc(sizeof(*new_fm), GFP_NOFS);
+ new_fm = kzalloc_obj(*new_fm, GFP_NOFS);
if (!new_fm) {
up_write(&ubi->fm_eba_sem);
up_write(&ubi->work_sem);
diff --git a/drivers/mtd/ubi/gluebi.c b/drivers/mtd/ubi/gluebi.c
index 1b980d15d9fb..e3891ae82dfa 100644
--- a/drivers/mtd/ubi/gluebi.c
+++ b/drivers/mtd/ubi/gluebi.c
@@ -281,7 +281,7 @@ static int gluebi_create(struct ubi_device_info *di,
struct gluebi_device *gluebi, *g;
struct mtd_info *mtd;
- gluebi = kzalloc(sizeof(struct gluebi_device), GFP_KERNEL);
+ gluebi = kzalloc_obj(struct gluebi_device);
if (!gluebi)
return -ENOMEM;
diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c
index a4999bce435f..915eb64cb001 100644
--- a/drivers/mtd/ubi/io.c
+++ b/drivers/mtd/ubi/io.c
@@ -868,6 +868,8 @@ int ubi_io_write_ec_hdr(struct ubi_device *ubi, int pnum,
return -EROFS;
}
+ memset((char *)ec_hdr + UBI_EC_HDR_SIZE, 0xFF, ubi->ec_hdr_alsize - UBI_EC_HDR_SIZE);
+
err = ubi_io_write(ubi, ec_hdr, pnum, 0, ubi->ec_hdr_alsize);
return err;
}
@@ -1150,6 +1152,14 @@ int ubi_io_write_vid_hdr(struct ubi_device *ubi, int pnum,
return -EROFS;
}
+ if (ubi->vid_hdr_shift) {
+ memset((char *)p, 0xFF, ubi->vid_hdr_shift);
+ memset((char *)p + ubi->vid_hdr_shift + UBI_VID_HDR_SIZE, 0xFF,
+ ubi->vid_hdr_alsize - (ubi->vid_hdr_shift + UBI_VID_HDR_SIZE));
+ } else {
+ memset((char *)p + UBI_VID_HDR_SIZE, 0xFF, ubi->vid_hdr_alsize - UBI_VID_HDR_SIZE);
+ }
+
err = ubi_io_write(ubi, p, pnum, ubi->vid_hdr_aloffset,
ubi->vid_hdr_alsize);
return err;
diff --git a/drivers/mtd/ubi/kapi.c b/drivers/mtd/ubi/kapi.c
index f1ea8677467f..9626990f67cb 100644
--- a/drivers/mtd/ubi/kapi.c
+++ b/drivers/mtd/ubi/kapi.c
@@ -140,7 +140,7 @@ struct ubi_volume_desc *ubi_open_volume(int ubi_num, int vol_id, int mode)
goto out_put_ubi;
}
- desc = kmalloc(sizeof(struct ubi_volume_desc), GFP_KERNEL);
+ desc = kmalloc_obj(struct ubi_volume_desc);
if (!desc) {
err = -ENOMEM;
goto out_put_ubi;
@@ -791,33 +791,6 @@ int ubi_sync(int ubi_num)
}
EXPORT_SYMBOL_GPL(ubi_sync);
-/**
- * ubi_flush - flush UBI work queue.
- * @ubi_num: UBI device to flush work queue
- * @vol_id: volume id to flush for
- * @lnum: logical eraseblock number to flush for
- *
- * This function executes all pending works for a particular volume id / logical
- * eraseblock number pair. If either value is set to %UBI_ALL, then it acts as
- * a wildcard for all of the corresponding volume numbers or logical
- * eraseblock numbers. It returns zero in case of success and a negative error
- * code in case of failure.
- */
-int ubi_flush(int ubi_num, int vol_id, int lnum)
-{
- struct ubi_device *ubi;
- int err = 0;
-
- ubi = ubi_get_device(ubi_num);
- if (!ubi)
- return -ENODEV;
-
- err = ubi_wl_flush(ubi, vol_id, lnum);
- ubi_put_device(ubi);
- return err;
-}
-EXPORT_SYMBOL_GPL(ubi_flush);
-
BLOCKING_NOTIFIER_HEAD(ubi_notifiers);
/**
diff --git a/drivers/mtd/ubi/nvmem.c b/drivers/mtd/ubi/nvmem.c
index 34f8c1d3cdee..2dade9727cf2 100644
--- a/drivers/mtd/ubi/nvmem.c
+++ b/drivers/mtd/ubi/nvmem.c
@@ -75,7 +75,7 @@ static int ubi_nvmem_add(struct ubi_volume_info *vi)
WARN_ON_ONCE(vi->size <= 0))
return -EINVAL;
- unv = kzalloc(sizeof(struct ubi_nvmem), GFP_KERNEL);
+ unv = kzalloc_obj(struct ubi_nvmem);
if (!unv)
return -ENOMEM;
diff --git a/drivers/mtd/ubi/ubi.h b/drivers/mtd/ubi/ubi.h
index c792b9bcab9b..af466cd83ae0 100644
--- a/drivers/mtd/ubi/ubi.h
+++ b/drivers/mtd/ubi/ubi.h
@@ -969,10 +969,22 @@ int ubi_scan_fastmap(struct ubi_device *ubi, struct ubi_attach_info *ai,
struct ubi_attach_info *scan_ai);
int ubi_fastmap_init_checkmap(struct ubi_volume *vol, int leb_count);
void ubi_fastmap_destroy_checkmap(struct ubi_volume *vol);
+static inline void ubi_free_fastmap(struct ubi_device *ubi)
+{
+ if (ubi->fm) {
+ int i;
+
+ for (i = 0; i < ubi->fm->used_blocks; i++)
+ kmem_cache_free(ubi_wl_entry_slab, ubi->fm->e[i]);
+ kfree(ubi->fm);
+ ubi->fm = NULL;
+ }
+}
#else
static inline int ubi_update_fastmap(struct ubi_device *ubi) { return 0; }
static inline int ubi_fastmap_init_checkmap(struct ubi_volume *vol, int leb_count) { return 0; }
static inline void ubi_fastmap_destroy_checkmap(struct ubi_volume *vol) {}
+static inline void ubi_free_fastmap(struct ubi_device *ubi) { }
#endif
/* block.c */
@@ -1088,7 +1100,7 @@ ubi_alloc_vid_buf(const struct ubi_device *ubi, gfp_t gfp_flags)
struct ubi_vid_io_buf *vidb;
void *buf;
- vidb = kzalloc(sizeof(*vidb), gfp_flags);
+ vidb = kzalloc_obj(*vidb, gfp_flags);
if (!vidb)
return NULL;
diff --git a/drivers/mtd/ubi/vmt.c b/drivers/mtd/ubi/vmt.c
index e5cf3bdca3b0..50192b95b6e4 100644
--- a/drivers/mtd/ubi/vmt.c
+++ b/drivers/mtd/ubi/vmt.c
@@ -172,7 +172,7 @@ int ubi_create_volume(struct ubi_device *ubi, struct ubi_mkvol_req *req)
if (ubi->ro_mode)
return -EROFS;
- vol = kzalloc(sizeof(struct ubi_volume), GFP_KERNEL);
+ vol = kzalloc_obj(struct ubi_volume);
if (!vol)
return -ENOMEM;
diff --git a/drivers/mtd/ubi/vtbl.c b/drivers/mtd/ubi/vtbl.c
index 6e5489e233dd..06d87a3013a5 100644
--- a/drivers/mtd/ubi/vtbl.c
+++ b/drivers/mtd/ubi/vtbl.c
@@ -531,7 +531,7 @@ static int init_volumes(struct ubi_device *ubi,
if (be32_to_cpu(vtbl[i].reserved_pebs) == 0)
continue; /* Empty record */
- vol = kzalloc(sizeof(struct ubi_volume), GFP_KERNEL);
+ vol = kzalloc_obj(struct ubi_volume);
if (!vol)
return -ENOMEM;
@@ -623,7 +623,7 @@ static int init_volumes(struct ubi_device *ubi,
}
/* And add the layout volume */
- vol = kzalloc(sizeof(struct ubi_volume), GFP_KERNEL);
+ vol = kzalloc_obj(struct ubi_volume);
if (!vol)
return -ENOMEM;
diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c
index fbd399cf6503..e3705db8e570 100644
--- a/drivers/mtd/ubi/wl.c
+++ b/drivers/mtd/ubi/wl.c
@@ -602,7 +602,7 @@ static int schedule_erase(struct ubi_device *ubi, struct ubi_wl_entry *e,
dbg_wl("schedule erasure of PEB %d, EC %d, torture %d",
e->pnum, e->ec, torture);
- wl_wrk = kmalloc(sizeof(struct ubi_work), GFP_NOFS);
+ wl_wrk = kmalloc_obj(struct ubi_work, GFP_NOFS);
if (!wl_wrk)
return -ENOMEM;
@@ -1071,7 +1071,7 @@ static int ensure_wear_leveling(struct ubi_device *ubi, int nested)
ubi->wl_scheduled = 1;
spin_unlock(&ubi->wl_lock);
- wrk = kmalloc(sizeof(struct ubi_work), GFP_NOFS);
+ wrk = kmalloc_obj(struct ubi_work, GFP_NOFS);
if (!wrk) {
err = -ENOMEM;
goto out_cancel;