From 4617721c502b2ddaa4e324e86da4997edf738fa5 Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Wed, 2 Sep 2026 09:55:01 -0400 Subject: ftrace: Synchronize the initialization of ftrace_ops There's some internal state that ftrace_ops needs to have set, but since it can be declared outside of the ftrace.c code, it calls ftrace_ops_init() on the ops in every global function. The issue is that if two tasks call it on the same ops at the same time it is possible to have the initialization of one corrupt the initialization of the other call. Create a ops_mutex to use to synchronize every initialization of the ftrace_ops. The mutex is taken within checking the ftrace_ops flag that states it was initializied but the flag is checked again after the mutex has been taken. Checking first outside the mutex allows it to shortcut having to take the mutex. But then the check needs to be done again after the mute is taken in case of races. Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock") Reported-by: sashiko-bot@kernel.org Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/ Signed-off-by: Steven Rostedt --- drivers/mtd/nand/Kconfig | 74 + drivers/mtd/nand/Makefile | 16 + drivers/mtd/nand/bbt.c | 128 + drivers/mtd/nand/core.c | 536 ++ drivers/mtd/nand/ecc-mtk.c | 635 ++ drivers/mtd/nand/ecc-mxic.c | 878 +++ drivers/mtd/nand/ecc-realtek.c | 467 ++ drivers/mtd/nand/ecc-sw-bch.c | 406 ++ drivers/mtd/nand/ecc-sw-hamming.c | 660 ++ drivers/mtd/nand/ecc.c | 735 +++ drivers/mtd/nand/onenand/Kconfig | 73 + drivers/mtd/nand/onenand/Makefile | 14 + drivers/mtd/nand/onenand/generic.c | 115 + drivers/mtd/nand/onenand/onenand_base.c | 4027 ++++++++++++ drivers/mtd/nand/onenand/onenand_bbt.c | 245 + drivers/mtd/nand/onenand/onenand_omap2.c | 608 ++ drivers/mtd/nand/onenand/onenand_samsung.c | 1006 +++ drivers/mtd/nand/onenand/samsung.h | 56 + drivers/mtd/nand/qpic_common.c | 779 +++ drivers/mtd/nand/raw/Kconfig | 541 ++ drivers/mtd/nand/raw/Makefile | 73 + drivers/mtd/nand/raw/ams-delta.c | 447 ++ drivers/mtd/nand/raw/arasan-nand-controller.c | 1518 +++++ drivers/mtd/nand/raw/atmel/Makefile | 5 + drivers/mtd/nand/raw/atmel/nand-controller.c | 2685 ++++++++ drivers/mtd/nand/raw/atmel/pmecc.c | 1012 +++ drivers/mtd/nand/raw/atmel/pmecc.h | 68 + drivers/mtd/nand/raw/au1550nd.c | 367 ++ drivers/mtd/nand/raw/bcm47xxnflash/Makefile | 5 + drivers/mtd/nand/raw/bcm47xxnflash/bcm47xxnflash.h | 26 + drivers/mtd/nand/raw/bcm47xxnflash/main.c | 79 + drivers/mtd/nand/raw/bcm47xxnflash/ops_bcm4706.c | 454 ++ drivers/mtd/nand/raw/brcmnand/Kconfig | 49 + drivers/mtd/nand/raw/brcmnand/Makefile | 10 + drivers/mtd/nand/raw/brcmnand/bcm6368_nand.c | 131 + drivers/mtd/nand/raw/brcmnand/bcma_nand.c | 132 + drivers/mtd/nand/raw/brcmnand/bcmbca_nand.c | 126 + drivers/mtd/nand/raw/brcmnand/brcmnand.c | 3585 +++++++++++ drivers/mtd/nand/raw/brcmnand/brcmnand.h | 97 + drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c | 36 + drivers/mtd/nand/raw/brcmnand/iproc_nand.c | 149 + drivers/mtd/nand/raw/cadence-nand-controller.c | 3348 ++++++++++ drivers/mtd/nand/raw/cafe_nand.c | 894 +++ drivers/mtd/nand/raw/cs553x_nand.c | 421 ++ drivers/mtd/nand/raw/davinci_nand.c | 1048 ++++ drivers/mtd/nand/raw/denali.c | 1381 ++++ drivers/mtd/nand/raw/denali.h | 398 ++ drivers/mtd/nand/raw/denali_dt.c | 235 + drivers/mtd/nand/raw/denali_pci.c | 139 + drivers/mtd/nand/raw/diskonchip.c | 1581 +++++ drivers/mtd/nand/raw/fsl_elbc_nand.c | 1009 +++ drivers/mtd/nand/raw/fsl_ifc_nand.c | 1155 ++++ drivers/mtd/nand/raw/fsl_upm.c | 270 + drivers/mtd/nand/raw/fsmc_nand.c | 1242 ++++ drivers/mtd/nand/raw/gpio.c | 406 ++ drivers/mtd/nand/raw/gpmi-nand/Makefile | 2 + drivers/mtd/nand/raw/gpmi-nand/bch-regs.h | 115 + drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c | 2935 +++++++++ drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h | 189 + drivers/mtd/nand/raw/gpmi-nand/gpmi-regs.h | 180 + drivers/mtd/nand/raw/hisi504_nand.c | 869 +++ drivers/mtd/nand/raw/ingenic/Kconfig | 45 + drivers/mtd/nand/raw/ingenic/Makefile | 9 + drivers/mtd/nand/raw/ingenic/ingenic_ecc.c | 164 + drivers/mtd/nand/raw/ingenic/ingenic_ecc.h | 83 + drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c | 561 ++ drivers/mtd/nand/raw/ingenic/jz4725b_bch.c | 295 + drivers/mtd/nand/raw/ingenic/jz4740_ecc.c | 197 + drivers/mtd/nand/raw/ingenic/jz4780_bch.c | 271 + drivers/mtd/nand/raw/intel-nand-controller.c | 742 +++ drivers/mtd/nand/raw/internals.h | 174 + drivers/mtd/nand/raw/loongson-nand-controller.c | 1024 +++ drivers/mtd/nand/raw/lpc32xx_mlc.c | 915 +++ drivers/mtd/nand/raw/lpc32xx_slc.c | 1035 +++ drivers/mtd/nand/raw/marvell_nand.c | 3194 ++++++++++ drivers/mtd/nand/raw/meson_nand.c | 1634 +++++ drivers/mtd/nand/raw/mpc5121_nfc.c | 834 +++ drivers/mtd/nand/raw/mtk_nand.c | 1657 +++++ drivers/mtd/nand/raw/mxc_nand.c | 1841 ++++++ drivers/mtd/nand/raw/mxic_nand.c | 587 ++ drivers/mtd/nand/raw/nand_amd.c | 53 + drivers/mtd/nand/raw/nand_base.c | 6609 ++++++++++++++++++++ drivers/mtd/nand/raw/nand_bbt.c | 1490 +++++ drivers/mtd/nand/raw/nand_esmt.c | 59 + drivers/mtd/nand/raw/nand_hynix.c | 747 +++ drivers/mtd/nand/raw/nand_ids.c | 220 + drivers/mtd/nand/raw/nand_jedec.c | 141 + drivers/mtd/nand/raw/nand_legacy.c | 644 ++ drivers/mtd/nand/raw/nand_macronix.c | 500 ++ drivers/mtd/nand/raw/nand_micron.c | 599 ++ drivers/mtd/nand/raw/nand_onfi.c | 362 ++ drivers/mtd/nand/raw/nand_samsung.c | 139 + drivers/mtd/nand/raw/nand_sandisk.c | 26 + drivers/mtd/nand/raw/nand_timings.c | 737 +++ drivers/mtd/nand/raw/nand_toshiba.c | 304 + drivers/mtd/nand/raw/nandsim.c | 2459 ++++++++ drivers/mtd/nand/raw/ndfc.c | 276 + .../mtd/nand/raw/nuvoton-ma35d1-nand-controller.c | 1029 +++ drivers/mtd/nand/raw/omap2.c | 2340 +++++++ drivers/mtd/nand/raw/omap_elm.c | 571 ++ drivers/mtd/nand/raw/orion_nand.c | 229 + drivers/mtd/nand/raw/pasemi_nand.c | 247 + drivers/mtd/nand/raw/pl35x-nand-controller.c | 1210 ++++ drivers/mtd/nand/raw/plat_nand.c | 181 + drivers/mtd/nand/raw/qcom_nandc.c | 2448 ++++++++ drivers/mtd/nand/raw/r852.c | 1094 ++++ drivers/mtd/nand/raw/r852.h | 155 + drivers/mtd/nand/raw/renesas-nand-controller.c | 1420 +++++ drivers/mtd/nand/raw/rockchip-nand-controller.c | 1507 +++++ drivers/mtd/nand/raw/sh_flctl.c | 1231 ++++ drivers/mtd/nand/raw/sharpsl.c | 244 + drivers/mtd/nand/raw/sm_common.c | 210 + drivers/mtd/nand/raw/sm_common.h | 58 + drivers/mtd/nand/raw/socrates_nand.c | 241 + drivers/mtd/nand/raw/stm32_fmc2_nand.c | 2163 +++++++ drivers/mtd/nand/raw/sunxi_nand.c | 2816 +++++++++ drivers/mtd/nand/raw/technologic-nand-controller.c | 222 + drivers/mtd/nand/raw/tegra_nand.c | 1290 ++++ drivers/mtd/nand/raw/txx9ndfmc.c | 417 ++ drivers/mtd/nand/raw/vf610_nfc.c | 949 +++ drivers/mtd/nand/raw/xway_nand.c | 266 + drivers/mtd/nand/spi/Kconfig | 9 + drivers/mtd/nand/spi/Makefile | 5 + drivers/mtd/nand/spi/alliancememory.c | 153 + drivers/mtd/nand/spi/ato.c | 86 + drivers/mtd/nand/spi/core.c | 2113 +++++++ drivers/mtd/nand/spi/dosilicon.c | 91 + drivers/mtd/nand/spi/esmt.c | 257 + drivers/mtd/nand/spi/fmsh.c | 249 + drivers/mtd/nand/spi/foresee.c | 139 + drivers/mtd/nand/spi/gigadevice.c | 670 ++ drivers/mtd/nand/spi/heyangtek.c | 132 + drivers/mtd/nand/spi/macronix.c | 545 ++ drivers/mtd/nand/spi/micron.c | 442 ++ drivers/mtd/nand/spi/otp.c | 362 ++ drivers/mtd/nand/spi/paragon.c | 131 + drivers/mtd/nand/spi/skyhigh.c | 147 + drivers/mtd/nand/spi/toshiba.c | 314 + drivers/mtd/nand/spi/winbond.c | 814 +++ drivers/mtd/nand/spi/xtx.c | 263 + 140 files changed, 100957 insertions(+) create mode 100644 drivers/mtd/nand/Kconfig create mode 100644 drivers/mtd/nand/Makefile create mode 100644 drivers/mtd/nand/bbt.c create mode 100644 drivers/mtd/nand/core.c create mode 100644 drivers/mtd/nand/ecc-mtk.c create mode 100644 drivers/mtd/nand/ecc-mxic.c create mode 100644 drivers/mtd/nand/ecc-realtek.c create mode 100644 drivers/mtd/nand/ecc-sw-bch.c create mode 100644 drivers/mtd/nand/ecc-sw-hamming.c create mode 100644 drivers/mtd/nand/ecc.c create mode 100644 drivers/mtd/nand/onenand/Kconfig create mode 100644 drivers/mtd/nand/onenand/Makefile create mode 100644 drivers/mtd/nand/onenand/generic.c create mode 100644 drivers/mtd/nand/onenand/onenand_base.c create mode 100644 drivers/mtd/nand/onenand/onenand_bbt.c create mode 100644 drivers/mtd/nand/onenand/onenand_omap2.c create mode 100644 drivers/mtd/nand/onenand/onenand_samsung.c create mode 100644 drivers/mtd/nand/onenand/samsung.h create mode 100644 drivers/mtd/nand/qpic_common.c create mode 100644 drivers/mtd/nand/raw/Kconfig create mode 100644 drivers/mtd/nand/raw/Makefile create mode 100644 drivers/mtd/nand/raw/ams-delta.c create mode 100644 drivers/mtd/nand/raw/arasan-nand-controller.c create mode 100644 drivers/mtd/nand/raw/atmel/Makefile create mode 100644 drivers/mtd/nand/raw/atmel/nand-controller.c create mode 100644 drivers/mtd/nand/raw/atmel/pmecc.c create mode 100644 drivers/mtd/nand/raw/atmel/pmecc.h create mode 100644 drivers/mtd/nand/raw/au1550nd.c create mode 100644 drivers/mtd/nand/raw/bcm47xxnflash/Makefile create mode 100644 drivers/mtd/nand/raw/bcm47xxnflash/bcm47xxnflash.h create mode 100644 drivers/mtd/nand/raw/bcm47xxnflash/main.c create mode 100644 drivers/mtd/nand/raw/bcm47xxnflash/ops_bcm4706.c create mode 100644 drivers/mtd/nand/raw/brcmnand/Kconfig create mode 100644 drivers/mtd/nand/raw/brcmnand/Makefile create mode 100644 drivers/mtd/nand/raw/brcmnand/bcm6368_nand.c create mode 100644 drivers/mtd/nand/raw/brcmnand/bcma_nand.c create mode 100644 drivers/mtd/nand/raw/brcmnand/bcmbca_nand.c create mode 100644 drivers/mtd/nand/raw/brcmnand/brcmnand.c create mode 100644 drivers/mtd/nand/raw/brcmnand/brcmnand.h create mode 100644 drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c create mode 100644 drivers/mtd/nand/raw/brcmnand/iproc_nand.c create mode 100644 drivers/mtd/nand/raw/cadence-nand-controller.c create mode 100644 drivers/mtd/nand/raw/cafe_nand.c create mode 100644 drivers/mtd/nand/raw/cs553x_nand.c create mode 100644 drivers/mtd/nand/raw/davinci_nand.c create mode 100644 drivers/mtd/nand/raw/denali.c create mode 100644 drivers/mtd/nand/raw/denali.h create mode 100644 drivers/mtd/nand/raw/denali_dt.c create mode 100644 drivers/mtd/nand/raw/denali_pci.c create mode 100644 drivers/mtd/nand/raw/diskonchip.c create mode 100644 drivers/mtd/nand/raw/fsl_elbc_nand.c create mode 100644 drivers/mtd/nand/raw/fsl_ifc_nand.c create mode 100644 drivers/mtd/nand/raw/fsl_upm.c create mode 100644 drivers/mtd/nand/raw/fsmc_nand.c create mode 100644 drivers/mtd/nand/raw/gpio.c create mode 100644 drivers/mtd/nand/raw/gpmi-nand/Makefile create mode 100644 drivers/mtd/nand/raw/gpmi-nand/bch-regs.h create mode 100644 drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c create mode 100644 drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h create mode 100644 drivers/mtd/nand/raw/gpmi-nand/gpmi-regs.h create mode 100644 drivers/mtd/nand/raw/hisi504_nand.c create mode 100644 drivers/mtd/nand/raw/ingenic/Kconfig create mode 100644 drivers/mtd/nand/raw/ingenic/Makefile create mode 100644 drivers/mtd/nand/raw/ingenic/ingenic_ecc.c create mode 100644 drivers/mtd/nand/raw/ingenic/ingenic_ecc.h create mode 100644 drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c create mode 100644 drivers/mtd/nand/raw/ingenic/jz4725b_bch.c create mode 100644 drivers/mtd/nand/raw/ingenic/jz4740_ecc.c create mode 100644 drivers/mtd/nand/raw/ingenic/jz4780_bch.c create mode 100644 drivers/mtd/nand/raw/intel-nand-controller.c create mode 100644 drivers/mtd/nand/raw/internals.h create mode 100644 drivers/mtd/nand/raw/loongson-nand-controller.c create mode 100644 drivers/mtd/nand/raw/lpc32xx_mlc.c create mode 100644 drivers/mtd/nand/raw/lpc32xx_slc.c create mode 100644 drivers/mtd/nand/raw/marvell_nand.c create mode 100644 drivers/mtd/nand/raw/meson_nand.c create mode 100644 drivers/mtd/nand/raw/mpc5121_nfc.c create mode 100644 drivers/mtd/nand/raw/mtk_nand.c create mode 100644 drivers/mtd/nand/raw/mxc_nand.c create mode 100644 drivers/mtd/nand/raw/mxic_nand.c create mode 100644 drivers/mtd/nand/raw/nand_amd.c create mode 100644 drivers/mtd/nand/raw/nand_base.c create mode 100644 drivers/mtd/nand/raw/nand_bbt.c create mode 100644 drivers/mtd/nand/raw/nand_esmt.c create mode 100644 drivers/mtd/nand/raw/nand_hynix.c create mode 100644 drivers/mtd/nand/raw/nand_ids.c create mode 100644 drivers/mtd/nand/raw/nand_jedec.c create mode 100644 drivers/mtd/nand/raw/nand_legacy.c create mode 100644 drivers/mtd/nand/raw/nand_macronix.c create mode 100644 drivers/mtd/nand/raw/nand_micron.c create mode 100644 drivers/mtd/nand/raw/nand_onfi.c create mode 100644 drivers/mtd/nand/raw/nand_samsung.c create mode 100644 drivers/mtd/nand/raw/nand_sandisk.c create mode 100644 drivers/mtd/nand/raw/nand_timings.c create mode 100644 drivers/mtd/nand/raw/nand_toshiba.c create mode 100644 drivers/mtd/nand/raw/nandsim.c create mode 100644 drivers/mtd/nand/raw/ndfc.c create mode 100644 drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c create mode 100644 drivers/mtd/nand/raw/omap2.c create mode 100644 drivers/mtd/nand/raw/omap_elm.c create mode 100644 drivers/mtd/nand/raw/orion_nand.c create mode 100644 drivers/mtd/nand/raw/pasemi_nand.c create mode 100644 drivers/mtd/nand/raw/pl35x-nand-controller.c create mode 100644 drivers/mtd/nand/raw/plat_nand.c create mode 100644 drivers/mtd/nand/raw/qcom_nandc.c create mode 100644 drivers/mtd/nand/raw/r852.c create mode 100644 drivers/mtd/nand/raw/r852.h create mode 100644 drivers/mtd/nand/raw/renesas-nand-controller.c create mode 100644 drivers/mtd/nand/raw/rockchip-nand-controller.c create mode 100644 drivers/mtd/nand/raw/sh_flctl.c create mode 100644 drivers/mtd/nand/raw/sharpsl.c create mode 100644 drivers/mtd/nand/raw/sm_common.c create mode 100644 drivers/mtd/nand/raw/sm_common.h create mode 100644 drivers/mtd/nand/raw/socrates_nand.c create mode 100644 drivers/mtd/nand/raw/stm32_fmc2_nand.c create mode 100644 drivers/mtd/nand/raw/sunxi_nand.c create mode 100644 drivers/mtd/nand/raw/technologic-nand-controller.c create mode 100644 drivers/mtd/nand/raw/tegra_nand.c create mode 100644 drivers/mtd/nand/raw/txx9ndfmc.c create mode 100644 drivers/mtd/nand/raw/vf610_nfc.c create mode 100644 drivers/mtd/nand/raw/xway_nand.c create mode 100644 drivers/mtd/nand/spi/Kconfig create mode 100644 drivers/mtd/nand/spi/Makefile create mode 100644 drivers/mtd/nand/spi/alliancememory.c create mode 100644 drivers/mtd/nand/spi/ato.c create mode 100644 drivers/mtd/nand/spi/core.c create mode 100644 drivers/mtd/nand/spi/dosilicon.c create mode 100644 drivers/mtd/nand/spi/esmt.c create mode 100644 drivers/mtd/nand/spi/fmsh.c create mode 100644 drivers/mtd/nand/spi/foresee.c create mode 100644 drivers/mtd/nand/spi/gigadevice.c create mode 100644 drivers/mtd/nand/spi/heyangtek.c create mode 100644 drivers/mtd/nand/spi/macronix.c create mode 100644 drivers/mtd/nand/spi/micron.c create mode 100644 drivers/mtd/nand/spi/otp.c create mode 100644 drivers/mtd/nand/spi/paragon.c create mode 100644 drivers/mtd/nand/spi/skyhigh.c create mode 100644 drivers/mtd/nand/spi/toshiba.c create mode 100644 drivers/mtd/nand/spi/winbond.c create mode 100644 drivers/mtd/nand/spi/xtx.c (limited to 'drivers/mtd/nand') diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig new file mode 100644 index 000000000..1e57c8de8 --- /dev/null +++ b/drivers/mtd/nand/Kconfig @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menu "NAND" + +config MTD_NAND_CORE + tristate + +source "drivers/mtd/nand/onenand/Kconfig" +source "drivers/mtd/nand/raw/Kconfig" +source "drivers/mtd/nand/spi/Kconfig" + +menu "ECC engine support" + +config MTD_NAND_ECC + bool + select MTD_NAND_CORE + +config MTD_NAND_ECC_SW_HAMMING + bool "Software Hamming ECC engine" + default y if MTD_RAW_NAND + select MTD_NAND_ECC + help + This enables support for software Hamming error + correction. This correction can correct up to 1 bit error + per chunk and detect up to 2 bit errors. While it used to be + widely used with old parts, newer NAND chips usually require + more strength correction and in this case BCH or RS will be + preferred. + +config MTD_NAND_ECC_SW_HAMMING_SMC + bool "NAND ECC Smart Media byte order" + depends on MTD_NAND_ECC_SW_HAMMING + default n + help + Software ECC according to the Smart Media Specification. + The original Linux implementation had byte 0 and 1 swapped. + +config MTD_NAND_ECC_SW_BCH + bool "Software BCH ECC engine" + select BCH + select MTD_NAND_ECC + default n + help + This enables support for software BCH error correction. Binary BCH + codes are more powerful and cpu intensive than traditional Hamming + ECC codes. They are used with NAND devices requiring more than 1 bit + of error correction. + +config MTD_NAND_ECC_MXIC + bool "Macronix external hardware ECC engine" + depends on HAS_IOMEM + select MTD_NAND_ECC + help + This enables support for the hardware ECC engine from Macronix. + +config MTD_NAND_ECC_MEDIATEK + tristate "Mediatek hardware ECC engine" + depends on HAS_IOMEM + depends on ARCH_MEDIATEK || COMPILE_TEST + select MTD_NAND_ECC + 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 new file mode 100644 index 000000000..2e0e56267 --- /dev/null +++ b/drivers/mtd/nand/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 + +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/ +obj-y += spi/ + +nandcore-$(CONFIG_MTD_NAND_ECC) += ecc.o +nandcore-$(CONFIG_MTD_NAND_ECC_SW_HAMMING) += ecc-sw-hamming.o +nandcore-$(CONFIG_MTD_NAND_ECC_SW_BCH) += ecc-sw-bch.o +nandcore-$(CONFIG_MTD_NAND_ECC_MXIC) += ecc-mxic.o diff --git a/drivers/mtd/nand/bbt.c b/drivers/mtd/nand/bbt.c new file mode 100644 index 000000000..db4f93a90 --- /dev/null +++ b/drivers/mtd/nand/bbt.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017 Free Electrons + * + * Authors: + * Boris Brezillon + * Peter Pan + */ + +#define pr_fmt(fmt) "nand-bbt: " fmt + +#include +#include + +/** + * nanddev_bbt_init() - Initialize the BBT (Bad Block Table) + * @nand: NAND device + * + * Initialize the in-memory BBT. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int nanddev_bbt_init(struct nand_device *nand) +{ + unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS); + unsigned int nblocks = nanddev_neraseblocks(nand); + + nand->bbt.cache = bitmap_zalloc(nblocks * bits_per_block, GFP_KERNEL); + if (!nand->bbt.cache) + return -ENOMEM; + + return 0; +} +EXPORT_SYMBOL_GPL(nanddev_bbt_init); + +/** + * nanddev_bbt_cleanup() - Cleanup the BBT (Bad Block Table) + * @nand: NAND device + * + * Undoes what has been done in nanddev_bbt_init() + */ +void nanddev_bbt_cleanup(struct nand_device *nand) +{ + bitmap_free(nand->bbt.cache); +} +EXPORT_SYMBOL_GPL(nanddev_bbt_cleanup); + +/** + * nanddev_bbt_update() - Update a BBT + * @nand: nand device + * + * Update the BBT. Currently a NOP function since on-flash bbt is not yet + * supported. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int nanddev_bbt_update(struct nand_device *nand) +{ + return 0; +} +EXPORT_SYMBOL_GPL(nanddev_bbt_update); + +/** + * nanddev_bbt_get_block_status() - Return the status of an eraseblock + * @nand: nand device + * @entry: the BBT entry + * + * Return: a positive number nand_bbt_block_status status or -%ERANGE if @entry + * is bigger than the BBT size. + */ +int nanddev_bbt_get_block_status(const struct nand_device *nand, + unsigned int entry) +{ + unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS); + unsigned long *pos = nand->bbt.cache + + ((entry * bits_per_block) / BITS_PER_LONG); + unsigned int offs = (entry * bits_per_block) % BITS_PER_LONG; + unsigned long status; + + if (entry >= nanddev_neraseblocks(nand)) + return -ERANGE; + + status = pos[0] >> offs; + if (bits_per_block + offs > BITS_PER_LONG) + status |= pos[1] << (BITS_PER_LONG - offs); + + return status & GENMASK(bits_per_block - 1, 0); +} +EXPORT_SYMBOL_GPL(nanddev_bbt_get_block_status); + +/** + * nanddev_bbt_set_block_status() - Update the status of an eraseblock in the + * in-memory BBT + * @nand: nand device + * @entry: the BBT entry to update + * @status: the new status + * + * Update an entry of the in-memory BBT. If you want to push the updated BBT + * the NAND you should call nanddev_bbt_update(). + * + * Return: 0 in case of success or -%ERANGE if @entry is bigger than the BBT + * size. + */ +int nanddev_bbt_set_block_status(struct nand_device *nand, unsigned int entry, + enum nand_bbt_block_status status) +{ + unsigned int bits_per_block = fls(NAND_BBT_BLOCK_NUM_STATUS); + unsigned long *pos = nand->bbt.cache + + ((entry * bits_per_block) / BITS_PER_LONG); + unsigned int offs = (entry * bits_per_block) % BITS_PER_LONG; + unsigned long val = status & GENMASK(bits_per_block - 1, 0); + + if (entry >= nanddev_neraseblocks(nand)) + return -ERANGE; + + pos[0] &= ~GENMASK(offs + bits_per_block - 1, offs); + pos[0] |= val << offs; + + if (bits_per_block + offs > BITS_PER_LONG) { + unsigned int rbits = bits_per_block + offs - BITS_PER_LONG; + + pos[1] &= ~GENMASK(rbits - 1, 0); + pos[1] |= val >> (bits_per_block - rbits); + } + + return 0; +} +EXPORT_SYMBOL_GPL(nanddev_bbt_set_block_status); diff --git a/drivers/mtd/nand/core.c b/drivers/mtd/nand/core.c new file mode 100644 index 000000000..3e76d1277 --- /dev/null +++ b/drivers/mtd/nand/core.c @@ -0,0 +1,536 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017 Free Electrons + * + * Authors: + * Boris Brezillon + * Peter Pan + */ + +#define pr_fmt(fmt) "nand: " fmt + +#include +#include + +/** + * 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 + * + * Return: true if the block is bad, false otherwise. + */ +bool nanddev_isbad(struct nand_device *nand, const struct nand_pos *pos) +{ + if (mtd_check_expert_analysis_mode()) + return false; + + if (nanddev_bbt_is_initialized(nand)) { + unsigned int entry; + int status; + + entry = nanddev_bbt_pos_to_entry(nand, pos); + status = nanddev_bbt_get_block_status(nand, entry); + /* Lazy block status retrieval */ + if (status == NAND_BBT_BLOCK_STATUS_UNKNOWN) { + if (nand->ops->isbad(nand, pos)) + status = NAND_BBT_BLOCK_FACTORY_BAD; + else + status = NAND_BBT_BLOCK_GOOD; + + nanddev_bbt_set_block_status(nand, entry, status); + } + + if (status == NAND_BBT_BLOCK_WORN || + status == NAND_BBT_BLOCK_FACTORY_BAD) + return true; + + return false; + } + + return nand->ops->isbad(nand, pos); +} +EXPORT_SYMBOL_GPL(nanddev_isbad); + +/** + * nanddev_markbad() - Mark a block as bad + * @nand: NAND device + * @pos: position of the block to mark bad + * + * Mark a block bad. This function is updating the BBT if available and + * calls the low-level markbad hook (nand->ops->markbad()). + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int nanddev_markbad(struct nand_device *nand, const struct nand_pos *pos) +{ + struct mtd_info *mtd = nanddev_to_mtd(nand); + unsigned int entry; + int ret = 0; + + if (nanddev_isbad(nand, pos)) + return 0; + + ret = nand->ops->markbad(nand, pos); + if (ret) + pr_warn("failed to write BBM to block @%llx (err = %d)\n", + nanddev_pos_to_offs(nand, pos), ret); + + if (!nanddev_bbt_is_initialized(nand)) + goto out; + + entry = nanddev_bbt_pos_to_entry(nand, pos); + ret = nanddev_bbt_set_block_status(nand, entry, NAND_BBT_BLOCK_WORN); + if (ret) + goto out; + + ret = nanddev_bbt_update(nand); + +out: + if (!ret) + mtd->ecc_stats.badblocks++; + + return ret; +} +EXPORT_SYMBOL_GPL(nanddev_markbad); + +/** + * nanddev_isreserved() - Check whether an eraseblock is reserved or not + * @nand: NAND device + * @pos: NAND position to test + * + * Checks whether the eraseblock pointed by @pos is reserved or not. + * + * Return: true if the eraseblock is reserved, false otherwise. + */ +bool nanddev_isreserved(struct nand_device *nand, const struct nand_pos *pos) +{ + unsigned int entry; + int status; + + if (!nanddev_bbt_is_initialized(nand)) + return false; + + /* Return info from the table */ + entry = nanddev_bbt_pos_to_entry(nand, pos); + status = nanddev_bbt_get_block_status(nand, entry); + return status == NAND_BBT_BLOCK_RESERVED; +} +EXPORT_SYMBOL_GPL(nanddev_isreserved); + +/** + * nanddev_erase() - Erase a NAND portion + * @nand: NAND device + * @pos: position of the block to erase + * + * Erases the block if it's not bad. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +static int nanddev_erase(struct nand_device *nand, const struct nand_pos *pos) +{ + if (nanddev_isbad(nand, pos) || nanddev_isreserved(nand, pos)) { + pr_warn("attempt to erase a bad/reserved block @%llx\n", + nanddev_pos_to_offs(nand, pos)); + return -EIO; + } + + return nand->ops->erase(nand, pos); +} + +/** + * nanddev_mtd_erase() - Generic mtd->_erase() implementation for NAND devices + * @mtd: MTD device + * @einfo: erase request + * + * This is a simple mtd->_erase() implementation iterating over all blocks + * concerned by @einfo and calling nand->ops->erase() on each of them. + * + * Note that mtd->_erase should not be directly assigned to this helper, + * because there's no locking here. NAND specialized layers should instead + * implement there own wrapper around nanddev_mtd_erase() taking the + * appropriate lock before calling nanddev_mtd_erase(). + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int nanddev_mtd_erase(struct mtd_info *mtd, struct erase_info *einfo) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct nand_pos pos, last; + int ret; + + nanddev_offs_to_pos(nand, einfo->addr, &pos); + nanddev_offs_to_pos(nand, einfo->addr + einfo->len - 1, &last); + while (nanddev_pos_cmp(&pos, &last) <= 0) { + ret = nanddev_erase(nand, &pos); + if (ret) { + einfo->fail_addr = nanddev_pos_to_offs(nand, &pos); + + return ret; + } + + nanddev_pos_next_eraseblock(nand, &pos); + } + + return 0; +} +EXPORT_SYMBOL_GPL(nanddev_mtd_erase); + +/** + * nanddev_mtd_max_bad_blocks() - Get the maximum number of bad eraseblock on + * a specific region of the NAND device + * @mtd: MTD device + * @offs: offset of the NAND region + * @len: length of the NAND region + * + * Default implementation for mtd->_max_bad_blocks(). Only works if + * nand->memorg.max_bad_eraseblocks_per_lun is > 0. + * + * Return: a positive number encoding the maximum number of eraseblocks on a + * portion of memory, a negative error code otherwise. + */ +int nanddev_mtd_max_bad_blocks(struct mtd_info *mtd, loff_t offs, size_t len) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct nand_pos pos, end; + unsigned int max_bb = 0; + + if (!nand->memorg.max_bad_eraseblocks_per_lun) + return -ENOTSUPP; + + nanddev_offs_to_pos(nand, offs, &pos); + nanddev_offs_to_pos(nand, offs + len, &end); + + for (nanddev_offs_to_pos(nand, offs, &pos); + nanddev_pos_cmp(&pos, &end) < 0; + nanddev_pos_next_lun(nand, &pos)) + max_bb += nand->memorg.max_bad_eraseblocks_per_lun; + + return max_bb; +} +EXPORT_SYMBOL_GPL(nanddev_mtd_max_bad_blocks); + +/** + * nanddev_get_ecc_engine() - Find and get a suitable ECC engine + * @nand: NAND device + */ +static int nanddev_get_ecc_engine(struct nand_device *nand) +{ + int engine_type; + + /* Read the user desires in terms of ECC engine/configuration */ + of_get_nand_ecc_user_config(nand); + + engine_type = nand->ecc.user_conf.engine_type; + if (engine_type == NAND_ECC_ENGINE_TYPE_INVALID) + engine_type = nand->ecc.defaults.engine_type; + + switch (engine_type) { + case NAND_ECC_ENGINE_TYPE_NONE: + return 0; + case NAND_ECC_ENGINE_TYPE_SOFT: + nand->ecc.engine = nand_ecc_get_sw_engine(nand); + break; + case NAND_ECC_ENGINE_TYPE_ON_DIE: + nand->ecc.engine = nand_ecc_get_on_die_hw_engine(nand); + break; + case NAND_ECC_ENGINE_TYPE_ON_HOST: + nand->ecc.engine = nand_ecc_get_on_host_hw_engine(nand); + if (PTR_ERR(nand->ecc.engine) == -EPROBE_DEFER) + return -EPROBE_DEFER; + break; + default: + pr_err("Missing ECC engine type\n"); + } + + if (!nand->ecc.engine) + return -EINVAL; + + return 0; +} + +/** + * nanddev_put_ecc_engine() - Dettach and put the in-use ECC engine + * @nand: NAND device + */ +static int nanddev_put_ecc_engine(struct nand_device *nand) +{ + switch (nand->ecc.ctx.conf.engine_type) { + case NAND_ECC_ENGINE_TYPE_ON_HOST: + nand_ecc_put_on_host_hw_engine(nand); + break; + case NAND_ECC_ENGINE_TYPE_NONE: + case NAND_ECC_ENGINE_TYPE_SOFT: + case NAND_ECC_ENGINE_TYPE_ON_DIE: + default: + break; + } + + return 0; +} + +/** + * nanddev_find_ecc_configuration() - Find a suitable ECC configuration + * @nand: NAND device + */ +static int nanddev_find_ecc_configuration(struct nand_device *nand) +{ + int ret; + + if (!nand->ecc.engine) + return -ENOTSUPP; + + ret = nand_ecc_init_ctx(nand); + if (ret) + return ret; + + if (!nand_ecc_is_strong_enough(nand)) + pr_warn("WARNING: %s: the ECC used on your system is too weak compared to the one required by the NAND chip\n", + nand->mtd.name); + + return 0; +} + +/** + * nanddev_ecc_engine_init() - Initialize an ECC engine for the chip + * @nand: NAND device + */ +int nanddev_ecc_engine_init(struct nand_device *nand) +{ + int ret; + + /* Look for the ECC engine to use */ + ret = nanddev_get_ecc_engine(nand); + if (ret) { + if (ret != -EPROBE_DEFER) + pr_err("No ECC engine found\n"); + + return ret; + } + + /* No ECC engine requested */ + if (!nand->ecc.engine) + return 0; + + /* Configure the engine: balance user input and chip requirements */ + ret = nanddev_find_ecc_configuration(nand); + if (ret) { + pr_err("No suitable ECC configuration\n"); + nanddev_put_ecc_engine(nand); + + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(nanddev_ecc_engine_init); + +/** + * nanddev_ecc_engine_cleanup() - Cleanup ECC engine initializations + * @nand: NAND device + */ +void nanddev_ecc_engine_cleanup(struct nand_device *nand) +{ + if (nand->ecc.engine) + nand_ecc_cleanup_ctx(nand); + + nanddev_put_ecc_engine(nand); +} +EXPORT_SYMBOL_GPL(nanddev_ecc_engine_cleanup); + +/** + * nanddev_init() - Initialize a NAND device + * @nand: NAND device + * @ops: NAND device operations + * @owner: NAND device owner + * + * Initializes a NAND device object. Consistency checks are done on @ops and + * @nand->memorg. Also takes care of initializing the BBT. + * + * Return: 0 in case of success, a negative error code otherwise. + */ +int nanddev_init(struct nand_device *nand, const struct nand_ops *ops, + struct module *owner) +{ + struct mtd_info *mtd = nanddev_to_mtd(nand); + struct nand_memory_organization *memorg = nanddev_get_memorg(nand); + + if (!nand || !ops) + return -EINVAL; + + if (!ops->erase || !ops->markbad || !ops->isbad) + return -EINVAL; + + if (!memorg->bits_per_cell || !memorg->pagesize || + !memorg->pages_per_eraseblock || !memorg->eraseblocks_per_lun || + !memorg->planes_per_lun || !memorg->luns_per_target || + !memorg->ntargets) + return -EINVAL; + + nand->rowconv.eraseblock_addr_shift = + fls(memorg->pages_per_eraseblock - 1); + nand->rowconv.lun_addr_shift = fls(memorg->eraseblocks_per_lun - 1) + + nand->rowconv.eraseblock_addr_shift; + + nand->ops = ops; + + mtd->type = memorg->bits_per_cell == 1 ? + MTD_NANDFLASH : MTD_MLCNANDFLASH; + mtd->flags = MTD_CAP_NANDFLASH; + mtd->erasesize = memorg->pagesize * memorg->pages_per_eraseblock; + mtd->writesize = memorg->pagesize; + mtd->writebufsize = memorg->pagesize; + mtd->oobsize = memorg->oobsize; + mtd->size = nanddev_size(nand); + mtd->owner = owner; + + return nanddev_bbt_init(nand); +} +EXPORT_SYMBOL_GPL(nanddev_init); + +/** + * nanddev_cleanup() - Release resources allocated in nanddev_init() + * @nand: NAND device + * + * Basically undoes what has been done in nanddev_init(). + */ +void nanddev_cleanup(struct nand_device *nand) +{ + if (nanddev_bbt_is_initialized(nand)) + nanddev_bbt_cleanup(nand); +} +EXPORT_SYMBOL_GPL(nanddev_cleanup); + +MODULE_DESCRIPTION("Generic NAND framework"); +MODULE_AUTHOR("Boris Brezillon "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/ecc-mtk.c b/drivers/mtd/nand/ecc-mtk.c new file mode 100644 index 000000000..66f0985ef --- /dev/null +++ b/drivers/mtd/nand/ecc-mtk.c @@ -0,0 +1,635 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * MTK ECC controller driver. + * Copyright (C) 2016 MediaTek Inc. + * Authors: Xiaolei Li + * Jorge Ramirez-Ortiz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ECC_IDLE_MASK BIT(0) +#define ECC_IRQ_EN BIT(0) +#define ECC_PG_IRQ_SEL BIT(1) +#define ECC_OP_ENABLE (1) +#define ECC_OP_DISABLE (0) + +#define ECC_ENCCON (0x00) +#define ECC_ENCCNFG (0x04) +#define ECC_MS_SHIFT (16) +#define ECC_ENCDIADDR (0x08) +#define ECC_ENCIDLE (0x0C) +#define ECC_DECCON (0x100) +#define ECC_DECCNFG (0x104) +#define DEC_EMPTY_EN BIT(31) +#define DEC_CNFG_CORRECT (0x3 << 12) +#define ECC_DECIDLE (0x10C) +#define ECC_DECENUM0 (0x114) + +#define ECC_TIMEOUT (500000) + +#define ECC_IDLE_REG(op) ((op) == ECC_ENCODE ? ECC_ENCIDLE : ECC_DECIDLE) +#define ECC_CTL_REG(op) ((op) == ECC_ENCODE ? ECC_ENCCON : ECC_DECCON) + +#define ECC_ERRMASK_MT7622 GENMASK(4, 0) +#define ECC_ERRMASK_MT2701 GENMASK(5, 0) +#define ECC_ERRMASK_MT2712 GENMASK(6, 0) + +struct mtk_ecc_caps { + u32 err_mask; + u32 err_shift; + const u8 *ecc_strength; + const u32 *ecc_regs; + u8 num_ecc_strength; + u8 ecc_mode_shift; + u32 parity_bits; + int pg_irq_sel; +}; + +struct mtk_ecc { + struct device *dev; + const struct mtk_ecc_caps *caps; + void __iomem *regs; + struct clk *clk; + + struct completion done; + struct mutex lock; + u32 sectors; + + u8 *eccdata; +}; + +/* ecc strength that each IP supports */ +static const u8 ecc_strength_mt2701[] = { + 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36, + 40, 44, 48, 52, 56, 60 +}; + +static const u8 ecc_strength_mt2712[] = { + 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 28, 32, 36, + 40, 44, 48, 52, 56, 60, 68, 72, 80 +}; + +static const u8 ecc_strength_mt7622[] = { + 4, 6, 8, 10, 12 +}; + +static const u8 ecc_strength_mt7986[] = { + 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24 +}; + +enum mtk_ecc_regs { + ECC_ENCPAR00, + ECC_ENCIRQ_EN, + ECC_ENCIRQ_STA, + ECC_DECDONE, + ECC_DECIRQ_EN, + ECC_DECIRQ_STA, +}; + +static int mt2701_ecc_regs[] = { + [ECC_ENCPAR00] = 0x10, + [ECC_ENCIRQ_EN] = 0x80, + [ECC_ENCIRQ_STA] = 0x84, + [ECC_DECDONE] = 0x124, + [ECC_DECIRQ_EN] = 0x200, + [ECC_DECIRQ_STA] = 0x204, +}; + +static int mt2712_ecc_regs[] = { + [ECC_ENCPAR00] = 0x300, + [ECC_ENCIRQ_EN] = 0x80, + [ECC_ENCIRQ_STA] = 0x84, + [ECC_DECDONE] = 0x124, + [ECC_DECIRQ_EN] = 0x200, + [ECC_DECIRQ_STA] = 0x204, +}; + +static int mt7622_ecc_regs[] = { + [ECC_ENCPAR00] = 0x10, + [ECC_ENCIRQ_EN] = 0x30, + [ECC_ENCIRQ_STA] = 0x34, + [ECC_DECDONE] = 0x11c, + [ECC_DECIRQ_EN] = 0x140, + [ECC_DECIRQ_STA] = 0x144, +}; + +static inline int mtk_ecc_wait_idle(struct mtk_ecc *ecc, + enum mtk_ecc_operation op) +{ + struct device *dev = ecc->dev; + u32 val; + int ret; + + ret = readl_poll_timeout_atomic(ecc->regs + ECC_IDLE_REG(op), val, + val & ECC_IDLE_MASK, + 10, ECC_TIMEOUT); + 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) +{ + struct mtk_ecc *ecc = id; + u32 dec, enc; + + dec = readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECIRQ_STA]) + & ECC_IRQ_EN; + if (dec) { + dec = readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECDONE]); + if (dec & ecc->sectors) { + /* + * Clear decode IRQ status once again to ensure that + * there will be no extra IRQ. + */ + readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECIRQ_STA]); + ecc->sectors = 0; + complete(&ecc->done); + } else { + return IRQ_HANDLED; + } + } else { + enc = readl(ecc->regs + ecc->caps->ecc_regs[ECC_ENCIRQ_STA]) + & ECC_IRQ_EN; + if (enc) + complete(&ecc->done); + else + return IRQ_NONE; + } + + return IRQ_HANDLED; +} + +static int mtk_ecc_config(struct mtk_ecc *ecc, struct mtk_ecc_config *config) +{ + u32 ecc_bit, dec_sz, enc_sz; + u32 reg, i; + + for (i = 0; i < ecc->caps->num_ecc_strength; i++) { + if (ecc->caps->ecc_strength[i] == config->strength) + break; + } + + if (i == ecc->caps->num_ecc_strength) { + dev_err(ecc->dev, "invalid ecc strength %d\n", + config->strength); + return -EINVAL; + } + + ecc_bit = i; + + if (config->op == ECC_ENCODE) { + /* configure ECC encoder (in bits) */ + enc_sz = config->len << 3; + + reg = ecc_bit | (config->mode << ecc->caps->ecc_mode_shift); + reg |= (enc_sz << ECC_MS_SHIFT); + writel(reg, ecc->regs + ECC_ENCCNFG); + + if (config->mode != ECC_NFI_MODE) + writel(lower_32_bits(config->addr), + ecc->regs + ECC_ENCDIADDR); + + } else { + /* configure ECC decoder (in bits) */ + dec_sz = (config->len << 3) + + config->strength * ecc->caps->parity_bits; + + reg = ecc_bit | (config->mode << ecc->caps->ecc_mode_shift); + reg |= (dec_sz << ECC_MS_SHIFT) | DEC_CNFG_CORRECT; + reg |= DEC_EMPTY_EN; + writel(reg, ecc->regs + ECC_DECCNFG); + + if (config->sectors) + ecc->sectors = 1 << (config->sectors - 1); + } + + return 0; +} + +void mtk_ecc_get_stats(struct mtk_ecc *ecc, struct mtk_ecc_stats *stats, + int sectors) +{ + u32 offset, i, err; + u32 bitflips = 0; + + stats->corrected = 0; + stats->failed = 0; + + for (i = 0; i < sectors; i++) { + offset = (i >> 2) << 2; + err = readl(ecc->regs + ECC_DECENUM0 + offset); + err = err >> ((i % 4) * ecc->caps->err_shift); + err &= ecc->caps->err_mask; + if (err == ecc->caps->err_mask) { + /* uncorrectable errors */ + stats->failed++; + continue; + } + + stats->corrected += err; + bitflips = max_t(u32, bitflips, err); + } + + stats->bitflips = bitflips; +} +EXPORT_SYMBOL(mtk_ecc_get_stats); + +void mtk_ecc_release(struct mtk_ecc *ecc) +{ + clk_disable_unprepare(ecc->clk); + put_device(ecc->dev); +} +EXPORT_SYMBOL(mtk_ecc_release); + +static void mtk_ecc_hw_init(struct mtk_ecc *ecc) +{ + mtk_ecc_wait_idle(ecc, ECC_ENCODE); + writew(ECC_OP_DISABLE, ecc->regs + ECC_ENCCON); + + mtk_ecc_wait_idle(ecc, ECC_DECODE); + writel(ECC_OP_DISABLE, ecc->regs + ECC_DECCON); +} + +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) + return ERR_PTR(-EPROBE_DEFER); + + ecc = platform_get_drvdata(pdev); + if (!ecc) { + put_device(&pdev->dev); + return ERR_PTR(-EPROBE_DEFER); + } + + ret = clk_prepare_enable(ecc->clk); + if (ret) { + put_device(&pdev->dev); + return ERR_PTR(ret); + } + + mtk_ecc_hw_init(ecc); + + return ecc; +} + +struct mtk_ecc *of_mtk_ecc_get(struct device_node *of_node) +{ + struct mtk_ecc *ecc = NULL; + struct device_node *np; + + np = of_parse_phandle(of_node, "nand-ecc-engine", 0); + /* for backward compatibility */ + if (!np) + np = of_parse_phandle(of_node, "ecc-engine", 0); + if (np) { + ecc = mtk_ecc_get(np); + of_node_put(np); + } + + return ecc; +} +EXPORT_SYMBOL(of_mtk_ecc_get); + +int mtk_ecc_enable(struct mtk_ecc *ecc, struct mtk_ecc_config *config) +{ + enum mtk_ecc_operation op = config->op; + u16 reg_val; + int ret; + + ret = mutex_lock_interruptible(&ecc->lock); + if (ret) { + dev_err(ecc->dev, "interrupted when attempting to lock\n"); + return ret; + } + + ret = mtk_ecc_wait_idle(ecc, op); + if (ret) { + mutex_unlock(&ecc->lock); + return ret; + } + + ret = mtk_ecc_config(ecc, config); + if (ret) { + mutex_unlock(&ecc->lock); + return ret; + } + + if (config->mode != ECC_NFI_MODE || op != ECC_ENCODE) { + init_completion(&ecc->done); + reg_val = ECC_IRQ_EN; + /* + * For ECC_NFI_MODE, if ecc->caps->pg_irq_sel is 1, then it + * means this chip can only generate one ecc irq during page + * read / write. If is 0, generate one ecc irq each ecc step. + */ + if (ecc->caps->pg_irq_sel && config->mode == ECC_NFI_MODE) + reg_val |= ECC_PG_IRQ_SEL; + if (op == ECC_ENCODE) + writew(reg_val, ecc->regs + + ecc->caps->ecc_regs[ECC_ENCIRQ_EN]); + else + writew(reg_val, ecc->regs + + ecc->caps->ecc_regs[ECC_DECIRQ_EN]); + } + + writew(ECC_OP_ENABLE, ecc->regs + ECC_CTL_REG(op)); + + return 0; +} +EXPORT_SYMBOL(mtk_ecc_enable); + +void mtk_ecc_disable(struct mtk_ecc *ecc) +{ + enum mtk_ecc_operation op = ECC_ENCODE; + + /* find out the running operation */ + if (readw(ecc->regs + ECC_CTL_REG(op)) != ECC_OP_ENABLE) + op = ECC_DECODE; + + /* disable it */ + mtk_ecc_wait_idle(ecc, op); + if (op == ECC_DECODE) { + /* + * Clear decode IRQ status in case there is a timeout to wait + * decode IRQ. + */ + readw(ecc->regs + ecc->caps->ecc_regs[ECC_DECDONE]); + writew(0, ecc->regs + ecc->caps->ecc_regs[ECC_DECIRQ_EN]); + } else { + writew(0, ecc->regs + ecc->caps->ecc_regs[ECC_ENCIRQ_EN]); + } + + writew(ECC_OP_DISABLE, ecc->regs + ECC_CTL_REG(op)); + + mutex_unlock(&ecc->lock); +} +EXPORT_SYMBOL(mtk_ecc_disable); + +int mtk_ecc_wait_done(struct mtk_ecc *ecc, enum mtk_ecc_operation op) +{ + int ret; + + ret = wait_for_completion_timeout(&ecc->done, msecs_to_jiffies(500)); + if (!ret) { + dev_err(ecc->dev, "%s timeout - interrupt did not arrive)\n", + (op == ECC_ENCODE) ? "encoder" : "decoder"); + return -ETIMEDOUT; + } + + return 0; +} +EXPORT_SYMBOL(mtk_ecc_wait_done); + +int mtk_ecc_encode(struct mtk_ecc *ecc, struct mtk_ecc_config *config, + u8 *data, u32 bytes) +{ + dma_addr_t addr; + u32 len; + int ret; + + addr = dma_map_single(ecc->dev, data, bytes, DMA_TO_DEVICE); + ret = dma_mapping_error(ecc->dev, addr); + if (ret) { + dev_err(ecc->dev, "dma mapping error\n"); + return -EINVAL; + } + + config->op = ECC_ENCODE; + config->addr = addr; + ret = mtk_ecc_enable(ecc, config); + if (ret) { + dma_unmap_single(ecc->dev, addr, bytes, DMA_TO_DEVICE); + return ret; + } + + ret = mtk_ecc_wait_done(ecc, ECC_ENCODE); + if (ret) + goto timeout; + + 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; + + /* write the parity bytes generated by the ECC back to temp buffer */ + __ioread32_copy(ecc->eccdata, + ecc->regs + ecc->caps->ecc_regs[ECC_ENCPAR00], + round_up(len, 4)); + + /* copy into possibly unaligned OOB region with actual length */ + memcpy(data + bytes, ecc->eccdata, len); +timeout: + + dma_unmap_single(ecc->dev, addr, bytes, DMA_TO_DEVICE); + mtk_ecc_disable(ecc); + + return ret; +} +EXPORT_SYMBOL(mtk_ecc_encode); + +void mtk_ecc_adjust_strength(struct mtk_ecc *ecc, u32 *p) +{ + const u8 *ecc_strength = ecc->caps->ecc_strength; + int i; + + for (i = 0; i < ecc->caps->num_ecc_strength; i++) { + if (*p <= ecc_strength[i]) { + if (!i) + *p = ecc_strength[i]; + else if (*p != ecc_strength[i]) + *p = ecc_strength[i - 1]; + return; + } + } + + *p = ecc_strength[ecc->caps->num_ecc_strength - 1]; +} +EXPORT_SYMBOL(mtk_ecc_adjust_strength); + +unsigned int mtk_ecc_get_parity_bits(struct mtk_ecc *ecc) +{ + return ecc->caps->parity_bits; +} +EXPORT_SYMBOL(mtk_ecc_get_parity_bits); + +static const struct mtk_ecc_caps mtk_ecc_caps_mt2701 = { + .err_mask = ECC_ERRMASK_MT2701, + .err_shift = 8, + .ecc_strength = ecc_strength_mt2701, + .ecc_regs = mt2701_ecc_regs, + .num_ecc_strength = 20, + .ecc_mode_shift = 5, + .parity_bits = 14, + .pg_irq_sel = 0, +}; + +static const struct mtk_ecc_caps mtk_ecc_caps_mt2712 = { + .err_mask = ECC_ERRMASK_MT2712, + .err_shift = 8, + .ecc_strength = ecc_strength_mt2712, + .ecc_regs = mt2712_ecc_regs, + .num_ecc_strength = 23, + .ecc_mode_shift = 5, + .parity_bits = 14, + .pg_irq_sel = 1, +}; + +static const struct mtk_ecc_caps mtk_ecc_caps_mt7622 = { + .err_mask = ECC_ERRMASK_MT7622, + .err_shift = 5, + .ecc_strength = ecc_strength_mt7622, + .ecc_regs = mt7622_ecc_regs, + .num_ecc_strength = 5, + .ecc_mode_shift = 4, + .parity_bits = 13, + .pg_irq_sel = 0, +}; + +static const struct mtk_ecc_caps mtk_ecc_caps_mt7986 = { + .err_mask = ECC_ERRMASK_MT7622, + .err_shift = 8, + .ecc_strength = ecc_strength_mt7986, + .ecc_regs = mt2712_ecc_regs, + .num_ecc_strength = 11, + .ecc_mode_shift = 5, + .parity_bits = 14, + .pg_irq_sel = 1, +}; + +static const struct of_device_id mtk_ecc_dt_match[] = { + { + .compatible = "mediatek,mt2701-ecc", + .data = &mtk_ecc_caps_mt2701, + }, { + .compatible = "mediatek,mt2712-ecc", + .data = &mtk_ecc_caps_mt2712, + }, { + .compatible = "mediatek,mt7622-ecc", + .data = &mtk_ecc_caps_mt7622, + }, { + .compatible = "mediatek,mt7986-ecc", + .data = &mtk_ecc_caps_mt7986, + }, + {}, +}; + +static int mtk_ecc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_ecc *ecc; + u32 max_eccdata_size; + int irq, ret; + + ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL); + if (!ecc) + return -ENOMEM; + + ecc->caps = of_device_get_match_data(dev); + + max_eccdata_size = ecc->caps->num_ecc_strength - 1; + max_eccdata_size = ecc->caps->ecc_strength[max_eccdata_size]; + max_eccdata_size = (max_eccdata_size * ecc->caps->parity_bits + 7) >> 3; + max_eccdata_size = round_up(max_eccdata_size, 4); + ecc->eccdata = devm_kzalloc(dev, max_eccdata_size, GFP_KERNEL); + if (!ecc->eccdata) + return -ENOMEM; + + ecc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ecc->regs)) + return PTR_ERR(ecc->regs); + + ecc->clk = devm_clk_get(dev, NULL); + if (IS_ERR(ecc->clk)) { + dev_err(dev, "failed to get clock: %ld\n", PTR_ERR(ecc->clk)); + return PTR_ERR(ecc->clk); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = dma_set_mask(dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(dev, "failed to set DMA mask\n"); + return ret; + } + + ret = devm_request_irq(dev, irq, mtk_ecc_irq, 0x0, "mtk-ecc", ecc); + if (ret) { + dev_err(dev, "failed to request irq\n"); + return -EINVAL; + } + + ecc->dev = dev; + mutex_init(&ecc->lock); + platform_set_drvdata(pdev, ecc); + dev_info(dev, "probed\n"); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int mtk_ecc_suspend(struct device *dev) +{ + struct mtk_ecc *ecc = dev_get_drvdata(dev); + + clk_disable_unprepare(ecc->clk); + + return 0; +} + +static int mtk_ecc_resume(struct device *dev) +{ + struct mtk_ecc *ecc = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(ecc->clk); + if (ret) { + dev_err(dev, "failed to enable clk\n"); + return ret; + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(mtk_ecc_pm_ops, mtk_ecc_suspend, mtk_ecc_resume); +#endif + +MODULE_DEVICE_TABLE(of, mtk_ecc_dt_match); + +static struct platform_driver mtk_ecc_driver = { + .probe = mtk_ecc_probe, + .driver = { + .name = "mtk-ecc", + .of_match_table = mtk_ecc_dt_match, +#ifdef CONFIG_PM_SLEEP + .pm = &mtk_ecc_pm_ops, +#endif + }, +}; + +module_platform_driver(mtk_ecc_driver); + +MODULE_AUTHOR("Xiaolei Li "); +MODULE_DESCRIPTION("MTK Nand ECC Driver"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/mtd/nand/ecc-mxic.c b/drivers/mtd/nand/ecc-mxic.c new file mode 100644 index 000000000..60cdcb417 --- /dev/null +++ b/drivers/mtd/nand/ecc-mxic.c @@ -0,0 +1,878 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Support for Macronix external hardware ECC engine for NAND devices, also + * called DPE for Data Processing Engine. + * + * Copyright © 2019 Macronix + * Author: Miquel Raynal + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* DPE Configuration */ +#define DP_CONFIG 0x00 +#define ECC_EN BIT(0) +#define ECC_TYP(idx) (((idx) << 3) & GENMASK(6, 3)) +/* DPE Interrupt Status */ +#define INTRPT_STS 0x04 +#define TRANS_CMPLT BIT(0) +#define SDMA_MAIN BIT(1) +#define SDMA_SPARE BIT(2) +#define ECC_ERR BIT(3) +#define TO_SPARE BIT(4) +#define TO_MAIN BIT(5) +/* DPE Interrupt Status Enable */ +#define INTRPT_STS_EN 0x08 +/* DPE Interrupt Signal Enable */ +#define INTRPT_SIG_EN 0x0C +/* Host Controller Configuration */ +#define HC_CONFIG 0x10 +#define DEV2MEM 0 /* TRANS_TYP_DMA in the spec */ +#define MEM2MEM BIT(4) /* TRANS_TYP_IO in the spec */ +#define MAPPING BIT(5) /* TRANS_TYP_MAPPING in the spec */ +#define ECC_PACKED 0 /* LAYOUT_TYP_INTEGRATED in the spec */ +#define ECC_INTERLEAVED BIT(2) /* LAYOUT_TYP_DISTRIBUTED in the spec */ +#define BURST_TYP_FIXED 0 +#define BURST_TYP_INCREASING BIT(0) +/* Host Controller Slave Address */ +#define HC_SLV_ADDR 0x14 +/* ECC Chunk Size */ +#define CHUNK_SIZE 0x20 +/* Main Data Size */ +#define MAIN_SIZE 0x24 +/* Spare Data Size */ +#define SPARE_SIZE 0x28 +#define META_SZ(reg) ((reg) & GENMASK(7, 0)) +#define PARITY_SZ(reg) (((reg) & GENMASK(15, 8)) >> 8) +#define RSV_SZ(reg) (((reg) & GENMASK(23, 16)) >> 16) +#define SPARE_SZ(reg) ((reg) >> 24) +/* ECC Chunk Count */ +#define CHUNK_CNT 0x30 +/* SDMA Control */ +#define SDMA_CTRL 0x40 +#define WRITE_NAND 0 +#define READ_NAND BIT(1) +#define CONT_NAND BIT(29) +#define CONT_SYSM BIT(30) /* Continue System Memory? */ +#define SDMA_STRT BIT(31) +/* SDMA Address of Main Data */ +#define SDMA_MAIN_ADDR 0x44 +/* SDMA Address of Spare Data */ +#define SDMA_SPARE_ADDR 0x48 +/* DPE Version Number */ +#define DP_VER 0xD0 +#define DP_VER_OFFSET 16 + +/* Status bytes between each chunk of spare data */ +#define STAT_BYTES 4 +#define NO_ERR 0x00 +#define MAX_CORR_ERR 0x28 +#define UNCORR_ERR 0xFE +#define ERASED_CHUNK 0xFF + +struct mxic_ecc_engine { + struct device *dev; + void __iomem *regs; + int irq; + struct completion complete; + struct nand_ecc_engine external_engine; + struct nand_ecc_engine pipelined_engine; + struct mutex lock; +}; + +struct mxic_ecc_ctx { + /* ECC machinery */ + unsigned int data_step_sz; + unsigned int oob_step_sz; + unsigned int parity_sz; + unsigned int meta_sz; + u8 *status; + int steps; + + /* DMA boilerplate */ + struct nand_ecc_req_tweak_ctx req_ctx; + u8 *oobwithstat; + struct scatterlist sg[2]; + struct nand_page_io_req *req; + unsigned int pageoffs; +}; + +static struct mxic_ecc_engine *ext_ecc_eng_to_mxic(struct nand_ecc_engine *eng) +{ + return container_of(eng, struct mxic_ecc_engine, external_engine); +} + +static struct mxic_ecc_engine *pip_ecc_eng_to_mxic(struct nand_ecc_engine *eng) +{ + return container_of(eng, struct mxic_ecc_engine, pipelined_engine); +} + +static struct mxic_ecc_engine *nand_to_mxic(struct nand_device *nand) +{ + struct nand_ecc_engine *eng = nand->ecc.engine; + + if (eng->integration == NAND_ECC_ENGINE_INTEGRATION_EXTERNAL) + return ext_ecc_eng_to_mxic(eng); + else + return pip_ecc_eng_to_mxic(eng); +} + +static int mxic_ecc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand); + + if (section < 0 || section >= ctx->steps) + return -ERANGE; + + oobregion->offset = (section * ctx->oob_step_sz) + ctx->meta_sz; + oobregion->length = ctx->parity_sz; + + return 0; +} + +static int mxic_ecc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand); + + if (section < 0 || section >= ctx->steps) + return -ERANGE; + + if (!section) { + oobregion->offset = 2; + oobregion->length = ctx->meta_sz - 2; + } else { + oobregion->offset = section * ctx->oob_step_sz; + oobregion->length = ctx->meta_sz; + } + + return 0; +} + +static const struct mtd_ooblayout_ops mxic_ecc_ooblayout_ops = { + .ecc = mxic_ecc_ooblayout_ecc, + .free = mxic_ecc_ooblayout_free, +}; + +static void mxic_ecc_disable_engine(struct mxic_ecc_engine *mxic) +{ + u32 reg; + + reg = readl(mxic->regs + DP_CONFIG); + reg &= ~ECC_EN; + writel(reg, mxic->regs + DP_CONFIG); +} + +static void mxic_ecc_enable_engine(struct mxic_ecc_engine *mxic) +{ + u32 reg; + + reg = readl(mxic->regs + DP_CONFIG); + reg |= ECC_EN; + writel(reg, mxic->regs + DP_CONFIG); +} + +static void mxic_ecc_disable_int(struct mxic_ecc_engine *mxic) +{ + writel(0, mxic->regs + INTRPT_SIG_EN); +} + +static void mxic_ecc_enable_int(struct mxic_ecc_engine *mxic) +{ + writel(TRANS_CMPLT, mxic->regs + INTRPT_SIG_EN); +} + +static irqreturn_t mxic_ecc_isr(int irq, void *dev_id) +{ + struct mxic_ecc_engine *mxic = dev_id; + u32 sts; + + sts = readl(mxic->regs + INTRPT_STS); + if (!sts) + return IRQ_NONE; + + if (sts & TRANS_CMPLT) + complete(&mxic->complete); + + writel(sts, mxic->regs + INTRPT_STS); + + return IRQ_HANDLED; +} + +static int mxic_ecc_init_ctx(struct nand_device *nand, struct device *dev) +{ + struct mxic_ecc_engine *mxic = nand_to_mxic(nand); + struct nand_ecc_props *conf = &nand->ecc.ctx.conf; + struct nand_ecc_props *reqs = &nand->ecc.requirements; + struct nand_ecc_props *user = &nand->ecc.user_conf; + struct mtd_info *mtd = nanddev_to_mtd(nand); + int step_size = 0, strength = 0, desired_correction = 0, steps, idx; + static const int possible_strength[] = {4, 8, 40, 48}; + static const int spare_size[] = {32, 32, 96, 96}; + struct mxic_ecc_ctx *ctx; + u32 spare_reg; + int ret; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + nand->ecc.ctx.priv = ctx; + + /* Only large page NAND chips may use BCH */ + if (mtd->oobsize < 64) { + pr_err("BCH cannot be used with small page NAND chips\n"); + return -EINVAL; + } + + mtd_set_ooblayout(mtd, &mxic_ecc_ooblayout_ops); + + /* Enable all status bits */ + writel(TRANS_CMPLT | SDMA_MAIN | SDMA_SPARE | ECC_ERR | + TO_SPARE | TO_MAIN, mxic->regs + INTRPT_STS_EN); + + /* Configure the correction depending on the NAND device topology */ + if (user->step_size && user->strength) { + step_size = user->step_size; + strength = user->strength; + } else if (reqs->step_size && reqs->strength) { + step_size = reqs->step_size; + strength = reqs->strength; + } + + if (step_size && strength) { + steps = mtd->writesize / step_size; + desired_correction = steps * strength; + } + + /* Step size is fixed to 1kiB, strength may vary (4 possible values) */ + conf->step_size = SZ_1K; + steps = mtd->writesize / conf->step_size; + + ctx->status = devm_kzalloc(dev, steps * sizeof(u8), GFP_KERNEL); + if (!ctx->status) + return -ENOMEM; + + if (desired_correction) { + strength = desired_correction / steps; + + for (idx = 0; idx < ARRAY_SIZE(possible_strength); idx++) + if (possible_strength[idx] >= strength) + break; + + idx = min_t(unsigned int, idx, + ARRAY_SIZE(possible_strength) - 1); + } else { + /* Missing data, maximize the correction */ + idx = ARRAY_SIZE(possible_strength) - 1; + } + + /* Tune the selected strength until it fits in the OOB area */ + for (; idx >= 0; idx--) { + if (spare_size[idx] * steps <= mtd->oobsize) + break; + } + + /* This engine cannot be used with this NAND device */ + if (idx < 0) + return -EINVAL; + + /* Configure the engine for the desired strength */ + writel(ECC_TYP(idx), mxic->regs + DP_CONFIG); + conf->strength = possible_strength[idx]; + spare_reg = readl(mxic->regs + SPARE_SIZE); + + ctx->steps = steps; + ctx->data_step_sz = mtd->writesize / steps; + ctx->oob_step_sz = mtd->oobsize / steps; + ctx->parity_sz = PARITY_SZ(spare_reg); + ctx->meta_sz = META_SZ(spare_reg); + + /* Ensure buffers will contain enough bytes to store the STAT_BYTES */ + ctx->req_ctx.oob_buffer_size = nanddev_per_page_oobsize(nand) + + (ctx->steps * STAT_BYTES); + ret = nand_ecc_init_req_tweaking(&ctx->req_ctx, nand); + if (ret) + return ret; + + ctx->oobwithstat = kmalloc(mtd->oobsize + (ctx->steps * STAT_BYTES), + GFP_KERNEL); + if (!ctx->oobwithstat) { + ret = -ENOMEM; + goto cleanup_req_tweak; + } + + sg_init_table(ctx->sg, 2); + + /* Configuration dump and sanity checks */ + dev_dbg(dev, "DPE version number: %d\n", + readl(mxic->regs + DP_VER) >> DP_VER_OFFSET); + 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)) { + dev_err(dev, "Wrong OOB configuration: %d + %d + %ld != %d\n", + ctx->meta_sz, ctx->parity_sz, RSV_SZ(spare_reg), + SPARE_SZ(spare_reg)); + ret = -EINVAL; + goto free_oobwithstat; + } + + if (ctx->oob_step_sz != SPARE_SZ(spare_reg)) { + dev_err(dev, "Wrong OOB configuration: %d != %d\n", + ctx->oob_step_sz, SPARE_SZ(spare_reg)); + ret = -EINVAL; + goto free_oobwithstat; + } + + return 0; + +free_oobwithstat: + kfree(ctx->oobwithstat); +cleanup_req_tweak: + nand_ecc_cleanup_req_tweaking(&ctx->req_ctx); + + return ret; +} + +static int mxic_ecc_init_ctx_external(struct nand_device *nand) +{ + struct mxic_ecc_engine *mxic = nand_to_mxic(nand); + struct device *dev = nand->ecc.engine->dev; + int ret; + + dev_info(dev, "Macronix ECC engine in external mode\n"); + + ret = mxic_ecc_init_ctx(nand, dev); + if (ret) + return ret; + + /* Trigger each step manually */ + writel(1, mxic->regs + CHUNK_CNT); + writel(BURST_TYP_INCREASING | ECC_PACKED | MEM2MEM, + mxic->regs + HC_CONFIG); + + return 0; +} + +static int mxic_ecc_init_ctx_pipelined(struct nand_device *nand) +{ + struct mxic_ecc_engine *mxic = nand_to_mxic(nand); + struct mxic_ecc_ctx *ctx; + struct device *dev; + int ret; + + dev = nand_ecc_get_engine_dev(nand->ecc.engine->dev); + if (!dev) + return -EINVAL; + + dev_info(dev, "Macronix ECC engine in pipelined/mapping mode\n"); + + ret = mxic_ecc_init_ctx(nand, dev); + if (ret) + return ret; + + ctx = nand_to_ecc_ctx(nand); + + /* All steps should be handled in one go directly by the internal DMA */ + writel(ctx->steps, mxic->regs + CHUNK_CNT); + + /* + * Interleaved ECC scheme cannot be used otherwise factory bad block + * markers would be lost. A packed layout is mandatory. + */ + writel(BURST_TYP_INCREASING | ECC_PACKED | MAPPING, + mxic->regs + HC_CONFIG); + + return 0; +} + +static void mxic_ecc_cleanup_ctx(struct nand_device *nand) +{ + struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand); + + if (ctx) { + nand_ecc_cleanup_req_tweaking(&ctx->req_ctx); + kfree(ctx->oobwithstat); + } +} + +static int mxic_ecc_data_xfer_wait_for_completion(struct mxic_ecc_engine *mxic) +{ + u32 val; + int ret; + + if (mxic->irq) { + reinit_completion(&mxic->complete); + mxic_ecc_enable_int(mxic); + ret = wait_for_completion_timeout(&mxic->complete, + msecs_to_jiffies(1000)); + ret = ret ? 0 : -ETIMEDOUT; + mxic_ecc_disable_int(mxic); + } else { + ret = readl_poll_timeout(mxic->regs + INTRPT_STS, val, + val & TRANS_CMPLT, 10, USEC_PER_SEC); + writel(val, mxic->regs + INTRPT_STS); + } + + if (ret) { + dev_err(mxic->dev, "Timeout on data xfer completion\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int mxic_ecc_process_data(struct mxic_ecc_engine *mxic, + unsigned int direction) +{ + unsigned int dir = (direction == NAND_PAGE_READ) ? + READ_NAND : WRITE_NAND; + int ret; + + mxic_ecc_enable_engine(mxic); + + /* Trigger processing */ + writel(SDMA_STRT | dir, mxic->regs + SDMA_CTRL); + + /* Wait for completion */ + ret = mxic_ecc_data_xfer_wait_for_completion(mxic); + + mxic_ecc_disable_engine(mxic); + + return ret; +} + +int mxic_ecc_process_data_pipelined(struct nand_ecc_engine *eng, + unsigned int direction, dma_addr_t dirmap) +{ + struct mxic_ecc_engine *mxic = pip_ecc_eng_to_mxic(eng); + + if (dirmap) + writel(dirmap, mxic->regs + HC_SLV_ADDR); + + return mxic_ecc_process_data(mxic, direction); +} +EXPORT_SYMBOL_GPL(mxic_ecc_process_data_pipelined); + +static void mxic_ecc_extract_status_bytes(struct mxic_ecc_ctx *ctx) +{ + u8 *buf = ctx->oobwithstat; + int next_stat_pos; + int step; + + /* Extract the ECC status */ + for (step = 0; step < ctx->steps; step++) { + next_stat_pos = ctx->oob_step_sz + + ((STAT_BYTES + ctx->oob_step_sz) * step); + + ctx->status[step] = buf[next_stat_pos]; + } +} + +static void mxic_ecc_reconstruct_oobbuf(struct mxic_ecc_ctx *ctx, + u8 *dst, const u8 *src) +{ + int step; + + /* Reconstruct the OOB buffer linearly (without the ECC status bytes) */ + for (step = 0; step < ctx->steps; step++) + memcpy(dst + (step * ctx->oob_step_sz), + src + (step * (ctx->oob_step_sz + STAT_BYTES)), + ctx->oob_step_sz); +} + +static void mxic_ecc_add_room_in_oobbuf(struct mxic_ecc_ctx *ctx, + u8 *dst, const u8 *src) +{ + int step; + + /* Add some space in the OOB buffer for the status bytes */ + for (step = 0; step < ctx->steps; step++) + memcpy(dst + (step * (ctx->oob_step_sz + STAT_BYTES)), + src + (step * ctx->oob_step_sz), + ctx->oob_step_sz); +} + +static int mxic_ecc_count_biterrs(struct mxic_ecc_engine *mxic, + struct nand_device *nand) +{ + struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand); + struct mtd_info *mtd = nanddev_to_mtd(nand); + struct device *dev = mxic->dev; + unsigned int max_bf = 0; + bool failure = false; + int step; + + for (step = 0; step < ctx->steps; step++) { + u8 stat = ctx->status[step]; + + if (stat == NO_ERR) { + dev_dbg(dev, "ECC step %d: no error\n", step); + } else if (stat == ERASED_CHUNK) { + dev_dbg(dev, "ECC step %d: erased\n", step); + } else if (stat == UNCORR_ERR || stat > MAX_CORR_ERR) { + dev_dbg(dev, "ECC step %d: uncorrectable\n", step); + mtd->ecc_stats.failed++; + failure = true; + } else { + dev_dbg(dev, "ECC step %d: %d bits corrected\n", + step, stat); + max_bf = max_t(unsigned int, max_bf, stat); + mtd->ecc_stats.corrected += stat; + } + } + + return failure ? -EBADMSG : max_bf; +} + +/* External ECC engine helpers */ +static int mxic_ecc_prepare_io_req_external(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct mxic_ecc_engine *mxic = nand_to_mxic(nand); + struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand); + struct mtd_info *mtd = nanddev_to_mtd(nand); + int offset, nents, step, ret; + + if (req->mode == MTD_OPS_RAW) + return 0; + + nand_ecc_tweak_req(&ctx->req_ctx, req); + ctx->req = req; + + if (req->type == NAND_PAGE_READ) + return 0; + + mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat, + ctx->req->oobbuf.out); + + sg_set_buf(&ctx->sg[0], req->databuf.out, req->datalen); + sg_set_buf(&ctx->sg[1], ctx->oobwithstat, + req->ooblen + (ctx->steps * STAT_BYTES)); + + nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL); + if (!nents) + return -EINVAL; + + mutex_lock(&mxic->lock); + + for (step = 0; step < ctx->steps; step++) { + writel(sg_dma_address(&ctx->sg[0]) + (step * ctx->data_step_sz), + mxic->regs + SDMA_MAIN_ADDR); + writel(sg_dma_address(&ctx->sg[1]) + (step * (ctx->oob_step_sz + STAT_BYTES)), + mxic->regs + SDMA_SPARE_ADDR); + ret = mxic_ecc_process_data(mxic, ctx->req->type); + if (ret) + break; + } + + mutex_unlock(&mxic->lock); + + dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL); + + if (ret) + return ret; + + /* Retrieve the calculated ECC bytes */ + for (step = 0; step < ctx->steps; step++) { + offset = ctx->meta_sz + (step * ctx->oob_step_sz); + mtd_ooblayout_get_eccbytes(mtd, + (u8 *)ctx->req->oobbuf.out + offset, + ctx->oobwithstat + (step * STAT_BYTES), + step * ctx->parity_sz, + ctx->parity_sz); + } + + return 0; +} + +static int mxic_ecc_finish_io_req_external(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct mxic_ecc_engine *mxic = nand_to_mxic(nand); + struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand); + int nents, step, ret = 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; + } + + /* Copy the OOB buffer and add room for the ECC engine status bytes */ + mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat, ctx->req->oobbuf.in); + + sg_set_buf(&ctx->sg[0], req->databuf.in, req->datalen); + sg_set_buf(&ctx->sg[1], ctx->oobwithstat, + req->ooblen + (ctx->steps * STAT_BYTES)); + nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL); + if (!nents) + return -EINVAL; + + mutex_lock(&mxic->lock); + + for (step = 0; step < ctx->steps; step++) { + writel(sg_dma_address(&ctx->sg[0]) + (step * ctx->data_step_sz), + mxic->regs + SDMA_MAIN_ADDR); + writel(sg_dma_address(&ctx->sg[1]) + (step * (ctx->oob_step_sz + STAT_BYTES)), + mxic->regs + SDMA_SPARE_ADDR); + ret = mxic_ecc_process_data(mxic, ctx->req->type); + if (ret) + break; + } + + mutex_unlock(&mxic->lock); + + dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL); + + if (ret) { + nand_ecc_restore_req(&ctx->req_ctx, req); + return ret; + } + + /* Extract the status bytes and reconstruct the buffer */ + mxic_ecc_extract_status_bytes(ctx); + mxic_ecc_reconstruct_oobbuf(ctx, ctx->req->oobbuf.in, ctx->oobwithstat); + + nand_ecc_restore_req(&ctx->req_ctx, req); + + return mxic_ecc_count_biterrs(mxic, nand); +} + +/* Pipelined ECC engine helpers */ +static int mxic_ecc_prepare_io_req_pipelined(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct mxic_ecc_engine *mxic = nand_to_mxic(nand); + struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand); + int nents; + + if (req->mode == MTD_OPS_RAW) + return 0; + + nand_ecc_tweak_req(&ctx->req_ctx, req); + ctx->req = req; + + /* Copy the OOB buffer and add room for the ECC engine status bytes */ + mxic_ecc_add_room_in_oobbuf(ctx, ctx->oobwithstat, ctx->req->oobbuf.in); + + sg_set_buf(&ctx->sg[0], req->databuf.in, req->datalen); + sg_set_buf(&ctx->sg[1], ctx->oobwithstat, + req->ooblen + (ctx->steps * STAT_BYTES)); + + nents = dma_map_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL); + if (!nents) + return -EINVAL; + + mutex_lock(&mxic->lock); + + writel(sg_dma_address(&ctx->sg[0]), mxic->regs + SDMA_MAIN_ADDR); + writel(sg_dma_address(&ctx->sg[1]), mxic->regs + SDMA_SPARE_ADDR); + + return 0; +} + +static int mxic_ecc_finish_io_req_pipelined(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct mxic_ecc_engine *mxic = nand_to_mxic(nand); + struct mxic_ecc_ctx *ctx = nand_to_ecc_ctx(nand); + int ret = 0; + + if (req->mode == MTD_OPS_RAW) + return 0; + + mutex_unlock(&mxic->lock); + + dma_unmap_sg(mxic->dev, ctx->sg, 2, DMA_BIDIRECTIONAL); + + if (req->type == NAND_PAGE_READ) { + mxic_ecc_extract_status_bytes(ctx); + mxic_ecc_reconstruct_oobbuf(ctx, ctx->req->oobbuf.in, + ctx->oobwithstat); + ret = mxic_ecc_count_biterrs(mxic, nand); + } + + nand_ecc_restore_req(&ctx->req_ctx, req); + + return ret; +} + +static const struct nand_ecc_engine_ops mxic_ecc_engine_external_ops = { + .init_ctx = mxic_ecc_init_ctx_external, + .cleanup_ctx = mxic_ecc_cleanup_ctx, + .prepare_io_req = mxic_ecc_prepare_io_req_external, + .finish_io_req = mxic_ecc_finish_io_req_external, +}; + +static const struct nand_ecc_engine_ops mxic_ecc_engine_pipelined_ops = { + .init_ctx = mxic_ecc_init_ctx_pipelined, + .cleanup_ctx = mxic_ecc_cleanup_ctx, + .prepare_io_req = mxic_ecc_prepare_io_req_pipelined, + .finish_io_req = mxic_ecc_finish_io_req_pipelined, +}; + +const struct nand_ecc_engine_ops *mxic_ecc_get_pipelined_ops(void) +{ + return &mxic_ecc_engine_pipelined_ops; +} +EXPORT_SYMBOL_GPL(mxic_ecc_get_pipelined_ops); + +static struct platform_device * +mxic_ecc_get_pdev(struct platform_device *spi_pdev) +{ + struct platform_device *eng_pdev; + struct device_node *np; + + /* Retrieve the nand-ecc-engine phandle */ + np = of_parse_phandle(spi_pdev->dev.of_node, "nand-ecc-engine", 0); + if (!np) + return NULL; + + /* Jump to the engine's device node */ + eng_pdev = of_find_device_by_node(np); + of_node_put(np); + + return eng_pdev; +} + +void mxic_ecc_put_pipelined_engine(struct nand_ecc_engine *eng) +{ + struct mxic_ecc_engine *mxic = pip_ecc_eng_to_mxic(eng); + + platform_device_put(to_platform_device(mxic->dev)); +} +EXPORT_SYMBOL_GPL(mxic_ecc_put_pipelined_engine); + +struct nand_ecc_engine * +mxic_ecc_get_pipelined_engine(struct platform_device *spi_pdev) +{ + struct platform_device *eng_pdev; + struct mxic_ecc_engine *mxic; + + eng_pdev = mxic_ecc_get_pdev(spi_pdev); + if (!eng_pdev) + return ERR_PTR(-ENODEV); + + mxic = platform_get_drvdata(eng_pdev); + if (!mxic) { + platform_device_put(eng_pdev); + return ERR_PTR(-EPROBE_DEFER); + } + + return &mxic->pipelined_engine; +} +EXPORT_SYMBOL_GPL(mxic_ecc_get_pipelined_engine); + +/* + * Only the external ECC engine is exported as the pipelined is SoC specific, so + * it is registered directly by the drivers that wrap it. + */ +static int mxic_ecc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mxic_ecc_engine *mxic; + int ret; + + mxic = devm_kzalloc(&pdev->dev, sizeof(*mxic), GFP_KERNEL); + if (!mxic) + return -ENOMEM; + + mxic->dev = &pdev->dev; + + /* + * Both memory regions for the ECC engine itself and the AXI slave + * address are mandatory. + */ + mxic->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(mxic->regs)) { + dev_err(&pdev->dev, "Missing memory region\n"); + return PTR_ERR(mxic->regs); + } + + mxic_ecc_disable_engine(mxic); + mxic_ecc_disable_int(mxic); + + /* IRQ is optional yet much more efficient */ + mxic->irq = platform_get_irq_byname_optional(pdev, "ecc-engine"); + if (mxic->irq > 0) { + ret = devm_request_irq(&pdev->dev, mxic->irq, mxic_ecc_isr, 0, + "mxic-ecc", mxic); + if (ret) + return ret; + } else { + dev_info(dev, "Invalid or missing IRQ, fallback to polling\n"); + mxic->irq = 0; + } + + mutex_init(&mxic->lock); + + /* + * In external mode, the device is the ECC engine. In pipelined mode, + * the device is the host controller. The device is used to match the + * right ECC engine based on the DT properties. + */ + mxic->external_engine.dev = &pdev->dev; + mxic->external_engine.integration = NAND_ECC_ENGINE_INTEGRATION_EXTERNAL; + mxic->external_engine.ops = &mxic_ecc_engine_external_ops; + + nand_ecc_register_on_host_hw_engine(&mxic->external_engine); + + platform_set_drvdata(pdev, mxic); + + return 0; +} + +static void mxic_ecc_remove(struct platform_device *pdev) +{ + struct mxic_ecc_engine *mxic = platform_get_drvdata(pdev); + + nand_ecc_unregister_on_host_hw_engine(&mxic->external_engine); +} + +static const struct of_device_id mxic_ecc_of_ids[] = { + { + .compatible = "mxicy,nand-ecc-engine-rev3", + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mxic_ecc_of_ids); + +static struct platform_driver mxic_ecc_driver = { + .driver = { + .name = "mxic-nand-ecc-engine", + .of_match_table = mxic_ecc_of_ids, + }, + .probe = mxic_ecc_probe, + .remove = mxic_ecc_remove, +}; +module_platform_driver(mxic_ecc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Miquel Raynal "); +MODULE_DESCRIPTION("Macronix NAND hardware ECC controller"); diff --git a/drivers/mtd/nand/ecc-realtek.c b/drivers/mtd/nand/ecc-realtek.c new file mode 100644 index 000000000..76d75e210 --- /dev/null +++ b/drivers/mtd/nand/ecc-realtek.c @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Support for Realtek hardware ECC engine in RTL93xx SoCs + */ + +#include +#include +#include +#include +#include +#include + +/* + * 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 */ }, +}; +MODULE_DEVICE_TABLE(of, rtl_ecc_of_ids); + +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 "); +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 new file mode 100644 index 000000000..26d5f3f10 --- /dev/null +++ b/drivers/mtd/nand/ecc-sw-bch.c @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This file provides ECC correction for more than 1 bit per block of data, + * using binary BCH codes. It relies on the generic BCH library lib/bch.c. + * + * Copyright © 2011 Ivan Djelic + */ + +#include +#include +#include +#include +#include +#include +#include + +/** + * nand_ecc_sw_bch_calculate - Calculate the ECC corresponding to a data block + * @nand: NAND device + * @buf: Input buffer with raw data + * @code: Output buffer with ECC + */ +int nand_ecc_sw_bch_calculate(struct nand_device *nand, + const unsigned char *buf, unsigned char *code) +{ + struct nand_ecc_sw_bch_conf *engine_conf = nand->ecc.ctx.priv; + unsigned int i; + + memset(code, 0, engine_conf->code_size); + bch_encode(engine_conf->bch, buf, nand->ecc.ctx.conf.step_size, code); + + /* apply mask so that an erased page is a valid codeword */ + for (i = 0; i < engine_conf->code_size; i++) + code[i] ^= engine_conf->eccmask[i]; + + return 0; +} +EXPORT_SYMBOL(nand_ecc_sw_bch_calculate); + +/** + * nand_ecc_sw_bch_correct - Detect, correct and report bit error(s) + * @nand: NAND device + * @buf: Raw data read from the chip + * @read_ecc: ECC bytes from the chip + * @calc_ecc: ECC calculated from the raw data + * + * Detect and correct bit errors for a data block. + */ +int nand_ecc_sw_bch_correct(struct nand_device *nand, unsigned char *buf, + unsigned char *read_ecc, unsigned char *calc_ecc) +{ + struct nand_ecc_sw_bch_conf *engine_conf = nand->ecc.ctx.priv; + unsigned int step_size = nand->ecc.ctx.conf.step_size; + unsigned int *errloc = engine_conf->errloc; + int i, count; + + count = bch_decode(engine_conf->bch, NULL, step_size, read_ecc, + calc_ecc, NULL, errloc); + if (count > 0) { + for (i = 0; i < count; i++) { + if (errloc[i] < (step_size * 8)) + /* The error is in the data area: correct it */ + buf[errloc[i] >> 3] ^= (1 << (errloc[i] & 7)); + + /* Otherwise the error is in the ECC area: nothing to do */ + pr_debug("%s: corrected bitflip %u\n", __func__, + errloc[i]); + } + } else if (count < 0) { + pr_err("ECC unrecoverable error\n"); + count = -EBADMSG; + } + + return count; +} +EXPORT_SYMBOL(nand_ecc_sw_bch_correct); + +/** + * nand_ecc_sw_bch_cleanup - Cleanup software BCH ECC resources + * @nand: NAND device + */ +static void nand_ecc_sw_bch_cleanup(struct nand_device *nand) +{ + struct nand_ecc_sw_bch_conf *engine_conf = nand->ecc.ctx.priv; + + bch_free(engine_conf->bch); + kfree(engine_conf->errloc); + kfree(engine_conf->eccmask); +} + +/** + * nand_ecc_sw_bch_init - Initialize software BCH ECC engine + * @nand: NAND device + * + * Returns: a pointer to a new NAND BCH control structure, or NULL upon failure + * + * Initialize NAND BCH error correction. @nand.ecc parameters 'step_size' and + * 'bytes' are used to compute the following BCH parameters: + * m, the Galois field order + * t, the error correction capability + * 'bytes' should be equal to the number of bytes required to store m * t + * bits, where m is such that 2^m - 1 > step_size * 8. + * + * Example: to configure 4 bit correction per 512 bytes, you should pass + * step_size = 512 (thus, m = 13 is the smallest integer such that 2^m - 1 > 512 * 8) + * bytes = 7 (7 bytes are required to store m * t = 13 * 4 = 52 bits) + */ +static int nand_ecc_sw_bch_init(struct nand_device *nand) +{ + struct nand_ecc_sw_bch_conf *engine_conf = nand->ecc.ctx.priv; + unsigned int eccsize = nand->ecc.ctx.conf.step_size; + unsigned int eccbytes = engine_conf->code_size; + unsigned int m, t, i; + unsigned char *erased_page; + int ret; + + m = fls(1 + (8 * eccsize)); + t = (eccbytes * 8) / m; + + engine_conf->bch = bch_init(m, t, 0, false); + if (!engine_conf->bch) + return -EINVAL; + + engine_conf->eccmask = kzalloc(eccbytes, GFP_KERNEL); + engine_conf->errloc = kmalloc_array(t, sizeof(*engine_conf->errloc), + GFP_KERNEL); + if (!engine_conf->eccmask || !engine_conf->errloc) { + ret = -ENOMEM; + goto cleanup; + } + + /* Compute and store the inverted ECC of an erased step */ + erased_page = kmalloc(eccsize, GFP_KERNEL); + if (!erased_page) { + ret = -ENOMEM; + goto cleanup; + } + + memset(erased_page, 0xff, eccsize); + bch_encode(engine_conf->bch, erased_page, eccsize, + engine_conf->eccmask); + kfree(erased_page); + + for (i = 0; i < eccbytes; i++) + engine_conf->eccmask[i] ^= 0xff; + + /* Verify that the number of code bytes has the expected value */ + if (engine_conf->bch->ecc_bytes != eccbytes) { + pr_err("Invalid number of ECC bytes: %u, expected: %u\n", + eccbytes, engine_conf->bch->ecc_bytes); + ret = -EINVAL; + goto cleanup; + } + + /* Sanity checks */ + if (8 * (eccsize + eccbytes) >= (1 << m)) { + pr_err("ECC step size is too large (%u)\n", eccsize); + ret = -EINVAL; + goto cleanup; + } + + return 0; + +cleanup: + nand_ecc_sw_bch_cleanup(nand); + + return ret; +} + +int nand_ecc_sw_bch_init_ctx(struct nand_device *nand) +{ + struct nand_ecc_props *conf = &nand->ecc.ctx.conf; + struct mtd_info *mtd = nanddev_to_mtd(nand); + struct nand_ecc_sw_bch_conf *engine_conf; + unsigned int code_size = 0, nsteps; + int ret; + + /* Only large page NAND chips may use BCH */ + if (mtd->oobsize < 64) { + pr_err("BCH cannot be used with small page NAND chips\n"); + return -EINVAL; + } + + if (!mtd->ooblayout) + mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout()); + + conf->engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + conf->algo = NAND_ECC_ALGO_BCH; + conf->step_size = nand->ecc.user_conf.step_size; + conf->strength = nand->ecc.user_conf.strength; + + /* + * Board driver should supply ECC size and ECC strength + * values to select how many bits are correctable. + * Otherwise, default to 512 bytes for large page devices and 256 for + * small page devices. + */ + if (!conf->step_size) { + if (mtd->oobsize >= 64) + conf->step_size = 512; + else + conf->step_size = 256; + + conf->strength = 4; + } + + nsteps = mtd->writesize / conf->step_size; + + /* Maximize */ + if (nand->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) { + conf->step_size = 1024; + nsteps = mtd->writesize / conf->step_size; + /* Reserve 2 bytes for the BBM */ + code_size = (mtd->oobsize - 2) / nsteps; + conf->strength = code_size * 8 / fls(8 * conf->step_size); + } + + if (!code_size) + code_size = DIV_ROUND_UP(conf->strength * + fls(8 * conf->step_size), 8); + + if (!conf->strength) + conf->strength = (code_size * 8) / fls(8 * conf->step_size); + + if (!code_size && !conf->strength) { + pr_err("Missing ECC parameters\n"); + return -EINVAL; + } + + engine_conf = kzalloc_obj(*engine_conf); + if (!engine_conf) + return -ENOMEM; + + ret = nand_ecc_init_req_tweaking(&engine_conf->req_ctx, nand); + if (ret) + goto free_engine_conf; + + engine_conf->code_size = code_size; + engine_conf->calc_buf = kzalloc(mtd->oobsize, GFP_KERNEL); + engine_conf->code_buf = kzalloc(mtd->oobsize, GFP_KERNEL); + if (!engine_conf->calc_buf || !engine_conf->code_buf) { + ret = -ENOMEM; + goto free_bufs; + } + + nand->ecc.ctx.priv = engine_conf; + nand->ecc.ctx.nsteps = nsteps; + nand->ecc.ctx.total = nsteps * code_size; + + ret = nand_ecc_sw_bch_init(nand); + if (ret) + goto free_bufs; + + /* Verify the layout validity */ + if (mtd_ooblayout_count_eccbytes(mtd) != + nand->ecc.ctx.nsteps * engine_conf->code_size) { + pr_err("Invalid ECC layout\n"); + ret = -EINVAL; + goto cleanup_bch_ctx; + } + + return 0; + +cleanup_bch_ctx: + nand_ecc_sw_bch_cleanup(nand); +free_bufs: + nand_ecc_cleanup_req_tweaking(&engine_conf->req_ctx); + kfree(engine_conf->calc_buf); + kfree(engine_conf->code_buf); +free_engine_conf: + kfree(engine_conf); + + return ret; +} +EXPORT_SYMBOL(nand_ecc_sw_bch_init_ctx); + +void nand_ecc_sw_bch_cleanup_ctx(struct nand_device *nand) +{ + struct nand_ecc_sw_bch_conf *engine_conf = nand->ecc.ctx.priv; + + if (engine_conf) { + nand_ecc_sw_bch_cleanup(nand); + nand_ecc_cleanup_req_tweaking(&engine_conf->req_ctx); + kfree(engine_conf->calc_buf); + kfree(engine_conf->code_buf); + kfree(engine_conf); + } +} +EXPORT_SYMBOL(nand_ecc_sw_bch_cleanup_ctx); + +static int nand_ecc_sw_bch_prepare_io_req(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct nand_ecc_sw_bch_conf *engine_conf = nand->ecc.ctx.priv; + struct mtd_info *mtd = nanddev_to_mtd(nand); + int eccsize = nand->ecc.ctx.conf.step_size; + int eccbytes = engine_conf->code_size; + int eccsteps = nand->ecc.ctx.nsteps; + int total = nand->ecc.ctx.total; + u8 *ecccalc = engine_conf->calc_buf; + const u8 *data; + int i; + + /* Nothing to do for a raw operation */ + if (req->mode == MTD_OPS_RAW) + return 0; + + /* This engine does not provide BBM/free OOB bytes protection */ + if (!req->datalen) + return 0; + + nand_ecc_tweak_req(&engine_conf->req_ctx, req); + + /* No more preparation for page read */ + if (req->type == NAND_PAGE_READ) + return 0; + + /* Preparation for page write: derive the ECC bytes and place them */ + for (i = 0, data = req->databuf.out; + eccsteps; + eccsteps--, i += eccbytes, data += eccsize) + nand_ecc_sw_bch_calculate(nand, data, &ecccalc[i]); + + return mtd_ooblayout_set_eccbytes(mtd, ecccalc, (void *)req->oobbuf.out, + 0, total); +} + +static int nand_ecc_sw_bch_finish_io_req(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct nand_ecc_sw_bch_conf *engine_conf = nand->ecc.ctx.priv; + struct mtd_info *mtd = nanddev_to_mtd(nand); + int eccsize = nand->ecc.ctx.conf.step_size; + int total = nand->ecc.ctx.total; + int eccbytes = engine_conf->code_size; + int eccsteps = nand->ecc.ctx.nsteps; + u8 *ecccalc = engine_conf->calc_buf; + u8 *ecccode = engine_conf->code_buf; + unsigned int max_bitflips = 0; + u8 *data = req->databuf.in; + int i, ret; + + /* Nothing to do for a raw operation */ + if (req->mode == MTD_OPS_RAW) + return 0; + + /* This engine does not provide BBM/free OOB bytes protection */ + if (!req->datalen) + return 0; + + /* No more preparation for page write */ + if (req->type == NAND_PAGE_WRITE) { + nand_ecc_restore_req(&engine_conf->req_ctx, req); + return 0; + } + + /* Finish a page read: retrieve the (raw) ECC bytes*/ + ret = mtd_ooblayout_get_eccbytes(mtd, ecccode, req->oobbuf.in, 0, + total); + if (ret) + return ret; + + /* Calculate the ECC bytes */ + for (i = 0; eccsteps; eccsteps--, i += eccbytes, data += eccsize) + nand_ecc_sw_bch_calculate(nand, data, &ecccalc[i]); + + /* Finish a page read: compare and correct */ + for (eccsteps = nand->ecc.ctx.nsteps, i = 0, data = req->databuf.in; + eccsteps; + eccsteps--, i += eccbytes, data += eccsize) { + int stat = nand_ecc_sw_bch_correct(nand, data, + &ecccode[i], + &ecccalc[i]); + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + + nand_ecc_restore_req(&engine_conf->req_ctx, req); + + return max_bitflips; +} + +static const struct nand_ecc_engine_ops nand_ecc_sw_bch_engine_ops = { + .init_ctx = nand_ecc_sw_bch_init_ctx, + .cleanup_ctx = nand_ecc_sw_bch_cleanup_ctx, + .prepare_io_req = nand_ecc_sw_bch_prepare_io_req, + .finish_io_req = nand_ecc_sw_bch_finish_io_req, +}; + +static struct nand_ecc_engine nand_ecc_sw_bch_engine = { + .ops = &nand_ecc_sw_bch_engine_ops, +}; + +struct nand_ecc_engine *nand_ecc_sw_bch_get_engine(void) +{ + return &nand_ecc_sw_bch_engine; +} +EXPORT_SYMBOL(nand_ecc_sw_bch_get_engine); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ivan Djelic "); +MODULE_DESCRIPTION("NAND software BCH ECC support"); diff --git a/drivers/mtd/nand/ecc-sw-hamming.c b/drivers/mtd/nand/ecc-sw-hamming.c new file mode 100644 index 000000000..460acc102 --- /dev/null +++ b/drivers/mtd/nand/ecc-sw-hamming.c @@ -0,0 +1,660 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This file contains an ECC algorithm that detects and corrects 1 bit + * errors in a 256 byte block of data. + * + * Copyright © 2008 Koninklijke Philips Electronics NV. + * Author: Frans Meulenbroeks + * + * Completely replaces the previous ECC implementation which was written by: + * Steven J. Hill (sjhill@realitydiluted.com) + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include + +/* + * invparity is a 256 byte table that contains the odd parity + * for each byte. So if the number of bits in a byte is even, + * the array element is 1, and when the number of bits is odd + * the array eleemnt is 0. + */ +static const char invparity[256] = { + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1 +}; + +/* + * bitsperbyte contains the number of bits per byte + * this is only used for testing and repairing parity + * (a precalculated value slightly improves performance) + */ +static const char bitsperbyte[256] = { + 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, + 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, +}; + +/* + * addressbits is a lookup table to filter out the bits from the xor-ed + * ECC data that identify the faulty location. + * this is only used for repairing parity + * see the comments in nand_ecc_sw_hamming_correct for more details + */ +static const char addressbits[256] = { + 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, + 0x02, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, + 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, + 0x02, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, + 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x05, + 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x07, 0x07, + 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x05, + 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x07, 0x07, + 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, + 0x02, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, + 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01, + 0x02, 0x02, 0x03, 0x03, 0x02, 0x02, 0x03, 0x03, + 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x05, + 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x07, 0x07, + 0x04, 0x04, 0x05, 0x05, 0x04, 0x04, 0x05, 0x05, + 0x06, 0x06, 0x07, 0x07, 0x06, 0x06, 0x07, 0x07, + 0x08, 0x08, 0x09, 0x09, 0x08, 0x08, 0x09, 0x09, + 0x0a, 0x0a, 0x0b, 0x0b, 0x0a, 0x0a, 0x0b, 0x0b, + 0x08, 0x08, 0x09, 0x09, 0x08, 0x08, 0x09, 0x09, + 0x0a, 0x0a, 0x0b, 0x0b, 0x0a, 0x0a, 0x0b, 0x0b, + 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d, + 0x0e, 0x0e, 0x0f, 0x0f, 0x0e, 0x0e, 0x0f, 0x0f, + 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d, + 0x0e, 0x0e, 0x0f, 0x0f, 0x0e, 0x0e, 0x0f, 0x0f, + 0x08, 0x08, 0x09, 0x09, 0x08, 0x08, 0x09, 0x09, + 0x0a, 0x0a, 0x0b, 0x0b, 0x0a, 0x0a, 0x0b, 0x0b, + 0x08, 0x08, 0x09, 0x09, 0x08, 0x08, 0x09, 0x09, + 0x0a, 0x0a, 0x0b, 0x0b, 0x0a, 0x0a, 0x0b, 0x0b, + 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d, + 0x0e, 0x0e, 0x0f, 0x0f, 0x0e, 0x0e, 0x0f, 0x0f, + 0x0c, 0x0c, 0x0d, 0x0d, 0x0c, 0x0c, 0x0d, 0x0d, + 0x0e, 0x0e, 0x0f, 0x0f, 0x0e, 0x0e, 0x0f, 0x0f +}; + +int ecc_sw_hamming_calculate(const unsigned char *buf, unsigned int step_size, + unsigned char *code, bool sm_order) +{ + const u32 *bp = (uint32_t *)buf; + const u32 eccsize_mult = (step_size == 256) ? 1 : 2; + /* current value in buffer */ + u32 cur; + /* rp0..rp17 are the various accumulated parities (per byte) */ + u32 rp0, rp1, rp2, rp3, rp4, rp5, rp6, rp7, rp8, rp9, rp10, rp11, rp12, + rp13, rp14, rp15, rp16, rp17; + /* Cumulative parity for all data */ + u32 par; + /* Cumulative parity at the end of the loop (rp12, rp14, rp16) */ + u32 tmppar; + int i; + + par = 0; + rp4 = 0; + rp6 = 0; + rp8 = 0; + rp10 = 0; + rp12 = 0; + rp14 = 0; + rp16 = 0; + rp17 = 0; + + /* + * The loop is unrolled a number of times; + * This avoids if statements to decide on which rp value to update + * Also we process the data by longwords. + * Note: passing unaligned data might give a performance penalty. + * It is assumed that the buffers are aligned. + * tmppar is the cumulative sum of this iteration. + * needed for calculating rp12, rp14, rp16 and par + * also used as a performance improvement for rp6, rp8 and rp10 + */ + for (i = 0; i < eccsize_mult << 2; i++) { + cur = *bp++; + tmppar = cur; + rp4 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp6 ^= tmppar; + cur = *bp++; + tmppar ^= cur; + rp4 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp8 ^= tmppar; + + cur = *bp++; + tmppar ^= cur; + rp4 ^= cur; + rp6 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp6 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp4 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp10 ^= tmppar; + + cur = *bp++; + tmppar ^= cur; + rp4 ^= cur; + rp6 ^= cur; + rp8 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp6 ^= cur; + rp8 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp4 ^= cur; + rp8 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp8 ^= cur; + + cur = *bp++; + tmppar ^= cur; + rp4 ^= cur; + rp6 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp6 ^= cur; + cur = *bp++; + tmppar ^= cur; + rp4 ^= cur; + cur = *bp++; + tmppar ^= cur; + + par ^= tmppar; + if ((i & 0x1) == 0) + rp12 ^= tmppar; + if ((i & 0x2) == 0) + rp14 ^= tmppar; + if (eccsize_mult == 2 && (i & 0x4) == 0) + rp16 ^= tmppar; + } + + /* + * handle the fact that we use longword operations + * we'll bring rp4..rp14..rp16 back to single byte entities by + * shifting and xoring first fold the upper and lower 16 bits, + * then the upper and lower 8 bits. + */ + rp4 ^= (rp4 >> 16); + rp4 ^= (rp4 >> 8); + rp4 &= 0xff; + rp6 ^= (rp6 >> 16); + rp6 ^= (rp6 >> 8); + rp6 &= 0xff; + rp8 ^= (rp8 >> 16); + rp8 ^= (rp8 >> 8); + rp8 &= 0xff; + rp10 ^= (rp10 >> 16); + rp10 ^= (rp10 >> 8); + rp10 &= 0xff; + rp12 ^= (rp12 >> 16); + rp12 ^= (rp12 >> 8); + rp12 &= 0xff; + rp14 ^= (rp14 >> 16); + rp14 ^= (rp14 >> 8); + rp14 &= 0xff; + if (eccsize_mult == 2) { + rp16 ^= (rp16 >> 16); + rp16 ^= (rp16 >> 8); + rp16 &= 0xff; + } + + /* + * we also need to calculate the row parity for rp0..rp3 + * This is present in par, because par is now + * rp3 rp3 rp2 rp2 in little endian and + * rp2 rp2 rp3 rp3 in big endian + * as well as + * rp1 rp0 rp1 rp0 in little endian and + * rp0 rp1 rp0 rp1 in big endian + * First calculate rp2 and rp3 + */ +#ifdef __BIG_ENDIAN + rp2 = (par >> 16); + rp2 ^= (rp2 >> 8); + rp2 &= 0xff; + rp3 = par & 0xffff; + rp3 ^= (rp3 >> 8); + rp3 &= 0xff; +#else + rp3 = (par >> 16); + rp3 ^= (rp3 >> 8); + rp3 &= 0xff; + rp2 = par & 0xffff; + rp2 ^= (rp2 >> 8); + rp2 &= 0xff; +#endif + + /* reduce par to 16 bits then calculate rp1 and rp0 */ + par ^= (par >> 16); +#ifdef __BIG_ENDIAN + rp0 = (par >> 8) & 0xff; + rp1 = (par & 0xff); +#else + rp1 = (par >> 8) & 0xff; + rp0 = (par & 0xff); +#endif + + /* finally reduce par to 8 bits */ + par ^= (par >> 8); + par &= 0xff; + + /* + * and calculate rp5..rp15..rp17 + * note that par = rp4 ^ rp5 and due to the commutative property + * of the ^ operator we can say: + * rp5 = (par ^ rp4); + * The & 0xff seems superfluous, but benchmarking learned that + * leaving it out gives slightly worse results. No idea why, probably + * it has to do with the way the pipeline in pentium is organized. + */ + rp5 = (par ^ rp4) & 0xff; + rp7 = (par ^ rp6) & 0xff; + rp9 = (par ^ rp8) & 0xff; + rp11 = (par ^ rp10) & 0xff; + rp13 = (par ^ rp12) & 0xff; + rp15 = (par ^ rp14) & 0xff; + if (eccsize_mult == 2) + rp17 = (par ^ rp16) & 0xff; + + /* + * Finally calculate the ECC bits. + * Again here it might seem that there are performance optimisations + * possible, but benchmarks showed that on the system this is developed + * the code below is the fastest + */ + if (sm_order) { + code[0] = (invparity[rp7] << 7) | (invparity[rp6] << 6) | + (invparity[rp5] << 5) | (invparity[rp4] << 4) | + (invparity[rp3] << 3) | (invparity[rp2] << 2) | + (invparity[rp1] << 1) | (invparity[rp0]); + code[1] = (invparity[rp15] << 7) | (invparity[rp14] << 6) | + (invparity[rp13] << 5) | (invparity[rp12] << 4) | + (invparity[rp11] << 3) | (invparity[rp10] << 2) | + (invparity[rp9] << 1) | (invparity[rp8]); + } else { + code[1] = (invparity[rp7] << 7) | (invparity[rp6] << 6) | + (invparity[rp5] << 5) | (invparity[rp4] << 4) | + (invparity[rp3] << 3) | (invparity[rp2] << 2) | + (invparity[rp1] << 1) | (invparity[rp0]); + code[0] = (invparity[rp15] << 7) | (invparity[rp14] << 6) | + (invparity[rp13] << 5) | (invparity[rp12] << 4) | + (invparity[rp11] << 3) | (invparity[rp10] << 2) | + (invparity[rp9] << 1) | (invparity[rp8]); + } + + if (eccsize_mult == 1) + code[2] = + (invparity[par & 0xf0] << 7) | + (invparity[par & 0x0f] << 6) | + (invparity[par & 0xcc] << 5) | + (invparity[par & 0x33] << 4) | + (invparity[par & 0xaa] << 3) | + (invparity[par & 0x55] << 2) | + 3; + else + code[2] = + (invparity[par & 0xf0] << 7) | + (invparity[par & 0x0f] << 6) | + (invparity[par & 0xcc] << 5) | + (invparity[par & 0x33] << 4) | + (invparity[par & 0xaa] << 3) | + (invparity[par & 0x55] << 2) | + (invparity[rp17] << 1) | + (invparity[rp16] << 0); + + return 0; +} +EXPORT_SYMBOL(ecc_sw_hamming_calculate); + +/** + * nand_ecc_sw_hamming_calculate - Calculate 3-byte ECC for 256/512-byte block + * @nand: NAND device + * @buf: Input buffer with raw data + * @code: Output buffer with ECC + */ +int nand_ecc_sw_hamming_calculate(struct nand_device *nand, + const unsigned char *buf, unsigned char *code) +{ + struct nand_ecc_sw_hamming_conf *engine_conf = nand->ecc.ctx.priv; + unsigned int step_size = nand->ecc.ctx.conf.step_size; + bool sm_order = engine_conf ? engine_conf->sm_order : false; + + return ecc_sw_hamming_calculate(buf, step_size, code, sm_order); +} +EXPORT_SYMBOL(nand_ecc_sw_hamming_calculate); + +int ecc_sw_hamming_correct(unsigned char *buf, unsigned char *read_ecc, + unsigned char *calc_ecc, unsigned int step_size, + bool sm_order) +{ + const u32 eccsize_mult = step_size >> 8; + unsigned char b0, b1, b2, bit_addr; + unsigned int byte_addr; + + /* + * b0 to b2 indicate which bit is faulty (if any) + * we might need the xor result more than once, + * so keep them in a local var + */ + if (sm_order) { + b0 = read_ecc[0] ^ calc_ecc[0]; + b1 = read_ecc[1] ^ calc_ecc[1]; + } else { + b0 = read_ecc[1] ^ calc_ecc[1]; + b1 = read_ecc[0] ^ calc_ecc[0]; + } + + b2 = read_ecc[2] ^ calc_ecc[2]; + + /* check if there are any bitfaults */ + + /* repeated if statements are slightly more efficient than switch ... */ + /* ordered in order of likelihood */ + + if ((b0 | b1 | b2) == 0) + return 0; /* no error */ + + if ((((b0 ^ (b0 >> 1)) & 0x55) == 0x55) && + (((b1 ^ (b1 >> 1)) & 0x55) == 0x55) && + ((eccsize_mult == 1 && ((b2 ^ (b2 >> 1)) & 0x54) == 0x54) || + (eccsize_mult == 2 && ((b2 ^ (b2 >> 1)) & 0x55) == 0x55))) { + /* single bit error */ + /* + * rp17/rp15/13/11/9/7/5/3/1 indicate which byte is the faulty + * byte, cp 5/3/1 indicate the faulty bit. + * A lookup table (called addressbits) is used to filter + * the bits from the byte they are in. + * A marginal optimisation is possible by having three + * different lookup tables. + * One as we have now (for b0), one for b2 + * (that would avoid the >> 1), and one for b1 (with all values + * << 4). However it was felt that introducing two more tables + * hardly justify the gain. + * + * The b2 shift is there to get rid of the lowest two bits. + * We could also do addressbits[b2] >> 1 but for the + * performance it does not make any difference + */ + if (eccsize_mult == 1) + byte_addr = (addressbits[b1] << 4) + addressbits[b0]; + else + byte_addr = (addressbits[b2 & 0x3] << 8) + + (addressbits[b1] << 4) + addressbits[b0]; + bit_addr = addressbits[b2 >> 2]; + /* flip the bit */ + buf[byte_addr] ^= (1 << bit_addr); + return 1; + + } + /* count nr of bits; use table lookup, faster than calculating it */ + if ((bitsperbyte[b0] + bitsperbyte[b1] + bitsperbyte[b2]) == 1) + return 1; /* error in ECC data; no action needed */ + + pr_err("%s: uncorrectable ECC error\n", __func__); + return -EBADMSG; +} +EXPORT_SYMBOL(ecc_sw_hamming_correct); + +/** + * nand_ecc_sw_hamming_correct - Detect and correct bit error(s) + * @nand: NAND device + * @buf: Raw data read from the chip + * @read_ecc: ECC bytes read from the chip + * @calc_ecc: ECC calculated from the raw data + * + * Detect and correct up to 1 bit error per 256/512-byte block. + */ +int nand_ecc_sw_hamming_correct(struct nand_device *nand, unsigned char *buf, + unsigned char *read_ecc, + unsigned char *calc_ecc) +{ + struct nand_ecc_sw_hamming_conf *engine_conf = nand->ecc.ctx.priv; + unsigned int step_size = nand->ecc.ctx.conf.step_size; + bool sm_order = engine_conf ? engine_conf->sm_order : false; + + return ecc_sw_hamming_correct(buf, read_ecc, calc_ecc, step_size, + sm_order); +} +EXPORT_SYMBOL(nand_ecc_sw_hamming_correct); + +int nand_ecc_sw_hamming_init_ctx(struct nand_device *nand) +{ + struct nand_ecc_props *conf = &nand->ecc.ctx.conf; + struct nand_ecc_sw_hamming_conf *engine_conf; + struct mtd_info *mtd = nanddev_to_mtd(nand); + int ret; + + if (!mtd->ooblayout) { + switch (mtd->oobsize) { + case 8: + case 16: + mtd_set_ooblayout(mtd, nand_get_small_page_ooblayout()); + break; + case 64: + case 128: + mtd_set_ooblayout(mtd, + nand_get_large_page_hamming_ooblayout()); + break; + default: + return -ENOTSUPP; + } + } + + conf->engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + conf->algo = NAND_ECC_ALGO_HAMMING; + conf->step_size = nand->ecc.user_conf.step_size; + conf->strength = 1; + + /* Use the strongest configuration by default */ + if (conf->step_size != 256 && conf->step_size != 512) + conf->step_size = 256; + + engine_conf = kzalloc_obj(*engine_conf); + if (!engine_conf) + return -ENOMEM; + + ret = nand_ecc_init_req_tweaking(&engine_conf->req_ctx, nand); + if (ret) + goto free_engine_conf; + + engine_conf->code_size = 3; + engine_conf->calc_buf = kzalloc(mtd->oobsize, GFP_KERNEL); + engine_conf->code_buf = kzalloc(mtd->oobsize, GFP_KERNEL); + if (!engine_conf->calc_buf || !engine_conf->code_buf) { + ret = -ENOMEM; + goto free_bufs; + } + + nand->ecc.ctx.priv = engine_conf; + nand->ecc.ctx.nsteps = mtd->writesize / conf->step_size; + nand->ecc.ctx.total = nand->ecc.ctx.nsteps * engine_conf->code_size; + + return 0; + +free_bufs: + nand_ecc_cleanup_req_tweaking(&engine_conf->req_ctx); + kfree(engine_conf->calc_buf); + kfree(engine_conf->code_buf); +free_engine_conf: + kfree(engine_conf); + + return ret; +} +EXPORT_SYMBOL(nand_ecc_sw_hamming_init_ctx); + +void nand_ecc_sw_hamming_cleanup_ctx(struct nand_device *nand) +{ + struct nand_ecc_sw_hamming_conf *engine_conf = nand->ecc.ctx.priv; + + if (engine_conf) { + nand_ecc_cleanup_req_tweaking(&engine_conf->req_ctx); + kfree(engine_conf->calc_buf); + kfree(engine_conf->code_buf); + kfree(engine_conf); + } +} +EXPORT_SYMBOL(nand_ecc_sw_hamming_cleanup_ctx); + +static int nand_ecc_sw_hamming_prepare_io_req(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct nand_ecc_sw_hamming_conf *engine_conf = nand->ecc.ctx.priv; + struct mtd_info *mtd = nanddev_to_mtd(nand); + int eccsize = nand->ecc.ctx.conf.step_size; + int eccbytes = engine_conf->code_size; + int eccsteps = nand->ecc.ctx.nsteps; + int total = nand->ecc.ctx.total; + u8 *ecccalc = engine_conf->calc_buf; + const u8 *data; + int i; + + /* Nothing to do for a raw operation */ + if (req->mode == MTD_OPS_RAW) + return 0; + + /* This engine does not provide BBM/free OOB bytes protection */ + if (!req->datalen) + return 0; + + nand_ecc_tweak_req(&engine_conf->req_ctx, req); + + /* No more preparation for page read */ + if (req->type == NAND_PAGE_READ) + return 0; + + /* Preparation for page write: derive the ECC bytes and place them */ + for (i = 0, data = req->databuf.out; + eccsteps; + eccsteps--, i += eccbytes, data += eccsize) + nand_ecc_sw_hamming_calculate(nand, data, &ecccalc[i]); + + return mtd_ooblayout_set_eccbytes(mtd, ecccalc, (void *)req->oobbuf.out, + 0, total); +} + +static int nand_ecc_sw_hamming_finish_io_req(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct nand_ecc_sw_hamming_conf *engine_conf = nand->ecc.ctx.priv; + struct mtd_info *mtd = nanddev_to_mtd(nand); + int eccsize = nand->ecc.ctx.conf.step_size; + int total = nand->ecc.ctx.total; + int eccbytes = engine_conf->code_size; + int eccsteps = nand->ecc.ctx.nsteps; + u8 *ecccalc = engine_conf->calc_buf; + u8 *ecccode = engine_conf->code_buf; + unsigned int max_bitflips = 0; + u8 *data = req->databuf.in; + int i, ret; + + /* Nothing to do for a raw operation */ + if (req->mode == MTD_OPS_RAW) + return 0; + + /* This engine does not provide BBM/free OOB bytes protection */ + if (!req->datalen) + return 0; + + /* No more preparation for page write */ + if (req->type == NAND_PAGE_WRITE) { + nand_ecc_restore_req(&engine_conf->req_ctx, req); + return 0; + } + + /* Finish a page read: retrieve the (raw) ECC bytes*/ + ret = mtd_ooblayout_get_eccbytes(mtd, ecccode, req->oobbuf.in, 0, + total); + if (ret) + return ret; + + /* Calculate the ECC bytes */ + for (i = 0; eccsteps; eccsteps--, i += eccbytes, data += eccsize) + nand_ecc_sw_hamming_calculate(nand, data, &ecccalc[i]); + + /* Finish a page read: compare and correct */ + for (eccsteps = nand->ecc.ctx.nsteps, i = 0, data = req->databuf.in; + eccsteps; + eccsteps--, i += eccbytes, data += eccsize) { + int stat = nand_ecc_sw_hamming_correct(nand, data, + &ecccode[i], + &ecccalc[i]); + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + + nand_ecc_restore_req(&engine_conf->req_ctx, req); + + return max_bitflips; +} + +static const struct nand_ecc_engine_ops nand_ecc_sw_hamming_engine_ops = { + .init_ctx = nand_ecc_sw_hamming_init_ctx, + .cleanup_ctx = nand_ecc_sw_hamming_cleanup_ctx, + .prepare_io_req = nand_ecc_sw_hamming_prepare_io_req, + .finish_io_req = nand_ecc_sw_hamming_finish_io_req, +}; + +static struct nand_ecc_engine nand_ecc_sw_hamming_engine = { + .ops = &nand_ecc_sw_hamming_engine_ops, +}; + +struct nand_ecc_engine *nand_ecc_sw_hamming_get_engine(void) +{ + return &nand_ecc_sw_hamming_engine; +} +EXPORT_SYMBOL(nand_ecc_sw_hamming_get_engine); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Frans Meulenbroeks "); +MODULE_DESCRIPTION("NAND software Hamming ECC support"); diff --git a/drivers/mtd/nand/ecc.c b/drivers/mtd/nand/ecc.c new file mode 100644 index 000000000..6ccdff3fc --- /dev/null +++ b/drivers/mtd/nand/ecc.c @@ -0,0 +1,735 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Generic Error-Correcting Code (ECC) engine + * + * Copyright (C) 2019 Macronix + * Author: + * Miquèl RAYNAL + * + * + * This file describes the abstraction of any NAND ECC engine. It has been + * designed to fit most cases, including parallel NANDs and SPI-NANDs. + * + * There are three main situations where instantiating this ECC engine makes + * sense: + * - external: The ECC engine is outside the NAND pipeline, typically this + * is a software ECC engine, or an hardware engine that is + * outside the NAND controller pipeline. + * - pipelined: The ECC engine is inside the NAND pipeline, ie. on the + * controller's side. This is the case of most of the raw NAND + * controllers. In the pipeline case, the ECC bytes are + * generated/data corrected on the fly when a page is + * written/read. + * - ondie: The ECC engine is inside the NAND pipeline, on the chip's side. + * Some NAND chips can correct themselves the data. + * + * Besides the initial setup and final cleanups, the interfaces are rather + * simple: + * - prepare: Prepare an I/O request. Enable/disable the ECC engine based on + * the I/O request type. In case of software correction or external + * engine, this step may involve to derive the ECC bytes and place + * them in the OOB area before a write. + * - finish: Finish an I/O request. Correct the data in case of a read + * request and report the number of corrected bits/uncorrectable + * errors. Most likely empty for write operations, unless you have + * hardware specific stuff to do, like shutting down the engine to + * save power. + * + * The I/O request should be enclosed in a prepare()/finish() pair of calls + * and will behave differently depending on the requested I/O type: + * - raw: Correction disabled + * - ecc: Correction enabled + * + * The request direction is impacting the logic as well: + * - read: Load data from the NAND chip + * - write: Store data in the NAND chip + * + * Mixing all this combinations together gives the following behavior. + * Those are just examples, drivers are free to add custom steps in their + * prepare/finish hook. + * + * [external ECC engine] + * - external + prepare + raw + read: do nothing + * - external + finish + raw + read: do nothing + * - external + prepare + raw + write: do nothing + * - external + finish + raw + write: do nothing + * - external + prepare + ecc + read: do nothing + * - external + finish + ecc + read: calculate expected ECC bytes, extract + * ECC bytes from OOB buffer, correct + * and report any bitflip/error + * - external + prepare + ecc + write: calculate ECC bytes and store them at + * the right place in the OOB buffer based + * on the OOB layout + * - external + finish + ecc + write: do nothing + * + * [pipelined ECC engine] + * - pipelined + prepare + raw + read: disable the controller's ECC engine if + * activated + * - pipelined + finish + raw + read: do nothing + * - pipelined + prepare + raw + write: disable the controller's ECC engine if + * activated + * - pipelined + finish + raw + write: do nothing + * - pipelined + prepare + ecc + read: enable the controller's ECC engine if + * deactivated + * - pipelined + finish + ecc + read: check the status, report any + * error/bitflip + * - pipelined + prepare + ecc + write: enable the controller's ECC engine if + * deactivated + * - pipelined + finish + ecc + write: do nothing + * + * [ondie ECC engine] + * - ondie + prepare + raw + read: send commands to disable the on-chip ECC + * engine if activated + * - ondie + finish + raw + read: do nothing + * - ondie + prepare + raw + write: send commands to disable the on-chip ECC + * engine if activated + * - ondie + finish + raw + write: do nothing + * - ondie + prepare + ecc + read: send commands to enable the on-chip ECC + * engine if deactivated + * - ondie + finish + ecc + read: send commands to check the status, report + * any error/bitflip + * - ondie + prepare + ecc + write: send commands to enable the on-chip ECC + * engine if deactivated + * - ondie + finish + ecc + write: do nothing + */ + +#include +#include +#include +#include +#include +#include + +static LIST_HEAD(on_host_hw_engines); +static DEFINE_MUTEX(on_host_hw_engines_mutex); + +/** + * nand_ecc_init_ctx - Init the ECC engine context + * @nand: the NAND device + * + * On success, the caller is responsible of calling @nand_ecc_cleanup_ctx(). + */ +int nand_ecc_init_ctx(struct nand_device *nand) +{ + if (!nand->ecc.engine || !nand->ecc.engine->ops->init_ctx) + return 0; + + return nand->ecc.engine->ops->init_ctx(nand); +} +EXPORT_SYMBOL(nand_ecc_init_ctx); + +/** + * nand_ecc_cleanup_ctx - Cleanup the ECC engine context + * @nand: the NAND device + */ +void nand_ecc_cleanup_ctx(struct nand_device *nand) +{ + if (nand->ecc.engine && nand->ecc.engine->ops->cleanup_ctx) + nand->ecc.engine->ops->cleanup_ctx(nand); +} +EXPORT_SYMBOL(nand_ecc_cleanup_ctx); + +/** + * nand_ecc_prepare_io_req - Prepare an I/O request + * @nand: the NAND device + * @req: the I/O request + */ +int nand_ecc_prepare_io_req(struct nand_device *nand, + struct nand_page_io_req *req) +{ + if (!nand->ecc.engine || !nand->ecc.engine->ops->prepare_io_req) + return 0; + + return nand->ecc.engine->ops->prepare_io_req(nand, req); +} +EXPORT_SYMBOL(nand_ecc_prepare_io_req); + +/** + * nand_ecc_finish_io_req - Finish an I/O request + * @nand: the NAND device + * @req: the I/O request + */ +int nand_ecc_finish_io_req(struct nand_device *nand, + struct nand_page_io_req *req) +{ + if (!nand->ecc.engine || !nand->ecc.engine->ops->finish_io_req) + return 0; + + return nand->ecc.engine->ops->finish_io_req(nand, req); +} +EXPORT_SYMBOL(nand_ecc_finish_io_req); + +/* Define default OOB placement schemes for large and small page devices */ +static int nand_ooblayout_ecc_sp(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + unsigned int total_ecc_bytes = nand->ecc.ctx.total; + + if (section > 1) + return -ERANGE; + + if (!section) { + oobregion->offset = 0; + if (mtd->oobsize == 16) + oobregion->length = 4; + else + oobregion->length = 3; + } else { + if (mtd->oobsize == 8) + return -ERANGE; + + oobregion->offset = 6; + oobregion->length = total_ecc_bytes - 4; + } + + return 0; +} + +static int nand_ooblayout_free_sp(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 1) + return -ERANGE; + + if (mtd->oobsize == 16) { + if (section) + return -ERANGE; + + oobregion->length = 8; + oobregion->offset = 8; + } else { + oobregion->length = 2; + if (!section) + oobregion->offset = 3; + else + oobregion->offset = 6; + } + + return 0; +} + +static const struct mtd_ooblayout_ops nand_ooblayout_sp_ops = { + .ecc = nand_ooblayout_ecc_sp, + .free = nand_ooblayout_free_sp, +}; + +const struct mtd_ooblayout_ops *nand_get_small_page_ooblayout(void) +{ + return &nand_ooblayout_sp_ops; +} +EXPORT_SYMBOL_GPL(nand_get_small_page_ooblayout); + +static int nand_ooblayout_ecc_lp(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + unsigned int total_ecc_bytes = nand->ecc.ctx.total; + + if (section || !total_ecc_bytes) + return -ERANGE; + + oobregion->length = total_ecc_bytes; + oobregion->offset = mtd->oobsize - oobregion->length; + + return 0; +} + +static int nand_ooblayout_free_lp(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + unsigned int total_ecc_bytes = nand->ecc.ctx.total; + + if (section) + return -ERANGE; + + oobregion->length = mtd->oobsize - total_ecc_bytes - 2; + oobregion->offset = 2; + + return 0; +} + +static const struct mtd_ooblayout_ops nand_ooblayout_lp_ops = { + .ecc = nand_ooblayout_ecc_lp, + .free = nand_ooblayout_free_lp, +}; + +const struct mtd_ooblayout_ops *nand_get_large_page_ooblayout(void) +{ + return &nand_ooblayout_lp_ops; +} +EXPORT_SYMBOL_GPL(nand_get_large_page_ooblayout); + +/* + * Support the old "large page" layout used for 1-bit Hamming ECC where ECC + * are placed at a fixed offset. + */ +static int nand_ooblayout_ecc_lp_hamming(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + unsigned int total_ecc_bytes = nand->ecc.ctx.total; + + if (section) + return -ERANGE; + + switch (mtd->oobsize) { + case 64: + oobregion->offset = 40; + break; + case 128: + oobregion->offset = 80; + break; + default: + return -EINVAL; + } + + oobregion->length = total_ecc_bytes; + if (oobregion->offset + oobregion->length > mtd->oobsize) + return -ERANGE; + + return 0; +} + +static int nand_ooblayout_free_lp_hamming(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + unsigned int total_ecc_bytes = nand->ecc.ctx.total; + int ecc_offset = 0; + + if (section < 0 || section > 1) + return -ERANGE; + + switch (mtd->oobsize) { + case 64: + ecc_offset = 40; + break; + case 128: + ecc_offset = 80; + break; + default: + return -EINVAL; + } + + if (section == 0) { + oobregion->offset = 2; + oobregion->length = ecc_offset - 2; + } else { + oobregion->offset = ecc_offset + total_ecc_bytes; + oobregion->length = mtd->oobsize - oobregion->offset; + } + + return 0; +} + +static const struct mtd_ooblayout_ops nand_ooblayout_lp_hamming_ops = { + .ecc = nand_ooblayout_ecc_lp_hamming, + .free = nand_ooblayout_free_lp_hamming, +}; + +const struct mtd_ooblayout_ops *nand_get_large_page_hamming_ooblayout(void) +{ + return &nand_ooblayout_lp_hamming_ops; +} +EXPORT_SYMBOL_GPL(nand_get_large_page_hamming_ooblayout); + +static enum nand_ecc_engine_type +of_get_nand_ecc_engine_type(struct device_node *np) +{ + struct device_node *eng_np; + + if (of_property_read_bool(np, "nand-no-ecc-engine")) + return NAND_ECC_ENGINE_TYPE_NONE; + + if (of_property_read_bool(np, "nand-use-soft-ecc-engine")) + return NAND_ECC_ENGINE_TYPE_SOFT; + + eng_np = of_parse_phandle(np, "nand-ecc-engine", 0); + of_node_put(eng_np); + + if (eng_np) { + if (eng_np == np) + return NAND_ECC_ENGINE_TYPE_ON_DIE; + else + return NAND_ECC_ENGINE_TYPE_ON_HOST; + } + + return NAND_ECC_ENGINE_TYPE_INVALID; +} + +static const char * const nand_ecc_placement[] = { + [NAND_ECC_PLACEMENT_OOB] = "oob", + [NAND_ECC_PLACEMENT_INTERLEAVED] = "interleaved", +}; + +static enum nand_ecc_placement of_get_nand_ecc_placement(struct device_node *np) +{ + enum nand_ecc_placement placement; + const char *pm; + int err; + + err = of_property_read_string(np, "nand-ecc-placement", &pm); + if (!err) { + for (placement = NAND_ECC_PLACEMENT_OOB; + placement < ARRAY_SIZE(nand_ecc_placement); placement++) { + if (!strcasecmp(pm, nand_ecc_placement[placement])) + return placement; + } + } + + return NAND_ECC_PLACEMENT_UNKNOWN; +} + +static const char * const nand_ecc_algos[] = { + [NAND_ECC_ALGO_HAMMING] = "hamming", + [NAND_ECC_ALGO_BCH] = "bch", + [NAND_ECC_ALGO_RS] = "rs", +}; + +static enum nand_ecc_algo of_get_nand_ecc_algo(struct device_node *np) +{ + enum nand_ecc_algo ecc_algo; + const char *pm; + int err; + + err = of_property_read_string(np, "nand-ecc-algo", &pm); + if (!err) { + for (ecc_algo = NAND_ECC_ALGO_HAMMING; + ecc_algo < ARRAY_SIZE(nand_ecc_algos); + ecc_algo++) { + if (!strcasecmp(pm, nand_ecc_algos[ecc_algo])) + return ecc_algo; + } + } + + return NAND_ECC_ALGO_UNKNOWN; +} + +static int of_get_nand_ecc_step_size(struct device_node *np) +{ + int ret; + u32 val; + + ret = of_property_read_u32(np, "nand-ecc-step-size", &val); + return ret ? ret : val; +} + +static int of_get_nand_ecc_strength(struct device_node *np) +{ + int ret; + u32 val; + + ret = of_property_read_u32(np, "nand-ecc-strength", &val); + return ret ? ret : val; +} + +void of_get_nand_ecc_user_config(struct nand_device *nand) +{ + struct device_node *dn = nanddev_get_of_node(nand); + int strength, size; + + nand->ecc.user_conf.engine_type = of_get_nand_ecc_engine_type(dn); + nand->ecc.user_conf.algo = of_get_nand_ecc_algo(dn); + nand->ecc.user_conf.placement = of_get_nand_ecc_placement(dn); + + strength = of_get_nand_ecc_strength(dn); + if (strength >= 0) + nand->ecc.user_conf.strength = strength; + + size = of_get_nand_ecc_step_size(dn); + if (size >= 0) + nand->ecc.user_conf.step_size = size; + + if (of_property_read_bool(dn, "nand-ecc-maximize")) + nand->ecc.user_conf.flags |= NAND_ECC_MAXIMIZE_STRENGTH; +} +EXPORT_SYMBOL(of_get_nand_ecc_user_config); + +/** + * nand_ecc_is_strong_enough - Check if the chip configuration meets the + * datasheet requirements. + * + * @nand: Device to check + * + * If our configuration corrects A bits per B bytes and the minimum + * required correction level is X bits per Y bytes, then we must ensure + * both of the following are true: + * + * (1) A / B >= X / Y + * (2) A >= X + * + * Requirement (1) ensures we can correct for the required bitflip density. + * Requirement (2) ensures we can correct even when all bitflips are clumped + * in the same sector. + */ +bool nand_ecc_is_strong_enough(struct nand_device *nand) +{ + const struct nand_ecc_props *reqs = nanddev_get_ecc_requirements(nand); + const struct nand_ecc_props *conf = nanddev_get_ecc_conf(nand); + struct mtd_info *mtd = nanddev_to_mtd(nand); + int corr, ds_corr; + + if (conf->step_size == 0 || reqs->step_size == 0) + /* Not enough information */ + return true; + + /* + * We get the number of corrected bits per page to compare + * the correction density. + */ + corr = (mtd->writesize * conf->strength) / conf->step_size; + ds_corr = (mtd->writesize * reqs->strength) / reqs->step_size; + + return corr >= ds_corr && conf->strength >= reqs->strength; +} +EXPORT_SYMBOL(nand_ecc_is_strong_enough); + +/* ECC engine driver internal helpers */ +int nand_ecc_init_req_tweaking(struct nand_ecc_req_tweak_ctx *ctx, + struct nand_device *nand) +{ + unsigned int total_buffer_size; + + ctx->nand = nand; + + /* Let the user decide the exact length of each buffer */ + if (!ctx->page_buffer_size) + ctx->page_buffer_size = nanddev_page_size(nand); + if (!ctx->oob_buffer_size) + ctx->oob_buffer_size = nanddev_per_page_oobsize(nand); + + total_buffer_size = ctx->page_buffer_size + ctx->oob_buffer_size; + + ctx->spare_databuf = kzalloc(total_buffer_size, GFP_KERNEL); + if (!ctx->spare_databuf) + return -ENOMEM; + + ctx->spare_oobbuf = ctx->spare_databuf + ctx->page_buffer_size; + + return 0; +} +EXPORT_SYMBOL_GPL(nand_ecc_init_req_tweaking); + +void nand_ecc_cleanup_req_tweaking(struct nand_ecc_req_tweak_ctx *ctx) +{ + kfree(ctx->spare_databuf); +} +EXPORT_SYMBOL_GPL(nand_ecc_cleanup_req_tweaking); + +/* + * Ensure data and OOB area is fully read/written otherwise the correction might + * not work as expected. + */ +void nand_ecc_tweak_req(struct nand_ecc_req_tweak_ctx *ctx, + struct nand_page_io_req *req) +{ + struct nand_device *nand = ctx->nand; + struct nand_page_io_req *orig, *tweak; + + /* Save the original request */ + ctx->orig_req = *req; + ctx->bounce_data = false; + ctx->bounce_oob = false; + orig = &ctx->orig_req; + tweak = req; + + /* Ensure the request covers the entire page */ + if (orig->datalen < nanddev_page_size(nand)) { + ctx->bounce_data = true; + tweak->dataoffs = 0; + tweak->datalen = nanddev_page_size(nand); + tweak->databuf.in = ctx->spare_databuf; + memset(tweak->databuf.in, 0xFF, ctx->page_buffer_size); + } + + if (orig->ooblen < nanddev_per_page_oobsize(nand)) { + ctx->bounce_oob = true; + tweak->ooboffs = 0; + tweak->ooblen = nanddev_per_page_oobsize(nand); + tweak->oobbuf.in = ctx->spare_oobbuf; + memset(tweak->oobbuf.in, 0xFF, ctx->oob_buffer_size); + } + + /* 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, + orig->databuf.out, orig->datalen); + + if (ctx->bounce_oob) + memcpy((void *)tweak->oobbuf.out + orig->ooboffs, + orig->oobbuf.out, orig->ooblen); + } +} +EXPORT_SYMBOL_GPL(nand_ecc_tweak_req); + +void nand_ecc_restore_req(struct nand_ecc_req_tweak_ctx *ctx, + struct nand_page_io_req *req) +{ + struct nand_page_io_req *orig, *tweak; + + orig = &ctx->orig_req; + tweak = req; + + /* Restore the data read from the bounce buffers, if needed */ + if (orig->type == NAND_PAGE_READ) { + if (ctx->bounce_data) + memcpy(orig->databuf.in, + tweak->databuf.in + orig->dataoffs, + orig->datalen); + + if (ctx->bounce_oob) + memcpy(orig->oobbuf.in, + tweak->oobbuf.in + orig->ooboffs, + orig->ooblen); + } + + /* Ensure the original request is restored */ + *req = *orig; +} +EXPORT_SYMBOL_GPL(nand_ecc_restore_req); + +struct nand_ecc_engine *nand_ecc_get_sw_engine(struct nand_device *nand) +{ + unsigned int algo = nand->ecc.user_conf.algo; + + if (algo == NAND_ECC_ALGO_UNKNOWN) + algo = nand->ecc.defaults.algo; + + switch (algo) { + case NAND_ECC_ALGO_HAMMING: + return nand_ecc_sw_hamming_get_engine(); + case NAND_ECC_ALGO_BCH: + return nand_ecc_sw_bch_get_engine(); + default: + break; + } + + return NULL; +} +EXPORT_SYMBOL(nand_ecc_get_sw_engine); + +struct nand_ecc_engine *nand_ecc_get_on_die_hw_engine(struct nand_device *nand) +{ + return nand->ecc.ondie_engine; +} +EXPORT_SYMBOL(nand_ecc_get_on_die_hw_engine); + +int nand_ecc_register_on_host_hw_engine(struct nand_ecc_engine *engine) +{ + struct nand_ecc_engine *item; + + if (!engine) + return -EINVAL; + + /* Prevent multiple registrations of one engine */ + list_for_each_entry(item, &on_host_hw_engines, node) + if (item == engine) + return 0; + + mutex_lock(&on_host_hw_engines_mutex); + list_add_tail(&engine->node, &on_host_hw_engines); + mutex_unlock(&on_host_hw_engines_mutex); + + return 0; +} +EXPORT_SYMBOL(nand_ecc_register_on_host_hw_engine); + +int nand_ecc_unregister_on_host_hw_engine(struct nand_ecc_engine *engine) +{ + if (!engine) + return -EINVAL; + + mutex_lock(&on_host_hw_engines_mutex); + list_del(&engine->node); + mutex_unlock(&on_host_hw_engines_mutex); + + return 0; +} +EXPORT_SYMBOL(nand_ecc_unregister_on_host_hw_engine); + +static struct nand_ecc_engine *nand_ecc_match_on_host_hw_engine(struct device *dev) +{ + struct nand_ecc_engine *item; + + list_for_each_entry(item, &on_host_hw_engines, node) + if (item->dev == dev) + return item; + + return NULL; +} + +struct nand_ecc_engine *nand_ecc_get_on_host_hw_engine(struct nand_device *nand) +{ + struct nand_ecc_engine *engine = NULL; + struct device *dev = &nand->mtd.dev; + struct platform_device *pdev; + struct device_node *np; + + if (list_empty(&on_host_hw_engines)) + return NULL; + + /* Check for an explicit nand-ecc-engine property */ + np = of_parse_phandle(dev->of_node, "nand-ecc-engine", 0); + if (np) { + pdev = of_find_device_by_node(np); + if (!pdev) + return ERR_PTR(-EPROBE_DEFER); + + engine = nand_ecc_match_on_host_hw_engine(&pdev->dev); + platform_device_put(pdev); + of_node_put(np); + + if (!engine) + return ERR_PTR(-EPROBE_DEFER); + } + + if (engine) + get_device(engine->dev); + + return engine; +} +EXPORT_SYMBOL(nand_ecc_get_on_host_hw_engine); + +void nand_ecc_put_on_host_hw_engine(struct nand_device *nand) +{ + put_device(nand->ecc.engine->dev); +} +EXPORT_SYMBOL(nand_ecc_put_on_host_hw_engine); + +/* + * In the case of a pipelined engine, the device registering the ECC + * engine is not necessarily the ECC engine itself but may be a host controller. + * It is then useful to provide a helper to retrieve the right device object + * which actually represents the ECC engine. + */ +struct device *nand_ecc_get_engine_dev(struct device *host) +{ + struct platform_device *ecc_pdev; + struct device_node *np; + + /* + * If the device node contains this property, it means we need to follow + * it in order to get the right ECC engine device we are looking for. + */ + np = of_parse_phandle(host->of_node, "nand-ecc-engine", 0); + if (!np) + return host; + + ecc_pdev = of_find_device_by_node(np); + if (!ecc_pdev) { + of_node_put(np); + return NULL; + } + + platform_device_put(ecc_pdev); + of_node_put(np); + + return &ecc_pdev->dev; +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Miquel Raynal "); +MODULE_DESCRIPTION("Generic ECC engine"); diff --git a/drivers/mtd/nand/onenand/Kconfig b/drivers/mtd/nand/onenand/Kconfig new file mode 100644 index 000000000..7d57836bf --- /dev/null +++ b/drivers/mtd/nand/onenand/Kconfig @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig MTD_ONENAND + tristate "OneNAND Device Support" + depends on HAS_IOMEM + help + This enables support for accessing all type of OneNAND flash + devices. + +if MTD_ONENAND + +config MTD_ONENAND_VERIFY_WRITE + bool "Verify OneNAND page writes" + help + This adds an extra check when data is written to the flash. The + OneNAND flash device internally checks only bits transitioning + from 1 to 0. There is a rare possibility that even though the + device thinks the write was successful, a bit could have been + flipped accidentally due to device wear or something else. + +config MTD_ONENAND_GENERIC + tristate "OneNAND Flash device via platform device driver" + help + Support for OneNAND flash via platform device driver. + +config MTD_ONENAND_OMAP2 + tristate "OneNAND on OMAP2/OMAP3 support" + depends on ARCH_OMAP2 || ARCH_OMAP3 || (COMPILE_TEST && ARM) + depends on OF + depends on OMAP_GPMC + help + Support for a OneNAND flash device connected to an OMAP2/OMAP3 SoC + via the GPMC memory controller. + Enable dmaengine and gpiolib for better performance. + +config MTD_ONENAND_SAMSUNG + tristate "OneNAND on Samsung SOC controller support" + depends on ARCH_S3C64XX || ARCH_S5PV210 || COMPILE_TEST + help + Support for a OneNAND flash device connected to Samsung S3C64XX + (using command mapping method) and S5PC110/S5PC210 (using generic + OneNAND method) SoCs. + Choose Y here only if you build for such Samsung SoC. + +config MTD_ONENAND_OTP + bool "OneNAND OTP Support" + help + One Block of the NAND Flash Array memory is reserved as + a One-Time Programmable Block memory area. + Also, 1st Block of NAND Flash Array can be used as OTP. + + The OTP block can be read, programmed and locked using the same + operations as any other NAND Flash Array memory block. + OTP block cannot be erased. + + OTP block is fully-guaranteed to be a valid block. + +config MTD_ONENAND_2X_PROGRAM + bool "OneNAND 2X program support" + help + The 2X Program is an extension of Program Operation. + Since the device is equipped with two DataRAMs, and two-plane NAND + Flash memory array, these two component enables simultaneous program + of 4KiB. Plane1 has only even blocks such as block0, block2, block4 + while Plane2 has only odd blocks such as block1, block3, block5. + So MTD regards it as 4KiB page size and 256KiB block size + + Now the following chips support it. (KFXXX16Q2M) + Demux: KFG2G16Q2M, KFH4G16Q2M, KFW8G16Q2M, + Mux: KFM2G16Q2M, KFN4G16Q2M, + + And more recent chips + +endif # MTD_ONENAND diff --git a/drivers/mtd/nand/onenand/Makefile b/drivers/mtd/nand/onenand/Makefile new file mode 100644 index 000000000..a0761c7e0 --- /dev/null +++ b/drivers/mtd/nand/onenand/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the OneNAND MTD +# + +# Core functionality. +obj-$(CONFIG_MTD_ONENAND) += onenand.o + +# Board specific. +obj-$(CONFIG_MTD_ONENAND_GENERIC) += generic.o +obj-$(CONFIG_MTD_ONENAND_OMAP2) += onenand_omap2.o +obj-$(CONFIG_MTD_ONENAND_SAMSUNG) += onenand_samsung.o + +onenand-objs = onenand_base.o onenand_bbt.o diff --git a/drivers/mtd/nand/onenand/generic.c b/drivers/mtd/nand/onenand/generic.c new file mode 100644 index 000000000..3191904ce --- /dev/null +++ b/drivers/mtd/nand/onenand/generic.c @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2005 Samsung Electronics + * Kyungmin Park + * + * Overview: + * This is a device driver for the OneNAND flash for generic boards. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* + * Note: Driver name and platform data format have been updated! + * + * This version of the driver is named "onenand-flash" and takes struct + * onenand_platform_data as platform data. The old ARM-specific version + * with the name "onenand" used to take struct flash_platform_data. + */ +#define DRIVER_NAME "onenand-flash" + +struct onenand_info { + struct mtd_info mtd; + struct onenand_chip onenand; +}; + +static int generic_onenand_probe(struct platform_device *pdev) +{ + struct onenand_info *info; + struct onenand_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct resource *res = pdev->resource; + unsigned long size = resource_size(res); + int err; + + info = kzalloc_obj(struct onenand_info); + if (!info) + return -ENOMEM; + + if (!request_mem_region(res->start, size, dev_name(&pdev->dev))) { + err = -EBUSY; + goto out_free_info; + } + + info->onenand.base = ioremap(res->start, size); + if (!info->onenand.base) { + err = -ENOMEM; + goto out_release_mem_region; + } + + info->onenand.mmcontrol = pdata ? pdata->mmcontrol : NULL; + + err = platform_get_irq(pdev, 0); + if (err < 0) + goto out_iounmap; + + info->onenand.irq = err; + + info->mtd.dev.parent = &pdev->dev; + info->mtd.priv = &info->onenand; + + if (onenand_scan(&info->mtd, 1)) { + err = -ENXIO; + goto out_iounmap; + } + + err = mtd_device_register(&info->mtd, pdata ? pdata->parts : NULL, + pdata ? pdata->nr_parts : 0); + + platform_set_drvdata(pdev, info); + + return 0; + +out_iounmap: + iounmap(info->onenand.base); +out_release_mem_region: + release_mem_region(res->start, size); +out_free_info: + kfree(info); + + return err; +} + +static void generic_onenand_remove(struct platform_device *pdev) +{ + struct onenand_info *info = platform_get_drvdata(pdev); + struct resource *res = pdev->resource; + unsigned long size = resource_size(res); + + if (info) { + onenand_release(&info->mtd); + release_mem_region(res->start, size); + iounmap(info->onenand.base); + kfree(info); + } +} + +static struct platform_driver generic_onenand_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = generic_onenand_probe, + .remove = generic_onenand_remove, +}; + +module_platform_driver(generic_onenand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kyungmin Park "); +MODULE_DESCRIPTION("Glue layer for OneNAND flash on generic boards"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/mtd/nand/onenand/onenand_base.c b/drivers/mtd/nand/onenand/onenand_base.c new file mode 100644 index 000000000..d08aeac86 --- /dev/null +++ b/drivers/mtd/nand/onenand/onenand_base.c @@ -0,0 +1,4027 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright © 2005-2009 Samsung Electronics + * Copyright © 2007 Nokia Corporation + * + * Kyungmin Park + * + * Credits: + * Adrian Hunter : + * auto-placement support, read-while load support, various fixes + * + * Vishak G , Rohit Hagargundgi + * Flex-OneNAND support + * Amul Kumar Saha + * OTP support + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* + * Multiblock erase if number of blocks to erase is 2 or more. + * Maximum number of blocks for simultaneous erase is 64. + */ +#define MB_ERASE_MIN_BLK_COUNT 2 +#define MB_ERASE_MAX_BLK_COUNT 64 + +/* Default Flex-OneNAND boundary and lock respectively */ +static int flex_bdry[MAX_DIES * 2] = { -1, 0, -1, 0 }; + +module_param_array(flex_bdry, int, NULL, 0400); +MODULE_PARM_DESC(flex_bdry, "SLC Boundary information for Flex-OneNAND" + "Syntax:flex_bdry=DIE_BDRY,LOCK,..." + "DIE_BDRY: SLC boundary of the die" + "LOCK: Locking information for SLC boundary" + " : 0->Set boundary in unlocked status" + " : 1->Set boundary in locked status"); + +/* Default OneNAND/Flex-OneNAND OTP options*/ +static int otp; + +module_param(otp, int, 0400); +MODULE_PARM_DESC(otp, "Corresponding behaviour of OneNAND in OTP" + "Syntax : otp=LOCK_TYPE" + "LOCK_TYPE : Keys issued, for specific OTP Lock type" + " : 0 -> Default (No Blocks Locked)" + " : 1 -> OTP Block lock" + " : 2 -> 1st Block lock" + " : 3 -> BOTH OTP Block and 1st Block lock"); + +/* + * flexonenand_oob_128 - oob info for Flex-Onenand with 4KB page + * For now, we expose only 64 out of 80 ecc bytes + */ +static int flexonenand_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 7) + return -ERANGE; + + oobregion->offset = (section * 16) + 6; + oobregion->length = 10; + + return 0; +} + +static int flexonenand_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 7) + return -ERANGE; + + oobregion->offset = (section * 16) + 2; + oobregion->length = 4; + + return 0; +} + +static const struct mtd_ooblayout_ops flexonenand_ooblayout_ops = { + .ecc = flexonenand_ooblayout_ecc, + .free = flexonenand_ooblayout_free, +}; + +/* + * onenand_oob_128 - oob info for OneNAND with 4KB page + * + * Based on specification: + * 4Gb M-die OneNAND Flash (KFM4G16Q4M, KFN8G16Q4M). Rev. 1.3, Apr. 2010 + * + */ +static int onenand_ooblayout_128_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 7) + return -ERANGE; + + oobregion->offset = (section * 16) + 7; + oobregion->length = 9; + + return 0; +} + +static int onenand_ooblayout_128_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section >= 8) + return -ERANGE; + + /* + * free bytes are using the spare area fields marked as + * "Managed by internal ECC logic for Logical Sector Number area" + */ + oobregion->offset = (section * 16) + 2; + oobregion->length = 3; + + return 0; +} + +static const struct mtd_ooblayout_ops onenand_oob_128_ooblayout_ops = { + .ecc = onenand_ooblayout_128_ecc, + .free = onenand_ooblayout_128_free, +}; + +/* + * onenand_oob_32_64 - oob info for large (2KB) page + */ +static int onenand_ooblayout_32_64_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 3) + return -ERANGE; + + oobregion->offset = (section * 16) + 8; + oobregion->length = 5; + + return 0; +} + +static int onenand_ooblayout_32_64_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + int sections = (mtd->oobsize / 32) * 2; + + if (section >= sections) + return -ERANGE; + + if (section & 1) { + oobregion->offset = ((section - 1) * 16) + 14; + oobregion->length = 2; + } else { + oobregion->offset = (section * 16) + 2; + oobregion->length = 3; + } + + return 0; +} + +static const struct mtd_ooblayout_ops onenand_oob_32_64_ooblayout_ops = { + .ecc = onenand_ooblayout_32_64_ecc, + .free = onenand_ooblayout_32_64_free, +}; + +static const unsigned char ffchars[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 16 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 32 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 48 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 64 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 80 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 96 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 112 */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, /* 128 */ +}; + +/** + * onenand_readw - [OneNAND Interface] Read OneNAND register + * @addr: address to read + * + * Read OneNAND register + */ +static unsigned short onenand_readw(void __iomem *addr) +{ + return readw(addr); +} + +/** + * onenand_writew - [OneNAND Interface] Write OneNAND register with value + * @value: value to write + * @addr: address to write + * + * Write OneNAND register with value + */ +static void onenand_writew(unsigned short value, void __iomem *addr) +{ + writew(value, addr); +} + +/** + * onenand_block_address - [DEFAULT] Get block address + * @this: onenand chip data structure + * @block: the block + * @return translated block address if DDP, otherwise same + * + * Setup Start Address 1 Register (F100h) + */ +static int onenand_block_address(struct onenand_chip *this, int block) +{ + /* Device Flash Core select, NAND Flash Block Address */ + if (block & this->density_mask) + return ONENAND_DDP_CHIP1 | (block ^ this->density_mask); + + return block; +} + +/** + * onenand_bufferram_address - [DEFAULT] Get bufferram address + * @this: onenand chip data structure + * @block: the block + * @return set DBS value if DDP, otherwise 0 + * + * Setup Start Address 2 Register (F101h) for DDP + */ +static int onenand_bufferram_address(struct onenand_chip *this, int block) +{ + /* Device BufferRAM Select */ + if (block & this->density_mask) + return ONENAND_DDP_CHIP1; + + return ONENAND_DDP_CHIP0; +} + +/** + * onenand_page_address - [DEFAULT] Get page address + * @page: the page address + * @sector: the sector address + * @return combined page and sector address + * + * Setup Start Address 8 Register (F107h) + */ +static int onenand_page_address(int page, int sector) +{ + /* Flash Page Address, Flash Sector Address */ + int fpa, fsa; + + fpa = page & ONENAND_FPA_MASK; + fsa = sector & ONENAND_FSA_MASK; + + return ((fpa << ONENAND_FPA_SHIFT) | fsa); +} + +/** + * onenand_buffer_address - [DEFAULT] Get buffer address + * @dataram1: DataRAM index + * @sectors: the sector address + * @count: the number of sectors + * Return: the start buffer value + * + * Setup Start Buffer Register (F200h) + */ +static int onenand_buffer_address(int dataram1, int sectors, int count) +{ + int bsa, bsc; + + /* BufferRAM Sector Address */ + bsa = sectors & ONENAND_BSA_MASK; + + if (dataram1) + bsa |= ONENAND_BSA_DATARAM1; /* DataRAM1 */ + else + bsa |= ONENAND_BSA_DATARAM0; /* DataRAM0 */ + + /* BufferRAM Sector Count */ + bsc = count & ONENAND_BSC_MASK; + + return ((bsa << ONENAND_BSA_SHIFT) | bsc); +} + +/** + * flexonenand_block- For given address return block number + * @this: - OneNAND device structure + * @addr: - Address for which block number is needed + */ +static unsigned flexonenand_block(struct onenand_chip *this, loff_t addr) +{ + unsigned boundary, blk, die = 0; + + if (ONENAND_IS_DDP(this) && addr >= this->diesize[0]) { + die = 1; + addr -= this->diesize[0]; + } + + boundary = this->boundary[die]; + + blk = addr >> (this->erase_shift - 1); + if (blk > boundary) + blk = (blk + boundary + 1) >> 1; + + blk += die ? this->density_mask : 0; + return blk; +} + +inline unsigned onenand_block(struct onenand_chip *this, loff_t addr) +{ + if (!FLEXONENAND(this)) + return addr >> this->erase_shift; + return flexonenand_block(this, addr); +} + +/** + * flexonenand_addr - Return address of the block + * @this: OneNAND device structure + * @block: Block number on Flex-OneNAND + * + * Return address of the block + */ +static loff_t flexonenand_addr(struct onenand_chip *this, int block) +{ + loff_t ofs = 0; + int die = 0, boundary; + + if (ONENAND_IS_DDP(this) && block >= this->density_mask) { + block -= this->density_mask; + die = 1; + ofs = this->diesize[0]; + } + + boundary = this->boundary[die]; + ofs += (loff_t)block << (this->erase_shift - 1); + if (block > (boundary + 1)) + ofs += (loff_t)(block - boundary - 1) << (this->erase_shift - 1); + return ofs; +} + +loff_t onenand_addr(struct onenand_chip *this, int block) +{ + if (!FLEXONENAND(this)) + return (loff_t)block << this->erase_shift; + return flexonenand_addr(this, block); +} +EXPORT_SYMBOL(onenand_addr); + +/** + * onenand_get_density - [DEFAULT] Get OneNAND density + * @dev_id: OneNAND device ID + * + * Get OneNAND density from device ID + */ +static inline int onenand_get_density(int dev_id) +{ + int density = dev_id >> ONENAND_DEVICE_DENSITY_SHIFT; + return (density & ONENAND_DEVICE_DENSITY_MASK); +} + +/** + * flexonenand_region - [Flex-OneNAND] Return erase region of addr + * @mtd: MTD device structure + * @addr: address whose erase region needs to be identified + */ +int flexonenand_region(struct mtd_info *mtd, loff_t addr) +{ + int i; + + for (i = 0; i < mtd->numeraseregions; i++) + if (addr < mtd->eraseregions[i].offset) + break; + return i - 1; +} +EXPORT_SYMBOL(flexonenand_region); + +/** + * onenand_command - [DEFAULT] Send command to OneNAND device + * @mtd: MTD device structure + * @cmd: the command to be sent + * @addr: offset to read from or write to + * @len: number of bytes to read or write + * + * Send command to OneNAND device. This function is used for middle/large page + * devices (1KB/2KB Bytes per page) + */ +static int onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, size_t len) +{ + struct onenand_chip *this = mtd->priv; + int value, block, page; + + /* Address translation */ + switch (cmd) { + case ONENAND_CMD_UNLOCK: + case ONENAND_CMD_LOCK: + case ONENAND_CMD_LOCK_TIGHT: + case ONENAND_CMD_UNLOCK_ALL: + block = -1; + page = -1; + break; + + case FLEXONENAND_CMD_PI_ACCESS: + /* addr contains die index */ + block = addr * this->density_mask; + page = -1; + break; + + case ONENAND_CMD_ERASE: + case ONENAND_CMD_MULTIBLOCK_ERASE: + case ONENAND_CMD_ERASE_VERIFY: + case ONENAND_CMD_BUFFERRAM: + case ONENAND_CMD_OTP_ACCESS: + block = onenand_block(this, addr); + page = -1; + break; + + case FLEXONENAND_CMD_READ_PI: + cmd = ONENAND_CMD_READ; + block = addr * this->density_mask; + page = 0; + break; + + default: + block = onenand_block(this, addr); + if (FLEXONENAND(this)) + page = (int) (addr - onenand_addr(this, block))>>\ + this->page_shift; + else + page = (int) (addr >> this->page_shift); + if (ONENAND_IS_2PLANE(this)) { + /* Make the even block number */ + block &= ~1; + /* Is it the odd plane? */ + if (addr & this->writesize) + block++; + page >>= 1; + } + page &= this->page_mask; + break; + } + + /* NOTE: The setting order of the registers is very important! */ + if (cmd == ONENAND_CMD_BUFFERRAM) { + /* Select DataRAM for DDP */ + value = onenand_bufferram_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); + + if (ONENAND_IS_2PLANE(this) || ONENAND_IS_4KB_PAGE(this)) + /* It is always BufferRAM0 */ + ONENAND_SET_BUFFERRAM0(this); + else + /* Switch to the next data buffer */ + ONENAND_SET_NEXT_BUFFERRAM(this); + + return 0; + } + + if (block != -1) { + /* Write 'DFS, FBA' of Flash */ + value = onenand_block_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1); + + /* Select DataRAM for DDP */ + value = onenand_bufferram_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); + } + + if (page != -1) { + /* Now we use page size operation */ + int sectors = 0, count = 0; + int dataram; + + switch (cmd) { + case FLEXONENAND_CMD_RECOVER_LSB: + case ONENAND_CMD_READ: + case ONENAND_CMD_READOOB: + if (ONENAND_IS_4KB_PAGE(this)) + /* It is always BufferRAM0 */ + dataram = ONENAND_SET_BUFFERRAM0(this); + else + dataram = ONENAND_SET_NEXT_BUFFERRAM(this); + break; + + default: + if (ONENAND_IS_2PLANE(this) && cmd == ONENAND_CMD_PROG) + cmd = ONENAND_CMD_2X_PROG; + dataram = ONENAND_CURRENT_BUFFERRAM(this); + break; + } + + /* Write 'FPA, FSA' of Flash */ + value = onenand_page_address(page, sectors); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS8); + + /* Write 'BSA, BSC' of DataRAM */ + value = onenand_buffer_address(dataram, sectors, count); + this->write_word(value, this->base + ONENAND_REG_START_BUFFER); + } + + /* Interrupt clear */ + this->write_word(ONENAND_INT_CLEAR, this->base + ONENAND_REG_INTERRUPT); + + /* Write command */ + this->write_word(cmd, this->base + ONENAND_REG_COMMAND); + + return 0; +} + +/** + * onenand_read_ecc - return ecc status + * @this: onenand chip structure + */ +static inline int onenand_read_ecc(struct onenand_chip *this) +{ + int ecc, i, result = 0; + + if (!FLEXONENAND(this) && !ONENAND_IS_4KB_PAGE(this)) + return this->read_word(this->base + ONENAND_REG_ECC_STATUS); + + for (i = 0; i < 4; i++) { + ecc = this->read_word(this->base + ONENAND_REG_ECC_STATUS + i*2); + if (likely(!ecc)) + continue; + if (ecc & FLEXONENAND_UNCORRECTABLE_ERROR) + return ONENAND_ECC_2BIT_ALL; + else + result = ONENAND_ECC_1BIT_ALL; + } + + return result; +} + +/** + * onenand_wait - [DEFAULT] wait until the command is done + * @mtd: MTD device structure + * @state: state to select the max. timeout value + * + * Wait for command done. This applies to all OneNAND command + * Read can take up to 30us, erase up to 2ms and program up to 350us + * according to general OneNAND specs + */ +static int onenand_wait(struct mtd_info *mtd, int state) +{ + struct onenand_chip * this = mtd->priv; + unsigned long timeout; + unsigned int flags = ONENAND_INT_MASTER; + unsigned int interrupt = 0; + unsigned int ctrl; + + /* The 20 msec is enough */ + timeout = jiffies + msecs_to_jiffies(20); + while (time_before(jiffies, timeout)) { + interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); + + if (interrupt & flags) + break; + + if (state != FL_READING && state != FL_PREPARING_ERASE) + cond_resched(); + } + /* To get correct interrupt status in timeout case */ + interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); + + ctrl = this->read_word(this->base + ONENAND_REG_CTRL_STATUS); + + /* + * In the Spec. it checks the controller status first + * However if you get the correct information in case of + * power off recovery (POR) test, it should read ECC status first + */ + if (interrupt & ONENAND_INT_READ) { + int ecc = onenand_read_ecc(this); + if (ecc) { + if (ecc & ONENAND_ECC_2BIT_ALL) { + printk(KERN_ERR "%s: ECC error = 0x%04x\n", + __func__, ecc); + mtd->ecc_stats.failed++; + return -EBADMSG; + } else if (ecc & ONENAND_ECC_1BIT_ALL) { + printk(KERN_DEBUG "%s: correctable ECC error = 0x%04x\n", + __func__, ecc); + mtd->ecc_stats.corrected++; + } + } + } else if (state == FL_READING) { + printk(KERN_ERR "%s: read timeout! ctrl=0x%04x intr=0x%04x\n", + __func__, ctrl, interrupt); + return -EIO; + } + + if (state == FL_PREPARING_ERASE && !(interrupt & ONENAND_INT_ERASE)) { + printk(KERN_ERR "%s: mb erase timeout! ctrl=0x%04x intr=0x%04x\n", + __func__, ctrl, interrupt); + return -EIO; + } + + if (!(interrupt & ONENAND_INT_MASTER)) { + printk(KERN_ERR "%s: timeout! ctrl=0x%04x intr=0x%04x\n", + __func__, ctrl, interrupt); + return -EIO; + } + + /* If there's controller error, it's a real error */ + if (ctrl & ONENAND_CTRL_ERROR) { + printk(KERN_ERR "%s: controller error = 0x%04x\n", + __func__, ctrl); + if (ctrl & ONENAND_CTRL_LOCK) + printk(KERN_ERR "%s: it's locked error.\n", __func__); + return -EIO; + } + + return 0; +} + +/* + * onenand_interrupt - [DEFAULT] onenand interrupt handler + * @irq: onenand interrupt number + * @dev_id: interrupt data + * + * complete the work + */ +static irqreturn_t onenand_interrupt(int irq, void *data) +{ + struct onenand_chip *this = data; + + /* To handle shared interrupt */ + if (!this->complete.done) + complete(&this->complete); + + return IRQ_HANDLED; +} + +/* + * onenand_interrupt_wait - [DEFAULT] wait until the command is done + * @mtd: MTD device structure + * @state: state to select the max. timeout value + * + * Wait for command done. + */ +static int onenand_interrupt_wait(struct mtd_info *mtd, int state) +{ + struct onenand_chip *this = mtd->priv; + + wait_for_completion(&this->complete); + + return onenand_wait(mtd, state); +} + +/* + * onenand_try_interrupt_wait - [DEFAULT] try interrupt wait + * @mtd: MTD device structure + * @state: state to select the max. timeout value + * + * Try interrupt based wait (It is used one-time) + */ +static int onenand_try_interrupt_wait(struct mtd_info *mtd, int state) +{ + struct onenand_chip *this = mtd->priv; + unsigned long remain, timeout; + + /* We use interrupt wait first */ + this->wait = onenand_interrupt_wait; + + timeout = msecs_to_jiffies(100); + remain = wait_for_completion_timeout(&this->complete, timeout); + if (!remain) { + printk(KERN_INFO "OneNAND: There's no interrupt. " + "We use the normal wait\n"); + + /* Release the irq */ + free_irq(this->irq, this); + + this->wait = onenand_wait; + } + + return onenand_wait(mtd, state); +} + +/* + * onenand_setup_wait - [OneNAND Interface] setup onenand wait method + * @mtd: MTD device structure + * + * There's two method to wait onenand work + * 1. polling - read interrupt status register + * 2. interrupt - use the kernel interrupt method + */ +static void onenand_setup_wait(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + int syscfg; + + init_completion(&this->complete); + + if (this->irq <= 0) { + this->wait = onenand_wait; + return; + } + + if (request_irq(this->irq, &onenand_interrupt, + IRQF_SHARED, "onenand", this)) { + /* If we can't get irq, use the normal wait */ + this->wait = onenand_wait; + return; + } + + /* Enable interrupt */ + syscfg = this->read_word(this->base + ONENAND_REG_SYS_CFG1); + syscfg |= ONENAND_SYS_CFG1_IOBE; + this->write_word(syscfg, this->base + ONENAND_REG_SYS_CFG1); + + this->wait = onenand_try_interrupt_wait; +} + +/** + * onenand_bufferram_offset - [DEFAULT] BufferRAM offset + * @mtd: MTD data structure + * @area: BufferRAM area + * @return offset given area + * + * Return BufferRAM offset given area + */ +static inline int onenand_bufferram_offset(struct mtd_info *mtd, int area) +{ + struct onenand_chip *this = mtd->priv; + + if (ONENAND_CURRENT_BUFFERRAM(this)) { + /* Note: the 'this->writesize' is a real page size */ + if (area == ONENAND_DATARAM) + return this->writesize; + if (area == ONENAND_SPARERAM) + return mtd->oobsize; + } + + return 0; +} + +/** + * onenand_read_bufferram - [OneNAND Interface] Read the bufferram area + * @mtd: MTD data structure + * @area: BufferRAM area + * @buffer: the databuffer to put/get data + * @offset: offset to read from or write to + * @count: number of bytes to read/write + * + * Read the BufferRAM area + */ +static int onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, size_t count) +{ + struct onenand_chip *this = mtd->priv; + void __iomem *bufferram; + + bufferram = this->base + area; + + bufferram += onenand_bufferram_offset(mtd, area); + + if (ONENAND_CHECK_BYTE_ACCESS(count)) { + unsigned short word; + + /* Align with word(16-bit) size */ + count--; + + /* Read word and save byte */ + word = this->read_word(bufferram + offset + count); + buffer[count] = (word & 0xff); + } + + memcpy(buffer, bufferram + offset, count); + + return 0; +} + +/** + * onenand_sync_read_bufferram - [OneNAND Interface] Read the bufferram area with Sync. Burst mode + * @mtd: MTD data structure + * @area: BufferRAM area + * @buffer: the databuffer to put/get data + * @offset: offset to read from or write to + * @count: number of bytes to read/write + * + * Read the BufferRAM area with Sync. Burst Mode + */ +static int onenand_sync_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, size_t count) +{ + struct onenand_chip *this = mtd->priv; + void __iomem *bufferram; + + bufferram = this->base + area; + + bufferram += onenand_bufferram_offset(mtd, area); + + this->mmcontrol(mtd, ONENAND_SYS_CFG1_SYNC_READ); + + if (ONENAND_CHECK_BYTE_ACCESS(count)) { + unsigned short word; + + /* Align with word(16-bit) size */ + count--; + + /* Read word and save byte */ + word = this->read_word(bufferram + offset + count); + buffer[count] = (word & 0xff); + } + + memcpy(buffer, bufferram + offset, count); + + this->mmcontrol(mtd, 0); + + return 0; +} + +/** + * onenand_write_bufferram - [OneNAND Interface] Write the bufferram area + * @mtd: MTD data structure + * @area: BufferRAM area + * @buffer: the databuffer to put/get data + * @offset: offset to read from or write to + * @count: number of bytes to read/write + * + * Write the BufferRAM area + */ +static int onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, int offset, size_t count) +{ + struct onenand_chip *this = mtd->priv; + void __iomem *bufferram; + + bufferram = this->base + area; + + bufferram += onenand_bufferram_offset(mtd, area); + + if (ONENAND_CHECK_BYTE_ACCESS(count)) { + unsigned short word; + int byte_offset; + + /* Align with word(16-bit) size */ + count--; + + /* Calculate byte access offset */ + byte_offset = offset + count; + + /* Read word and save byte */ + word = this->read_word(bufferram + byte_offset); + word = (word & ~0xff) | buffer[count]; + this->write_word(word, bufferram + byte_offset); + } + + memcpy(bufferram + offset, buffer, count); + + return 0; +} + +/** + * onenand_get_2x_blockpage - [GENERIC] Get blockpage at 2x program mode + * @mtd: MTD data structure + * @addr: address to check + * @return blockpage address + * + * Get blockpage address at 2x program mode + */ +static int onenand_get_2x_blockpage(struct mtd_info *mtd, loff_t addr) +{ + struct onenand_chip *this = mtd->priv; + int blockpage, block, page; + + /* Calculate the even block number */ + block = (int) (addr >> this->erase_shift) & ~1; + /* Is it the odd plane? */ + if (addr & this->writesize) + block++; + page = (int) (addr >> (this->page_shift + 1)) & this->page_mask; + blockpage = (block << 7) | page; + + return blockpage; +} + +/** + * onenand_check_bufferram - [GENERIC] Check BufferRAM information + * @mtd: MTD data structure + * @addr: address to check + * @return 1 if there are valid data, otherwise 0 + * + * Check bufferram if there is data we required + */ +static int onenand_check_bufferram(struct mtd_info *mtd, loff_t addr) +{ + struct onenand_chip *this = mtd->priv; + int blockpage, found = 0; + unsigned int i; + + if (ONENAND_IS_2PLANE(this)) + blockpage = onenand_get_2x_blockpage(mtd, addr); + else + blockpage = (int) (addr >> this->page_shift); + + /* Is there valid data? */ + i = ONENAND_CURRENT_BUFFERRAM(this); + if (this->bufferram[i].blockpage == blockpage) + found = 1; + else { + /* Check another BufferRAM */ + i = ONENAND_NEXT_BUFFERRAM(this); + if (this->bufferram[i].blockpage == blockpage) { + ONENAND_SET_NEXT_BUFFERRAM(this); + found = 1; + } + } + + if (found && ONENAND_IS_DDP(this)) { + /* Select DataRAM for DDP */ + int block = onenand_block(this, addr); + int value = onenand_bufferram_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); + } + + return found; +} + +/** + * onenand_update_bufferram - [GENERIC] Update BufferRAM information + * @mtd: MTD data structure + * @addr: address to update + * @valid: valid flag + * + * Update BufferRAM information + */ +static void onenand_update_bufferram(struct mtd_info *mtd, loff_t addr, + int valid) +{ + struct onenand_chip *this = mtd->priv; + int blockpage; + unsigned int i; + + if (ONENAND_IS_2PLANE(this)) + blockpage = onenand_get_2x_blockpage(mtd, addr); + else + blockpage = (int) (addr >> this->page_shift); + + /* Invalidate another BufferRAM */ + i = ONENAND_NEXT_BUFFERRAM(this); + if (this->bufferram[i].blockpage == blockpage) + this->bufferram[i].blockpage = -1; + + /* Update BufferRAM */ + i = ONENAND_CURRENT_BUFFERRAM(this); + if (valid) + this->bufferram[i].blockpage = blockpage; + else + this->bufferram[i].blockpage = -1; +} + +/** + * onenand_invalidate_bufferram - [GENERIC] Invalidate BufferRAM information + * @mtd: MTD data structure + * @addr: start address to invalidate + * @len: length to invalidate + * + * Invalidate BufferRAM information + */ +static void onenand_invalidate_bufferram(struct mtd_info *mtd, loff_t addr, + unsigned int len) +{ + struct onenand_chip *this = mtd->priv; + int i; + loff_t end_addr = addr + len; + + /* Invalidate BufferRAM */ + for (i = 0; i < MAX_BUFFERRAM; i++) { + loff_t buf_addr = this->bufferram[i].blockpage << this->page_shift; + if (buf_addr >= addr && buf_addr < end_addr) + this->bufferram[i].blockpage = -1; + } +} + +/** + * onenand_get_device - [GENERIC] Get chip for selected access + * @mtd: MTD device structure + * @new_state: the state which is requested + * + * Get the device and lock it for exclusive access + */ +static int onenand_get_device(struct mtd_info *mtd, int new_state) +{ + struct onenand_chip *this = mtd->priv; + DECLARE_WAITQUEUE(wait, current); + + /* + * Grab the lock and see if the device is available + */ + while (1) { + spin_lock(&this->chip_lock); + if (this->state == FL_READY) { + this->state = new_state; + spin_unlock(&this->chip_lock); + if (new_state != FL_PM_SUSPENDED && this->enable) + this->enable(mtd); + break; + } + if (new_state == FL_PM_SUSPENDED) { + spin_unlock(&this->chip_lock); + return (this->state == FL_PM_SUSPENDED) ? 0 : -EAGAIN; + } + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&this->wq, &wait); + spin_unlock(&this->chip_lock); + schedule(); + remove_wait_queue(&this->wq, &wait); + } + + return 0; +} + +/** + * onenand_release_device - [GENERIC] release chip + * @mtd: MTD device structure + * + * Deselect, release chip lock and wake up anyone waiting on the device + */ +static void onenand_release_device(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + + if (this->state != FL_PM_SUSPENDED && this->disable) + this->disable(mtd); + /* Release the chip */ + spin_lock(&this->chip_lock); + this->state = FL_READY; + wake_up(&this->wq); + spin_unlock(&this->chip_lock); +} + +/** + * onenand_transfer_auto_oob - [INTERN] oob auto-placement transfer + * @mtd: MTD device structure + * @buf: destination address + * @column: oob offset to read from + * @thislen: oob length to read + */ +static int onenand_transfer_auto_oob(struct mtd_info *mtd, uint8_t *buf, int column, + int thislen) +{ + struct onenand_chip *this = mtd->priv; + + this->read_bufferram(mtd, ONENAND_SPARERAM, this->oob_buf, 0, + mtd->oobsize); + return mtd_ooblayout_get_databytes(mtd, buf, this->oob_buf, + column, thislen); +} + +/** + * onenand_recover_lsb - [Flex-OneNAND] Recover LSB page data + * @mtd: MTD device structure + * @addr: address to recover + * @status: return value from onenand_wait / onenand_bbt_wait + * + * MLC NAND Flash cell has paired pages - LSB page and MSB page. LSB page has + * lower page address and MSB page has higher page address in paired pages. + * If power off occurs during MSB page program, the paired LSB page data can + * become corrupt. LSB page recovery read is a way to read LSB page though page + * data are corrupted. When uncorrectable error occurs as a result of LSB page + * read after power up, issue LSB page recovery read. + */ +static int onenand_recover_lsb(struct mtd_info *mtd, loff_t addr, int status) +{ + struct onenand_chip *this = mtd->priv; + int i; + + /* Recovery is only for Flex-OneNAND */ + if (!FLEXONENAND(this)) + return status; + + /* check if we failed due to uncorrectable error */ + if (!mtd_is_eccerr(status) && status != ONENAND_BBT_READ_ECC_ERROR) + return status; + + /* check if address lies in MLC region */ + i = flexonenand_region(mtd, addr); + if (mtd->eraseregions[i].erasesize < (1 << this->erase_shift)) + return status; + + /* We are attempting to reread, so decrement stats.failed + * which was incremented by onenand_wait due to read failure + */ + printk(KERN_INFO "%s: Attempting to recover from uncorrectable read\n", + __func__); + mtd->ecc_stats.failed--; + + /* Issue the LSB page recovery command */ + this->command(mtd, FLEXONENAND_CMD_RECOVER_LSB, addr, this->writesize); + return this->wait(mtd, FL_READING); +} + +/** + * onenand_mlc_read_ops_nolock - MLC OneNAND read main and/or out-of-band + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob operation description structure + * + * MLC OneNAND / Flex-OneNAND has 4KB page size and 4KB dataram. + * So, read-while-load is not present. + */ +static int onenand_mlc_read_ops_nolock(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct onenand_chip *this = mtd->priv; + struct mtd_ecc_stats stats; + size_t len = ops->len; + size_t ooblen = ops->ooblen; + u_char *buf = ops->datbuf; + u_char *oobbuf = ops->oobbuf; + int read = 0, column, thislen; + int oobread = 0, oobcolumn, thisooblen, oobsize; + int ret = 0; + int writesize = this->writesize; + + pr_debug("%s: from = 0x%08x, len = %i\n", __func__, (unsigned int)from, + (int)len); + + oobsize = mtd_oobavail(mtd, ops); + oobcolumn = from & (mtd->oobsize - 1); + + /* Do not allow reads past end of device */ + if (from + len > mtd->size) { + printk(KERN_ERR "%s: Attempt read beyond end of device\n", + __func__); + ops->retlen = 0; + ops->oobretlen = 0; + return -EINVAL; + } + + stats = mtd->ecc_stats; + + while (read < len) { + cond_resched(); + + thislen = min_t(int, writesize, len - read); + + column = from & (writesize - 1); + if (column + thislen > writesize) + thislen = writesize - column; + + if (!onenand_check_bufferram(mtd, from)) { + this->command(mtd, ONENAND_CMD_READ, from, writesize); + + ret = this->wait(mtd, FL_READING); + if (unlikely(ret)) + ret = onenand_recover_lsb(mtd, from, ret); + onenand_update_bufferram(mtd, from, !ret); + if (mtd_is_eccerr(ret)) + ret = 0; + if (ret) + break; + } + + this->read_bufferram(mtd, ONENAND_DATARAM, buf, column, thislen); + if (oobbuf) { + thisooblen = oobsize - oobcolumn; + thisooblen = min_t(int, thisooblen, ooblen - oobread); + + if (ops->mode == MTD_OPS_AUTO_OOB) + onenand_transfer_auto_oob(mtd, oobbuf, oobcolumn, thisooblen); + else + this->read_bufferram(mtd, ONENAND_SPARERAM, oobbuf, oobcolumn, thisooblen); + oobread += thisooblen; + oobbuf += thisooblen; + oobcolumn = 0; + } + + read += thislen; + if (read == len) + break; + + from += thislen; + buf += thislen; + } + + /* + * Return success, if no ECC failures, else -EBADMSG + * fs driver will take care of that, because + * retlen == desired len and result == -EBADMSG + */ + ops->retlen = read; + ops->oobretlen = oobread; + + if (ret) + return ret; + + if (mtd->ecc_stats.failed - stats.failed) + return -EBADMSG; + + /* return max bitflips per ecc step; ONENANDs correct 1 bit only */ + return mtd->ecc_stats.corrected != stats.corrected ? 1 : 0; +} + +/** + * onenand_read_ops_nolock - [OneNAND Interface] OneNAND read main and/or out-of-band + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob operation description structure + * + * OneNAND read main and/or out-of-band data + */ +static int onenand_read_ops_nolock(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct onenand_chip *this = mtd->priv; + struct mtd_ecc_stats stats; + size_t len = ops->len; + size_t ooblen = ops->ooblen; + u_char *buf = ops->datbuf; + u_char *oobbuf = ops->oobbuf; + int read = 0, column, thislen; + int oobread = 0, oobcolumn, thisooblen, oobsize; + int ret = 0, boundary = 0; + int writesize = this->writesize; + + pr_debug("%s: from = 0x%08x, len = %i\n", __func__, (unsigned int)from, + (int)len); + + oobsize = mtd_oobavail(mtd, ops); + oobcolumn = from & (mtd->oobsize - 1); + + /* Do not allow reads past end of device */ + if ((from + len) > mtd->size) { + printk(KERN_ERR "%s: Attempt read beyond end of device\n", + __func__); + ops->retlen = 0; + ops->oobretlen = 0; + return -EINVAL; + } + + stats = mtd->ecc_stats; + + /* Read-while-load method */ + + /* Do first load to bufferRAM */ + if (read < len) { + if (!onenand_check_bufferram(mtd, from)) { + this->command(mtd, ONENAND_CMD_READ, from, writesize); + ret = this->wait(mtd, FL_READING); + onenand_update_bufferram(mtd, from, !ret); + if (mtd_is_eccerr(ret)) + ret = 0; + } + } + + thislen = min_t(int, writesize, len - read); + column = from & (writesize - 1); + if (column + thislen > writesize) + thislen = writesize - column; + + while (!ret) { + /* If there is more to load then start next load */ + from += thislen; + if (read + thislen < len) { + this->command(mtd, ONENAND_CMD_READ, from, writesize); + /* + * Chip boundary handling in DDP + * Now we issued chip 1 read and pointed chip 1 + * bufferram so we have to point chip 0 bufferram. + */ + if (ONENAND_IS_DDP(this) && + unlikely(from == (this->chipsize >> 1))) { + this->write_word(ONENAND_DDP_CHIP0, this->base + ONENAND_REG_START_ADDRESS2); + boundary = 1; + } else + boundary = 0; + ONENAND_SET_PREV_BUFFERRAM(this); + } + /* While load is going, read from last bufferRAM */ + this->read_bufferram(mtd, ONENAND_DATARAM, buf, column, thislen); + + /* Read oob area if needed */ + if (oobbuf) { + thisooblen = oobsize - oobcolumn; + thisooblen = min_t(int, thisooblen, ooblen - oobread); + + if (ops->mode == MTD_OPS_AUTO_OOB) + onenand_transfer_auto_oob(mtd, oobbuf, oobcolumn, thisooblen); + else + this->read_bufferram(mtd, ONENAND_SPARERAM, oobbuf, oobcolumn, thisooblen); + oobread += thisooblen; + oobbuf += thisooblen; + oobcolumn = 0; + } + + /* See if we are done */ + read += thislen; + if (read == len) + break; + /* Set up for next read from bufferRAM */ + if (unlikely(boundary)) + this->write_word(ONENAND_DDP_CHIP1, this->base + ONENAND_REG_START_ADDRESS2); + ONENAND_SET_NEXT_BUFFERRAM(this); + buf += thislen; + thislen = min_t(int, writesize, len - read); + column = 0; + cond_resched(); + /* Now wait for load */ + ret = this->wait(mtd, FL_READING); + onenand_update_bufferram(mtd, from, !ret); + if (mtd_is_eccerr(ret)) + ret = 0; + } + + /* + * Return success, if no ECC failures, else -EBADMSG + * fs driver will take care of that, because + * retlen == desired len and result == -EBADMSG + */ + ops->retlen = read; + ops->oobretlen = oobread; + + if (ret) + return ret; + + if (mtd->ecc_stats.failed - stats.failed) + return -EBADMSG; + + /* return max bitflips per ecc step; ONENANDs correct 1 bit only */ + return mtd->ecc_stats.corrected != stats.corrected ? 1 : 0; +} + +/** + * onenand_read_oob_nolock - [MTD Interface] OneNAND read out-of-band + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob operation description structure + * + * OneNAND read out-of-band data from the spare area + */ +static int onenand_read_oob_nolock(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct onenand_chip *this = mtd->priv; + struct mtd_ecc_stats stats; + int read = 0, thislen, column, oobsize; + size_t len = ops->ooblen; + unsigned int mode = ops->mode; + u_char *buf = ops->oobbuf; + int ret = 0, readcmd; + + from += ops->ooboffs; + + pr_debug("%s: from = 0x%08x, len = %i\n", __func__, (unsigned int)from, + (int)len); + + /* Initialize return length value */ + ops->oobretlen = 0; + + if (mode == MTD_OPS_AUTO_OOB) + oobsize = mtd->oobavail; + else + oobsize = mtd->oobsize; + + column = from & (mtd->oobsize - 1); + + if (unlikely(column >= oobsize)) { + printk(KERN_ERR "%s: Attempted to start read outside oob\n", + __func__); + return -EINVAL; + } + + stats = mtd->ecc_stats; + + readcmd = ONENAND_IS_4KB_PAGE(this) ? ONENAND_CMD_READ : ONENAND_CMD_READOOB; + + while (read < len) { + cond_resched(); + + thislen = oobsize - column; + thislen = min_t(int, thislen, len); + + this->command(mtd, readcmd, from, mtd->oobsize); + + onenand_update_bufferram(mtd, from, 0); + + ret = this->wait(mtd, FL_READING); + if (unlikely(ret)) + ret = onenand_recover_lsb(mtd, from, ret); + + if (ret && !mtd_is_eccerr(ret)) { + printk(KERN_ERR "%s: read failed = 0x%x\n", + __func__, ret); + break; + } + + if (mode == MTD_OPS_AUTO_OOB) + onenand_transfer_auto_oob(mtd, buf, column, thislen); + else + this->read_bufferram(mtd, ONENAND_SPARERAM, buf, column, thislen); + + read += thislen; + + if (read == len) + break; + + buf += thislen; + + /* Read more? */ + if (read < len) { + /* Page size */ + from += mtd->writesize; + column = 0; + } + } + + ops->oobretlen = read; + + if (ret) + return ret; + + if (mtd->ecc_stats.failed - stats.failed) + return -EBADMSG; + + return 0; +} + +/** + * onenand_read_oob - [MTD Interface] Read main and/or out-of-band + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob operation description structure + * + * Read main and/or out-of-band + */ +static int onenand_read_oob(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct onenand_chip *this = mtd->priv; + struct mtd_ecc_stats old_stats; + int ret; + + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_AUTO_OOB: + break; + case MTD_OPS_RAW: + /* Not implemented yet */ + default: + return -EINVAL; + } + + onenand_get_device(mtd, FL_READING); + + old_stats = mtd->ecc_stats; + + if (ops->datbuf) + ret = ONENAND_IS_4KB_PAGE(this) ? + onenand_mlc_read_ops_nolock(mtd, from, ops) : + onenand_read_ops_nolock(mtd, from, ops); + else + ret = onenand_read_oob_nolock(mtd, from, ops); + + if (ops->stats) { + ops->stats->uncorrectable_errors += + mtd->ecc_stats.failed - old_stats.failed; + ops->stats->corrected_bitflips += + mtd->ecc_stats.corrected - old_stats.corrected; + } + + onenand_release_device(mtd); + + return ret; +} + +/** + * onenand_bbt_wait - [DEFAULT] wait until the command is done + * @mtd: MTD device structure + * @state: state to select the max. timeout value + * + * Wait for command done. + */ +static int onenand_bbt_wait(struct mtd_info *mtd, int state) +{ + struct onenand_chip *this = mtd->priv; + unsigned long timeout; + unsigned int interrupt, ctrl, ecc, addr1, addr8; + + /* The 20 msec is enough */ + timeout = jiffies + msecs_to_jiffies(20); + while (time_before(jiffies, timeout)) { + interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); + if (interrupt & ONENAND_INT_MASTER) + break; + } + /* To get correct interrupt status in timeout case */ + interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); + ctrl = this->read_word(this->base + ONENAND_REG_CTRL_STATUS); + addr1 = this->read_word(this->base + ONENAND_REG_START_ADDRESS1); + addr8 = this->read_word(this->base + ONENAND_REG_START_ADDRESS8); + + if (interrupt & ONENAND_INT_READ) { + ecc = onenand_read_ecc(this); + if (ecc & ONENAND_ECC_2BIT_ALL) { + printk(KERN_DEBUG "%s: ecc 0x%04x ctrl 0x%04x " + "intr 0x%04x addr1 %#x addr8 %#x\n", + __func__, ecc, ctrl, interrupt, addr1, addr8); + return ONENAND_BBT_READ_ECC_ERROR; + } + } else { + printk(KERN_ERR "%s: read timeout! ctrl 0x%04x " + "intr 0x%04x addr1 %#x addr8 %#x\n", + __func__, ctrl, interrupt, addr1, addr8); + return ONENAND_BBT_READ_FATAL_ERROR; + } + + /* Initial bad block case: 0x2400 or 0x0400 */ + if (ctrl & ONENAND_CTRL_ERROR) { + printk(KERN_DEBUG "%s: ctrl 0x%04x intr 0x%04x addr1 %#x " + "addr8 %#x\n", __func__, ctrl, interrupt, addr1, addr8); + return ONENAND_BBT_READ_ERROR; + } + + return 0; +} + +/** + * onenand_bbt_read_oob - [MTD Interface] OneNAND read out-of-band for bbt scan + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob operation description structure + * + * OneNAND read out-of-band data from the spare area for bbt scan + */ +int onenand_bbt_read_oob(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct onenand_chip *this = mtd->priv; + int read = 0, thislen, column; + int ret = 0, readcmd; + size_t len = ops->ooblen; + u_char *buf = ops->oobbuf; + + pr_debug("%s: from = 0x%08x, len = %zi\n", __func__, (unsigned int)from, + len); + + /* Initialize return value */ + ops->oobretlen = 0; + + /* Do not allow reads past end of device */ + if (unlikely((from + len) > mtd->size)) { + printk(KERN_ERR "%s: Attempt read beyond end of device\n", + __func__); + return ONENAND_BBT_READ_FATAL_ERROR; + } + + /* Grab the lock and see if the device is available */ + onenand_get_device(mtd, FL_READING); + + column = from & (mtd->oobsize - 1); + + readcmd = ONENAND_IS_4KB_PAGE(this) ? ONENAND_CMD_READ : ONENAND_CMD_READOOB; + + while (read < len) { + cond_resched(); + + thislen = mtd->oobsize - column; + thislen = min_t(int, thislen, len); + + this->command(mtd, readcmd, from, mtd->oobsize); + + onenand_update_bufferram(mtd, from, 0); + + ret = this->bbt_wait(mtd, FL_READING); + if (unlikely(ret)) + ret = onenand_recover_lsb(mtd, from, ret); + + if (ret) + break; + + this->read_bufferram(mtd, ONENAND_SPARERAM, buf, column, thislen); + read += thislen; + if (read == len) + break; + + buf += thislen; + + /* Read more? */ + if (read < len) { + /* Update Page size */ + from += this->writesize; + column = 0; + } + } + + /* Deselect and wake up anyone waiting on the device */ + onenand_release_device(mtd); + + ops->oobretlen = read; + return ret; +} + +#ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE +/** + * onenand_verify_oob - [GENERIC] verify the oob contents after a write + * @mtd: MTD device structure + * @buf: the databuffer to verify + * @to: offset to read from + */ +static int onenand_verify_oob(struct mtd_info *mtd, const u_char *buf, loff_t to) +{ + struct onenand_chip *this = mtd->priv; + u_char *oob_buf = this->oob_buf; + int status, i, readcmd; + + readcmd = ONENAND_IS_4KB_PAGE(this) ? ONENAND_CMD_READ : ONENAND_CMD_READOOB; + + this->command(mtd, readcmd, to, mtd->oobsize); + onenand_update_bufferram(mtd, to, 0); + status = this->wait(mtd, FL_READING); + if (status) + return status; + + this->read_bufferram(mtd, ONENAND_SPARERAM, oob_buf, 0, mtd->oobsize); + for (i = 0; i < mtd->oobsize; i++) + if (buf[i] != 0xFF && buf[i] != oob_buf[i]) + return -EBADMSG; + + return 0; +} + +/** + * onenand_verify - [GENERIC] verify the chip contents after a write + * @mtd: MTD device structure + * @buf: the databuffer to verify + * @addr: offset to read from + * @len: number of bytes to read and compare + */ +static int onenand_verify(struct mtd_info *mtd, const u_char *buf, loff_t addr, size_t len) +{ + struct onenand_chip *this = mtd->priv; + int ret = 0; + int thislen, column; + + column = addr & (this->writesize - 1); + + while (len != 0) { + thislen = min_t(int, this->writesize - column, len); + + this->command(mtd, ONENAND_CMD_READ, addr, this->writesize); + + onenand_update_bufferram(mtd, addr, 0); + + ret = this->wait(mtd, FL_READING); + if (ret) + return ret; + + onenand_update_bufferram(mtd, addr, 1); + + this->read_bufferram(mtd, ONENAND_DATARAM, this->verify_buf, 0, mtd->writesize); + + if (memcmp(buf, this->verify_buf + column, thislen)) + return -EBADMSG; + + len -= thislen; + buf += thislen; + addr += thislen; + column = 0; + } + + return 0; +} +#else +#define onenand_verify(...) (0) +#define onenand_verify_oob(...) (0) +#endif + +#define NOTALIGNED(x) ((x & (this->subpagesize - 1)) != 0) + +static void onenand_panic_wait(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + unsigned int interrupt; + int i; + + for (i = 0; i < 2000; i++) { + interrupt = this->read_word(this->base + ONENAND_REG_INTERRUPT); + if (interrupt & ONENAND_INT_MASTER) + break; + udelay(10); + } +} + +/** + * onenand_panic_write - [MTD Interface] write buffer to FLASH in a panic context + * @mtd: MTD device structure + * @to: offset to write to + * @len: number of bytes to write + * @retlen: pointer to variable to store the number of written bytes + * @buf: the data to write + * + * Write with ECC + */ +static int onenand_panic_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct onenand_chip *this = mtd->priv; + int column, subpage; + int written = 0; + + if (this->state == FL_PM_SUSPENDED) + return -EBUSY; + + /* Wait for any existing operation to clear */ + onenand_panic_wait(mtd); + + pr_debug("%s: to = 0x%08x, len = %i\n", __func__, (unsigned int)to, + (int)len); + + /* Reject writes, which are not page aligned */ + if (unlikely(NOTALIGNED(to) || NOTALIGNED(len))) { + printk(KERN_ERR "%s: Attempt to write not page aligned data\n", + __func__); + return -EINVAL; + } + + column = to & (mtd->writesize - 1); + + /* Loop until all data write */ + while (written < len) { + int thislen = min_t(int, mtd->writesize - column, len - written); + u_char *wbuf = (u_char *) buf; + + this->command(mtd, ONENAND_CMD_BUFFERRAM, to, thislen); + + /* Partial page write */ + subpage = thislen < mtd->writesize; + if (subpage) { + memset(this->page_buf, 0xff, mtd->writesize); + memcpy(this->page_buf + column, buf, thislen); + wbuf = this->page_buf; + } + + this->write_bufferram(mtd, ONENAND_DATARAM, wbuf, 0, mtd->writesize); + this->write_bufferram(mtd, ONENAND_SPARERAM, ffchars, 0, mtd->oobsize); + + this->command(mtd, ONENAND_CMD_PROG, to, mtd->writesize); + + onenand_panic_wait(mtd); + + /* In partial page write we don't update bufferram */ + onenand_update_bufferram(mtd, to, !subpage); + if (ONENAND_IS_2PLANE(this)) { + ONENAND_SET_BUFFERRAM1(this); + onenand_update_bufferram(mtd, to + this->writesize, !subpage); + } + + written += thislen; + + if (written == len) + break; + + column = 0; + to += thislen; + buf += thislen; + } + + *retlen = written; + return 0; +} + +/** + * onenand_fill_auto_oob - [INTERN] oob auto-placement transfer + * @mtd: MTD device structure + * @oob_buf: oob buffer + * @buf: source address + * @column: oob offset to write to + * @thislen: oob length to write + */ +static int onenand_fill_auto_oob(struct mtd_info *mtd, u_char *oob_buf, + const u_char *buf, int column, int thislen) +{ + return mtd_ooblayout_set_databytes(mtd, buf, oob_buf, column, thislen); +} + +/** + * onenand_write_ops_nolock - [OneNAND Interface] write main and/or out-of-band + * @mtd: MTD device structure + * @to: offset to write to + * @ops: oob operation description structure + * + * Write main and/or oob with ECC + */ +static int onenand_write_ops_nolock(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct onenand_chip *this = mtd->priv; + int written = 0, column, thislen = 0, subpage = 0; + int prev = 0, prevlen = 0, prev_subpage = 0, first = 1; + int oobwritten = 0, oobcolumn, thisooblen, oobsize; + size_t len = ops->len; + size_t ooblen = ops->ooblen; + const u_char *buf = ops->datbuf; + const u_char *oob = ops->oobbuf; + u_char *oobbuf; + int ret = 0, cmd; + + pr_debug("%s: to = 0x%08x, len = %i\n", __func__, (unsigned int)to, + (int)len); + + /* Initialize retlen, in case of early exit */ + ops->retlen = 0; + ops->oobretlen = 0; + + /* Reject writes, which are not page aligned */ + if (unlikely(NOTALIGNED(to) || NOTALIGNED(len))) { + printk(KERN_ERR "%s: Attempt to write not page aligned data\n", + __func__); + return -EINVAL; + } + + /* Check zero length */ + if (!len) + return 0; + oobsize = mtd_oobavail(mtd, ops); + oobcolumn = to & (mtd->oobsize - 1); + + column = to & (mtd->writesize - 1); + + /* Loop until all data write */ + while (1) { + if (written < len) { + u_char *wbuf = (u_char *) buf; + + thislen = min_t(int, mtd->writesize - column, len - written); + thisooblen = min_t(int, oobsize - oobcolumn, ooblen - oobwritten); + + cond_resched(); + + this->command(mtd, ONENAND_CMD_BUFFERRAM, to, thislen); + + /* Partial page write */ + subpage = thislen < mtd->writesize; + if (subpage) { + memset(this->page_buf, 0xff, mtd->writesize); + memcpy(this->page_buf + column, buf, thislen); + wbuf = this->page_buf; + } + + this->write_bufferram(mtd, ONENAND_DATARAM, wbuf, 0, mtd->writesize); + + if (oob) { + oobbuf = this->oob_buf; + + /* We send data to spare ram with oobsize + * to prevent byte access */ + memset(oobbuf, 0xff, mtd->oobsize); + if (ops->mode == MTD_OPS_AUTO_OOB) + onenand_fill_auto_oob(mtd, oobbuf, oob, oobcolumn, thisooblen); + else + memcpy(oobbuf + oobcolumn, oob, thisooblen); + + oobwritten += thisooblen; + oob += thisooblen; + oobcolumn = 0; + } else + oobbuf = (u_char *) ffchars; + + this->write_bufferram(mtd, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize); + } else + ONENAND_SET_NEXT_BUFFERRAM(this); + + /* + * 2 PLANE, MLC, and Flex-OneNAND do not support + * write-while-program feature. + */ + if (!ONENAND_IS_2PLANE(this) && !ONENAND_IS_4KB_PAGE(this) && !first) { + ONENAND_SET_PREV_BUFFERRAM(this); + + ret = this->wait(mtd, FL_WRITING); + + /* In partial page write we don't update bufferram */ + onenand_update_bufferram(mtd, prev, !ret && !prev_subpage); + if (ret) { + written -= prevlen; + printk(KERN_ERR "%s: write failed %d\n", + __func__, ret); + break; + } + + if (written == len) { + /* Only check verify write turn on */ + ret = onenand_verify(mtd, buf - len, to - len, len); + if (ret) + printk(KERN_ERR "%s: verify failed %d\n", + __func__, ret); + break; + } + + ONENAND_SET_NEXT_BUFFERRAM(this); + } + + this->ongoing = 0; + cmd = ONENAND_CMD_PROG; + + /* Exclude 1st OTP and OTP blocks for cache program feature */ + if (ONENAND_IS_CACHE_PROGRAM(this) && + likely(onenand_block(this, to) != 0) && + ONENAND_IS_4KB_PAGE(this) && + ((written + thislen) < len)) { + cmd = ONENAND_CMD_2X_CACHE_PROG; + this->ongoing = 1; + } + + this->command(mtd, cmd, to, mtd->writesize); + + /* + * 2 PLANE, MLC, and Flex-OneNAND wait here + */ + if (ONENAND_IS_2PLANE(this) || ONENAND_IS_4KB_PAGE(this)) { + ret = this->wait(mtd, FL_WRITING); + + /* In partial page write we don't update bufferram */ + onenand_update_bufferram(mtd, to, !ret && !subpage); + if (ret) { + printk(KERN_ERR "%s: write failed %d\n", + __func__, ret); + break; + } + + /* Only check verify write turn on */ + ret = onenand_verify(mtd, buf, to, thislen); + if (ret) { + printk(KERN_ERR "%s: verify failed %d\n", + __func__, ret); + break; + } + + written += thislen; + + if (written == len) + break; + + } else + written += thislen; + + column = 0; + prev_subpage = subpage; + prev = to; + prevlen = thislen; + to += thislen; + buf += thislen; + first = 0; + } + + /* In error case, clear all bufferrams */ + if (written != len) + onenand_invalidate_bufferram(mtd, 0, -1); + + ops->retlen = written; + ops->oobretlen = oobwritten; + + return ret; +} + + +/** + * onenand_write_oob_nolock - [INTERN] OneNAND write out-of-band + * @mtd: MTD device structure + * @to: offset to write to + * @ops: oob operation description structure + * + * OneNAND write out-of-band + */ +static int onenand_write_oob_nolock(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct onenand_chip *this = mtd->priv; + int column, ret = 0, oobsize; + int written = 0, oobcmd; + u_char *oobbuf; + size_t len = ops->ooblen; + const u_char *buf = ops->oobbuf; + unsigned int mode = ops->mode; + + to += ops->ooboffs; + + pr_debug("%s: to = 0x%08x, len = %i\n", __func__, (unsigned int)to, + (int)len); + + /* Initialize retlen, in case of early exit */ + ops->oobretlen = 0; + + if (mode == MTD_OPS_AUTO_OOB) + oobsize = mtd->oobavail; + else + oobsize = mtd->oobsize; + + column = to & (mtd->oobsize - 1); + + if (unlikely(column >= oobsize)) { + printk(KERN_ERR "%s: Attempted to start write outside oob\n", + __func__); + return -EINVAL; + } + + /* For compatibility with NAND: Do not allow write past end of page */ + if (unlikely(column + len > oobsize)) { + printk(KERN_ERR "%s: Attempt to write past end of page\n", + __func__); + return -EINVAL; + } + + oobbuf = this->oob_buf; + + oobcmd = ONENAND_IS_4KB_PAGE(this) ? ONENAND_CMD_PROG : ONENAND_CMD_PROGOOB; + + /* Loop until all data write */ + while (written < len) { + int thislen = min_t(int, oobsize, len - written); + + cond_resched(); + + this->command(mtd, ONENAND_CMD_BUFFERRAM, to, mtd->oobsize); + + /* We send data to spare ram with oobsize + * to prevent byte access */ + memset(oobbuf, 0xff, mtd->oobsize); + if (mode == MTD_OPS_AUTO_OOB) + onenand_fill_auto_oob(mtd, oobbuf, buf, column, thislen); + else + memcpy(oobbuf + column, buf, thislen); + this->write_bufferram(mtd, ONENAND_SPARERAM, oobbuf, 0, mtd->oobsize); + + if (ONENAND_IS_4KB_PAGE(this)) { + /* Set main area of DataRAM to 0xff*/ + memset(this->page_buf, 0xff, mtd->writesize); + this->write_bufferram(mtd, ONENAND_DATARAM, + this->page_buf, 0, mtd->writesize); + } + + this->command(mtd, oobcmd, to, mtd->oobsize); + + onenand_update_bufferram(mtd, to, 0); + if (ONENAND_IS_2PLANE(this)) { + ONENAND_SET_BUFFERRAM1(this); + onenand_update_bufferram(mtd, to + this->writesize, 0); + } + + ret = this->wait(mtd, FL_WRITING); + if (ret) { + printk(KERN_ERR "%s: write failed %d\n", __func__, ret); + break; + } + + ret = onenand_verify_oob(mtd, oobbuf, to); + if (ret) { + printk(KERN_ERR "%s: verify failed %d\n", + __func__, ret); + break; + } + + written += thislen; + if (written == len) + break; + + to += mtd->writesize; + buf += thislen; + column = 0; + } + + ops->oobretlen = written; + + return ret; +} + +/** + * onenand_write_oob - [MTD Interface] NAND write data and/or out-of-band + * @mtd: MTD device structure + * @to: offset to write + * @ops: oob operation description structure + */ +static int onenand_write_oob(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + int ret; + + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_AUTO_OOB: + break; + case MTD_OPS_RAW: + /* Not implemented yet */ + default: + return -EINVAL; + } + + onenand_get_device(mtd, FL_WRITING); + if (ops->datbuf) + ret = onenand_write_ops_nolock(mtd, to, ops); + else + ret = onenand_write_oob_nolock(mtd, to, ops); + onenand_release_device(mtd); + + return ret; +} + +/** + * onenand_block_isbad_nolock - [GENERIC] Check if a block is marked bad + * @mtd: MTD device structure + * @ofs: offset from device start + * @allowbbt: 1, if its allowed to access the bbt area + * + * Check, if the block is bad. Either by reading the bad block table or + * calling of the scan function. + */ +static int onenand_block_isbad_nolock(struct mtd_info *mtd, loff_t ofs, int allowbbt) +{ + struct onenand_chip *this = mtd->priv; + struct bbm_info *bbm = this->bbm; + + /* Return info from the table */ + return bbm->isbad_bbt(mtd, ofs, allowbbt); +} + + +static int onenand_multiblock_erase_verify(struct mtd_info *mtd, + struct erase_info *instr) +{ + struct onenand_chip *this = mtd->priv; + loff_t addr = instr->addr; + int len = instr->len; + unsigned int block_size = (1 << this->erase_shift); + int ret = 0; + + while (len) { + this->command(mtd, ONENAND_CMD_ERASE_VERIFY, addr, block_size); + ret = this->wait(mtd, FL_VERIFYING_ERASE); + if (ret) { + printk(KERN_ERR "%s: Failed verify, block %d\n", + __func__, onenand_block(this, addr)); + instr->fail_addr = addr; + return -1; + } + len -= block_size; + addr += block_size; + } + return 0; +} + +/** + * onenand_multiblock_erase - [INTERN] erase block(s) using multiblock erase + * @mtd: MTD device structure + * @instr: erase instruction + * @block_size: block size + * + * Erase one or more blocks up to 64 block at a time + */ +static int onenand_multiblock_erase(struct mtd_info *mtd, + struct erase_info *instr, + unsigned int block_size) +{ + struct onenand_chip *this = mtd->priv; + loff_t addr = instr->addr; + int len = instr->len; + int eb_count = 0; + int ret = 0; + int bdry_block = 0; + + if (ONENAND_IS_DDP(this)) { + loff_t bdry_addr = this->chipsize >> 1; + if (addr < bdry_addr && (addr + len) > bdry_addr) + bdry_block = bdry_addr >> this->erase_shift; + } + + /* Pre-check bbs */ + while (len) { + /* Check if we have a bad block, we do not erase bad blocks */ + if (onenand_block_isbad_nolock(mtd, addr, 0)) { + printk(KERN_WARNING "%s: attempt to erase a bad block " + "at addr 0x%012llx\n", + __func__, (unsigned long long) addr); + return -EIO; + } + len -= block_size; + addr += block_size; + } + + len = instr->len; + addr = instr->addr; + + /* loop over 64 eb batches */ + while (len) { + struct erase_info verify_instr = *instr; + int max_eb_count = MB_ERASE_MAX_BLK_COUNT; + + verify_instr.addr = addr; + verify_instr.len = 0; + + /* do not cross chip boundary */ + if (bdry_block) { + int this_block = (addr >> this->erase_shift); + + if (this_block < bdry_block) { + max_eb_count = min(max_eb_count, + (bdry_block - this_block)); + } + } + + eb_count = 0; + + while (len > block_size && eb_count < (max_eb_count - 1)) { + this->command(mtd, ONENAND_CMD_MULTIBLOCK_ERASE, + addr, block_size); + onenand_invalidate_bufferram(mtd, addr, block_size); + + ret = this->wait(mtd, FL_PREPARING_ERASE); + if (ret) { + printk(KERN_ERR "%s: Failed multiblock erase, " + "block %d\n", __func__, + onenand_block(this, addr)); + instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN; + return -EIO; + } + + len -= block_size; + addr += block_size; + eb_count++; + } + + /* last block of 64-eb series */ + cond_resched(); + this->command(mtd, ONENAND_CMD_ERASE, addr, block_size); + onenand_invalidate_bufferram(mtd, addr, block_size); + + ret = this->wait(mtd, FL_ERASING); + /* Check if it is write protected */ + if (ret) { + printk(KERN_ERR "%s: Failed erase, block %d\n", + __func__, onenand_block(this, addr)); + instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN; + return -EIO; + } + + len -= block_size; + addr += block_size; + eb_count++; + + /* verify */ + verify_instr.len = eb_count * block_size; + if (onenand_multiblock_erase_verify(mtd, &verify_instr)) { + instr->fail_addr = verify_instr.fail_addr; + return -EIO; + } + + } + return 0; +} + + +/** + * onenand_block_by_block_erase - [INTERN] erase block(s) using regular erase + * @mtd: MTD device structure + * @instr: erase instruction + * @region: erase region + * @block_size: erase block size + * + * Erase one or more blocks one block at a time + */ +static int onenand_block_by_block_erase(struct mtd_info *mtd, + struct erase_info *instr, + struct mtd_erase_region_info *region, + unsigned int block_size) +{ + struct onenand_chip *this = mtd->priv; + loff_t addr = instr->addr; + int len = instr->len; + loff_t region_end = 0; + int ret = 0; + + if (region) { + /* region is set for Flex-OneNAND */ + region_end = region->offset + region->erasesize * region->numblocks; + } + + /* Loop through the blocks */ + while (len) { + cond_resched(); + + /* Check if we have a bad block, we do not erase bad blocks */ + if (onenand_block_isbad_nolock(mtd, addr, 0)) { + printk(KERN_WARNING "%s: attempt to erase a bad block " + "at addr 0x%012llx\n", + __func__, (unsigned long long) addr); + return -EIO; + } + + this->command(mtd, ONENAND_CMD_ERASE, addr, block_size); + + onenand_invalidate_bufferram(mtd, addr, block_size); + + ret = this->wait(mtd, FL_ERASING); + /* Check, if it is write protected */ + if (ret) { + printk(KERN_ERR "%s: Failed erase, block %d\n", + __func__, onenand_block(this, addr)); + instr->fail_addr = addr; + return -EIO; + } + + len -= block_size; + addr += block_size; + + if (region && addr == region_end) { + if (!len) + break; + region++; + + block_size = region->erasesize; + region_end = region->offset + region->erasesize * region->numblocks; + + if (len & (block_size - 1)) { + /* FIXME: This should be handled at MTD partitioning level. */ + printk(KERN_ERR "%s: Unaligned address\n", + __func__); + return -EIO; + } + } + } + return 0; +} + +/** + * onenand_erase - [MTD Interface] erase block(s) + * @mtd: MTD device structure + * @instr: erase instruction + * + * Erase one or more blocks + */ +static int onenand_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + struct onenand_chip *this = mtd->priv; + unsigned int block_size; + loff_t addr = instr->addr; + loff_t len = instr->len; + int ret = 0; + struct mtd_erase_region_info *region = NULL; + loff_t region_offset = 0; + + pr_debug("%s: start=0x%012llx, len=%llu\n", __func__, + (unsigned long long)instr->addr, + (unsigned long long)instr->len); + + if (FLEXONENAND(this)) { + /* Find the eraseregion of this address */ + int i = flexonenand_region(mtd, addr); + + region = &mtd->eraseregions[i]; + block_size = region->erasesize; + + /* Start address within region must align on block boundary. + * Erase region's start offset is always block start address. + */ + region_offset = region->offset; + } else + block_size = 1 << this->erase_shift; + + /* Start address must align on block boundary */ + if (unlikely((addr - region_offset) & (block_size - 1))) { + printk(KERN_ERR "%s: Unaligned address\n", __func__); + return -EINVAL; + } + + /* Length must align on block boundary */ + if (unlikely(len & (block_size - 1))) { + printk(KERN_ERR "%s: Length not block aligned\n", __func__); + return -EINVAL; + } + + /* Grab the lock and see if the device is available */ + onenand_get_device(mtd, FL_ERASING); + + if (ONENAND_IS_4KB_PAGE(this) || region || + instr->len < MB_ERASE_MIN_BLK_COUNT * block_size) { + /* region is set for Flex-OneNAND (no mb erase) */ + ret = onenand_block_by_block_erase(mtd, instr, + region, block_size); + } else { + ret = onenand_multiblock_erase(mtd, instr, block_size); + } + + /* Deselect and wake up anyone waiting on the device */ + onenand_release_device(mtd); + + return ret; +} + +/** + * onenand_sync - [MTD Interface] sync + * @mtd: MTD device structure + * + * Sync is actually a wait for chip ready function + */ +static void onenand_sync(struct mtd_info *mtd) +{ + pr_debug("%s: called\n", __func__); + + /* Grab the lock and see if the device is available */ + onenand_get_device(mtd, FL_SYNCING); + + /* Release it and go back */ + onenand_release_device(mtd); +} + +/** + * onenand_block_isbad - [MTD Interface] Check whether the block at the given offset is bad + * @mtd: MTD device structure + * @ofs: offset relative to mtd start + * + * Check whether the block is bad + */ +static int onenand_block_isbad(struct mtd_info *mtd, loff_t ofs) +{ + int ret; + + onenand_get_device(mtd, FL_READING); + ret = onenand_block_isbad_nolock(mtd, ofs, 0); + onenand_release_device(mtd); + return ret; +} + +/** + * onenand_default_block_markbad - [DEFAULT] mark a block bad + * @mtd: MTD device structure + * @ofs: offset from device start + * + * This is the default implementation, which can be overridden by + * a hardware specific driver. + */ +static int onenand_default_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + struct onenand_chip *this = mtd->priv; + struct bbm_info *bbm = this->bbm; + u_char buf[2] = {0, 0}; + struct mtd_oob_ops ops = { + .mode = MTD_OPS_PLACE_OOB, + .ooblen = 2, + .oobbuf = buf, + .ooboffs = 0, + }; + int block; + + /* Get block number */ + block = onenand_block(this, ofs); + if (bbm->bbt) + bbm->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1); + + /* We write two bytes, so we don't have to mess with 16-bit access */ + ofs += mtd->oobsize + (this->badblockpos & ~0x01); + /* FIXME : What to do when marking SLC block in partition + * with MLC erasesize? For now, it is not advisable to + * create partitions containing both SLC and MLC regions. + */ + return onenand_write_oob_nolock(mtd, ofs, &ops); +} + +/** + * onenand_block_markbad - [MTD Interface] Mark the block at the given offset as bad + * @mtd: MTD device structure + * @ofs: offset relative to mtd start + * + * Mark the block as bad + */ +static int onenand_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + struct onenand_chip *this = mtd->priv; + int ret; + + ret = onenand_block_isbad(mtd, ofs); + if (ret) { + /* If it was bad already, return success and do nothing */ + if (ret > 0) + return 0; + return ret; + } + + onenand_get_device(mtd, FL_WRITING); + ret = this->block_markbad(mtd, ofs); + onenand_release_device(mtd); + return ret; +} + +/** + * onenand_do_lock_cmd - [OneNAND Interface] Lock or unlock block(s) + * @mtd: MTD device structure + * @ofs: offset relative to mtd start + * @len: number of bytes to lock or unlock + * @cmd: lock or unlock command + * + * Lock or unlock one or more blocks + */ +static int onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, size_t len, int cmd) +{ + struct onenand_chip *this = mtd->priv; + int start, end, block, value, status; + int wp_status_mask; + + start = onenand_block(this, ofs); + end = onenand_block(this, ofs + len) - 1; + + if (cmd == ONENAND_CMD_LOCK) + wp_status_mask = ONENAND_WP_LS; + else + wp_status_mask = ONENAND_WP_US; + + /* Continuous lock scheme */ + if (this->options & ONENAND_HAS_CONT_LOCK) { + /* Set start block address */ + this->write_word(start, this->base + ONENAND_REG_START_BLOCK_ADDRESS); + /* Set end block address */ + this->write_word(end, this->base + ONENAND_REG_END_BLOCK_ADDRESS); + /* Write lock command */ + this->command(mtd, cmd, 0, 0); + + /* There's no return value */ + this->wait(mtd, FL_LOCKING); + + /* Sanity check */ + while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) + & ONENAND_CTRL_ONGO) + continue; + + /* Check lock status */ + status = this->read_word(this->base + ONENAND_REG_WP_STATUS); + if (!(status & wp_status_mask)) + printk(KERN_ERR "%s: wp status = 0x%x\n", + __func__, status); + + return 0; + } + + /* Block lock scheme */ + for (block = start; block < end + 1; block++) { + /* Set block address */ + value = onenand_block_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1); + /* Select DataRAM for DDP */ + value = onenand_bufferram_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); + /* Set start block address */ + this->write_word(block, this->base + ONENAND_REG_START_BLOCK_ADDRESS); + /* Write lock command */ + this->command(mtd, cmd, 0, 0); + + /* There's no return value */ + this->wait(mtd, FL_LOCKING); + + /* Sanity check */ + while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) + & ONENAND_CTRL_ONGO) + continue; + + /* Check lock status */ + status = this->read_word(this->base + ONENAND_REG_WP_STATUS); + if (!(status & wp_status_mask)) + printk(KERN_ERR "%s: block = %d, wp status = 0x%x\n", + __func__, block, status); + } + + return 0; +} + +/** + * onenand_lock - [MTD Interface] Lock block(s) + * @mtd: MTD device structure + * @ofs: offset relative to mtd start + * @len: number of bytes to unlock + * + * Lock one or more blocks + */ +static int onenand_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + int ret; + + onenand_get_device(mtd, FL_LOCKING); + ret = onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_LOCK); + onenand_release_device(mtd); + return ret; +} + +/** + * onenand_unlock - [MTD Interface] Unlock block(s) + * @mtd: MTD device structure + * @ofs: offset relative to mtd start + * @len: number of bytes to unlock + * + * Unlock one or more blocks + */ +static int onenand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + int ret; + + onenand_get_device(mtd, FL_LOCKING); + ret = onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK); + onenand_release_device(mtd); + return ret; +} + +/** + * onenand_check_lock_status - [OneNAND Interface] Check lock status + * @this: onenand chip data structure + * + * Check lock status + */ +static int onenand_check_lock_status(struct onenand_chip *this) +{ + unsigned int value, block, status; + unsigned int end; + + end = this->chipsize >> this->erase_shift; + for (block = 0; block < end; block++) { + /* Set block address */ + value = onenand_block_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS1); + /* Select DataRAM for DDP */ + value = onenand_bufferram_address(this, block); + this->write_word(value, this->base + ONENAND_REG_START_ADDRESS2); + /* Set start block address */ + this->write_word(block, this->base + ONENAND_REG_START_BLOCK_ADDRESS); + + /* Check lock status */ + status = this->read_word(this->base + ONENAND_REG_WP_STATUS); + if (!(status & ONENAND_WP_US)) { + printk(KERN_ERR "%s: block = %d, wp status = 0x%x\n", + __func__, block, status); + return 0; + } + } + + return 1; +} + +/** + * onenand_unlock_all - [OneNAND Interface] unlock all blocks + * @mtd: MTD device structure + * + * Unlock all blocks + */ +static void onenand_unlock_all(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + loff_t ofs = 0; + loff_t len = mtd->size; + + if (this->options & ONENAND_HAS_UNLOCK_ALL) { + /* Set start block address */ + this->write_word(0, this->base + ONENAND_REG_START_BLOCK_ADDRESS); + /* Write unlock command */ + this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0); + + /* There's no return value */ + this->wait(mtd, FL_LOCKING); + + /* Sanity check */ + while (this->read_word(this->base + ONENAND_REG_CTRL_STATUS) + & ONENAND_CTRL_ONGO) + continue; + + /* Don't check lock status */ + if (this->options & ONENAND_SKIP_UNLOCK_CHECK) + return; + + /* Check lock status */ + if (onenand_check_lock_status(this)) + return; + + /* Workaround for all block unlock in DDP */ + if (ONENAND_IS_DDP(this) && !FLEXONENAND(this)) { + /* All blocks on another chip */ + ofs = this->chipsize >> 1; + len = this->chipsize >> 1; + } + } + + onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK); +} + +#ifdef CONFIG_MTD_ONENAND_OTP + +/** + * onenand_otp_command - Send OTP specific command to OneNAND device + * @mtd: MTD device structure + * @cmd: the command to be sent + * @addr: offset to read from or write to + * @len: number of bytes to read or write + */ +static int onenand_otp_command(struct mtd_info *mtd, int cmd, loff_t addr, + size_t len) +{ + struct onenand_chip *this = mtd->priv; + int value, block, page; + + /* Address translation */ + switch (cmd) { + case ONENAND_CMD_OTP_ACCESS: + block = (int) (addr >> this->erase_shift); + page = -1; + break; + + default: + block = (int) (addr >> this->erase_shift); + page = (int) (addr >> this->page_shift); + + if (ONENAND_IS_2PLANE(this)) { + /* Make the even block number */ + block &= ~1; + /* Is it the odd plane? */ + if (addr & this->writesize) + block++; + page >>= 1; + } + page &= this->page_mask; + break; + } + + if (block != -1) { + /* Write 'DFS, FBA' of Flash */ + value = onenand_block_address(this, block); + this->write_word(value, this->base + + ONENAND_REG_START_ADDRESS1); + } + + if (page != -1) { + /* Now we use page size operation */ + int sectors = 4, count = 4; + int dataram; + + switch (cmd) { + default: + if (ONENAND_IS_2PLANE(this) && cmd == ONENAND_CMD_PROG) + cmd = ONENAND_CMD_2X_PROG; + dataram = ONENAND_CURRENT_BUFFERRAM(this); + break; + } + + /* Write 'FPA, FSA' of Flash */ + value = onenand_page_address(page, sectors); + this->write_word(value, this->base + + ONENAND_REG_START_ADDRESS8); + + /* Write 'BSA, BSC' of DataRAM */ + value = onenand_buffer_address(dataram, sectors, count); + this->write_word(value, this->base + ONENAND_REG_START_BUFFER); + } + + /* Interrupt clear */ + this->write_word(ONENAND_INT_CLEAR, this->base + ONENAND_REG_INTERRUPT); + + /* Write command */ + this->write_word(cmd, this->base + ONENAND_REG_COMMAND); + + return 0; +} + +/** + * onenand_otp_write_oob_nolock - [INTERN] OneNAND write out-of-band, specific to OTP + * @mtd: MTD device structure + * @to: offset to write to + * @ops: oob operation description structure + * + * OneNAND write out-of-band only for OTP + */ +static int onenand_otp_write_oob_nolock(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct onenand_chip *this = mtd->priv; + int column, ret = 0, oobsize; + int written = 0; + u_char *oobbuf; + size_t len = ops->ooblen; + const u_char *buf = ops->oobbuf; + int block, value, status; + + to += ops->ooboffs; + + /* Initialize retlen, in case of early exit */ + ops->oobretlen = 0; + + oobsize = mtd->oobsize; + + column = to & (mtd->oobsize - 1); + + oobbuf = this->oob_buf; + + /* Loop until all data write */ + while (written < len) { + int thislen = min_t(int, oobsize, len - written); + + cond_resched(); + + block = (int) (to >> this->erase_shift); + /* + * Write 'DFS, FBA' of Flash + * Add: F100h DQ=DFS, FBA + */ + + value = onenand_block_address(this, block); + this->write_word(value, this->base + + ONENAND_REG_START_ADDRESS1); + + /* + * Select DataRAM for DDP + * Add: F101h DQ=DBS + */ + + value = onenand_bufferram_address(this, block); + this->write_word(value, this->base + + ONENAND_REG_START_ADDRESS2); + ONENAND_SET_NEXT_BUFFERRAM(this); + + /* + * Enter OTP access mode + */ + this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0); + this->wait(mtd, FL_OTPING); + + /* We send data to spare ram with oobsize + * to prevent byte access */ + memcpy(oobbuf + column, buf, thislen); + + /* + * Write Data into DataRAM + * Add: 8th Word + * in sector0/spare/page0 + * DQ=XXFCh + */ + this->write_bufferram(mtd, ONENAND_SPARERAM, + oobbuf, 0, mtd->oobsize); + + onenand_otp_command(mtd, ONENAND_CMD_PROGOOB, to, mtd->oobsize); + onenand_update_bufferram(mtd, to, 0); + if (ONENAND_IS_2PLANE(this)) { + ONENAND_SET_BUFFERRAM1(this); + onenand_update_bufferram(mtd, to + this->writesize, 0); + } + + ret = this->wait(mtd, FL_WRITING); + if (ret) { + printk(KERN_ERR "%s: write failed %d\n", __func__, ret); + break; + } + + /* Exit OTP access mode */ + this->command(mtd, ONENAND_CMD_RESET, 0, 0); + this->wait(mtd, FL_RESETTING); + + status = this->read_word(this->base + ONENAND_REG_CTRL_STATUS); + status &= 0x60; + + if (status == 0x60) { + printk(KERN_DEBUG "\nBLOCK\tSTATUS\n"); + printk(KERN_DEBUG "1st Block\tLOCKED\n"); + printk(KERN_DEBUG "OTP Block\tLOCKED\n"); + } else if (status == 0x20) { + printk(KERN_DEBUG "\nBLOCK\tSTATUS\n"); + printk(KERN_DEBUG "1st Block\tLOCKED\n"); + printk(KERN_DEBUG "OTP Block\tUN-LOCKED\n"); + } else if (status == 0x40) { + printk(KERN_DEBUG "\nBLOCK\tSTATUS\n"); + printk(KERN_DEBUG "1st Block\tUN-LOCKED\n"); + printk(KERN_DEBUG "OTP Block\tLOCKED\n"); + } else { + printk(KERN_DEBUG "Reboot to check\n"); + } + + written += thislen; + if (written == len) + break; + + to += mtd->writesize; + buf += thislen; + column = 0; + } + + ops->oobretlen = written; + + return ret; +} + +/* Internal OTP operation */ +typedef int (*otp_op_t)(struct mtd_info *mtd, loff_t form, size_t len, + size_t *retlen, u_char *buf); + +/** + * do_otp_read - [DEFAULT] Read OTP block area + * @mtd: MTD device structure + * @from: The offset to read + * @len: number of bytes to read + * @retlen: pointer to variable to store the number of readbytes + * @buf: the databuffer to put/get data + * + * Read OTP block area. + */ +static int do_otp_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct onenand_chip *this = mtd->priv; + struct mtd_oob_ops ops = { + .len = len, + .ooblen = 0, + .datbuf = buf, + .oobbuf = NULL, + }; + int ret; + + /* Enter OTP access mode */ + this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0); + this->wait(mtd, FL_OTPING); + + ret = ONENAND_IS_4KB_PAGE(this) ? + onenand_mlc_read_ops_nolock(mtd, from, &ops) : + onenand_read_ops_nolock(mtd, from, &ops); + *retlen = ops.retlen; + + /* Exit OTP access mode */ + this->command(mtd, ONENAND_CMD_RESET, 0, 0); + this->wait(mtd, FL_RESETTING); + + return ret; +} + +/** + * do_otp_write - [DEFAULT] Write OTP block area + * @mtd: MTD device structure + * @to: The offset to write + * @len: number of bytes to write + * @retlen: pointer to variable to store the number of write bytes + * @buf: the databuffer to put/get data + * + * Write OTP block area. + */ +static int do_otp_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, u_char *buf) +{ + struct onenand_chip *this = mtd->priv; + unsigned char *pbuf = buf; + int ret; + struct mtd_oob_ops ops = { }; + + /* Force buffer page aligned */ + if (len < mtd->writesize) { + memcpy(this->page_buf, buf, len); + memset(this->page_buf + len, 0xff, mtd->writesize - len); + pbuf = this->page_buf; + len = mtd->writesize; + } + + /* Enter OTP access mode */ + this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0); + this->wait(mtd, FL_OTPING); + + ops.len = len; + ops.ooblen = 0; + ops.datbuf = pbuf; + ops.oobbuf = NULL; + ret = onenand_write_ops_nolock(mtd, to, &ops); + *retlen = ops.retlen; + + /* Exit OTP access mode */ + this->command(mtd, ONENAND_CMD_RESET, 0, 0); + this->wait(mtd, FL_RESETTING); + + return ret; +} + +/** + * do_otp_lock - [DEFAULT] Lock OTP block area + * @mtd: MTD device structure + * @from: The offset to lock + * @len: number of bytes to lock + * @retlen: pointer to variable to store the number of lock bytes + * @buf: the databuffer to put/get data + * + * Lock OTP block area. + */ +static int do_otp_lock(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct onenand_chip *this = mtd->priv; + struct mtd_oob_ops ops = { }; + int ret; + + if (FLEXONENAND(this)) { + + /* Enter OTP access mode */ + this->command(mtd, ONENAND_CMD_OTP_ACCESS, 0, 0); + this->wait(mtd, FL_OTPING); + /* + * For Flex-OneNAND, we write lock mark to 1st word of sector 4 of + * main area of page 49. + */ + ops.len = mtd->writesize; + ops.ooblen = 0; + ops.datbuf = buf; + ops.oobbuf = NULL; + ret = onenand_write_ops_nolock(mtd, mtd->writesize * 49, &ops); + *retlen = ops.retlen; + + /* Exit OTP access mode */ + this->command(mtd, ONENAND_CMD_RESET, 0, 0); + this->wait(mtd, FL_RESETTING); + } else { + ops.mode = MTD_OPS_PLACE_OOB; + ops.ooblen = len; + ops.oobbuf = buf; + ops.ooboffs = 0; + ret = onenand_otp_write_oob_nolock(mtd, from, &ops); + *retlen = ops.oobretlen; + } + + return ret; +} + +/** + * onenand_otp_walk - [DEFAULT] Handle OTP operation + * @mtd: MTD device structure + * @from: The offset to read/write + * @len: number of bytes to read/write + * @retlen: pointer to variable to store the number of read bytes + * @buf: the databuffer to put/get data + * @action: do given action + * @mode: specify user and factory + * + * Handle OTP operation. + */ +static int onenand_otp_walk(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf, + otp_op_t action, int mode) +{ + struct onenand_chip *this = mtd->priv; + int otp_pages; + int density; + int ret = 0; + + *retlen = 0; + + density = onenand_get_density(this->device_id); + if (density < ONENAND_DEVICE_DENSITY_512Mb) + otp_pages = 20; + else + otp_pages = 50; + + if (mode == MTD_OTP_FACTORY) { + from += mtd->writesize * otp_pages; + otp_pages = ONENAND_PAGES_PER_BLOCK - otp_pages; + } + + /* Check User/Factory boundary */ + if (mode == MTD_OTP_USER) { + if (mtd->writesize * otp_pages < from + len) + return 0; + } else { + if (mtd->writesize * otp_pages < len) + return 0; + } + + onenand_get_device(mtd, FL_OTPING); + while (len > 0 && otp_pages > 0) { + if (!action) { /* OTP Info functions */ + struct otp_info *otpinfo; + + len -= sizeof(struct otp_info); + if (len <= 0) { + ret = -ENOSPC; + break; + } + + otpinfo = (struct otp_info *) buf; + otpinfo->start = from; + otpinfo->length = mtd->writesize; + otpinfo->locked = 0; + + from += mtd->writesize; + buf += sizeof(struct otp_info); + *retlen += sizeof(struct otp_info); + } else { + size_t tmp_retlen; + + ret = action(mtd, from, len, &tmp_retlen, buf); + if (ret) + break; + + buf += tmp_retlen; + len -= tmp_retlen; + *retlen += tmp_retlen; + + } + otp_pages--; + } + onenand_release_device(mtd); + + return ret; +} + +/** + * onenand_get_fact_prot_info - [MTD Interface] Read factory OTP info + * @mtd: MTD device structure + * @len: number of bytes to read + * @retlen: pointer to variable to store the number of read bytes + * @buf: the databuffer to put/get data + * + * Read factory OTP info. + */ +static int onenand_get_fact_prot_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf) +{ + return onenand_otp_walk(mtd, 0, len, retlen, (u_char *) buf, NULL, + MTD_OTP_FACTORY); +} + +/** + * onenand_read_fact_prot_reg - [MTD Interface] Read factory OTP area + * @mtd: MTD device structure + * @from: The offset to read + * @len: number of bytes to read + * @retlen: pointer to variable to store the number of read bytes + * @buf: the databuffer to put/get data + * + * Read factory OTP area. + */ +static int onenand_read_fact_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, u_char *buf) +{ + return onenand_otp_walk(mtd, from, len, retlen, buf, do_otp_read, MTD_OTP_FACTORY); +} + +/** + * onenand_get_user_prot_info - [MTD Interface] Read user OTP info + * @mtd: MTD device structure + * @retlen: pointer to variable to store the number of read bytes + * @len: number of bytes to read + * @buf: the databuffer to put/get data + * + * Read user OTP info. + */ +static int onenand_get_user_prot_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf) +{ + return onenand_otp_walk(mtd, 0, len, retlen, (u_char *) buf, NULL, + MTD_OTP_USER); +} + +/** + * onenand_read_user_prot_reg - [MTD Interface] Read user OTP area + * @mtd: MTD device structure + * @from: The offset to read + * @len: number of bytes to read + * @retlen: pointer to variable to store the number of read bytes + * @buf: the databuffer to put/get data + * + * Read user OTP area. + */ +static int onenand_read_user_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, u_char *buf) +{ + return onenand_otp_walk(mtd, from, len, retlen, buf, do_otp_read, MTD_OTP_USER); +} + +/** + * onenand_write_user_prot_reg - [MTD Interface] Write user OTP area + * @mtd: MTD device structure + * @from: The offset to write + * @len: number of bytes to write + * @retlen: pointer to variable to store the number of write bytes + * @buf: the databuffer to put/get data + * + * Write user OTP area. + */ +static int onenand_write_user_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, const u_char *buf) +{ + return onenand_otp_walk(mtd, from, len, retlen, (u_char *)buf, + do_otp_write, MTD_OTP_USER); +} + +/** + * onenand_lock_user_prot_reg - [MTD Interface] Lock user OTP area + * @mtd: MTD device structure + * @from: The offset to lock + * @len: number of bytes to unlock + * + * Write lock mark on spare area in page 0 in OTP block + */ +static int onenand_lock_user_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len) +{ + struct onenand_chip *this = mtd->priv; + u_char *buf = FLEXONENAND(this) ? this->page_buf : this->oob_buf; + size_t retlen; + int ret; + unsigned int otp_lock_offset = ONENAND_OTP_LOCK_OFFSET; + + memset(buf, 0xff, FLEXONENAND(this) ? this->writesize + : mtd->oobsize); + /* + * Write lock mark to 8th word of sector0 of page0 of the spare0. + * We write 16 bytes spare area instead of 2 bytes. + * For Flex-OneNAND, we write lock mark to 1st word of sector 4 of + * main area of page 49. + */ + + from = 0; + len = FLEXONENAND(this) ? mtd->writesize : 16; + + /* + * Note: OTP lock operation + * OTP block : 0xXXFC XX 1111 1100 + * 1st block : 0xXXF3 (If chip support) XX 1111 0011 + * Both : 0xXXF0 (If chip support) XX 1111 0000 + */ + if (FLEXONENAND(this)) + otp_lock_offset = FLEXONENAND_OTP_LOCK_OFFSET; + + /* ONENAND_OTP_AREA | ONENAND_OTP_BLOCK0 | ONENAND_OTP_AREA_BLOCK0 */ + if (otp == 1) + buf[otp_lock_offset] = 0xFC; + else if (otp == 2) + buf[otp_lock_offset] = 0xF3; + else if (otp == 3) + buf[otp_lock_offset] = 0xF0; + else if (otp != 0) + printk(KERN_DEBUG "[OneNAND] Invalid option selected for OTP\n"); + + ret = onenand_otp_walk(mtd, from, len, &retlen, buf, do_otp_lock, MTD_OTP_USER); + + return ret ? : retlen; +} + +#endif /* CONFIG_MTD_ONENAND_OTP */ + +/** + * onenand_check_features - Check and set OneNAND features + * @mtd: MTD data structure + * + * Check and set OneNAND features + * - lock scheme + * - two plane + */ +static void onenand_check_features(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + unsigned int density, process, numbufs; + + /* Lock scheme depends on density and process */ + density = onenand_get_density(this->device_id); + process = this->version_id >> ONENAND_VERSION_PROCESS_SHIFT; + numbufs = this->read_word(this->base + ONENAND_REG_NUM_BUFFERS) >> 8; + + /* Lock scheme */ + switch (density) { + case ONENAND_DEVICE_DENSITY_8Gb: + this->options |= ONENAND_HAS_NOP_1; + fallthrough; + case ONENAND_DEVICE_DENSITY_4Gb: + if (ONENAND_IS_DDP(this)) + this->options |= ONENAND_HAS_2PLANE; + else if (numbufs == 1) { + this->options |= ONENAND_HAS_4KB_PAGE; + this->options |= ONENAND_HAS_CACHE_PROGRAM; + /* + * There are two different 4KiB pagesize chips + * and no way to detect it by H/W config values. + * + * To detect the correct NOP for each chips, + * It should check the version ID as workaround. + * + * Now it has as following + * KFM4G16Q4M has NOP 4 with version ID 0x0131 + * KFM4G16Q5M has NOP 1 with versoin ID 0x013e + */ + if ((this->version_id & 0xf) == 0xe) + this->options |= ONENAND_HAS_NOP_1; + } + this->options |= ONENAND_HAS_UNLOCK_ALL; + break; + + case ONENAND_DEVICE_DENSITY_2Gb: + /* 2Gb DDP does not have 2 plane */ + if (!ONENAND_IS_DDP(this)) + this->options |= ONENAND_HAS_2PLANE; + this->options |= ONENAND_HAS_UNLOCK_ALL; + break; + + case ONENAND_DEVICE_DENSITY_1Gb: + /* A-Die has all block unlock */ + if (process) + this->options |= ONENAND_HAS_UNLOCK_ALL; + break; + + default: + /* Some OneNAND has continuous lock scheme */ + if (!process) + this->options |= ONENAND_HAS_CONT_LOCK; + break; + } + + /* The MLC has 4KiB pagesize. */ + if (ONENAND_IS_MLC(this)) + this->options |= ONENAND_HAS_4KB_PAGE; + + if (ONENAND_IS_4KB_PAGE(this)) + this->options &= ~ONENAND_HAS_2PLANE; + + if (FLEXONENAND(this)) { + this->options &= ~ONENAND_HAS_CONT_LOCK; + this->options |= ONENAND_HAS_UNLOCK_ALL; + } + + if (this->options & ONENAND_HAS_CONT_LOCK) + printk(KERN_DEBUG "Lock scheme is Continuous Lock\n"); + if (this->options & ONENAND_HAS_UNLOCK_ALL) + printk(KERN_DEBUG "Chip support all block unlock\n"); + if (this->options & ONENAND_HAS_2PLANE) + printk(KERN_DEBUG "Chip has 2 plane\n"); + if (this->options & ONENAND_HAS_4KB_PAGE) + printk(KERN_DEBUG "Chip has 4KiB pagesize\n"); + if (this->options & ONENAND_HAS_CACHE_PROGRAM) + printk(KERN_DEBUG "Chip has cache program feature\n"); +} + +/** + * onenand_print_device_info - Print device & version ID + * @device: device ID + * @version: version ID + * + * Print device & version ID + */ +static void onenand_print_device_info(int device, int version) +{ + int vcc, demuxed, ddp, density, flexonenand; + + vcc = device & ONENAND_DEVICE_VCC_MASK; + demuxed = device & ONENAND_DEVICE_IS_DEMUX; + ddp = device & ONENAND_DEVICE_IS_DDP; + density = onenand_get_density(device); + flexonenand = device & DEVICE_IS_FLEXONENAND; + printk(KERN_INFO "%s%sOneNAND%s %dMB %sV 16-bit (0x%02x)\n", + demuxed ? "" : "Muxed ", + flexonenand ? "Flex-" : "", + ddp ? "(DDP)" : "", + (16 << density), + vcc ? "2.65/3.3" : "1.8", + device); + printk(KERN_INFO "OneNAND version = 0x%04x\n", version); +} + +static const struct onenand_manufacturers onenand_manuf_ids[] = { + {ONENAND_MFR_SAMSUNG, "Samsung"}, + {ONENAND_MFR_NUMONYX, "Numonyx"}, +}; + +/** + * onenand_check_maf - Check manufacturer ID + * @manuf: manufacturer ID + * + * Check manufacturer ID + */ +static int onenand_check_maf(int manuf) +{ + int size = ARRAY_SIZE(onenand_manuf_ids); + char *name; + int i; + + for (i = 0; i < size; i++) + if (manuf == onenand_manuf_ids[i].id) + break; + + if (i < size) + name = onenand_manuf_ids[i].name; + else + name = "Unknown"; + + printk(KERN_DEBUG "OneNAND Manufacturer: %s (0x%0x)\n", name, manuf); + + return (i == size); +} + +/** + * flexonenand_get_boundary - Reads the SLC boundary + * @mtd: MTD data structure + */ +static int flexonenand_get_boundary(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + unsigned die, bdry; + int syscfg, locked; + + /* Disable ECC */ + syscfg = this->read_word(this->base + ONENAND_REG_SYS_CFG1); + this->write_word((syscfg | 0x0100), this->base + ONENAND_REG_SYS_CFG1); + + for (die = 0; die < this->dies; die++) { + this->command(mtd, FLEXONENAND_CMD_PI_ACCESS, die, 0); + this->wait(mtd, FL_SYNCING); + + this->command(mtd, FLEXONENAND_CMD_READ_PI, die, 0); + this->wait(mtd, FL_READING); + + bdry = this->read_word(this->base + ONENAND_DATARAM); + if ((bdry >> FLEXONENAND_PI_UNLOCK_SHIFT) == 3) + locked = 0; + else + locked = 1; + this->boundary[die] = bdry & FLEXONENAND_PI_MASK; + + this->command(mtd, ONENAND_CMD_RESET, 0, 0); + this->wait(mtd, FL_RESETTING); + + printk(KERN_INFO "Die %d boundary: %d%s\n", die, + this->boundary[die], locked ? "(Locked)" : "(Unlocked)"); + } + + /* Enable ECC */ + this->write_word(syscfg, this->base + ONENAND_REG_SYS_CFG1); + return 0; +} + +/** + * flexonenand_get_size - Fill up fields in onenand_chip and mtd_info + * boundary[], diesize[], mtd->size, mtd->erasesize + * @mtd: - MTD device structure + */ +static void flexonenand_get_size(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + int die, i, eraseshift, density; + int blksperdie, maxbdry; + loff_t ofs; + + density = onenand_get_density(this->device_id); + blksperdie = ((loff_t)(16 << density) << 20) >> (this->erase_shift); + blksperdie >>= ONENAND_IS_DDP(this) ? 1 : 0; + maxbdry = blksperdie - 1; + eraseshift = this->erase_shift - 1; + + mtd->numeraseregions = this->dies << 1; + + /* This fills up the device boundary */ + flexonenand_get_boundary(mtd); + die = ofs = 0; + i = -1; + for (; die < this->dies; die++) { + if (!die || this->boundary[die-1] != maxbdry) { + i++; + mtd->eraseregions[i].offset = ofs; + mtd->eraseregions[i].erasesize = 1 << eraseshift; + mtd->eraseregions[i].numblocks = + this->boundary[die] + 1; + ofs += mtd->eraseregions[i].numblocks << eraseshift; + eraseshift++; + } else { + mtd->numeraseregions -= 1; + mtd->eraseregions[i].numblocks += + this->boundary[die] + 1; + ofs += (this->boundary[die] + 1) << (eraseshift - 1); + } + if (this->boundary[die] != maxbdry) { + i++; + mtd->eraseregions[i].offset = ofs; + mtd->eraseregions[i].erasesize = 1 << eraseshift; + mtd->eraseregions[i].numblocks = maxbdry ^ + this->boundary[die]; + ofs += mtd->eraseregions[i].numblocks << eraseshift; + eraseshift--; + } else + mtd->numeraseregions -= 1; + } + + /* Expose MLC erase size except when all blocks are SLC */ + mtd->erasesize = 1 << this->erase_shift; + if (mtd->numeraseregions == 1) + mtd->erasesize >>= 1; + + printk(KERN_INFO "Device has %d eraseregions\n", mtd->numeraseregions); + for (i = 0; i < mtd->numeraseregions; i++) + printk(KERN_INFO "[offset: 0x%08x, erasesize: 0x%05x," + " numblocks: %04u]\n", + (unsigned int) mtd->eraseregions[i].offset, + mtd->eraseregions[i].erasesize, + mtd->eraseregions[i].numblocks); + + for (die = 0, mtd->size = 0; die < this->dies; die++) { + this->diesize[die] = (loff_t)blksperdie << this->erase_shift; + this->diesize[die] -= (loff_t)(this->boundary[die] + 1) + << (this->erase_shift - 1); + mtd->size += this->diesize[die]; + } +} + +/** + * flexonenand_check_blocks_erased - Check if blocks are erased + * @mtd: mtd info structure + * @start: first erase block to check + * @end: last erase block to check + * + * Converting an unerased block from MLC to SLC + * causes byte values to change. Since both data and its ECC + * have changed, reads on the block give uncorrectable error. + * This might lead to the block being detected as bad. + * + * Avoid this by ensuring that the block to be converted is + * erased. + */ +static int flexonenand_check_blocks_erased(struct mtd_info *mtd, int start, int end) +{ + struct onenand_chip *this = mtd->priv; + int i, ret; + int block; + struct mtd_oob_ops ops = { + .mode = MTD_OPS_PLACE_OOB, + .ooboffs = 0, + .ooblen = mtd->oobsize, + .datbuf = NULL, + .oobbuf = this->oob_buf, + }; + loff_t addr; + + printk(KERN_DEBUG "Check blocks from %d to %d\n", start, end); + + for (block = start; block <= end; block++) { + addr = flexonenand_addr(this, block); + if (onenand_block_isbad_nolock(mtd, addr, 0)) + continue; + + /* + * Since main area write results in ECC write to spare, + * it is sufficient to check only ECC bytes for change. + */ + ret = onenand_read_oob_nolock(mtd, addr, &ops); + if (ret) + return ret; + + for (i = 0; i < mtd->oobsize; i++) + if (this->oob_buf[i] != 0xff) + break; + + if (i != mtd->oobsize) { + printk(KERN_WARNING "%s: Block %d not erased.\n", + __func__, block); + return 1; + } + } + + return 0; +} + +/* + * flexonenand_set_boundary - Writes the SLC boundary + */ +static int flexonenand_set_boundary(struct mtd_info *mtd, int die, + int boundary, int lock) +{ + struct onenand_chip *this = mtd->priv; + int ret, density, blksperdie, old, new, thisboundary; + loff_t addr; + + /* Change only once for SDP Flex-OneNAND */ + if (die && (!ONENAND_IS_DDP(this))) + return 0; + + /* boundary value of -1 indicates no required change */ + if (boundary < 0 || boundary == this->boundary[die]) + return 0; + + density = onenand_get_density(this->device_id); + blksperdie = ((16 << density) << 20) >> this->erase_shift; + blksperdie >>= ONENAND_IS_DDP(this) ? 1 : 0; + + if (boundary >= blksperdie) { + printk(KERN_ERR "%s: Invalid boundary value. " + "Boundary not changed.\n", __func__); + return -EINVAL; + } + + /* Check if converting blocks are erased */ + old = this->boundary[die] + (die * this->density_mask); + new = boundary + (die * this->density_mask); + ret = flexonenand_check_blocks_erased(mtd, min(old, new) + 1, max(old, new)); + if (ret) { + printk(KERN_ERR "%s: Please erase blocks " + "before boundary change\n", __func__); + return ret; + } + + this->command(mtd, FLEXONENAND_CMD_PI_ACCESS, die, 0); + this->wait(mtd, FL_SYNCING); + + /* Check is boundary is locked */ + this->command(mtd, FLEXONENAND_CMD_READ_PI, die, 0); + this->wait(mtd, FL_READING); + + thisboundary = this->read_word(this->base + ONENAND_DATARAM); + if ((thisboundary >> FLEXONENAND_PI_UNLOCK_SHIFT) != 3) { + printk(KERN_ERR "%s: boundary locked\n", __func__); + ret = 1; + goto out; + } + + printk(KERN_INFO "Changing die %d boundary: %d%s\n", + die, boundary, lock ? "(Locked)" : "(Unlocked)"); + + addr = die ? this->diesize[0] : 0; + + boundary &= FLEXONENAND_PI_MASK; + boundary |= lock ? 0 : (3 << FLEXONENAND_PI_UNLOCK_SHIFT); + + this->command(mtd, ONENAND_CMD_ERASE, addr, 0); + ret = this->wait(mtd, FL_ERASING); + if (ret) { + printk(KERN_ERR "%s: Failed PI erase for Die %d\n", + __func__, die); + goto out; + } + + this->write_word(boundary, this->base + ONENAND_DATARAM); + this->command(mtd, ONENAND_CMD_PROG, addr, 0); + ret = this->wait(mtd, FL_WRITING); + if (ret) { + printk(KERN_ERR "%s: Failed PI write for Die %d\n", + __func__, die); + goto out; + } + + this->command(mtd, FLEXONENAND_CMD_PI_UPDATE, die, 0); + ret = this->wait(mtd, FL_WRITING); +out: + this->write_word(ONENAND_CMD_RESET, this->base + ONENAND_REG_COMMAND); + this->wait(mtd, FL_RESETTING); + if (!ret) + /* Recalculate device size on boundary change*/ + flexonenand_get_size(mtd); + + return ret; +} + +/** + * onenand_chip_probe - [OneNAND Interface] The generic chip probe + * @mtd: MTD device structure + * + * OneNAND detection method: + * Compare the values from command with ones from register + */ +static int onenand_chip_probe(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + int bram_maf_id, bram_dev_id, maf_id, dev_id; + int syscfg; + + /* Save system configuration 1 */ + syscfg = this->read_word(this->base + ONENAND_REG_SYS_CFG1); + /* Clear Sync. Burst Read mode to read BootRAM */ + this->write_word((syscfg & ~ONENAND_SYS_CFG1_SYNC_READ & ~ONENAND_SYS_CFG1_SYNC_WRITE), this->base + ONENAND_REG_SYS_CFG1); + + /* Send the command for reading device ID from BootRAM */ + this->write_word(ONENAND_CMD_READID, this->base + ONENAND_BOOTRAM); + + /* Read manufacturer and device IDs from BootRAM */ + bram_maf_id = this->read_word(this->base + ONENAND_BOOTRAM + 0x0); + bram_dev_id = this->read_word(this->base + ONENAND_BOOTRAM + 0x2); + + /* Reset OneNAND to read default register values */ + this->write_word(ONENAND_CMD_RESET, this->base + ONENAND_BOOTRAM); + /* Wait reset */ + this->wait(mtd, FL_RESETTING); + + /* Restore system configuration 1 */ + this->write_word(syscfg, this->base + ONENAND_REG_SYS_CFG1); + + /* Check manufacturer ID */ + if (onenand_check_maf(bram_maf_id)) + return -ENXIO; + + /* Read manufacturer and device IDs from Register */ + maf_id = this->read_word(this->base + ONENAND_REG_MANUFACTURER_ID); + dev_id = this->read_word(this->base + ONENAND_REG_DEVICE_ID); + + /* Check OneNAND device */ + if (maf_id != bram_maf_id || dev_id != bram_dev_id) + return -ENXIO; + + return 0; +} + +/** + * onenand_probe - [OneNAND Interface] Probe the OneNAND device + * @mtd: MTD device structure + */ +static int onenand_probe(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + int dev_id, ver_id; + int density; + int ret; + + ret = this->chip_probe(mtd); + if (ret) + return ret; + + /* Device and version IDs from Register */ + dev_id = this->read_word(this->base + ONENAND_REG_DEVICE_ID); + ver_id = this->read_word(this->base + ONENAND_REG_VERSION_ID); + this->technology = this->read_word(this->base + ONENAND_REG_TECHNOLOGY); + + /* Flash device information */ + onenand_print_device_info(dev_id, ver_id); + this->device_id = dev_id; + this->version_id = ver_id; + + /* Check OneNAND features */ + onenand_check_features(mtd); + + density = onenand_get_density(dev_id); + if (FLEXONENAND(this)) { + this->dies = ONENAND_IS_DDP(this) ? 2 : 1; + /* Maximum possible erase regions */ + mtd->numeraseregions = this->dies << 1; + mtd->eraseregions = + kzalloc_objs(struct mtd_erase_region_info, + this->dies << 1); + if (!mtd->eraseregions) + return -ENOMEM; + } + + /* + * For Flex-OneNAND, chipsize represents maximum possible device size. + * mtd->size represents the actual device size. + */ + this->chipsize = (16 << density) << 20; + + /* OneNAND page size & block size */ + /* The data buffer size is equal to page size */ + mtd->writesize = this->read_word(this->base + ONENAND_REG_DATA_BUFFER_SIZE); + /* We use the full BufferRAM */ + if (ONENAND_IS_4KB_PAGE(this)) + mtd->writesize <<= 1; + + mtd->oobsize = mtd->writesize >> 5; + /* Pages per a block are always 64 in OneNAND */ + mtd->erasesize = mtd->writesize << 6; + /* + * Flex-OneNAND SLC area has 64 pages per block. + * Flex-OneNAND MLC area has 128 pages per block. + * Expose MLC erase size to find erase_shift and page_mask. + */ + if (FLEXONENAND(this)) + mtd->erasesize <<= 1; + + this->erase_shift = ffs(mtd->erasesize) - 1; + this->page_shift = ffs(mtd->writesize) - 1; + this->page_mask = (1 << (this->erase_shift - this->page_shift)) - 1; + /* Set density mask. it is used for DDP */ + if (ONENAND_IS_DDP(this)) + this->density_mask = this->chipsize >> (this->erase_shift + 1); + /* It's real page size */ + this->writesize = mtd->writesize; + + /* REVISIT: Multichip handling */ + + if (FLEXONENAND(this)) + flexonenand_get_size(mtd); + else + mtd->size = this->chipsize; + + /* + * We emulate the 4KiB page and 256KiB erase block size + * But oobsize is still 64 bytes. + * It is only valid if you turn on 2X program support, + * Otherwise it will be ignored by compiler. + */ + if (ONENAND_IS_2PLANE(this)) { + mtd->writesize <<= 1; + mtd->erasesize <<= 1; + } + + return 0; +} + +/** + * onenand_suspend - [MTD Interface] Suspend the OneNAND flash + * @mtd: MTD device structure + */ +static int onenand_suspend(struct mtd_info *mtd) +{ + return onenand_get_device(mtd, FL_PM_SUSPENDED); +} + +/** + * onenand_resume - [MTD Interface] Resume the OneNAND flash + * @mtd: MTD device structure + */ +static void onenand_resume(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + + if (this->state == FL_PM_SUSPENDED) + onenand_release_device(mtd); + else + printk(KERN_ERR "%s: resume() called for the chip which is not " + "in suspended state\n", __func__); +} + +/** + * onenand_scan - [OneNAND Interface] Scan for the OneNAND device + * @mtd: MTD device structure + * @maxchips: Number of chips to scan for + * + * This fills out all the not initialized function pointers + * with the defaults. + * The flash ID is read and the mtd/chip structures are + * filled with the appropriate values. + */ +int onenand_scan(struct mtd_info *mtd, int maxchips) +{ + int i, ret; + struct onenand_chip *this = mtd->priv; + + if (!this->read_word) + this->read_word = onenand_readw; + if (!this->write_word) + this->write_word = onenand_writew; + + if (!this->command) + this->command = onenand_command; + if (!this->wait) + onenand_setup_wait(mtd); + if (!this->bbt_wait) + this->bbt_wait = onenand_bbt_wait; + if (!this->unlock_all) + this->unlock_all = onenand_unlock_all; + + if (!this->chip_probe) + this->chip_probe = onenand_chip_probe; + + if (!this->read_bufferram) + this->read_bufferram = onenand_read_bufferram; + if (!this->write_bufferram) + this->write_bufferram = onenand_write_bufferram; + + if (!this->block_markbad) + this->block_markbad = onenand_default_block_markbad; + if (!this->scan_bbt) + this->scan_bbt = onenand_default_bbt; + + if (onenand_probe(mtd)) + return -ENXIO; + + /* Set Sync. Burst Read after probing */ + if (this->mmcontrol) { + printk(KERN_INFO "OneNAND Sync. Burst Read support\n"); + this->read_bufferram = onenand_sync_read_bufferram; + } + + /* Allocate buffers, if necessary */ + if (!this->page_buf) { + this->page_buf = kzalloc(mtd->writesize, GFP_KERNEL); + if (!this->page_buf) + return -ENOMEM; +#ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE + this->verify_buf = kzalloc(mtd->writesize, GFP_KERNEL); + if (!this->verify_buf) { + kfree(this->page_buf); + return -ENOMEM; + } +#endif + this->options |= ONENAND_PAGEBUF_ALLOC; + } + if (!this->oob_buf) { + this->oob_buf = kzalloc(mtd->oobsize, GFP_KERNEL); + if (!this->oob_buf) { + if (this->options & ONENAND_PAGEBUF_ALLOC) { + this->options &= ~ONENAND_PAGEBUF_ALLOC; +#ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE + kfree(this->verify_buf); +#endif + kfree(this->page_buf); + } + return -ENOMEM; + } + this->options |= ONENAND_OOBBUF_ALLOC; + } + + this->state = FL_READY; + init_waitqueue_head(&this->wq); + spin_lock_init(&this->chip_lock); + + /* + * Allow subpage writes up to oobsize. + */ + switch (mtd->oobsize) { + case 128: + if (FLEXONENAND(this)) { + mtd_set_ooblayout(mtd, &flexonenand_ooblayout_ops); + mtd->subpage_sft = 0; + } else { + mtd_set_ooblayout(mtd, &onenand_oob_128_ooblayout_ops); + mtd->subpage_sft = 2; + } + if (ONENAND_IS_NOP_1(this)) + mtd->subpage_sft = 0; + break; + case 64: + mtd_set_ooblayout(mtd, &onenand_oob_32_64_ooblayout_ops); + mtd->subpage_sft = 2; + break; + + case 32: + mtd_set_ooblayout(mtd, &onenand_oob_32_64_ooblayout_ops); + mtd->subpage_sft = 1; + break; + + default: + printk(KERN_WARNING "%s: No OOB scheme defined for oobsize %d\n", + __func__, mtd->oobsize); + mtd->subpage_sft = 0; + /* To prevent kernel oops */ + mtd_set_ooblayout(mtd, &onenand_oob_32_64_ooblayout_ops); + break; + } + + this->subpagesize = mtd->writesize >> mtd->subpage_sft; + + /* + * The number of bytes available for a client to place data into + * the out of band area + */ + ret = mtd_ooblayout_count_freebytes(mtd); + if (ret < 0) + ret = 0; + + mtd->oobavail = ret; + + mtd->ecc_strength = 1; + + /* Fill in remaining MTD driver data */ + mtd->type = ONENAND_IS_MLC(this) ? MTD_MLCNANDFLASH : MTD_NANDFLASH; + mtd->flags = MTD_CAP_NANDFLASH; + mtd->_erase = onenand_erase; + mtd->_point = NULL; + mtd->_unpoint = NULL; + mtd->_read_oob = onenand_read_oob; + mtd->_write_oob = onenand_write_oob; + mtd->_panic_write = onenand_panic_write; +#ifdef CONFIG_MTD_ONENAND_OTP + mtd->_get_fact_prot_info = onenand_get_fact_prot_info; + mtd->_read_fact_prot_reg = onenand_read_fact_prot_reg; + mtd->_get_user_prot_info = onenand_get_user_prot_info; + mtd->_read_user_prot_reg = onenand_read_user_prot_reg; + mtd->_write_user_prot_reg = onenand_write_user_prot_reg; + mtd->_lock_user_prot_reg = onenand_lock_user_prot_reg; +#endif + mtd->_sync = onenand_sync; + mtd->_lock = onenand_lock; + mtd->_unlock = onenand_unlock; + mtd->_suspend = onenand_suspend; + mtd->_resume = onenand_resume; + mtd->_block_isbad = onenand_block_isbad; + mtd->_block_markbad = onenand_block_markbad; + mtd->owner = THIS_MODULE; + mtd->writebufsize = mtd->writesize; + + /* Unlock whole block */ + if (!(this->options & ONENAND_SKIP_INITIAL_UNLOCKING)) + this->unlock_all(mtd); + + /* Set the bad block marker position */ + this->badblockpos = ONENAND_BADBLOCK_POS; + + ret = this->scan_bbt(mtd); + if ((!FLEXONENAND(this)) || ret) + return ret; + + /* Change Flex-OneNAND boundaries if required */ + for (i = 0; i < MAX_DIES; i++) + flexonenand_set_boundary(mtd, i, flex_bdry[2 * i], + flex_bdry[(2 * i) + 1]); + + return 0; +} + +/** + * onenand_release - [OneNAND Interface] Free resources held by the OneNAND device + * @mtd: MTD device structure + */ +void onenand_release(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + + /* Deregister partitions */ + mtd_device_unregister(mtd); + + /* Free bad block table memory, if allocated */ + if (this->bbm) { + struct bbm_info *bbm = this->bbm; + kfree(bbm->bbt); + kfree(this->bbm); + } + /* Buffers allocated by onenand_scan */ + if (this->options & ONENAND_PAGEBUF_ALLOC) { + kfree(this->page_buf); +#ifdef CONFIG_MTD_ONENAND_VERIFY_WRITE + kfree(this->verify_buf); +#endif + } + if (this->options & ONENAND_OOBBUF_ALLOC) + kfree(this->oob_buf); + kfree(mtd->eraseregions); +} + +EXPORT_SYMBOL_GPL(onenand_scan); +EXPORT_SYMBOL_GPL(onenand_release); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kyungmin Park "); +MODULE_DESCRIPTION("Generic OneNAND flash driver code"); diff --git a/drivers/mtd/nand/onenand/onenand_bbt.c b/drivers/mtd/nand/onenand/onenand_bbt.c new file mode 100644 index 000000000..012163fb6 --- /dev/null +++ b/drivers/mtd/nand/onenand/onenand_bbt.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Bad Block Table support for the OneNAND driver + * + * Copyright(c) 2005 Samsung Electronics + * Kyungmin Park + * + * Derived from nand_bbt.c + * + * TODO: + * Split BBT core and chip specific BBT. + */ + +#include +#include +#include +#include + +/** + * check_short_pattern - [GENERIC] check if a pattern is in the buffer + * @buf: the buffer to search + * @len: the length of buffer to search + * @paglen: the pagelength + * @td: search pattern descriptor + * + * Check for a pattern at the given place. Used to search bad block + * tables and good / bad block identifiers. Same as check_pattern, but + * no optional empty check and the pattern is expected to start + * at offset 0. + * + */ +static int check_short_pattern(uint8_t *buf, int len, int paglen, struct nand_bbt_descr *td) +{ + int i; + uint8_t *p = buf; + + /* Compare the pattern */ + for (i = 0; i < td->len; i++) { + if (p[i] != td->pattern[i]) + return -1; + } + return 0; +} + +/** + * create_bbt - [GENERIC] Create a bad block table by scanning the device + * @mtd: MTD device structure + * @buf: temporary buffer + * @bd: descriptor for the good/bad block search pattern + * @chip: create the table for a specific chip, -1 read all chips. + * Applies only if NAND_BBT_PERCHIP option is set + * + * Create a bad block table by scanning the device + * for the given good/bad block identify pattern + */ +static int create_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *bd, int chip) +{ + struct onenand_chip *this = mtd->priv; + struct bbm_info *bbm = this->bbm; + int i, j, numblocks, len, scanlen; + int startblock; + loff_t from; + size_t readlen; + struct mtd_oob_ops ops = { }; + int rgn; + + printk(KERN_INFO "Scanning device for bad blocks\n"); + + len = 2; + + /* We need only read few bytes from the OOB area */ + scanlen = 0; + readlen = bd->len; + + /* chip == -1 case only */ + /* Note that numblocks is 2 * (real numblocks) here; + * see i += 2 below as it makses shifting and masking less painful + */ + numblocks = this->chipsize >> (bbm->bbt_erase_shift - 1); + startblock = 0; + from = 0; + + ops.mode = MTD_OPS_PLACE_OOB; + ops.ooblen = readlen; + ops.oobbuf = buf; + ops.len = ops.ooboffs = ops.retlen = ops.oobretlen = 0; + + for (i = startblock; i < numblocks; ) { + int ret; + + for (j = 0; j < len; j++) { + /* No need to read pages fully, + * just read required OOB bytes */ + ret = onenand_bbt_read_oob(mtd, + from + j * this->writesize + bd->offs, &ops); + + /* If it is a initial bad block, just ignore it */ + if (ret == ONENAND_BBT_READ_FATAL_ERROR) + return -EIO; + + if (ret || check_short_pattern(&buf[j * scanlen], + scanlen, this->writesize, bd)) { + bbm->bbt[i >> 3] |= 0x03 << (i & 0x6); + printk(KERN_INFO "OneNAND eraseblock %d is an " + "initial bad block\n", i >> 1); + mtd->ecc_stats.badblocks++; + break; + } + } + i += 2; + + if (FLEXONENAND(this)) { + rgn = flexonenand_region(mtd, from); + from += mtd->eraseregions[rgn].erasesize; + } else + from += (1 << bbm->bbt_erase_shift); + } + + return 0; +} + + +/** + * onenand_memory_bbt - [GENERIC] create a memory based bad block table + * @mtd: MTD device structure + * @bd: descriptor for the good/bad block search pattern + * + * The function creates a memory based bbt by scanning the device + * for manufacturer / software marked good / bad blocks + */ +static inline int onenand_memory_bbt (struct mtd_info *mtd, struct nand_bbt_descr *bd) +{ + struct onenand_chip *this = mtd->priv; + + return create_bbt(mtd, this->page_buf, bd, -1); +} + +/** + * onenand_isbad_bbt - [OneNAND Interface] Check if a block is bad + * @mtd: MTD device structure + * @offs: offset in the device + * @allowbbt: allow access to bad block table region + */ +static int onenand_isbad_bbt(struct mtd_info *mtd, loff_t offs, int allowbbt) +{ + struct onenand_chip *this = mtd->priv; + struct bbm_info *bbm = this->bbm; + int block; + uint8_t res; + + /* Get block number * 2 */ + block = (int) (onenand_block(this, offs) << 1); + res = (bbm->bbt[block >> 3] >> (block & 0x06)) & 0x03; + + pr_debug("onenand_isbad_bbt: bbt info for offs 0x%08x: (block %d) 0x%02x\n", + (unsigned int) offs, block >> 1, res); + + switch ((int) res) { + case 0x00: return 0; + case 0x01: return 1; + case 0x02: return allowbbt ? 0 : 1; + } + + return 1; +} + +/** + * onenand_scan_bbt - [OneNAND Interface] scan, find, read and maybe create bad block table(s) + * @mtd: MTD device structure + * @bd: descriptor for the good/bad block search pattern + * + * The function checks, if a bad block table(s) is/are already + * available. If not it scans the device for manufacturer + * marked good / bad blocks and writes the bad block table(s) to + * the selected place. + * + * The bad block table memory is allocated here. It is freed + * by the onenand_release function. + * + */ +static int onenand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd) +{ + struct onenand_chip *this = mtd->priv; + struct bbm_info *bbm = this->bbm; + int len, ret = 0; + + len = this->chipsize >> (this->erase_shift + 2); + /* Allocate memory (2bit per block) and clear the memory bad block table */ + bbm->bbt = kzalloc(len, GFP_KERNEL); + if (!bbm->bbt) + return -ENOMEM; + + /* Set erase shift */ + bbm->bbt_erase_shift = this->erase_shift; + + if (!bbm->isbad_bbt) + bbm->isbad_bbt = onenand_isbad_bbt; + + /* Scan the device to build a memory based bad block table */ + if ((ret = onenand_memory_bbt(mtd, bd))) { + printk(KERN_ERR "onenand_scan_bbt: Can't scan flash and build the RAM-based BBT\n"); + kfree(bbm->bbt); + bbm->bbt = NULL; + } + + return ret; +} + +/* + * Define some generic bad / good block scan pattern which are used + * while scanning a device for factory marked good / bad blocks. + */ +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; + +static struct nand_bbt_descr largepage_memorybased = { + .options = 0, + .offs = 0, + .len = 2, + .pattern = scan_ff_pattern, +}; + +/** + * onenand_default_bbt - [OneNAND Interface] Select a default bad block table for the device + * @mtd: MTD device structure + * + * This function selects the default bad block table + * support for the device and calls the onenand_scan_bbt function + */ +int onenand_default_bbt(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + struct bbm_info *bbm; + + this->bbm = kzalloc_obj(struct bbm_info); + if (!this->bbm) + return -ENOMEM; + + bbm = this->bbm; + + /* 1KB page has same configuration as 2KB page */ + if (!bbm->badblock_pattern) + bbm->badblock_pattern = &largepage_memorybased; + + return onenand_scan_bbt(mtd, bbm->badblock_pattern); +} diff --git a/drivers/mtd/nand/onenand/onenand_omap2.c b/drivers/mtd/nand/onenand/onenand_omap2.c new file mode 100644 index 000000000..0793251ad --- /dev/null +++ b/drivers/mtd/nand/onenand/onenand_omap2.c @@ -0,0 +1,608 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * OneNAND driver for OMAP2 / OMAP3 + * + * Copyright © 2005-2006 Nokia Corporation + * + * Author: Jarkko Lavinen and Juha Yrjölä + * IRQ and DMA support written by Timo Teras + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_NAME "omap2-onenand" + +#define ONENAND_BUFRAM_SIZE (1024 * 5) + +struct omap2_onenand { + struct platform_device *pdev; + int gpmc_cs; + unsigned long phys_base; + struct gpio_desc *int_gpiod; + struct mtd_info mtd; + struct onenand_chip onenand; + struct completion irq_done; + struct completion dma_done; + struct dma_chan *dma_chan; +}; + +static void omap2_onenand_dma_complete_func(void *completion) +{ + complete(completion); +} + +static irqreturn_t omap2_onenand_interrupt(int irq, void *dev_id) +{ + struct omap2_onenand *c = dev_id; + + complete(&c->irq_done); + + return IRQ_HANDLED; +} + +static inline unsigned short read_reg(struct omap2_onenand *c, int reg) +{ + return readw(c->onenand.base + reg); +} + +static inline void write_reg(struct omap2_onenand *c, unsigned short value, + int reg) +{ + writew(value, c->onenand.base + reg); +} + +static int omap2_onenand_set_cfg(struct omap2_onenand *c, + bool sr, bool sw, + int latency, int burst_len) +{ + unsigned short reg = ONENAND_SYS_CFG1_RDY | ONENAND_SYS_CFG1_INT; + + reg |= latency << ONENAND_SYS_CFG1_BRL_SHIFT; + + switch (burst_len) { + case 0: /* continuous */ + break; + case 4: + reg |= ONENAND_SYS_CFG1_BL_4; + break; + case 8: + reg |= ONENAND_SYS_CFG1_BL_8; + break; + case 16: + reg |= ONENAND_SYS_CFG1_BL_16; + break; + case 32: + reg |= ONENAND_SYS_CFG1_BL_32; + break; + default: + return -EINVAL; + } + + if (latency > 5) + reg |= ONENAND_SYS_CFG1_HF; + if (latency > 7) + reg |= ONENAND_SYS_CFG1_VHF; + if (sr) + reg |= ONENAND_SYS_CFG1_SYNC_READ; + if (sw) + reg |= ONENAND_SYS_CFG1_SYNC_WRITE; + + write_reg(c, reg, ONENAND_REG_SYS_CFG1); + + return 0; +} + +static int omap2_onenand_get_freq(int ver) +{ + switch ((ver >> 4) & 0xf) { + case 0: + return 40; + case 1: + return 54; + case 2: + return 66; + case 3: + return 83; + case 4: + return 104; + } + + return -EINVAL; +} + +static void wait_err(char *msg, int state, unsigned int ctrl, unsigned int intr) +{ + printk(KERN_ERR "onenand_wait: %s! state %d ctrl 0x%04x intr 0x%04x\n", + msg, state, ctrl, intr); +} + +static void wait_warn(char *msg, int state, unsigned int ctrl, + unsigned int intr) +{ + printk(KERN_WARNING "onenand_wait: %s! state %d ctrl 0x%04x " + "intr 0x%04x\n", msg, state, ctrl, intr); +} + +static int omap2_onenand_wait(struct mtd_info *mtd, int state) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + unsigned int intr = 0; + unsigned int ctrl, ctrl_mask; + unsigned long timeout; + u32 syscfg; + + if (state == FL_RESETTING || state == FL_PREPARING_ERASE || + state == FL_VERIFYING_ERASE) { + int i = 21; + unsigned int intr_flags = ONENAND_INT_MASTER; + + switch (state) { + case FL_RESETTING: + intr_flags |= ONENAND_INT_RESET; + break; + case FL_PREPARING_ERASE: + intr_flags |= ONENAND_INT_ERASE; + break; + case FL_VERIFYING_ERASE: + i = 101; + break; + } + + while (--i) { + udelay(1); + intr = read_reg(c, ONENAND_REG_INTERRUPT); + if (intr & ONENAND_INT_MASTER) + break; + } + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + if (ctrl & ONENAND_CTRL_ERROR) { + wait_err("controller error", state, ctrl, intr); + return -EIO; + } + if ((intr & intr_flags) == intr_flags) + return 0; + /* Continue in wait for interrupt branch */ + } + + if (state != FL_READING) { + int result; + + /* Turn interrupts on */ + syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); + if (!(syscfg & ONENAND_SYS_CFG1_IOBE)) { + syscfg |= ONENAND_SYS_CFG1_IOBE; + write_reg(c, syscfg, ONENAND_REG_SYS_CFG1); + /* Add a delay to let GPIO settle */ + syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); + } + + reinit_completion(&c->irq_done); + result = gpiod_get_value(c->int_gpiod); + if (result < 0) { + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + intr = read_reg(c, ONENAND_REG_INTERRUPT); + wait_err("gpio error", state, ctrl, intr); + return result; + } else if (result == 0) { + int retry_cnt = 0; +retry: + if (!wait_for_completion_io_timeout(&c->irq_done, + msecs_to_jiffies(20))) { + /* Timeout after 20ms */ + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + if (ctrl & ONENAND_CTRL_ONGO && + !this->ongoing) { + /* + * The operation seems to be still going + * so give it some more time. + */ + retry_cnt += 1; + if (retry_cnt < 3) + goto retry; + intr = read_reg(c, + ONENAND_REG_INTERRUPT); + wait_err("timeout", state, ctrl, intr); + return -EIO; + } + intr = read_reg(c, ONENAND_REG_INTERRUPT); + if ((intr & ONENAND_INT_MASTER) == 0) + wait_warn("timeout", state, ctrl, intr); + } + } + } else { + int retry_cnt = 0; + + /* Turn interrupts off */ + syscfg = read_reg(c, ONENAND_REG_SYS_CFG1); + syscfg &= ~ONENAND_SYS_CFG1_IOBE; + write_reg(c, syscfg, ONENAND_REG_SYS_CFG1); + + timeout = jiffies + msecs_to_jiffies(20); + while (1) { + if (time_before(jiffies, timeout)) { + intr = read_reg(c, ONENAND_REG_INTERRUPT); + if (intr & ONENAND_INT_MASTER) + break; + } else { + /* Timeout after 20ms */ + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + if (ctrl & ONENAND_CTRL_ONGO) { + /* + * The operation seems to be still going + * so give it some more time. + */ + retry_cnt += 1; + if (retry_cnt < 3) { + timeout = jiffies + + msecs_to_jiffies(20); + continue; + } + } + break; + } + } + } + + intr = read_reg(c, ONENAND_REG_INTERRUPT); + ctrl = read_reg(c, ONENAND_REG_CTRL_STATUS); + + if (intr & ONENAND_INT_READ) { + int ecc = read_reg(c, ONENAND_REG_ECC_STATUS); + + if (ecc) { + unsigned int addr1, addr8; + + addr1 = read_reg(c, ONENAND_REG_START_ADDRESS1); + addr8 = read_reg(c, ONENAND_REG_START_ADDRESS8); + if (ecc & ONENAND_ECC_2BIT_ALL) { + printk(KERN_ERR "onenand_wait: ECC error = " + "0x%04x, addr1 %#x, addr8 %#x\n", + ecc, addr1, addr8); + mtd->ecc_stats.failed++; + return -EBADMSG; + } else if (ecc & ONENAND_ECC_1BIT_ALL) { + printk(KERN_NOTICE "onenand_wait: correctable " + "ECC error = 0x%04x, addr1 %#x, " + "addr8 %#x\n", ecc, addr1, addr8); + mtd->ecc_stats.corrected++; + } + } + } else if (state == FL_READING) { + wait_err("timeout", state, ctrl, intr); + return -EIO; + } + + if (ctrl & ONENAND_CTRL_ERROR) { + wait_err("controller error", state, ctrl, intr); + if (ctrl & ONENAND_CTRL_LOCK) + printk(KERN_ERR "onenand_wait: " + "Device is write protected!!!\n"); + return -EIO; + } + + ctrl_mask = 0xFE9F; + if (this->ongoing) + ctrl_mask &= ~0x8000; + + if (ctrl & ctrl_mask) + wait_warn("unexpected controller status", state, ctrl, intr); + + return 0; +} + +static inline int omap2_onenand_bufferram_offset(struct mtd_info *mtd, int area) +{ + struct onenand_chip *this = mtd->priv; + + if (ONENAND_CURRENT_BUFFERRAM(this)) { + if (area == ONENAND_DATARAM) + return this->writesize; + if (area == ONENAND_SPARERAM) + return mtd->oobsize; + } + + return 0; +} + +static inline int omap2_onenand_dma_transfer(struct omap2_onenand *c, + dma_addr_t src, dma_addr_t dst, + size_t count) +{ + struct dma_async_tx_descriptor *tx; + dma_cookie_t cookie; + + tx = dmaengine_prep_dma_memcpy(c->dma_chan, dst, src, count, + DMA_CTRL_ACK | DMA_PREP_INTERRUPT); + if (!tx) { + dev_err(&c->pdev->dev, "Failed to prepare DMA memcpy\n"); + return -EIO; + } + + reinit_completion(&c->dma_done); + + tx->callback = omap2_onenand_dma_complete_func; + tx->callback_param = &c->dma_done; + + cookie = tx->tx_submit(tx); + if (dma_submit_error(cookie)) { + dev_err(&c->pdev->dev, "Failed to do DMA tx_submit\n"); + return -EIO; + } + + dma_async_issue_pending(c->dma_chan); + + if (!wait_for_completion_io_timeout(&c->dma_done, + msecs_to_jiffies(20))) { + dmaengine_terminate_sync(c->dma_chan); + return -ETIMEDOUT; + } + + return 0; +} + +static int omap2_onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, + size_t count) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + struct device *dev = &c->pdev->dev; + void *buf = (void *)buffer; + dma_addr_t dma_src, dma_dst; + int bram_offset, err; + size_t xtra; + + bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset; + /* + * If the buffer address is not DMA-able, len is not long enough to + * make DMA transfers profitable or if invoked from panic_write() + * fallback to PIO mode. + */ + if (!virt_addr_valid(buf) || bram_offset & 3 || (size_t)buf & 3 || + count < 384 || mtd->oops_panic_write) + goto out_copy; + + xtra = count & 3; + if (xtra) { + count -= xtra; + memcpy(buf + count, this->base + bram_offset + count, xtra); + } + + dma_dst = dma_map_single(dev, buf, count, DMA_FROM_DEVICE); + dma_src = c->phys_base + bram_offset; + + if (dma_mapping_error(dev, dma_dst)) { + dev_err(dev, "Couldn't DMA map a %d byte buffer\n", count); + goto out_copy; + } + + err = omap2_onenand_dma_transfer(c, dma_src, dma_dst, count); + dma_unmap_single(dev, dma_dst, count, DMA_FROM_DEVICE); + if (!err) + return 0; + + dev_err(dev, "timeout waiting for DMA\n"); + +out_copy: + memcpy(buf, this->base + bram_offset, count); + return 0; +} + +static int omap2_onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, + int offset, size_t count) +{ + struct omap2_onenand *c = container_of(mtd, struct omap2_onenand, mtd); + struct onenand_chip *this = mtd->priv; + struct device *dev = &c->pdev->dev; + void *buf = (void *)buffer; + dma_addr_t dma_src, dma_dst; + int bram_offset, err; + + bram_offset = omap2_onenand_bufferram_offset(mtd, area) + area + offset; + /* + * If the buffer address is not DMA-able, len is not long enough to + * make DMA transfers profitable or if invoked from panic_write() + * fallback to PIO mode. + */ + if (!virt_addr_valid(buf) || bram_offset & 3 || (size_t)buf & 3 || + count < 384 || mtd->oops_panic_write) + goto out_copy; + + dma_src = dma_map_single(dev, buf, count, DMA_TO_DEVICE); + dma_dst = c->phys_base + bram_offset; + if (dma_mapping_error(dev, dma_src)) { + dev_err(dev, "Couldn't DMA map a %d byte buffer\n", count); + goto out_copy; + } + + err = omap2_onenand_dma_transfer(c, dma_src, dma_dst, count); + dma_unmap_page(dev, dma_src, count, DMA_TO_DEVICE); + if (!err) + return 0; + + dev_err(dev, "timeout waiting for DMA\n"); + +out_copy: + memcpy(this->base + bram_offset, buf, count); + return 0; +} + +static void omap2_onenand_shutdown(struct platform_device *pdev) +{ + struct omap2_onenand *c = dev_get_drvdata(&pdev->dev); + + /* With certain content in the buffer RAM, the OMAP boot ROM code + * can recognize the flash chip incorrectly. Zero it out before + * soft reset. + */ + memset((__force void *)c->onenand.base, 0, ONENAND_BUFRAM_SIZE); +} + +static int omap2_onenand_probe(struct platform_device *pdev) +{ + u32 val; + dma_cap_mask_t mask; + int freq, latency, r; + struct resource *res; + struct omap2_onenand *c; + struct gpmc_onenand_info info; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + + r = of_property_read_u32(np, "reg", &val); + if (r) { + dev_err(dev, "reg not found in DT\n"); + return r; + } + + c = devm_kzalloc(dev, sizeof(struct omap2_onenand), GFP_KERNEL); + if (!c) + return -ENOMEM; + + init_completion(&c->irq_done); + init_completion(&c->dma_done); + c->gpmc_cs = val; + + c->onenand.base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(c->onenand.base)) + return PTR_ERR(c->onenand.base); + c->phys_base = res->start; + + c->int_gpiod = devm_gpiod_get_optional(dev, "int", GPIOD_IN); + if (IS_ERR(c->int_gpiod)) { + /* Just try again if this happens */ + return dev_err_probe(dev, PTR_ERR(c->int_gpiod), "error getting gpio\n"); + } + + if (c->int_gpiod) { + r = devm_request_irq(dev, gpiod_to_irq(c->int_gpiod), + omap2_onenand_interrupt, + IRQF_TRIGGER_RISING, "onenand", c); + if (r) + return r; + + c->onenand.wait = omap2_onenand_wait; + } + + dma_cap_zero(mask); + dma_cap_set(DMA_MEMCPY, mask); + + c->dma_chan = dma_request_channel(mask, NULL, NULL); + if (c->dma_chan) { + c->onenand.read_bufferram = omap2_onenand_read_bufferram; + c->onenand.write_bufferram = omap2_onenand_write_bufferram; + } + + c->pdev = pdev; + c->mtd.priv = &c->onenand; + c->mtd.dev.parent = dev; + mtd_set_of_node(&c->mtd, dev->of_node); + + dev_info(dev, "initializing on CS%d (0x%08lx), va %p, %s mode\n", + c->gpmc_cs, c->phys_base, c->onenand.base, + c->dma_chan ? "DMA" : "PIO"); + + r = onenand_scan(&c->mtd, 1); + if (r < 0) + goto err_release_dma; + + freq = omap2_onenand_get_freq(c->onenand.version_id); + if (freq > 0) { + switch (freq) { + case 104: + latency = 7; + break; + case 83: + latency = 6; + break; + case 66: + latency = 5; + break; + case 56: + latency = 4; + break; + default: /* 40 MHz or lower */ + latency = 3; + break; + } + + r = gpmc_omap_onenand_set_timings(dev, c->gpmc_cs, + freq, latency, &info); + if (r) + goto err_release_onenand; + + r = omap2_onenand_set_cfg(c, info.sync_read, info.sync_write, + latency, info.burst_len); + if (r) + goto err_release_onenand; + + if (info.sync_read || info.sync_write) + dev_info(dev, "optimized timings for %d MHz\n", freq); + } + + r = mtd_device_register(&c->mtd, NULL, 0); + if (r) + goto err_release_onenand; + + platform_set_drvdata(pdev, c); + + return 0; + +err_release_onenand: + onenand_release(&c->mtd); +err_release_dma: + if (c->dma_chan) + dma_release_channel(c->dma_chan); + + return r; +} + +static void omap2_onenand_remove(struct platform_device *pdev) +{ + struct omap2_onenand *c = dev_get_drvdata(&pdev->dev); + + onenand_release(&c->mtd); + if (c->dma_chan) + dma_release_channel(c->dma_chan); + omap2_onenand_shutdown(pdev); +} + +static const struct of_device_id omap2_onenand_id_table[] = { + { .compatible = "ti,omap2-onenand", }, + {}, +}; +MODULE_DEVICE_TABLE(of, omap2_onenand_id_table); + +static struct platform_driver omap2_onenand_driver = { + .probe = omap2_onenand_probe, + .remove = omap2_onenand_remove, + .shutdown = omap2_onenand_shutdown, + .driver = { + .name = DRIVER_NAME, + .of_match_table = omap2_onenand_id_table, + }, +}; + +module_platform_driver(omap2_onenand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jarkko Lavinen "); +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 new file mode 100644 index 000000000..b7b7758ce --- /dev/null +++ b/drivers/mtd/nand/onenand/onenand_samsung.c @@ -0,0 +1,1006 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung S3C64XX/S5PC1XX OneNAND driver + * + * Copyright © 2008-2010 Samsung Electronics + * Kyungmin Park + * Marek Szyprowski + * + * Implementation: + * S3C64XX: emulate the pseudo BufferRAM + * S5PC110: use DMA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "samsung.h" + +enum soc_type { + TYPE_S3C6400, + TYPE_S3C6410, + TYPE_S5PC110, +}; + +#define ONENAND_ERASE_STATUS 0x00 +#define ONENAND_MULTI_ERASE_SET 0x01 +#define ONENAND_ERASE_START 0x03 +#define ONENAND_UNLOCK_START 0x08 +#define ONENAND_UNLOCK_END 0x09 +#define ONENAND_LOCK_START 0x0A +#define ONENAND_LOCK_END 0x0B +#define ONENAND_LOCK_TIGHT_START 0x0C +#define ONENAND_LOCK_TIGHT_END 0x0D +#define ONENAND_UNLOCK_ALL 0x0E +#define ONENAND_OTP_ACCESS 0x12 +#define ONENAND_SPARE_ACCESS_ONLY 0x13 +#define ONENAND_MAIN_ACCESS_ONLY 0x14 +#define ONENAND_ERASE_VERIFY 0x15 +#define ONENAND_MAIN_SPARE_ACCESS 0x16 +#define ONENAND_PIPELINE_READ 0x4000 + +#define MAP_00 (0x0) +#define MAP_01 (0x1) +#define MAP_10 (0x2) +#define MAP_11 (0x3) + +#define S3C64XX_CMD_MAP_SHIFT 24 + +#define S3C6400_FBA_SHIFT 10 +#define S3C6400_FPA_SHIFT 4 +#define S3C6400_FSA_SHIFT 2 + +#define S3C6410_FBA_SHIFT 12 +#define S3C6410_FPA_SHIFT 6 +#define S3C6410_FSA_SHIFT 4 + +/* S5PC110 specific definitions */ +#define S5PC110_DMA_SRC_ADDR 0x400 +#define S5PC110_DMA_SRC_CFG 0x404 +#define S5PC110_DMA_DST_ADDR 0x408 +#define S5PC110_DMA_DST_CFG 0x40C +#define S5PC110_DMA_TRANS_SIZE 0x414 +#define S5PC110_DMA_TRANS_CMD 0x418 +#define S5PC110_DMA_TRANS_STATUS 0x41C +#define S5PC110_DMA_TRANS_DIR 0x420 +#define S5PC110_INTC_DMA_CLR 0x1004 +#define S5PC110_INTC_ONENAND_CLR 0x1008 +#define S5PC110_INTC_DMA_MASK 0x1024 +#define S5PC110_INTC_ONENAND_MASK 0x1028 +#define S5PC110_INTC_DMA_PEND 0x1044 +#define S5PC110_INTC_ONENAND_PEND 0x1048 +#define S5PC110_INTC_DMA_STATUS 0x1064 +#define S5PC110_INTC_ONENAND_STATUS 0x1068 + +#define S5PC110_INTC_DMA_TD (1 << 24) +#define S5PC110_INTC_DMA_TE (1 << 16) + +#define S5PC110_DMA_CFG_SINGLE (0x0 << 16) +#define S5PC110_DMA_CFG_4BURST (0x2 << 16) +#define S5PC110_DMA_CFG_8BURST (0x3 << 16) +#define S5PC110_DMA_CFG_16BURST (0x4 << 16) + +#define S5PC110_DMA_CFG_INC (0x0 << 8) +#define S5PC110_DMA_CFG_CNT (0x1 << 8) + +#define S5PC110_DMA_CFG_8BIT (0x0 << 0) +#define S5PC110_DMA_CFG_16BIT (0x1 << 0) +#define S5PC110_DMA_CFG_32BIT (0x2 << 0) + +#define S5PC110_DMA_SRC_CFG_READ (S5PC110_DMA_CFG_16BURST | \ + S5PC110_DMA_CFG_INC | \ + S5PC110_DMA_CFG_16BIT) +#define S5PC110_DMA_DST_CFG_READ (S5PC110_DMA_CFG_16BURST | \ + S5PC110_DMA_CFG_INC | \ + S5PC110_DMA_CFG_32BIT) +#define S5PC110_DMA_SRC_CFG_WRITE (S5PC110_DMA_CFG_16BURST | \ + S5PC110_DMA_CFG_INC | \ + S5PC110_DMA_CFG_32BIT) +#define S5PC110_DMA_DST_CFG_WRITE (S5PC110_DMA_CFG_16BURST | \ + S5PC110_DMA_CFG_INC | \ + S5PC110_DMA_CFG_16BIT) + +#define S5PC110_DMA_TRANS_CMD_TDC (0x1 << 18) +#define S5PC110_DMA_TRANS_CMD_TEC (0x1 << 16) +#define S5PC110_DMA_TRANS_CMD_TR (0x1 << 0) + +#define S5PC110_DMA_TRANS_STATUS_TD (0x1 << 18) +#define S5PC110_DMA_TRANS_STATUS_TB (0x1 << 17) +#define S5PC110_DMA_TRANS_STATUS_TE (0x1 << 16) + +#define S5PC110_DMA_DIR_READ 0x0 +#define S5PC110_DMA_DIR_WRITE 0x1 + +struct s3c_onenand { + struct mtd_info *mtd; + struct platform_device *pdev; + enum soc_type type; + void __iomem *base; + void __iomem *ahb_addr; + int bootram_command; + void *page_buf; + void *oob_buf; + unsigned int (*mem_addr)(int fba, int fpa, int fsa); + unsigned int (*cmd_map)(unsigned int type, unsigned int val); + void __iomem *dma_addr; + unsigned long phys_base; + struct completion complete; +}; + +#define CMD_MAP_00(dev, addr) (dev->cmd_map(MAP_00, ((addr) << 1))) +#define CMD_MAP_01(dev, mem_addr) (dev->cmd_map(MAP_01, (mem_addr))) +#define CMD_MAP_10(dev, mem_addr) (dev->cmd_map(MAP_10, (mem_addr))) +#define CMD_MAP_11(dev, addr) (dev->cmd_map(MAP_11, ((addr) << 2))) + +static struct s3c_onenand *onenand; + +static inline int s3c_read_reg(int offset) +{ + return readl(onenand->base + offset); +} + +static inline void s3c_write_reg(int value, int offset) +{ + writel(value, onenand->base + offset); +} + +static inline int s3c_read_cmd(unsigned int cmd) +{ + return readl(onenand->ahb_addr + cmd); +} + +static inline void s3c_write_cmd(int value, unsigned int cmd) +{ + writel(value, onenand->ahb_addr + cmd); +} + +#ifdef SAMSUNG_DEBUG +static void s3c_dump_reg(void) +{ + int i; + + for (i = 0; i < 0x400; i += 0x40) { + printk(KERN_INFO "0x%08X: 0x%08x 0x%08x 0x%08x 0x%08x\n", + (unsigned int) onenand->base + i, + s3c_read_reg(i), s3c_read_reg(i + 0x10), + s3c_read_reg(i + 0x20), s3c_read_reg(i + 0x30)); + } +} +#endif + +static unsigned int s3c64xx_cmd_map(unsigned type, unsigned val) +{ + return (type << S3C64XX_CMD_MAP_SHIFT) | val; +} + +static unsigned int s3c6400_mem_addr(int fba, int fpa, int fsa) +{ + return (fba << S3C6400_FBA_SHIFT) | (fpa << S3C6400_FPA_SHIFT) | + (fsa << S3C6400_FSA_SHIFT); +} + +static unsigned int s3c6410_mem_addr(int fba, int fpa, int fsa) +{ + return (fba << S3C6410_FBA_SHIFT) | (fpa << S3C6410_FPA_SHIFT) | + (fsa << S3C6410_FSA_SHIFT); +} + +static void s3c_onenand_reset(void) +{ + unsigned long timeout = 0x10000; + int stat; + + s3c_write_reg(ONENAND_MEM_RESET_COLD, MEM_RESET_OFFSET); + while (1 && timeout--) { + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + if (stat & RST_CMP) + break; + } + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + s3c_write_reg(stat, INT_ERR_ACK_OFFSET); + + /* Clear interrupt */ + s3c_write_reg(0x0, INT_ERR_ACK_OFFSET); + /* Clear the ECC status */ + s3c_write_reg(0x0, ECC_ERR_STAT_OFFSET); +} + +static unsigned short s3c_onenand_readw(void __iomem *addr) +{ + struct onenand_chip *this = onenand->mtd->priv; + struct device *dev = &onenand->pdev->dev; + int reg = addr - this->base; + int word_addr = reg >> 1; + int value; + + /* It's used for probing time */ + switch (reg) { + case ONENAND_REG_MANUFACTURER_ID: + return s3c_read_reg(MANUFACT_ID_OFFSET); + case ONENAND_REG_DEVICE_ID: + return s3c_read_reg(DEVICE_ID_OFFSET); + case ONENAND_REG_VERSION_ID: + return s3c_read_reg(FLASH_VER_ID_OFFSET); + case ONENAND_REG_DATA_BUFFER_SIZE: + return s3c_read_reg(DATA_BUF_SIZE_OFFSET); + case ONENAND_REG_TECHNOLOGY: + return s3c_read_reg(TECH_OFFSET); + case ONENAND_REG_SYS_CFG1: + return s3c_read_reg(MEM_CFG_OFFSET); + + /* Used at unlock all status */ + case ONENAND_REG_CTRL_STATUS: + return 0; + + case ONENAND_REG_WP_STATUS: + return ONENAND_WP_US; + + default: + break; + } + + /* BootRAM access control */ + if ((unsigned long)addr < ONENAND_DATARAM && onenand->bootram_command) { + if (word_addr == 0) + return s3c_read_reg(MANUFACT_ID_OFFSET); + if (word_addr == 1) + return s3c_read_reg(DEVICE_ID_OFFSET); + if (word_addr == 2) + return s3c_read_reg(FLASH_VER_ID_OFFSET); + } + + value = s3c_read_cmd(CMD_MAP_11(onenand, word_addr)) & 0xffff; + dev_info(dev, "%s: Illegal access at reg 0x%x, value 0x%x\n", __func__, + word_addr, value); + return value; +} + +static void s3c_onenand_writew(unsigned short value, void __iomem *addr) +{ + struct onenand_chip *this = onenand->mtd->priv; + struct device *dev = &onenand->pdev->dev; + unsigned int reg = addr - this->base; + unsigned int word_addr = reg >> 1; + + /* It's used for probing time */ + switch (reg) { + case ONENAND_REG_SYS_CFG1: + s3c_write_reg(value, MEM_CFG_OFFSET); + return; + + case ONENAND_REG_START_ADDRESS1: + case ONENAND_REG_START_ADDRESS2: + return; + + /* Lock/lock-tight/unlock/unlock_all */ + case ONENAND_REG_START_BLOCK_ADDRESS: + return; + + default: + break; + } + + /* BootRAM access control */ + if ((unsigned long)addr < ONENAND_DATARAM) { + if (value == ONENAND_CMD_READID) { + onenand->bootram_command = 1; + return; + } + if (value == ONENAND_CMD_RESET) { + s3c_write_reg(ONENAND_MEM_RESET_COLD, MEM_RESET_OFFSET); + onenand->bootram_command = 0; + return; + } + } + + dev_info(dev, "%s: Illegal access at reg 0x%x, value 0x%x\n", __func__, + word_addr, value); + + s3c_write_cmd(value, CMD_MAP_11(onenand, word_addr)); +} + +static int s3c_onenand_wait(struct mtd_info *mtd, int state) +{ + struct device *dev = &onenand->pdev->dev; + unsigned int flags = INT_ACT; + unsigned int stat, ecc; + unsigned long timeout; + + switch (state) { + case FL_READING: + flags |= BLK_RW_CMP | LOAD_CMP; + break; + case FL_WRITING: + flags |= BLK_RW_CMP | PGM_CMP; + break; + case FL_ERASING: + flags |= BLK_RW_CMP | ERS_CMP; + break; + case FL_LOCKING: + flags |= BLK_RW_CMP; + break; + default: + break; + } + + /* The 20 msec is enough */ + timeout = jiffies + msecs_to_jiffies(20); + while (time_before(jiffies, timeout)) { + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + if (stat & flags) + break; + + if (state != FL_READING) + cond_resched(); + } + /* To get correct interrupt status in timeout case */ + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + s3c_write_reg(stat, INT_ERR_ACK_OFFSET); + + /* + * In the Spec. it checks the controller status first + * However if you get the correct information in case of + * power off recovery (POR) test, it should read ECC status first + */ + if (stat & LOAD_CMP) { + ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET); + if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) { + dev_info(dev, "%s: ECC error = 0x%04x\n", __func__, + ecc); + mtd->ecc_stats.failed++; + return -EBADMSG; + } + } + + if (stat & (LOCKED_BLK | ERS_FAIL | PGM_FAIL | LD_FAIL_ECC_ERR)) { + dev_info(dev, "%s: controller error = 0x%04x\n", __func__, + stat); + if (stat & LOCKED_BLK) + dev_info(dev, "%s: it's locked error = 0x%04x\n", + __func__, stat); + + return -EIO; + } + + return 0; +} + +static int s3c_onenand_command(struct mtd_info *mtd, int cmd, loff_t addr, + size_t len) +{ + struct onenand_chip *this = mtd->priv; + unsigned int *m, *s; + int fba, fpa, fsa = 0; + unsigned int mem_addr, cmd_map_01, cmd_map_10; + int i, mcount, scount; + int index; + + fba = (int) (addr >> this->erase_shift); + fpa = (int) (addr >> this->page_shift); + fpa &= this->page_mask; + + mem_addr = onenand->mem_addr(fba, fpa, fsa); + cmd_map_01 = CMD_MAP_01(onenand, mem_addr); + cmd_map_10 = CMD_MAP_10(onenand, mem_addr); + + switch (cmd) { + case ONENAND_CMD_READ: + case ONENAND_CMD_READOOB: + case ONENAND_CMD_BUFFERRAM: + ONENAND_SET_NEXT_BUFFERRAM(this); + break; + default: + break; + } + + index = ONENAND_CURRENT_BUFFERRAM(this); + + /* + * Emulate Two BufferRAMs and access with 4 bytes pointer + */ + m = onenand->page_buf; + s = onenand->oob_buf; + + if (index) { + m += (this->writesize >> 2); + s += (mtd->oobsize >> 2); + } + + mcount = mtd->writesize >> 2; + scount = mtd->oobsize >> 2; + + switch (cmd) { + case ONENAND_CMD_READ: + /* Main */ + for (i = 0; i < mcount; i++) + *m++ = s3c_read_cmd(cmd_map_01); + return 0; + + case ONENAND_CMD_READOOB: + s3c_write_reg(TSRF, TRANS_SPARE_OFFSET); + /* Main */ + for (i = 0; i < mcount; i++) + *m++ = s3c_read_cmd(cmd_map_01); + + /* Spare */ + for (i = 0; i < scount; i++) + *s++ = s3c_read_cmd(cmd_map_01); + + s3c_write_reg(0, TRANS_SPARE_OFFSET); + return 0; + + case ONENAND_CMD_PROG: + /* Main */ + for (i = 0; i < mcount; i++) + s3c_write_cmd(*m++, cmd_map_01); + return 0; + + case ONENAND_CMD_PROGOOB: + s3c_write_reg(TSRF, TRANS_SPARE_OFFSET); + + /* Main - dummy write */ + for (i = 0; i < mcount; i++) + s3c_write_cmd(0xffffffff, cmd_map_01); + + /* Spare */ + for (i = 0; i < scount; i++) + s3c_write_cmd(*s++, cmd_map_01); + + s3c_write_reg(0, TRANS_SPARE_OFFSET); + return 0; + + case ONENAND_CMD_UNLOCK_ALL: + s3c_write_cmd(ONENAND_UNLOCK_ALL, cmd_map_10); + return 0; + + case ONENAND_CMD_ERASE: + s3c_write_cmd(ONENAND_ERASE_START, cmd_map_10); + return 0; + + default: + break; + } + + return 0; +} + +static unsigned char *s3c_get_bufferram(struct mtd_info *mtd, int area) +{ + struct onenand_chip *this = mtd->priv; + int index = ONENAND_CURRENT_BUFFERRAM(this); + unsigned char *p; + + if (area == ONENAND_DATARAM) { + p = onenand->page_buf; + if (index == 1) + p += this->writesize; + } else { + p = onenand->oob_buf; + if (index == 1) + p += mtd->oobsize; + } + + return p; +} + +static int onenand_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, + size_t count) +{ + unsigned char *p; + + p = s3c_get_bufferram(mtd, area); + memcpy(buffer, p + offset, count); + return 0; +} + +static int onenand_write_bufferram(struct mtd_info *mtd, int area, + const unsigned char *buffer, int offset, + size_t count) +{ + unsigned char *p; + + p = s3c_get_bufferram(mtd, area); + memcpy(p + offset, buffer, count); + return 0; +} + +static int (*s5pc110_dma_ops)(dma_addr_t dst, dma_addr_t src, size_t count, int direction); + +static int s5pc110_dma_poll(dma_addr_t dst, dma_addr_t src, size_t count, int direction) +{ + void __iomem *base = onenand->dma_addr; + int status; + unsigned long timeout; + + writel(src, base + S5PC110_DMA_SRC_ADDR); + writel(dst, base + S5PC110_DMA_DST_ADDR); + + if (direction == S5PC110_DMA_DIR_READ) { + writel(S5PC110_DMA_SRC_CFG_READ, base + S5PC110_DMA_SRC_CFG); + writel(S5PC110_DMA_DST_CFG_READ, base + S5PC110_DMA_DST_CFG); + } else { + writel(S5PC110_DMA_SRC_CFG_WRITE, base + S5PC110_DMA_SRC_CFG); + writel(S5PC110_DMA_DST_CFG_WRITE, base + S5PC110_DMA_DST_CFG); + } + + writel(count, base + S5PC110_DMA_TRANS_SIZE); + writel(direction, base + S5PC110_DMA_TRANS_DIR); + + writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD); + + /* + * There's no exact timeout values at Spec. + * In real case it takes under 1 msec. + * So 20 msecs are enough. + */ + timeout = jiffies + msecs_to_jiffies(20); + + do { + status = readl(base + S5PC110_DMA_TRANS_STATUS); + if (status & S5PC110_DMA_TRANS_STATUS_TE) { + writel(S5PC110_DMA_TRANS_CMD_TEC, + base + S5PC110_DMA_TRANS_CMD); + return -EIO; + } + } 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; +} + +static irqreturn_t s5pc110_onenand_irq(int irq, void *data) +{ + void __iomem *base = onenand->dma_addr; + int status, cmd = 0; + + status = readl(base + S5PC110_INTC_DMA_STATUS); + + if (likely(status & S5PC110_INTC_DMA_TD)) + cmd = S5PC110_DMA_TRANS_CMD_TDC; + + if (unlikely(status & S5PC110_INTC_DMA_TE)) + cmd = S5PC110_DMA_TRANS_CMD_TEC; + + writel(cmd, base + S5PC110_DMA_TRANS_CMD); + writel(status, base + S5PC110_INTC_DMA_CLR); + + if (!onenand->complete.done) + complete(&onenand->complete); + + return IRQ_HANDLED; +} + +static int s5pc110_dma_irq(dma_addr_t dst, dma_addr_t src, size_t count, int direction) +{ + void __iomem *base = onenand->dma_addr; + int status; + + status = readl(base + S5PC110_INTC_DMA_MASK); + if (status) { + status &= ~(S5PC110_INTC_DMA_TD | S5PC110_INTC_DMA_TE); + writel(status, base + S5PC110_INTC_DMA_MASK); + } + + writel(src, base + S5PC110_DMA_SRC_ADDR); + writel(dst, base + S5PC110_DMA_DST_ADDR); + + if (direction == S5PC110_DMA_DIR_READ) { + writel(S5PC110_DMA_SRC_CFG_READ, base + S5PC110_DMA_SRC_CFG); + writel(S5PC110_DMA_DST_CFG_READ, base + S5PC110_DMA_DST_CFG); + } else { + writel(S5PC110_DMA_SRC_CFG_WRITE, base + S5PC110_DMA_SRC_CFG); + writel(S5PC110_DMA_DST_CFG_WRITE, base + S5PC110_DMA_DST_CFG); + } + + writel(count, base + S5PC110_DMA_TRANS_SIZE); + writel(direction, base + S5PC110_DMA_TRANS_DIR); + + writel(S5PC110_DMA_TRANS_CMD_TR, base + S5PC110_DMA_TRANS_CMD); + + if (!wait_for_completion_timeout(&onenand->complete, + msecs_to_jiffies(20))) + return -ETIMEDOUT; + + return 0; +} + +static int s5pc110_read_bufferram(struct mtd_info *mtd, int area, + unsigned char *buffer, int offset, size_t count) +{ + struct onenand_chip *this = mtd->priv; + void __iomem *p; + void *buf = (void *) buffer; + dma_addr_t dma_src, dma_dst; + int err, ofs, page_dma = 0; + struct device *dev = &onenand->pdev->dev; + + p = this->base + area; + if (ONENAND_CURRENT_BUFFERRAM(this)) { + if (area == ONENAND_DATARAM) + p += this->writesize; + else + p += mtd->oobsize; + } + + if (offset & 3 || (size_t) buf & 3 || + !onenand->dma_addr || count != mtd->writesize) + goto normal; + + /* Handle vmalloc address */ + if (buf >= high_memory) { + struct page *page; + + if (((size_t) buf & PAGE_MASK) != + ((size_t) (buf + count - 1) & PAGE_MASK)) + goto normal; + page = vmalloc_to_page(buf); + if (!page) + goto normal; + + /* Page offset */ + ofs = ((size_t) buf & ~PAGE_MASK); + page_dma = 1; + + /* DMA routine */ + dma_src = onenand->phys_base + (p - this->base); + dma_dst = dma_map_page(dev, page, ofs, count, DMA_FROM_DEVICE); + } else { + /* DMA routine */ + dma_src = onenand->phys_base + (p - this->base); + dma_dst = dma_map_single(dev, buf, count, DMA_FROM_DEVICE); + } + if (dma_mapping_error(dev, dma_dst)) { + dev_err(dev, "Couldn't map a %zu byte buffer for DMA\n", count); + goto normal; + } + err = s5pc110_dma_ops(dma_dst, dma_src, + count, S5PC110_DMA_DIR_READ); + + if (page_dma) + dma_unmap_page(dev, dma_dst, count, DMA_FROM_DEVICE); + else + dma_unmap_single(dev, dma_dst, count, DMA_FROM_DEVICE); + + if (!err) + return 0; + +normal: + if (count != mtd->writesize) { + /* Copy the bufferram to memory to prevent unaligned access */ + memcpy_fromio(this->page_buf, p, mtd->writesize); + memcpy(buffer, this->page_buf + offset, count); + } else { + memcpy_fromio(buffer, p, count); + } + + return 0; +} + +static int s5pc110_chip_probe(struct mtd_info *mtd) +{ + /* Now just return 0 */ + return 0; +} + +static int s3c_onenand_bbt_wait(struct mtd_info *mtd, int state) +{ + unsigned int flags = INT_ACT | LOAD_CMP; + unsigned int stat; + unsigned long timeout; + + /* The 20 msec is enough */ + timeout = jiffies + msecs_to_jiffies(20); + while (time_before(jiffies, timeout)) { + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + if (stat & flags) + break; + } + /* To get correct interrupt status in timeout case */ + stat = s3c_read_reg(INT_ERR_STAT_OFFSET); + s3c_write_reg(stat, INT_ERR_ACK_OFFSET); + + if (stat & LD_FAIL_ECC_ERR) { + s3c_onenand_reset(); + return ONENAND_BBT_READ_ERROR; + } + + if (stat & LOAD_CMP) { + int ecc = s3c_read_reg(ECC_ERR_STAT_OFFSET); + if (ecc & ONENAND_ECC_4BIT_UNCORRECTABLE) { + s3c_onenand_reset(); + return ONENAND_BBT_READ_ERROR; + } + } + + return 0; +} + +static void s3c_onenand_check_lock_status(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + struct device *dev = &onenand->pdev->dev; + unsigned int block, end; + + end = this->chipsize >> this->erase_shift; + + for (block = 0; block < end; block++) { + unsigned int mem_addr = onenand->mem_addr(block, 0, 0); + s3c_read_cmd(CMD_MAP_01(onenand, mem_addr)); + + if (s3c_read_reg(INT_ERR_STAT_OFFSET) & LOCKED_BLK) { + dev_err(dev, "block %d is write-protected!\n", block); + s3c_write_reg(LOCKED_BLK, INT_ERR_ACK_OFFSET); + } + } +} + +static void s3c_onenand_do_lock_cmd(struct mtd_info *mtd, loff_t ofs, + size_t len, int cmd) +{ + struct onenand_chip *this = mtd->priv; + int start, end, start_mem_addr, end_mem_addr; + + start = ofs >> this->erase_shift; + start_mem_addr = onenand->mem_addr(start, 0, 0); + end = start + (len >> this->erase_shift) - 1; + end_mem_addr = onenand->mem_addr(end, 0, 0); + + if (cmd == ONENAND_CMD_LOCK) { + s3c_write_cmd(ONENAND_LOCK_START, CMD_MAP_10(onenand, + start_mem_addr)); + s3c_write_cmd(ONENAND_LOCK_END, CMD_MAP_10(onenand, + end_mem_addr)); + } else { + s3c_write_cmd(ONENAND_UNLOCK_START, CMD_MAP_10(onenand, + start_mem_addr)); + s3c_write_cmd(ONENAND_UNLOCK_END, CMD_MAP_10(onenand, + end_mem_addr)); + } + + this->wait(mtd, FL_LOCKING); +} + +static void s3c_unlock_all(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + loff_t ofs = 0; + size_t len = this->chipsize; + + if (this->options & ONENAND_HAS_UNLOCK_ALL) { + /* Write unlock command */ + this->command(mtd, ONENAND_CMD_UNLOCK_ALL, 0, 0); + + /* No need to check return value */ + this->wait(mtd, FL_LOCKING); + + /* Workaround for all block unlock in DDP */ + if (!ONENAND_IS_DDP(this)) { + s3c_onenand_check_lock_status(mtd); + return; + } + + /* All blocks on another chip */ + ofs = this->chipsize >> 1; + len = this->chipsize >> 1; + } + + s3c_onenand_do_lock_cmd(mtd, ofs, len, ONENAND_CMD_UNLOCK); + + s3c_onenand_check_lock_status(mtd); +} + +static void s3c_onenand_setup(struct mtd_info *mtd) +{ + struct onenand_chip *this = mtd->priv; + + onenand->mtd = mtd; + + if (onenand->type == TYPE_S3C6400) { + onenand->mem_addr = s3c6400_mem_addr; + onenand->cmd_map = s3c64xx_cmd_map; + } else if (onenand->type == TYPE_S3C6410) { + onenand->mem_addr = s3c6410_mem_addr; + onenand->cmd_map = s3c64xx_cmd_map; + } else if (onenand->type == TYPE_S5PC110) { + /* Use generic onenand functions */ + this->read_bufferram = s5pc110_read_bufferram; + this->chip_probe = s5pc110_chip_probe; + return; + } else { + BUG(); + } + + this->read_word = s3c_onenand_readw; + this->write_word = s3c_onenand_writew; + + this->wait = s3c_onenand_wait; + this->bbt_wait = s3c_onenand_bbt_wait; + this->unlock_all = s3c_unlock_all; + this->command = s3c_onenand_command; + + this->read_bufferram = onenand_read_bufferram; + this->write_bufferram = onenand_write_bufferram; +} + +static int s3c_onenand_probe(struct platform_device *pdev) +{ + struct onenand_platform_data *pdata; + struct onenand_chip *this; + struct mtd_info *mtd; + struct resource *r; + int size, err; + + pdata = dev_get_platdata(&pdev->dev); + /* No need to check pdata. the platform data is optional */ + + size = sizeof(struct mtd_info) + sizeof(struct onenand_chip); + mtd = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); + if (!mtd) + return -ENOMEM; + + onenand = devm_kzalloc(&pdev->dev, sizeof(struct s3c_onenand), + GFP_KERNEL); + if (!onenand) + return -ENOMEM; + + this = (struct onenand_chip *) &mtd[1]; + mtd->priv = this; + mtd->dev.parent = &pdev->dev; + onenand->pdev = pdev; + onenand->type = platform_get_device_id(pdev)->driver_data; + + s3c_onenand_setup(mtd); + + onenand->base = devm_platform_get_and_ioremap_resource(pdev, 0, &r); + if (IS_ERR(onenand->base)) + return PTR_ERR(onenand->base); + + onenand->phys_base = r->start; + + /* Set onenand_chip also */ + this->base = onenand->base; + + /* Use runtime badblock check */ + this->options |= ONENAND_SKIP_UNLOCK_CHECK; + + if (onenand->type != TYPE_S5PC110) { + onenand->ahb_addr = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(onenand->ahb_addr)) + return PTR_ERR(onenand->ahb_addr); + + /* Allocate 4KiB BufferRAM */ + onenand->page_buf = devm_kzalloc(&pdev->dev, SZ_4K, + GFP_KERNEL); + if (!onenand->page_buf) + return -ENOMEM; + + /* Allocate 128 SpareRAM */ + onenand->oob_buf = devm_kzalloc(&pdev->dev, 128, GFP_KERNEL); + if (!onenand->oob_buf) + return -ENOMEM; + + /* S3C doesn't handle subpage write */ + mtd->subpage_sft = 0; + this->subpagesize = mtd->writesize; + + } else { /* S5PC110 */ + onenand->dma_addr = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(onenand->dma_addr)) + return PTR_ERR(onenand->dma_addr); + + s5pc110_dma_ops = s5pc110_dma_poll; + /* Interrupt support */ + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (r) { + init_completion(&onenand->complete); + s5pc110_dma_ops = s5pc110_dma_irq; + err = devm_request_irq(&pdev->dev, r->start, + s5pc110_onenand_irq, + IRQF_SHARED, "onenand", + onenand); + if (err) { + dev_err(&pdev->dev, "failed to get irq\n"); + return err; + } + } + } + + err = onenand_scan(mtd, 1); + if (err) + return err; + + if (onenand->type != TYPE_S5PC110) { + /* S3C doesn't handle subpage write */ + mtd->subpage_sft = 0; + this->subpagesize = mtd->writesize; + } + + if (s3c_read_reg(MEM_CFG_OFFSET) & ONENAND_SYS_CFG1_SYNC_READ) + dev_info(&onenand->pdev->dev, "OneNAND Sync. Burst Read enabled\n"); + + err = mtd_device_register(mtd, pdata ? pdata->parts : NULL, + pdata ? pdata->nr_parts : 0); + if (err) { + dev_err(&pdev->dev, "failed to parse partitions and register the MTD device\n"); + onenand_release(mtd); + return err; + } + + platform_set_drvdata(pdev, mtd); + + return 0; +} + +static void s3c_onenand_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + + onenand_release(mtd); +} + +static int s3c_pm_ops_suspend(struct device *dev) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + struct onenand_chip *this = mtd->priv; + + this->wait(mtd, FL_PM_SUSPENDED); + return 0; +} + +static int s3c_pm_ops_resume(struct device *dev) +{ + struct mtd_info *mtd = dev_get_drvdata(dev); + struct onenand_chip *this = mtd->priv; + + this->unlock_all(mtd); + return 0; +} + +static const struct dev_pm_ops s3c_pm_ops = { + .suspend = s3c_pm_ops_suspend, + .resume = s3c_pm_ops_resume, +}; + +static const struct platform_device_id s3c_onenand_driver_ids[] = { + { + .name = "s3c6400-onenand", + .driver_data = TYPE_S3C6400, + }, { + .name = "s3c6410-onenand", + .driver_data = TYPE_S3C6410, + }, { + .name = "s5pc110-onenand", + .driver_data = TYPE_S5PC110, + }, { }, +}; +MODULE_DEVICE_TABLE(platform, s3c_onenand_driver_ids); + +static struct platform_driver s3c_onenand_driver = { + .driver = { + .name = "samsung-onenand", + .pm = &s3c_pm_ops, + }, + .id_table = s3c_onenand_driver_ids, + .probe = s3c_onenand_probe, + .remove = s3c_onenand_remove, +}; + +module_platform_driver(s3c_onenand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kyungmin Park "); +MODULE_DESCRIPTION("Samsung OneNAND controller support"); diff --git a/drivers/mtd/nand/onenand/samsung.h b/drivers/mtd/nand/onenand/samsung.h new file mode 100644 index 000000000..892bbb6ca --- /dev/null +++ b/drivers/mtd/nand/onenand/samsung.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2008-2010 Samsung Electronics + * Kyungmin Park + */ +#ifndef __SAMSUNG_ONENAND_H__ +#define __SAMSUNG_ONENAND_H__ + +/* + * OneNAND Controller + */ +#define MEM_CFG_OFFSET 0x0000 +#define BURST_LEN_OFFSET 0x0010 +#define MEM_RESET_OFFSET 0x0020 +#define INT_ERR_STAT_OFFSET 0x0030 +#define INT_ERR_MASK_OFFSET 0x0040 +#define INT_ERR_ACK_OFFSET 0x0050 +#define ECC_ERR_STAT_OFFSET 0x0060 +#define MANUFACT_ID_OFFSET 0x0070 +#define DEVICE_ID_OFFSET 0x0080 +#define DATA_BUF_SIZE_OFFSET 0x0090 +#define BOOT_BUF_SIZE_OFFSET 0x00A0 +#define BUF_AMOUNT_OFFSET 0x00B0 +#define TECH_OFFSET 0x00C0 +#define FBA_WIDTH_OFFSET 0x00D0 +#define FPA_WIDTH_OFFSET 0x00E0 +#define FSA_WIDTH_OFFSET 0x00F0 +#define TRANS_SPARE_OFFSET 0x0140 +#define DBS_DFS_WIDTH_OFFSET 0x0160 +#define INT_PIN_ENABLE_OFFSET 0x01A0 +#define ACC_CLOCK_OFFSET 0x01C0 +#define FLASH_VER_ID_OFFSET 0x01F0 +#define FLASH_AUX_CNTRL_OFFSET 0x0300 /* s3c64xx only */ + +#define ONENAND_MEM_RESET_HOT 0x3 +#define ONENAND_MEM_RESET_COLD 0x2 +#define ONENAND_MEM_RESET_WARM 0x1 + +#define CACHE_OP_ERR (1 << 13) +#define RST_CMP (1 << 12) +#define RDY_ACT (1 << 11) +#define INT_ACT (1 << 10) +#define UNSUP_CMD (1 << 9) +#define LOCKED_BLK (1 << 8) +#define BLK_RW_CMP (1 << 7) +#define ERS_CMP (1 << 6) +#define PGM_CMP (1 << 5) +#define LOAD_CMP (1 << 4) +#define ERS_FAIL (1 << 3) +#define PGM_FAIL (1 << 2) +#define INT_TO (1 << 1) +#define LD_FAIL_ECC_ERR (1 << 0) + +#define TSRF (1 << 0) + +#endif diff --git a/drivers/mtd/nand/qpic_common.c b/drivers/mtd/nand/qpic_common.c new file mode 100644 index 000000000..4f3e4dd76 --- /dev/null +++ b/drivers/mtd/nand/qpic_common.c @@ -0,0 +1,779 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Copyright (c) 2024 Qualcomm Innovation Center, Inc. All rights reserved + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * qcom_free_bam_transaction() - Frees the BAM transaction memory + * @nandc: qpic nand controller + * + * This function frees the bam transaction memory + */ +void qcom_free_bam_transaction(struct qcom_nand_controller *nandc) +{ + struct bam_transaction *bam_txn = nandc->bam_txn; + + kfree(bam_txn); +} +EXPORT_SYMBOL(qcom_free_bam_transaction); + +/** + * qcom_alloc_bam_transaction() - allocate BAM transaction + * @nandc: qpic nand controller + * + * This function will allocate and initialize the BAM transaction structure + */ +struct bam_transaction * +qcom_alloc_bam_transaction(struct qcom_nand_controller *nandc) +{ + struct bam_transaction *bam_txn; + size_t bam_txn_size; + unsigned int num_cw = nandc->max_cwperpage; + void *bam_txn_buf; + + bam_txn_size = + sizeof(*bam_txn) + num_cw * + ((sizeof(*bam_txn->bam_ce) * QPIC_PER_CW_CMD_ELEMENTS) + + (sizeof(*bam_txn->cmd_sgl) * QPIC_PER_CW_CMD_SGL) + + (sizeof(*bam_txn->data_sgl) * QPIC_PER_CW_DATA_SGL)); + + bam_txn_buf = kzalloc(bam_txn_size, GFP_KERNEL); + if (!bam_txn_buf) + return NULL; + + bam_txn = bam_txn_buf; + bam_txn_buf += sizeof(*bam_txn); + + bam_txn->bam_ce = bam_txn_buf; + 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->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); + + return bam_txn; +} +EXPORT_SYMBOL(qcom_alloc_bam_transaction); + +/** + * qcom_clear_bam_transaction() - Clears the BAM transaction + * @nandc: qpic nand controller + * + * This function will clear the BAM transaction indexes. + */ +void qcom_clear_bam_transaction(struct qcom_nand_controller *nandc) +{ + struct bam_transaction *bam_txn = nandc->bam_txn; + + if (!nandc->props->supports_bam) + return; + + memset(&bam_txn->bam_positions, 0, sizeof(bam_txn->bam_positions)); + bam_txn->last_data_desc = NULL; + + 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); +} +EXPORT_SYMBOL(qcom_clear_bam_transaction); + +/** + * qcom_qpic_bam_dma_done() - Callback for DMA descriptor completion + * @data: data pointer + * + * This function is a callback for DMA descriptor completion + */ +void qcom_qpic_bam_dma_done(void *data) +{ + struct bam_transaction *bam_txn = data; + + complete(&bam_txn->txn_done); +} +EXPORT_SYMBOL(qcom_qpic_bam_dma_done); + +/** + * qcom_nandc_dev_to_mem() - Check for dma sync for cpu or device + * @nandc: qpic nand controller + * @is_cpu: cpu or Device + * + * This function will check for dma sync for cpu or device + */ +inline void qcom_nandc_dev_to_mem(struct qcom_nand_controller *nandc, bool is_cpu) +{ + if (!nandc->props->supports_bam) + return; + + if (is_cpu) + dma_sync_single_for_cpu(nandc->dev, nandc->reg_read_dma, + MAX_REG_RD * + sizeof(*nandc->reg_read_buf), + DMA_FROM_DEVICE); + else + dma_sync_single_for_device(nandc->dev, nandc->reg_read_dma, + MAX_REG_RD * + sizeof(*nandc->reg_read_buf), + DMA_FROM_DEVICE); +} +EXPORT_SYMBOL(qcom_nandc_dev_to_mem); + +/** + * qcom_prepare_bam_async_desc() - Prepare DMA descriptor + * @nandc: qpic nand controller + * @chan: dma channel + * @flags: flags to control DMA descriptor preparation + * + * This function maps the scatter gather list for DMA transfer and forms the + * DMA descriptor for BAM.This descriptor will be added in the NAND DMA + * descriptor queue which will be submitted to DMA engine. + */ +int qcom_prepare_bam_async_desc(struct qcom_nand_controller *nandc, + struct dma_chan *chan, unsigned long flags) +{ + struct desc_info *desc; + struct scatterlist *sgl; + unsigned int sgl_cnt; + int ret; + struct bam_transaction *bam_txn = nandc->bam_txn; + enum dma_transfer_direction dir_eng; + struct dma_async_tx_descriptor *dma_desc; + + desc = kzalloc_obj(*desc); + if (!desc) + return -ENOMEM; + + if (chan == nandc->cmd_chan) { + sgl = &bam_txn->cmd_sgl[bam_txn->cmd_sgl_start]; + sgl_cnt = bam_txn->cmd_sgl_pos - bam_txn->cmd_sgl_start; + bam_txn->cmd_sgl_start = bam_txn->cmd_sgl_pos; + dir_eng = DMA_MEM_TO_DEV; + desc->dir = DMA_TO_DEVICE; + } else if (chan == nandc->tx_chan) { + sgl = &bam_txn->data_sgl[bam_txn->tx_sgl_start]; + sgl_cnt = bam_txn->tx_sgl_pos - bam_txn->tx_sgl_start; + bam_txn->tx_sgl_start = bam_txn->tx_sgl_pos; + dir_eng = DMA_MEM_TO_DEV; + desc->dir = DMA_TO_DEVICE; + } else { + sgl = &bam_txn->data_sgl[bam_txn->rx_sgl_start]; + sgl_cnt = bam_txn->rx_sgl_pos - bam_txn->rx_sgl_start; + bam_txn->rx_sgl_start = bam_txn->rx_sgl_pos; + dir_eng = DMA_DEV_TO_MEM; + desc->dir = DMA_FROM_DEVICE; + } + + sg_mark_end(sgl + sgl_cnt - 1); + ret = dma_map_sg(nandc->dev, sgl, sgl_cnt, desc->dir); + if (ret == 0) { + dev_err(nandc->dev, "failure in mapping desc\n"); + kfree(desc); + return -ENOMEM; + } + + desc->sgl_cnt = sgl_cnt; + desc->bam_sgl = sgl; + + dma_desc = dmaengine_prep_slave_sg(chan, sgl, sgl_cnt, dir_eng, + flags); + + if (!dma_desc) { + dev_err(nandc->dev, "failure in prep desc\n"); + dma_unmap_sg(nandc->dev, sgl, sgl_cnt, desc->dir); + kfree(desc); + return -EINVAL; + } + + desc->dma_desc = dma_desc; + + /* update last data/command descriptor */ + if (chan == nandc->cmd_chan) + bam_txn->last_cmd_desc = dma_desc; + else + bam_txn->last_data_desc = dma_desc; + + list_add_tail(&desc->node, &nandc->desc_list); + + return 0; +} +EXPORT_SYMBOL(qcom_prepare_bam_async_desc); + +/** + * qcom_prep_bam_dma_desc_cmd() - Prepares the command descriptor for BAM DMA + * @nandc: qpic nand controller + * @read: read or write type + * @reg_off: offset within the controller's data buffer + * @vaddr: virtual address of the buffer we want to write to + * @size: DMA transaction size in bytes + * @flags: flags to control DMA descriptor preparation + * + * This function will prepares the command descriptor for BAM DMA + * which will be used for NAND register reads and writes. + */ +int qcom_prep_bam_dma_desc_cmd(struct qcom_nand_controller *nandc, bool read, + int reg_off, const void *vaddr, + int size, unsigned int flags) +{ + int bam_ce_size; + 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], + offset, BAM_READ_COMMAND, + reg_buf_dma_addr(nandc, + (__le32 *)vaddr + i)); + else + bam_prep_ce_le32(&bam_ce_buffer[i], + offset, BAM_WRITE_COMMAND, + *((__le32 *)vaddr + i)); + } + + bam_txn->bam_ce_pos += size; + + /* 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) * + sizeof(struct bam_cmd_element); + sg_set_buf(&bam_txn->cmd_sgl[bam_txn->cmd_sgl_pos], + bam_ce_buffer, bam_ce_size); + bam_txn->cmd_sgl_pos++; + bam_txn->bam_ce_start = bam_txn->bam_ce_pos; + + if (flags & NAND_BAM_NWD) { + ret = qcom_prepare_bam_async_desc(nandc, nandc->cmd_chan, + DMA_PREP_FENCE | DMA_PREP_CMD); + if (ret) + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL(qcom_prep_bam_dma_desc_cmd); + +/** + * qcom_prep_bam_dma_desc_data() - Prepares the data descriptor for BAM DMA + * @nandc: qpic nand controller + * @read: read or write type + * @vaddr: virtual address of the buffer we want to write to + * @size: DMA transaction size in bytes + * @flags: flags to control DMA descriptor preparation + * + * This function will prepares the data descriptor for BAM DMA which + * will be used for NAND data reads and writes. + */ +int qcom_prep_bam_dma_desc_data(struct qcom_nand_controller *nandc, bool read, + const void *vaddr, int size, unsigned int flags) +{ + int ret; + 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++; + + /* + * BAM will only set EOT for DMA_PREP_INTERRUPT so if this flag + * is not set, form the DMA descriptor + */ + if (!(flags & NAND_BAM_NO_EOT)) { + ret = qcom_prepare_bam_async_desc(nandc, nandc->tx_chan, + DMA_PREP_INTERRUPT); + if (ret) + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL(qcom_prep_bam_dma_desc_data); + +/** + * qcom_prep_adm_dma_desc() - Prepare descriptor for adma + * @nandc: qpic nand controller + * @read: read or write type + * @reg_off: offset within the controller's data buffer + * @vaddr: virtual address of the buffer we want to write to + * @size: adm dma transaction size in bytes + * @flow_control: flow controller + * + * This function will prepare descriptor for adma + */ +int qcom_prep_adm_dma_desc(struct qcom_nand_controller *nandc, bool read, + int reg_off, const void *vaddr, int size, + bool flow_control) +{ + struct qcom_adm_peripheral_config periph_conf = {}; + struct dma_async_tx_descriptor *dma_desc; + struct dma_slave_config slave_conf = {0}; + enum dma_transfer_direction dir_eng; + struct desc_info *desc; + struct scatterlist *sgl; + int ret; + + desc = kzalloc_obj(*desc); + if (!desc) + return -ENOMEM; + + sgl = &desc->adm_sgl; + + sg_init_one(sgl, vaddr, size); + + if (read) { + dir_eng = DMA_DEV_TO_MEM; + desc->dir = DMA_FROM_DEVICE; + } else { + dir_eng = DMA_MEM_TO_DEV; + desc->dir = DMA_TO_DEVICE; + } + + ret = dma_map_sg(nandc->dev, sgl, 1, desc->dir); + if (!ret) { + ret = -ENOMEM; + goto err; + } + + slave_conf.device_fc = flow_control; + if (read) { + slave_conf.src_maxburst = 16; + slave_conf.src_addr = nandc->base_dma + reg_off; + if (nandc->data_crci) { + periph_conf.crci = nandc->data_crci; + slave_conf.peripheral_config = &periph_conf; + slave_conf.peripheral_size = sizeof(periph_conf); + } + } else { + slave_conf.dst_maxburst = 16; + slave_conf.dst_addr = nandc->base_dma + reg_off; + if (nandc->cmd_crci) { + periph_conf.crci = nandc->cmd_crci; + slave_conf.peripheral_config = &periph_conf; + slave_conf.peripheral_size = sizeof(periph_conf); + } + } + + ret = dmaengine_slave_config(nandc->chan, &slave_conf); + if (ret) { + dev_err(nandc->dev, "failed to configure dma channel\n"); + goto err; + } + + dma_desc = dmaengine_prep_slave_sg(nandc->chan, sgl, 1, dir_eng, 0); + if (!dma_desc) { + dev_err(nandc->dev, "failed to prepare desc\n"); + ret = -EINVAL; + goto err; + } + + desc->dma_desc = dma_desc; + + list_add_tail(&desc->node, &nandc->desc_list); + + return 0; +err: + kfree(desc); + + return ret; +} +EXPORT_SYMBOL(qcom_prep_adm_dma_desc); + +/** + * qcom_read_reg_dma() - read a given number of registers to the reg_read_buf pointer + * @nandc: qpic nand controller + * @first: offset of the first register in the contiguous block + * @num_regs: number of registers to read + * @flags: flags to control DMA descriptor preparation + * + * This function will prepares a descriptor to read a given number of + * contiguous registers to the reg_read_buf pointer. + */ +int qcom_read_reg_dma(struct qcom_nand_controller *nandc, int first, + int num_regs, unsigned int flags) +{ + bool flow_control = false; + void *vaddr; + + vaddr = nandc->reg_read_buf + nandc->reg_read_pos; + nandc->reg_read_pos += num_regs; + + if (first == NAND_DEV_CMD_VLD || first == NAND_DEV_CMD1) + first = dev_cmd_reg_addr(nandc, first); + + if (nandc->props->supports_bam) + return qcom_prep_bam_dma_desc_cmd(nandc, true, first, vaddr, + num_regs, flags); + + if (first == NAND_READ_ID || first == NAND_FLASH_STATUS) + flow_control = true; + + return qcom_prep_adm_dma_desc(nandc, true, first, vaddr, + num_regs * sizeof(u32), flow_control); +} +EXPORT_SYMBOL(qcom_read_reg_dma); + +/** + * qcom_write_reg_dma() - write a given number of registers + * @nandc: qpic nand controller + * @vaddr: contiguous memory from where register value will + * be written + * @first: offset of the first register in the contiguous block + * @num_regs: number of registers to write + * @flags: flags to control DMA descriptor preparation + * + * This function will prepares a descriptor to write a given number of + * contiguous registers + */ +int qcom_write_reg_dma(struct qcom_nand_controller *nandc, __le32 *vaddr, + int first, int num_regs, unsigned int flags) +{ + bool flow_control = false; + + if (first == NAND_EXEC_CMD) + flags |= NAND_BAM_NWD; + + if (first == NAND_DEV_CMD1_RESTORE || first == NAND_DEV_CMD1) + first = dev_cmd_reg_addr(nandc, NAND_DEV_CMD1); + + if (first == NAND_DEV_CMD_VLD_RESTORE || first == NAND_DEV_CMD_VLD) + first = dev_cmd_reg_addr(nandc, NAND_DEV_CMD_VLD); + + if (nandc->props->supports_bam) + return qcom_prep_bam_dma_desc_cmd(nandc, false, first, vaddr, + num_regs, flags); + + if (first == NAND_FLASH_CMD) + flow_control = true; + + return qcom_prep_adm_dma_desc(nandc, false, first, vaddr, + num_regs * sizeof(u32), flow_control); +} +EXPORT_SYMBOL(qcom_write_reg_dma); + +/** + * qcom_read_data_dma() - transfer data + * @nandc: qpic nand controller + * @reg_off: offset within the controller's data buffer + * @vaddr: virtual address of the buffer we want to write to + * @size: DMA transaction size in bytes + * @flags: flags to control DMA descriptor preparation + * + * This function will prepares a DMA descriptor to transfer data from the + * controller's internal buffer to the buffer 'vaddr' + */ +int qcom_read_data_dma(struct qcom_nand_controller *nandc, int reg_off, + const u8 *vaddr, int size, unsigned int flags) +{ + if (nandc->props->supports_bam) + return qcom_prep_bam_dma_desc_data(nandc, true, vaddr, size, flags); + + return qcom_prep_adm_dma_desc(nandc, true, reg_off, vaddr, size, false); +} +EXPORT_SYMBOL(qcom_read_data_dma); + +/** + * qcom_write_data_dma() - transfer data + * @nandc: qpic nand controller + * @reg_off: offset within the controller's data buffer + * @vaddr: virtual address of the buffer we want to read from + * @size: DMA transaction size in bytes + * @flags: flags to control DMA descriptor preparation + * + * This function will prepares a DMA descriptor to transfer data from + * 'vaddr' to the controller's internal buffer + */ +int qcom_write_data_dma(struct qcom_nand_controller *nandc, int reg_off, + const u8 *vaddr, int size, unsigned int flags) +{ + if (nandc->props->supports_bam) + return qcom_prep_bam_dma_desc_data(nandc, false, vaddr, size, flags); + + return qcom_prep_adm_dma_desc(nandc, false, reg_off, vaddr, size, false); +} +EXPORT_SYMBOL(qcom_write_data_dma); + +/** + * qcom_submit_descs() - submit dma descriptor + * @nandc: qpic nand controller + * + * This function will submit all the prepared dma descriptor + * cmd or data descriptor + */ +int qcom_submit_descs(struct qcom_nand_controller *nandc) +{ + struct desc_info *desc, *n; + dma_cookie_t cookie = 0; + struct bam_transaction *bam_txn = nandc->bam_txn; + int ret = 0; + + if (nandc->props->supports_bam) { + if (bam_txn->rx_sgl_pos > bam_txn->rx_sgl_start) { + ret = qcom_prepare_bam_async_desc(nandc, nandc->rx_chan, 0); + if (ret) + goto err_unmap_free_desc; + } + + if (bam_txn->tx_sgl_pos > bam_txn->tx_sgl_start) { + ret = qcom_prepare_bam_async_desc(nandc, nandc->tx_chan, + DMA_PREP_INTERRUPT); + if (ret) + goto err_unmap_free_desc; + } + + if (bam_txn->cmd_sgl_pos > bam_txn->cmd_sgl_start) { + ret = qcom_prepare_bam_async_desc(nandc, nandc->cmd_chan, + DMA_PREP_CMD); + if (ret) + goto err_unmap_free_desc; + } + } + + list_for_each_entry(desc, &nandc->desc_list, node) + cookie = dmaengine_submit(desc->dma_desc); + + if (nandc->props->supports_bam) { + bam_txn->last_cmd_desc->callback = qcom_qpic_bam_dma_done; + bam_txn->last_cmd_desc->callback_param = bam_txn; + + dma_async_issue_pending(nandc->tx_chan); + dma_async_issue_pending(nandc->rx_chan); + dma_async_issue_pending(nandc->cmd_chan); + + if (!wait_for_completion_timeout(&bam_txn->txn_done, + QPIC_NAND_COMPLETION_TIMEOUT)) + ret = -ETIMEDOUT; + } else { + if (dma_sync_wait(nandc->chan, cookie) != DMA_COMPLETE) + ret = -ETIMEDOUT; + } + +err_unmap_free_desc: + /* + * Unmap the dma sg_list and free the desc allocated by both + * qcom_prepare_bam_async_desc() and qcom_prep_adm_dma_desc() functions. + */ + list_for_each_entry_safe(desc, n, &nandc->desc_list, node) { + list_del(&desc->node); + + if (nandc->props->supports_bam) + dma_unmap_sg(nandc->dev, desc->bam_sgl, + desc->sgl_cnt, desc->dir); + else + dma_unmap_sg(nandc->dev, &desc->adm_sgl, 1, + desc->dir); + + kfree(desc); + } + + return ret; +} +EXPORT_SYMBOL(qcom_submit_descs); + +/** + * qcom_clear_read_regs() - reset the read register buffer + * @nandc: qpic nand controller + * + * This function reset the register read buffer for next NAND operation + */ +void qcom_clear_read_regs(struct qcom_nand_controller *nandc) +{ + nandc->reg_read_pos = 0; + qcom_nandc_dev_to_mem(nandc, false); +} +EXPORT_SYMBOL(qcom_clear_read_regs); + +/** + * qcom_nandc_unalloc() - unallocate qpic nand controller + * @nandc: qpic nand controller + * + * This function will unallocate memory alloacted for qpic nand controller + */ +void qcom_nandc_unalloc(struct qcom_nand_controller *nandc) +{ + if (nandc->props->supports_bam) { + if (!dma_mapping_error(nandc->dev, nandc->reg_read_dma)) + dma_unmap_single(nandc->dev, nandc->reg_read_dma, + MAX_REG_RD * + sizeof(*nandc->reg_read_buf), + DMA_FROM_DEVICE); + + if (nandc->tx_chan) + dma_release_channel(nandc->tx_chan); + + if (nandc->rx_chan) + dma_release_channel(nandc->rx_chan); + + if (nandc->cmd_chan) + dma_release_channel(nandc->cmd_chan); + } else { + if (nandc->chan) + dma_release_channel(nandc->chan); + } +} +EXPORT_SYMBOL(qcom_nandc_unalloc); + +/** + * qcom_nandc_alloc() - Allocate qpic nand controller + * @nandc: qpic nand controller + * + * This function will allocate memory for qpic nand controller + */ +int qcom_nandc_alloc(struct qcom_nand_controller *nandc) +{ + int ret; + + ret = dma_set_coherent_mask(nandc->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(nandc->dev, "failed to set DMA mask\n"); + return ret; + } + + /* + * we use the internal buffer for reading ONFI params, reading small + * data like ID and status, and preforming read-copy-write operations + * when writing to a codeword partially. 532 is the maximum possible + * size of a codeword for our nand controller + */ + nandc->buf_size = 532; + + nandc->data_buffer = devm_kzalloc(nandc->dev, nandc->buf_size, GFP_KERNEL); + if (!nandc->data_buffer) + return -ENOMEM; + + nandc->regs = devm_kzalloc(nandc->dev, sizeof(*nandc->regs), GFP_KERNEL); + if (!nandc->regs) + return -ENOMEM; + + nandc->reg_read_buf = devm_kcalloc(nandc->dev, MAX_REG_RD, + sizeof(*nandc->reg_read_buf), + GFP_KERNEL); + if (!nandc->reg_read_buf) + return -ENOMEM; + + if (nandc->props->supports_bam) { + nandc->reg_read_dma = + dma_map_single(nandc->dev, nandc->reg_read_buf, + MAX_REG_RD * + sizeof(*nandc->reg_read_buf), + DMA_FROM_DEVICE); + if (dma_mapping_error(nandc->dev, nandc->reg_read_dma)) { + dev_err(nandc->dev, "failed to DMA MAP reg buffer\n"); + return -EIO; + } + + nandc->tx_chan = dma_request_chan(nandc->dev, "tx"); + if (IS_ERR(nandc->tx_chan)) { + ret = PTR_ERR(nandc->tx_chan); + nandc->tx_chan = NULL; + dev_err_probe(nandc->dev, ret, + "tx DMA channel request failed\n"); + goto unalloc; + } + + nandc->rx_chan = dma_request_chan(nandc->dev, "rx"); + if (IS_ERR(nandc->rx_chan)) { + ret = PTR_ERR(nandc->rx_chan); + nandc->rx_chan = NULL; + dev_err_probe(nandc->dev, ret, + "rx DMA channel request failed\n"); + goto unalloc; + } + + nandc->cmd_chan = dma_request_chan(nandc->dev, "cmd"); + if (IS_ERR(nandc->cmd_chan)) { + ret = PTR_ERR(nandc->cmd_chan); + nandc->cmd_chan = NULL; + dev_err_probe(nandc->dev, ret, + "cmd DMA channel request failed\n"); + goto unalloc; + } + + /* + * Initially allocate BAM transaction to read ONFI param page. + * After detecting all the devices, this BAM transaction will + * be freed and the next BAM transaction will be allocated with + * maximum codeword size + */ + nandc->max_cwperpage = 1; + nandc->bam_txn = qcom_alloc_bam_transaction(nandc); + if (!nandc->bam_txn) { + dev_err(nandc->dev, + "failed to allocate bam transaction\n"); + ret = -ENOMEM; + goto unalloc; + } + } else { + nandc->chan = dma_request_chan(nandc->dev, "rxtx"); + if (IS_ERR(nandc->chan)) { + ret = PTR_ERR(nandc->chan); + nandc->chan = NULL; + dev_err_probe(nandc->dev, ret, + "rxtx DMA channel request failed\n"); + return ret; + } + } + + INIT_LIST_HEAD(&nandc->desc_list); + INIT_LIST_HEAD(&nandc->host_list); + + return 0; +unalloc: + qcom_nandc_unalloc(nandc); + return ret; +} +EXPORT_SYMBOL(qcom_nandc_alloc); + +MODULE_DESCRIPTION("QPIC controller common api"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig new file mode 100644 index 000000000..1f4053e53 --- /dev/null +++ b/drivers/mtd/nand/raw/Kconfig @@ -0,0 +1,541 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig MTD_RAW_NAND + tristate "Raw/Parallel NAND Device Support" + select MTD_NAND_CORE + select MTD_NAND_ECC + help + This enables support for accessing all type of raw/parallel + NAND flash devices. For further information see + . + +if MTD_RAW_NAND + +comment "Raw/parallel NAND flash controllers" + +config MTD_NAND_DENALI + tristate + +config MTD_NAND_DENALI_PCI + tristate "Denali NAND controller on Intel Moorestown" + select MTD_NAND_DENALI + depends on PCI + help + Enable the driver for NAND flash on Intel Moorestown, using the + Denali NAND controller core. + +config MTD_NAND_DENALI_DT + tristate "Denali NAND controller as a DT device" + select MTD_NAND_DENALI + depends on HAS_DMA && HAVE_CLK && OF && HAS_IOMEM + help + Enable the driver for NAND flash on platforms using a Denali NAND + controller as a DT device. + +config MTD_NAND_AMS_DELTA + tristate "Amstrad E3 NAND controller" + depends on MACH_AMS_DELTA || COMPILE_TEST + default MACH_AMS_DELTA + help + Support for NAND flash on Amstrad E3 (Delta). + +config MTD_NAND_OMAP2 + tristate "OMAP2, OMAP3, OMAP4 and Keystone NAND controller" + depends on ARCH_OMAP2PLUS || ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST + depends on HAS_IOMEM + depends on OMAP_GPMC + help + Support for NAND flash on Texas Instruments OMAP2, OMAP3, OMAP4 + and Keystone platforms. + +config MTD_NAND_OMAP_BCH + depends on MTD_NAND_OMAP2 + bool "Support hardware based BCH error correction" + default n + select BCH + help + This config enables the ELM hardware engine, which can be used to + locate and correct errors when using BCH ECC scheme. This offloads + the cpu from doing ECC error searching and correction. However some + legacy OMAP families like OMAP2xxx, OMAP3xxx do not have ELM engine + so this is optional for them. + +config MTD_NAND_OMAP_BCH_BUILD + def_tristate MTD_NAND_OMAP2 && MTD_NAND_OMAP_BCH + +config MTD_NAND_AU1550 + tristate "Au1550/1200 NAND support" + depends on MIPS_ALCHEMY + help + This enables the driver for the NAND flash controller on the + AMD/Alchemy 1550 SOC. + +config MTD_NAND_NDFC + tristate "IBM/MCC 4xx NAND controller" + 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_SHARPSL + tristate "Sharp SL Series (C7xx + others) NAND controller" + depends on ARCH_PXA || COMPILE_TEST + depends on HAS_IOMEM + +config MTD_NAND_CAFE + tristate "OLPC CAFÉ NAND controller" + depends on PCI + select REED_SOLOMON + select REED_SOLOMON_DEC16 + help + Use NAND flash attached to the CAFÉ chip designed for the OLPC + laptop. + +config MTD_NAND_CS553X + tristate "CS5535/CS5536 (AMD Geode companion) NAND controller" + depends on X86_32 + depends on !UML && HAS_IOMEM + help + The CS553x companion chips for the AMD Geode processor + include NAND flash controllers with built-in hardware ECC + capabilities; enabling this option will allow you to use + these. The driver will check the MSRs to verify that the + controller is enabled for NAND, and currently requires that + the controller be in MMIO mode. + + If you say "m", the module will be called cs553x_nand. + +config MTD_NAND_ATMEL + tristate "Atmel AT91 NAND Flash/SmartMedia NAND controller" + depends on ARCH_AT91 || COMPILE_TEST + depends on HAS_IOMEM + select GENERIC_ALLOCATOR + select MFD_ATMEL_SMC + help + Enables support for NAND Flash / Smart Media Card interface + on Atmel AT91 processors. + +config MTD_NAND_ORION + tristate "Marvell Orion NAND controller" + depends on PLAT_ORION + help + This enables the NAND flash controller on Orion machines. + + No board specific support is done by this driver, each board + must advertise a platform_device for the driver to attach. + +config MTD_NAND_MARVELL + tristate "Marvell EBU NAND controller" + depends on PXA3xx || ARCH_MMP || PLAT_ORION || ARCH_MVEBU || \ + COMPILE_TEST + depends on HAS_IOMEM + help + This enables the NAND flash controller driver for Marvell boards, + including: + - PXA3xx processors (NFCv1) + - 32-bit Armada platforms (XP, 37x, 38x, 39x) (NFCv2) + - 64-bit Aramda platforms (7k, 8k, ac5) (NFCv2) + +config MTD_NAND_SLC_LPC32XX + tristate "NXP LPC32xx SLC NAND controller" + depends on ARCH_LPC32XX || COMPILE_TEST + depends on HAS_IOMEM + help + Enables support for NXP's LPC32XX SLC (i.e. for Single Level Cell + chips) NAND controller. This is the default for the PHYTEC 3250 + reference board which contains a NAND256R3A2CZA6 chip. + + Please check the actual NAND chip connected and its support + by the SLC NAND controller. + +config MTD_NAND_MLC_LPC32XX + tristate "NXP LPC32xx MLC NAND controller" + depends on ARCH_LPC32XX || COMPILE_TEST + depends on HAS_IOMEM + help + Uses the LPC32XX MLC (i.e. for Multi Level Cell chips) NAND + controller. This is the default for the WORK92105 controller + board. + + Please check the actual NAND chip connected and its support + by the MLC NAND controller. + +config MTD_NAND_PASEMI + tristate "PA Semi PWRficient NAND controller" + depends on PPC_PASEMI + help + Enables support for NAND Flash interface on PA Semi PWRficient + based boards + +source "drivers/mtd/nand/raw/brcmnand/Kconfig" + +config MTD_NAND_BCM47XXNFLASH + tristate "BCM4706 BCMA NAND controller" + depends on BCMA_NFLASH + depends on BCMA + help + BCMA bus can have various flash memories attached, they are + registered by bcma as platform devices. This enables driver for + NAND flash memories. For now only BCM4706 is supported. + +config MTD_NAND_MPC5121_NFC + tristate "MPC5121 NAND controller" + depends on PPC_MPC512x + help + This enables the driver for the NAND flash controller on the + MPC5121 SoC. + +config MTD_NAND_GPMI_NAND + tristate "Freescale GPMI NAND controller" + depends on MXS_DMA + help + Enables NAND Flash support for IMX23, IMX28 or IMX6. + The GPMI controller is very powerful, with the help of BCH + module, it can do the hardware ECC. The GPMI supports several + NAND flashs at the same time. + +config MTD_NAND_FSL_ELBC + tristate "Freescale eLBC NAND controller" + depends on FSL_SOC + select FSL_LBC + help + Various Freescale chips, including the 8313, include a NAND Flash + Controller Module with built-in hardware ECC capabilities. + Enabling this option will enable you to use this to control + external NAND devices. + +config MTD_NAND_FSL_IFC + tristate "Freescale IFC NAND controller" + depends on FSL_SOC || ARCH_LAYERSCAPE || SOC_LS1021A || COMPILE_TEST + depends on HAS_IOMEM + depends on FSL_IFC + help + Various Freescale chips e.g P1010, include a NAND Flash machine + with built-in hardware ECC capabilities. + Enabling this option will enable you to use this to control + external NAND devices. + +config MTD_NAND_FSL_UPM + tristate "Freescale UPM NAND controller" + depends on PPC_83xx || PPC_85xx + select FSL_LBC + help + Enables support for NAND Flash chips wired onto Freescale PowerPC + processor localbus with User-Programmable Machine support. + +config MTD_NAND_VF610_NFC + tristate "Freescale VF610/MPC5125 NAND controller" + depends on (SOC_VF610 || COMPILE_TEST) + depends on HAS_IOMEM + help + Enables support for NAND Flash Controller on some Freescale + processors like the VF610, MPC5125, MCF54418 or Kinetis K70. + The driver supports a maximum 2k page size. With 2k pages and + 64 bytes or more of OOB, hardware ECC with up to 32-bit error + correction is supported. Hardware ECC is only enabled through + device tree. + +config MTD_NAND_MXC + tristate "Freescale MXC NAND controller" + depends on ARCH_MXC || COMPILE_TEST + depends on HAS_IOMEM && OF + help + This enables the driver for the NAND flash controller on the + MXC processors. + +config MTD_NAND_SH_FLCTL + tristate "Renesas SuperH FLCTL NAND controller" + depends on SUPERH || COMPILE_TEST + depends on HAS_IOMEM + help + Several Renesas SuperH CPU has FLCTL. This option enables support + for NAND Flash using FLCTL. + +config MTD_NAND_DAVINCI + tristate "DaVinci/Keystone NAND controller" + depends on COMPILE_TEST || ARCH_DAVINCI || ARCH_KEYSTONE + depends on HAS_IOMEM && TI_AEMIF + help + Enable the driver for NAND flash chips on Texas Instruments + DaVinci/Keystone processors. + +config MTD_NAND_TXX9NDFMC + tristate "TXx9 NAND controller" + depends on SOC_TX4938 || COMPILE_TEST + depends on HAS_IOMEM + help + This enables the NAND flash controller on the TXx9 SoCs. + +config MTD_NAND_SOCRATES + tristate "Socrates NAND controller" + depends on SOCRATES + help + Enables support for NAND Flash chips wired onto Socrates board. + +source "drivers/mtd/nand/raw/ingenic/Kconfig" + +config MTD_NAND_FSMC + tristate "ST Micros FSMC NAND controller" + depends on OF && HAS_IOMEM + depends on PLAT_SPEAR || ARCH_NOMADIK || ARCH_U8500 || COMPILE_TEST + help + Enables support for NAND Flash chips on the ST Microelectronics + Flexible Static Memory Controller (FSMC) + +config MTD_NAND_XWAY + bool "Lantiq XWAY NAND controller" + depends on LANTIQ && SOC_TYPE_XWAY + help + Enables support for NAND Flash chips on Lantiq XWAY SoCs. NAND is attached + to the External Bus Unit (EBU). + +config MTD_NAND_SUNXI + tristate "Allwinner NAND controller" + depends on ARCH_SUNXI || COMPILE_TEST + depends on HAS_IOMEM + help + Enables support for NAND Flash chips on Allwinner SoCs. + +config MTD_NAND_HISI504 + tristate "Hisilicon Hip04 NAND controller" + depends on ARCH_HISI || COMPILE_TEST + depends on HAS_IOMEM + help + Enables support for NAND controller on Hisilicon SoC Hip04. + +config MTD_NAND_QCOM + tristate "Qualcomm NAND controller" + depends on ARCH_QCOM || COMPILE_TEST + depends on HAS_IOMEM + help + Enables support for NAND flash chips on SoCs containing the EBI2 NAND + controller. This controller is found on IPQ806x SoC. + +config MTD_NAND_MTK + tristate "MTK NAND controller" + depends on MTD_NAND_ECC_MEDIATEK + depends on ARCH_MEDIATEK || COMPILE_TEST + depends on HAS_IOMEM + help + Enables support for NAND controller on MTK SoCs. + This controller is found on mt27xx, mt81xx, mt65xx SoCs. + +config MTD_NAND_MXIC + tristate "Macronix raw NAND controller" + depends on HAS_IOMEM || COMPILE_TEST + help + This selects the Macronix raw NAND controller driver. + +config MTD_NAND_TEGRA + tristate "NVIDIA Tegra NAND controller" + depends on ARCH_TEGRA || COMPILE_TEST + depends on HAS_IOMEM + help + Enables support for NAND flash controller on NVIDIA Tegra SoC. + The driver has been developed and tested on a Tegra 2 SoC. DMA + support, raw read/write page as well as HW ECC read/write page + is supported. Extra OOB bytes when using HW ECC are currently + not supported. + +config MTD_NAND_STM32_FMC2 + tristate "Support for NAND controller on STM32MP SoCs" + depends on ARCH_STM32 || COMPILE_TEST + select MFD_SYSCON + help + Enables support for NAND Flash chips on SoCs containing the FMC2 + NAND controller. This controller is found on STM32MP SoCs. + The controller supports a maximum 8k page size and supports + a maximum 8-bit correction error per sector of 512 bytes. + +config MTD_NAND_MESON + tristate "Support for NAND controller on Amlogic's Meson SoCs" + depends on COMMON_CLK && (ARCH_MESON || COMPILE_TEST) + select MFD_SYSCON + help + Enables support for NAND controller on Amlogic's Meson SoCs. + This controller is found on Meson SoCs. + +config MTD_NAND_GPIO + tristate "GPIO assisted NAND controller" + depends on GPIOLIB || COMPILE_TEST + depends on HAS_IOMEM + help + This enables a NAND flash driver where control signals are + connected to GPIO pins, and commands and data are communicated + via a memory mapped interface. + +config MTD_NAND_PLATFORM + tristate "Generic NAND controller" + depends on HAS_IOMEM + help + This implements a generic NAND driver for on-SOC platform + devices. You will need to provide platform-specific functions + via platform_data. + +config MTD_NAND_CADENCE + tristate "Support Cadence NAND (HPNFC) controller" + depends on OF && HAS_IOMEM + help + Enable the driver for NAND flash on platforms using a Cadence NAND + controller. + +config MTD_NAND_ARASAN + tristate "Support for Arasan NAND flash controller" + depends on HAS_IOMEM && HAS_DMA + select BCH + help + Enables the driver for the Arasan NAND flash controller on + Zynq Ultrascale+ MPSoC. + +config MTD_NAND_INTEL_LGM + tristate "Support for NAND controller on Intel LGM SoC" + depends on OF + depends on HAS_IOMEM + help + Enables support for NAND Flash chips on Intel's LGM SoC. + NAND flash controller interfaced through the External Bus Unit. + +config MTD_NAND_ROCKCHIP + tristate "Rockchip NAND controller" + depends on ARCH_ROCKCHIP && HAS_IOMEM + help + Enables support for NAND controller on Rockchip SoCs. + There are four different versions of NAND FLASH Controllers, + including: + NFC v600: RK2928, RK3066, RK3188 + NFC v622: RK3036, RK3128 + NFC v800: RK3308, RV1108 + NFC v900: PX30, RK3326 + +config MTD_NAND_PL35X + tristate "ARM PL35X NAND controller" + depends on OF + depends on PL353_SMC + help + Enables support for PrimeCell SMC PL351 and PL353 NAND + controller found on Zynq7000. + +config MTD_NAND_RENESAS + tristate "Renesas R-Car Gen3 & RZ/N1 NAND controller" + depends on ARCH_RENESAS || COMPILE_TEST + help + Enables support for the NAND controller found on Renesas R-Car + Gen3 and RZ/N1 SoC families. + +config MTD_NAND_TS72XX + tristate "ts72xx NAND controller" + depends on ARCH_EP93XX && HAS_IOMEM + help + Enables support for NAND controller on ts72xx SBCs. + +config MTD_NAND_NUVOTON_MA35 + tristate "Nuvoton MA35 SoC NAND controller" + depends on ARCH_MA35 || COMPILE_TEST + depends on OF + help + 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 + tristate + default n + +config MTD_NAND_NANDSIM + tristate "Support for NAND Flash Simulator" + help + The simulator may simulate various NAND flash chips for the + MTD nand layer. + +config MTD_NAND_RICOH + tristate "Ricoh xD card reader" + default n + depends on PCI + select MTD_SM_COMMON + help + Enable support for Ricoh R5C852 xD card reader + You also need to enable either + NAND SSFDC (SmartMedia) read only translation layer' or new + experimental, readwrite + 'SmartMedia/xD new translation layer' + +config MTD_NAND_DISKONCHIP + tristate "DiskOnChip 2000, Millennium and Millennium Plus (NAND reimplementation)" + depends on HAS_IOMEM + select REED_SOLOMON + select REED_SOLOMON_DEC16 + help + This is a reimplementation of M-Systems DiskOnChip 2000, + Millennium and Millennium Plus as a standard NAND device driver, + as opposed to the earlier self-contained MTD device drivers. + This should enable, among other things, proper JFFS2 operation on + these devices. + +config MTD_NAND_DISKONCHIP_PROBE_ADVANCED + bool "Advanced detection options for DiskOnChip" + depends on MTD_NAND_DISKONCHIP + help + This option allows you to specify nonstandard address at which to + probe for a DiskOnChip, or to change the detection options. You + are unlikely to need any of this unless you are using LinuxBIOS. + Say 'N'. + +config MTD_NAND_DISKONCHIP_PROBE_ADDRESS + hex "Physical address of DiskOnChip" if MTD_NAND_DISKONCHIP_PROBE_ADVANCED + depends on MTD_NAND_DISKONCHIP + default "0" + help + By default, the probe for DiskOnChip devices will look for a + DiskOnChip at every multiple of 0x2000 between 0xC8000 and 0xEE000. + This option allows you to specify a single address at which to probe + for the device, which is useful if you have other devices in that + range which get upset when they are probed. + + (Note that on PowerPC, the normal probe will only check at + 0xE4000000.) + + Normally, you should leave this set to zero, to allow the probe at + the normal addresses. + +config MTD_NAND_DISKONCHIP_PROBE_HIGH + bool "Probe high addresses" + depends on MTD_NAND_DISKONCHIP_PROBE_ADVANCED + help + By default, the probe for DiskOnChip devices will look for a + DiskOnChip at every multiple of 0x2000 between 0xC8000 and 0xEE000. + This option changes to make it probe between 0xFFFC8000 and + 0xFFFEE000. Unless you are using LinuxBIOS, this is unlikely to be + useful to you. Say 'N'. + +config MTD_NAND_DISKONCHIP_BBTWRITE + bool "Allow BBT writes on DiskOnChip Millennium and 2000TSOP" + depends on MTD_NAND_DISKONCHIP + help + On DiskOnChip devices shipped with the INFTL filesystem (Millennium + and 2000 TSOP/Alon), Linux reserves some space at the end of the + device for the Bad Block Table (BBT). If you have existing INFTL + data on your device (created by non-Linux tools such as M-Systems' + DOS drivers), your data might overlap the area Linux wants to use for + the BBT. If this is a concern for you, leave this option disabled and + Linux will not write BBT data into this area. + The downside of leaving this option disabled is that if bad blocks + are detected by Linux, they will not be recorded in the BBT, which + could cause future problems. + Once you enable this option, new filesystems (INFTL or others, created + in Linux or other operating systems) will not use the reserved area. + The only reason not to enable this option is to prevent damage to + preexisting filesystems. + Even if you leave this disabled, you can enable BBT writes at module + load time (assuming you build diskonchip as a module) with the module + parameter "inftl_bbt_write=1". + +endif # MTD_RAW_NAND diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile new file mode 100644 index 000000000..619760138 --- /dev/null +++ b/drivers/mtd/nand/raw/Makefile @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_MTD_RAW_NAND) += nand.o +obj-$(CONFIG_MTD_SM_COMMON) += sm_common.o + +obj-$(CONFIG_MTD_NAND_CAFE) += cafe_nand.o +obj-$(CONFIG_MTD_NAND_AMS_DELTA) += ams-delta.o +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_DAVINCI) += davinci_nand.o +obj-$(CONFIG_MTD_NAND_DISKONCHIP) += diskonchip.o +obj-$(CONFIG_MTD_NAND_FSMC) += fsmc_nand.o +obj-$(CONFIG_MTD_NAND_SHARPSL) += sharpsl.o +obj-$(CONFIG_MTD_NAND_NANDSIM) += nandsim.o +obj-$(CONFIG_MTD_NAND_CS553X) += cs553x_nand.o +obj-$(CONFIG_MTD_NAND_NDFC) += ndfc.o +obj-$(CONFIG_MTD_NAND_ATMEL) += atmel/ +obj-$(CONFIG_MTD_NAND_GPIO) += gpio.o +omap2_nand-objs := omap2.o +obj-$(CONFIG_MTD_NAND_OMAP2) += omap2_nand.o +obj-$(CONFIG_MTD_NAND_OMAP_BCH_BUILD) += omap_elm.o +obj-$(CONFIG_MTD_NAND_MARVELL) += marvell_nand.o +obj-$(CONFIG_MTD_NAND_PLATFORM) += plat_nand.o +obj-$(CONFIG_MTD_NAND_PASEMI) += pasemi_nand.o +obj-$(CONFIG_MTD_NAND_ORION) += orion_nand.o +obj-$(CONFIG_MTD_NAND_FSL_ELBC) += fsl_elbc_nand.o +obj-$(CONFIG_MTD_NAND_FSL_IFC) += fsl_ifc_nand.o +obj-$(CONFIG_MTD_NAND_FSL_UPM) += fsl_upm.o +obj-$(CONFIG_MTD_NAND_SLC_LPC32XX) += lpc32xx_slc.o +obj-$(CONFIG_MTD_NAND_MLC_LPC32XX) += lpc32xx_mlc.o +obj-$(CONFIG_MTD_NAND_SH_FLCTL) += sh_flctl.o +obj-$(CONFIG_MTD_NAND_MXC) += mxc_nand.o +obj-$(CONFIG_MTD_NAND_SOCRATES) += socrates_nand.o +obj-$(CONFIG_MTD_NAND_TS72XX) += technologic-nand-controller.o +obj-$(CONFIG_MTD_NAND_TXX9NDFMC) += txx9ndfmc.o +obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mpc5121_nfc.o +obj-$(CONFIG_MTD_NAND_VF610_NFC) += vf610_nfc.o +obj-$(CONFIG_MTD_NAND_RICOH) += r852.o +obj-y += ingenic/ +obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand/ +obj-$(CONFIG_MTD_NAND_XWAY) += xway_nand.o +obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash/ +obj-$(CONFIG_MTD_NAND_SUNXI) += sunxi_nand.o +obj-$(CONFIG_MTD_NAND_HISI504) += hisi504_nand.o +obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand/ +obj-$(CONFIG_MTD_NAND_QCOM) += qcom_nandc.o +obj-$(CONFIG_MTD_NAND_MTK) += mtk_nand.o +obj-$(CONFIG_MTD_NAND_MXIC) += mxic_nand.o +obj-$(CONFIG_MTD_NAND_TEGRA) += tegra_nand.o +obj-$(CONFIG_MTD_NAND_STM32_FMC2) += stm32_fmc2_nand.o +obj-$(CONFIG_MTD_NAND_MESON) += meson_nand.o +obj-$(CONFIG_MTD_NAND_CADENCE) += cadence-nand-controller.o +obj-$(CONFIG_MTD_NAND_ARASAN) += arasan-nand-controller.o +obj-$(CONFIG_MTD_NAND_INTEL_LGM) += intel-nand-controller.o +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 +nand-objs += nand_jedec.o +nand-objs += nand_amd.o +nand-objs += nand_esmt.o +nand-objs += nand_hynix.o +nand-objs += nand_macronix.o +nand-objs += nand_micron.o +nand-objs += nand_sandisk.o +nand-objs += nand_samsung.o +nand-objs += nand_toshiba.o diff --git a/drivers/mtd/nand/raw/ams-delta.c b/drivers/mtd/nand/raw/ams-delta.c new file mode 100644 index 000000000..fb2b7db70 --- /dev/null +++ b/drivers/mtd/nand/raw/ams-delta.c @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2006 Jonathan McDowell + * + * Derived from drivers/mtd/nand/toto.c (removed in v2.6.28) + * Copyright (c) 2003 Texas Instruments + * Copyright (c) 2002 Thomas Gleixner + * + * Converted to platform driver by Janusz Krzysztofik + * Partially stolen from plat_nand.c + * + * Overview: + * This is a device driver for the NAND flash device found on the + * Amstrad E3 (Delta). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * MTD structure for E3 (Delta) + */ +struct gpio_nand { + struct nand_controller base; + struct nand_chip nand_chip; + struct gpio_desc *gpiod_rdy; + struct gpio_desc *gpiod_nce; + struct gpio_desc *gpiod_nre; + struct gpio_desc *gpiod_nwp; + struct gpio_desc *gpiod_nwe; + struct gpio_desc *gpiod_ale; + struct gpio_desc *gpiod_cle; + struct gpio_descs *data_gpiods; + bool data_in; + unsigned int tRP; + unsigned int tWP; + u8 (*io_read)(struct gpio_nand *this); + void (*io_write)(struct gpio_nand *this, u8 byte); +}; + +static void gpio_nand_write_commit(struct gpio_nand *priv) +{ + gpiod_set_value(priv->gpiod_nwe, 1); + ndelay(priv->tWP); + gpiod_set_value(priv->gpiod_nwe, 0); +} + +static void gpio_nand_io_write(struct gpio_nand *priv, u8 byte) +{ + struct gpio_descs *data_gpiods = priv->data_gpiods; + DECLARE_BITMAP(values, BITS_PER_TYPE(byte)) = { byte, }; + + gpiod_set_raw_array_value(data_gpiods->ndescs, data_gpiods->desc, + data_gpiods->info, values); + + gpio_nand_write_commit(priv); +} + +static void gpio_nand_dir_output(struct gpio_nand *priv, u8 byte) +{ + struct gpio_descs *data_gpiods = priv->data_gpiods; + DECLARE_BITMAP(values, BITS_PER_TYPE(byte)) = { byte, }; + int i; + + for (i = 0; i < data_gpiods->ndescs; i++) + gpiod_direction_output_raw(data_gpiods->desc[i], + test_bit(i, values)); + + gpio_nand_write_commit(priv); + + priv->data_in = false; +} + +static u8 gpio_nand_io_read(struct gpio_nand *priv) +{ + u8 res; + struct gpio_descs *data_gpiods = priv->data_gpiods; + DECLARE_BITMAP(values, BITS_PER_TYPE(res)) = { 0, }; + + gpiod_set_value(priv->gpiod_nre, 1); + ndelay(priv->tRP); + + gpiod_get_raw_array_value(data_gpiods->ndescs, data_gpiods->desc, + data_gpiods->info, values); + + gpiod_set_value(priv->gpiod_nre, 0); + + res = values[0]; + return res; +} + +static void gpio_nand_dir_input(struct gpio_nand *priv) +{ + struct gpio_descs *data_gpiods = priv->data_gpiods; + int i; + + for (i = 0; i < data_gpiods->ndescs; i++) + gpiod_direction_input(data_gpiods->desc[i]); + + priv->data_in = true; +} + +static void gpio_nand_write_buf(struct gpio_nand *priv, const u8 *buf, int len) +{ + int i = 0; + + if (len > 0 && priv->data_in) + gpio_nand_dir_output(priv, buf[i++]); + + while (i < len) + priv->io_write(priv, buf[i++]); +} + +static void gpio_nand_read_buf(struct gpio_nand *priv, u8 *buf, int len) +{ + int i; + + if (priv->data_gpiods && !priv->data_in) + gpio_nand_dir_input(priv); + + for (i = 0; i < len; i++) + buf[i] = priv->io_read(priv); +} + +static void gpio_nand_ctrl_cs(struct gpio_nand *priv, bool assert) +{ + gpiod_set_value(priv->gpiod_nce, assert); +} + +static int gpio_nand_exec_op(struct nand_chip *this, + const struct nand_operation *op, bool check_only) +{ + struct gpio_nand *priv = nand_get_controller_data(this); + const struct nand_op_instr *instr; + int ret = 0; + + if (check_only) + return 0; + + gpio_nand_ctrl_cs(priv, 1); + + for (instr = op->instrs; instr < op->instrs + op->ninstrs; instr++) { + switch (instr->type) { + case NAND_OP_CMD_INSTR: + gpiod_set_value(priv->gpiod_cle, 1); + gpio_nand_write_buf(priv, &instr->ctx.cmd.opcode, 1); + gpiod_set_value(priv->gpiod_cle, 0); + break; + + case NAND_OP_ADDR_INSTR: + gpiod_set_value(priv->gpiod_ale, 1); + gpio_nand_write_buf(priv, instr->ctx.addr.addrs, + instr->ctx.addr.naddrs); + gpiod_set_value(priv->gpiod_ale, 0); + break; + + case NAND_OP_DATA_IN_INSTR: + gpio_nand_read_buf(priv, instr->ctx.data.buf.in, + instr->ctx.data.len); + break; + + case NAND_OP_DATA_OUT_INSTR: + gpio_nand_write_buf(priv, instr->ctx.data.buf.out, + instr->ctx.data.len); + break; + + case NAND_OP_WAITRDY_INSTR: + ret = priv->gpiod_rdy ? + nand_gpio_waitrdy(this, priv->gpiod_rdy, + instr->ctx.waitrdy.timeout_ms) : + nand_soft_waitrdy(this, + instr->ctx.waitrdy.timeout_ms); + break; + } + + if (ret) + break; + } + + gpio_nand_ctrl_cs(priv, 0); + + return ret; +} + +static int gpio_nand_setup_interface(struct nand_chip *this, int csline, + const struct nand_interface_config *cf) +{ + struct gpio_nand *priv = nand_get_controller_data(this); + const struct nand_sdr_timings *sdr = nand_get_sdr_timings(cf); + struct device *dev = &nand_to_mtd(this)->dev; + + if (IS_ERR(sdr)) + return PTR_ERR(sdr); + + if (csline == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + if (priv->gpiod_nre) { + priv->tRP = DIV_ROUND_UP(sdr->tRP_min, 1000); + dev_dbg(dev, "using %u ns read pulse width\n", priv->tRP); + } + + priv->tWP = DIV_ROUND_UP(sdr->tWP_min, 1000); + dev_dbg(dev, "using %u ns write pulse width\n", priv->tWP); + + return 0; +} + +static int gpio_nand_attach_chip(struct nand_chip *chip) +{ + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT && + chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + + return 0; +} + +static const struct nand_controller_ops gpio_nand_ops = { + .exec_op = gpio_nand_exec_op, + .attach_chip = gpio_nand_attach_chip, + .setup_interface = gpio_nand_setup_interface, +}; + +/* + * Main initialization routine + */ +static int gpio_nand_probe(struct platform_device *pdev) +{ + struct gpio_nand_platdata *pdata = dev_get_platdata(&pdev->dev); + const struct mtd_partition *partitions = NULL; + int num_partitions = 0; + struct gpio_nand *priv; + struct nand_chip *this; + struct mtd_info *mtd; + int (*probe)(struct platform_device *pdev, struct gpio_nand *priv); + int err = 0; + + if (pdata) { + partitions = pdata->parts; + num_partitions = pdata->num_parts; + } + + /* Allocate memory for MTD device structure and private data */ + priv = devm_kzalloc(&pdev->dev, sizeof(struct gpio_nand), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + this = &priv->nand_chip; + + mtd = nand_to_mtd(this); + mtd->dev.parent = &pdev->dev; + + nand_set_controller_data(this, priv); + nand_set_flash_node(this, pdev->dev.of_node); + + priv->gpiod_rdy = devm_gpiod_get_optional(&pdev->dev, "rdy", GPIOD_IN); + if (IS_ERR(priv->gpiod_rdy)) { + err = PTR_ERR(priv->gpiod_rdy); + dev_warn(&pdev->dev, "RDY GPIO request failed (%d)\n", err); + return err; + } + + platform_set_drvdata(pdev, priv); + + /* Set chip enabled but write protected */ + priv->gpiod_nwp = devm_gpiod_get_optional(&pdev->dev, "nwp", + GPIOD_OUT_HIGH); + if (IS_ERR(priv->gpiod_nwp)) { + err = PTR_ERR(priv->gpiod_nwp); + dev_err(&pdev->dev, "NWP GPIO request failed (%d)\n", err); + return err; + } + + priv->gpiod_nce = devm_gpiod_get_optional(&pdev->dev, "nce", + GPIOD_OUT_LOW); + if (IS_ERR(priv->gpiod_nce)) { + err = PTR_ERR(priv->gpiod_nce); + dev_err(&pdev->dev, "NCE GPIO request failed (%d)\n", err); + return err; + } + + priv->gpiod_nre = devm_gpiod_get_optional(&pdev->dev, "nre", + GPIOD_OUT_LOW); + if (IS_ERR(priv->gpiod_nre)) { + err = PTR_ERR(priv->gpiod_nre); + dev_err(&pdev->dev, "NRE GPIO request failed (%d)\n", err); + return err; + } + + priv->gpiod_nwe = devm_gpiod_get_optional(&pdev->dev, "nwe", + GPIOD_OUT_LOW); + if (IS_ERR(priv->gpiod_nwe)) { + err = PTR_ERR(priv->gpiod_nwe); + dev_err(&pdev->dev, "NWE GPIO request failed (%d)\n", err); + return err; + } + + priv->gpiod_ale = devm_gpiod_get(&pdev->dev, "ale", GPIOD_OUT_LOW); + if (IS_ERR(priv->gpiod_ale)) { + err = PTR_ERR(priv->gpiod_ale); + dev_err(&pdev->dev, "ALE GPIO request failed (%d)\n", err); + return err; + } + + priv->gpiod_cle = devm_gpiod_get(&pdev->dev, "cle", GPIOD_OUT_LOW); + if (IS_ERR(priv->gpiod_cle)) { + err = PTR_ERR(priv->gpiod_cle); + dev_err(&pdev->dev, "CLE GPIO request failed (%d)\n", err); + return err; + } + + /* Request array of data pins, initialize them as input */ + priv->data_gpiods = devm_gpiod_get_array_optional(&pdev->dev, "data", + GPIOD_IN); + if (IS_ERR(priv->data_gpiods)) { + err = PTR_ERR(priv->data_gpiods); + dev_err(&pdev->dev, "data GPIO request failed: %d\n", err); + return err; + } + if (priv->data_gpiods) { + if (!priv->gpiod_nwe) { + dev_err(&pdev->dev, + "mandatory NWE pin not provided by platform\n"); + return -ENODEV; + } + + priv->io_read = gpio_nand_io_read; + priv->io_write = gpio_nand_io_write; + priv->data_in = true; + } + + if (pdev->id_entry) + probe = (void *) pdev->id_entry->driver_data; + else + probe = of_device_get_match_data(&pdev->dev); + if (probe) + err = probe(pdev, priv); + if (err) + return err; + + if (!priv->io_read || !priv->io_write) { + dev_err(&pdev->dev, "incomplete device configuration\n"); + return -ENODEV; + } + + /* Initialize the NAND controller object embedded in gpio_nand. */ + priv->base.ops = &gpio_nand_ops; + nand_controller_init(&priv->base); + this->controller = &priv->base; + + /* + * FIXME: We should release write protection only after nand_scan() to + * be on the safe side but we can't do that until we have a generic way + * to assert/deassert WP from the core. Even if the core shouldn't + * write things in the nand_scan() path, it should have control on this + * pin just in case we ever need to disable write protection during + * chip detection/initialization. + */ + /* Release write protection */ + gpiod_set_value(priv->gpiod_nwp, 0); + + /* + * This driver assumes that the default ECC engine should be TYPE_SOFT. + * Set ->engine_type before registering the NAND devices in order to + * provide a driver specific default value. + */ + this->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + + /* Scan to find existence of the device */ + err = nand_scan(this, 1); + if (err) + return err; + + /* Register the partitions */ + err = mtd_device_register(mtd, partitions, num_partitions); + if (err) + goto err_nand_cleanup; + + return 0; + +err_nand_cleanup: + nand_cleanup(this); + + return err; +} + +/* + * Clean up routine + */ +static void gpio_nand_remove(struct platform_device *pdev) +{ + struct gpio_nand *priv = platform_get_drvdata(pdev); + struct mtd_info *mtd = nand_to_mtd(&priv->nand_chip); + int ret; + + /* Apply write protection */ + gpiod_set_value(priv->gpiod_nwp, 1); + + /* Unregister device */ + ret = mtd_device_unregister(mtd); + WARN_ON(ret); + nand_cleanup(mtd_to_nand(mtd)); +} + +#ifdef CONFIG_OF +static const struct of_device_id gpio_nand_of_id_table[] = { + { + /* sentinel */ + }, +}; +MODULE_DEVICE_TABLE(of, gpio_nand_of_id_table); +#endif + +static const struct platform_device_id gpio_nand_plat_id_table[] = { + { + .name = "ams-delta-nand", + }, { + /* sentinel */ + }, +}; +MODULE_DEVICE_TABLE(platform, gpio_nand_plat_id_table); + +static struct platform_driver gpio_nand_driver = { + .probe = gpio_nand_probe, + .remove = gpio_nand_remove, + .id_table = gpio_nand_plat_id_table, + .driver = { + .name = "ams-delta-nand", + .of_match_table = of_match_ptr(gpio_nand_of_id_table), + }, +}; + +module_platform_driver(gpio_nand_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Jonathan McDowell "); +MODULE_DESCRIPTION("Glue layer for NAND flash on Amstrad E3 (Delta)"); diff --git a/drivers/mtd/nand/raw/arasan-nand-controller.c b/drivers/mtd/nand/raw/arasan-nand-controller.c new file mode 100644 index 000000000..865754737 --- /dev/null +++ b/drivers/mtd/nand/raw/arasan-nand-controller.c @@ -0,0 +1,1518 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Arasan NAND Flash Controller Driver + * + * Copyright (C) 2014 - 2020 Xilinx, Inc. + * Author: + * Miquel Raynal + * Original work (fully rewritten): + * Punnaiah Choudary Kalluri + * Naga Sureshkumar Relli + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PKT_REG 0x00 +#define PKT_SIZE(x) FIELD_PREP(GENMASK(10, 0), (x)) +#define PKT_STEPS(x) FIELD_PREP(GENMASK(23, 12), (x)) + +#define MEM_ADDR1_REG 0x04 + +#define MEM_ADDR2_REG 0x08 +#define ADDR2_STRENGTH(x) FIELD_PREP(GENMASK(27, 25), (x)) +#define ADDR2_CS(x) FIELD_PREP(GENMASK(31, 30), (x)) + +#define CMD_REG 0x0C +#define CMD_1(x) FIELD_PREP(GENMASK(7, 0), (x)) +#define CMD_2(x) FIELD_PREP(GENMASK(15, 8), (x)) +#define CMD_PAGE_SIZE(x) FIELD_PREP(GENMASK(25, 23), (x)) +#define CMD_DMA_ENABLE BIT(27) +#define CMD_NADDRS(x) FIELD_PREP(GENMASK(30, 28), (x)) +#define CMD_ECC_ENABLE BIT(31) + +#define PROG_REG 0x10 +#define PROG_PGRD BIT(0) +#define PROG_ERASE BIT(2) +#define PROG_STATUS BIT(3) +#define PROG_PGPROG BIT(4) +#define PROG_RDID BIT(6) +#define PROG_RDPARAM BIT(7) +#define PROG_RST BIT(8) +#define PROG_GET_FEATURE BIT(9) +#define PROG_SET_FEATURE BIT(10) +#define PROG_CHG_RD_COL_ENH BIT(14) + +#define INTR_STS_EN_REG 0x14 +#define INTR_SIG_EN_REG 0x18 +#define INTR_STS_REG 0x1C +#define WRITE_READY BIT(0) +#define READ_READY BIT(1) +#define XFER_COMPLETE BIT(2) +#define DMA_BOUNDARY BIT(6) +#define EVENT_MASK GENMASK(7, 0) + +#define READY_STS_REG 0x20 + +#define DMA_ADDR0_REG 0x50 +#define DMA_ADDR1_REG 0x24 + +#define FLASH_STS_REG 0x28 + +#define TIMING_REG 0x2C +#define TCCS_TIME_500NS 0 +#define TCCS_TIME_300NS 3 +#define TCCS_TIME_200NS 2 +#define TCCS_TIME_100NS 1 +#define FAST_TCAD BIT(2) +#define DQS_BUFF_SEL_IN(x) FIELD_PREP(GENMASK(6, 3), (x)) +#define DQS_BUFF_SEL_OUT(x) FIELD_PREP(GENMASK(18, 15), (x)) + +#define DATA_PORT_REG 0x30 + +#define ECC_CONF_REG 0x34 +#define ECC_CONF_COL(x) FIELD_PREP(GENMASK(15, 0), (x)) +#define ECC_CONF_LEN(x) FIELD_PREP(GENMASK(26, 16), (x)) +#define ECC_CONF_BCH_EN BIT(27) + +#define ECC_ERR_CNT_REG 0x38 +#define GET_PKT_ERR_CNT(x) FIELD_GET(GENMASK(7, 0), (x)) +#define GET_PAGE_ERR_CNT(x) FIELD_GET(GENMASK(16, 8), (x)) + +#define ECC_SP_REG 0x3C +#define ECC_SP_CMD1(x) FIELD_PREP(GENMASK(7, 0), (x)) +#define ECC_SP_CMD2(x) FIELD_PREP(GENMASK(15, 8), (x)) +#define ECC_SP_ADDRS(x) FIELD_PREP(GENMASK(30, 28), (x)) + +#define ECC_1ERR_CNT_REG 0x40 +#define ECC_2ERR_CNT_REG 0x44 + +#define DATA_INTERFACE_REG 0x6C +#define DIFACE_SDR_MODE(x) FIELD_PREP(GENMASK(2, 0), (x)) +#define DIFACE_DDR_MODE(x) FIELD_PREP(GENMASK(5, 3), (x)) +#define DIFACE_SDR 0 +#define DIFACE_NVDDR BIT(9) + +#define ANFC_MAX_CS 2 +#define ANFC_DFLT_TIMEOUT_US 1000000 +#define ANFC_MAX_CHUNK_SIZE SZ_1M +#define ANFC_MAX_PARAM_SIZE SZ_4K +#define ANFC_MAX_STEPS SZ_2K +#define ANFC_MAX_PKT_SIZE (SZ_2K - 1) +#define ANFC_MAX_ADDR_CYC 5U +#define ANFC_RSVD_ECC_BYTES 21 + +#define ANFC_XLNX_SDR_DFLT_CORE_CLK 100000000 +#define ANFC_XLNX_SDR_HS_CORE_CLK 80000000 + +static struct gpio_desc *anfc_default_cs_array[2] = {NULL, NULL}; + +/** + * struct anfc_op - Defines how to execute an operation + * @pkt_reg: Packet register + * @addr1_reg: Memory address 1 register + * @addr2_reg: Memory address 2 register + * @cmd_reg: Command register + * @prog_reg: Program register + * @steps: Number of "packets" to read/write + * @rdy_timeout_ms: Timeout for waits on Ready/Busy pin + * @len: Data transfer length + * @read: Data transfer direction from the controller point of view + * @buf: Data buffer + */ +struct anfc_op { + u32 pkt_reg; + u32 addr1_reg; + u32 addr2_reg; + u32 cmd_reg; + u32 prog_reg; + int steps; + unsigned int rdy_timeout_ms; + unsigned int len; + bool read; + u8 *buf; +}; + +/** + * struct anand - Defines the NAND chip related information + * @node: Used to store NAND chips into a list + * @chip: NAND chip information structure + * @rb: Ready-busy line + * @page_sz: Register value of the page_sz field to use + * @clk: Expected clock frequency to use + * @data_iface: Data interface timing mode to use + * @timings: NV-DDR specific timings to use + * @ecc_conf: Hardware ECC configuration value + * @strength: Register value of the ECC strength + * @raddr_cycles: Row address cycle information + * @caddr_cycles: Column address cycle information + * @ecc_bits: Exact number of ECC bits per syndrome + * @ecc_total: Total number of ECC bytes + * @errloc: Array of errors located with soft BCH + * @hw_ecc: Buffer to store syndromes computed by hardware + * @bch: BCH structure + * @cs_idx: Array of chip-select for this device, values are indexes + * of the controller structure @gpio_cs array + * @ncs_idx: Size of the @cs_idx array + */ +struct anand { + struct list_head node; + struct nand_chip chip; + unsigned int rb; + unsigned int page_sz; + unsigned long clk; + u32 data_iface; + u32 timings; + u32 ecc_conf; + u32 strength; + u16 raddr_cycles; + u16 caddr_cycles; + unsigned int ecc_bits; + unsigned int ecc_total; + unsigned int *errloc; + u8 *hw_ecc; + struct bch_control *bch; + int *cs_idx; + int ncs_idx; +}; + +/** + * struct arasan_nfc - Defines the Arasan NAND flash controller driver instance + * @dev: Pointer to the device structure + * @base: Remapped register area + * @controller_clk: Pointer to the system clock + * @bus_clk: Pointer to the flash clock + * @controller: Base controller structure + * @chips: List of all NAND chips attached to the controller + * @cur_clk: Current clock rate + * @cs_array: CS array. Native CS are left empty, the other cells are + * populated with their corresponding GPIO descriptor. + * @ncs: Size of @cs_array + * @cur_cs: Index in @cs_array of the currently in use CS + * @native_cs: Currently selected native CS + * @spare_cs: Native CS that is not wired (may be selected when a GPIO + * CS is in use) + */ +struct arasan_nfc { + struct device *dev; + void __iomem *base; + struct clk *controller_clk; + struct clk *bus_clk; + struct nand_controller controller; + struct list_head chips; + unsigned int cur_clk; + struct gpio_desc **cs_array; + unsigned int ncs; + int cur_cs; + unsigned int native_cs; + unsigned int spare_cs; +}; + +static struct anand *to_anand(struct nand_chip *nand) +{ + return container_of(nand, struct anand, chip); +} + +static struct arasan_nfc *to_anfc(struct nand_controller *ctrl) +{ + return container_of(ctrl, struct arasan_nfc, controller); +} + +static int anfc_wait_for_event(struct arasan_nfc *nfc, unsigned int event) +{ + u32 val; + int ret; + + ret = readl_relaxed_poll_timeout(nfc->base + INTR_STS_REG, val, + val & event, 0, + ANFC_DFLT_TIMEOUT_US); + if (ret) { + dev_err(nfc->dev, "Timeout waiting for event 0x%x\n", event); + return -ETIMEDOUT; + } + + writel_relaxed(event, nfc->base + INTR_STS_REG); + + return 0; +} + +static int anfc_wait_for_rb(struct arasan_nfc *nfc, struct nand_chip *chip, + unsigned int timeout_ms) +{ + struct anand *anand = to_anand(chip); + u32 val; + int ret; + + /* There is no R/B interrupt, we must poll a register */ + ret = readl_relaxed_poll_timeout(nfc->base + READY_STS_REG, val, + val & BIT(anand->rb), + 1, timeout_ms * 1000); + if (ret) { + dev_err(nfc->dev, "Timeout waiting for R/B 0x%x\n", + readl_relaxed(nfc->base + READY_STS_REG)); + return -ETIMEDOUT; + } + + return 0; +} + +static void anfc_trigger_op(struct arasan_nfc *nfc, struct anfc_op *nfc_op) +{ + writel_relaxed(nfc_op->pkt_reg, nfc->base + PKT_REG); + writel_relaxed(nfc_op->addr1_reg, nfc->base + MEM_ADDR1_REG); + writel_relaxed(nfc_op->addr2_reg, nfc->base + MEM_ADDR2_REG); + writel_relaxed(nfc_op->cmd_reg, nfc->base + CMD_REG); + writel_relaxed(nfc_op->prog_reg, nfc->base + PROG_REG); +} + +static int anfc_pkt_len_config(unsigned int len, unsigned int *steps, + unsigned int *pktsize) +{ + unsigned int nb, sz; + + for (nb = 1; nb < ANFC_MAX_STEPS; nb *= 2) { + sz = len / nb; + if (sz <= ANFC_MAX_PKT_SIZE) + break; + } + + if (sz * nb != len) + return -ENOTSUPP; + + if (steps) + *steps = nb; + + if (pktsize) + *pktsize = sz; + + return 0; +} + +static bool anfc_is_gpio_cs(struct arasan_nfc *nfc, int nfc_cs) +{ + return nfc_cs >= 0 && nfc->cs_array[nfc_cs]; +} + +static int anfc_relative_to_absolute_cs(struct anand *anand, int num) +{ + return anand->cs_idx[num]; +} + +static void anfc_assert_cs(struct arasan_nfc *nfc, unsigned int nfc_cs_idx) +{ + /* CS did not change: do nothing */ + if (nfc->cur_cs == nfc_cs_idx) + return; + + /* Deassert the previous CS if it was a GPIO */ + if (anfc_is_gpio_cs(nfc, nfc->cur_cs)) + gpiod_set_value_cansleep(nfc->cs_array[nfc->cur_cs], 1); + + /* Assert the new one */ + if (anfc_is_gpio_cs(nfc, nfc_cs_idx)) { + nfc->native_cs = nfc->spare_cs; + gpiod_set_value_cansleep(nfc->cs_array[nfc_cs_idx], 0); + } else { + nfc->native_cs = nfc_cs_idx; + } + + nfc->cur_cs = nfc_cs_idx; +} + +static int anfc_select_target(struct nand_chip *chip, int target) +{ + struct anand *anand = to_anand(chip); + struct arasan_nfc *nfc = to_anfc(chip->controller); + unsigned int nfc_cs_idx = anfc_relative_to_absolute_cs(anand, target); + int ret; + + anfc_assert_cs(nfc, nfc_cs_idx); + + /* Update the controller timings and the potential ECC configuration */ + writel_relaxed(anand->data_iface, nfc->base + DATA_INTERFACE_REG); + writel_relaxed(anand->timings, nfc->base + TIMING_REG); + + /* Update clock frequency */ + if (nfc->cur_clk != anand->clk) { + clk_disable_unprepare(nfc->bus_clk); + ret = clk_set_rate(nfc->bus_clk, anand->clk); + if (ret) { + dev_err(nfc->dev, "Failed to change clock rate\n"); + return ret; + } + + ret = clk_prepare_enable(nfc->bus_clk); + if (ret) { + dev_err(nfc->dev, + "Failed to re-enable the bus clock\n"); + return ret; + } + + nfc->cur_clk = anand->clk; + } + + return 0; +} + +/* + * When using the embedded hardware ECC engine, the controller is in charge of + * feeding the engine with, first, the ECC residue present in the data array. + * A typical read operation is: + * 1/ Assert the read operation by sending the relevant command/address cycles + * but targeting the column of the first ECC bytes in the OOB area instead of + * the main data directly. + * 2/ After having read the relevant number of ECC bytes, the controller uses + * the RNDOUT/RNDSTART commands which are set into the "ECC Spare Command + * Register" to move the pointer back at the beginning of the main data. + * 3/ It will read the content of the main area for a given size (pktsize) and + * will feed the ECC engine with this buffer again. + * 4/ The ECC engine derives the ECC bytes for the given data and compare them + * with the ones already received. It eventually trigger status flags and + * then set the "Buffer Read Ready" flag. + * 5/ The corrected data is then available for reading from the data port + * register. + * + * The hardware BCH ECC engine is known to be inconstent in BCH mode and never + * reports uncorrectable errors. Because of this bug, we have to use the + * software BCH implementation in the read path. + */ +static int anfc_read_page_hw_ecc(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct arasan_nfc *nfc = to_anfc(chip->controller); + struct mtd_info *mtd = nand_to_mtd(chip); + struct anand *anand = to_anand(chip); + unsigned int len = mtd->writesize + (oob_required ? mtd->oobsize : 0); + unsigned int max_bitflips = 0; + dma_addr_t dma_addr; + int step, ret; + struct anfc_op nfc_op = { + .pkt_reg = + PKT_SIZE(chip->ecc.size) | + PKT_STEPS(chip->ecc.steps), + .addr1_reg = + (page & 0xFF) << (8 * (anand->caddr_cycles)) | + (((page >> 8) & 0xFF) << (8 * (1 + anand->caddr_cycles))), + .addr2_reg = + ((page >> 16) & 0xFF) | + ADDR2_STRENGTH(anand->strength) | + ADDR2_CS(nfc->native_cs), + .cmd_reg = + CMD_1(NAND_CMD_READ0) | + CMD_2(NAND_CMD_READSTART) | + CMD_PAGE_SIZE(anand->page_sz) | + CMD_DMA_ENABLE | + CMD_NADDRS(anand->caddr_cycles + + anand->raddr_cycles), + .prog_reg = PROG_PGRD, + }; + + dma_addr = dma_map_single(nfc->dev, (void *)buf, len, DMA_FROM_DEVICE); + if (dma_mapping_error(nfc->dev, dma_addr)) { + dev_err(nfc->dev, "Buffer mapping error"); + return -EIO; + } + + writel_relaxed(lower_32_bits(dma_addr), nfc->base + DMA_ADDR0_REG); + writel_relaxed(upper_32_bits(dma_addr), nfc->base + DMA_ADDR1_REG); + + anfc_trigger_op(nfc, &nfc_op); + + ret = anfc_wait_for_event(nfc, XFER_COMPLETE); + dma_unmap_single(nfc->dev, dma_addr, len, DMA_FROM_DEVICE); + if (ret) { + dev_err(nfc->dev, "Error reading page %d\n", page); + return ret; + } + + /* Store the raw OOB bytes as well */ + ret = nand_change_read_column_op(chip, mtd->writesize, chip->oob_poi, + mtd->oobsize, 0); + if (ret) + return ret; + + /* + * For each step, compute by softare the BCH syndrome over the raw data. + * Compare the theoretical amount of errors and compare with the + * hardware engine feedback. + */ + for (step = 0; step < chip->ecc.steps; step++) { + u8 *raw_buf = &buf[step * chip->ecc.size]; + unsigned int bit, byte; + int bf, i; + + /* Extract the syndrome, it is not necessarily aligned */ + memset(anand->hw_ecc, 0, chip->ecc.bytes); + nand_extract_bits(anand->hw_ecc, 0, + &chip->oob_poi[mtd->oobsize - anand->ecc_total], + anand->ecc_bits * step, anand->ecc_bits); + + bf = bch_decode(anand->bch, raw_buf, chip->ecc.size, + anand->hw_ecc, NULL, NULL, anand->errloc); + if (!bf) { + continue; + } else if (bf > 0) { + for (i = 0; i < bf; i++) { + /* Only correct the data, not the syndrome */ + if (anand->errloc[i] < (chip->ecc.size * 8)) { + bit = BIT(anand->errloc[i] & 7); + byte = anand->errloc[i] >> 3; + raw_buf[byte] ^= bit; + } + } + + mtd->ecc_stats.corrected += bf; + max_bitflips = max_t(unsigned int, max_bitflips, bf); + + continue; + } + + bf = nand_check_erased_ecc_chunk(raw_buf, chip->ecc.size, + anand->hw_ecc, chip->ecc.bytes, NULL, 0, + chip->ecc.strength); + if (bf > 0) { + mtd->ecc_stats.corrected += bf; + max_bitflips = max_t(unsigned int, max_bitflips, bf); + memset(raw_buf, 0xFF, chip->ecc.size); + } else if (bf < 0) { + mtd->ecc_stats.failed++; + } + } + + return 0; +} + +static int anfc_sel_read_page_hw_ecc(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + int ret; + + ret = anfc_select_target(chip, chip->cur_cs); + if (ret) + return ret; + + return anfc_read_page_hw_ecc(chip, buf, oob_required, page); +}; + +static int anfc_write_page_hw_ecc(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + struct anand *anand = to_anand(chip); + struct arasan_nfc *nfc = to_anfc(chip->controller); + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int len = mtd->writesize + (oob_required ? mtd->oobsize : 0); + dma_addr_t dma_addr; + u8 status; + int ret; + struct anfc_op nfc_op = { + .pkt_reg = + PKT_SIZE(chip->ecc.size) | + PKT_STEPS(chip->ecc.steps), + .addr1_reg = + (page & 0xFF) << (8 * (anand->caddr_cycles)) | + (((page >> 8) & 0xFF) << (8 * (1 + anand->caddr_cycles))), + .addr2_reg = + ((page >> 16) & 0xFF) | + ADDR2_STRENGTH(anand->strength) | + ADDR2_CS(nfc->native_cs), + .cmd_reg = + CMD_1(NAND_CMD_SEQIN) | + CMD_2(NAND_CMD_PAGEPROG) | + CMD_PAGE_SIZE(anand->page_sz) | + CMD_DMA_ENABLE | + CMD_NADDRS(anand->caddr_cycles + + anand->raddr_cycles) | + CMD_ECC_ENABLE, + .prog_reg = PROG_PGPROG, + }; + + writel_relaxed(anand->ecc_conf, nfc->base + ECC_CONF_REG); + writel_relaxed(ECC_SP_CMD1(NAND_CMD_RNDIN) | + ECC_SP_ADDRS(anand->caddr_cycles), + nfc->base + ECC_SP_REG); + + dma_addr = dma_map_single(nfc->dev, (void *)buf, len, DMA_TO_DEVICE); + if (dma_mapping_error(nfc->dev, dma_addr)) { + dev_err(nfc->dev, "Buffer mapping error"); + return -EIO; + } + + writel_relaxed(lower_32_bits(dma_addr), nfc->base + DMA_ADDR0_REG); + writel_relaxed(upper_32_bits(dma_addr), nfc->base + DMA_ADDR1_REG); + + anfc_trigger_op(nfc, &nfc_op); + ret = anfc_wait_for_event(nfc, XFER_COMPLETE); + dma_unmap_single(nfc->dev, dma_addr, len, DMA_TO_DEVICE); + if (ret) { + dev_err(nfc->dev, "Error writing page %d\n", page); + return ret; + } + + /* Spare data is not protected */ + if (oob_required) { + ret = nand_write_oob_std(chip, page); + if (ret) + return ret; + } + + /* Check write status on the chip side */ + ret = nand_status_op(chip, &status); + if (ret) + return ret; + + if (status & NAND_STATUS_FAIL) + return -EIO; + + return 0; +} + +static int anfc_sel_write_page_hw_ecc(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + int ret; + + ret = anfc_select_target(chip, chip->cur_cs); + if (ret) + return ret; + + return anfc_write_page_hw_ecc(chip, buf, oob_required, page); +}; + +/* NAND framework ->exec_op() hooks and related helpers */ +static int anfc_parse_instructions(struct nand_chip *chip, + const struct nand_subop *subop, + struct anfc_op *nfc_op) +{ + struct arasan_nfc *nfc = to_anfc(chip->controller); + struct anand *anand = to_anand(chip); + const struct nand_op_instr *instr = NULL; + bool first_cmd = true; + unsigned int op_id; + int ret, i; + + memset(nfc_op, 0, sizeof(*nfc_op)); + nfc_op->addr2_reg = ADDR2_CS(nfc->native_cs); + nfc_op->cmd_reg = CMD_PAGE_SIZE(anand->page_sz); + + for (op_id = 0; op_id < subop->ninstrs; op_id++) { + unsigned int offset, naddrs, pktsize; + const u8 *addrs; + u8 *buf; + + instr = &subop->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + if (first_cmd) + nfc_op->cmd_reg |= CMD_1(instr->ctx.cmd.opcode); + else + nfc_op->cmd_reg |= CMD_2(instr->ctx.cmd.opcode); + + first_cmd = false; + break; + + case NAND_OP_ADDR_INSTR: + offset = nand_subop_get_addr_start_off(subop, op_id); + naddrs = nand_subop_get_num_addr_cyc(subop, op_id); + addrs = &instr->ctx.addr.addrs[offset]; + nfc_op->cmd_reg |= CMD_NADDRS(naddrs); + + for (i = 0; i < min(ANFC_MAX_ADDR_CYC, naddrs); i++) { + if (i < 4) + nfc_op->addr1_reg |= (u32)addrs[i] << i * 8; + else + nfc_op->addr2_reg |= addrs[i]; + } + + break; + case NAND_OP_DATA_IN_INSTR: + nfc_op->read = true; + fallthrough; + case NAND_OP_DATA_OUT_INSTR: + offset = nand_subop_get_data_start_off(subop, op_id); + buf = instr->ctx.data.buf.in; + nfc_op->buf = &buf[offset]; + nfc_op->len = nand_subop_get_data_len(subop, op_id); + ret = anfc_pkt_len_config(nfc_op->len, &nfc_op->steps, + &pktsize); + if (ret) + return ret; + + /* + * Number of DATA cycles must be aligned on 4, this + * means the controller might read/write more than + * requested. This is harmless most of the time as extra + * DATA are discarded in the write path and read pointer + * adjusted in the read path. + * + * FIXME: The core should mark operations where + * reading/writing more is allowed so the exec_op() + * implementation can take the right decision when the + * alignment constraint is not met: adjust the number of + * DATA cycles when it's allowed, reject the operation + * otherwise. + */ + nfc_op->pkt_reg |= PKT_SIZE(round_up(pktsize, 4)) | + PKT_STEPS(nfc_op->steps); + break; + case NAND_OP_WAITRDY_INSTR: + nfc_op->rdy_timeout_ms = instr->ctx.waitrdy.timeout_ms; + break; + } + } + + return 0; +} + +static int anfc_rw_pio_op(struct arasan_nfc *nfc, struct anfc_op *nfc_op) +{ + unsigned int dwords = (nfc_op->len / 4) / nfc_op->steps; + unsigned int last_len = nfc_op->len % 4; + unsigned int offset, dir; + u8 *buf = nfc_op->buf; + int ret, i; + + for (i = 0; i < nfc_op->steps; i++) { + dir = nfc_op->read ? READ_READY : WRITE_READY; + ret = anfc_wait_for_event(nfc, dir); + if (ret) { + dev_err(nfc->dev, "PIO %s ready signal not received\n", + nfc_op->read ? "Read" : "Write"); + return ret; + } + + offset = i * (dwords * 4); + if (nfc_op->read) + ioread32_rep(nfc->base + DATA_PORT_REG, &buf[offset], + dwords); + else + iowrite32_rep(nfc->base + DATA_PORT_REG, &buf[offset], + dwords); + } + + if (last_len) { + u32 remainder; + + offset = nfc_op->len - last_len; + + if (nfc_op->read) { + remainder = readl_relaxed(nfc->base + DATA_PORT_REG); + memcpy(&buf[offset], &remainder, last_len); + } else { + memcpy(&remainder, &buf[offset], last_len); + writel_relaxed(remainder, nfc->base + DATA_PORT_REG); + } + } + + return anfc_wait_for_event(nfc, XFER_COMPLETE); +} + +static int anfc_misc_data_type_exec(struct nand_chip *chip, + const struct nand_subop *subop, + u32 prog_reg) +{ + struct arasan_nfc *nfc = to_anfc(chip->controller); + struct anfc_op nfc_op = {}; + int ret; + + ret = anfc_parse_instructions(chip, subop, &nfc_op); + if (ret) + return ret; + + nfc_op.prog_reg = prog_reg; + anfc_trigger_op(nfc, &nfc_op); + + if (nfc_op.rdy_timeout_ms) { + ret = anfc_wait_for_rb(nfc, chip, nfc_op.rdy_timeout_ms); + if (ret) + return ret; + } + + return anfc_rw_pio_op(nfc, &nfc_op); +} + +static int anfc_param_read_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + return anfc_misc_data_type_exec(chip, subop, PROG_RDPARAM); +} + +static int anfc_data_read_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + u32 prog_reg = PROG_PGRD; + + /* + * Experience shows that while in SDR mode sending a CHANGE READ COLUMN + * command through the READ PAGE "type" always works fine, when in + * NV-DDR mode the same command simply fails. However, it was also + * spotted that any CHANGE READ COLUMN command sent through the CHANGE + * READ COLUMN ENHANCED "type" would correctly work in both cases (SDR + * and NV-DDR). So, for simplicity, let's program the controller with + * the CHANGE READ COLUMN ENHANCED "type" whenever we are requested to + * perform a CHANGE READ COLUMN operation. + */ + if (subop->instrs[0].ctx.cmd.opcode == NAND_CMD_RNDOUT && + subop->instrs[2].ctx.cmd.opcode == NAND_CMD_RNDOUTSTART) + prog_reg = PROG_CHG_RD_COL_ENH; + + return anfc_misc_data_type_exec(chip, subop, prog_reg); +} + +static int anfc_param_write_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + return anfc_misc_data_type_exec(chip, subop, PROG_SET_FEATURE); +} + +static int anfc_data_write_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + return anfc_misc_data_type_exec(chip, subop, PROG_PGPROG); +} + +static int anfc_misc_zerolen_type_exec(struct nand_chip *chip, + const struct nand_subop *subop, + u32 prog_reg) +{ + struct arasan_nfc *nfc = to_anfc(chip->controller); + struct anfc_op nfc_op = {}; + int ret; + + ret = anfc_parse_instructions(chip, subop, &nfc_op); + if (ret) + return ret; + + nfc_op.prog_reg = prog_reg; + anfc_trigger_op(nfc, &nfc_op); + + ret = anfc_wait_for_event(nfc, XFER_COMPLETE); + if (ret) + return ret; + + if (nfc_op.rdy_timeout_ms) + ret = anfc_wait_for_rb(nfc, chip, nfc_op.rdy_timeout_ms); + + return ret; +} + +static int anfc_status_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct arasan_nfc *nfc = to_anfc(chip->controller); + u32 tmp; + int ret; + + /* See anfc_check_op() for details about this constraint */ + if (subop->instrs[0].ctx.cmd.opcode != NAND_CMD_STATUS) + return -ENOTSUPP; + + ret = anfc_misc_zerolen_type_exec(chip, subop, PROG_STATUS); + if (ret) + return ret; + + tmp = readl_relaxed(nfc->base + FLASH_STS_REG); + memcpy(subop->instrs[1].ctx.data.buf.in, &tmp, 1); + + return 0; +} + +static int anfc_reset_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + return anfc_misc_zerolen_type_exec(chip, subop, PROG_RST); +} + +static int anfc_erase_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + return anfc_misc_zerolen_type_exec(chip, subop, PROG_ERASE); +} + +static int anfc_wait_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct arasan_nfc *nfc = to_anfc(chip->controller); + struct anfc_op nfc_op = {}; + int ret; + + ret = anfc_parse_instructions(chip, subop, &nfc_op); + if (ret) + return ret; + + return anfc_wait_for_rb(nfc, chip, nfc_op.rdy_timeout_ms); +} + +static const struct nand_op_parser anfc_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN( + anfc_param_read_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, ANFC_MAX_ADDR_CYC), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, ANFC_MAX_CHUNK_SIZE)), + NAND_OP_PARSER_PATTERN( + anfc_param_write_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, ANFC_MAX_ADDR_CYC), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, ANFC_MAX_PARAM_SIZE)), + NAND_OP_PARSER_PATTERN( + anfc_data_read_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, ANFC_MAX_ADDR_CYC), + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, ANFC_MAX_CHUNK_SIZE)), + NAND_OP_PARSER_PATTERN( + anfc_data_write_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, ANFC_MAX_ADDR_CYC), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, ANFC_MAX_CHUNK_SIZE), + NAND_OP_PARSER_PAT_CMD_ELEM(false)), + NAND_OP_PARSER_PATTERN( + anfc_reset_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + NAND_OP_PARSER_PATTERN( + anfc_erase_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, ANFC_MAX_ADDR_CYC), + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + NAND_OP_PARSER_PATTERN( + anfc_status_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, ANFC_MAX_CHUNK_SIZE)), + NAND_OP_PARSER_PATTERN( + anfc_wait_type_exec, + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + ); + +static int anfc_check_op(struct nand_chip *chip, + const struct nand_operation *op) +{ + const struct nand_op_instr *instr; + int op_id; + + /* + * The controller abstracts all the NAND operations and do not support + * data only operations. + * + * TODO: The nand_op_parser framework should be extended to + * support custom checks on DATA instructions. + */ + for (op_id = 0; op_id < op->ninstrs; op_id++) { + instr = &op->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_ADDR_INSTR: + if (instr->ctx.addr.naddrs > ANFC_MAX_ADDR_CYC) + return -ENOTSUPP; + + break; + case NAND_OP_DATA_IN_INSTR: + case NAND_OP_DATA_OUT_INSTR: + if (instr->ctx.data.len > ANFC_MAX_CHUNK_SIZE) + return -ENOTSUPP; + + if (anfc_pkt_len_config(instr->ctx.data.len, NULL, NULL)) + return -ENOTSUPP; + + break; + default: + break; + } + } + + /* + * The controller does not allow to proceed with a CMD+DATA_IN cycle + * manually on the bus by reading data from the data register. Instead, + * the controller abstract a status read operation with its own status + * register after ordering a read status operation. Hence, we cannot + * support any CMD+DATA_IN operation other than a READ STATUS. + * + * TODO: The nand_op_parser() framework should be extended to describe + * fixed patterns instead of open-coding this check here. + */ + if (op->ninstrs == 2 && + op->instrs[0].type == NAND_OP_CMD_INSTR && + op->instrs[0].ctx.cmd.opcode != NAND_CMD_STATUS && + op->instrs[1].type == NAND_OP_DATA_IN_INSTR) + return -ENOTSUPP; + + return nand_op_parser_exec_op(chip, &anfc_op_parser, op, true); +} + +static int anfc_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + int ret; + + if (check_only) + return anfc_check_op(chip, op); + + ret = anfc_select_target(chip, op->cs); + if (ret) + return ret; + + return nand_op_parser_exec_op(chip, &anfc_op_parser, op, check_only); +} + +static int anfc_setup_interface(struct nand_chip *chip, int target, + const struct nand_interface_config *conf) +{ + struct anand *anand = to_anand(chip); + struct arasan_nfc *nfc = to_anfc(chip->controller); + struct device_node *np = nfc->dev->of_node; + const struct nand_sdr_timings *sdr; + const struct nand_nvddr_timings *nvddr; + unsigned int tccs_min, dqs_mode, fast_tcad; + + if (nand_interface_is_nvddr(conf)) { + nvddr = nand_get_nvddr_timings(conf); + if (IS_ERR(nvddr)) + return PTR_ERR(nvddr); + } else { + sdr = nand_get_sdr_timings(conf); + if (IS_ERR(sdr)) + return PTR_ERR(sdr); + } + + if (target < 0) + return 0; + + if (nand_interface_is_sdr(conf)) { + anand->data_iface = DIFACE_SDR | + DIFACE_SDR_MODE(conf->timings.mode); + anand->timings = 0; + } else { + anand->data_iface = DIFACE_NVDDR | + DIFACE_DDR_MODE(conf->timings.mode); + + if (conf->timings.nvddr.tCCS_min <= 100000) + tccs_min = TCCS_TIME_100NS; + else if (conf->timings.nvddr.tCCS_min <= 200000) + tccs_min = TCCS_TIME_200NS; + else if (conf->timings.nvddr.tCCS_min <= 300000) + tccs_min = TCCS_TIME_300NS; + else + tccs_min = TCCS_TIME_500NS; + + fast_tcad = 0; + if (conf->timings.nvddr.tCAD_min < 45000) + fast_tcad = FAST_TCAD; + + switch (conf->timings.mode) { + case 5: + case 4: + dqs_mode = 2; + break; + case 3: + dqs_mode = 3; + break; + case 2: + dqs_mode = 4; + break; + case 1: + dqs_mode = 5; + break; + case 0: + default: + dqs_mode = 6; + break; + } + + anand->timings = tccs_min | fast_tcad | + DQS_BUFF_SEL_IN(dqs_mode) | + DQS_BUFF_SEL_OUT(dqs_mode); + } + + if (nand_interface_is_sdr(conf)) { + anand->clk = ANFC_XLNX_SDR_DFLT_CORE_CLK; + } else { + /* ONFI timings are defined in picoseconds */ + anand->clk = div_u64((u64)NSEC_PER_SEC * 1000, + conf->timings.nvddr.tCK_min); + } + + /* + * Due to a hardware bug in the ZynqMP SoC, SDR timing modes 0-1 work + * with f > 90MHz (default clock is 100MHz) but signals are unstable + * with higher modes. Hence we decrease a little bit the clock rate to + * 80MHz when using SDR modes 2-5 with this SoC. + */ + if (of_device_is_compatible(np, "xlnx,zynqmp-nand-controller") && + nand_interface_is_sdr(conf) && conf->timings.mode >= 2) + anand->clk = ANFC_XLNX_SDR_HS_CORE_CLK; + + return 0; +} + +static int anfc_calc_hw_ecc_bytes(int step_size, int strength) +{ + unsigned int bch_gf_mag, ecc_bits; + + switch (step_size) { + case SZ_512: + bch_gf_mag = 13; + break; + case SZ_1K: + bch_gf_mag = 14; + break; + default: + return -EINVAL; + } + + ecc_bits = bch_gf_mag * strength; + + return DIV_ROUND_UP(ecc_bits, 8); +} + +static const int anfc_hw_ecc_512_strengths[] = {4, 8, 12}; + +static const int anfc_hw_ecc_1024_strengths[] = {24}; + +static const struct nand_ecc_step_info anfc_hw_ecc_step_infos[] = { + { + .stepsize = SZ_512, + .strengths = anfc_hw_ecc_512_strengths, + .nstrengths = ARRAY_SIZE(anfc_hw_ecc_512_strengths), + }, + { + .stepsize = SZ_1K, + .strengths = anfc_hw_ecc_1024_strengths, + .nstrengths = ARRAY_SIZE(anfc_hw_ecc_1024_strengths), + }, +}; + +static const struct nand_ecc_caps anfc_hw_ecc_caps = { + .stepinfos = anfc_hw_ecc_step_infos, + .nstepinfos = ARRAY_SIZE(anfc_hw_ecc_step_infos), + .calc_ecc_bytes = anfc_calc_hw_ecc_bytes, +}; + +static int anfc_init_hw_ecc_controller(struct arasan_nfc *nfc, + struct nand_chip *chip) +{ + struct anand *anand = to_anand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + unsigned int bch_prim_poly = 0, bch_gf_mag = 0, ecc_offset; + int ret; + + switch (mtd->writesize) { + case SZ_512: + case SZ_2K: + case SZ_4K: + case SZ_8K: + case SZ_16K: + break; + default: + dev_err(nfc->dev, "Unsupported page size %d\n", mtd->writesize); + return -EINVAL; + } + + ret = nand_ecc_choose_conf(chip, &anfc_hw_ecc_caps, mtd->oobsize); + if (ret) + return ret; + + switch (ecc->strength) { + case 12: + anand->strength = 0x1; + break; + case 8: + anand->strength = 0x2; + break; + case 4: + anand->strength = 0x3; + break; + case 24: + anand->strength = 0x4; + break; + default: + dev_err(nfc->dev, "Unsupported strength %d\n", ecc->strength); + return -EINVAL; + } + + switch (ecc->size) { + case SZ_512: + bch_gf_mag = 13; + bch_prim_poly = 0x201b; + break; + case SZ_1K: + bch_gf_mag = 14; + bch_prim_poly = 0x4443; + break; + default: + dev_err(nfc->dev, "Unsupported step size %d\n", ecc->strength); + return -EINVAL; + } + + mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout()); + + ecc->steps = mtd->writesize / ecc->size; + ecc->algo = NAND_ECC_ALGO_BCH; + anand->ecc_bits = bch_gf_mag * ecc->strength; + ecc->bytes = DIV_ROUND_UP(anand->ecc_bits, 8); + anand->ecc_total = DIV_ROUND_UP(anand->ecc_bits * ecc->steps, 8); + ecc_offset = mtd->writesize + mtd->oobsize - anand->ecc_total; + anand->ecc_conf = ECC_CONF_COL(ecc_offset) | + ECC_CONF_LEN(anand->ecc_total) | + ECC_CONF_BCH_EN; + + anand->errloc = devm_kmalloc_array(nfc->dev, ecc->strength, + sizeof(*anand->errloc), GFP_KERNEL); + if (!anand->errloc) + return -ENOMEM; + + anand->hw_ecc = devm_kmalloc(nfc->dev, ecc->bytes, GFP_KERNEL); + if (!anand->hw_ecc) + return -ENOMEM; + + /* Enforce bit swapping to fit the hardware */ + anand->bch = bch_init(bch_gf_mag, ecc->strength, bch_prim_poly, true); + if (!anand->bch) + return -EINVAL; + + ecc->read_page = anfc_sel_read_page_hw_ecc; + ecc->write_page = anfc_sel_write_page_hw_ecc; + + return 0; +} + +static int anfc_attach_chip(struct nand_chip *chip) +{ + struct anand *anand = to_anand(chip); + struct arasan_nfc *nfc = to_anfc(chip->controller); + struct mtd_info *mtd = nand_to_mtd(chip); + int ret = 0; + + if (mtd->writesize <= SZ_512) + anand->caddr_cycles = 1; + else + anand->caddr_cycles = 2; + + if (chip->options & NAND_ROW_ADDR_3) + anand->raddr_cycles = 3; + else + anand->raddr_cycles = 2; + + switch (mtd->writesize) { + case 512: + anand->page_sz = 0; + break; + case 1024: + anand->page_sz = 5; + break; + case 2048: + anand->page_sz = 1; + break; + case 4096: + anand->page_sz = 2; + break; + case 8192: + anand->page_sz = 3; + break; + case 16384: + anand->page_sz = 4; + break; + default: + return -EINVAL; + } + + /* These hooks are valid for all ECC providers */ + chip->ecc.read_page_raw = nand_monolithic_read_page_raw; + chip->ecc.write_page_raw = nand_monolithic_write_page_raw; + + switch (chip->ecc.engine_type) { + case NAND_ECC_ENGINE_TYPE_NONE: + case NAND_ECC_ENGINE_TYPE_SOFT: + case NAND_ECC_ENGINE_TYPE_ON_DIE: + break; + case NAND_ECC_ENGINE_TYPE_ON_HOST: + ret = anfc_init_hw_ecc_controller(nfc, chip); + break; + default: + dev_err(nfc->dev, "Unsupported ECC mode: %d\n", + chip->ecc.engine_type); + return -EINVAL; + } + + return ret; +} + +static void anfc_detach_chip(struct nand_chip *chip) +{ + struct anand *anand = to_anand(chip); + + if (anand->bch) + bch_free(anand->bch); +} + +static const struct nand_controller_ops anfc_ops = { + .exec_op = anfc_exec_op, + .setup_interface = anfc_setup_interface, + .attach_chip = anfc_attach_chip, + .detach_chip = anfc_detach_chip, +}; + +static int anfc_chip_init(struct arasan_nfc *nfc, struct device_node *np) +{ + struct anand *anand; + struct nand_chip *chip; + struct mtd_info *mtd; + int rb, ret, i; + + anand = devm_kzalloc(nfc->dev, sizeof(*anand), GFP_KERNEL); + if (!anand) + return -ENOMEM; + + /* Chip-select init */ + anand->ncs_idx = of_property_count_elems_of_size(np, "reg", sizeof(u32)); + if (anand->ncs_idx <= 0 || anand->ncs_idx > nfc->ncs) { + dev_err(nfc->dev, "Invalid reg property\n"); + return -EINVAL; + } + + anand->cs_idx = devm_kcalloc(nfc->dev, anand->ncs_idx, + sizeof(*anand->cs_idx), GFP_KERNEL); + if (!anand->cs_idx) + return -ENOMEM; + + for (i = 0; i < anand->ncs_idx; i++) { + ret = of_property_read_u32_index(np, "reg", i, + &anand->cs_idx[i]); + if (ret) { + dev_err(nfc->dev, "invalid CS property: %d\n", ret); + return ret; + } + } + + /* Ready-busy init */ + ret = of_property_read_u32(np, "nand-rb", &rb); + if (ret) + return ret; + + if (rb >= ANFC_MAX_CS) { + dev_err(nfc->dev, "Wrong RB %d\n", rb); + return -EINVAL; + } + + anand->rb = rb; + + chip = &anand->chip; + mtd = nand_to_mtd(chip); + mtd->dev.parent = nfc->dev; + chip->controller = &nfc->controller; + chip->options = NAND_BUSWIDTH_AUTO | NAND_NO_SUBPAGE_WRITE | + NAND_USES_DMA; + + nand_set_flash_node(chip, np); + if (!mtd->name) { + dev_err(nfc->dev, "NAND label property is mandatory\n"); + return -EINVAL; + } + + ret = nand_scan(chip, anand->ncs_idx); + if (ret) { + dev_err(nfc->dev, "Scan operation failed\n"); + return ret; + } + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + nand_cleanup(chip); + return ret; + } + + list_add_tail(&anand->node, &nfc->chips); + + return 0; +} + +static void anfc_chips_cleanup(struct arasan_nfc *nfc) +{ + struct anand *anand, *tmp; + struct nand_chip *chip; + int ret; + + list_for_each_entry_safe(anand, tmp, &nfc->chips, node) { + chip = &anand->chip; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + list_del(&anand->node); + } +} + +static int anfc_chips_init(struct arasan_nfc *nfc) +{ + struct device_node *np = nfc->dev->of_node; + int nchips = of_get_child_count(np); + int ret; + + if (!nchips) { + dev_err(nfc->dev, "Incorrect number of NAND chips (%d)\n", + nchips); + return -EINVAL; + } + + for_each_child_of_node_scoped(np, nand_np) { + ret = anfc_chip_init(nfc, nand_np); + if (ret) { + anfc_chips_cleanup(nfc); + break; + } + } + + return ret; +} + +static void anfc_reset(struct arasan_nfc *nfc) +{ + /* Disable interrupt signals */ + writel_relaxed(0, nfc->base + INTR_SIG_EN_REG); + + /* Enable interrupt status */ + writel_relaxed(EVENT_MASK, nfc->base + INTR_STS_EN_REG); + + nfc->cur_cs = -1; +} + +static int anfc_parse_cs(struct arasan_nfc *nfc) +{ + int ret; + + /* Check the gpio-cs property */ + ret = rawnand_dt_parse_gpio_cs(nfc->dev, &nfc->cs_array, &nfc->ncs); + if (ret) + return ret; + + /* + * The controller native CS cannot be both disabled at the same time. + * Hence, only one native CS can be used if GPIO CS are needed, so that + * the other is selected when a non-native CS must be asserted (not + * wired physically or configured as GPIO instead of NAND CS). In this + * case, the "not" chosen CS is assigned to nfc->spare_cs and selected + * whenever a GPIO CS must be asserted. + */ + if (nfc->cs_array) { + if (nfc->ncs > 2 && !nfc->cs_array[0] && !nfc->cs_array[1]) { + dev_err(nfc->dev, + "Assign a single native CS when using GPIOs\n"); + return -EINVAL; + } + + if (nfc->cs_array[0]) + nfc->spare_cs = 0; + else + nfc->spare_cs = 1; + } + + if (!nfc->cs_array) { + nfc->cs_array = anfc_default_cs_array; + nfc->ncs = ANFC_MAX_CS; + return 0; + } + + return 0; +} + +static int anfc_probe(struct platform_device *pdev) +{ + struct arasan_nfc *nfc; + int ret; + + nfc = devm_kzalloc(&pdev->dev, sizeof(*nfc), GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->dev = &pdev->dev; + nand_controller_init(&nfc->controller); + nfc->controller.ops = &anfc_ops; + INIT_LIST_HEAD(&nfc->chips); + + nfc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(nfc->base)) + return PTR_ERR(nfc->base); + + anfc_reset(nfc); + + nfc->controller_clk = devm_clk_get_enabled(&pdev->dev, "controller"); + if (IS_ERR(nfc->controller_clk)) + return PTR_ERR(nfc->controller_clk); + + nfc->bus_clk = devm_clk_get_enabled(&pdev->dev, "bus"); + if (IS_ERR(nfc->bus_clk)) + return PTR_ERR(nfc->bus_clk); + + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)); + if (ret) + return ret; + + ret = anfc_parse_cs(nfc); + if (ret) + return ret; + + ret = anfc_chips_init(nfc); + if (ret) + return ret; + + platform_set_drvdata(pdev, nfc); + + return 0; +} + +static void anfc_remove(struct platform_device *pdev) +{ + int i; + struct arasan_nfc *nfc = platform_get_drvdata(pdev); + + for (i = 0; i < nfc->ncs; i++) { + if (nfc->cs_array[i]) { + gpiod_put(nfc->cs_array[i]); + } + } + + anfc_chips_cleanup(nfc); +} + +static const struct of_device_id anfc_ids[] = { + { + .compatible = "xlnx,zynqmp-nand-controller", + }, + { + .compatible = "arasan,nfc-v3p10", + }, + {} +}; +MODULE_DEVICE_TABLE(of, anfc_ids); + +static struct platform_driver anfc_driver = { + .driver = { + .name = "arasan-nand-controller", + .of_match_table = anfc_ids, + }, + .probe = anfc_probe, + .remove = anfc_remove, +}; +module_platform_driver(anfc_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Punnaiah Choudary Kalluri "); +MODULE_AUTHOR("Naga Sureshkumar Relli "); +MODULE_AUTHOR("Miquel Raynal "); +MODULE_DESCRIPTION("Arasan NAND Flash Controller Driver"); diff --git a/drivers/mtd/nand/raw/atmel/Makefile b/drivers/mtd/nand/raw/atmel/Makefile new file mode 100644 index 000000000..27c2dd50e --- /dev/null +++ b/drivers/mtd/nand/raw/atmel/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_MTD_NAND_ATMEL) += atmel-nand-controller.o atmel-pmecc.o + +atmel-nand-controller-objs := nand-controller.o +atmel-pmecc-objs := pmecc.o diff --git a/drivers/mtd/nand/raw/atmel/nand-controller.c b/drivers/mtd/nand/raw/atmel/nand-controller.c new file mode 100644 index 000000000..8a19408ab --- /dev/null +++ b/drivers/mtd/nand/raw/atmel/nand-controller.c @@ -0,0 +1,2685 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2017 ATMEL + * Copyright 2017 Free Electrons + * + * Author: Boris Brezillon + * + * Derived from the atmel_nand.c driver which contained the following + * copyrights: + * + * Copyright 2003 Rick Bronson + * + * Derived from drivers/mtd/nand/autcpu12.c (removed in v3.8) + * Copyright 2001 Thomas Gleixner (gleixner@autronix.de) + * + * Derived from drivers/mtd/spia.c (removed in v3.8) + * Copyright 2000 Steven J. Hill (sjhill@cotw.com) + * + * + * Add Hardware ECC support for AT91SAM9260 / AT91SAM9263 + * Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright 2007 + * + * Derived from Das U-Boot source code + * (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c) + * Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas + * + * Add Programmable Multibit ECC support for various AT91 SoC + * Copyright 2012 ATMEL, Hong Xu + * + * Add Nand Flash Controller support for SAMA5 SoC + * Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com) + * + * A few words about the naming convention in this file. This convention + * applies to structure and function names. + * + * Prefixes: + * + * - atmel_nand_: all generic structures/functions + * - atmel_smc_nand_: all structures/functions specific to the SMC interface + * (at91sam9 and avr32 SoCs) + * - atmel_hsmc_nand_: all structures/functions specific to the HSMC interface + * (sama5 SoCs and later) + * - atmel_nfc_: all structures/functions used to manipulate the NFC sub-block + * that is available in the HSMC block + * - _nand_: all SoC specific structures/functions + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmecc.h" + +#define ATMEL_HSMC_NFC_CFG 0x0 +#define ATMEL_HSMC_NFC_CFG_SPARESIZE(x) (((x) / 4) << 24) +#define ATMEL_HSMC_NFC_CFG_SPARESIZE_MASK GENMASK(30, 24) +#define ATMEL_HSMC_NFC_CFG_DTO(cyc, mul) (((cyc) << 16) | ((mul) << 20)) +#define ATMEL_HSMC_NFC_CFG_DTO_MAX GENMASK(22, 16) +#define ATMEL_HSMC_NFC_CFG_RBEDGE BIT(13) +#define ATMEL_HSMC_NFC_CFG_FALLING_EDGE BIT(12) +#define ATMEL_HSMC_NFC_CFG_RSPARE BIT(9) +#define ATMEL_HSMC_NFC_CFG_WSPARE BIT(8) +#define ATMEL_HSMC_NFC_CFG_PAGESIZE_MASK GENMASK(2, 0) +#define ATMEL_HSMC_NFC_CFG_PAGESIZE(x) (fls((x) / 512) - 1) + +#define ATMEL_HSMC_NFC_CTRL 0x4 +#define ATMEL_HSMC_NFC_CTRL_EN BIT(0) +#define ATMEL_HSMC_NFC_CTRL_DIS BIT(1) + +#define ATMEL_HSMC_NFC_SR 0x8 +#define ATMEL_HSMC_NFC_IER 0xc +#define ATMEL_HSMC_NFC_IDR 0x10 +#define ATMEL_HSMC_NFC_IMR 0x14 +#define ATMEL_HSMC_NFC_SR_ENABLED BIT(1) +#define ATMEL_HSMC_NFC_SR_RB_RISE BIT(4) +#define ATMEL_HSMC_NFC_SR_RB_FALL BIT(5) +#define ATMEL_HSMC_NFC_SR_BUSY BIT(8) +#define ATMEL_HSMC_NFC_SR_WR BIT(11) +#define ATMEL_HSMC_NFC_SR_CSID GENMASK(14, 12) +#define ATMEL_HSMC_NFC_SR_XFRDONE BIT(16) +#define ATMEL_HSMC_NFC_SR_CMDDONE BIT(17) +#define ATMEL_HSMC_NFC_SR_DTOE BIT(20) +#define ATMEL_HSMC_NFC_SR_UNDEF BIT(21) +#define ATMEL_HSMC_NFC_SR_AWB BIT(22) +#define ATMEL_HSMC_NFC_SR_NFCASE BIT(23) +#define ATMEL_HSMC_NFC_SR_ERRORS (ATMEL_HSMC_NFC_SR_DTOE | \ + ATMEL_HSMC_NFC_SR_UNDEF | \ + ATMEL_HSMC_NFC_SR_AWB | \ + ATMEL_HSMC_NFC_SR_NFCASE) +#define ATMEL_HSMC_NFC_SR_RBEDGE(x) BIT((x) + 24) + +#define ATMEL_HSMC_NFC_ADDR 0x18 +#define ATMEL_HSMC_NFC_BANK 0x1c + +#define ATMEL_NFC_MAX_RB_ID 7 + +#define ATMEL_NFC_SRAM_SIZE 0x2400 + +#define ATMEL_NFC_CMD(pos, cmd) ((cmd) << (((pos) * 8) + 2)) +#define ATMEL_NFC_VCMD2 BIT(18) +#define ATMEL_NFC_ACYCLE(naddrs) ((naddrs) << 19) +#define ATMEL_NFC_CSID(cs) ((cs) << 22) +#define ATMEL_NFC_DATAEN BIT(25) +#define ATMEL_NFC_NFCWR BIT(26) + +#define ATMEL_NFC_MAX_ADDR_CYCLES 5 + +#define ATMEL_NAND_ALE_OFFSET BIT(21) +#define ATMEL_NAND_CLE_OFFSET BIT(22) + +#define DEFAULT_TIMEOUT_MS 1000 +#define MIN_DMA_LEN 128 + +static bool atmel_nand_avoid_dma __read_mostly; + +MODULE_PARM_DESC(avoiddma, "Avoid using DMA"); +module_param_named(avoiddma, atmel_nand_avoid_dma, bool, 0400); + +enum atmel_nand_rb_type { + ATMEL_NAND_NO_RB, + ATMEL_NAND_NATIVE_RB, + ATMEL_NAND_GPIO_RB, +}; + +struct atmel_nand_rb { + enum atmel_nand_rb_type type; + union { + struct gpio_desc *gpio; + int id; + }; +}; + +struct atmel_nand_cs { + int id; + struct atmel_nand_rb rb; + struct gpio_desc *csgpio; + struct { + void __iomem *virt; + dma_addr_t dma; + } io; + + struct atmel_smc_cs_conf smcconf; +}; + +struct atmel_nand { + struct list_head node; + struct device *dev; + struct nand_chip base; + struct atmel_nand_cs *activecs; + struct atmel_pmecc_user *pmecc; + struct gpio_desc *cdgpio; + int numcs; + struct atmel_nand_cs cs[] __counted_by(numcs); +}; + +static inline struct atmel_nand *to_atmel_nand(struct nand_chip *chip) +{ + return container_of(chip, struct atmel_nand, base); +} + +enum atmel_nfc_data_xfer { + ATMEL_NFC_NO_DATA, + ATMEL_NFC_READ_DATA, + ATMEL_NFC_WRITE_DATA, +}; + +struct atmel_nfc_op { + u8 cs; + u8 ncmds; + u8 cmds[2]; + u8 naddrs; + u8 addrs[5]; + enum atmel_nfc_data_xfer data; + u32 wait; + u32 errors; +}; + +struct atmel_nand_controller; +struct atmel_nand_controller_caps; + +struct atmel_nand_controller_ops { + int (*probe)(struct platform_device *pdev, + const struct atmel_nand_controller_caps *caps); + int (*remove)(struct atmel_nand_controller *nc); + void (*nand_init)(struct atmel_nand_controller *nc, + struct atmel_nand *nand); + int (*ecc_init)(struct nand_chip *chip); + int (*setup_interface)(struct atmel_nand *nand, int csline, + const struct nand_interface_config *conf); + int (*exec_op)(struct atmel_nand *nand, + const struct nand_operation *op, bool check_only); +}; + +struct atmel_nand_controller_caps { + bool has_dma; + bool legacy_of_bindings; + u32 ale_offs; + u32 cle_offs; + const char *ebi_csa_regmap_name; + const struct atmel_nand_controller_ops *ops; +}; + +struct atmel_nand_controller { + struct nand_controller base; + const struct atmel_nand_controller_caps *caps; + struct device *dev; + struct regmap *smc; + struct dma_chan *dmac; + struct atmel_pmecc *pmecc; + struct list_head chips; + struct clk *mck; +}; + +static inline struct atmel_nand_controller * +to_nand_controller(struct nand_controller *ctl) +{ + return container_of(ctl, struct atmel_nand_controller, base); +} + +struct atmel_smc_nand_ebi_csa_cfg { + u32 offs; + u32 nfd0_on_d16; +}; + +struct atmel_smc_nand_controller { + struct atmel_nand_controller base; + struct regmap *ebi_csa_regmap; + struct atmel_smc_nand_ebi_csa_cfg *ebi_csa; +}; + +static inline struct atmel_smc_nand_controller * +to_smc_nand_controller(struct nand_controller *ctl) +{ + return container_of(to_nand_controller(ctl), + struct atmel_smc_nand_controller, base); +} + +struct atmel_hsmc_nand_controller { + struct atmel_nand_controller base; + struct { + struct gen_pool *pool; + void __iomem *virt; + dma_addr_t dma; + } sram; + const struct atmel_hsmc_reg_layout *hsmc_layout; + struct regmap *io; + struct atmel_nfc_op op; + struct completion complete; + u32 cfg; + int irq; + + /* Only used when instantiating from legacy DT bindings. */ + struct clk *clk; +}; + +static inline struct atmel_hsmc_nand_controller * +to_hsmc_nand_controller(struct nand_controller *ctl) +{ + return container_of(to_nand_controller(ctl), + struct atmel_hsmc_nand_controller, base); +} + +static bool atmel_nfc_op_done(struct atmel_nfc_op *op, u32 status) +{ + op->errors |= status & ATMEL_HSMC_NFC_SR_ERRORS; + op->wait ^= status & op->wait; + + return !op->wait || op->errors; +} + +static irqreturn_t atmel_nfc_interrupt(int irq, void *data) +{ + struct atmel_hsmc_nand_controller *nc = data; + u32 sr, rcvd; + bool done; + + regmap_read(nc->base.smc, ATMEL_HSMC_NFC_SR, &sr); + + rcvd = sr & (nc->op.wait | ATMEL_HSMC_NFC_SR_ERRORS); + done = atmel_nfc_op_done(&nc->op, sr); + + if (rcvd) + regmap_write(nc->base.smc, ATMEL_HSMC_NFC_IDR, rcvd); + + if (done) + complete(&nc->complete); + + return rcvd ? IRQ_HANDLED : IRQ_NONE; +} + +static int atmel_nfc_wait(struct atmel_hsmc_nand_controller *nc, bool poll, + unsigned int timeout_ms) +{ + int ret; + + if (!timeout_ms) + timeout_ms = DEFAULT_TIMEOUT_MS; + + if (poll) { + u32 status; + + ret = regmap_read_poll_timeout(nc->base.smc, + ATMEL_HSMC_NFC_SR, status, + atmel_nfc_op_done(&nc->op, + status), + 0, timeout_ms * 1000); + } else { + init_completion(&nc->complete); + regmap_write(nc->base.smc, ATMEL_HSMC_NFC_IER, + nc->op.wait | ATMEL_HSMC_NFC_SR_ERRORS); + ret = wait_for_completion_timeout(&nc->complete, + msecs_to_jiffies(timeout_ms)); + if (!ret) + ret = -ETIMEDOUT; + else + ret = 0; + + regmap_write(nc->base.smc, ATMEL_HSMC_NFC_IDR, 0xffffffff); + } + + if (nc->op.errors & ATMEL_HSMC_NFC_SR_DTOE) { + dev_err(nc->base.dev, "Waiting NAND R/B Timeout\n"); + ret = -ETIMEDOUT; + } + + if (nc->op.errors & ATMEL_HSMC_NFC_SR_UNDEF) { + dev_err(nc->base.dev, "Access to an undefined area\n"); + ret = -EIO; + } + + if (nc->op.errors & ATMEL_HSMC_NFC_SR_AWB) { + dev_err(nc->base.dev, "Access while busy\n"); + ret = -EIO; + } + + if (nc->op.errors & ATMEL_HSMC_NFC_SR_NFCASE) { + dev_err(nc->base.dev, "Wrong access size\n"); + ret = -EIO; + } + + return ret; +} + +static void atmel_nand_dma_transfer_finished(void *data) +{ + struct completion *finished = data; + + complete(finished); +} + +static int atmel_nand_dma_transfer(struct atmel_nand_controller *nc, + void *buf, dma_addr_t dev_dma, size_t len, + enum dma_data_direction dir) +{ + DECLARE_COMPLETION_ONSTACK(finished); + dma_addr_t src_dma, dst_dma, buf_dma; + struct dma_async_tx_descriptor *tx; + dma_cookie_t cookie; + + buf_dma = dma_map_single(nc->dev, buf, len, dir); + if (dma_mapping_error(nc->dev, buf_dma)) { + dev_err(nc->dev, + "Failed to prepare a buffer for DMA access\n"); + goto err; + } + + if (dir == DMA_FROM_DEVICE) { + src_dma = dev_dma; + dst_dma = buf_dma; + } else { + src_dma = buf_dma; + dst_dma = dev_dma; + } + + tx = dmaengine_prep_dma_memcpy(nc->dmac, dst_dma, src_dma, len, + DMA_CTRL_ACK | DMA_PREP_INTERRUPT); + if (!tx) { + dev_err(nc->dev, "Failed to prepare DMA memcpy\n"); + goto err_unmap; + } + + tx->callback = atmel_nand_dma_transfer_finished; + tx->callback_param = &finished; + + cookie = dmaengine_submit(tx); + if (dma_submit_error(cookie)) { + dev_err(nc->dev, "Failed to do DMA tx_submit\n"); + goto err_unmap; + } + + dma_async_issue_pending(nc->dmac); + wait_for_completion(&finished); + dma_unmap_single(nc->dev, buf_dma, len, dir); + + return 0; + +err_unmap: + dma_unmap_single(nc->dev, buf_dma, len, dir); + +err: + dev_dbg(nc->dev, "Fall back to CPU I/O\n"); + + return -EIO; +} + +static int atmel_nfc_exec_op(struct atmel_hsmc_nand_controller *nc, bool poll) +{ + u8 *addrs = nc->op.addrs; + unsigned int op = 0; + u32 addr, val; + int i, ret; + + nc->op.wait = ATMEL_HSMC_NFC_SR_CMDDONE; + + for (i = 0; i < nc->op.ncmds; i++) + op |= ATMEL_NFC_CMD(i, nc->op.cmds[i]); + + if (nc->op.naddrs == ATMEL_NFC_MAX_ADDR_CYCLES) + regmap_write(nc->base.smc, ATMEL_HSMC_NFC_ADDR, *addrs++); + + op |= ATMEL_NFC_CSID(nc->op.cs) | + ATMEL_NFC_ACYCLE(nc->op.naddrs); + + if (nc->op.ncmds > 1) + op |= ATMEL_NFC_VCMD2; + + addr = addrs[0] | (addrs[1] << 8) | (addrs[2] << 16) | + (addrs[3] << 24); + + if (nc->op.data != ATMEL_NFC_NO_DATA) { + op |= ATMEL_NFC_DATAEN; + nc->op.wait |= ATMEL_HSMC_NFC_SR_XFRDONE; + + if (nc->op.data == ATMEL_NFC_WRITE_DATA) + op |= ATMEL_NFC_NFCWR; + } + + /* Clear all flags. */ + regmap_read(nc->base.smc, ATMEL_HSMC_NFC_SR, &val); + + /* Send the command. */ + regmap_write(nc->io, op, addr); + + ret = atmel_nfc_wait(nc, poll, 0); + if (ret) + dev_err(nc->base.dev, + "Failed to send NAND command (err = %d)!", + ret); + + /* Reset the op state. */ + memset(&nc->op, 0, sizeof(nc->op)); + + return ret; +} + +static void atmel_nand_data_in(struct atmel_nand *nand, void *buf, + unsigned int len, bool force_8bit) +{ + struct atmel_nand_controller *nc; + + nc = to_nand_controller(nand->base.controller); + + /* + * If the controller supports DMA, the buffer address is DMA-able and + * len is long enough to make DMA transfers profitable, let's trigger + * a DMA transfer. If it fails, fallback to PIO mode. + */ + if (nc->dmac && virt_addr_valid(buf) && + len >= MIN_DMA_LEN && !force_8bit && + !atmel_nand_dma_transfer(nc, buf, nand->activecs->io.dma, len, + DMA_FROM_DEVICE)) + return; + + if ((nand->base.options & NAND_BUSWIDTH_16) && !force_8bit) + ioread16_rep(nand->activecs->io.virt, buf, len / 2); + else + ioread8_rep(nand->activecs->io.virt, buf, len); +} + +static void atmel_nand_data_out(struct atmel_nand *nand, const void *buf, + unsigned int len, bool force_8bit) +{ + struct atmel_nand_controller *nc; + + nc = to_nand_controller(nand->base.controller); + + /* + * If the controller supports DMA, the buffer address is DMA-able and + * len is long enough to make DMA transfers profitable, let's trigger + * a DMA transfer. If it fails, fallback to PIO mode. + */ + if (nc->dmac && virt_addr_valid(buf) && + len >= MIN_DMA_LEN && !force_8bit && + !atmel_nand_dma_transfer(nc, (void *)buf, nand->activecs->io.dma, + len, DMA_TO_DEVICE)) + return; + + if ((nand->base.options & NAND_BUSWIDTH_16) && !force_8bit) + iowrite16_rep(nand->activecs->io.virt, buf, len / 2); + else + iowrite8_rep(nand->activecs->io.virt, buf, len); +} + +static int atmel_nand_waitrdy(struct atmel_nand *nand, unsigned int timeout_ms) +{ + if (nand->activecs->rb.type == ATMEL_NAND_NO_RB) + return nand_soft_waitrdy(&nand->base, timeout_ms); + + return nand_gpio_waitrdy(&nand->base, nand->activecs->rb.gpio, + timeout_ms); +} + +static int atmel_hsmc_nand_waitrdy(struct atmel_nand *nand, + unsigned int timeout_ms) +{ + struct atmel_hsmc_nand_controller *nc; + u32 status, mask; + + if (nand->activecs->rb.type != ATMEL_NAND_NATIVE_RB) + return atmel_nand_waitrdy(nand, timeout_ms); + + nc = to_hsmc_nand_controller(nand->base.controller); + mask = ATMEL_HSMC_NFC_SR_RBEDGE(nand->activecs->rb.id); + return regmap_read_poll_timeout_atomic(nc->base.smc, ATMEL_HSMC_NFC_SR, + status, status & mask, + 10, timeout_ms * 1000); +} + +static void atmel_nand_select_target(struct atmel_nand *nand, + unsigned int cs) +{ + nand->activecs = &nand->cs[cs]; +} + +static void atmel_hsmc_nand_select_target(struct atmel_nand *nand, + unsigned int cs) +{ + struct mtd_info *mtd = nand_to_mtd(&nand->base); + struct atmel_hsmc_nand_controller *nc; + u32 cfg = ATMEL_HSMC_NFC_CFG_PAGESIZE(mtd->writesize) | + ATMEL_HSMC_NFC_CFG_SPARESIZE(mtd->oobsize) | + ATMEL_HSMC_NFC_CFG_RSPARE; + + nand->activecs = &nand->cs[cs]; + nc = to_hsmc_nand_controller(nand->base.controller); + if (nc->cfg == cfg) + return; + + regmap_update_bits(nc->base.smc, ATMEL_HSMC_NFC_CFG, + ATMEL_HSMC_NFC_CFG_PAGESIZE_MASK | + ATMEL_HSMC_NFC_CFG_SPARESIZE_MASK | + ATMEL_HSMC_NFC_CFG_RSPARE | + ATMEL_HSMC_NFC_CFG_WSPARE, + cfg); + nc->cfg = cfg; +} + +static int atmel_smc_nand_exec_instr(struct atmel_nand *nand, + const struct nand_op_instr *instr) +{ + struct atmel_nand_controller *nc; + unsigned int i; + + nc = to_nand_controller(nand->base.controller); + switch (instr->type) { + case NAND_OP_CMD_INSTR: + writeb(instr->ctx.cmd.opcode, + nand->activecs->io.virt + nc->caps->cle_offs); + return 0; + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) + writeb(instr->ctx.addr.addrs[i], + nand->activecs->io.virt + nc->caps->ale_offs); + return 0; + case NAND_OP_DATA_IN_INSTR: + atmel_nand_data_in(nand, instr->ctx.data.buf.in, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + return 0; + case NAND_OP_DATA_OUT_INSTR: + atmel_nand_data_out(nand, instr->ctx.data.buf.out, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + return 0; + case NAND_OP_WAITRDY_INSTR: + return atmel_nand_waitrdy(nand, + instr->ctx.waitrdy.timeout_ms); + default: + break; + } + + return -EINVAL; +} + +static int atmel_smc_nand_exec_op(struct atmel_nand *nand, + const struct nand_operation *op, + bool check_only) +{ + unsigned int i; + int ret = 0; + + if (check_only) + return 0; + + atmel_nand_select_target(nand, op->cs); + gpiod_set_value(nand->activecs->csgpio, 0); + for (i = 0; i < op->ninstrs; i++) { + ret = atmel_smc_nand_exec_instr(nand, &op->instrs[i]); + if (ret) + break; + } + gpiod_set_value(nand->activecs->csgpio, 1); + + return ret; +} + +static int atmel_hsmc_exec_cmd_addr(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct atmel_nand *nand = to_atmel_nand(chip); + struct atmel_hsmc_nand_controller *nc; + unsigned int i, j; + + nc = to_hsmc_nand_controller(chip->controller); + + nc->op.cs = nand->activecs->id; + for (i = 0; i < subop->ninstrs; i++) { + const struct nand_op_instr *instr = &subop->instrs[i]; + + if (instr->type == NAND_OP_CMD_INSTR) { + nc->op.cmds[nc->op.ncmds++] = instr->ctx.cmd.opcode; + continue; + } + + for (j = nand_subop_get_addr_start_off(subop, i); + j < nand_subop_get_num_addr_cyc(subop, i); j++) { + nc->op.addrs[nc->op.naddrs] = instr->ctx.addr.addrs[j]; + nc->op.naddrs++; + } + } + + return atmel_nfc_exec_op(nc, true); +} + +static int atmel_hsmc_exec_rw(struct nand_chip *chip, + const struct nand_subop *subop) +{ + const struct nand_op_instr *instr = subop->instrs; + struct atmel_nand *nand = to_atmel_nand(chip); + + if (instr->type == NAND_OP_DATA_IN_INSTR) + atmel_nand_data_in(nand, instr->ctx.data.buf.in, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + else + atmel_nand_data_out(nand, instr->ctx.data.buf.out, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + + return 0; +} + +static int atmel_hsmc_exec_waitrdy(struct nand_chip *chip, + const struct nand_subop *subop) +{ + const struct nand_op_instr *instr = subop->instrs; + struct atmel_nand *nand = to_atmel_nand(chip); + + return atmel_hsmc_nand_waitrdy(nand, instr->ctx.waitrdy.timeout_ms); +} + +static const struct nand_op_parser atmel_hsmc_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN(atmel_hsmc_exec_cmd_addr, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 5), + NAND_OP_PARSER_PAT_CMD_ELEM(true)), + NAND_OP_PARSER_PATTERN(atmel_hsmc_exec_rw, + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 0)), + NAND_OP_PARSER_PATTERN(atmel_hsmc_exec_rw, + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 0)), + NAND_OP_PARSER_PATTERN(atmel_hsmc_exec_waitrdy, + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), +); + +static int atmel_hsmc_nand_exec_op(struct atmel_nand *nand, + const struct nand_operation *op, + bool check_only) +{ + int ret; + + if (check_only) + return nand_op_parser_exec_op(&nand->base, + &atmel_hsmc_op_parser, op, true); + + atmel_hsmc_nand_select_target(nand, op->cs); + ret = nand_op_parser_exec_op(&nand->base, &atmel_hsmc_op_parser, op, + false); + + return ret; +} + +static void atmel_nfc_copy_to_sram(struct nand_chip *chip, const u8 *buf, + bool oob_required) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_hsmc_nand_controller *nc; + int ret = -EIO; + + nc = to_hsmc_nand_controller(chip->controller); + + if (nc->base.dmac) + ret = atmel_nand_dma_transfer(&nc->base, (void *)buf, + nc->sram.dma, mtd->writesize, + DMA_TO_DEVICE); + + /* Falling back to CPU copy. */ + if (ret) + memcpy_toio(nc->sram.virt, buf, mtd->writesize); + + if (oob_required) + memcpy_toio(nc->sram.virt + mtd->writesize, chip->oob_poi, + mtd->oobsize); +} + +static void atmel_nfc_copy_from_sram(struct nand_chip *chip, u8 *buf, + bool oob_required) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_hsmc_nand_controller *nc; + int ret = -EIO; + + nc = to_hsmc_nand_controller(chip->controller); + + if (nc->base.dmac) + ret = atmel_nand_dma_transfer(&nc->base, buf, nc->sram.dma, + mtd->writesize, DMA_FROM_DEVICE); + + /* Falling back to CPU copy. */ + if (ret) + memcpy_fromio(buf, nc->sram.virt, mtd->writesize); + + if (oob_required) + memcpy_fromio(chip->oob_poi, nc->sram.virt + mtd->writesize, + mtd->oobsize); +} + +static void atmel_nfc_set_op_addr(struct nand_chip *chip, int page, int column) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_hsmc_nand_controller *nc; + + nc = to_hsmc_nand_controller(chip->controller); + + if (column >= 0) { + nc->op.addrs[nc->op.naddrs++] = column; + + /* + * 2 address cycles for the column offset on large page NANDs. + */ + if (mtd->writesize > 512) + nc->op.addrs[nc->op.naddrs++] = column >> 8; + } + + if (page >= 0) { + nc->op.addrs[nc->op.naddrs++] = page; + nc->op.addrs[nc->op.naddrs++] = page >> 8; + + if (chip->options & NAND_ROW_ADDR_3) + nc->op.addrs[nc->op.naddrs++] = page >> 16; + } +} + +static int atmel_nand_pmecc_enable(struct nand_chip *chip, int op, bool raw) +{ + struct atmel_nand *nand = to_atmel_nand(chip); + struct atmel_nand_controller *nc; + int ret; + + nc = to_nand_controller(chip->controller); + + if (raw) + return 0; + + ret = atmel_pmecc_enable(nand->pmecc, op); + if (ret) + dev_err(nc->dev, + "Failed to enable ECC engine (err = %d)\n", ret); + + return ret; +} + +static void atmel_nand_pmecc_disable(struct nand_chip *chip, bool raw) +{ + struct atmel_nand *nand = to_atmel_nand(chip); + + if (!raw) + atmel_pmecc_disable(nand->pmecc); +} + +static int atmel_nand_pmecc_generate_eccbytes(struct nand_chip *chip, bool raw) +{ + struct atmel_nand *nand = to_atmel_nand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_nand_controller *nc; + struct mtd_oob_region oobregion; + void *eccbuf; + int ret, i; + + nc = to_nand_controller(chip->controller); + + if (raw) + return 0; + + ret = atmel_pmecc_wait_rdy(nand->pmecc); + if (ret) { + dev_err(nc->dev, + "Failed to transfer NAND page data (err = %d)\n", + ret); + return ret; + } + + mtd_ooblayout_ecc(mtd, 0, &oobregion); + eccbuf = chip->oob_poi + oobregion.offset; + + for (i = 0; i < chip->ecc.steps; i++) { + atmel_pmecc_get_generated_eccbytes(nand->pmecc, i, + eccbuf); + eccbuf += chip->ecc.bytes; + } + + return 0; +} + +static int atmel_nand_pmecc_correct_data(struct nand_chip *chip, void *buf, + bool raw) +{ + struct atmel_nand *nand = to_atmel_nand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_nand_controller *nc; + struct mtd_oob_region oobregion; + int ret, i, max_bitflips = 0; + void *databuf, *eccbuf; + + nc = to_nand_controller(chip->controller); + + if (raw) + return 0; + + ret = atmel_pmecc_wait_rdy(nand->pmecc); + if (ret) { + dev_err(nc->dev, + "Failed to read NAND page data (err = %d)\n", + ret); + return ret; + } + + mtd_ooblayout_ecc(mtd, 0, &oobregion); + eccbuf = chip->oob_poi + oobregion.offset; + databuf = buf; + + for (i = 0; i < chip->ecc.steps; i++) { + ret = atmel_pmecc_correct_sector(nand->pmecc, i, databuf, + eccbuf); + if (ret < 0 && !atmel_pmecc_correct_erased_chunks(nand->pmecc)) + ret = nand_check_erased_ecc_chunk(databuf, + chip->ecc.size, + eccbuf, + chip->ecc.bytes, + NULL, 0, + chip->ecc.strength); + + if (ret >= 0) { + mtd->ecc_stats.corrected += ret; + max_bitflips = max(ret, max_bitflips); + } else { + mtd->ecc_stats.failed++; + } + + databuf += chip->ecc.size; + eccbuf += chip->ecc.bytes; + } + + return max_bitflips; +} + +static int atmel_nand_pmecc_write_pg(struct nand_chip *chip, const u8 *buf, + bool oob_required, int page, bool raw) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_nand *nand = to_atmel_nand(chip); + int ret; + + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + + ret = atmel_nand_pmecc_enable(chip, NAND_ECC_WRITE, raw); + if (ret) + return ret; + + nand_write_data_op(chip, buf, mtd->writesize, false); + + ret = atmel_nand_pmecc_generate_eccbytes(chip, raw); + if (ret) { + atmel_pmecc_disable(nand->pmecc); + return ret; + } + + atmel_nand_pmecc_disable(chip, raw); + + nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false); + + return nand_prog_page_end_op(chip); +} + +static int atmel_nand_pmecc_write_page(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + return atmel_nand_pmecc_write_pg(chip, buf, oob_required, page, false); +} + +static int atmel_nand_pmecc_write_page_raw(struct nand_chip *chip, + const u8 *buf, int oob_required, + int page) +{ + return atmel_nand_pmecc_write_pg(chip, buf, oob_required, page, true); +} + +static int atmel_nand_pmecc_read_pg(struct nand_chip *chip, u8 *buf, + bool oob_required, int page, bool raw) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + nand_read_page_op(chip, page, 0, NULL, 0); + + ret = atmel_nand_pmecc_enable(chip, NAND_ECC_READ, raw); + if (ret) + return ret; + + ret = nand_read_data_op(chip, buf, mtd->writesize, false, false); + if (ret) + goto out_disable; + + ret = nand_read_data_op(chip, chip->oob_poi, mtd->oobsize, false, false); + if (ret) + goto out_disable; + + ret = atmel_nand_pmecc_correct_data(chip, buf, raw); + +out_disable: + atmel_nand_pmecc_disable(chip, raw); + + return ret; +} + +static int atmel_nand_pmecc_read_page(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page, false); +} + +static int atmel_nand_pmecc_read_page_raw(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page, true); +} + +static int atmel_hsmc_nand_pmecc_write_pg(struct nand_chip *chip, + const u8 *buf, bool oob_required, + int page, bool raw) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_nand *nand = to_atmel_nand(chip); + struct atmel_hsmc_nand_controller *nc; + int ret; + + atmel_hsmc_nand_select_target(nand, chip->cur_cs); + nc = to_hsmc_nand_controller(chip->controller); + + atmel_nfc_copy_to_sram(chip, buf, false); + + nc->op.cmds[0] = NAND_CMD_SEQIN; + nc->op.ncmds = 1; + atmel_nfc_set_op_addr(chip, page, 0x0); + nc->op.cs = nand->activecs->id; + nc->op.data = ATMEL_NFC_WRITE_DATA; + + ret = atmel_nand_pmecc_enable(chip, NAND_ECC_WRITE, raw); + if (ret) + return ret; + + ret = atmel_nfc_exec_op(nc, false); + if (ret) { + atmel_nand_pmecc_disable(chip, raw); + dev_err(nc->base.dev, + "Failed to transfer NAND page data (err = %d)\n", + ret); + return ret; + } + + ret = atmel_nand_pmecc_generate_eccbytes(chip, raw); + + atmel_nand_pmecc_disable(chip, raw); + + if (ret) + return ret; + + nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false); + + return nand_prog_page_end_op(chip); +} + +static int atmel_hsmc_nand_pmecc_write_page(struct nand_chip *chip, + const u8 *buf, int oob_required, + int page) +{ + return atmel_hsmc_nand_pmecc_write_pg(chip, buf, oob_required, page, + false); +} + +static int atmel_hsmc_nand_pmecc_write_page_raw(struct nand_chip *chip, + const u8 *buf, + int oob_required, int page) +{ + return atmel_hsmc_nand_pmecc_write_pg(chip, buf, oob_required, page, + true); +} + +static int atmel_hsmc_nand_pmecc_read_pg(struct nand_chip *chip, u8 *buf, + bool oob_required, int page, + bool raw) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct atmel_nand *nand = to_atmel_nand(chip); + struct atmel_hsmc_nand_controller *nc; + int ret; + + atmel_hsmc_nand_select_target(nand, chip->cur_cs); + nc = to_hsmc_nand_controller(chip->controller); + + /* + * Optimized read page accessors only work when the NAND R/B pin is + * connected to a native SoC R/B pin. If that's not the case, fallback + * to the non-optimized one. + */ + if (nand->activecs->rb.type != ATMEL_NAND_NATIVE_RB) + return atmel_nand_pmecc_read_pg(chip, buf, oob_required, page, + raw); + + nc->op.cmds[nc->op.ncmds++] = NAND_CMD_READ0; + + if (mtd->writesize > 512) + nc->op.cmds[nc->op.ncmds++] = NAND_CMD_READSTART; + + atmel_nfc_set_op_addr(chip, page, 0x0); + nc->op.cs = nand->activecs->id; + nc->op.data = ATMEL_NFC_READ_DATA; + + ret = atmel_nand_pmecc_enable(chip, NAND_ECC_READ, raw); + if (ret) + return ret; + + ret = atmel_nfc_exec_op(nc, false); + if (ret) { + atmel_nand_pmecc_disable(chip, raw); + dev_err(nc->base.dev, + "Failed to load NAND page data (err = %d)\n", + ret); + return ret; + } + + atmel_nfc_copy_from_sram(chip, buf, true); + + ret = atmel_nand_pmecc_correct_data(chip, buf, raw); + + atmel_nand_pmecc_disable(chip, raw); + + return ret; +} + +static int atmel_hsmc_nand_pmecc_read_page(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + return atmel_hsmc_nand_pmecc_read_pg(chip, buf, oob_required, page, + false); +} + +static int atmel_hsmc_nand_pmecc_read_page_raw(struct nand_chip *chip, + u8 *buf, int oob_required, + int page) +{ + return atmel_hsmc_nand_pmecc_read_pg(chip, buf, oob_required, page, + true); +} + +static int atmel_nand_pmecc_init(struct nand_chip *chip) +{ + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&chip->base); + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_device *nanddev = mtd_to_nanddev(mtd); + struct atmel_nand *nand = to_atmel_nand(chip); + struct atmel_nand_controller *nc; + struct atmel_pmecc_user_req req; + + nc = to_nand_controller(chip->controller); + + if (!nc->pmecc) { + dev_err(nc->dev, "HW ECC not supported\n"); + return -ENOTSUPP; + } + + if (nc->caps->legacy_of_bindings) { + u32 val; + + if (!of_property_read_u32(nc->dev->of_node, "atmel,pmecc-cap", + &val)) + chip->ecc.strength = val; + + if (!of_property_read_u32(nc->dev->of_node, + "atmel,pmecc-sector-size", + &val)) + chip->ecc.size = val; + } + + if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) + req.ecc.strength = ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH; + else if (chip->ecc.strength) + req.ecc.strength = chip->ecc.strength; + else if (requirements->strength) + req.ecc.strength = requirements->strength; + else + req.ecc.strength = ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH; + + if (chip->ecc.size) + req.ecc.sectorsize = chip->ecc.size; + else if (requirements->step_size) + req.ecc.sectorsize = requirements->step_size; + else + req.ecc.sectorsize = ATMEL_PMECC_SECTOR_SIZE_AUTO; + + req.pagesize = mtd->writesize; + req.oobsize = mtd->oobsize; + + if (mtd->writesize <= 512) { + req.ecc.bytes = 4; + req.ecc.ooboffset = 0; + } else { + req.ecc.bytes = mtd->oobsize - 2; + req.ecc.ooboffset = ATMEL_PMECC_OOBOFFSET_AUTO; + } + + nand->pmecc = atmel_pmecc_create_user(nc->pmecc, &req); + if (IS_ERR(nand->pmecc)) + return PTR_ERR(nand->pmecc); + + chip->ecc.algo = NAND_ECC_ALGO_BCH; + chip->ecc.size = req.ecc.sectorsize; + chip->ecc.bytes = req.ecc.bytes / req.ecc.nsectors; + chip->ecc.strength = req.ecc.strength; + + chip->options |= NAND_NO_SUBPAGE_WRITE; + + mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout()); + + return 0; +} + +static int atmel_nand_ecc_init(struct nand_chip *chip) +{ + struct atmel_nand_controller *nc; + int ret; + + nc = to_nand_controller(chip->controller); + + switch (chip->ecc.engine_type) { + case NAND_ECC_ENGINE_TYPE_NONE: + case NAND_ECC_ENGINE_TYPE_SOFT: + /* + * Nothing to do, the core will initialize everything for us. + */ + break; + + case NAND_ECC_ENGINE_TYPE_ON_HOST: + ret = atmel_nand_pmecc_init(chip); + if (ret) + return ret; + + chip->ecc.read_page = atmel_nand_pmecc_read_page; + chip->ecc.write_page = atmel_nand_pmecc_write_page; + chip->ecc.read_page_raw = atmel_nand_pmecc_read_page_raw; + chip->ecc.write_page_raw = atmel_nand_pmecc_write_page_raw; + break; + + default: + /* Other modes are not supported. */ + dev_err(nc->dev, "Unsupported ECC mode: %d\n", + chip->ecc.engine_type); + return -ENOTSUPP; + } + + return 0; +} + +static int atmel_hsmc_nand_ecc_init(struct nand_chip *chip) +{ + int ret; + + ret = atmel_nand_ecc_init(chip); + if (ret) + return ret; + + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return 0; + + /* Adjust the ECC operations for the HSMC IP. */ + chip->ecc.read_page = atmel_hsmc_nand_pmecc_read_page; + chip->ecc.write_page = atmel_hsmc_nand_pmecc_write_page; + chip->ecc.read_page_raw = atmel_hsmc_nand_pmecc_read_page_raw; + chip->ecc.write_page_raw = atmel_hsmc_nand_pmecc_write_page_raw; + + return 0; +} + +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, pulse; + struct atmel_nand_controller *nc; + int ret; + + nc = to_nand_controller(nand->base.controller); + + /* DDR interface not supported. */ + if (!nand_interface_is_sdr(conf)) + return -ENOTSUPP; + + /* + * tRC < 30ns implies EDO mode. This controller does not support this + * mode. + */ + if (conf->timings.sdr.tRC_min < 30000) + return -ENOTSUPP; + + atmel_smc_cs_conf_init(smcconf); + + mckperiodps = NSEC_PER_SEC / clk_get_rate(nc->mck); + mckperiodps *= 1000; + + /* + * Set write pulse timing. This one is easy to extract: + * + * NWE_PULSE = tWP + */ + ncycles = DIV_ROUND_UP(conf->timings.sdr.tWP_min, mckperiodps); + totalcycles = ncycles; + ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NWE_SHIFT, + ncycles); + if (ret) + return ret; + + /* + * The write setup timing depends on the operation done on the NAND. + * All operations goes through the same data bus, but the operation + * type depends on the address we are writing to (ALE/CLE address + * lines). + * Since we have no way to differentiate the different operations at + * the SMC level, we must consider the worst case (the biggest setup + * time among all operation types): + * + * NWE_SETUP = max(tCLS, tCS, tALS, tDS) - NWE_PULSE + */ + timeps = max3(conf->timings.sdr.tCLS_min, conf->timings.sdr.tCS_min, + conf->timings.sdr.tALS_min); + timeps = max(timeps, conf->timings.sdr.tDS_min); + ncycles = DIV_ROUND_UP(timeps, mckperiodps); + ncycles = ncycles > totalcycles ? ncycles - totalcycles : 0; + totalcycles += ncycles; + ret = atmel_smc_cs_conf_set_setup(smcconf, ATMEL_SMC_NWE_SHIFT, + ncycles); + if (ret) + return ret; + + /* + * As for the write setup timing, the write hold timing depends on the + * operation done on the NAND: + * + * NWE_HOLD = max(tCLH, tCH, tALH, tDH, tWH) + */ + timeps = max3(conf->timings.sdr.tCLH_min, conf->timings.sdr.tCH_min, + conf->timings.sdr.tALH_min); + timeps = max3(timeps, conf->timings.sdr.tDH_min, + conf->timings.sdr.tWH_min); + ncycles = DIV_ROUND_UP(timeps, mckperiodps); + totalcycles += ncycles; + + /* + * The write cycle timing is directly matching tWC, but is also + * dependent on the other timings on the setup and hold timings we + * calculated earlier, which gives: + * + * NWE_CYCLE = max(tWC, NWE_SETUP + NWE_PULSE + NWE_HOLD) + */ + ncycles = DIV_ROUND_UP(conf->timings.sdr.tWC_min, mckperiodps); + ncycles = max(totalcycles, ncycles); + ret = atmel_smc_cs_conf_set_cycle(smcconf, ATMEL_SMC_NWE_SHIFT, + ncycles); + if (ret) + return ret; + + /* + * We don't want the CS line to be toggled between each byte/word + * transfer to the NAND. The only way to guarantee that is to have the + * NCS_{WR,RD}_{SETUP,HOLD} timings set to 0, which in turn means: + * + * NCS_WR_PULSE = NWE_CYCLE + */ + ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NCS_WR_SHIFT, + ncycles); + if (ret) + return ret; + + /* + * As for the write setup timing, the read hold timing depends on the + * operation done on the NAND: + * + * NRD_HOLD = max(tREH, tRHOH) + */ + timeps = max(conf->timings.sdr.tREH_min, conf->timings.sdr.tRHOH_min); + ncycles = DIV_ROUND_UP(timeps, mckperiodps); + totalcycles = ncycles; + + /* + * TDF = tRHZ - NRD_HOLD + */ + ncycles = DIV_ROUND_UP(conf->timings.sdr.tRHZ_max, mckperiodps); + ncycles -= totalcycles; + + /* + * In ONFI 4.0 specs, tRHZ has been increased to support EDO NANDs and + * we might end up with a config that does not fit in the TDF field. + * Just take the max value in this case and hope that the NAND is more + * tolerant than advertised. + */ + if (ncycles > ATMEL_SMC_MODE_TDF_MAX) + ncycles = ATMEL_SMC_MODE_TDF_MAX; + else if (ncycles < ATMEL_SMC_MODE_TDF_MIN) + ncycles = ATMEL_SMC_MODE_TDF_MIN; + + smcconf->mode |= ATMEL_SMC_MODE_TDF(ncycles) | + ATMEL_SMC_MODE_TDFMODE_OPTIMIZED; + + /* + * 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 = max(tRP, tREA) + */ + 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); + if (ret) + 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_SETUP + NRD_PULSE + NRD_HOLD) + */ + ncycles = DIV_ROUND_UP(conf->timings.sdr.tRC_min, mckperiodps); + ncycles = max(totalcycles, ncycles); + ret = atmel_smc_cs_conf_set_cycle(smcconf, ATMEL_SMC_NRD_SHIFT, + ncycles); + if (ret) + return ret; + + /* + * We don't want the CS line to be toggled between each byte/word + * transfer from the NAND. The only way to guarantee that is to have + * the NCS_{WR,RD}_{SETUP,HOLD} timings set to 0, which in turn means: + * + * NCS_RD_PULSE = NRD_CYCLE + */ + ret = atmel_smc_cs_conf_set_pulse(smcconf, ATMEL_SMC_NCS_RD_SHIFT, + ncycles); + if (ret) + return ret; + + /* Txxx timings are directly matching tXXX ones. */ + ncycles = DIV_ROUND_UP(conf->timings.sdr.tCLR_min, mckperiodps); + ret = atmel_smc_cs_conf_set_timing(smcconf, + ATMEL_HSMC_TIMINGS_TCLR_SHIFT, + ncycles); + if (ret) + return ret; + + ncycles = DIV_ROUND_UP(conf->timings.sdr.tADL_min, mckperiodps); + ret = atmel_smc_cs_conf_set_timing(smcconf, + ATMEL_HSMC_TIMINGS_TADL_SHIFT, + ncycles); + /* + * Version 4 of the ONFI spec mandates that tADL be at least 400 + * nanoseconds, but, depending on the master clock rate, 400 ns may not + * fit in the tADL field of the SMC reg. We need to relax the check and + * accept the -ERANGE return code. + * + * Note that previous versions of the ONFI spec had a lower tADL_min + * (100 or 200 ns). It's not clear why this timing constraint got + * increased but it seems most NANDs are fine with values lower than + * 400ns, so we should be safe. + */ + if (ret && ret != -ERANGE) + return ret; + + ncycles = DIV_ROUND_UP(conf->timings.sdr.tAR_min, mckperiodps); + ret = atmel_smc_cs_conf_set_timing(smcconf, + ATMEL_HSMC_TIMINGS_TAR_SHIFT, + ncycles); + if (ret) + return ret; + + ncycles = DIV_ROUND_UP(conf->timings.sdr.tRR_min, mckperiodps); + ret = atmel_smc_cs_conf_set_timing(smcconf, + ATMEL_HSMC_TIMINGS_TRR_SHIFT, + ncycles); + if (ret) + return ret; + + ncycles = DIV_ROUND_UP(conf->timings.sdr.tWB_max, mckperiodps); + ret = atmel_smc_cs_conf_set_timing(smcconf, + ATMEL_HSMC_TIMINGS_TWB_SHIFT, + ncycles); + if (ret) + return ret; + + /* Attach the CS line to the NFC logic. */ + smcconf->timings |= ATMEL_HSMC_TIMINGS_NFSEL; + + /* Set the appropriate data bus width. */ + if (nand->base.options & NAND_BUSWIDTH_16) + smcconf->mode |= ATMEL_SMC_MODE_DBW_16; + + /* Operate in NRD/NWE READ/WRITEMODE. */ + smcconf->mode |= ATMEL_SMC_MODE_READMODE_NRD | + ATMEL_SMC_MODE_WRITEMODE_NWE; + + return 0; +} + +static int atmel_smc_nand_setup_interface(struct atmel_nand *nand, + int csline, + const struct nand_interface_config *conf) +{ + struct atmel_nand_controller *nc; + struct atmel_smc_cs_conf smcconf; + struct atmel_nand_cs *cs; + int ret; + + nc = to_nand_controller(nand->base.controller); + + ret = atmel_smc_nand_prepare_smcconf(nand, conf, &smcconf); + if (ret) + return ret; + + if (csline == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + cs = &nand->cs[csline]; + cs->smcconf = smcconf; + atmel_smc_cs_conf_apply(nc->smc, cs->id, &cs->smcconf); + + return 0; +} + +static int atmel_hsmc_nand_setup_interface(struct atmel_nand *nand, + int csline, + const struct nand_interface_config *conf) +{ + struct atmel_hsmc_nand_controller *nc; + struct atmel_smc_cs_conf smcconf; + struct atmel_nand_cs *cs; + int ret; + + nc = to_hsmc_nand_controller(nand->base.controller); + + ret = atmel_smc_nand_prepare_smcconf(nand, conf, &smcconf); + if (ret) + return ret; + + if (csline == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + cs = &nand->cs[csline]; + cs->smcconf = smcconf; + + if (cs->rb.type == ATMEL_NAND_NATIVE_RB) + cs->smcconf.timings |= ATMEL_HSMC_TIMINGS_RBNSEL(cs->rb.id); + + atmel_hsmc_cs_conf_apply(nc->base.smc, nc->hsmc_layout, cs->id, + &cs->smcconf); + + return 0; +} + +static int atmel_nand_setup_interface(struct nand_chip *chip, int csline, + const struct nand_interface_config *conf) +{ + struct atmel_nand *nand = to_atmel_nand(chip); + const struct nand_sdr_timings *sdr; + struct atmel_nand_controller *nc; + + sdr = nand_get_sdr_timings(conf); + if (IS_ERR(sdr)) + return PTR_ERR(sdr); + + nc = to_nand_controller(nand->base.controller); + + if (csline >= nand->numcs || + (csline < 0 && csline != NAND_DATA_IFACE_CHECK_ONLY)) + return -EINVAL; + + return nc->caps->ops->setup_interface(nand, csline, conf); +} + +static int atmel_nand_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + struct atmel_nand *nand = to_atmel_nand(chip); + struct atmel_nand_controller *nc; + + nc = to_nand_controller(nand->base.controller); + + return nc->caps->ops->exec_op(nand, op, check_only); +} + +static void atmel_nand_init(struct atmel_nand_controller *nc, + struct atmel_nand *nand) +{ + struct nand_chip *chip = &nand->base; + struct mtd_info *mtd = nand_to_mtd(chip); + + mtd->dev.parent = nc->dev; + nand->base.controller = &nc->base; + + if (!nc->mck || !nc->caps->ops->setup_interface) + chip->options |= NAND_KEEP_TIMINGS; + + /* + * Use a bounce buffer when the buffer passed by the MTD user is not + * suitable for DMA. + */ + if (nc->dmac) + chip->options |= NAND_USES_DMA; + + /* Default to HW ECC if pmecc is available. */ + if (nc->pmecc) + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; +} + +static void atmel_smc_nand_init(struct atmel_nand_controller *nc, + struct atmel_nand *nand) +{ + struct nand_chip *chip = &nand->base; + struct atmel_smc_nand_controller *smc_nc; + int i; + + atmel_nand_init(nc, nand); + + smc_nc = to_smc_nand_controller(chip->controller); + if (!smc_nc->ebi_csa_regmap) + return; + + /* Attach the CS to the NAND Flash logic. */ + for (i = 0; i < nand->numcs; i++) + regmap_update_bits(smc_nc->ebi_csa_regmap, + smc_nc->ebi_csa->offs, + BIT(nand->cs[i].id), BIT(nand->cs[i].id)); + + if (smc_nc->ebi_csa->nfd0_on_d16) + regmap_update_bits(smc_nc->ebi_csa_regmap, + smc_nc->ebi_csa->offs, + smc_nc->ebi_csa->nfd0_on_d16, + smc_nc->ebi_csa->nfd0_on_d16); +} + +static int atmel_nand_controller_remove_nand(struct atmel_nand *nand) +{ + struct nand_chip *chip = &nand->base; + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + ret = mtd_device_unregister(mtd); + if (ret) + return ret; + + nand_cleanup(chip); + list_del(&nand->node); + + return 0; +} + +static struct atmel_nand *atmel_nand_create(struct atmel_nand_controller *nc, + struct device_node *np, + int reg_cells) +{ + struct atmel_nand *nand; + struct gpio_desc *gpio; + int numcs, ret, i; + + numcs = of_property_count_elems_of_size(np, "reg", + reg_cells * sizeof(u32)); + if (numcs < 1) { + dev_err(nc->dev, "Missing or invalid reg property\n"); + return ERR_PTR(-EINVAL); + } + + nand = devm_kzalloc(nc->dev, struct_size(nand, cs, numcs), GFP_KERNEL); + if (!nand) + return ERR_PTR(-ENOMEM); + + nand->numcs = numcs; + + gpio = devm_fwnode_gpiod_get(nc->dev, of_fwnode_handle(np), + "det", GPIOD_IN, "nand-det"); + if (IS_ERR(gpio) && PTR_ERR(gpio) != -ENOENT) { + dev_err(nc->dev, + "Failed to get detect gpio (err = %ld)\n", + PTR_ERR(gpio)); + return ERR_CAST(gpio); + } + + if (!IS_ERR(gpio)) + nand->cdgpio = gpio; + + for (i = 0; i < numcs; i++) { + struct resource res; + u32 val; + + ret = of_address_to_resource(np, 0, &res); + if (ret) { + dev_err(nc->dev, "Invalid reg property (err = %d)\n", + ret); + return ERR_PTR(ret); + } + + ret = of_property_read_u32_index(np, "reg", i * reg_cells, + &val); + if (ret) { + dev_err(nc->dev, "Invalid reg property (err = %d)\n", + ret); + return ERR_PTR(ret); + } + + nand->cs[i].id = val; + + nand->cs[i].io.dma = res.start; + nand->cs[i].io.virt = devm_ioremap_resource(nc->dev, &res); + if (IS_ERR(nand->cs[i].io.virt)) + return ERR_CAST(nand->cs[i].io.virt); + + if (!of_property_read_u32(np, "atmel,rb", &val)) { + if (val > ATMEL_NFC_MAX_RB_ID) + return ERR_PTR(-EINVAL); + + nand->cs[i].rb.type = ATMEL_NAND_NATIVE_RB; + nand->cs[i].rb.id = val; + } else { + gpio = devm_fwnode_gpiod_get_index(nc->dev, + of_fwnode_handle(np), + "rb", i, GPIOD_IN, + "nand-rb"); + if (IS_ERR(gpio) && PTR_ERR(gpio) != -ENOENT) { + dev_err(nc->dev, + "Failed to get R/B gpio (err = %ld)\n", + PTR_ERR(gpio)); + return ERR_CAST(gpio); + } + + if (!IS_ERR(gpio)) { + nand->cs[i].rb.type = ATMEL_NAND_GPIO_RB; + nand->cs[i].rb.gpio = gpio; + } + } + + gpio = devm_fwnode_gpiod_get_index(nc->dev, + of_fwnode_handle(np), + "cs", i, GPIOD_OUT_HIGH, + "nand-cs"); + if (IS_ERR(gpio) && PTR_ERR(gpio) != -ENOENT) { + dev_err(nc->dev, + "Failed to get CS gpio (err = %ld)\n", + PTR_ERR(gpio)); + return ERR_CAST(gpio); + } + + if (!IS_ERR(gpio)) + nand->cs[i].csgpio = gpio; + } + + nand_set_flash_node(&nand->base, np); + + return nand; +} + +static int +atmel_nand_controller_add_nand(struct atmel_nand_controller *nc, + struct atmel_nand *nand) +{ + struct nand_chip *chip = &nand->base; + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + /* No card inserted, skip this NAND. */ + if (nand->cdgpio && gpiod_get_value(nand->cdgpio)) { + dev_info(nc->dev, "No SmartMedia card inserted.\n"); + return 0; + } + + nc->caps->ops->nand_init(nc, nand); + + ret = nand_scan(chip, nand->numcs); + if (ret) { + dev_err(nc->dev, "NAND scan failed: %d\n", ret); + return ret; + } + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(nc->dev, "Failed to register mtd device: %d\n", ret); + nand_cleanup(chip); + return ret; + } + + list_add_tail(&nand->node, &nc->chips); + + return 0; +} + +static int +atmel_nand_controller_remove_nands(struct atmel_nand_controller *nc) +{ + struct atmel_nand *nand, *tmp; + int ret; + + list_for_each_entry_safe(nand, tmp, &nc->chips, node) { + ret = atmel_nand_controller_remove_nand(nand); + if (ret) + return ret; + } + + return 0; +} + +static int +atmel_nand_controller_legacy_add_nands(struct atmel_nand_controller *nc) +{ + struct device *dev = nc->dev; + struct platform_device *pdev = to_platform_device(dev); + struct atmel_nand *nand; + struct gpio_desc *gpio; + struct resource *res; + + /* + * Legacy bindings only allow connecting a single NAND with a unique CS + * line to the controller. + */ + nand = devm_kzalloc(nc->dev, struct_size(nand, cs, 1), GFP_KERNEL); + if (!nand) + return -ENOMEM; + + nand->numcs = 1; + + nand->cs[0].io.virt = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(nand->cs[0].io.virt)) + return PTR_ERR(nand->cs[0].io.virt); + + nand->cs[0].io.dma = res->start; + + /* + * The old driver was hardcoding the CS id to 3 for all sama5 + * controllers. Since this id is only meaningful for the sama5 + * controller we can safely assign this id to 3 no matter the + * controller. + * If one wants to connect a NAND to a different CS line, he will + * have to use the new bindings. + */ + nand->cs[0].id = 3; + + /* R/B GPIO. */ + gpio = devm_gpiod_get_index_optional(dev, NULL, 0, GPIOD_IN); + if (IS_ERR(gpio)) { + dev_err(dev, "Failed to get R/B gpio (err = %ld)\n", + PTR_ERR(gpio)); + return PTR_ERR(gpio); + } + + if (gpio) { + nand->cs[0].rb.type = ATMEL_NAND_GPIO_RB; + nand->cs[0].rb.gpio = gpio; + } + + /* CS GPIO. */ + gpio = devm_gpiod_get_index_optional(dev, NULL, 1, GPIOD_OUT_HIGH); + if (IS_ERR(gpio)) { + dev_err(dev, "Failed to get CS gpio (err = %ld)\n", + PTR_ERR(gpio)); + return PTR_ERR(gpio); + } + + nand->cs[0].csgpio = gpio; + + /* Card detect GPIO. */ + gpio = devm_gpiod_get_index_optional(nc->dev, NULL, 2, GPIOD_IN); + if (IS_ERR(gpio)) { + dev_err(dev, + "Failed to get detect gpio (err = %ld)\n", + PTR_ERR(gpio)); + return PTR_ERR(gpio); + } + + nand->cdgpio = gpio; + + nand_set_flash_node(&nand->base, nc->dev->of_node); + + return atmel_nand_controller_add_nand(nc, nand); +} + +static int atmel_nand_controller_add_nands(struct atmel_nand_controller *nc) +{ + struct device_node *np; + struct device *dev = nc->dev; + int ret, reg_cells; + u32 val; + + /* We do not retrieve the SMC syscon when parsing old DTs. */ + if (nc->caps->legacy_of_bindings) + return atmel_nand_controller_legacy_add_nands(nc); + + np = dev->of_node; + + ret = of_property_read_u32(np, "#address-cells", &val); + if (ret) { + dev_err(dev, "missing #address-cells property\n"); + return ret; + } + + reg_cells = val; + + ret = of_property_read_u32(np, "#size-cells", &val); + if (ret) { + dev_err(dev, "missing #size-cells property\n"); + return ret; + } + + reg_cells += val; + + for_each_child_of_node_scoped(np, nand_np) { + struct atmel_nand *nand; + + nand = atmel_nand_create(nc, nand_np, reg_cells); + if (IS_ERR(nand)) { + ret = PTR_ERR(nand); + goto err; + } + + ret = atmel_nand_controller_add_nand(nc, nand); + if (ret) + goto err; + } + + return 0; + +err: + atmel_nand_controller_remove_nands(nc); + + return ret; +} + +static void atmel_nand_controller_cleanup(struct atmel_nand_controller *nc) +{ + if (nc->dmac) + dma_release_channel(nc->dmac); + + clk_put(nc->mck); +} + +static const struct atmel_smc_nand_ebi_csa_cfg at91sam9260_ebi_csa = { + .offs = AT91SAM9260_MATRIX_EBICSA, +}; + +static const struct atmel_smc_nand_ebi_csa_cfg at91sam9261_ebi_csa = { + .offs = AT91SAM9261_MATRIX_EBICSA, +}; + +static const struct atmel_smc_nand_ebi_csa_cfg at91sam9263_ebi_csa = { + .offs = AT91SAM9263_MATRIX_EBI0CSA, +}; + +static const struct atmel_smc_nand_ebi_csa_cfg at91sam9rl_ebi_csa = { + .offs = AT91SAM9RL_MATRIX_EBICSA, +}; + +static const struct atmel_smc_nand_ebi_csa_cfg at91sam9g45_ebi_csa = { + .offs = AT91SAM9G45_MATRIX_EBICSA, +}; + +static const struct atmel_smc_nand_ebi_csa_cfg at91sam9n12_ebi_csa = { + .offs = AT91SAM9N12_MATRIX_EBICSA, +}; + +static const struct atmel_smc_nand_ebi_csa_cfg at91sam9x5_ebi_csa = { + .offs = AT91SAM9X5_MATRIX_EBICSA, +}; + +static const struct atmel_smc_nand_ebi_csa_cfg sam9x60_ebi_csa = { + .offs = AT91_SFR_CCFG_EBICSA, + .nfd0_on_d16 = AT91_SFR_CCFG_NFD0_ON_D16, +}; + +static const struct of_device_id __maybe_unused atmel_ebi_csa_regmap_of_ids[] = { + { + .compatible = "atmel,at91sam9260-matrix", + .data = &at91sam9260_ebi_csa, + }, + { + .compatible = "atmel,at91sam9261-matrix", + .data = &at91sam9261_ebi_csa, + }, + { + .compatible = "atmel,at91sam9263-matrix", + .data = &at91sam9263_ebi_csa, + }, + { + .compatible = "atmel,at91sam9rl-matrix", + .data = &at91sam9rl_ebi_csa, + }, + { + .compatible = "atmel,at91sam9g45-matrix", + .data = &at91sam9g45_ebi_csa, + }, + { + .compatible = "atmel,at91sam9n12-matrix", + .data = &at91sam9n12_ebi_csa, + }, + { + .compatible = "atmel,at91sam9x5-matrix", + .data = &at91sam9x5_ebi_csa, + }, + { + .compatible = "microchip,sam9x60-sfr", + .data = &sam9x60_ebi_csa, + }, + { /* sentinel */ }, +}; + +static int atmel_nand_attach_chip(struct nand_chip *chip) +{ + struct atmel_nand_controller *nc = to_nand_controller(chip->controller); + struct atmel_nand *nand = to_atmel_nand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + ret = nc->caps->ops->ecc_init(chip); + if (ret) + return ret; + + if (nc->caps->legacy_of_bindings || !nc->dev->of_node) { + /* + * We keep the MTD name unchanged to avoid breaking platforms + * where the MTD cmdline parser is used and the bootloader + * has not been updated to use the new naming scheme. + */ + mtd->name = "atmel_nand"; + } else if (!mtd->name) { + /* + * If the new bindings are used and the bootloader has not been + * updated to pass a new mtdparts parameter on the cmdline, you + * should define the following property in your nand node: + * + * label = "atmel_nand"; + * + * This way, mtd->name will be set by the core when + * nand_set_flash_node() is called. + */ + mtd->name = devm_kasprintf(nc->dev, GFP_KERNEL, + "%s:nand.%d", dev_name(nc->dev), + nand->cs[0].id); + if (!mtd->name) { + dev_err(nc->dev, "Failed to allocate mtd->name\n"); + return -ENOMEM; + } + } + + return 0; +} + +static const struct nand_controller_ops atmel_nand_controller_ops = { + .attach_chip = atmel_nand_attach_chip, + .setup_interface = atmel_nand_setup_interface, + .exec_op = atmel_nand_exec_op, +}; + +static int atmel_nand_controller_init(struct atmel_nand_controller *nc, + struct platform_device *pdev, + const struct atmel_nand_controller_caps *caps) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + int ret; + + nand_controller_init(&nc->base); + nc->base.ops = &atmel_nand_controller_ops; + INIT_LIST_HEAD(&nc->chips); + nc->dev = dev; + nc->caps = caps; + + platform_set_drvdata(pdev, nc); + + nc->pmecc = devm_atmel_pmecc_get(dev); + if (IS_ERR(nc->pmecc)) + return dev_err_probe(dev, PTR_ERR(nc->pmecc), + "Could not get PMECC object\n"); + + if (nc->caps->has_dma && !atmel_nand_avoid_dma) { + dma_cap_mask_t mask; + + dma_cap_zero(mask); + dma_cap_set(DMA_MEMCPY, mask); + + nc->dmac = dma_request_channel(mask, NULL, NULL); + if (nc->dmac) + dev_info(nc->dev, "using %s for DMA transfers\n", + dma_chan_name(nc->dmac)); + else + dev_err(nc->dev, "Failed to request DMA channel\n"); + } + + /* We do not retrieve the SMC syscon when parsing old DTs. */ + if (nc->caps->legacy_of_bindings) + return 0; + + nc->mck = of_clk_get(dev->parent->of_node, 0); + if (IS_ERR(nc->mck)) { + dev_err(dev, "Failed to retrieve MCK clk\n"); + ret = PTR_ERR(nc->mck); + goto out_release_dma; + } + + np = of_parse_phandle(dev->parent->of_node, "atmel,smc", 0); + if (!np) { + dev_err(dev, "Missing or invalid atmel,smc property\n"); + ret = -EINVAL; + goto out_release_dma; + } + + nc->smc = syscon_node_to_regmap(np); + of_node_put(np); + if (IS_ERR(nc->smc)) { + ret = PTR_ERR(nc->smc); + dev_err(dev, "Could not get SMC regmap (err = %d)\n", ret); + goto out_release_dma; + } + + return 0; + +out_release_dma: + if (nc->dmac) + dma_release_channel(nc->dmac); + + return ret; +} + +static int +atmel_smc_nand_controller_init(struct atmel_smc_nand_controller *nc) +{ + struct device *dev = nc->base.dev; + const struct of_device_id *match; + struct device_node *np; + int ret; + + /* We do not retrieve the EBICSA regmap when parsing old DTs. */ + if (nc->base.caps->legacy_of_bindings) + return 0; + + np = of_parse_phandle(dev->parent->of_node, + nc->base.caps->ebi_csa_regmap_name, 0); + if (!np) + return 0; + + match = of_match_node(atmel_ebi_csa_regmap_of_ids, np); + if (!match) { + of_node_put(np); + return 0; + } + + nc->ebi_csa_regmap = syscon_node_to_regmap(np); + of_node_put(np); + if (IS_ERR(nc->ebi_csa_regmap)) { + ret = PTR_ERR(nc->ebi_csa_regmap); + dev_err(dev, "Could not get EBICSA regmap (err = %d)\n", ret); + return ret; + } + + nc->ebi_csa = (struct atmel_smc_nand_ebi_csa_cfg *)match->data; + + /* + * The at91sam9263 has 2 EBIs, if the NAND controller is under EBI1 + * add 4 to ->ebi_csa->offs. + */ + if (of_device_is_compatible(dev->parent->of_node, + "atmel,at91sam9263-ebi1")) + nc->ebi_csa->offs += 4; + + return 0; +} + +static int +atmel_hsmc_nand_controller_legacy_init(struct atmel_hsmc_nand_controller *nc) +{ + struct regmap_config regmap_conf = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + }; + + struct device *dev = nc->base.dev; + struct device_node *nand_np, *nfc_np; + void __iomem *iomem; + struct resource res; + int ret; + + nand_np = dev->of_node; + nfc_np = of_get_compatible_child(dev->of_node, "atmel,sama5d3-nfc"); + if (!nfc_np) { + dev_err(dev, "Could not find device node for sama5d3-nfc\n"); + return -ENODEV; + } + + nc->clk = of_clk_get(nfc_np, 0); + if (IS_ERR(nc->clk)) { + ret = PTR_ERR(nc->clk); + dev_err(dev, "Failed to retrieve HSMC clock (err = %d)\n", + ret); + goto out; + } + + ret = clk_prepare_enable(nc->clk); + if (ret) { + dev_err(dev, "Failed to enable the HSMC clock (err = %d)\n", + ret); + goto out; + } + + nc->irq = of_irq_get(nand_np, 0); + if (nc->irq <= 0) { + ret = nc->irq ?: -ENXIO; + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get IRQ number (err = %d)\n", + ret); + goto out; + } + + ret = of_address_to_resource(nfc_np, 0, &res); + if (ret) { + dev_err(dev, "Invalid or missing NFC IO resource (err = %d)\n", + ret); + goto out; + } + + iomem = devm_ioremap_resource(dev, &res); + if (IS_ERR(iomem)) { + ret = PTR_ERR(iomem); + goto out; + } + + regmap_conf.name = "nfc-io"; + regmap_conf.max_register = resource_size(&res) - 4; + nc->io = devm_regmap_init_mmio(dev, iomem, ®map_conf); + if (IS_ERR(nc->io)) { + ret = PTR_ERR(nc->io); + dev_err(dev, "Could not create NFC IO regmap (err = %d)\n", + ret); + goto out; + } + + ret = of_address_to_resource(nfc_np, 1, &res); + if (ret) { + dev_err(dev, "Invalid or missing HSMC resource (err = %d)\n", + ret); + goto out; + } + + iomem = devm_ioremap_resource(dev, &res); + if (IS_ERR(iomem)) { + ret = PTR_ERR(iomem); + goto out; + } + + regmap_conf.name = "smc"; + regmap_conf.max_register = resource_size(&res) - 4; + nc->base.smc = devm_regmap_init_mmio(dev, iomem, ®map_conf); + if (IS_ERR(nc->base.smc)) { + ret = PTR_ERR(nc->base.smc); + dev_err(dev, "Could not create NFC IO regmap (err = %d)\n", + ret); + goto out; + } + + ret = of_address_to_resource(nfc_np, 2, &res); + if (ret) { + dev_err(dev, "Invalid or missing SRAM resource (err = %d)\n", + ret); + goto out; + } + + nc->sram.virt = devm_ioremap_resource(dev, &res); + if (IS_ERR(nc->sram.virt)) { + ret = PTR_ERR(nc->sram.virt); + goto out; + } + + nc->sram.dma = res.start; + +out: + of_node_put(nfc_np); + + return ret; +} + +static int +atmel_hsmc_nand_controller_init(struct atmel_hsmc_nand_controller *nc) +{ + struct device *dev = nc->base.dev; + struct device_node *np; + int ret; + + np = of_parse_phandle(dev->parent->of_node, "atmel,smc", 0); + if (!np) { + dev_err(dev, "Missing or invalid atmel,smc property\n"); + return -EINVAL; + } + + nc->hsmc_layout = atmel_hsmc_get_reg_layout(np); + + nc->irq = of_irq_get(np, 0); + of_node_put(np); + if (nc->irq <= 0) { + ret = nc->irq ?: -ENXIO; + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get IRQ number (err = %d)\n", + ret); + return ret; + } + + np = of_parse_phandle(dev->of_node, "atmel,nfc-io", 0); + if (!np) { + dev_err(dev, "Missing or invalid atmel,nfc-io property\n"); + return -EINVAL; + } + + nc->io = syscon_node_to_regmap(np); + of_node_put(np); + if (IS_ERR(nc->io)) { + ret = PTR_ERR(nc->io); + dev_err(dev, "Could not get NFC IO regmap (err = %d)\n", ret); + return ret; + } + + nc->sram.pool = of_gen_pool_get(nc->base.dev->of_node, + "atmel,nfc-sram", 0); + 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, + &nc->sram.dma); + if (!nc->sram.virt) { + dev_err(nc->base.dev, + "Could not allocate memory from the NFC SRAM pool\n"); + return -ENOMEM; + } + + return 0; +} + +static int +atmel_hsmc_nand_controller_remove(struct atmel_nand_controller *nc) +{ + struct atmel_hsmc_nand_controller *hsmc_nc; + int ret; + + ret = atmel_nand_controller_remove_nands(nc); + if (ret) + return ret; + + hsmc_nc = container_of(nc, struct atmel_hsmc_nand_controller, base); + regmap_write(hsmc_nc->base.smc, ATMEL_HSMC_NFC_CTRL, + ATMEL_HSMC_NFC_CTRL_DIS); + + if (hsmc_nc->sram.pool) + gen_pool_free(hsmc_nc->sram.pool, + (unsigned long)hsmc_nc->sram.virt, + ATMEL_NFC_SRAM_SIZE); + + if (hsmc_nc->clk) { + clk_disable_unprepare(hsmc_nc->clk); + clk_put(hsmc_nc->clk); + } + + atmel_nand_controller_cleanup(nc); + + return 0; +} + +static int atmel_hsmc_nand_controller_probe(struct platform_device *pdev, + const struct atmel_nand_controller_caps *caps) +{ + struct device *dev = &pdev->dev; + struct atmel_hsmc_nand_controller *nc; + int ret; + + nc = devm_kzalloc(dev, sizeof(*nc), GFP_KERNEL); + if (!nc) + return -ENOMEM; + + ret = atmel_nand_controller_init(&nc->base, pdev, caps); + if (ret) + return ret; + + if (caps->legacy_of_bindings) + ret = atmel_hsmc_nand_controller_legacy_init(nc); + else + ret = atmel_hsmc_nand_controller_init(nc); + + if (ret) + return ret; + + /* Make sure all irqs are masked before registering our IRQ handler. */ + regmap_write(nc->base.smc, ATMEL_HSMC_NFC_IDR, 0xffffffff); + ret = devm_request_irq(dev, nc->irq, atmel_nfc_interrupt, + IRQF_SHARED, "nfc", nc); + if (ret) { + dev_err(dev, + "Could not get register NFC interrupt handler (err = %d)\n", + ret); + goto err; + } + + /* Initial NFC configuration. */ + regmap_write(nc->base.smc, ATMEL_HSMC_NFC_CFG, + ATMEL_HSMC_NFC_CFG_DTO_MAX); + regmap_write(nc->base.smc, ATMEL_HSMC_NFC_CTRL, + ATMEL_HSMC_NFC_CTRL_EN); + + ret = atmel_nand_controller_add_nands(&nc->base); + if (ret) + goto err; + + return 0; + +err: + atmel_hsmc_nand_controller_remove(&nc->base); + + return ret; +} + +static const struct atmel_nand_controller_ops atmel_hsmc_nc_ops = { + .probe = atmel_hsmc_nand_controller_probe, + .remove = atmel_hsmc_nand_controller_remove, + .ecc_init = atmel_hsmc_nand_ecc_init, + .nand_init = atmel_nand_init, + .setup_interface = atmel_hsmc_nand_setup_interface, + .exec_op = atmel_hsmc_nand_exec_op, +}; + +static const struct atmel_nand_controller_caps atmel_sama5_nc_caps = { + .has_dma = true, + .ale_offs = BIT(21), + .cle_offs = BIT(22), + .ops = &atmel_hsmc_nc_ops, +}; + +/* Only used to parse old bindings. */ +static const struct atmel_nand_controller_caps atmel_sama5_nand_caps = { + .has_dma = true, + .ale_offs = BIT(21), + .cle_offs = BIT(22), + .ops = &atmel_hsmc_nc_ops, + .legacy_of_bindings = true, +}; + +static int atmel_smc_nand_controller_probe(struct platform_device *pdev, + const struct atmel_nand_controller_caps *caps) +{ + struct device *dev = &pdev->dev; + struct atmel_smc_nand_controller *nc; + int ret; + + nc = devm_kzalloc(dev, sizeof(*nc), GFP_KERNEL); + if (!nc) + return -ENOMEM; + + ret = atmel_nand_controller_init(&nc->base, pdev, caps); + if (ret) + return ret; + + ret = atmel_smc_nand_controller_init(nc); + if (ret) + return ret; + + return atmel_nand_controller_add_nands(&nc->base); +} + +static int +atmel_smc_nand_controller_remove(struct atmel_nand_controller *nc) +{ + int ret; + + ret = atmel_nand_controller_remove_nands(nc); + if (ret) + return ret; + + atmel_nand_controller_cleanup(nc); + + return 0; +} + +/* + * The SMC reg layout of at91rm9200 is completely different which prevents us + * from re-using atmel_smc_nand_setup_interface() for the + * ->setup_interface() hook. + * At this point, there's no support for the at91rm9200 SMC IP, so we leave + * ->setup_interface() unassigned. + */ +static const struct atmel_nand_controller_ops at91rm9200_nc_ops = { + .probe = atmel_smc_nand_controller_probe, + .remove = atmel_smc_nand_controller_remove, + .ecc_init = atmel_nand_ecc_init, + .nand_init = atmel_smc_nand_init, + .exec_op = atmel_smc_nand_exec_op, +}; + +static const struct atmel_nand_controller_caps atmel_rm9200_nc_caps = { + .ale_offs = BIT(21), + .cle_offs = BIT(22), + .ebi_csa_regmap_name = "atmel,matrix", + .ops = &at91rm9200_nc_ops, +}; + +static const struct atmel_nand_controller_ops atmel_smc_nc_ops = { + .probe = atmel_smc_nand_controller_probe, + .remove = atmel_smc_nand_controller_remove, + .ecc_init = atmel_nand_ecc_init, + .nand_init = atmel_smc_nand_init, + .setup_interface = atmel_smc_nand_setup_interface, + .exec_op = atmel_smc_nand_exec_op, +}; + +static const struct atmel_nand_controller_caps atmel_sam9260_nc_caps = { + .ale_offs = BIT(21), + .cle_offs = BIT(22), + .ebi_csa_regmap_name = "atmel,matrix", + .ops = &atmel_smc_nc_ops, +}; + +static const struct atmel_nand_controller_caps atmel_sam9261_nc_caps = { + .ale_offs = BIT(22), + .cle_offs = BIT(21), + .ebi_csa_regmap_name = "atmel,matrix", + .ops = &atmel_smc_nc_ops, +}; + +static const struct atmel_nand_controller_caps atmel_sam9g45_nc_caps = { + .has_dma = true, + .ale_offs = BIT(21), + .cle_offs = BIT(22), + .ebi_csa_regmap_name = "atmel,matrix", + .ops = &atmel_smc_nc_ops, +}; + +static const struct atmel_nand_controller_caps microchip_sam9x60_nc_caps = { + .has_dma = true, + .ale_offs = BIT(21), + .cle_offs = BIT(22), + .ebi_csa_regmap_name = "microchip,sfr", + .ops = &atmel_smc_nc_ops, +}; + +/* Only used to parse old bindings. */ +static const struct atmel_nand_controller_caps atmel_rm9200_nand_caps = { + .ale_offs = BIT(21), + .cle_offs = BIT(22), + .ops = &atmel_smc_nc_ops, + .legacy_of_bindings = true, +}; + +static const struct atmel_nand_controller_caps atmel_sam9261_nand_caps = { + .ale_offs = BIT(22), + .cle_offs = BIT(21), + .ops = &atmel_smc_nc_ops, + .legacy_of_bindings = true, +}; + +static const struct atmel_nand_controller_caps atmel_sam9g45_nand_caps = { + .has_dma = true, + .ale_offs = BIT(21), + .cle_offs = BIT(22), + .ops = &atmel_smc_nc_ops, + .legacy_of_bindings = true, +}; + +static const struct of_device_id atmel_nand_controller_of_ids[] = { + { + .compatible = "atmel,at91rm9200-nand-controller", + .data = &atmel_rm9200_nc_caps, + }, + { + .compatible = "atmel,at91sam9260-nand-controller", + .data = &atmel_sam9260_nc_caps, + }, + { + .compatible = "atmel,at91sam9261-nand-controller", + .data = &atmel_sam9261_nc_caps, + }, + { + .compatible = "atmel,at91sam9g45-nand-controller", + .data = &atmel_sam9g45_nc_caps, + }, + { + .compatible = "atmel,sama5d3-nand-controller", + .data = &atmel_sama5_nc_caps, + }, + { + .compatible = "microchip,sam9x60-nand-controller", + .data = µchip_sam9x60_nc_caps, + }, + /* Support for old/deprecated bindings: */ + { + .compatible = "atmel,at91rm9200-nand", + .data = &atmel_rm9200_nand_caps, + }, + { + .compatible = "atmel,sama5d4-nand", + .data = &atmel_rm9200_nand_caps, + }, + { + .compatible = "atmel,sama5d2-nand", + .data = &atmel_rm9200_nand_caps, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, atmel_nand_controller_of_ids); + +static int atmel_nand_controller_probe(struct platform_device *pdev) +{ + const struct atmel_nand_controller_caps *caps; + + if (pdev->id_entry) + caps = (void *)pdev->id_entry->driver_data; + else + caps = of_device_get_match_data(&pdev->dev); + + if (!caps) { + dev_err(&pdev->dev, "Could not retrieve NFC caps\n"); + return -EINVAL; + } + + if (caps->legacy_of_bindings) { + struct device_node *nfc_node; + u32 ale_offs = 21; + + /* + * If we are parsing legacy DT props and the DT contains a + * valid NFC node, forward the request to the sama5 logic. + */ + nfc_node = of_get_compatible_child(pdev->dev.of_node, + "atmel,sama5d3-nfc"); + if (nfc_node) { + caps = &atmel_sama5_nand_caps; + of_node_put(nfc_node); + } + + /* + * Even if the compatible says we are dealing with an + * at91rm9200 controller, the atmel,nand-has-dma specify that + * this controller supports DMA, which means we are in fact + * dealing with an at91sam9g45+ controller. + */ + if (!caps->has_dma && + of_property_read_bool(pdev->dev.of_node, + "atmel,nand-has-dma")) + caps = &atmel_sam9g45_nand_caps; + + /* + * All SoCs except the at91sam9261 are assigning ALE to A21 and + * CLE to A22. If atmel,nand-addr-offset != 21 this means we're + * actually dealing with an at91sam9261 controller. + */ + of_property_read_u32(pdev->dev.of_node, + "atmel,nand-addr-offset", &ale_offs); + if (ale_offs != 21) + caps = &atmel_sam9261_nand_caps; + } + + return caps->ops->probe(pdev, caps); +} + +static void atmel_nand_controller_remove(struct platform_device *pdev) +{ + struct atmel_nand_controller *nc = platform_get_drvdata(pdev); + + WARN_ON(nc->caps->ops->remove(nc)); +} + +static __maybe_unused int atmel_nand_controller_resume(struct device *dev) +{ + struct atmel_nand_controller *nc = dev_get_drvdata(dev); + struct atmel_nand *nand; + + if (nc->pmecc) + atmel_pmecc_reset(nc->pmecc); + + list_for_each_entry(nand, &nc->chips, node) { + int i; + + for (i = 0; i < nand->numcs; i++) + nand_reset(&nand->base, i); + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(atmel_nand_controller_pm_ops, NULL, + atmel_nand_controller_resume); + +static struct platform_driver atmel_nand_controller_driver = { + .driver = { + .name = "atmel-nand-controller", + .of_match_table = atmel_nand_controller_of_ids, + .pm = &atmel_nand_controller_pm_ops, + }, + .probe = atmel_nand_controller_probe, + .remove = atmel_nand_controller_remove, +}; +module_platform_driver(atmel_nand_controller_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Boris Brezillon "); +MODULE_DESCRIPTION("NAND Flash Controller driver for Atmel SoCs"); +MODULE_ALIAS("platform:atmel-nand-controller"); diff --git a/drivers/mtd/nand/raw/atmel/pmecc.c b/drivers/mtd/nand/raw/atmel/pmecc.c new file mode 100644 index 000000000..1d0e93e4e --- /dev/null +++ b/drivers/mtd/nand/raw/atmel/pmecc.c @@ -0,0 +1,1012 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2017 ATMEL + * Copyright 2017 Free Electrons + * + * Author: Boris Brezillon + * + * Derived from the atmel_nand.c driver which contained the following + * copyrights: + * + * Copyright 2003 Rick Bronson + * + * Derived from drivers/mtd/nand/autcpu12.c (removed in v3.8) + * Copyright 2001 Thomas Gleixner (gleixner@autronix.de) + * + * Derived from drivers/mtd/spia.c (removed in v3.8) + * Copyright 2000 Steven J. Hill (sjhill@cotw.com) + * + * Add Hardware ECC support for AT91SAM9260 / AT91SAM9263 + * Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright 2007 + * + * Derived from Das U-Boot source code + * (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c) + * Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas + * + * Add Programmable Multibit ECC support for various AT91 SoC + * Copyright 2012 ATMEL, Hong Xu + * + * Add Nand Flash Controller support for SAMA5 SoC + * Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com) + * + * The PMECC is an hardware assisted BCH engine, which means part of the + * ECC algorithm is left to the software. The hardware/software repartition + * is explained in the "PMECC Controller Functional Description" chapter in + * Atmel datasheets, and some of the functions in this file are directly + * implementing the algorithms described in the "Software Implementation" + * sub-section. + * + * TODO: it seems that the software BCH implementation in lib/bch.c is already + * providing some of the logic we are implementing here. It would be smart + * to expose the needed lib/bch.c helpers/functions and re-use them here. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pmecc.h" + +/* Galois field dimension */ +#define PMECC_GF_DIMENSION_13 13 +#define PMECC_GF_DIMENSION_14 14 + +/* Primitive Polynomial used by PMECC */ +#define PMECC_GF_13_PRIMITIVE_POLY 0x201b +#define PMECC_GF_14_PRIMITIVE_POLY 0x4443 + +#define PMECC_LOOKUP_TABLE_SIZE_512 0x2000 +#define PMECC_LOOKUP_TABLE_SIZE_1024 0x4000 + +/* Time out value for reading PMECC status register */ +#define PMECC_MAX_TIMEOUT_MS 100 + +/* PMECC Register Definitions */ +#define ATMEL_PMECC_CFG 0x0 +#define PMECC_CFG_BCH_STRENGTH(x) (x) +#define PMECC_CFG_BCH_STRENGTH_MASK GENMASK(2, 0) +#define PMECC_CFG_SECTOR512 (0 << 4) +#define PMECC_CFG_SECTOR1024 (1 << 4) +#define PMECC_CFG_NSECTORS(x) ((fls(x) - 1) << 8) +#define PMECC_CFG_READ_OP (0 << 12) +#define PMECC_CFG_WRITE_OP (1 << 12) +#define PMECC_CFG_SPARE_ENABLE BIT(16) +#define PMECC_CFG_AUTO_ENABLE BIT(20) + +#define ATMEL_PMECC_SAREA 0x4 +#define ATMEL_PMECC_SADDR 0x8 +#define ATMEL_PMECC_EADDR 0xc + +#define ATMEL_PMECC_CLK 0x10 +#define PMECC_CLK_133MHZ (2 << 0) + +#define ATMEL_PMECC_CTRL 0x14 +#define PMECC_CTRL_RST BIT(0) +#define PMECC_CTRL_DATA BIT(1) +#define PMECC_CTRL_USER BIT(2) +#define PMECC_CTRL_ENABLE BIT(4) +#define PMECC_CTRL_DISABLE BIT(5) + +#define ATMEL_PMECC_SR 0x18 +#define PMECC_SR_BUSY BIT(0) +#define PMECC_SR_ENABLE BIT(4) + +#define ATMEL_PMECC_IER 0x1c +#define ATMEL_PMECC_IDR 0x20 +#define ATMEL_PMECC_IMR 0x24 +#define ATMEL_PMECC_ISR 0x28 +#define PMECC_ERROR_INT BIT(0) + +#define ATMEL_PMECC_ECC(sector, n) \ + ((((sector) + 1) * 0x40) + (n)) + +#define ATMEL_PMECC_REM(sector, n) \ + ((((sector) + 1) * 0x40) + ((n) * 4) + 0x200) + +/* PMERRLOC Register Definitions */ +#define ATMEL_PMERRLOC_ELCFG 0x0 +#define PMERRLOC_ELCFG_SECTOR_512 (0 << 0) +#define PMERRLOC_ELCFG_SECTOR_1024 (1 << 0) +#define PMERRLOC_ELCFG_NUM_ERRORS(n) ((n) << 16) + +#define ATMEL_PMERRLOC_ELPRIM 0x4 +#define ATMEL_PMERRLOC_ELEN 0x8 +#define ATMEL_PMERRLOC_ELDIS 0xc +#define PMERRLOC_DISABLE BIT(0) + +#define ATMEL_PMERRLOC_ELSR 0x10 +#define PMERRLOC_ELSR_BUSY BIT(0) + +#define ATMEL_PMERRLOC_ELIER 0x14 +#define ATMEL_PMERRLOC_ELIDR 0x18 +#define ATMEL_PMERRLOC_ELIMR 0x1c +#define ATMEL_PMERRLOC_ELISR 0x20 +#define PMERRLOC_ERR_NUM_MASK GENMASK(12, 8) +#define PMERRLOC_CALC_DONE BIT(0) + +#define ATMEL_PMERRLOC_SIGMA(x) (((x) * 0x4) + 0x28) + +#define ATMEL_PMERRLOC_EL(offs, x) (((x) * 0x4) + (offs)) + +struct atmel_pmecc_gf_tables { + u16 *alpha_to; + u16 *index_of; +}; + +struct atmel_pmecc_caps { + const int *strengths; + int nstrengths; + int el_offset; + bool correct_erased_chunks; + bool clk_ctrl; +}; + +struct atmel_pmecc { + struct device *dev; + const struct atmel_pmecc_caps *caps; + + struct { + void __iomem *base; + void __iomem *errloc; + } regs; + + struct mutex lock; +}; + +struct atmel_pmecc_user_conf_cache { + u32 cfg; + u32 sarea; + u32 saddr; + u32 eaddr; +}; + +struct atmel_pmecc_user { + struct atmel_pmecc_user_conf_cache cache; + struct atmel_pmecc *pmecc; + const struct atmel_pmecc_gf_tables *gf_tables; + int eccbytes; + s16 *partial_syn; + s16 *si; + s16 *lmu; + s16 *smu; + s32 *mu; + s32 *dmu; + s32 *delta; + u32 isr; +}; + +static DEFINE_MUTEX(pmecc_gf_tables_lock); +static const struct atmel_pmecc_gf_tables *pmecc_gf_tables_512; +static const struct atmel_pmecc_gf_tables *pmecc_gf_tables_1024; + +static inline int deg(unsigned int poly) +{ + /* polynomial degree is the most-significant bit index */ + return fls(poly) - 1; +} + +static int atmel_pmecc_build_gf_tables(int mm, unsigned int poly, + struct atmel_pmecc_gf_tables *gf_tables) +{ + unsigned int i, x = 1; + const unsigned int k = BIT(deg(poly)); + unsigned int nn = BIT(mm) - 1; + + /* primitive polynomial must be of degree m */ + if (k != (1u << mm)) + return -EINVAL; + + for (i = 0; i < nn; i++) { + gf_tables->alpha_to[i] = x; + gf_tables->index_of[x] = i; + if (i && (x == 1)) + /* polynomial is not primitive (a^i=1 with 0alpha_to[nn] = 1; + gf_tables->index_of[0] = 0; + + return 0; +} + +static const struct atmel_pmecc_gf_tables * +atmel_pmecc_create_gf_tables(const struct atmel_pmecc_user_req *req) +{ + struct atmel_pmecc_gf_tables *gf_tables; + unsigned int poly, degree, table_size; + int ret; + + if (req->ecc.sectorsize == 512) { + degree = PMECC_GF_DIMENSION_13; + poly = PMECC_GF_13_PRIMITIVE_POLY; + table_size = PMECC_LOOKUP_TABLE_SIZE_512; + } else { + degree = PMECC_GF_DIMENSION_14; + poly = PMECC_GF_14_PRIMITIVE_POLY; + table_size = PMECC_LOOKUP_TABLE_SIZE_1024; + } + + gf_tables = kzalloc(sizeof(*gf_tables) + + (2 * table_size * sizeof(u16)), + GFP_KERNEL); + if (!gf_tables) + return ERR_PTR(-ENOMEM); + + gf_tables->alpha_to = (void *)(gf_tables + 1); + gf_tables->index_of = gf_tables->alpha_to + table_size; + + ret = atmel_pmecc_build_gf_tables(degree, poly, gf_tables); + if (ret) { + kfree(gf_tables); + return ERR_PTR(ret); + } + + return gf_tables; +} + +static const struct atmel_pmecc_gf_tables * +atmel_pmecc_get_gf_tables(const struct atmel_pmecc_user_req *req) +{ + const struct atmel_pmecc_gf_tables **gf_tables, *ret; + + mutex_lock(&pmecc_gf_tables_lock); + if (req->ecc.sectorsize == 512) + gf_tables = &pmecc_gf_tables_512; + else + gf_tables = &pmecc_gf_tables_1024; + + ret = *gf_tables; + + if (!ret) { + ret = atmel_pmecc_create_gf_tables(req); + if (!IS_ERR(ret)) + *gf_tables = ret; + } + mutex_unlock(&pmecc_gf_tables_lock); + + return ret; +} + +static int atmel_pmecc_prepare_user_req(struct atmel_pmecc *pmecc, + struct atmel_pmecc_user_req *req) +{ + int i, max_eccbytes, eccbytes = 0, eccstrength = 0; + + if (req->pagesize <= 0 || req->oobsize <= 0 || req->ecc.bytes <= 0) + return -EINVAL; + + if (req->ecc.ooboffset >= 0 && + req->ecc.ooboffset + req->ecc.bytes > req->oobsize) + return -EINVAL; + + if (req->ecc.sectorsize == ATMEL_PMECC_SECTOR_SIZE_AUTO) { + if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH) + return -EINVAL; + + if (req->pagesize > 512) + req->ecc.sectorsize = 1024; + else + req->ecc.sectorsize = 512; + } + + if (req->ecc.sectorsize != 512 && req->ecc.sectorsize != 1024) + return -EINVAL; + + if (req->pagesize % req->ecc.sectorsize) + return -EINVAL; + + req->ecc.nsectors = req->pagesize / req->ecc.sectorsize; + + max_eccbytes = req->ecc.bytes; + + for (i = 0; i < pmecc->caps->nstrengths; i++) { + int nbytes, strength = pmecc->caps->strengths[i]; + + if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH && + strength < req->ecc.strength) + continue; + + nbytes = DIV_ROUND_UP(strength * fls(8 * req->ecc.sectorsize), + 8); + nbytes *= req->ecc.nsectors; + + if (nbytes > max_eccbytes) + break; + + eccstrength = strength; + eccbytes = nbytes; + + if (req->ecc.strength != ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH) + break; + } + + if (!eccstrength) + return -EINVAL; + + req->ecc.bytes = eccbytes; + req->ecc.strength = eccstrength; + + if (req->ecc.ooboffset < 0) + req->ecc.ooboffset = req->oobsize - eccbytes; + + return 0; +} + +struct atmel_pmecc_user * +atmel_pmecc_create_user(struct atmel_pmecc *pmecc, + struct atmel_pmecc_user_req *req) +{ + struct atmel_pmecc_user *user; + const struct atmel_pmecc_gf_tables *gf_tables; + int strength, size, ret; + + ret = atmel_pmecc_prepare_user_req(pmecc, req); + if (ret) + return ERR_PTR(ret); + + size = sizeof(*user); + size = ALIGN(size, sizeof(u16)); + /* Reserve space for partial_syn, si and smu */ + size += ((2 * req->ecc.strength) + 1) * sizeof(u16) * + (2 + req->ecc.strength + 2); + /* Reserve space for lmu. */ + size += (req->ecc.strength + 1) * sizeof(u16); + /* Reserve space for mu, dmu and delta. */ + size = ALIGN(size, sizeof(s32)); + size += (req->ecc.strength + 1) * sizeof(s32) * 3; + + user = devm_kzalloc(pmecc->dev, size, GFP_KERNEL); + if (!user) + return ERR_PTR(-ENOMEM); + + user->pmecc = pmecc; + + user->partial_syn = (s16 *)PTR_ALIGN(user + 1, sizeof(u16)); + user->si = user->partial_syn + ((2 * req->ecc.strength) + 1); + user->lmu = user->si + ((2 * req->ecc.strength) + 1); + user->smu = user->lmu + (req->ecc.strength + 1); + user->mu = (s32 *)PTR_ALIGN(user->smu + + (((2 * req->ecc.strength) + 1) * + (req->ecc.strength + 2)), + sizeof(s32)); + user->dmu = user->mu + req->ecc.strength + 1; + user->delta = user->dmu + req->ecc.strength + 1; + + gf_tables = atmel_pmecc_get_gf_tables(req); + if (IS_ERR(gf_tables)) + return ERR_CAST(gf_tables); + + user->gf_tables = gf_tables; + + user->eccbytes = req->ecc.bytes / req->ecc.nsectors; + + for (strength = 0; strength < pmecc->caps->nstrengths; strength++) { + if (pmecc->caps->strengths[strength] == req->ecc.strength) + break; + } + + user->cache.cfg = PMECC_CFG_BCH_STRENGTH(strength) | + PMECC_CFG_NSECTORS(req->ecc.nsectors); + + if (req->ecc.sectorsize == 1024) + user->cache.cfg |= PMECC_CFG_SECTOR1024; + + user->cache.sarea = req->oobsize - 1; + user->cache.saddr = req->ecc.ooboffset; + user->cache.eaddr = req->ecc.ooboffset + req->ecc.bytes - 1; + + return user; +} +EXPORT_SYMBOL_GPL(atmel_pmecc_create_user); + +static int get_strength(struct atmel_pmecc_user *user) +{ + const int *strengths = user->pmecc->caps->strengths; + + return strengths[user->cache.cfg & PMECC_CFG_BCH_STRENGTH_MASK]; +} + +static int get_sectorsize(struct atmel_pmecc_user *user) +{ + return user->cache.cfg & PMECC_CFG_SECTOR1024 ? 1024 : 512; +} + +static void atmel_pmecc_gen_syndrome(struct atmel_pmecc_user *user, int sector) +{ + int strength = get_strength(user); + u32 value; + int i; + + /* Fill odd syndromes */ + for (i = 0; i < strength; i++) { + value = readl_relaxed(user->pmecc->regs.base + + ATMEL_PMECC_REM(sector, i / 2)); + if (i & 1) + value >>= 16; + + user->partial_syn[(2 * i) + 1] = value; + } +} + +static void atmel_pmecc_substitute(struct atmel_pmecc_user *user) +{ + int degree = get_sectorsize(user) == 512 ? 13 : 14; + int cw_len = BIT(degree) - 1; + int strength = get_strength(user); + s16 *alpha_to = user->gf_tables->alpha_to; + s16 *index_of = user->gf_tables->index_of; + s16 *partial_syn = user->partial_syn; + s16 *si; + int i, j; + + /* + * si[] is a table that holds the current syndrome value, + * an element of that table belongs to the field + */ + si = user->si; + + memset(&si[1], 0, sizeof(s16) * ((2 * strength) - 1)); + + /* Computation 2t syndromes based on S(x) */ + /* Odd syndromes */ + for (i = 1; i < 2 * strength; i += 2) { + for (j = 0; j < degree; j++) { + if (partial_syn[i] & BIT(j)) + si[i] = alpha_to[i * j] ^ si[i]; + } + } + /* Even syndrome = (Odd syndrome) ** 2 */ + for (i = 2, j = 1; j <= strength; i = ++j << 1) { + if (si[j] == 0) { + si[i] = 0; + } else { + s16 tmp; + + tmp = index_of[si[j]]; + tmp = (tmp * 2) % cw_len; + si[i] = alpha_to[tmp]; + } + } +} + +static void atmel_pmecc_get_sigma(struct atmel_pmecc_user *user) +{ + s16 *lmu = user->lmu; + s16 *si = user->si; + s32 *mu = user->mu; + s32 *dmu = user->dmu; + s32 *delta = user->delta; + int degree = get_sectorsize(user) == 512 ? 13 : 14; + int cw_len = BIT(degree) - 1; + int strength = get_strength(user); + int num = 2 * strength + 1; + s16 *index_of = user->gf_tables->index_of; + s16 *alpha_to = user->gf_tables->alpha_to; + int i, j, k; + u32 dmu_0_count, tmp; + s16 *smu = user->smu; + + /* index of largest delta */ + int ro; + int largest; + int diff; + + dmu_0_count = 0; + + /* First Row */ + + /* Mu */ + mu[0] = -1; + + memset(smu, 0, sizeof(s16) * num); + smu[0] = 1; + + /* discrepancy set to 1 */ + dmu[0] = 1; + /* polynom order set to 0 */ + lmu[0] = 0; + delta[0] = (mu[0] * 2 - lmu[0]) >> 1; + + /* Second Row */ + + /* Mu */ + mu[1] = 0; + /* Sigma(x) set to 1 */ + memset(&smu[num], 0, sizeof(s16) * num); + smu[num] = 1; + + /* discrepancy set to S1 */ + dmu[1] = si[1]; + + /* polynom order set to 0 */ + lmu[1] = 0; + + delta[1] = (mu[1] * 2 - lmu[1]) >> 1; + + /* Init the Sigma(x) last row */ + memset(&smu[(strength + 1) * num], 0, sizeof(s16) * num); + + for (i = 1; i <= strength; i++) { + mu[i + 1] = i << 1; + /* Begin Computing Sigma (Mu+1) and L(mu) */ + /* check if discrepancy is set to 0 */ + if (dmu[i] == 0) { + dmu_0_count++; + + tmp = ((strength - (lmu[i] >> 1) - 1) / 2); + if ((strength - (lmu[i] >> 1) - 1) & 0x1) + tmp += 2; + else + tmp += 1; + + if (dmu_0_count == tmp) { + for (j = 0; j <= (lmu[i] >> 1) + 1; j++) + smu[(strength + 1) * num + j] = + smu[i * num + j]; + + lmu[strength + 1] = lmu[i]; + return; + } + + /* copy polynom */ + for (j = 0; j <= lmu[i] >> 1; j++) + smu[(i + 1) * num + j] = smu[i * num + j]; + + /* copy previous polynom order to the next */ + lmu[i + 1] = lmu[i]; + } else { + ro = 0; + largest = -1; + /* find largest delta with dmu != 0 */ + for (j = 0; j < i; j++) { + if ((dmu[j]) && (delta[j] > largest)) { + largest = delta[j]; + ro = j; + } + } + + /* compute difference */ + diff = (mu[i] - mu[ro]); + + /* Compute degree of the new smu polynomial */ + if ((lmu[i] >> 1) > ((lmu[ro] >> 1) + diff)) + lmu[i + 1] = lmu[i]; + else + lmu[i + 1] = ((lmu[ro] >> 1) + diff) * 2; + + /* Init smu[i+1] with 0 */ + for (k = 0; k < num; k++) + smu[(i + 1) * num + k] = 0; + + /* Compute smu[i+1] */ + for (k = 0; k <= lmu[ro] >> 1; k++) { + s16 a, b, c; + + if (!(smu[ro * num + k] && dmu[i])) + continue; + + a = index_of[dmu[i]]; + b = index_of[dmu[ro]]; + c = index_of[smu[ro * num + k]]; + tmp = a + (cw_len - b) + c; + a = alpha_to[tmp % cw_len]; + smu[(i + 1) * num + (k + diff)] = a; + } + + for (k = 0; k <= lmu[i] >> 1; k++) + smu[(i + 1) * num + k] ^= smu[i * num + k]; + } + + /* End Computing Sigma (Mu+1) and L(mu) */ + /* In either case compute delta */ + delta[i + 1] = (mu[i + 1] * 2 - lmu[i + 1]) >> 1; + + /* Do not compute discrepancy for the last iteration */ + if (i >= strength) + continue; + + for (k = 0; k <= (lmu[i + 1] >> 1); k++) { + tmp = 2 * (i - 1); + if (k == 0) { + dmu[i + 1] = si[tmp + 3]; + } else if (smu[(i + 1) * num + k] && si[tmp + 3 - k]) { + s16 a, b, c; + + a = index_of[smu[(i + 1) * num + k]]; + b = si[2 * (i - 1) + 3 - k]; + c = index_of[b]; + tmp = a + c; + tmp %= cw_len; + dmu[i + 1] = alpha_to[tmp] ^ dmu[i + 1]; + } + } + } +} + +static int atmel_pmecc_err_location(struct atmel_pmecc_user *user) +{ + int sector_size = get_sectorsize(user); + int degree = sector_size == 512 ? 13 : 14; + struct atmel_pmecc *pmecc = user->pmecc; + int strength = get_strength(user); + int ret, roots_nbr, i, err_nbr = 0; + int num = (2 * strength) + 1; + s16 *smu = user->smu; + u32 val; + + writel(PMERRLOC_DISABLE, pmecc->regs.errloc + ATMEL_PMERRLOC_ELDIS); + + for (i = 0; i <= user->lmu[strength + 1] >> 1; i++) { + writel_relaxed(smu[(strength + 1) * num + i], + pmecc->regs.errloc + ATMEL_PMERRLOC_SIGMA(i)); + err_nbr++; + } + + val = (err_nbr - 1) << 16; + if (sector_size == 1024) + val |= 1; + + writel(val, pmecc->regs.errloc + ATMEL_PMERRLOC_ELCFG); + writel((sector_size * 8) + (degree * strength), + pmecc->regs.errloc + ATMEL_PMERRLOC_ELEN); + + ret = readl_relaxed_poll_timeout(pmecc->regs.errloc + + ATMEL_PMERRLOC_ELISR, + val, val & PMERRLOC_CALC_DONE, 0, + PMECC_MAX_TIMEOUT_MS * 1000); + if (ret) { + dev_err(pmecc->dev, + "PMECC: Timeout to calculate error location.\n"); + return ret; + } + + roots_nbr = (val & PMERRLOC_ERR_NUM_MASK) >> 8; + /* Number of roots == degree of smu hence <= cap */ + if (roots_nbr == user->lmu[strength + 1] >> 1) + return err_nbr - 1; + + /* + * Number of roots does not match the degree of smu + * unable to correct error. + */ + return -EBADMSG; +} + +int atmel_pmecc_correct_sector(struct atmel_pmecc_user *user, int sector, + void *data, void *ecc) +{ + struct atmel_pmecc *pmecc = user->pmecc; + int sectorsize = get_sectorsize(user); + int eccbytes = user->eccbytes; + int i, nerrors; + + if (!(user->isr & BIT(sector))) + return 0; + + atmel_pmecc_gen_syndrome(user, sector); + atmel_pmecc_substitute(user); + atmel_pmecc_get_sigma(user); + + nerrors = atmel_pmecc_err_location(user); + if (nerrors < 0) + return nerrors; + + for (i = 0; i < nerrors; i++) { + const char *area; + int byte, bit; + u32 errpos; + u8 *ptr; + + errpos = readl_relaxed(pmecc->regs.errloc + + ATMEL_PMERRLOC_EL(pmecc->caps->el_offset, i)); + errpos--; + + byte = errpos / 8; + bit = errpos % 8; + + if (byte < sectorsize) { + ptr = data + byte; + area = "data"; + } else if (byte < sectorsize + eccbytes) { + ptr = ecc + byte - sectorsize; + area = "ECC"; + } else { + dev_dbg(pmecc->dev, + "Invalid errpos value (%d, max is %d)\n", + errpos, (sectorsize + eccbytes) * 8); + return -EINVAL; + } + + dev_dbg(pmecc->dev, + "Bit flip in %s area, byte %d: 0x%02x -> 0x%02x\n", + area, byte, *ptr, (unsigned int)(*ptr ^ BIT(bit))); + + *ptr ^= BIT(bit); + } + + return nerrors; +} +EXPORT_SYMBOL_GPL(atmel_pmecc_correct_sector); + +bool atmel_pmecc_correct_erased_chunks(struct atmel_pmecc_user *user) +{ + return user->pmecc->caps->correct_erased_chunks; +} +EXPORT_SYMBOL_GPL(atmel_pmecc_correct_erased_chunks); + +void atmel_pmecc_get_generated_eccbytes(struct atmel_pmecc_user *user, + int sector, void *ecc) +{ + struct atmel_pmecc *pmecc = user->pmecc; + u8 *ptr = ecc; + int i; + + for (i = 0; i < user->eccbytes; i++) + ptr[i] = readb_relaxed(pmecc->regs.base + + ATMEL_PMECC_ECC(sector, i)); +} +EXPORT_SYMBOL_GPL(atmel_pmecc_get_generated_eccbytes); + +void atmel_pmecc_reset(struct atmel_pmecc *pmecc) +{ + writel(PMECC_CTRL_RST, pmecc->regs.base + ATMEL_PMECC_CTRL); + writel(PMECC_CTRL_DISABLE, pmecc->regs.base + ATMEL_PMECC_CTRL); +} +EXPORT_SYMBOL_GPL(atmel_pmecc_reset); + +int atmel_pmecc_enable(struct atmel_pmecc_user *user, int op) +{ + struct atmel_pmecc *pmecc = user->pmecc; + u32 cfg; + + if (op != NAND_ECC_READ && op != NAND_ECC_WRITE) { + dev_err(pmecc->dev, "Bad ECC operation!"); + return -EINVAL; + } + + mutex_lock(&user->pmecc->lock); + + cfg = user->cache.cfg; + if (op == NAND_ECC_WRITE) + cfg |= PMECC_CFG_WRITE_OP; + else + cfg |= PMECC_CFG_AUTO_ENABLE; + + writel(cfg, pmecc->regs.base + ATMEL_PMECC_CFG); + writel(user->cache.sarea, pmecc->regs.base + ATMEL_PMECC_SAREA); + writel(user->cache.saddr, pmecc->regs.base + ATMEL_PMECC_SADDR); + writel(user->cache.eaddr, pmecc->regs.base + ATMEL_PMECC_EADDR); + + writel(PMECC_CTRL_ENABLE, pmecc->regs.base + ATMEL_PMECC_CTRL); + writel(PMECC_CTRL_DATA, pmecc->regs.base + ATMEL_PMECC_CTRL); + + return 0; +} +EXPORT_SYMBOL_GPL(atmel_pmecc_enable); + +void atmel_pmecc_disable(struct atmel_pmecc_user *user) +{ + atmel_pmecc_reset(user->pmecc); + mutex_unlock(&user->pmecc->lock); +} +EXPORT_SYMBOL_GPL(atmel_pmecc_disable); + +int atmel_pmecc_wait_rdy(struct atmel_pmecc_user *user) +{ + struct atmel_pmecc *pmecc = user->pmecc; + u32 status; + int ret; + + ret = readl_relaxed_poll_timeout(pmecc->regs.base + + ATMEL_PMECC_SR, + status, !(status & PMECC_SR_BUSY), 0, + PMECC_MAX_TIMEOUT_MS * 1000); + if (ret) { + dev_err(pmecc->dev, + "Timeout while waiting for PMECC ready.\n"); + return ret; + } + + user->isr = readl_relaxed(pmecc->regs.base + ATMEL_PMECC_ISR); + + return 0; +} +EXPORT_SYMBOL_GPL(atmel_pmecc_wait_rdy); + +static struct atmel_pmecc *atmel_pmecc_create(struct platform_device *pdev, + const struct atmel_pmecc_caps *caps, + int pmecc_res_idx, int errloc_res_idx) +{ + struct device *dev = &pdev->dev; + struct atmel_pmecc *pmecc; + + pmecc = devm_kzalloc(dev, sizeof(*pmecc), GFP_KERNEL); + if (!pmecc) + return ERR_PTR(-ENOMEM); + + pmecc->caps = caps; + pmecc->dev = dev; + mutex_init(&pmecc->lock); + + pmecc->regs.base = devm_platform_ioremap_resource(pdev, pmecc_res_idx); + if (IS_ERR(pmecc->regs.base)) + return ERR_CAST(pmecc->regs.base); + + pmecc->regs.errloc = devm_platform_ioremap_resource(pdev, errloc_res_idx); + 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); + + return pmecc; +} + +static void devm_atmel_pmecc_put(struct device *dev, void *res) +{ + struct atmel_pmecc **pmecc = res; + + put_device((*pmecc)->dev); +} + +static struct atmel_pmecc *atmel_pmecc_get_by_node(struct device *userdev, + struct device_node *np) +{ + struct platform_device *pdev; + struct atmel_pmecc *pmecc, **ptr; + int ret; + + pdev = of_find_device_by_node(np); + if (!pdev) + return ERR_PTR(-EPROBE_DEFER); + pmecc = platform_get_drvdata(pdev); + if (!pmecc) { + ret = -EPROBE_DEFER; + goto err_put_device; + } + + ptr = devres_alloc(devm_atmel_pmecc_put, sizeof(*ptr), GFP_KERNEL); + if (!ptr) { + ret = -ENOMEM; + goto err_put_device; + } + + *ptr = pmecc; + + devres_add(userdev, ptr); + + return pmecc; + +err_put_device: + put_device(&pdev->dev); + return ERR_PTR(ret); +} + +static const int atmel_pmecc_strengths[] = { 2, 4, 8, 12, 24, 32 }; + +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 = { + .strengths = atmel_pmecc_strengths, + .nstrengths = 5, + .el_offset = 0x8c, + .correct_erased_chunks = true, +}; + +static struct atmel_pmecc_caps sama5d2_caps = { + .strengths = atmel_pmecc_strengths, + .nstrengths = 6, + .el_offset = 0xac, + .correct_erased_chunks = true, +}; + +static const struct of_device_id __maybe_unused atmel_pmecc_legacy_match[] = { + { .compatible = "atmel,sama5d4-nand", &sama5d4_caps }, + { .compatible = "atmel,sama5d2-nand", &sama5d2_caps }, + { /* sentinel */ } +}; + +struct atmel_pmecc *devm_atmel_pmecc_get(struct device *userdev) +{ + struct atmel_pmecc *pmecc; + struct device_node *np; + + if (!userdev) + return ERR_PTR(-EINVAL); + + if (!userdev->of_node) + return NULL; + + np = of_parse_phandle(userdev->of_node, "ecc-engine", 0); + if (np) { + pmecc = atmel_pmecc_get_by_node(userdev, np); + of_node_put(np); + } else { + /* + * Support old DT bindings: in this case the PMECC iomem + * resources are directly defined in the user pdev at position + * 1 and 2. Extract all relevant information from there. + */ + struct platform_device *pdev = to_platform_device(userdev); + const struct atmel_pmecc_caps *caps; + const struct of_device_id *match; + + /* No PMECC engine available. */ + if (!of_property_read_bool(userdev->of_node, + "atmel,has-pmecc")) + return NULL; + + caps = &at91sam9g45_caps; + + /* Find the caps associated to the NAND dev node. */ + match = of_match_node(atmel_pmecc_legacy_match, + userdev->of_node); + if (match && match->data) + caps = match->data; + + pmecc = atmel_pmecc_create(pdev, caps, 1, 2); + } + + return pmecc; +} +EXPORT_SYMBOL(devm_atmel_pmecc_get); + +static const struct of_device_id atmel_pmecc_match[] = { + { .compatible = "atmel,at91sam9g45-pmecc", &at91sam9g45_caps }, + { .compatible = "atmel,sama5d4-pmecc", &sama5d4_caps }, + { .compatible = "atmel,sama5d2-pmecc", &sama5d2_caps }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, atmel_pmecc_match); + +static int atmel_pmecc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct atmel_pmecc_caps *caps; + struct atmel_pmecc *pmecc; + + caps = of_device_get_match_data(&pdev->dev); + if (!caps) { + dev_err(dev, "Invalid caps\n"); + return -EINVAL; + } + + pmecc = atmel_pmecc_create(pdev, caps, 0, 1); + if (IS_ERR(pmecc)) + return PTR_ERR(pmecc); + + platform_set_drvdata(pdev, pmecc); + + return 0; +} + +static struct platform_driver atmel_pmecc_driver = { + .driver = { + .name = "atmel-pmecc", + .of_match_table = atmel_pmecc_match, + }, + .probe = atmel_pmecc_probe, +}; +module_platform_driver(atmel_pmecc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Boris Brezillon "); +MODULE_DESCRIPTION("PMECC engine driver"); diff --git a/drivers/mtd/nand/raw/atmel/pmecc.h b/drivers/mtd/nand/raw/atmel/pmecc.h new file mode 100644 index 000000000..cc0c5af1f --- /dev/null +++ b/drivers/mtd/nand/raw/atmel/pmecc.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * © Copyright 2016 ATMEL + * © Copyright 2016 Free Electrons + * + * Author: Boris Brezillon + * + * Derived from the atmel_nand.c driver which contained the following + * copyrights: + * + * Copyright © 2003 Rick Bronson + * + * Derived from drivers/mtd/nand/autcpu12.c (removed in v3.8) + * Copyright © 2001 Thomas Gleixner (gleixner@autronix.de) + * + * Derived from drivers/mtd/spia.c (removed in v3.8) + * Copyright © 2000 Steven J. Hill (sjhill@cotw.com) + * + * + * Add Hardware ECC support for AT91SAM9260 / AT91SAM9263 + * Richard Genoud (richard.genoud@gmail.com), Adeneo Copyright © 2007 + * + * Derived from Das U-Boot source code + * (u-boot-1.1.5/board/atmel/at91sam9263ek/nand.c) + * © Copyright 2006 ATMEL Rousset, Lacressonniere Nicolas + * + * Add Programmable Multibit ECC support for various AT91 SoC + * © Copyright 2012 ATMEL, Hong Xu + * + * Add Nand Flash Controller support for SAMA5 SoC + * © Copyright 2013 ATMEL, Josh Wu (josh.wu@atmel.com) + */ + +#ifndef ATMEL_PMECC_H +#define ATMEL_PMECC_H + +#define ATMEL_PMECC_MAXIMIZE_ECC_STRENGTH 0 +#define ATMEL_PMECC_SECTOR_SIZE_AUTO 0 +#define ATMEL_PMECC_OOBOFFSET_AUTO -1 + +struct atmel_pmecc_user_req { + int pagesize; + int oobsize; + struct { + int strength; + int bytes; + int sectorsize; + int nsectors; + int ooboffset; + } ecc; +}; + +struct atmel_pmecc *devm_atmel_pmecc_get(struct device *dev); + +struct atmel_pmecc_user * +atmel_pmecc_create_user(struct atmel_pmecc *pmecc, + struct atmel_pmecc_user_req *req); +void atmel_pmecc_reset(struct atmel_pmecc *pmecc); +int atmel_pmecc_enable(struct atmel_pmecc_user *user, int op); +void atmel_pmecc_disable(struct atmel_pmecc_user *user); +int atmel_pmecc_wait_rdy(struct atmel_pmecc_user *user); +int atmel_pmecc_correct_sector(struct atmel_pmecc_user *user, int sector, + void *data, void *ecc); +bool atmel_pmecc_correct_erased_chunks(struct atmel_pmecc_user *user); +void atmel_pmecc_get_generated_eccbytes(struct atmel_pmecc_user *user, + int sector, void *ecc); + +#endif /* ATMEL_PMECC_H */ diff --git a/drivers/mtd/nand/raw/au1550nd.c b/drivers/mtd/nand/raw/au1550nd.c new file mode 100644 index 000000000..f1e99197a --- /dev/null +++ b/drivers/mtd/nand/raw/au1550nd.c @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2004 Embedded Edge, LLC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct au1550nd_ctx { + struct nand_controller controller; + struct nand_chip chip; + + int cs; + void __iomem *base; +}; + +static struct au1550nd_ctx *chip_to_au_ctx(struct nand_chip *this) +{ + return container_of(this, struct au1550nd_ctx, chip); +} + +/** + * au_write_buf - write buffer to chip + * @this: NAND chip object + * @buf: data buffer + * @len: number of bytes to write + * + * write function for 8bit buswidth + */ +static void au_write_buf(struct nand_chip *this, const void *buf, + unsigned int len) +{ + struct au1550nd_ctx *ctx = chip_to_au_ctx(this); + const u8 *p = buf; + int i; + + for (i = 0; i < len; i++) { + writeb(p[i], ctx->base + MEM_STNAND_DATA); + wmb(); /* drain writebuffer */ + } +} + +/** + * au_read_buf - read chip data into buffer + * @this: NAND chip object + * @buf: buffer to store date + * @len: number of bytes to read + * + * read function for 8bit buswidth + */ +static void au_read_buf(struct nand_chip *this, void *buf, + unsigned int len) +{ + struct au1550nd_ctx *ctx = chip_to_au_ctx(this); + u8 *p = buf; + int i; + + for (i = 0; i < len; i++) { + p[i] = readb(ctx->base + MEM_STNAND_DATA); + wmb(); /* drain writebuffer */ + } +} + +/** + * au_write_buf16 - write buffer to chip + * @this: NAND chip object + * @buf: data buffer + * @len: number of bytes to write + * + * write function for 16bit buswidth + */ +static void au_write_buf16(struct nand_chip *this, const void *buf, + unsigned int len) +{ + struct au1550nd_ctx *ctx = chip_to_au_ctx(this); + const u16 *p = buf; + unsigned int i; + + len >>= 1; + for (i = 0; i < len; i++) { + writew(p[i], ctx->base + MEM_STNAND_DATA); + wmb(); /* drain writebuffer */ + } +} + +/** + * au_read_buf16 - read chip data into buffer + * @this: NAND chip object + * @buf: buffer to store date + * @len: number of bytes to read + * + * read function for 16bit buswidth + */ +static void au_read_buf16(struct nand_chip *this, void *buf, unsigned int len) +{ + struct au1550nd_ctx *ctx = chip_to_au_ctx(this); + unsigned int i; + u16 *p = buf; + + len >>= 1; + for (i = 0; i < len; i++) { + p[i] = readw(ctx->base + MEM_STNAND_DATA); + wmb(); /* drain writebuffer */ + } +} + +static int find_nand_cs(unsigned long nand_base) +{ + void __iomem *base = + (void __iomem *)KSEG1ADDR(AU1000_STATIC_MEM_PHYS_ADDR); + unsigned long addr, staddr, start, mask, end; + int i; + + for (i = 0; i < 4; i++) { + addr = 0x1000 + (i * 0x10); /* CSx */ + staddr = __raw_readl(base + addr + 0x08); /* STADDRx */ + /* figure out the decoded range of this CS */ + start = (staddr << 4) & 0xfffc0000; + mask = (staddr << 18) & 0xfffc0000; + end = (start | (start - 1)) & ~(start ^ mask); + if ((nand_base >= start) && (nand_base < end)) + return i; + } + + return -ENODEV; +} + +static int au1550nd_waitrdy(struct nand_chip *this, unsigned int timeout_ms) +{ + unsigned long timeout_jiffies = jiffies; + + timeout_jiffies += msecs_to_jiffies(timeout_ms) + 1; + do { + if (alchemy_rdsmem(AU1000_MEM_STSTAT) & 0x1) + return 0; + + usleep_range(10, 100); + } while (time_before(jiffies, timeout_jiffies)); + + return -ETIMEDOUT; +} + +static int au1550nd_exec_instr(struct nand_chip *this, + const struct nand_op_instr *instr) +{ + struct au1550nd_ctx *ctx = chip_to_au_ctx(this); + unsigned int i; + int ret = 0; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + writeb(instr->ctx.cmd.opcode, + ctx->base + MEM_STNAND_CMD); + /* Drain the writebuffer */ + wmb(); + break; + + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) { + writeb(instr->ctx.addr.addrs[i], + ctx->base + MEM_STNAND_ADDR); + /* Drain the writebuffer */ + wmb(); + } + break; + + case NAND_OP_DATA_IN_INSTR: + if ((this->options & NAND_BUSWIDTH_16) && + !instr->ctx.data.force_8bit) + au_read_buf16(this, instr->ctx.data.buf.in, + instr->ctx.data.len); + else + au_read_buf(this, instr->ctx.data.buf.in, + instr->ctx.data.len); + break; + + case NAND_OP_DATA_OUT_INSTR: + if ((this->options & NAND_BUSWIDTH_16) && + !instr->ctx.data.force_8bit) + au_write_buf16(this, instr->ctx.data.buf.out, + instr->ctx.data.len); + else + au_write_buf(this, instr->ctx.data.buf.out, + instr->ctx.data.len); + break; + + case NAND_OP_WAITRDY_INSTR: + ret = au1550nd_waitrdy(this, instr->ctx.waitrdy.timeout_ms); + break; + default: + return -EINVAL; + } + + if (instr->delay_ns) + ndelay(instr->delay_ns); + + return ret; +} + +static int au1550nd_exec_op(struct nand_chip *this, + const struct nand_operation *op, + bool check_only) +{ + struct au1550nd_ctx *ctx = chip_to_au_ctx(this); + unsigned int i; + int ret; + + if (check_only) + return 0; + + /* assert (force assert) chip enable */ + alchemy_wrsmem((1 << (4 + ctx->cs)), AU1000_MEM_STNDCTL); + /* Drain the writebuffer */ + wmb(); + + for (i = 0; i < op->ninstrs; i++) { + ret = au1550nd_exec_instr(this, &op->instrs[i]); + if (ret) + break; + } + + /* deassert chip enable */ + alchemy_wrsmem(0, AU1000_MEM_STNDCTL); + /* Drain the writebuffer */ + wmb(); + + return ret; +} + +static int au1550nd_attach_chip(struct nand_chip *chip) +{ + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT && + chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + + return 0; +} + +static const struct nand_controller_ops au1550nd_ops = { + .exec_op = au1550nd_exec_op, + .attach_chip = au1550nd_attach_chip, +}; + +static int au1550nd_probe(struct platform_device *pdev) +{ + struct au1550nd_platdata *pd; + struct au1550nd_ctx *ctx; + struct nand_chip *this; + struct mtd_info *mtd; + struct resource *r; + int ret, cs; + + pd = dev_get_platdata(&pdev->dev); + if (!pd) { + dev_err(&pdev->dev, "missing platform data\n"); + return -ENODEV; + } + + ctx = kzalloc_obj(*ctx); + if (!ctx) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "no NAND memory resource\n"); + ret = -ENODEV; + goto out1; + } + if (request_mem_region(r->start, resource_size(r), "au1550-nand")) { + dev_err(&pdev->dev, "cannot claim NAND memory area\n"); + ret = -ENOMEM; + goto out1; + } + + ctx->base = ioremap(r->start, 0x1000); + if (!ctx->base) { + dev_err(&pdev->dev, "cannot remap NAND memory area\n"); + ret = -ENODEV; + goto out2; + } + + this = &ctx->chip; + mtd = nand_to_mtd(this); + mtd->dev.parent = &pdev->dev; + + /* figure out which CS# r->start belongs to */ + cs = find_nand_cs(r->start); + if (cs < 0) { + dev_err(&pdev->dev, "cannot detect NAND chipselect\n"); + ret = -ENODEV; + goto out3; + } + ctx->cs = cs; + + nand_controller_init(&ctx->controller); + ctx->controller.ops = &au1550nd_ops; + this->controller = &ctx->controller; + + if (pd->devwidth) + this->options |= NAND_BUSWIDTH_16; + + /* + * This driver assumes that the default ECC engine should be TYPE_SOFT. + * Set ->engine_type before registering the NAND devices in order to + * provide a driver specific default value. + */ + this->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + + ret = nand_scan(this, 1); + if (ret) { + dev_err(&pdev->dev, "NAND scan failed with %d\n", ret); + goto out3; + } + + mtd_device_register(mtd, pd->parts, pd->num_parts); + + platform_set_drvdata(pdev, ctx); + + return 0; + +out3: + iounmap(ctx->base); +out2: + release_mem_region(r->start, resource_size(r)); +out1: + kfree(ctx); + return ret; +} + +static void au1550nd_remove(struct platform_device *pdev) +{ + struct au1550nd_ctx *ctx = platform_get_drvdata(pdev); + struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct nand_chip *chip = &ctx->chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + iounmap(ctx->base); + release_mem_region(r->start, 0x1000); + kfree(ctx); +} + +static struct platform_driver au1550nd_driver = { + .driver = { + .name = "au1550-nand", + }, + .probe = au1550nd_probe, + .remove = au1550nd_remove, +}; + +module_platform_driver(au1550nd_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Embedded Edge, LLC"); +MODULE_DESCRIPTION("Board-specific glue layer for NAND flash on Pb1550 board"); diff --git a/drivers/mtd/nand/raw/bcm47xxnflash/Makefile b/drivers/mtd/nand/raw/bcm47xxnflash/Makefile new file mode 100644 index 000000000..b531a630c --- /dev/null +++ b/drivers/mtd/nand/raw/bcm47xxnflash/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +bcm47xxnflash-y += main.o +bcm47xxnflash-y += ops_bcm4706.o + +obj-$(CONFIG_MTD_NAND_BCM47XXNFLASH) += bcm47xxnflash.o diff --git a/drivers/mtd/nand/raw/bcm47xxnflash/bcm47xxnflash.h b/drivers/mtd/nand/raw/bcm47xxnflash/bcm47xxnflash.h new file mode 100644 index 000000000..201b9baa5 --- /dev/null +++ b/drivers/mtd/nand/raw/bcm47xxnflash/bcm47xxnflash.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __BCM47XXNFLASH_H +#define __BCM47XXNFLASH_H + +#ifndef pr_fmt +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#endif + +#include +#include + +struct bcm47xxnflash { + struct bcma_drv_cc *cc; + + struct nand_chip nand_chip; + + unsigned curr_command; + int curr_page_addr; + int curr_column; + + u8 id_data[8]; +}; + +int bcm47xxnflash_ops_bcm4706_init(struct bcm47xxnflash *b47n); + +#endif /* BCM47XXNFLASH */ diff --git a/drivers/mtd/nand/raw/bcm47xxnflash/main.c b/drivers/mtd/nand/raw/bcm47xxnflash/main.c new file mode 100644 index 000000000..4d4e185c2 --- /dev/null +++ b/drivers/mtd/nand/raw/bcm47xxnflash/main.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BCM47XX NAND flash driver + * + * Copyright (C) 2012 Rafał Miłecki + */ + +#include "bcm47xxnflash.h" + +#include +#include +#include +#include +#include + +MODULE_DESCRIPTION("NAND flash driver for BCMA bus"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Rafał Miłecki"); + +static const char *probes[] = { "bcm47xxpart", NULL }; + +static int bcm47xxnflash_probe(struct platform_device *pdev) +{ + struct bcma_nflash *nflash = dev_get_platdata(&pdev->dev); + struct bcm47xxnflash *b47n; + struct mtd_info *mtd; + int err = 0; + + b47n = devm_kzalloc(&pdev->dev, sizeof(*b47n), GFP_KERNEL); + if (!b47n) + return -ENOMEM; + + nand_set_controller_data(&b47n->nand_chip, b47n); + mtd = nand_to_mtd(&b47n->nand_chip); + mtd->dev.parent = &pdev->dev; + b47n->cc = container_of(nflash, struct bcma_drv_cc, nflash); + + if (b47n->cc->core->bus->chipinfo.id == BCMA_CHIP_ID_BCM4706) { + err = bcm47xxnflash_ops_bcm4706_init(b47n); + } else { + pr_err("Device not supported\n"); + err = -ENOTSUPP; + } + if (err) { + pr_err("Initialization failed: %d\n", err); + return err; + } + + platform_set_drvdata(pdev, b47n); + + err = mtd_device_parse_register(mtd, probes, NULL, NULL, 0); + if (err) { + pr_err("Failed to register MTD device: %d\n", err); + return err; + } + + return 0; +} + +static void bcm47xxnflash_remove(struct platform_device *pdev) +{ + struct bcm47xxnflash *nflash = platform_get_drvdata(pdev); + struct nand_chip *chip = &nflash->nand_chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); +} + +static struct platform_driver bcm47xxnflash_driver = { + .probe = bcm47xxnflash_probe, + .remove = bcm47xxnflash_remove, + .driver = { + .name = "bcma_nflash", + }, +}; + +module_platform_driver(bcm47xxnflash_driver); diff --git a/drivers/mtd/nand/raw/bcm47xxnflash/ops_bcm4706.c b/drivers/mtd/nand/raw/bcm47xxnflash/ops_bcm4706.c new file mode 100644 index 000000000..e532c3535 --- /dev/null +++ b/drivers/mtd/nand/raw/bcm47xxnflash/ops_bcm4706.c @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * BCM47XX NAND flash driver + * + * Copyright (C) 2012 Rafał Miłecki + */ + +#include "bcm47xxnflash.h" + +#include +#include +#include +#include +#include + +/* Broadcom uses 1'000'000 but it seems to be too many. Tests on WNDR4500 has + * shown ~1000 retries as maximum. */ +#define NFLASH_READY_RETRIES 10000 + +#define NFLASH_SECTOR_SIZE 512 + +#define NCTL_CMD0 0x00010000 +#define NCTL_COL 0x00020000 /* Update column with value from BCMA_CC_NFLASH_COL_ADDR */ +#define NCTL_ROW 0x00040000 /* Update row (page) with value from BCMA_CC_NFLASH_ROW_ADDR */ +#define NCTL_CMD1W 0x00080000 +#define NCTL_READ 0x00100000 +#define NCTL_WRITE 0x00200000 +#define NCTL_SPECADDR 0x01000000 +#define NCTL_READY 0x04000000 +#define NCTL_ERR 0x08000000 +#define NCTL_CSA 0x40000000 +#define NCTL_START 0x80000000 + +/************************************************** + * Various helpers + **************************************************/ + +static inline u8 bcm47xxnflash_ops_bcm4706_ns_to_cycle(u16 ns, u16 clock) +{ + return ((ns * 1000 * clock) / 1000000) + 1; +} + +static int bcm47xxnflash_ops_bcm4706_ctl_cmd(struct bcma_drv_cc *cc, u32 code) +{ + int i = 0; + + bcma_cc_write32(cc, BCMA_CC_NFLASH_CTL, NCTL_START | code); + for (i = 0; i < NFLASH_READY_RETRIES; i++) { + if (!(bcma_cc_read32(cc, BCMA_CC_NFLASH_CTL) & NCTL_START)) { + i = 0; + break; + } + } + if (i) { + pr_err("NFLASH control command not ready!\n"); + return -EBUSY; + } + return 0; +} + +static int bcm47xxnflash_ops_bcm4706_poll(struct bcma_drv_cc *cc) +{ + int i; + + for (i = 0; i < NFLASH_READY_RETRIES; i++) { + if (bcma_cc_read32(cc, BCMA_CC_NFLASH_CTL) & NCTL_READY) { + if (bcma_cc_read32(cc, BCMA_CC_NFLASH_CTL) & + BCMA_CC_NFLASH_CTL_ERR) { + pr_err("Error on polling\n"); + return -EBUSY; + } else { + return 0; + } + } + } + + pr_err("Polling timeout!\n"); + return -EBUSY; +} + +/************************************************** + * R/W + **************************************************/ + +static void bcm47xxnflash_ops_bcm4706_read(struct mtd_info *mtd, uint8_t *buf, + int len) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + struct bcm47xxnflash *b47n = nand_get_controller_data(nand_chip); + + u32 ctlcode; + u32 *dest = (u32 *)buf; + int i; + int toread; + + BUG_ON(b47n->curr_page_addr & ~nand_chip->pagemask); + /* Don't validate column using nand_chip->page_shift, it may be bigger + * when accessing OOB */ + + while (len) { + /* We can read maximum of 0x200 bytes at once */ + toread = min(len, 0x200); + + /* Set page and column */ + bcma_cc_write32(b47n->cc, BCMA_CC_NFLASH_COL_ADDR, + b47n->curr_column); + bcma_cc_write32(b47n->cc, BCMA_CC_NFLASH_ROW_ADDR, + b47n->curr_page_addr); + + /* Prepare to read */ + ctlcode = NCTL_CSA | NCTL_CMD1W | NCTL_ROW | NCTL_COL | + NCTL_CMD0; + ctlcode |= NAND_CMD_READSTART << 8; + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc, ctlcode)) + return; + if (bcm47xxnflash_ops_bcm4706_poll(b47n->cc)) + return; + + /* Eventually read some data :) */ + for (i = 0; i < toread; i += 4, dest++) { + ctlcode = NCTL_CSA | 0x30000000 | NCTL_READ; + if (i == toread - 4) /* Last read goes without that */ + ctlcode &= ~NCTL_CSA; + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc, + ctlcode)) + return; + *dest = bcma_cc_read32(b47n->cc, BCMA_CC_NFLASH_DATA); + } + + b47n->curr_column += toread; + len -= toread; + } +} + +static void bcm47xxnflash_ops_bcm4706_write(struct mtd_info *mtd, + const uint8_t *buf, int len) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + struct bcm47xxnflash *b47n = nand_get_controller_data(nand_chip); + struct bcma_drv_cc *cc = b47n->cc; + + u32 ctlcode; + const u32 *data = (u32 *)buf; + int i; + + BUG_ON(b47n->curr_page_addr & ~nand_chip->pagemask); + /* Don't validate column using nand_chip->page_shift, it may be bigger + * when accessing OOB */ + + for (i = 0; i < len; i += 4, data++) { + bcma_cc_write32(cc, BCMA_CC_NFLASH_DATA, *data); + + ctlcode = NCTL_CSA | 0x30000000 | NCTL_WRITE; + if (i == len - 4) /* Last read goes without that */ + ctlcode &= ~NCTL_CSA; + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, ctlcode)) { + pr_err("%s ctl_cmd didn't work!\n", __func__); + return; + } + } + + b47n->curr_column += len; +} + +/************************************************** + * NAND chip ops + **************************************************/ + +static void bcm47xxnflash_ops_bcm4706_cmd_ctrl(struct nand_chip *nand_chip, + int cmd, unsigned int ctrl) +{ + struct bcm47xxnflash *b47n = nand_get_controller_data(nand_chip); + u32 code = 0; + int rc; + + if (cmd == NAND_CMD_NONE) + return; + + if (cmd & NAND_CTRL_CLE) + code = cmd | NCTL_CMD0; + + /* nCS is not needed for reset command */ + if (cmd != NAND_CMD_RESET) + code |= NCTL_CSA; + + 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 */ +static void bcm47xxnflash_ops_bcm4706_select_chip(struct nand_chip *chip, + int cs) +{ + return; +} + +static int bcm47xxnflash_ops_bcm4706_dev_ready(struct nand_chip *nand_chip) +{ + struct bcm47xxnflash *b47n = nand_get_controller_data(nand_chip); + + return !!(bcma_cc_read32(b47n->cc, BCMA_CC_NFLASH_CTL) & NCTL_READY); +} + +/* + * Default nand_command and nand_command_lp don't match BCM4706 hardware layout. + * For example, reading chip id is performed in a non-standard way. + * Setting column and page is also handled differently, we use a special + * registers of ChipCommon core. Hacking cmd_ctrl to understand and convert + * standard commands would be much more complicated. + */ +static void bcm47xxnflash_ops_bcm4706_cmdfunc(struct nand_chip *nand_chip, + unsigned command, int column, + int page_addr) +{ + struct mtd_info *mtd = nand_to_mtd(nand_chip); + struct bcm47xxnflash *b47n = nand_get_controller_data(nand_chip); + struct bcma_drv_cc *cc = b47n->cc; + u32 ctlcode; + int i; + + if (column != -1) + b47n->curr_column = column; + if (page_addr != -1) + b47n->curr_page_addr = page_addr; + + switch (command) { + case NAND_CMD_RESET: + nand_chip->legacy.cmd_ctrl(nand_chip, command, NAND_CTRL_CLE); + + ndelay(100); + nand_wait_ready(nand_chip); + break; + case NAND_CMD_READID: + ctlcode = NCTL_CSA | 0x01000000 | NCTL_CMD1W | NCTL_CMD0; + ctlcode |= NAND_CMD_READID; + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc, ctlcode)) { + pr_err("READID error\n"); + break; + } + + /* + * Reading is specific, last one has to go without NCTL_CSA + * bit. We don't know how many reads NAND subsystem is going + * to perform, so cache everything. + */ + for (i = 0; i < ARRAY_SIZE(b47n->id_data); i++) { + ctlcode = NCTL_CSA | NCTL_READ; + if (i == ARRAY_SIZE(b47n->id_data) - 1) + ctlcode &= ~NCTL_CSA; + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(b47n->cc, + ctlcode)) { + pr_err("READID error\n"); + break; + } + b47n->id_data[i] = + bcma_cc_read32(b47n->cc, BCMA_CC_NFLASH_DATA) + & 0xFF; + } + + break; + case NAND_CMD_STATUS: + ctlcode = NCTL_CSA | NCTL_CMD0 | NAND_CMD_STATUS; + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, ctlcode)) + pr_err("STATUS command error\n"); + break; + case NAND_CMD_READ0: + break; + case NAND_CMD_READOOB: + if (page_addr != -1) + b47n->curr_column += mtd->writesize; + break; + case NAND_CMD_ERASE1: + bcma_cc_write32(cc, BCMA_CC_NFLASH_ROW_ADDR, + b47n->curr_page_addr); + ctlcode = NCTL_ROW | NCTL_CMD1W | NCTL_CMD0 | + NAND_CMD_ERASE1 | (NAND_CMD_ERASE2 << 8); + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, ctlcode)) + pr_err("ERASE1 failed\n"); + break; + case NAND_CMD_ERASE2: + break; + case NAND_CMD_SEQIN: + /* Set page and column */ + bcma_cc_write32(cc, BCMA_CC_NFLASH_COL_ADDR, + b47n->curr_column); + bcma_cc_write32(cc, BCMA_CC_NFLASH_ROW_ADDR, + b47n->curr_page_addr); + + /* Prepare to write */ + ctlcode = 0x40000000 | NCTL_ROW | NCTL_COL | NCTL_CMD0; + ctlcode |= NAND_CMD_SEQIN; + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, ctlcode)) + pr_err("SEQIN failed\n"); + break; + case NAND_CMD_PAGEPROG: + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, NCTL_CMD0 | + NAND_CMD_PAGEPROG)) + pr_err("PAGEPROG failed\n"); + if (bcm47xxnflash_ops_bcm4706_poll(cc)) + pr_err("PAGEPROG not ready\n"); + break; + default: + pr_err("Command 0x%X unsupported\n", command); + break; + } + b47n->curr_command = command; +} + +static u8 bcm47xxnflash_ops_bcm4706_read_byte(struct nand_chip *nand_chip) +{ + struct mtd_info *mtd = nand_to_mtd(nand_chip); + struct bcm47xxnflash *b47n = nand_get_controller_data(nand_chip); + struct bcma_drv_cc *cc = b47n->cc; + u32 tmp = 0; + + switch (b47n->curr_command) { + case NAND_CMD_READID: + if (b47n->curr_column >= ARRAY_SIZE(b47n->id_data)) { + pr_err("Requested invalid id_data: %d\n", + b47n->curr_column); + return 0; + } + return b47n->id_data[b47n->curr_column++]; + case NAND_CMD_STATUS: + if (bcm47xxnflash_ops_bcm4706_ctl_cmd(cc, NCTL_READ)) + return 0; + return bcma_cc_read32(cc, BCMA_CC_NFLASH_DATA) & 0xff; + case NAND_CMD_READOOB: + bcm47xxnflash_ops_bcm4706_read(mtd, (u8 *)&tmp, 4); + return tmp & 0xFF; + } + + pr_err("Invalid command for byte read: 0x%X\n", b47n->curr_command); + return 0; +} + +static void bcm47xxnflash_ops_bcm4706_read_buf(struct nand_chip *nand_chip, + uint8_t *buf, int len) +{ + struct bcm47xxnflash *b47n = nand_get_controller_data(nand_chip); + + switch (b47n->curr_command) { + case NAND_CMD_READ0: + case NAND_CMD_READOOB: + bcm47xxnflash_ops_bcm4706_read(nand_to_mtd(nand_chip), buf, + len); + return; + } + + pr_err("Invalid command for buf read: 0x%X\n", b47n->curr_command); +} + +static void bcm47xxnflash_ops_bcm4706_write_buf(struct nand_chip *nand_chip, + const uint8_t *buf, int len) +{ + struct bcm47xxnflash *b47n = nand_get_controller_data(nand_chip); + + switch (b47n->curr_command) { + case NAND_CMD_SEQIN: + bcm47xxnflash_ops_bcm4706_write(nand_to_mtd(nand_chip), buf, + len); + return; + } + + pr_err("Invalid command for buf write: 0x%X\n", b47n->curr_command); +} + +/************************************************** + * Init + **************************************************/ + +int bcm47xxnflash_ops_bcm4706_init(struct bcm47xxnflash *b47n) +{ + struct nand_chip *nand_chip = (struct nand_chip *)&b47n->nand_chip; + int err; + u32 freq; + u16 clock; + u8 w0, w1, w2, w3, w4; + + unsigned long chipsize; /* MiB */ + u8 tbits, col_bits, col_size, row_bits, row_bsize; + u32 val; + + nand_chip->legacy.select_chip = bcm47xxnflash_ops_bcm4706_select_chip; + nand_chip->legacy.cmd_ctrl = bcm47xxnflash_ops_bcm4706_cmd_ctrl; + nand_chip->legacy.dev_ready = bcm47xxnflash_ops_bcm4706_dev_ready; + b47n->nand_chip.legacy.cmdfunc = bcm47xxnflash_ops_bcm4706_cmdfunc; + b47n->nand_chip.legacy.read_byte = bcm47xxnflash_ops_bcm4706_read_byte; + b47n->nand_chip.legacy.read_buf = bcm47xxnflash_ops_bcm4706_read_buf; + b47n->nand_chip.legacy.write_buf = bcm47xxnflash_ops_bcm4706_write_buf; + b47n->nand_chip.legacy.set_features = nand_get_set_features_notsupp; + b47n->nand_chip.legacy.get_features = nand_get_set_features_notsupp; + + nand_chip->legacy.chip_delay = 50; + b47n->nand_chip.bbt_options = NAND_BBT_USE_FLASH; + /* TODO: implement ECC */ + b47n->nand_chip.ecc.engine_type = NAND_ECC_ENGINE_TYPE_NONE; + + /* Enable NAND flash access */ + bcma_cc_set32(b47n->cc, BCMA_CC_4706_FLASHSCFG, + BCMA_CC_4706_FLASHSCFG_NF1); + + /* Configure wait counters */ + if (b47n->cc->status & BCMA_CC_CHIPST_4706_PKG_OPTION) { + /* 400 MHz */ + freq = 400000000 / 4; + } else { + freq = bcma_chipco_pll_read(b47n->cc, 4); + freq = (freq & 0xFFF) >> 3; + /* Fixed reference clock 25 MHz and m = 2 */ + freq = (freq * 25000000 / 2) / 4; + } + clock = freq / 1000000; + w0 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(15, clock); + w1 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(20, clock); + w2 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(10, clock); + w3 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(10, clock); + w4 = bcm47xxnflash_ops_bcm4706_ns_to_cycle(100, clock); + bcma_cc_write32(b47n->cc, BCMA_CC_NFLASH_WAITCNT0, + (w4 << 24 | w3 << 18 | w2 << 12 | w1 << 6 | w0)); + + /* Scan NAND */ + err = nand_scan(&b47n->nand_chip, 1); + if (err) { + pr_err("Could not scan NAND flash: %d\n", err); + goto exit; + } + + /* Configure FLASH */ + chipsize = nanddev_target_size(&b47n->nand_chip.base) >> 20; + tbits = ffs(chipsize); /* find first bit set */ + if (!tbits || tbits != fls(chipsize)) { + pr_err("Invalid flash size: 0x%lX\n", chipsize); + err = -ENOTSUPP; + goto exit; + } + tbits += 19; /* Broadcom increases *index* by 20, we increase *pos* */ + + col_bits = b47n->nand_chip.page_shift + 1; + col_size = (col_bits + 7) / 8; + + row_bits = tbits - col_bits + 1; + row_bsize = (row_bits + 7) / 8; + + val = ((row_bsize - 1) << 6) | ((col_size - 1) << 4) | 2; + bcma_cc_write32(b47n->cc, BCMA_CC_NFLASH_CONF, val); + +exit: + if (err) + bcma_cc_mask32(b47n->cc, BCMA_CC_4706_FLASHSCFG, + ~BCMA_CC_4706_FLASHSCFG_NF1); + return err; +} diff --git a/drivers/mtd/nand/raw/brcmnand/Kconfig b/drivers/mtd/nand/raw/brcmnand/Kconfig new file mode 100644 index 000000000..4bc51bf60 --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/Kconfig @@ -0,0 +1,49 @@ +config MTD_NAND_BRCMNAND + tristate "Broadcom STB NAND controller" + depends on ARM || ARM64 || MIPS || COMPILE_TEST + depends on HAS_IOMEM + help + Enables the Broadcom NAND controller driver. The controller was + originally designed for Set-Top Box but is used on various BCM7xxx, + BCM3xxx, BCM63xxx, iProc/Cygnus and more. + +if MTD_NAND_BRCMNAND + +config MTD_NAND_BRCMNAND_BCM63XX + tristate "Broadcom BCM63xx NAND controller glue" + default BCM63XX + help + Enables the BRCMNAND glue driver to register the NAND controller + on Broadcom BCM63xx MIPS-based DSL platforms. + +config MTD_NAND_BRCMNAND_BCMA + tristate "Broadcom BCMA NAND controller" + depends on BCMA_NFLASH + depends on BCMA + help + Enables the BRCMNAND controller over BCMA on BCM47186/BCM5358 SoCs. + The glue driver will take care of performing the low-level I/O + operations to interface the BRCMNAND controller over the BCMA bus. + +config MTD_NAND_BRCMNAND_BCMBCA + tristate "Broadcom BCMBCA NAND controller glue" + default ARCH_BCMBCA + help + Enables the BRCMNAND glue driver to register the NAND controller + on Broadcom BCA platforms. + +config MTD_NAND_BRCMNAND_BRCMSTB + tristate "Broadcom STB Nand controller glue" + default ARCH_BRCMSTB + help + Enables the BRCMNAND glue driver to register the NAND controller + on Broadcom STB platforms. + +config MTD_NAND_BRCMNAND_IPROC + tristate "Broadcom iProc NAND controller glue" + default ARCH_BCM_IPROC + help + Enables the BRCMNAND controller glue driver to register the NAND + controller on Broadcom iProc platforms. + +endif # MTD_NAND_BRCMNAND diff --git a/drivers/mtd/nand/raw/brcmnand/Makefile b/drivers/mtd/nand/raw/brcmnand/Makefile new file mode 100644 index 000000000..0536568c6 --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# link order matters; don't link the more generic brcmstb_nand.o before the +# more specific iproc_nand.o, for instance +obj-$(CONFIG_MTD_NAND_BRCMNAND_IPROC) += iproc_nand.o +obj-$(CONFIG_MTD_NAND_BRCMNAND_BCMBCA) += bcmbca_nand.o +obj-$(CONFIG_MTD_NAND_BRCMNAND_BCM63XX) += bcm6368_nand.o +obj-$(CONFIG_MTD_NAND_BRCMNAND_BRCMSTB) += brcmstb_nand.o +obj-$(CONFIG_MTD_NAND_BRCMNAND) += brcmnand.o + +obj-$(CONFIG_MTD_NAND_BRCMNAND_BCMA) += bcma_nand.o diff --git a/drivers/mtd/nand/raw/brcmnand/bcm6368_nand.c b/drivers/mtd/nand/raw/brcmnand/bcm6368_nand.c new file mode 100644 index 000000000..a06cd87f8 --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/bcm6368_nand.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright 2015 Simon Arlott + * + * Derived from bcm63138_nand.c: + * Copyright © 2015 Broadcom Corporation + * + * Derived from bcm963xx_4.12L.06B_consumer/shared/opensource/include/bcm963xx/63268_map_part.h: + * Copyright 2000-2010 Broadcom Corporation + * + * Derived from bcm963xx_4.12L.06B_consumer/shared/opensource/flash/nandflash.c: + * Copyright 2000-2010 Broadcom Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "brcmnand.h" + +struct bcm6368_nand_soc { + struct brcmnand_soc soc; + void __iomem *base; +}; + +#define BCM6368_NAND_INT 0x00 +#define BCM6368_NAND_STATUS_SHIFT 0 +#define BCM6368_NAND_STATUS_MASK (0xfff << BCM6368_NAND_STATUS_SHIFT) +#define BCM6368_NAND_ENABLE_SHIFT 16 +#define BCM6368_NAND_ENABLE_MASK (0xffff << BCM6368_NAND_ENABLE_SHIFT) +#define BCM6368_NAND_BASE_ADDR0 0x04 +#define BCM6368_NAND_BASE_ADDR1 0x0c + +enum { + BCM6368_NP_READ = BIT(0), + BCM6368_BLOCK_ERASE = BIT(1), + BCM6368_COPY_BACK = BIT(2), + BCM6368_PAGE_PGM = BIT(3), + BCM6368_CTRL_READY = BIT(4), + BCM6368_DEV_RBPIN = BIT(5), + BCM6368_ECC_ERR_UNC = BIT(6), + BCM6368_ECC_ERR_CORR = BIT(7), +}; + +static bool bcm6368_nand_intc_ack(struct brcmnand_soc *soc) +{ + struct bcm6368_nand_soc *priv = + container_of(soc, struct bcm6368_nand_soc, soc); + void __iomem *mmio = priv->base + BCM6368_NAND_INT; + u32 val = brcmnand_readl(mmio); + + if (val & (BCM6368_CTRL_READY << BCM6368_NAND_STATUS_SHIFT)) { + /* Ack interrupt */ + val &= ~BCM6368_NAND_STATUS_MASK; + val |= BCM6368_CTRL_READY << BCM6368_NAND_STATUS_SHIFT; + brcmnand_writel(val, mmio); + return true; + } + + return false; +} + +static void bcm6368_nand_intc_set(struct brcmnand_soc *soc, bool en) +{ + struct bcm6368_nand_soc *priv = + container_of(soc, struct bcm6368_nand_soc, soc); + void __iomem *mmio = priv->base + BCM6368_NAND_INT; + u32 val = brcmnand_readl(mmio); + + /* Don't ack any interrupts */ + val &= ~BCM6368_NAND_STATUS_MASK; + + if (en) + val |= BCM6368_CTRL_READY << BCM6368_NAND_ENABLE_SHIFT; + else + val &= ~(BCM6368_CTRL_READY << BCM6368_NAND_ENABLE_SHIFT); + + brcmnand_writel(val, mmio); +} + +static int bcm6368_nand_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bcm6368_nand_soc *priv; + struct brcmnand_soc *soc; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + soc = &priv->soc; + + priv->base = devm_platform_ioremap_resource_byname(pdev, "nand-int-base"); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + soc->ctlrdy_ack = bcm6368_nand_intc_ack; + soc->ctlrdy_set_enabled = bcm6368_nand_intc_set; + + /* Disable and ack all interrupts */ + brcmnand_writel(0, priv->base + BCM6368_NAND_INT); + brcmnand_writel(BCM6368_NAND_STATUS_MASK, + priv->base + BCM6368_NAND_INT); + + return brcmnand_probe(pdev, soc); +} + +static const struct of_device_id bcm6368_nand_of_match[] = { + { .compatible = "brcm,nand-bcm6368" }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcm6368_nand_of_match); + +static struct platform_driver bcm6368_nand_driver = { + .probe = bcm6368_nand_probe, + .remove = brcmnand_remove, + .driver = { + .name = "bcm6368_nand", + .pm = &brcmnand_pm_ops, + .of_match_table = bcm6368_nand_of_match, + } +}; +module_platform_driver(bcm6368_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Simon Arlott"); +MODULE_DESCRIPTION("NAND driver for BCM6368"); diff --git a/drivers/mtd/nand/raw/brcmnand/bcma_nand.c b/drivers/mtd/nand/raw/brcmnand/bcma_nand.c new file mode 100644 index 000000000..dd2797791 --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/bcma_nand.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright © 2021 Broadcom + */ +#include +#include +#include +#include +#include + +#include "brcmnand.h" + +struct brcmnand_bcma_soc { + struct brcmnand_soc soc; + struct bcma_drv_cc *cc; +}; + +static inline bool brcmnand_bcma_needs_swapping(u32 offset) +{ + switch (offset) { + case BCMA_CC_NAND_SPARE_RD0: + case BCMA_CC_NAND_SPARE_RD4: + case BCMA_CC_NAND_SPARE_RD8: + case BCMA_CC_NAND_SPARE_RD12: + case BCMA_CC_NAND_SPARE_WR0: + case BCMA_CC_NAND_SPARE_WR4: + case BCMA_CC_NAND_SPARE_WR8: + case BCMA_CC_NAND_SPARE_WR12: + case BCMA_CC_NAND_DEVID: + case BCMA_CC_NAND_DEVID_X: + case BCMA_CC_NAND_SPARE_RD16: + case BCMA_CC_NAND_SPARE_RD20: + case BCMA_CC_NAND_SPARE_RD24: + case BCMA_CC_NAND_SPARE_RD28: + return true; + } + + return false; +} + +static inline struct brcmnand_bcma_soc *to_bcma_soc(struct brcmnand_soc *soc) +{ + return container_of(soc, struct brcmnand_bcma_soc, soc); +} + +static u32 brcmnand_bcma_read_reg(struct brcmnand_soc *soc, u32 offset) +{ + struct brcmnand_bcma_soc *sc = to_bcma_soc(soc); + u32 val; + + /* Offset into the NAND block and deal with the flash cache separately */ + if (offset == BRCMNAND_NON_MMIO_FC_ADDR) + offset = BCMA_CC_NAND_CACHE_DATA; + else + offset += BCMA_CC_NAND_REVISION; + + val = bcma_cc_read32(sc->cc, offset); + + /* Swap if necessary */ + if (brcmnand_bcma_needs_swapping(offset)) + val = be32_to_cpu((__force __be32)val); + return val; +} + +static void brcmnand_bcma_write_reg(struct brcmnand_soc *soc, u32 val, + u32 offset) +{ + struct brcmnand_bcma_soc *sc = to_bcma_soc(soc); + + /* Offset into the NAND block */ + if (offset == BRCMNAND_NON_MMIO_FC_ADDR) + offset = BCMA_CC_NAND_CACHE_DATA; + else + offset += BCMA_CC_NAND_REVISION; + + /* Swap if necessary */ + if (brcmnand_bcma_needs_swapping(offset)) + val = (__force u32)cpu_to_be32(val); + + bcma_cc_write32(sc->cc, offset, val); +} + +static struct brcmnand_io_ops brcmnand_bcma_io_ops = { + .read_reg = brcmnand_bcma_read_reg, + .write_reg = brcmnand_bcma_write_reg, +}; + +static void brcmnand_bcma_prepare_data_bus(struct brcmnand_soc *soc, bool prepare, + bool is_param) +{ + struct brcmnand_bcma_soc *sc = to_bcma_soc(soc); + + /* Reset the cache address to ensure we are already accessing the + * beginning of a sub-page. + */ + bcma_cc_write32(sc->cc, BCMA_CC_NAND_CACHE_ADDR, 0); +} + +static int brcmnand_bcma_nand_probe(struct platform_device *pdev) +{ + struct bcma_nflash *nflash = dev_get_platdata(&pdev->dev); + struct brcmnand_bcma_soc *soc; + + soc = devm_kzalloc(&pdev->dev, sizeof(*soc), GFP_KERNEL); + if (!soc) + return -ENOMEM; + + soc->cc = container_of(nflash, struct bcma_drv_cc, nflash); + soc->soc.prepare_data_bus = brcmnand_bcma_prepare_data_bus; + soc->soc.ops = &brcmnand_bcma_io_ops; + + if (soc->cc->core->bus->chipinfo.id == BCMA_CHIP_ID_BCM4706) { + dev_err(&pdev->dev, "Use bcm47xxnflash for 4706!\n"); + return -ENODEV; + } + + return brcmnand_probe(pdev, &soc->soc); +} + +static struct platform_driver brcmnand_bcma_nand_driver = { + .probe = brcmnand_bcma_nand_probe, + .remove = brcmnand_remove, + .driver = { + .name = "bcma_brcmnand", + .pm = &brcmnand_pm_ops, + } +}; +module_platform_driver(brcmnand_bcma_nand_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Broadcom"); +MODULE_DESCRIPTION("NAND controller driver glue for BCMA chips"); diff --git a/drivers/mtd/nand/raw/brcmnand/bcmbca_nand.c b/drivers/mtd/nand/raw/brcmnand/bcmbca_nand.c new file mode 100644 index 000000000..c31d7f37d --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/bcmbca_nand.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright © 2015 Broadcom Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "brcmnand.h" + +struct bcmbca_nand_soc { + struct brcmnand_soc soc; + void __iomem *base; +}; + +#define BCMBCA_NAND_INT_STATUS 0x00 +#define BCMBCA_NAND_INT_EN 0x04 + +enum { + BCMBCA_CTLRDY = BIT(4), +}; + +#if defined(CONFIG_ARM64) +#define ALIGN_REQ 8 +#else +#define ALIGN_REQ 4 +#endif + +static inline bool bcmbca_nand_is_buf_aligned(void *flash_cache, void *buffer) +{ + return IS_ALIGNED((uintptr_t)buffer, ALIGN_REQ) && + IS_ALIGNED((uintptr_t)flash_cache, ALIGN_REQ); +} + +static bool bcmbca_nand_intc_ack(struct brcmnand_soc *soc) +{ + struct bcmbca_nand_soc *priv = + container_of(soc, struct bcmbca_nand_soc, soc); + void __iomem *mmio = priv->base + BCMBCA_NAND_INT_STATUS; + u32 val = brcmnand_readl(mmio); + + if (val & BCMBCA_CTLRDY) { + brcmnand_writel(val & ~BCMBCA_CTLRDY, mmio); + return true; + } + + return false; +} + +static void bcmbca_nand_intc_set(struct brcmnand_soc *soc, bool en) +{ + struct bcmbca_nand_soc *priv = + container_of(soc, struct bcmbca_nand_soc, soc); + void __iomem *mmio = priv->base + BCMBCA_NAND_INT_EN; + u32 val = brcmnand_readl(mmio); + + if (en) + val |= BCMBCA_CTLRDY; + else + val &= ~BCMBCA_CTLRDY; + + brcmnand_writel(val, mmio); +} + +static void bcmbca_read_data_bus(struct brcmnand_soc *soc, + void __iomem *flash_cache, u32 *buffer, int fc_words) +{ + /* + * memcpy can do unaligned aligned access depending on source + * and dest address, which is incompatible with nand cache. Fallback + * to the memcpy_fromio in such case + */ + if (bcmbca_nand_is_buf_aligned((void __force *)flash_cache, buffer)) + memcpy((void *)buffer, (void __force *)flash_cache, fc_words * 4); + else + memcpy_fromio((void *)buffer, flash_cache, fc_words * 4); +} + +static int bcmbca_nand_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bcmbca_nand_soc *priv; + struct brcmnand_soc *soc; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + soc = &priv->soc; + + priv->base = devm_platform_ioremap_resource_byname(pdev, "nand-int-base"); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + soc->ctlrdy_ack = bcmbca_nand_intc_ack; + soc->ctlrdy_set_enabled = bcmbca_nand_intc_set; + soc->read_data_bus = bcmbca_read_data_bus; + + return brcmnand_probe(pdev, soc); +} + +static const struct of_device_id bcmbca_nand_of_match[] = { + { .compatible = "brcm,nand-bcm63138" }, + {}, +}; +MODULE_DEVICE_TABLE(of, bcmbca_nand_of_match); + +static struct platform_driver bcmbca_nand_driver = { + .probe = bcmbca_nand_probe, + .remove = brcmnand_remove, + .driver = { + .name = "bcmbca_nand", + .pm = &brcmnand_pm_ops, + .of_match_table = bcmbca_nand_of_match, + } +}; +module_platform_driver(bcmbca_nand_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Brian Norris"); +MODULE_DESCRIPTION("NAND driver for BCMBCA"); diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.c b/drivers/mtd/nand/raw/brcmnand/brcmnand.c new file mode 100644 index 000000000..5b9dadd54 --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.c @@ -0,0 +1,3585 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright © 2010-2015 Broadcom Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "brcmnand.h" + +/* + * This flag controls if WP stays on between erase/write commands to mitigate + * flash corruption due to power glitches. Values: + * 0: NAND_WP is not used or not available + * 1: NAND_WP is set by default, cleared for erase/write operations + * 2: NAND_WP is always cleared + */ +static int wp_on = 1; +module_param(wp_on, int, 0444); + +/*********************************************************************** + * Definitions + ***********************************************************************/ + +#define DRV_NAME "brcmnand" + +#define CMD_NULL 0x00 +#define CMD_PAGE_READ 0x01 +#define CMD_SPARE_AREA_READ 0x02 +#define CMD_STATUS_READ 0x03 +#define CMD_PROGRAM_PAGE 0x04 +#define CMD_PROGRAM_SPARE_AREA 0x05 +#define CMD_COPY_BACK 0x06 +#define CMD_DEVICE_ID_READ 0x07 +#define CMD_BLOCK_ERASE 0x08 +#define CMD_FLASH_RESET 0x09 +#define CMD_BLOCKS_LOCK 0x0a +#define CMD_BLOCKS_LOCK_DOWN 0x0b +#define CMD_BLOCKS_UNLOCK 0x0c +#define CMD_READ_BLOCKS_LOCK_STATUS 0x0d +#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; + u32 next_desc_ext; + u32 cmd_irq; + u32 dram_addr; + u32 dram_addr_ext; + u32 tfr_len; + u32 total_len; + u32 flash_addr; + u32 flash_addr_ext; + u32 cs; + u32 pad2[5]; + u32 status_valid; +} __packed; + +/* Bitfields for brcm_nand_dma_desc::status_valid */ +#define FLASH_DMA_ECC_ERROR (1 << 8) +#define FLASH_DMA_CORR_ERROR (1 << 9) + +/* Bitfields for DMA_MODE */ +#define FLASH_DMA_MODE_STOP_ON_ERROR BIT(1) /* stop in Uncorr ECC error */ +#define FLASH_DMA_MODE_MODE BIT(0) /* link list */ +#define FLASH_DMA_MODE_MASK (FLASH_DMA_MODE_STOP_ON_ERROR | \ + FLASH_DMA_MODE_MODE) + +/* 512B flash cache in the NAND controller HW */ +#define FC_SHIFT 9U +#define FC_BYTES 512U +#define FC_WORDS (FC_BYTES >> 2) + +#define BRCMNAND_MIN_PAGESIZE 512 +#define BRCMNAND_MIN_BLOCKSIZE (8 * 1024) +#define BRCMNAND_MIN_DEVSIZE (4ULL * 1024 * 1024) + +#define NAND_CTRL_RDY (INTFC_CTLR_READY | INTFC_FLASH_READY) +#define NAND_POLL_STATUS_TIMEOUT_MS 500 + +#define EDU_CMD_WRITE 0x00 +#define EDU_CMD_READ 0x01 +#define EDU_STATUS_ACTIVE BIT(0) +#define EDU_ERR_STATUS_ERRACK BIT(0) +#define EDU_DONE_MASK GENMASK(1, 0) + +#define EDU_CONFIG_MODE_NAND BIT(0) +#define EDU_CONFIG_SWAP_BYTE BIT(1) +#ifdef CONFIG_CPU_BIG_ENDIAN +#define EDU_CONFIG_SWAP_CFG EDU_CONFIG_SWAP_BYTE +#else +#define EDU_CONFIG_SWAP_CFG 0 +#endif + +/* edu registers */ +enum edu_reg { + EDU_CONFIG = 0, + EDU_DRAM_ADDR, + EDU_EXT_ADDR, + EDU_LENGTH, + EDU_CMD, + EDU_STOP, + EDU_STATUS, + EDU_DONE, + EDU_ERR_STATUS, +}; + +static const u16 edu_regs[] = { + [EDU_CONFIG] = 0x00, + [EDU_DRAM_ADDR] = 0x04, + [EDU_EXT_ADDR] = 0x08, + [EDU_LENGTH] = 0x0c, + [EDU_CMD] = 0x10, + [EDU_STOP] = 0x14, + [EDU_STATUS] = 0x18, + [EDU_DONE] = 0x1c, + [EDU_ERR_STATUS] = 0x20, +}; + +/* flash_dma registers */ +enum flash_dma_reg { + FLASH_DMA_REVISION = 0, + FLASH_DMA_FIRST_DESC, + FLASH_DMA_FIRST_DESC_EXT, + FLASH_DMA_CTRL, + FLASH_DMA_MODE, + FLASH_DMA_STATUS, + FLASH_DMA_INTERRUPT_DESC, + FLASH_DMA_INTERRUPT_DESC_EXT, + FLASH_DMA_ERROR_STATUS, + FLASH_DMA_CURRENT_DESC, + FLASH_DMA_CURRENT_DESC_EXT, +}; + +/* flash_dma registers v0*/ +static const u16 flash_dma_regs_v0[] = { + [FLASH_DMA_REVISION] = 0x00, + [FLASH_DMA_FIRST_DESC] = 0x04, + [FLASH_DMA_CTRL] = 0x08, + [FLASH_DMA_MODE] = 0x0c, + [FLASH_DMA_STATUS] = 0x10, + [FLASH_DMA_INTERRUPT_DESC] = 0x14, + [FLASH_DMA_ERROR_STATUS] = 0x18, + [FLASH_DMA_CURRENT_DESC] = 0x1c, +}; + +/* flash_dma registers v1*/ +static const u16 flash_dma_regs_v1[] = { + [FLASH_DMA_REVISION] = 0x00, + [FLASH_DMA_FIRST_DESC] = 0x04, + [FLASH_DMA_FIRST_DESC_EXT] = 0x08, + [FLASH_DMA_CTRL] = 0x0c, + [FLASH_DMA_MODE] = 0x10, + [FLASH_DMA_STATUS] = 0x14, + [FLASH_DMA_INTERRUPT_DESC] = 0x18, + [FLASH_DMA_INTERRUPT_DESC_EXT] = 0x1c, + [FLASH_DMA_ERROR_STATUS] = 0x20, + [FLASH_DMA_CURRENT_DESC] = 0x24, + [FLASH_DMA_CURRENT_DESC_EXT] = 0x28, +}; + +/* flash_dma registers v4 */ +static const u16 flash_dma_regs_v4[] = { + [FLASH_DMA_REVISION] = 0x00, + [FLASH_DMA_FIRST_DESC] = 0x08, + [FLASH_DMA_FIRST_DESC_EXT] = 0x0c, + [FLASH_DMA_CTRL] = 0x10, + [FLASH_DMA_MODE] = 0x14, + [FLASH_DMA_STATUS] = 0x18, + [FLASH_DMA_INTERRUPT_DESC] = 0x20, + [FLASH_DMA_INTERRUPT_DESC_EXT] = 0x24, + [FLASH_DMA_ERROR_STATUS] = 0x28, + [FLASH_DMA_CURRENT_DESC] = 0x30, + [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), + BRCMNAND_HAS_PREFETCH = BIT(1), + BRCMNAND_HAS_CACHE_MODE = BIT(2), + BRCMNAND_HAS_WP = BIT(3), +}; + +struct brcmnand_host; + +static DEFINE_STATIC_KEY_FALSE(brcmnand_soc_has_ops_key); + +struct brcmnand_controller { + struct device *dev; + struct nand_controller controller; + void __iomem *nand_base; + void __iomem *nand_fc; /* flash cache */ + void __iomem *flash_dma_base; + int irq; + unsigned int dma_irq; + int nand_version; + + /* Some SoCs provide custom interrupt status register(s) */ + struct brcmnand_soc *soc; + + /* Some SoCs have a gateable clock for the controller */ + struct clk *clk; + + int cmd_pending; + bool dma_pending; + bool edu_pending; + struct completion done; + struct completion dma_done; + struct completion edu_done; + + /* List of NAND hosts (one for each chip-select) */ + struct list_head host_list; + + /* 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; + int edu_irq; + int edu_count; + u64 edu_dram_addr; + u32 edu_ext_addr; + u32 edu_cmd; + u32 edu_config; + int sas; /* spare area size, per flash cache */ + int sector_size_1k; + u8 *oob; + + /* flash_dma reg */ + const u16 *flash_dma_offsets; + struct brcm_nand_dma_desc *dma_desc; + dma_addr_t dma_pa; + + int (*dma_trans)(struct brcmnand_host *host, u64 addr, u32 *buf, + u8 *oob, u32 len, u8 dma_cmd); + + /* in-memory cache of the FLASH_CACHE, used only for some commands */ + u8 flash_cache[FC_BYTES]; + + /* Controller revision details */ + const u16 *reg_offsets; + unsigned int reg_spacing; /* between CS1, CS2, ... regs */ + const u8 *cs_offsets; /* within each chip-select */ + const u8 *cs0_offsets; /* within CS0, if different */ + unsigned int max_block_size; + const unsigned int *block_sizes; + unsigned int max_page_size; + const unsigned int *page_sizes; + unsigned int page_size_shift; + unsigned int max_oob; + u32 ecc_level_shift; + u32 features; + + /* for low-power standby/resume only */ + u32 nand_cs_nand_select; + u32 nand_cs_nand_xor; + u32 corr_stat_threshold; + u32 flash_dma_mode; + u32 flash_edu_mode; + bool pio_poll_mode; +}; + +struct brcmnand_cfg { + u64 device_size; + unsigned int block_size; + unsigned int page_size; + unsigned int spare_area_size; + unsigned int device_width; + unsigned int col_adr_bytes; + unsigned int blk_adr_bytes; + unsigned int ful_adr_bytes; + unsigned int sector_size_1k; + unsigned int ecc_level; + /* use for low-power standby/resume only */ + u32 acc_control; + u32 config; + u32 config_ext; + u32 timing_1; + u32 timing_2; +}; + +struct brcmnand_host { + struct list_head node; + + struct nand_chip chip; + struct platform_device *pdev; + int cs; + + struct brcmnand_cfg hwcfg; + struct brcmnand_controller *ctrl; +}; + +enum brcmnand_reg { + BRCMNAND_CMD_START = 0, + BRCMNAND_CMD_EXT_ADDRESS, + BRCMNAND_CMD_ADDRESS, + BRCMNAND_INTFC_STATUS, + BRCMNAND_CS_SELECT, + BRCMNAND_CS_XOR, + BRCMNAND_LL_OP, + BRCMNAND_CS0_BASE, + BRCMNAND_CS1_BASE, /* CS1 regs, if non-contiguous */ + BRCMNAND_CORR_THRESHOLD, + 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, + BRCMNAND_UNCORR_ADDR, + BRCMNAND_SEMAPHORE, + BRCMNAND_ID, + BRCMNAND_ID_EXT, + BRCMNAND_LL_RDATA, + BRCMNAND_OOB_READ_BASE, + BRCMNAND_OOB_READ_10_BASE, /* offset 0x10, if non-contiguous */ + BRCMNAND_OOB_WRITE_BASE, + BRCMNAND_OOB_WRITE_10_BASE, /* offset 0x10, if non-contiguous */ + BRCMNAND_FC_BASE, +}; + +/* BRCMNAND v2.1-v2.2 */ +static const u16 brcmnand_regs_v21[] = { + [BRCMNAND_CMD_START] = 0x04, + [BRCMNAND_CMD_EXT_ADDRESS] = 0x08, + [BRCMNAND_CMD_ADDRESS] = 0x0c, + [BRCMNAND_INTFC_STATUS] = 0x5c, + [BRCMNAND_CS_SELECT] = 0x14, + [BRCMNAND_CS_XOR] = 0x18, + [BRCMNAND_LL_OP] = 0, + [BRCMNAND_CS0_BASE] = 0x40, + [BRCMNAND_CS1_BASE] = 0, + [BRCMNAND_CORR_THRESHOLD] = 0, + [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, + [BRCMNAND_UNCORR_ADDR] = 0x6c, + [BRCMNAND_SEMAPHORE] = 0x50, + [BRCMNAND_ID] = 0x54, + [BRCMNAND_ID_EXT] = 0, + [BRCMNAND_LL_RDATA] = 0, + [BRCMNAND_OOB_READ_BASE] = 0x20, + [BRCMNAND_OOB_READ_10_BASE] = 0, + [BRCMNAND_OOB_WRITE_BASE] = 0x30, + [BRCMNAND_OOB_WRITE_10_BASE] = 0, + [BRCMNAND_FC_BASE] = 0x200, +}; + +/* BRCMNAND v3.3-v4.0 */ +static const u16 brcmnand_regs_v33[] = { + [BRCMNAND_CMD_START] = 0x04, + [BRCMNAND_CMD_EXT_ADDRESS] = 0x08, + [BRCMNAND_CMD_ADDRESS] = 0x0c, + [BRCMNAND_INTFC_STATUS] = 0x6c, + [BRCMNAND_CS_SELECT] = 0x14, + [BRCMNAND_CS_XOR] = 0x18, + [BRCMNAND_LL_OP] = 0x178, + [BRCMNAND_CS0_BASE] = 0x40, + [BRCMNAND_CS1_BASE] = 0xd0, + [BRCMNAND_CORR_THRESHOLD] = 0x84, + [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, + [BRCMNAND_UNCORR_ADDR] = 0x7c, + [BRCMNAND_SEMAPHORE] = 0x58, + [BRCMNAND_ID] = 0x60, + [BRCMNAND_ID_EXT] = 0x64, + [BRCMNAND_LL_RDATA] = 0x17c, + [BRCMNAND_OOB_READ_BASE] = 0x20, + [BRCMNAND_OOB_READ_10_BASE] = 0x130, + [BRCMNAND_OOB_WRITE_BASE] = 0x30, + [BRCMNAND_OOB_WRITE_10_BASE] = 0, + [BRCMNAND_FC_BASE] = 0x200, +}; + +/* BRCMNAND v5.0 */ +static const u16 brcmnand_regs_v50[] = { + [BRCMNAND_CMD_START] = 0x04, + [BRCMNAND_CMD_EXT_ADDRESS] = 0x08, + [BRCMNAND_CMD_ADDRESS] = 0x0c, + [BRCMNAND_INTFC_STATUS] = 0x6c, + [BRCMNAND_CS_SELECT] = 0x14, + [BRCMNAND_CS_XOR] = 0x18, + [BRCMNAND_LL_OP] = 0x178, + [BRCMNAND_CS0_BASE] = 0x40, + [BRCMNAND_CS1_BASE] = 0xd0, + [BRCMNAND_CORR_THRESHOLD] = 0x84, + [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, + [BRCMNAND_UNCORR_ADDR] = 0x7c, + [BRCMNAND_SEMAPHORE] = 0x58, + [BRCMNAND_ID] = 0x60, + [BRCMNAND_ID_EXT] = 0x64, + [BRCMNAND_LL_RDATA] = 0x17c, + [BRCMNAND_OOB_READ_BASE] = 0x20, + [BRCMNAND_OOB_READ_10_BASE] = 0x130, + [BRCMNAND_OOB_WRITE_BASE] = 0x30, + [BRCMNAND_OOB_WRITE_10_BASE] = 0x140, + [BRCMNAND_FC_BASE] = 0x200, +}; + +/* BRCMNAND v6.0 - v7.1 */ +static const u16 brcmnand_regs_v60[] = { + [BRCMNAND_CMD_START] = 0x04, + [BRCMNAND_CMD_EXT_ADDRESS] = 0x08, + [BRCMNAND_CMD_ADDRESS] = 0x0c, + [BRCMNAND_INTFC_STATUS] = 0x14, + [BRCMNAND_CS_SELECT] = 0x18, + [BRCMNAND_CS_XOR] = 0x1c, + [BRCMNAND_LL_OP] = 0x20, + [BRCMNAND_CS0_BASE] = 0x50, + [BRCMNAND_CS1_BASE] = 0, + [BRCMNAND_CORR_THRESHOLD] = 0xc0, + [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, + [BRCMNAND_UNCORR_ADDR] = 0x118, + [BRCMNAND_SEMAPHORE] = 0x150, + [BRCMNAND_ID] = 0x194, + [BRCMNAND_ID_EXT] = 0x198, + [BRCMNAND_LL_RDATA] = 0x19c, + [BRCMNAND_OOB_READ_BASE] = 0x200, + [BRCMNAND_OOB_READ_10_BASE] = 0, + [BRCMNAND_OOB_WRITE_BASE] = 0x280, + [BRCMNAND_OOB_WRITE_10_BASE] = 0, + [BRCMNAND_FC_BASE] = 0x400, +}; + +/* BRCMNAND v7.1 */ +static const u16 brcmnand_regs_v71[] = { + [BRCMNAND_CMD_START] = 0x04, + [BRCMNAND_CMD_EXT_ADDRESS] = 0x08, + [BRCMNAND_CMD_ADDRESS] = 0x0c, + [BRCMNAND_INTFC_STATUS] = 0x14, + [BRCMNAND_CS_SELECT] = 0x18, + [BRCMNAND_CS_XOR] = 0x1c, + [BRCMNAND_LL_OP] = 0x20, + [BRCMNAND_CS0_BASE] = 0x50, + [BRCMNAND_CS1_BASE] = 0, + [BRCMNAND_CORR_THRESHOLD] = 0xdc, + [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, + [BRCMNAND_UNCORR_ADDR] = 0x118, + [BRCMNAND_SEMAPHORE] = 0x150, + [BRCMNAND_ID] = 0x194, + [BRCMNAND_ID_EXT] = 0x198, + [BRCMNAND_LL_RDATA] = 0x19c, + [BRCMNAND_OOB_READ_BASE] = 0x200, + [BRCMNAND_OOB_READ_10_BASE] = 0, + [BRCMNAND_OOB_WRITE_BASE] = 0x280, + [BRCMNAND_OOB_WRITE_10_BASE] = 0, + [BRCMNAND_FC_BASE] = 0x400, +}; + +/* BRCMNAND v7.2 */ +static const u16 brcmnand_regs_v72[] = { + [BRCMNAND_CMD_START] = 0x04, + [BRCMNAND_CMD_EXT_ADDRESS] = 0x08, + [BRCMNAND_CMD_ADDRESS] = 0x0c, + [BRCMNAND_INTFC_STATUS] = 0x14, + [BRCMNAND_CS_SELECT] = 0x18, + [BRCMNAND_CS_XOR] = 0x1c, + [BRCMNAND_LL_OP] = 0x20, + [BRCMNAND_CS0_BASE] = 0x50, + [BRCMNAND_CS1_BASE] = 0, + [BRCMNAND_CORR_THRESHOLD] = 0xdc, + [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, + [BRCMNAND_UNCORR_ADDR] = 0x118, + [BRCMNAND_SEMAPHORE] = 0x150, + [BRCMNAND_ID] = 0x194, + [BRCMNAND_ID_EXT] = 0x198, + [BRCMNAND_LL_RDATA] = 0x19c, + [BRCMNAND_OOB_READ_BASE] = 0x200, + [BRCMNAND_OOB_READ_10_BASE] = 0, + [BRCMNAND_OOB_WRITE_BASE] = 0x400, + [BRCMNAND_OOB_WRITE_10_BASE] = 0, + [BRCMNAND_FC_BASE] = 0x600, +}; + +enum brcmnand_cs_reg { + BRCMNAND_CS_CFG_EXT = 0, + BRCMNAND_CS_CFG, + BRCMNAND_CS_ACC_CONTROL, + BRCMNAND_CS_TIMING1, + BRCMNAND_CS_TIMING2, +}; + +/* Per chip-select offsets for v7.1 */ +static const u8 brcmnand_cs_offsets_v71[] = { + [BRCMNAND_CS_ACC_CONTROL] = 0x00, + [BRCMNAND_CS_CFG_EXT] = 0x04, + [BRCMNAND_CS_CFG] = 0x08, + [BRCMNAND_CS_TIMING1] = 0x0c, + [BRCMNAND_CS_TIMING2] = 0x10, +}; + +/* Per chip-select offsets for pre v7.1, except CS0 on <= v5.0 */ +static const u8 brcmnand_cs_offsets[] = { + [BRCMNAND_CS_ACC_CONTROL] = 0x00, + [BRCMNAND_CS_CFG_EXT] = 0x04, + [BRCMNAND_CS_CFG] = 0x04, + [BRCMNAND_CS_TIMING1] = 0x08, + [BRCMNAND_CS_TIMING2] = 0x0c, +}; + +/* Per chip-select offset for <= v5.0 on CS0 only */ +static const u8 brcmnand_cs_offsets_cs0[] = { + [BRCMNAND_CS_ACC_CONTROL] = 0x00, + [BRCMNAND_CS_CFG_EXT] = 0x08, + [BRCMNAND_CS_CFG] = 0x08, + [BRCMNAND_CS_TIMING1] = 0x10, + [BRCMNAND_CS_TIMING2] = 0x14, +}; + +/* + * Bitfields for the CFG and CFG_EXT registers. Pre-v7.1 controllers only had + * one config register, but once the bitfields overflowed, newer controllers + * (v7.1 and newer) added a CFG_EXT register and shuffled a few fields around. + */ +enum { + CFG_BLK_ADR_BYTES_SHIFT = 8, + CFG_COL_ADR_BYTES_SHIFT = 12, + CFG_FUL_ADR_BYTES_SHIFT = 16, + CFG_BUS_WIDTH_SHIFT = 23, + CFG_BUS_WIDTH = BIT(CFG_BUS_WIDTH_SHIFT), + CFG_DEVICE_SIZE_SHIFT = 24, + + /* Only for v2.1 */ + CFG_PAGE_SIZE_SHIFT_v2_1 = 30, + + /* Only for pre-v7.1 (with no CFG_EXT register) */ + CFG_PAGE_SIZE_SHIFT = 20, + CFG_BLK_SIZE_SHIFT = 28, + + /* Only for v7.1+ (with CFG_EXT register) */ + CFG_EXT_PAGE_SIZE_SHIFT = 0, + CFG_EXT_BLK_SIZE_SHIFT = 4, +}; + +/* BRCMNAND_INTFC_STATUS */ +enum { + INTFC_FLASH_STATUS = GENMASK(7, 0), + + INTFC_ERASED = BIT(27), + INTFC_OOB_VALID = BIT(28), + INTFC_CACHE_VALID = BIT(29), + INTFC_FLASH_READY = BIT(30), + INTFC_CTLR_READY = BIT(31), +}; + +/*********************************************************************** + * NAND ACC CONTROL bitfield + * + * Some bits have remained constant throughout hardware revision, while + * others have shifted around. + ***********************************************************************/ + +/* Constant for all versions (where supported) */ +enum { + /* See BRCMNAND_HAS_CACHE_MODE */ + ACC_CONTROL_CACHE_MODE = BIT(22), + + /* See BRCMNAND_HAS_PREFETCH */ + ACC_CONTROL_PREFETCH = BIT(23), + + ACC_CONTROL_PAGE_HIT = BIT(24), + ACC_CONTROL_WR_PREEMPT = BIT(25), + ACC_CONTROL_PARTIAL_PAGE = BIT(26), + ACC_CONTROL_RD_ERASED = BIT(27), + ACC_CONTROL_FAST_PGM_RDIN = BIT(28), + ACC_CONTROL_WR_ECC = BIT(30), + ACC_CONTROL_RD_ECC = BIT(31), +}; + +#define ACC_CONTROL_ECC_SHIFT 16 +/* Only for v7.2 */ +#define ACC_CONTROL_ECC_EXT_SHIFT 13 + +static int brcmnand_status(struct brcmnand_host *host); + +static inline bool brcmnand_non_mmio_ops(struct brcmnand_controller *ctrl) +{ +#if IS_ENABLED(CONFIG_MTD_NAND_BRCMNAND_BCMA) + return static_branch_unlikely(&brcmnand_soc_has_ops_key); +#else + return false; +#endif +} + +static inline u32 nand_readreg(struct brcmnand_controller *ctrl, u32 offs) +{ + if (brcmnand_non_mmio_ops(ctrl)) + return brcmnand_soc_read(ctrl->soc, offs); + return brcmnand_readl(ctrl->nand_base + offs); +} + +static inline void nand_writereg(struct brcmnand_controller *ctrl, u32 offs, + u32 val) +{ + if (brcmnand_non_mmio_ops(ctrl)) + brcmnand_soc_write(ctrl->soc, val, offs); + else + brcmnand_writel(val, ctrl->nand_base + offs); +} + +static int brcmnand_revision_init(struct brcmnand_controller *ctrl) +{ + static const unsigned int block_sizes_v6[] = { 8, 16, 128, 256, 512, 1024, 2048, 0 }; + static const unsigned int block_sizes_v4[] = { 16, 128, 8, 512, 256, 1024, 2048, 0 }; + static const unsigned int block_sizes_v2_2[] = { 16, 128, 8, 512, 256, 0 }; + static const unsigned int block_sizes_v2_1[] = { 16, 128, 8, 512, 0 }; + static const unsigned int page_sizes_v3_4[] = { 512, 2048, 4096, 8192, 0 }; + static const unsigned int page_sizes_v2_2[] = { 512, 2048, 4096, 0 }; + static const unsigned int page_sizes_v2_1[] = { 512, 2048, 0 }; + + ctrl->nand_version = nand_readreg(ctrl, 0) & 0xffff; + + /* Only support v2.1+ */ + if (ctrl->nand_version < 0x0201) { + dev_err(ctrl->dev, "version %#x not supported\n", + ctrl->nand_version); + return -ENODEV; + } + + /* Register offsets */ + if (ctrl->nand_version >= 0x0702) + ctrl->reg_offsets = brcmnand_regs_v72; + else if (ctrl->nand_version == 0x0701) + ctrl->reg_offsets = brcmnand_regs_v71; + else if (ctrl->nand_version >= 0x0600) + ctrl->reg_offsets = brcmnand_regs_v60; + else if (ctrl->nand_version >= 0x0500) + ctrl->reg_offsets = brcmnand_regs_v50; + else if (ctrl->nand_version >= 0x0303) + ctrl->reg_offsets = brcmnand_regs_v33; + else if (ctrl->nand_version >= 0x0201) + ctrl->reg_offsets = brcmnand_regs_v21; + + /* Chip-select stride */ + if (ctrl->nand_version >= 0x0701) + ctrl->reg_spacing = 0x14; + else + ctrl->reg_spacing = 0x10; + + /* Per chip-select registers */ + if (ctrl->nand_version >= 0x0701) { + ctrl->cs_offsets = brcmnand_cs_offsets_v71; + } else { + ctrl->cs_offsets = brcmnand_cs_offsets; + + /* v3.3-5.0 have a different CS0 offset layout */ + if (ctrl->nand_version >= 0x0303 && + ctrl->nand_version <= 0x0500) + ctrl->cs0_offsets = brcmnand_cs_offsets_cs0; + } + + /* Page / block sizes */ + if (ctrl->nand_version >= 0x0701) { + /* >= v7.1 use nice power-of-2 values! */ + ctrl->max_page_size = 16 * 1024; + ctrl->max_block_size = 2 * 1024 * 1024; + } else { + if (ctrl->nand_version >= 0x0304) + ctrl->page_sizes = page_sizes_v3_4; + else if (ctrl->nand_version >= 0x0202) + ctrl->page_sizes = page_sizes_v2_2; + else + ctrl->page_sizes = page_sizes_v2_1; + + if (ctrl->nand_version >= 0x0202) + ctrl->page_size_shift = CFG_PAGE_SIZE_SHIFT; + else + ctrl->page_size_shift = CFG_PAGE_SIZE_SHIFT_v2_1; + + if (ctrl->nand_version >= 0x0600) + ctrl->block_sizes = block_sizes_v6; + else if (ctrl->nand_version >= 0x0400) + ctrl->block_sizes = block_sizes_v4; + else if (ctrl->nand_version >= 0x0202) + ctrl->block_sizes = block_sizes_v2_2; + else + ctrl->block_sizes = block_sizes_v2_1; + + if (ctrl->nand_version < 0x0400) { + if (ctrl->nand_version < 0x0202) + ctrl->max_page_size = 2048; + else + ctrl->max_page_size = 4096; + ctrl->max_block_size = 512 * 1024; + } + } + + /* Maximum spare area sector size (per 512B) */ + if (ctrl->nand_version == 0x0702) + ctrl->max_oob = 128; + else if (ctrl->nand_version >= 0x0600) + ctrl->max_oob = 64; + else if (ctrl->nand_version >= 0x0500) + ctrl->max_oob = 32; + else + ctrl->max_oob = 16; + + /* v6.0 and newer (except v6.1) have prefetch support */ + if (ctrl->nand_version >= 0x0600 && ctrl->nand_version != 0x0601) + ctrl->features |= BRCMNAND_HAS_PREFETCH; + + /* + * v6.x has cache mode, but it's implemented differently. Ignore it for + * now. + */ + if (ctrl->nand_version >= 0x0700) + ctrl->features |= BRCMNAND_HAS_CACHE_MODE; + + if (ctrl->nand_version >= 0x0500) + ctrl->features |= BRCMNAND_HAS_1K_SECTORS; + + if (ctrl->nand_version >= 0x0700) + ctrl->features |= BRCMNAND_HAS_WP; + else if (of_property_read_bool(ctrl->dev->of_node, "brcm,nand-has-wp")) + ctrl->features |= BRCMNAND_HAS_WP; + + /* v7.2 has different ecc level shift in the acc register */ + if (ctrl->nand_version == 0x0702) + ctrl->ecc_level_shift = ACC_CONTROL_ECC_EXT_SHIFT; + else + ctrl->ecc_level_shift = ACC_CONTROL_ECC_SHIFT; + + return 0; +} + +static void brcmnand_flash_dma_revision_init(struct brcmnand_controller *ctrl) +{ + /* flash_dma register offsets */ + if (ctrl->nand_version >= 0x0703) + ctrl->flash_dma_offsets = flash_dma_regs_v4; + else if (ctrl->nand_version == 0x0602) + ctrl->flash_dma_offsets = flash_dma_regs_v0; + else + ctrl->flash_dma_offsets = flash_dma_regs_v1; +} + +static inline u32 brcmnand_read_reg(struct brcmnand_controller *ctrl, + enum brcmnand_reg reg) +{ + u16 offs = ctrl->reg_offsets[reg]; + + if (offs) + return nand_readreg(ctrl, offs); + else + return 0; +} + +static inline void brcmnand_write_reg(struct brcmnand_controller *ctrl, + enum brcmnand_reg reg, u32 val) +{ + u16 offs = ctrl->reg_offsets[reg]; + + if (offs) + nand_writereg(ctrl, offs, val); +} + +static inline void brcmnand_rmw_reg(struct brcmnand_controller *ctrl, + enum brcmnand_reg reg, u32 mask, unsigned + int shift, u32 val) +{ + u32 tmp = brcmnand_read_reg(ctrl, reg); + + tmp &= ~mask; + tmp |= val << shift; + brcmnand_write_reg(ctrl, reg, tmp); +} + +static inline u32 brcmnand_read_fc(struct brcmnand_controller *ctrl, int word) +{ + if (brcmnand_non_mmio_ops(ctrl)) + return brcmnand_soc_read(ctrl->soc, BRCMNAND_NON_MMIO_FC_ADDR); + return __raw_readl(ctrl->nand_fc + word * 4); +} + +static inline void brcmnand_write_fc(struct brcmnand_controller *ctrl, + int word, u32 val) +{ + if (brcmnand_non_mmio_ops(ctrl)) + brcmnand_soc_write(ctrl->soc, val, BRCMNAND_NON_MMIO_FC_ADDR); + else + __raw_writel(val, ctrl->nand_fc + word * 4); +} + +static inline void edu_writel(struct brcmnand_controller *ctrl, + enum edu_reg reg, u32 val) +{ + u16 offs = ctrl->edu_offsets[reg]; + + brcmnand_writel(val, ctrl->edu_base + offs); +} + +static inline u32 edu_readl(struct brcmnand_controller *ctrl, + enum edu_reg reg) +{ + u16 offs = ctrl->edu_offsets[reg]; + + return brcmnand_readl(ctrl->edu_base + offs); +} + +static inline void brcmnand_read_data_bus(struct brcmnand_controller *ctrl, + void __iomem *flash_cache, u32 *buffer, int fc_words) +{ + struct brcmnand_soc *soc = ctrl->soc; + int i; + + if (soc && soc->read_data_bus) { + soc->read_data_bus(soc, flash_cache, buffer, fc_words); + } else { + for (i = 0; i < fc_words; i++) + buffer[i] = brcmnand_read_fc(ctrl, i); + } +} + +static void brcmnand_clear_ecc_addr(struct brcmnand_controller *ctrl) +{ + + /* Clear error addresses */ + brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_ADDR, 0); + brcmnand_write_reg(ctrl, BRCMNAND_CORR_ADDR, 0); + brcmnand_write_reg(ctrl, BRCMNAND_UNCORR_EXT_ADDR, 0); + brcmnand_write_reg(ctrl, BRCMNAND_CORR_EXT_ADDR, 0); +} + +static u64 brcmnand_get_uncorrecc_addr(struct brcmnand_controller *ctrl) +{ + u64 err_addr; + + err_addr = brcmnand_read_reg(ctrl, BRCMNAND_UNCORR_ADDR); + err_addr |= ((u64)(brcmnand_read_reg(ctrl, + BRCMNAND_UNCORR_EXT_ADDR) + & 0xffff) << 32); + + return err_addr; +} + +static u64 brcmnand_get_correcc_addr(struct brcmnand_controller *ctrl) +{ + u64 err_addr; + + err_addr = brcmnand_read_reg(ctrl, BRCMNAND_CORR_ADDR); + err_addr |= ((u64)(brcmnand_read_reg(ctrl, + BRCMNAND_CORR_EXT_ADDR) + & 0xffff) << 32); + + return err_addr; +} + +static void brcmnand_set_cmd_addr(struct mtd_info *mtd, u64 addr) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_controller *ctrl = host->ctrl; + + brcmnand_write_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS, + (host->cs << 16) | ((addr >> 32) & 0xffff)); + (void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_EXT_ADDRESS); + brcmnand_write_reg(ctrl, BRCMNAND_CMD_ADDRESS, + lower_32_bits(addr)); + (void)brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS); +} + +static inline u16 brcmnand_cs_offset(struct brcmnand_controller *ctrl, int cs, + enum brcmnand_cs_reg reg) +{ + u16 offs_cs0 = ctrl->reg_offsets[BRCMNAND_CS0_BASE]; + u16 offs_cs1 = ctrl->reg_offsets[BRCMNAND_CS1_BASE]; + u8 cs_offs; + + if (cs == 0 && ctrl->cs0_offsets) + cs_offs = ctrl->cs0_offsets[reg]; + else + cs_offs = ctrl->cs_offsets[reg]; + + if (cs && offs_cs1) + return offs_cs1 + (cs - 1) * ctrl->reg_spacing + cs_offs; + + return offs_cs0 + cs * ctrl->reg_spacing + cs_offs; +} + +static inline u32 brcmnand_corr_total(struct brcmnand_controller *ctrl) +{ + 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) +{ + struct brcmnand_controller *ctrl = host->ctrl; + unsigned int shift = 0, bits; + enum brcmnand_reg reg = BRCMNAND_CORR_THRESHOLD; + int cs = host->cs; + + if (!ctrl->reg_offsets[reg]) + return; + + if (ctrl->nand_version == 0x0702) + bits = 7; + else if (ctrl->nand_version >= 0x0600) + bits = 6; + else if (ctrl->nand_version >= 0x0500) + bits = 5; + else + bits = 4; + + if (ctrl->nand_version >= 0x0702) { + if (cs >= 4) + reg = BRCMNAND_CORR_THRESHOLD_EXT; + shift = (cs % 4) * bits; + } else if (ctrl->nand_version >= 0x0600) { + if (cs >= 5) + reg = BRCMNAND_CORR_THRESHOLD_EXT; + shift = (cs % 5) * bits; + } + brcmnand_rmw_reg(ctrl, reg, (bits - 1) << shift, shift, val); +} + +static inline int brcmnand_cmd_shift(struct brcmnand_controller *ctrl) +{ + /* Kludge for the BCMA-based NAND controller which does not actually + * shift the command + */ + if (ctrl->nand_version == 0x0304 && brcmnand_non_mmio_ops(ctrl)) + return 0; + + if (ctrl->nand_version < 0x0602) + return 24; + return 0; +} + +static inline u32 brcmnand_spare_area_mask(struct brcmnand_controller *ctrl) +{ + if (ctrl->nand_version == 0x0702) + return GENMASK(7, 0); + else if (ctrl->nand_version >= 0x0600) + return GENMASK(6, 0); + else if (ctrl->nand_version >= 0x0303) + return GENMASK(5, 0); + else + return GENMASK(4, 0); +} + +static inline u32 brcmnand_ecc_level_mask(struct brcmnand_controller *ctrl) +{ + u32 mask = (ctrl->nand_version >= 0x0600) ? 0x1f : 0x0f; + + mask <<= ACC_CONTROL_ECC_SHIFT; + + /* v7.2 includes additional ECC levels */ + if (ctrl->nand_version == 0x0702) + mask |= 0x7 << ACC_CONTROL_ECC_EXT_SHIFT; + + return mask; +} + +static void brcmnand_set_ecc_enabled(struct brcmnand_host *host, int en) +{ + struct brcmnand_controller *ctrl = host->ctrl; + u16 offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_ACC_CONTROL); + u32 acc_control = nand_readreg(ctrl, offs); + u32 ecc_flags = ACC_CONTROL_WR_ECC | ACC_CONTROL_RD_ECC; + + if (en) { + acc_control |= ecc_flags; /* enable RD/WR ECC */ + acc_control &= ~brcmnand_ecc_level_mask(ctrl); + acc_control |= host->hwcfg.ecc_level << ctrl->ecc_level_shift; + } else { + acc_control &= ~ecc_flags; /* disable RD/WR ECC */ + acc_control &= ~brcmnand_ecc_level_mask(ctrl); + } + + nand_writereg(ctrl, offs, acc_control); +} + +static inline int brcmnand_sector_1k_shift(struct brcmnand_controller *ctrl) +{ + if (ctrl->nand_version >= 0x0702) + return 9; + else if (ctrl->nand_version >= 0x0600) + return 7; + else if (ctrl->nand_version >= 0x0500) + return 6; + else + return -1; +} + +static bool brcmnand_get_sector_size_1k(struct brcmnand_host *host) +{ + struct brcmnand_controller *ctrl = host->ctrl; + int sector_size_bit = brcmnand_sector_1k_shift(ctrl); + u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs, + BRCMNAND_CS_ACC_CONTROL); + u32 acc_control; + + if (sector_size_bit < 0) + return false; + + acc_control = nand_readreg(ctrl, acc_control_offs); + + return ((acc_control & BIT(sector_size_bit)) != 0); +} + +static void brcmnand_set_sector_size_1k(struct brcmnand_host *host, int val) +{ + struct brcmnand_controller *ctrl = host->ctrl; + int shift = brcmnand_sector_1k_shift(ctrl); + u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs, + BRCMNAND_CS_ACC_CONTROL); + u32 tmp; + + if (shift < 0) + return; + + tmp = nand_readreg(ctrl, acc_control_offs); + tmp &= ~(1 << shift); + tmp |= (!!val) << shift; + nand_writereg(ctrl, acc_control_offs, tmp); +} + +static int brcmnand_get_spare_size(struct brcmnand_host *host) +{ + struct brcmnand_controller *ctrl = host->ctrl; + u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs, + BRCMNAND_CS_ACC_CONTROL); + u32 acc = nand_readreg(ctrl, acc_control_offs); + + return (acc & brcmnand_spare_area_mask(ctrl)); +} + +static void brcmnand_get_ecc_settings(struct brcmnand_host *host, struct nand_chip *chip) +{ + struct brcmnand_controller *ctrl = host->ctrl; + u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs, + BRCMNAND_CS_ACC_CONTROL); + bool sector_size_1k = brcmnand_get_sector_size_1k(host); + int spare_area_size, ecc_level; + u32 acc; + + spare_area_size = brcmnand_get_spare_size(host); + acc = nand_readreg(ctrl, acc_control_offs); + ecc_level = (acc & brcmnand_ecc_level_mask(ctrl)) >> ctrl->ecc_level_shift; + if (sector_size_1k) + chip->ecc.strength = ecc_level * 2; + else if (spare_area_size == 16 && ecc_level == 15) + chip->ecc.strength = 1; /* hamming */ + else + chip->ecc.strength = ecc_level; + + if (chip->ecc.size == 0) { + if (sector_size_1k) + chip->ecc.size = 1024; + else + chip->ecc.size = 512; + } +} + +/*********************************************************************** + * CS_NAND_SELECT + ***********************************************************************/ + +enum { + CS_SELECT_NAND_WP = BIT(29), + CS_SELECT_AUTO_DEVICE_ID_CFG = BIT(30), +}; + +static int bcmnand_ctrl_poll_status(struct brcmnand_host *host, + u32 mask, u32 expected_val, + unsigned long timeout_ms) +{ + struct brcmnand_controller *ctrl = host->ctrl; + unsigned long limit; + u32 val; + + if (!timeout_ms) + timeout_ms = NAND_POLL_STATUS_TIMEOUT_MS; + + limit = jiffies + msecs_to_jiffies(timeout_ms); + do { + if (mask & INTFC_FLASH_STATUS) + brcmnand_status(host); + + val = brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS); + if ((val & mask) == expected_val) + return 0; + + cpu_relax(); + } while (time_after(limit, jiffies)); + + /* + * do a final check after time out in case the CPU was busy and the driver + * did not get enough time to perform the polling to avoid false alarms + */ + if (mask & INTFC_FLASH_STATUS) + brcmnand_status(host); + + val = brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS); + if ((val & mask) == expected_val) + return 0; + + dev_err(ctrl->dev, "timeout on status poll (expected %x got %x)\n", + expected_val, val & mask); + + return -ETIMEDOUT; +} + +static inline void brcmnand_set_wp(struct brcmnand_controller *ctrl, bool en) +{ + u32 val = en ? CS_SELECT_NAND_WP : 0; + + brcmnand_rmw_reg(ctrl, BRCMNAND_CS_SELECT, CS_SELECT_NAND_WP, 0, val); +} + +/*********************************************************************** + * Flash DMA + ***********************************************************************/ + +static inline bool has_flash_dma(struct brcmnand_controller *ctrl) +{ + return ctrl->flash_dma_base; +} + +static inline bool has_edu(struct brcmnand_controller *ctrl) +{ + return ctrl->edu_base; +} + +static inline bool use_dma(struct brcmnand_controller *ctrl) +{ + return has_flash_dma(ctrl) || has_edu(ctrl); +} + +static inline void disable_ctrl_irqs(struct brcmnand_controller *ctrl) +{ + if (ctrl->pio_poll_mode) + return; + + if (has_flash_dma(ctrl)) { + ctrl->flash_dma_base = NULL; + disable_irq(ctrl->dma_irq); + } + + disable_irq(ctrl->irq); + ctrl->pio_poll_mode = true; +} + +static inline bool flash_dma_buf_ok(const void *buf) +{ + return buf && !is_vmalloc_addr(buf) && + likely(IS_ALIGNED((uintptr_t)buf, 4)); +} + +static inline void flash_dma_writel(struct brcmnand_controller *ctrl, + enum flash_dma_reg dma_reg, u32 val) +{ + u16 offs = ctrl->flash_dma_offsets[dma_reg]; + + brcmnand_writel(val, ctrl->flash_dma_base + offs); +} + +static inline u32 flash_dma_readl(struct brcmnand_controller *ctrl, + enum flash_dma_reg dma_reg) +{ + u16 offs = ctrl->flash_dma_offsets[dma_reg]; + + return brcmnand_readl(ctrl->flash_dma_base + offs); +} + +/* Low-level operation types: command, address, write, or read */ +enum brcmnand_llop_type { + LL_OP_CMD, + LL_OP_ADDR, + LL_OP_WR, + LL_OP_RD, +}; + +/*********************************************************************** + * Internal support functions + ***********************************************************************/ + +static inline bool is_hamming_ecc(struct brcmnand_controller *ctrl, + struct brcmnand_cfg *cfg) +{ + if (ctrl->nand_version <= 0x0701) + return cfg->sector_size_1k == 0 && cfg->spare_area_size == 16 && + cfg->ecc_level == 15; + else + return cfg->sector_size_1k == 0 && ((cfg->spare_area_size == 16 && + cfg->ecc_level == 15) || + (cfg->spare_area_size == 28 && cfg->ecc_level == 16)); +} + +/* + * Set mtd->ooblayout to the appropriate mtd_ooblayout_ops given + * the layout/configuration. + * Returns -ERRCODE on failure. + */ +static int brcmnand_hamming_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_cfg *cfg = &host->hwcfg; + int sas = cfg->spare_area_size << cfg->sector_size_1k; + int sectors = cfg->page_size / (512 << cfg->sector_size_1k); + + if (section >= sectors) + return -ERANGE; + + oobregion->offset = (section * sas) + 6; + oobregion->length = 3; + + return 0; +} + +static int brcmnand_hamming_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_cfg *cfg = &host->hwcfg; + int sas = cfg->spare_area_size << cfg->sector_size_1k; + int sectors = cfg->page_size / (512 << cfg->sector_size_1k); + u32 next; + + if (section > sectors) + return -ERANGE; + + next = (section * sas); + if (section < sectors) + next += 6; + + if (section) { + oobregion->offset = ((section - 1) * sas) + 9; + } else { + if (cfg->page_size > 512) { + /* Large page NAND uses first 2 bytes for BBI */ + oobregion->offset = 2; + } else { + /* Small page NAND uses last byte before ECC for BBI */ + oobregion->offset = 0; + next--; + } + } + + oobregion->length = next - oobregion->offset; + + return 0; +} + +static const struct mtd_ooblayout_ops brcmnand_hamming_ooblayout_ops = { + .ecc = brcmnand_hamming_ooblayout_ecc, + .free = brcmnand_hamming_ooblayout_free, +}; + +static int brcmnand_bch_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_cfg *cfg = &host->hwcfg; + int sas = cfg->spare_area_size << cfg->sector_size_1k; + int sectors = cfg->page_size / (512 << cfg->sector_size_1k); + + if (section >= sectors) + return -ERANGE; + + oobregion->offset = ((section + 1) * sas) - chip->ecc.bytes; + oobregion->length = chip->ecc.bytes; + + return 0; +} + +static int brcmnand_bch_ooblayout_free_lp(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_cfg *cfg = &host->hwcfg; + int sas = cfg->spare_area_size << cfg->sector_size_1k; + int sectors = cfg->page_size / (512 << cfg->sector_size_1k); + + if (section >= sectors) + return -ERANGE; + + if (sas <= chip->ecc.bytes) + return 0; + + oobregion->offset = section * sas; + oobregion->length = sas - chip->ecc.bytes; + + if (!section) { + oobregion->offset++; + oobregion->length--; + } + + return 0; +} + +static int brcmnand_bch_ooblayout_free_sp(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_cfg *cfg = &host->hwcfg; + int sas = cfg->spare_area_size << cfg->sector_size_1k; + + if (section > 1 || sas - chip->ecc.bytes < 6 || + (section && sas - chip->ecc.bytes == 6)) + return -ERANGE; + + if (!section) { + oobregion->offset = 0; + oobregion->length = 5; + } else { + oobregion->offset = 6; + oobregion->length = sas - chip->ecc.bytes - 6; + } + + return 0; +} + +static const struct mtd_ooblayout_ops brcmnand_bch_lp_ooblayout_ops = { + .ecc = brcmnand_bch_ooblayout_ecc, + .free = brcmnand_bch_ooblayout_free_lp, +}; + +static const struct mtd_ooblayout_ops brcmnand_bch_sp_ooblayout_ops = { + .ecc = brcmnand_bch_ooblayout_ecc, + .free = brcmnand_bch_ooblayout_free_sp, +}; + +static int brcmstb_choose_ecc_layout(struct brcmnand_host *host) +{ + struct brcmnand_cfg *p = &host->hwcfg; + struct mtd_info *mtd = nand_to_mtd(&host->chip); + struct nand_ecc_ctrl *ecc = &host->chip.ecc; + unsigned int ecc_level = p->ecc_level; + int sas = p->spare_area_size << p->sector_size_1k; + int sectors = p->page_size / (512 << p->sector_size_1k); + + if (p->sector_size_1k) + ecc_level <<= 1; + + if (is_hamming_ecc(host->ctrl, p)) { + ecc->bytes = 3 * sectors; + mtd_set_ooblayout(mtd, &brcmnand_hamming_ooblayout_ops); + return 0; + } + + /* + * CONTROLLER_VERSION: + * < v5.0: ECC_REQ = ceil(BCH_T * 13/8) + * >= v5.0: ECC_REQ = ceil(BCH_T * 14/8) + * But we will just be conservative. + */ + ecc->bytes = DIV_ROUND_UP(ecc_level * 14, 8); + if (p->page_size == 512) + mtd_set_ooblayout(mtd, &brcmnand_bch_sp_ooblayout_ops); + else + mtd_set_ooblayout(mtd, &brcmnand_bch_lp_ooblayout_ops); + + if (ecc->bytes >= sas) { + dev_err(&host->pdev->dev, + "error: ECC too large for OOB (ECC bytes %d, spare sector %d)\n", + ecc->bytes, sas); + return -EINVAL; + } + + return 0; +} + +static void brcmnand_wp(struct mtd_info *mtd, int wp) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_controller *ctrl = host->ctrl; + + if ((ctrl->features & BRCMNAND_HAS_WP) && wp_on == 1) { + static int old_wp = -1; + int ret; + + if (old_wp != wp) { + dev_dbg(ctrl->dev, "WP %s\n", str_on_off(wp)); + old_wp = wp; + } + + /* + * make sure ctrl/flash ready before and after + * changing state of #WP pin + */ + ret = bcmnand_ctrl_poll_status(host, NAND_CTRL_RDY | + NAND_STATUS_READY, + NAND_CTRL_RDY | + NAND_STATUS_READY, 0); + if (ret) + return; + + brcmnand_set_wp(ctrl, wp); + /* force controller operation to update internal copy of NAND chip status */ + brcmnand_status(host); + /* NAND_STATUS_WP 0x00 = protected, 0x80 = not protected */ + ret = bcmnand_ctrl_poll_status(host, + NAND_CTRL_RDY | + NAND_STATUS_READY | + NAND_STATUS_WP, + NAND_CTRL_RDY | + NAND_STATUS_READY | + (wp ? 0 : NAND_STATUS_WP), 0); + + if (ret) + dev_err_ratelimited(&host->pdev->dev, + "nand #WP expected %s\n", + str_on_off(wp)); + } +} + +/* Helper functions for reading and writing OOB registers */ +static inline u8 oob_reg_read(struct brcmnand_controller *ctrl, u32 offs) +{ + u16 offset0, offset10, reg_offs; + + offset0 = ctrl->reg_offsets[BRCMNAND_OOB_READ_BASE]; + offset10 = ctrl->reg_offsets[BRCMNAND_OOB_READ_10_BASE]; + + if (offs >= ctrl->max_oob) + return 0x77; + + if (offs >= 16 && offset10) + reg_offs = offset10 + ((offs - 0x10) & ~0x03); + else + reg_offs = offset0 + (offs & ~0x03); + + return nand_readreg(ctrl, reg_offs) >> (24 - ((offs & 0x03) << 3)); +} + +static inline void oob_reg_write(struct brcmnand_controller *ctrl, u32 offs, + u32 data) +{ + u16 offset0, offset10, reg_offs; + + offset0 = ctrl->reg_offsets[BRCMNAND_OOB_WRITE_BASE]; + offset10 = ctrl->reg_offsets[BRCMNAND_OOB_WRITE_10_BASE]; + + if (offs >= ctrl->max_oob) + return; + + if (offs >= 16 && offset10) + reg_offs = offset10 + ((offs - 0x10) & ~0x03); + else + reg_offs = offset0 + (offs & ~0x03); + + nand_writereg(ctrl, reg_offs, data); +} + +/* + * read_oob_from_regs - read data from OOB registers + * @ctrl: NAND controller + * @i: sub-page sector index + * @oob: buffer to read to + * @sas: spare area sector size (i.e., OOB size per FLASH_CACHE) + * @sector_1k: 1 for 1KiB sectors, 0 for 512B, other values are illegal + */ +static int read_oob_from_regs(struct brcmnand_controller *ctrl, int i, u8 *oob, + int sas, int sector_1k) +{ + int tbytes = sas << sector_1k; + int j; + + /* Adjust OOB values for 1K sector size */ + if (sector_1k && (i & 0x01)) + tbytes = max(0, tbytes - (int)ctrl->max_oob); + tbytes = min_t(int, tbytes, ctrl->max_oob); + + for (j = 0; j < tbytes; j++) + oob[j] = oob_reg_read(ctrl, j); + return tbytes; +} + +/* + * write_oob_to_regs - write data to OOB registers + * @i: sub-page sector index + * @oob: buffer to write from + * @sas: spare area sector size (i.e., OOB size per FLASH_CACHE) + * @sector_1k: 1 for 1KiB sectors, 0 for 512B, other values are illegal + */ +static int write_oob_to_regs(struct brcmnand_controller *ctrl, int i, + const u8 *oob, int sas, int sector_1k) +{ + int tbytes = sas << sector_1k; + int j, k = 0; + u32 last = 0xffffffff; + u8 *plast = (u8 *)&last; + + /* Adjust OOB values for 1K sector size */ + if (sector_1k && (i & 0x01)) + tbytes = max(0, tbytes - (int)ctrl->max_oob); + tbytes = min_t(int, tbytes, ctrl->max_oob); + + /* + * tbytes may not be multiple of words. Make sure we don't read out of + * the boundary and stop at last word. + */ + for (j = 0; (j + 3) < tbytes; j += 4) + oob_reg_write(ctrl, j, + (oob[j + 0] << 24) | + (oob[j + 1] << 16) | + (oob[j + 2] << 8) | + (oob[j + 3] << 0)); + + /* handle the remaining bytes */ + while (j < tbytes) + plast[k++] = oob[j++]; + + if (tbytes & 0x3) + oob_reg_write(ctrl, (tbytes & ~0x3), (__force u32)cpu_to_be32(last)); + + return tbytes; +} + +static void brcmnand_edu_init(struct brcmnand_controller *ctrl) +{ + /* initialize edu */ + edu_writel(ctrl, EDU_ERR_STATUS, 0); + edu_readl(ctrl, EDU_ERR_STATUS); + edu_writel(ctrl, EDU_DONE, 0); + edu_writel(ctrl, EDU_DONE, 0); + edu_writel(ctrl, EDU_DONE, 0); + edu_writel(ctrl, EDU_DONE, 0); + edu_readl(ctrl, EDU_DONE); +} + +/* edu irq */ +static irqreturn_t brcmnand_edu_irq(int irq, void *data) +{ + struct brcmnand_controller *ctrl = data; + + if (ctrl->edu_count) { + ctrl->edu_count--; + while (!(edu_readl(ctrl, EDU_DONE) & EDU_DONE_MASK)) + udelay(1); + edu_writel(ctrl, EDU_DONE, 0); + edu_readl(ctrl, EDU_DONE); + } + + if (ctrl->edu_count) { + ctrl->edu_dram_addr += FC_BYTES; + ctrl->edu_ext_addr += FC_BYTES; + + edu_writel(ctrl, EDU_DRAM_ADDR, (u32)ctrl->edu_dram_addr); + edu_readl(ctrl, EDU_DRAM_ADDR); + edu_writel(ctrl, EDU_EXT_ADDR, ctrl->edu_ext_addr); + edu_readl(ctrl, EDU_EXT_ADDR); + + if (ctrl->oob) { + if (ctrl->edu_cmd == EDU_CMD_READ) { + ctrl->oob += read_oob_from_regs(ctrl, + ctrl->edu_count + 1, + ctrl->oob, ctrl->sas, + ctrl->sector_size_1k); + } else { + brcmnand_write_reg(ctrl, BRCMNAND_CMD_ADDRESS, + ctrl->edu_ext_addr); + brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS); + ctrl->oob += write_oob_to_regs(ctrl, + ctrl->edu_count, + ctrl->oob, ctrl->sas, + ctrl->sector_size_1k); + } + } + + mb(); /* flush previous writes */ + edu_writel(ctrl, EDU_CMD, ctrl->edu_cmd); + edu_readl(ctrl, EDU_CMD); + + return IRQ_HANDLED; + } + + complete(&ctrl->edu_done); + + return IRQ_HANDLED; +} + +static irqreturn_t brcmnand_ctlrdy_irq(int irq, void *data) +{ + struct brcmnand_controller *ctrl = data; + + /* Discard all NAND_CTLRDY interrupts during DMA */ + if (ctrl->dma_pending) + return IRQ_HANDLED; + + /* check if you need to piggy back on the ctrlrdy irq */ + if (ctrl->edu_pending) { + if (irq == ctrl->irq && ((int)ctrl->edu_irq >= 0)) + /* Discard interrupts while using dedicated edu irq */ + return IRQ_HANDLED; + + /* no registered edu irq, call handler */ + return brcmnand_edu_irq(irq, data); + } + + complete(&ctrl->done); + return IRQ_HANDLED; +} + +/* Handle SoC-specific interrupt hardware */ +static irqreturn_t brcmnand_irq(int irq, void *data) +{ + struct brcmnand_controller *ctrl = data; + + if (ctrl->soc->ctlrdy_ack(ctrl->soc)) + return brcmnand_ctlrdy_irq(irq, data); + + return IRQ_NONE; +} + +static irqreturn_t brcmnand_dma_irq(int irq, void *data) +{ + struct brcmnand_controller *ctrl = data; + + complete(&ctrl->dma_done); + + return IRQ_HANDLED; +} + +static void brcmnand_send_cmd(struct brcmnand_host *host, int cmd) +{ + struct brcmnand_controller *ctrl = host->ctrl; + int ret; + u64 cmd_addr; + + cmd_addr = brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS); + + dev_dbg(ctrl->dev, "send native cmd %d addr 0x%llx\n", cmd, cmd_addr); + + /* + * If we came here through _panic_write and there is a pending + * command, try to wait for it. If it times out, rather than + * hitting BUG_ON, just return so we don't crash while crashing. + */ + if (oops_in_progress) { + if (ctrl->cmd_pending && + bcmnand_ctrl_poll_status(host, NAND_CTRL_RDY, NAND_CTRL_RDY, 0)) + return; + } else + BUG_ON(ctrl->cmd_pending != 0); + ctrl->cmd_pending = cmd; + + ret = bcmnand_ctrl_poll_status(host, NAND_CTRL_RDY, NAND_CTRL_RDY, 0); + WARN_ON(ret); + + mb(); /* flush previous writes */ + brcmnand_write_reg(ctrl, BRCMNAND_CMD_START, + cmd << brcmnand_cmd_shift(ctrl)); +} + +static bool brcmstb_nand_wait_for_completion(struct nand_chip *chip) +{ + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_controller *ctrl = host->ctrl; + struct mtd_info *mtd = nand_to_mtd(chip); + bool err = false; + int sts; + + if (mtd->oops_panic_write || ctrl->irq < 0) { + /* switch to interrupt polling and PIO mode */ + disable_ctrl_irqs(ctrl); + sts = bcmnand_ctrl_poll_status(host, NAND_CTRL_RDY, + NAND_CTRL_RDY, 0); + err = sts < 0; + } else { + unsigned long timeo = msecs_to_jiffies( + NAND_POLL_STATUS_TIMEOUT_MS); + /* wait for completion interrupt */ + sts = wait_for_completion_timeout(&ctrl->done, timeo); + err = !sts; + } + + return err; +} + +static int brcmnand_waitfunc(struct nand_chip *chip) +{ + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_controller *ctrl = host->ctrl; + bool err = false; + + dev_dbg(ctrl->dev, "wait on native cmd %d\n", ctrl->cmd_pending); + if (ctrl->cmd_pending) + err = brcmstb_nand_wait_for_completion(chip); + + ctrl->cmd_pending = 0; + if (err) { + u32 cmd = brcmnand_read_reg(ctrl, BRCMNAND_CMD_START) + >> brcmnand_cmd_shift(ctrl); + + dev_err_ratelimited(ctrl->dev, + "timeout waiting for command %#02x\n", cmd); + dev_err_ratelimited(ctrl->dev, "intfc status %08x\n", + brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS)); + return -ETIMEDOUT; + } + return brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS) & + INTFC_FLASH_STATUS; +} + +static int brcmnand_status(struct brcmnand_host *host) +{ + struct nand_chip *chip = &host->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + + brcmnand_set_cmd_addr(mtd, 0); + brcmnand_send_cmd(host, CMD_STATUS_READ); + + return brcmnand_waitfunc(chip); +} + +static int brcmnand_reset(struct brcmnand_host *host) +{ + struct nand_chip *chip = &host->chip; + + brcmnand_send_cmd(host, CMD_FLASH_RESET); + + return brcmnand_waitfunc(chip); +} + +enum { + LLOP_RE = BIT(16), + LLOP_WE = BIT(17), + LLOP_ALE = BIT(18), + LLOP_CLE = BIT(19), + LLOP_RETURN_IDLE = BIT(31), + + LLOP_DATA_MASK = GENMASK(15, 0), +}; + +static int brcmnand_low_level_op(struct brcmnand_host *host, + enum brcmnand_llop_type type, u32 data, + bool last_op) +{ + struct nand_chip *chip = &host->chip; + struct brcmnand_controller *ctrl = host->ctrl; + u32 tmp; + + tmp = data & LLOP_DATA_MASK; + switch (type) { + case LL_OP_CMD: + tmp |= LLOP_WE | LLOP_CLE; + break; + case LL_OP_ADDR: + /* WE | ALE */ + tmp |= LLOP_WE | LLOP_ALE; + break; + case LL_OP_WR: + /* WE */ + tmp |= LLOP_WE; + break; + case LL_OP_RD: + /* RE */ + tmp |= LLOP_RE; + break; + } + if (last_op) + /* RETURN_IDLE */ + tmp |= LLOP_RETURN_IDLE; + + dev_dbg(ctrl->dev, "ll_op cmd %#x\n", tmp); + + brcmnand_write_reg(ctrl, BRCMNAND_LL_OP, tmp); + (void)brcmnand_read_reg(ctrl, BRCMNAND_LL_OP); + + brcmnand_send_cmd(host, CMD_LOW_LEVEL_OP); + return brcmnand_waitfunc(chip); +} + +/* + * Kick EDU engine + */ +static int brcmnand_edu_trans(struct brcmnand_host *host, u64 addr, u32 *buf, + u8 *oob, u32 len, u8 cmd) +{ + struct brcmnand_controller *ctrl = host->ctrl; + struct brcmnand_cfg *cfg = &host->hwcfg; + unsigned long timeo = msecs_to_jiffies(200); + int ret = 0; + int dir = (cmd == CMD_PAGE_READ ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + u8 edu_cmd = (cmd == CMD_PAGE_READ ? EDU_CMD_READ : EDU_CMD_WRITE); + unsigned int trans = len >> FC_SHIFT; + dma_addr_t pa; + + 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)) { + dev_err(ctrl->dev, "unable to map buffer for EDU DMA\n"); + return -ENOMEM; + } + + ctrl->edu_pending = true; + ctrl->edu_dram_addr = pa; + ctrl->edu_ext_addr = addr; + ctrl->edu_cmd = edu_cmd; + ctrl->edu_count = trans; + ctrl->sas = cfg->spare_area_size; + ctrl->oob = oob; + + edu_writel(ctrl, EDU_DRAM_ADDR, (u32)ctrl->edu_dram_addr); + edu_readl(ctrl, EDU_DRAM_ADDR); + edu_writel(ctrl, EDU_EXT_ADDR, ctrl->edu_ext_addr); + edu_readl(ctrl, EDU_EXT_ADDR); + edu_writel(ctrl, EDU_LENGTH, FC_BYTES); + edu_readl(ctrl, EDU_LENGTH); + + if (ctrl->oob && (ctrl->edu_cmd == EDU_CMD_WRITE)) { + brcmnand_write_reg(ctrl, BRCMNAND_CMD_ADDRESS, + ctrl->edu_ext_addr); + brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS); + ctrl->oob += write_oob_to_regs(ctrl, + 1, + ctrl->oob, ctrl->sas, + ctrl->sector_size_1k); + } + + /* Start edu engine */ + mb(); /* flush previous writes */ + edu_writel(ctrl, EDU_CMD, ctrl->edu_cmd); + edu_readl(ctrl, EDU_CMD); + + if (wait_for_completion_timeout(&ctrl->edu_done, timeo) <= 0) { + dev_err(ctrl->dev, + "timeout waiting for EDU; status %#x, error status %#x\n", + edu_readl(ctrl, EDU_STATUS), + edu_readl(ctrl, EDU_ERR_STATUS)); + } + + dma_unmap_single(ctrl->dev, pa, len, dir); + + /* read last subpage oob */ + if (ctrl->oob && (ctrl->edu_cmd == EDU_CMD_READ)) { + ctrl->oob += read_oob_from_regs(ctrl, + 1, + ctrl->oob, ctrl->sas, + ctrl->sector_size_1k); + } + + /* for program page check NAND status */ + if (((brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS) & + INTFC_FLASH_STATUS) & NAND_STATUS_FAIL) && + edu_cmd == EDU_CMD_WRITE) { + dev_info(ctrl->dev, "program failed at %llx\n", + (unsigned long long)addr); + ret = -EIO; + } + + /* Make sure the EDU status is clean */ + if (edu_readl(ctrl, EDU_STATUS) & EDU_STATUS_ACTIVE) + dev_warn(ctrl->dev, "EDU still active: %#x\n", + edu_readl(ctrl, EDU_STATUS)); + + if (unlikely(edu_readl(ctrl, EDU_ERR_STATUS) & EDU_ERR_STATUS_ERRACK)) { + dev_warn(ctrl->dev, "EDU RBUS error at addr %llx\n", + (unsigned long long)addr); + ret = -EIO; + } + + ctrl->edu_pending = false; + brcmnand_edu_init(ctrl); + edu_writel(ctrl, EDU_STOP, 0); /* force stop */ + edu_readl(ctrl, EDU_STOP); + + if (!ret && edu_cmd == EDU_CMD_READ) { + u64 err_addr = 0; + + /* + * check for ECC errors here, subpage ECC errors are + * retained in ECC error address register + */ + err_addr = brcmnand_get_uncorrecc_addr(ctrl); + if (!err_addr) { + err_addr = brcmnand_get_correcc_addr(ctrl); + if (err_addr) + ret = -EUCLEAN; + } else + ret = -EBADMSG; + } + + return ret; +} + +/* + * Construct a FLASH_DMA descriptor as part of a linked list. You must know the + * following ahead of time: + * - Is this descriptor the beginning or end of a linked list? + * - What is the (DMA) address of the next descriptor in the linked list? + */ +static int brcmnand_fill_dma_desc(struct brcmnand_host *host, + struct brcm_nand_dma_desc *desc, u64 addr, + dma_addr_t buf, u32 len, u8 dma_cmd, + bool begin, bool end, + dma_addr_t next_desc) +{ + memset(desc, 0, sizeof(*desc)); + /* Descriptors are written in native byte order (wordwise) */ + desc->next_desc = lower_32_bits(next_desc); + desc->next_desc_ext = upper_32_bits(next_desc); + desc->cmd_irq = (dma_cmd << 24) | + (end ? (0x03 << 8) : 0) | /* IRQ | STOP */ + (!!begin) | ((!!end) << 1); /* head, tail */ +#ifdef CONFIG_CPU_BIG_ENDIAN + desc->cmd_irq |= 0x01 << 12; +#endif + desc->dram_addr = lower_32_bits(buf); + desc->dram_addr_ext = upper_32_bits(buf); + desc->tfr_len = len; + desc->total_len = len; + desc->flash_addr = lower_32_bits(addr); + desc->flash_addr_ext = upper_32_bits(addr); + desc->cs = host->cs; + desc->status_valid = 0x01; + return 0; +} + +/* + * Kick the FLASH_DMA engine, with a given DMA descriptor + */ +static void brcmnand_dma_run(struct brcmnand_host *host, dma_addr_t desc) +{ + struct brcmnand_controller *ctrl = host->ctrl; + unsigned long timeo = msecs_to_jiffies(100); + + flash_dma_writel(ctrl, FLASH_DMA_FIRST_DESC, lower_32_bits(desc)); + (void)flash_dma_readl(ctrl, FLASH_DMA_FIRST_DESC); + if (ctrl->nand_version > 0x0602) { + flash_dma_writel(ctrl, FLASH_DMA_FIRST_DESC_EXT, + upper_32_bits(desc)); + (void)flash_dma_readl(ctrl, FLASH_DMA_FIRST_DESC_EXT); + } + + /* Start FLASH_DMA engine */ + ctrl->dma_pending = true; + mb(); /* flush previous writes */ + flash_dma_writel(ctrl, FLASH_DMA_CTRL, 0x03); /* wake | run */ + + if (wait_for_completion_timeout(&ctrl->dma_done, timeo) <= 0) { + dev_err(ctrl->dev, + "timeout waiting for DMA; status %#x, error status %#x\n", + flash_dma_readl(ctrl, FLASH_DMA_STATUS), + flash_dma_readl(ctrl, FLASH_DMA_ERROR_STATUS)); + } + ctrl->dma_pending = false; + flash_dma_writel(ctrl, FLASH_DMA_CTRL, 0); /* force stop */ +} + +static int brcmnand_dma_trans(struct brcmnand_host *host, u64 addr, u32 *buf, + u8 *oob, u32 len, u8 dma_cmd) +{ + struct brcmnand_controller *ctrl = host->ctrl; + dma_addr_t buf_pa; + int dir = dma_cmd == CMD_PAGE_READ ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + + buf_pa = dma_map_single(ctrl->dev, buf, len, dir); + if (dma_mapping_error(ctrl->dev, buf_pa)) { + dev_err(ctrl->dev, "unable to map buffer for DMA\n"); + return -ENOMEM; + } + + brcmnand_fill_dma_desc(host, ctrl->dma_desc, addr, buf_pa, len, + dma_cmd, true, true, 0); + + brcmnand_dma_run(host, ctrl->dma_pa); + + dma_unmap_single(ctrl->dev, buf_pa, len, dir); + + if (ctrl->dma_desc->status_valid & FLASH_DMA_ECC_ERROR) + return -EBADMSG; + else if (ctrl->dma_desc->status_valid & FLASH_DMA_CORR_ERROR) + return -EUCLEAN; + + return 0; +} + +/* + * Assumes proper CS is already set + */ +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, 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); + brcmnand_waitfunc(chip); + + if (likely(buf)) { + brcmnand_soc_data_bus_prepare(ctrl->soc, false); + + brcmnand_read_data_bus(ctrl, ctrl->nand_fc, buf, FC_WORDS); + buf += FC_WORDS; + + brcmnand_soc_data_bus_unprepare(ctrl->soc, false); + } + + if (oob) + oob += read_oob_from_regs(ctrl, i, oob, + mtd->oobsize / trans, + host->hwcfg.sector_size_1k); + + if (ret != -EBADMSG) { + *err_addr = brcmnand_get_uncorrecc_addr(ctrl); + + if (*err_addr) + ret = -EBADMSG; + else { + *err_addr = brcmnand_get_correcc_addr(ctrl); + + if (*err_addr) { + ret = -EUCLEAN; + + if (corr && (brcmnand_corr_total(ctrl) - prev_corr) > *corr) + *corr = brcmnand_corr_total(ctrl) - prev_corr; + } + } + } + } + + return ret; +} + +/* + * Check a page to see if it is erased (w/ bitflips) after an uncorrectable ECC + * error + * + * Because the HW ECC signals an ECC error if an erase paged has even a single + * bitflip, we must check each ECC error to see if it is actually an erased + * page with bitflips, not a truly corrupted page. + * + * On a real error, return a negative error code (-EBADMSG for ECC error), and + * buf will contain raw data. + * Otherwise, buf gets filled with 0xffs and return the maximum number of + * bitflips-per-ECC-sector to the caller. + * + */ +static int brcmstb_nand_verify_erased_page(struct mtd_info *mtd, + struct nand_chip *chip, void *buf, u64 addr) +{ + struct mtd_oob_region ecc; + int i; + int bitflips = 0; + int page = addr >> chip->page_shift; + int ret; + void *ecc_bytes; + void *ecc_chunk; + + if (!buf) + buf = nand_get_data_buf(chip); + + /* read without ecc for verification */ + ret = chip->ecc.read_page_raw(chip, buf, true, page); + if (ret) + return ret; + + for (i = 0; i < chip->ecc.steps; i++) { + ecc_chunk = buf + chip->ecc.size * i; + + mtd_ooblayout_ecc(mtd, i, &ecc); + ecc_bytes = chip->oob_poi + ecc.offset; + + ret = nand_check_erased_ecc_chunk(ecc_chunk, chip->ecc.size, + ecc_bytes, ecc.length, + NULL, 0, + chip->ecc.strength); + if (ret < 0) + return ret; + + bitflips = max(bitflips, ret); + } + + return bitflips; +} + +static int brcmnand_read(struct mtd_info *mtd, struct nand_chip *chip, + u64 addr, unsigned int trans, u32 *buf, u8 *oob) +{ + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_controller *ctrl = host->ctrl; + u64 err_addr = 0; + 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); + +try_dmaread: + brcmnand_clear_ecc_addr(ctrl); + + if (ctrl->dma_trans && (has_edu(ctrl) || !oob) && + flash_dma_buf_ok(buf)) { + err = ctrl->dma_trans(host, addr, buf, oob, + trans * FC_BYTES, + CMD_PAGE_READ); + + if (err) { + if (mtd_is_bitflip_or_eccerr(err)) + err_addr = addr; + else + return -EIO; + } + + if (has_edu(ctrl) && err_addr) + edu_err = true; + + } else { + if (oob) + memset(oob, 0x99, mtd->oobsize); + + err = brcmnand_read_by_pio(mtd, chip, addr, trans, buf, + 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 + * prior PIO read that reported uncorrectable error, + * the DMA engine captures this error following DMA read + * cleared only on subsequent DMA read, so just retry once + * to clear a possible false error reported for current DMA + * read + */ + if ((ctrl->nand_version == 0x0700) || + (ctrl->nand_version == 0x0701)) { + if (retry) { + retry = false; + goto try_dmaread; + } + } + + /* + * Controller version 7.2 has hw encoder to detect erased page + * bitflips, apply sw verification for older controllers only + */ + if (ctrl->nand_version < 0x0702) { + err = brcmstb_nand_verify_erased_page(mtd, chip, buf, + addr); + /* erased page bitflips corrected */ + if (err >= 0) + return err; + } + + dev_err(ctrl->dev, "uncorrectable error at 0x%llx\n", + (unsigned long long)err_addr); + mtd->ecc_stats.failed++; + /* NAND layer expects zero on ECC errors */ + return 0; + } + + if (mtd_is_bitflip(err)) { + /* 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, &corrected); + + dev_dbg(ctrl->dev, "corrected error at 0x%llx\n", + (unsigned long long)err_addr); + /* + * 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); + } + + return 0; +} + +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); + u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL; + u64 addr = (u64)page << chip->page_shift; + + 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, + int oob_required, int page) +{ + struct brcmnand_host *host = nand_get_controller_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + u8 *oob = oob_required ? (u8 *)chip->oob_poi : NULL; + int ret; + u64 addr = (u64)page << chip->page_shift; + + brcmnand_set_ecc_enabled(host, 0); + ret = brcmnand_read(mtd, chip, addr, mtd->writesize >> FC_SHIFT, + (u32 *)buf, oob); + brcmnand_set_ecc_enabled(host, 1); + return ret; +} + +static int brcmnand_read_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + return brcmnand_read(mtd, chip, (u64)page << chip->page_shift, + mtd->writesize >> FC_SHIFT, + NULL, (u8 *)chip->oob_poi); +} + +static int brcmnand_read_oob_raw(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct brcmnand_host *host = nand_get_controller_data(chip); + + brcmnand_set_ecc_enabled(host, 0); + brcmnand_read(mtd, chip, (u64)page << chip->page_shift, + mtd->writesize >> FC_SHIFT, + NULL, (u8 *)chip->oob_poi); + brcmnand_set_ecc_enabled(host, 1); + return 0; +} + +static int brcmnand_write(struct mtd_info *mtd, struct nand_chip *chip, + u64 addr, const u32 *buf, u8 *oob) +{ + struct brcmnand_host *host = nand_get_controller_data(chip); + struct brcmnand_controller *ctrl = host->ctrl; + unsigned int i, j, trans = mtd->writesize >> FC_SHIFT; + int status, ret = 0; + + dev_dbg(ctrl->dev, "write %llx <- %p\n", (unsigned long long)addr, buf); + + if (unlikely((unsigned long)buf & 0x03)) { + dev_warn(ctrl->dev, "unaligned buffer: %p\n", buf); + buf = (u32 *)((unsigned long)buf & ~0x03); + } + + brcmnand_wp(mtd, 0); + + for (i = 0; i < ctrl->max_oob; i += 4) + oob_reg_write(ctrl, i, 0xffffffff); + + if (mtd->oops_panic_write) { + /* switch to interrupt polling and PIO mode */ + disable_ctrl_irqs(ctrl); + } 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; + } + + for (i = 0; i < trans; i++, addr += FC_BYTES) { + /* full address MUST be set before populating FC */ + brcmnand_set_cmd_addr(mtd, addr); + + if (buf) { + brcmnand_soc_data_bus_prepare(ctrl->soc, false); + + for (j = 0; j < FC_WORDS; j++, buf++) + brcmnand_write_fc(ctrl, j, *buf); + + brcmnand_soc_data_bus_unprepare(ctrl->soc, false); + } else if (oob) { + for (j = 0; j < FC_WORDS; j++) + brcmnand_write_fc(ctrl, j, 0xffffffff); + } + + if (oob) { + oob += write_oob_to_regs(ctrl, i, oob, + mtd->oobsize / trans, + host->hwcfg.sector_size_1k); + } + + /* we cannot use SPARE_AREA_PROGRAM when PARTIAL_PAGE_EN=0 */ + brcmnand_send_cmd(host, CMD_PROGRAM_PAGE); + status = brcmnand_waitfunc(chip); + + if (status < 0) { + ret = status; + goto out; + } + + if (status & NAND_STATUS_FAIL) { + dev_info(ctrl->dev, "program failed at %llx\n", + (unsigned long long)addr); + ret = -EIO; + goto out; + } + } +out: + brcmnand_wp(mtd, 1); + return ret; +} + +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); + void *oob = oob_required ? chip->oob_poi : NULL; + u64 addr = (u64)page << chip->page_shift; + + return brcmnand_write(mtd, chip, addr, (const u32 *)buf, oob); +} + +static int brcmnand_write_page_raw(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; + int ret = 0; + + brcmnand_set_ecc_enabled(host, 0); + ret = brcmnand_write(mtd, chip, addr, (const u32 *)buf, oob); + brcmnand_set_ecc_enabled(host, 1); + + return ret; +} + +static int brcmnand_write_oob(struct nand_chip *chip, int page) +{ + return brcmnand_write(nand_to_mtd(chip), chip, + (u64)page << chip->page_shift, NULL, + chip->oob_poi); +} + +static int brcmnand_write_oob_raw(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct brcmnand_host *host = nand_get_controller_data(chip); + int ret; + + brcmnand_set_ecc_enabled(host, 0); + ret = brcmnand_write(mtd, chip, (u64)page << chip->page_shift, NULL, + (u8 *)chip->oob_poi); + brcmnand_set_ecc_enabled(host, 1); + + return ret; +} + +static int brcmnand_exec_instr(struct brcmnand_host *host, int i, + const struct nand_operation *op) +{ + const struct nand_op_instr *instr = &op->instrs[i]; + struct brcmnand_controller *ctrl = host->ctrl; + const u8 *out; + bool last_op; + int ret = 0; + u8 *in; + + /* + * The controller needs to be aware of the last command in the operation + * (WAITRDY excepted). + */ + last_op = ((i == (op->ninstrs - 1)) && (instr->type != NAND_OP_WAITRDY_INSTR)) || + ((i == (op->ninstrs - 2)) && (op->instrs[i + 1].type == NAND_OP_WAITRDY_INSTR)); + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + brcmnand_low_level_op(host, LL_OP_CMD, instr->ctx.cmd.opcode, last_op); + break; + + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) + brcmnand_low_level_op(host, LL_OP_ADDR, instr->ctx.addr.addrs[i], + last_op && (i == (instr->ctx.addr.naddrs - 1))); + break; + + case NAND_OP_DATA_IN_INSTR: + in = instr->ctx.data.buf.in; + for (i = 0; i < instr->ctx.data.len; i++) { + brcmnand_low_level_op(host, LL_OP_RD, 0, + last_op && (i == (instr->ctx.data.len - 1))); + in[i] = brcmnand_read_reg(host->ctrl, BRCMNAND_LL_RDATA); + } + break; + + case NAND_OP_DATA_OUT_INSTR: + out = instr->ctx.data.buf.out; + for (i = 0; i < instr->ctx.data.len; i++) + brcmnand_low_level_op(host, LL_OP_WR, out[i], + last_op && (i == (instr->ctx.data.len - 1))); + break; + + case NAND_OP_WAITRDY_INSTR: + ret = bcmnand_ctrl_poll_status(host, NAND_CTRL_RDY, NAND_CTRL_RDY, 0); + break; + + default: + dev_err(ctrl->dev, "unsupported instruction type: %d\n", + instr->type); + ret = -EINVAL; + break; + } + + return ret; +} + +static int brcmnand_op_is_status(const struct nand_operation *op) +{ + if (op->ninstrs == 2 && + op->instrs[0].type == NAND_OP_CMD_INSTR && + op->instrs[0].ctx.cmd.opcode == NAND_CMD_STATUS && + op->instrs[1].type == NAND_OP_DATA_IN_INSTR) + return 1; + + return 0; +} + +static int brcmnand_op_is_reset(const struct nand_operation *op) +{ + if (op->ninstrs == 2 && + op->instrs[0].type == NAND_OP_CMD_INSTR && + op->instrs[0].ctx.cmd.opcode == NAND_CMD_RESET && + op->instrs[1].type == NAND_OP_WAITRDY_INSTR) + return 1; + + return 0; +} + +static int brcmnand_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; + int ret = 0; + + if (check_only) + return ctrl->check_instr(chip, op); + + if (brcmnand_op_is_status(op)) { + status = op->instrs[1].ctx.data.buf.in; + ret = brcmnand_status(host); + if (ret < 0) + return ret; + + *status = ret & 0xFF; + + return 0; + } else if (brcmnand_op_is_reset(op)) { + ret = brcmnand_reset(host); + if (ret < 0) + return ret; + + brcmnand_wp(mtd, 1); + + return 0; + } + + if (op->deassert_wp) + brcmnand_wp(mtd, 0); + + ret = ctrl->exec_instr(chip, op); + + if (op->deassert_wp) + brcmnand_wp(mtd, 1); + + return ret; +} + +/*********************************************************************** + * Per-CS setup (1 NAND device) + ***********************************************************************/ + +static int brcmnand_set_cfg(struct brcmnand_host *host, + struct brcmnand_cfg *cfg) +{ + struct brcmnand_controller *ctrl = host->ctrl; + struct nand_chip *chip = &host->chip; + u16 cfg_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_CFG); + u16 cfg_ext_offs = brcmnand_cs_offset(ctrl, host->cs, + BRCMNAND_CS_CFG_EXT); + u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs, + BRCMNAND_CS_ACC_CONTROL); + u8 block_size = 0, page_size = 0, device_size = 0; + u32 tmp; + + if (ctrl->block_sizes) { + int i, found; + + for (i = 0, found = 0; ctrl->block_sizes[i]; i++) + if (ctrl->block_sizes[i] * 1024 == cfg->block_size) { + block_size = i; + found = 1; + } + if (!found) { + dev_warn(ctrl->dev, "invalid block size %u\n", + cfg->block_size); + return -EINVAL; + } + } else { + block_size = ffs(cfg->block_size) - ffs(BRCMNAND_MIN_BLOCKSIZE); + } + + if (cfg->block_size < BRCMNAND_MIN_BLOCKSIZE || (ctrl->max_block_size && + cfg->block_size > ctrl->max_block_size)) { + dev_warn(ctrl->dev, "invalid block size %u\n", + cfg->block_size); + block_size = 0; + } + + if (ctrl->page_sizes) { + int i, found; + + for (i = 0, found = 0; ctrl->page_sizes[i]; i++) + if (ctrl->page_sizes[i] == cfg->page_size) { + page_size = i; + found = 1; + } + if (!found) { + dev_warn(ctrl->dev, "invalid page size %u\n", + cfg->page_size); + return -EINVAL; + } + } else { + page_size = ffs(cfg->page_size) - ffs(BRCMNAND_MIN_PAGESIZE); + } + + if (cfg->page_size < BRCMNAND_MIN_PAGESIZE || (ctrl->max_page_size && + cfg->page_size > ctrl->max_page_size)) { + dev_warn(ctrl->dev, "invalid page size %u\n", cfg->page_size); + return -EINVAL; + } + + if (fls64(cfg->device_size) < fls64(BRCMNAND_MIN_DEVSIZE)) { + dev_warn(ctrl->dev, "invalid device size 0x%llx\n", + (unsigned long long)cfg->device_size); + return -EINVAL; + } + device_size = fls64(cfg->device_size) - fls64(BRCMNAND_MIN_DEVSIZE); + + tmp = (cfg->blk_adr_bytes << CFG_BLK_ADR_BYTES_SHIFT) | + (cfg->col_adr_bytes << CFG_COL_ADR_BYTES_SHIFT) | + (cfg->ful_adr_bytes << CFG_FUL_ADR_BYTES_SHIFT) | + (!!(cfg->device_width == 16) << CFG_BUS_WIDTH_SHIFT) | + (device_size << CFG_DEVICE_SIZE_SHIFT); + if (cfg_offs == cfg_ext_offs) { + tmp |= (page_size << ctrl->page_size_shift) | + (block_size << CFG_BLK_SIZE_SHIFT); + nand_writereg(ctrl, cfg_offs, tmp); + } else { + nand_writereg(ctrl, cfg_offs, tmp); + tmp = (page_size << CFG_EXT_PAGE_SIZE_SHIFT) | + (block_size << CFG_EXT_BLK_SIZE_SHIFT); + nand_writereg(ctrl, cfg_ext_offs, tmp); + } + + tmp = nand_readreg(ctrl, acc_control_offs); + tmp &= ~brcmnand_ecc_level_mask(ctrl); + tmp &= ~brcmnand_spare_area_mask(ctrl); + if (ctrl->nand_version >= 0x0302) { + tmp |= cfg->ecc_level << ctrl->ecc_level_shift; + tmp |= cfg->spare_area_size; + } + nand_writereg(ctrl, acc_control_offs, tmp); + + brcmnand_set_sector_size_1k(host, cfg->sector_size_1k); + + /* threshold = ceil(BCH-level * 0.75) */ + brcmnand_wr_corr_thresh(host, DIV_ROUND_UP(chip->ecc.strength * 3, 4)); + + return 0; +} + +static void brcmnand_print_cfg(struct brcmnand_host *host, + char *buf, struct brcmnand_cfg *cfg) +{ + buf += sprintf(buf, + "%lluMiB total, %uKiB blocks, %u%s pages, %uB OOB, %u-bit", + (unsigned long long)cfg->device_size >> 20, + cfg->block_size >> 10, + cfg->page_size >= 1024 ? cfg->page_size >> 10 : cfg->page_size, + cfg->page_size >= 1024 ? "KiB" : "B", + cfg->spare_area_size, cfg->device_width); + + /* Account for Hamming ECC and for BCH 512B vs 1KiB sectors */ + if (is_hamming_ecc(host->ctrl, cfg)) + sprintf(buf, ", Hamming ECC"); + else if (cfg->sector_size_1k) + sprintf(buf, ", BCH-%u (1KiB sector)", cfg->ecc_level << 1); + else + sprintf(buf, ", BCH-%u", cfg->ecc_level); +} + +/* + * Minimum number of bytes to address a page. Calculated as: + * roundup(log2(size / page-size) / 8) + * + * NB: the following does not "round up" for non-power-of-2 'size'; but this is + * OK because many other things will break if 'size' is irregular... + */ +static inline int get_blk_adr_bytes(u64 size, u32 writesize) +{ + return ALIGN(ilog2(size) - ilog2(writesize), 8) >> 3; +} + +static int brcmnand_setup_dev(struct brcmnand_host *host) +{ + struct mtd_info *mtd = nand_to_mtd(&host->chip); + struct nand_chip *chip = &host->chip; + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&chip->base); + struct nand_memory_organization *memorg = + nanddev_get_memorg(&chip->base); + struct brcmnand_controller *ctrl = host->ctrl; + struct brcmnand_cfg *cfg = &host->hwcfg; + struct device_node *np = nand_get_flash_node(chip); + u32 offs, tmp, oob_sector; + bool use_strap = false; + char msg[128]; + int ret; + + memset(cfg, 0, sizeof(*cfg)); + use_strap = of_property_read_bool(np, "brcm,nand-ecc-use-strap"); + + /* + * Either nand-ecc-xxx or brcm,nand-ecc-use-strap can be set. Error out + * if both exist. + */ + if (chip->ecc.strength && use_strap) { + dev_err(ctrl->dev, + "ECC strap and DT ECC configuration properties are mutually exclusive\n"); + return -EINVAL; + } + + if (use_strap) + brcmnand_get_ecc_settings(host, chip); + + ret = of_property_read_u32(np, "brcm,nand-oob-sector-size", + &oob_sector); + if (ret) { + if (use_strap) + cfg->spare_area_size = brcmnand_get_spare_size(host); + else + /* Use detected size */ + cfg->spare_area_size = mtd->oobsize / + (mtd->writesize >> FC_SHIFT); + } else { + cfg->spare_area_size = oob_sector; + } + if (cfg->spare_area_size > ctrl->max_oob) + cfg->spare_area_size = ctrl->max_oob; + /* + * Set mtd and memorg oobsize to be consistent with controller's + * spare_area_size, as the rest is inaccessible. + */ + mtd->oobsize = cfg->spare_area_size * (mtd->writesize >> FC_SHIFT); + memorg->oobsize = mtd->oobsize; + + cfg->device_size = mtd->size; + cfg->block_size = mtd->erasesize; + cfg->page_size = mtd->writesize; + cfg->device_width = (chip->options & NAND_BUSWIDTH_16) ? 16 : 8; + cfg->col_adr_bytes = 2; + cfg->blk_adr_bytes = get_blk_adr_bytes(mtd->size, mtd->writesize); + + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) { + dev_err(ctrl->dev, "only HW ECC supported; selected: %d\n", + chip->ecc.engine_type); + return -EINVAL; + } + + if (chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) { + if (chip->ecc.strength == 1 && chip->ecc.size == 512) + /* Default to Hamming for 1-bit ECC, if unspecified */ + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + else + /* Otherwise, BCH */ + chip->ecc.algo = NAND_ECC_ALGO_BCH; + } + + if (chip->ecc.algo == NAND_ECC_ALGO_HAMMING && + (chip->ecc.strength != 1 || chip->ecc.size != 512)) { + dev_err(ctrl->dev, "invalid Hamming params: %d bits per %d bytes\n", + chip->ecc.strength, chip->ecc.size); + return -EINVAL; + } + + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_NONE && + (!chip->ecc.size || !chip->ecc.strength)) { + if (requirements->step_size && requirements->strength) { + /* use detected ECC parameters */ + chip->ecc.size = requirements->step_size; + chip->ecc.strength = requirements->strength; + dev_info(ctrl->dev, "Using ECC step-size %d, strength %d\n", + chip->ecc.size, chip->ecc.strength); + } + } + + switch (chip->ecc.size) { + case 512: + if (chip->ecc.algo == NAND_ECC_ALGO_HAMMING) + cfg->ecc_level = 15; + else + cfg->ecc_level = chip->ecc.strength; + cfg->sector_size_1k = 0; + break; + case 1024: + if (!(ctrl->features & BRCMNAND_HAS_1K_SECTORS)) { + dev_err(ctrl->dev, "1KB sectors not supported\n"); + return -EINVAL; + } + if (chip->ecc.strength & 0x1) { + dev_err(ctrl->dev, + "odd ECC not supported with 1KB sectors\n"); + return -EINVAL; + } + + cfg->ecc_level = chip->ecc.strength >> 1; + cfg->sector_size_1k = 1; + break; + default: + dev_err(ctrl->dev, "unsupported ECC size: %d\n", + chip->ecc.size); + return -EINVAL; + } + + cfg->ful_adr_bytes = cfg->blk_adr_bytes; + if (mtd->writesize > 512) + cfg->ful_adr_bytes += cfg->col_adr_bytes; + else + cfg->ful_adr_bytes += 1; + + ret = brcmnand_set_cfg(host, cfg); + if (ret) + return ret; + + brcmnand_set_ecc_enabled(host, 1); + + brcmnand_print_cfg(host, msg, cfg); + dev_info(ctrl->dev, "detected %s\n", msg); + + /* Configure ACC_CONTROL */ + offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_ACC_CONTROL); + tmp = nand_readreg(ctrl, offs); + tmp &= ~ACC_CONTROL_PARTIAL_PAGE; + tmp &= ~ACC_CONTROL_RD_ERASED; + + /* We need to turn on Read from erased paged protected by ECC */ + if (ctrl->nand_version >= 0x0702) + tmp |= ACC_CONTROL_RD_ERASED; + tmp &= ~ACC_CONTROL_FAST_PGM_RDIN; + if (ctrl->features & BRCMNAND_HAS_PREFETCH) + tmp &= ~ACC_CONTROL_PREFETCH; + + nand_writereg(ctrl, offs, tmp); + + return 0; +} + +static int brcmnand_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct brcmnand_host *host = nand_get_controller_data(chip); + int ret; + + chip->options |= NAND_NO_SUBPAGE_WRITE; + /* + * Avoid (for instance) kmap()'d buffers from JFFS2, which we can't DMA + * to/from, and have nand_base pass us a bounce buffer instead, as + * needed. + */ + chip->options |= NAND_USES_DMA; + + if (chip->bbt_options & NAND_BBT_USE_FLASH) + chip->bbt_options |= NAND_BBT_NO_OOB; + + if (brcmnand_setup_dev(host)) + return -ENXIO; + + chip->ecc.size = host->hwcfg.sector_size_1k ? 1024 : 512; + + /* only use our internal HW threshold */ + mtd->bitflip_threshold = 1; + + ret = brcmstb_choose_ecc_layout(host); + + /* If OOB is written with ECC enabled it will cause ECC errors */ + if (is_hamming_ecc(host->ctrl, &host->hwcfg)) { + chip->ecc.write_oob = brcmnand_write_oob_raw; + chip->ecc.read_oob = brcmnand_read_oob_raw; + } + + return ret; +} + +static const struct nand_controller_ops brcmnand_controller_ops = { + .attach_chip = brcmnand_attach_chip, + .exec_op = brcmnand_exec_op, +}; + +static int brcmnand_init_cs(struct brcmnand_host *host, + const char * const *part_probe_types) +{ + struct brcmnand_controller *ctrl = host->ctrl; + struct device *dev = ctrl->dev; + struct mtd_info *mtd; + struct nand_chip *chip; + int ret; + u16 cfg_offs; + + mtd = nand_to_mtd(&host->chip); + chip = &host->chip; + + nand_set_controller_data(chip, host); + mtd->name = devm_kasprintf(dev, GFP_KERNEL, "brcmnand.%d", + host->cs); + if (!mtd->name) + return -ENOMEM; + + mtd->owner = THIS_MODULE; + mtd->dev.parent = dev; + + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + chip->ecc.read_page = brcmnand_read_page; + chip->ecc.write_page = brcmnand_write_page; + chip->ecc.read_page_raw = brcmnand_read_page_raw; + chip->ecc.write_page_raw = brcmnand_write_page_raw; + chip->ecc.write_oob_raw = brcmnand_write_oob_raw; + chip->ecc.read_oob_raw = brcmnand_read_oob_raw; + chip->ecc.read_oob = brcmnand_read_oob; + chip->ecc.write_oob = brcmnand_write_oob; + + chip->controller = &ctrl->controller; + ctrl->controller.controller_wp = 1; + + /* + * The bootloader might have configured 16bit mode but + * NAND READID command only works in 8bit mode. We force + * 8bit mode here to ensure that NAND READID commands works. + */ + cfg_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_CFG); + nand_writereg(ctrl, cfg_offs, + nand_readreg(ctrl, cfg_offs) & ~CFG_BUS_WIDTH); + + ret = nand_scan(chip, 1); + if (ret) + return ret; + + ret = mtd_device_parse_register(mtd, part_probe_types, NULL, NULL, 0); + if (ret) + nand_cleanup(chip); + + return ret; +} + +static void brcmnand_save_restore_cs_config(struct brcmnand_host *host, + int restore) +{ + struct brcmnand_controller *ctrl = host->ctrl; + u16 cfg_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_CFG); + u16 cfg_ext_offs = brcmnand_cs_offset(ctrl, host->cs, + BRCMNAND_CS_CFG_EXT); + u16 acc_control_offs = brcmnand_cs_offset(ctrl, host->cs, + BRCMNAND_CS_ACC_CONTROL); + u16 t1_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_TIMING1); + u16 t2_offs = brcmnand_cs_offset(ctrl, host->cs, BRCMNAND_CS_TIMING2); + + if (restore) { + nand_writereg(ctrl, cfg_offs, host->hwcfg.config); + if (cfg_offs != cfg_ext_offs) + nand_writereg(ctrl, cfg_ext_offs, + host->hwcfg.config_ext); + nand_writereg(ctrl, acc_control_offs, host->hwcfg.acc_control); + nand_writereg(ctrl, t1_offs, host->hwcfg.timing_1); + nand_writereg(ctrl, t2_offs, host->hwcfg.timing_2); + } else { + host->hwcfg.config = nand_readreg(ctrl, cfg_offs); + if (cfg_offs != cfg_ext_offs) + host->hwcfg.config_ext = + nand_readreg(ctrl, cfg_ext_offs); + host->hwcfg.acc_control = nand_readreg(ctrl, acc_control_offs); + host->hwcfg.timing_1 = nand_readreg(ctrl, t1_offs); + host->hwcfg.timing_2 = nand_readreg(ctrl, t2_offs); + } +} + +static int brcmnand_suspend(struct device *dev) +{ + struct brcmnand_controller *ctrl = dev_get_drvdata(dev); + struct brcmnand_host *host; + + list_for_each_entry(host, &ctrl->host_list, node) + brcmnand_save_restore_cs_config(host, 0); + + ctrl->nand_cs_nand_select = brcmnand_read_reg(ctrl, BRCMNAND_CS_SELECT); + ctrl->nand_cs_nand_xor = brcmnand_read_reg(ctrl, BRCMNAND_CS_XOR); + ctrl->corr_stat_threshold = + brcmnand_read_reg(ctrl, BRCMNAND_CORR_THRESHOLD); + + if (has_flash_dma(ctrl)) + ctrl->flash_dma_mode = flash_dma_readl(ctrl, FLASH_DMA_MODE); + else if (has_edu(ctrl)) + ctrl->edu_config = edu_readl(ctrl, EDU_CONFIG); + + return 0; +} + +static int brcmnand_resume(struct device *dev) +{ + struct brcmnand_controller *ctrl = dev_get_drvdata(dev); + struct brcmnand_host *host; + + if (has_flash_dma(ctrl)) { + flash_dma_writel(ctrl, FLASH_DMA_MODE, ctrl->flash_dma_mode); + flash_dma_writel(ctrl, FLASH_DMA_ERROR_STATUS, 0); + } + + if (has_edu(ctrl)) { + ctrl->edu_config = edu_readl(ctrl, EDU_CONFIG); + edu_writel(ctrl, EDU_CONFIG, ctrl->edu_config); + edu_readl(ctrl, EDU_CONFIG); + brcmnand_edu_init(ctrl); + } + + brcmnand_write_reg(ctrl, BRCMNAND_CS_SELECT, ctrl->nand_cs_nand_select); + brcmnand_write_reg(ctrl, BRCMNAND_CS_XOR, ctrl->nand_cs_nand_xor); + brcmnand_write_reg(ctrl, BRCMNAND_CORR_THRESHOLD, + ctrl->corr_stat_threshold); + if (ctrl->soc) { + /* Clear/re-enable interrupt */ + ctrl->soc->ctlrdy_ack(ctrl->soc); + ctrl->soc->ctlrdy_set_enabled(ctrl->soc, true); + } + + list_for_each_entry(host, &ctrl->host_list, node) { + struct nand_chip *chip = &host->chip; + + brcmnand_save_restore_cs_config(host, 1); + + /* Reset the chip, required by some chips after power-up */ + nand_reset(chip, 0); + } + + return 0; +} + +const struct dev_pm_ops brcmnand_pm_ops = { + .suspend = brcmnand_suspend, + .resume = brcmnand_resume, +}; +EXPORT_SYMBOL_GPL(brcmnand_pm_ops); + +static const struct of_device_id __maybe_unused brcmnand_of_match[] = { + { .compatible = "brcm,brcmnand-v2.1" }, + { .compatible = "brcm,brcmnand-v2.2" }, + { .compatible = "brcm,brcmnand-v4.0" }, + { .compatible = "brcm,brcmnand-v5.0" }, + { .compatible = "brcm,brcmnand-v6.0" }, + { .compatible = "brcm,brcmnand-v6.1" }, + { .compatible = "brcm,brcmnand-v6.2" }, + { .compatible = "brcm,brcmnand-v7.0" }, + { .compatible = "brcm,brcmnand-v7.1" }, + { .compatible = "brcm,brcmnand-v7.2" }, + { .compatible = "brcm,brcmnand-v7.3" }, + {}, +}; +MODULE_DEVICE_TABLE(of, brcmnand_of_match); + +/*********************************************************************** + * Platform driver setup (per controller) + ***********************************************************************/ +static int brcmnand_edu_setup(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct brcmnand_controller *ctrl = dev_get_drvdata(&pdev->dev); + struct resource *res; + int ret; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "flash-edu"); + if (res) { + ctrl->edu_base = devm_ioremap_resource(dev, res); + if (IS_ERR(ctrl->edu_base)) + return PTR_ERR(ctrl->edu_base); + + ctrl->edu_offsets = edu_regs; + + edu_writel(ctrl, EDU_CONFIG, EDU_CONFIG_MODE_NAND | + EDU_CONFIG_SWAP_CFG); + edu_readl(ctrl, EDU_CONFIG); + + /* initialize edu */ + brcmnand_edu_init(ctrl); + + ctrl->edu_irq = platform_get_irq_optional(pdev, 1); + if (ctrl->edu_irq < 0) { + dev_warn(dev, + "FLASH EDU enabled, using ctlrdy irq\n"); + } else { + ret = devm_request_irq(dev, ctrl->edu_irq, + brcmnand_edu_irq, 0, + "brcmnand-edu", ctrl); + if (ret < 0) { + dev_err(ctrl->dev, "can't allocate IRQ %d: error %d\n", + ctrl->edu_irq, ret); + return ret; + } + + dev_info(dev, "FLASH EDU enabled using irq %u\n", + ctrl->edu_irq); + } + } + + return 0; +} + +int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc) +{ + struct brcmnand_platform_data *pd = dev_get_platdata(&pdev->dev); + struct device *dev = &pdev->dev; + struct device_node *dn = dev->of_node; + struct brcmnand_controller *ctrl; + struct brcmnand_host *host; + struct resource *res; + int ret; + + if (dn && !of_match_node(brcmnand_of_match, dn)) + return -ENODEV; + + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + dev_set_drvdata(dev, ctrl); + ctrl->dev = dev; + ctrl->soc = soc; + + /* Enable the static key if the soc provides I/O operations indicating + * that a non-memory mapped IO access path must be used + */ + if (brcmnand_soc_has_ops(ctrl->soc)) + static_branch_enable(&brcmnand_soc_has_ops_key); + + init_completion(&ctrl->done); + init_completion(&ctrl->dma_done); + init_completion(&ctrl->edu_done); + nand_controller_init(&ctrl->controller); + ctrl->controller.ops = &brcmnand_controller_ops; + INIT_LIST_HEAD(&ctrl->host_list); + + /* NAND register range */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ctrl->nand_base = devm_ioremap_resource(dev, res); + if (IS_ERR(ctrl->nand_base) && !brcmnand_soc_has_ops(soc)) + return PTR_ERR(ctrl->nand_base); + + /* Enable clock before using NAND registers */ + ctrl->clk = devm_clk_get(dev, "nand"); + if (!IS_ERR(ctrl->clk)) { + ret = clk_prepare_enable(ctrl->clk); + if (ret) + return ret; + } else { + ret = PTR_ERR(ctrl->clk); + if (ret == -EPROBE_DEFER) + return ret; + + ctrl->clk = NULL; + } + + /* Initialize NAND revision */ + ret = brcmnand_revision_init(ctrl); + 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. + */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand-cache"); + if (res) { + ctrl->nand_fc = devm_ioremap_resource(dev, res); + if (IS_ERR(ctrl->nand_fc)) { + ret = PTR_ERR(ctrl->nand_fc); + goto err; + } + } else { + ctrl->nand_fc = ctrl->nand_base + + ctrl->reg_offsets[BRCMNAND_FC_BASE]; + } + + /* FLASH_DMA */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "flash-dma"); + if (res) { + ctrl->flash_dma_base = devm_ioremap_resource(dev, res); + if (IS_ERR(ctrl->flash_dma_base)) { + ret = PTR_ERR(ctrl->flash_dma_base); + goto err; + } + + /* initialize the dma version */ + brcmnand_flash_dma_revision_init(ctrl); + + ret = -EIO; + if (ctrl->nand_version >= 0x0700) + ret = dma_set_mask_and_coherent(&pdev->dev, + DMA_BIT_MASK(40)); + if (ret) + ret = dma_set_mask_and_coherent(&pdev->dev, + DMA_BIT_MASK(32)); + if (ret) + goto err; + + /* linked-list and stop on error */ + flash_dma_writel(ctrl, FLASH_DMA_MODE, FLASH_DMA_MODE_MASK); + flash_dma_writel(ctrl, FLASH_DMA_ERROR_STATUS, 0); + + /* Allocate descriptor(s) */ + ctrl->dma_desc = dmam_alloc_coherent(dev, + sizeof(*ctrl->dma_desc), + &ctrl->dma_pa, GFP_KERNEL); + if (!ctrl->dma_desc) { + ret = -ENOMEM; + goto err; + } + + ctrl->dma_irq = platform_get_irq(pdev, 1); + if ((int)ctrl->dma_irq < 0) { + dev_err(dev, "missing FLASH_DMA IRQ\n"); + ret = -ENODEV; + goto err; + } + + ret = devm_request_irq(dev, ctrl->dma_irq, + brcmnand_dma_irq, 0, DRV_NAME, + ctrl); + if (ret < 0) { + dev_err(dev, "can't allocate IRQ %d: error %d\n", + ctrl->dma_irq, ret); + goto err; + } + + dev_info(dev, "enabling FLASH_DMA\n"); + /* set flash dma transfer function to call */ + ctrl->dma_trans = brcmnand_dma_trans; + } else { + ret = brcmnand_edu_setup(pdev); + if (ret < 0) + goto err; + + if (has_edu(ctrl)) + /* set edu transfer function to call */ + ctrl->dma_trans = brcmnand_edu_trans; + } + + /* Disable automatic device ID config, direct addressing */ + brcmnand_rmw_reg(ctrl, BRCMNAND_CS_SELECT, + CS_SELECT_AUTO_DEVICE_ID_CFG | 0xff, 0, 0); + /* Disable XOR addressing */ + brcmnand_rmw_reg(ctrl, BRCMNAND_CS_XOR, 0xff, 0, 0); + + /* Check if the board connects the WP pin */ + if (of_property_read_bool(dn, "brcm,wp-not-connected")) + wp_on = 0; + + if (ctrl->features & BRCMNAND_HAS_WP) { + /* Permanently disable write protection */ + if (wp_on == 2) + brcmnand_set_wp(ctrl, false); + } else { + wp_on = 0; + } + + /* IRQ */ + ctrl->irq = platform_get_irq_optional(pdev, 0); + if (ctrl->irq > 0) { + /* + * Some SoCs integrate this controller (e.g., its interrupt bits) in + * interesting ways + */ + if (soc) { + ret = devm_request_irq(dev, ctrl->irq, brcmnand_irq, 0, + DRV_NAME, ctrl); + + /* Enable interrupt */ + ctrl->soc->ctlrdy_ack(ctrl->soc); + ctrl->soc->ctlrdy_set_enabled(ctrl->soc, true); + } else { + /* Use standard interrupt infrastructure */ + ret = devm_request_irq(dev, ctrl->irq, brcmnand_ctlrdy_irq, 0, + DRV_NAME, ctrl); + } + if (ret < 0) { + dev_err(dev, "can't allocate IRQ %d: error %d\n", + ctrl->irq, ret); + goto err; + } + } + + 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) { + ret = -ENOMEM; + goto err; + } + host->pdev = pdev; + host->ctrl = ctrl; + + ret = of_property_read_u32(child, "reg", &host->cs); + if (ret) { + dev_err(dev, "can't get chip-select\n"); + devm_kfree(dev, host); + continue; + } + + nand_set_flash_node(&host->chip, child); + + ret = brcmnand_init_cs(host, NULL); + if (ret) { + if (ret == -EPROBE_DEFER) + goto err; + + devm_kfree(dev, host); + continue; /* Try all chip-selects */ + } + + list_add_tail(&host->node, &ctrl->host_list); + } + } + + if (!list_empty(&ctrl->host_list)) + return 0; + + if (!pd) { + ret = -ENODEV; + goto err; + } + + /* If we got there we must have been probing via platform data */ + host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); + if (!host) { + ret = -ENOMEM; + goto err; + } + host->pdev = pdev; + host->ctrl = ctrl; + host->cs = pd->chip_select; + host->chip.ecc.size = pd->ecc_stepsize; + host->chip.ecc.strength = pd->ecc_strength; + + ret = brcmnand_init_cs(host, pd->part_probe_types); + if (ret) + goto err; + + list_add_tail(&host->node, &ctrl->host_list); + + /* No chip-selects could initialize properly */ + if (list_empty(&ctrl->host_list)) { + ret = -ENODEV; + goto err; + } + + return 0; + +err: + clk_disable_unprepare(ctrl->clk); + return ret; + +} +EXPORT_SYMBOL_GPL(brcmnand_probe); + +void brcmnand_remove(struct platform_device *pdev) +{ + struct brcmnand_controller *ctrl = dev_get_drvdata(&pdev->dev); + struct brcmnand_host *host; + struct nand_chip *chip; + int ret; + + list_for_each_entry(host, &ctrl->host_list, node) { + chip = &host->chip; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + } + + clk_disable_unprepare(ctrl->clk); + + dev_set_drvdata(&pdev->dev, NULL); +} +EXPORT_SYMBOL_GPL(brcmnand_remove); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Kevin Cernekee"); +MODULE_AUTHOR("Brian Norris"); +MODULE_DESCRIPTION("NAND driver for Broadcom chips"); +MODULE_ALIAS("platform:brcmnand"); diff --git a/drivers/mtd/nand/raw/brcmnand/brcmnand.h b/drivers/mtd/nand/raw/brcmnand/brcmnand.h new file mode 100644 index 000000000..9f171252a --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/brcmnand.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright © 2015 Broadcom Corporation + */ + +#ifndef __BRCMNAND_H__ +#define __BRCMNAND_H__ + +#include +#include + +struct platform_device; +struct dev_pm_ops; +struct brcmnand_io_ops; + +/* Special register offset constant to intercept a non-MMIO access + * to the flash cache register space. This is intentionally large + * not to overlap with an existing offset. + */ +#define BRCMNAND_NON_MMIO_FC_ADDR 0xffffffff + +struct brcmnand_soc { + bool (*ctlrdy_ack)(struct brcmnand_soc *soc); + void (*ctlrdy_set_enabled)(struct brcmnand_soc *soc, bool en); + void (*prepare_data_bus)(struct brcmnand_soc *soc, bool prepare, + bool is_param); + void (*read_data_bus)(struct brcmnand_soc *soc, void __iomem *flash_cache, + u32 *buffer, int fc_words); + const struct brcmnand_io_ops *ops; +}; + +struct brcmnand_io_ops { + u32 (*read_reg)(struct brcmnand_soc *soc, u32 offset); + void (*write_reg)(struct brcmnand_soc *soc, u32 val, u32 offset); +}; + +static inline void brcmnand_soc_data_bus_prepare(struct brcmnand_soc *soc, + bool is_param) +{ + if (soc && soc->prepare_data_bus) + soc->prepare_data_bus(soc, true, is_param); +} + +static inline void brcmnand_soc_data_bus_unprepare(struct brcmnand_soc *soc, + bool is_param) +{ + if (soc && soc->prepare_data_bus) + soc->prepare_data_bus(soc, false, is_param); +} + +static inline u32 brcmnand_readl(void __iomem *addr) +{ + /* + * MIPS endianness is configured by boot strap, which also reverses all + * bus endianness (i.e., big-endian CPU + big endian bus ==> native + * endian I/O). + * + * Other architectures (e.g., ARM) either do not support big endian, or + * else leave I/O in little endian mode. + */ + if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) + return __raw_readl(addr); + else + return readl_relaxed(addr); +} + +static inline void brcmnand_writel(u32 val, void __iomem *addr) +{ + /* See brcmnand_readl() comments */ + if (IS_ENABLED(CONFIG_MIPS) && IS_ENABLED(CONFIG_CPU_BIG_ENDIAN)) + __raw_writel(val, addr); + else + writel_relaxed(val, addr); +} + +static inline bool brcmnand_soc_has_ops(struct brcmnand_soc *soc) +{ + return soc && soc->ops && soc->ops->read_reg && soc->ops->write_reg; +} + +static inline u32 brcmnand_soc_read(struct brcmnand_soc *soc, u32 offset) +{ + return soc->ops->read_reg(soc, offset); +} + +static inline void brcmnand_soc_write(struct brcmnand_soc *soc, u32 val, + u32 offset) +{ + soc->ops->write_reg(soc, val, offset); +} + +int brcmnand_probe(struct platform_device *pdev, struct brcmnand_soc *soc); +void brcmnand_remove(struct platform_device *pdev); + +extern const struct dev_pm_ops brcmnand_pm_ops; + +#endif /* __BRCMNAND_H__ */ diff --git a/drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c b/drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c new file mode 100644 index 000000000..8ed64613f --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/brcmstb_nand.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright © 2015 Broadcom Corporation + */ + +#include +#include +#include + +#include "brcmnand.h" + +static const struct of_device_id brcmstb_nand_of_match[] = { + { .compatible = "brcm,brcmnand" }, + {}, +}; +MODULE_DEVICE_TABLE(of, brcmstb_nand_of_match); + +static int brcmstb_nand_probe(struct platform_device *pdev) +{ + return brcmnand_probe(pdev, NULL); +} + +static struct platform_driver brcmstb_nand_driver = { + .probe = brcmstb_nand_probe, + .remove = brcmnand_remove, + .driver = { + .name = "brcmstb_nand", + .pm = &brcmnand_pm_ops, + .of_match_table = brcmstb_nand_of_match, + } +}; +module_platform_driver(brcmstb_nand_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Brian Norris"); +MODULE_DESCRIPTION("NAND driver for Broadcom STB chips"); diff --git a/drivers/mtd/nand/raw/brcmnand/iproc_nand.c b/drivers/mtd/nand/raw/brcmnand/iproc_nand.c new file mode 100644 index 000000000..089c70fc6 --- /dev/null +++ b/drivers/mtd/nand/raw/brcmnand/iproc_nand.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright © 2015 Broadcom Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "brcmnand.h" + +struct iproc_nand_soc { + struct brcmnand_soc soc; + + void __iomem *idm_base; + void __iomem *ext_base; + spinlock_t idm_lock; +}; + +#define IPROC_NAND_CTLR_READY_OFFSET 0x10 +#define IPROC_NAND_CTLR_READY BIT(0) + +#define IPROC_NAND_IO_CTRL_OFFSET 0x00 +#define IPROC_NAND_APB_LE_MODE BIT(24) +#define IPROC_NAND_INT_CTRL_READ_ENABLE BIT(6) + +static bool iproc_nand_intc_ack(struct brcmnand_soc *soc) +{ + struct iproc_nand_soc *priv = + container_of(soc, struct iproc_nand_soc, soc); + void __iomem *mmio = priv->ext_base + IPROC_NAND_CTLR_READY_OFFSET; + u32 val = brcmnand_readl(mmio); + + if (val & IPROC_NAND_CTLR_READY) { + brcmnand_writel(IPROC_NAND_CTLR_READY, mmio); + return true; + } + + return false; +} + +static void iproc_nand_intc_set(struct brcmnand_soc *soc, bool en) +{ + struct iproc_nand_soc *priv = + container_of(soc, struct iproc_nand_soc, soc); + void __iomem *mmio = priv->idm_base + IPROC_NAND_IO_CTRL_OFFSET; + u32 val; + unsigned long flags; + + spin_lock_irqsave(&priv->idm_lock, flags); + + val = brcmnand_readl(mmio); + + if (en) + val |= IPROC_NAND_INT_CTRL_READ_ENABLE; + else + val &= ~IPROC_NAND_INT_CTRL_READ_ENABLE; + + brcmnand_writel(val, mmio); + + spin_unlock_irqrestore(&priv->idm_lock, flags); +} + +static void iproc_nand_apb_access(struct brcmnand_soc *soc, bool prepare, + bool is_param) +{ + struct iproc_nand_soc *priv = + container_of(soc, struct iproc_nand_soc, soc); + void __iomem *mmio = priv->idm_base + IPROC_NAND_IO_CTRL_OFFSET; + u32 val; + unsigned long flags; + + spin_lock_irqsave(&priv->idm_lock, flags); + + val = brcmnand_readl(mmio); + + /* + * In the case of BE or when dealing with NAND data, alway configure + * the APB bus to LE mode before accessing the FIFO and back to BE mode + * after the access is done + */ + if (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) || !is_param) { + if (prepare) + val |= IPROC_NAND_APB_LE_MODE; + else + val &= ~IPROC_NAND_APB_LE_MODE; + } else { /* when in LE accessing the parameter page, keep APB in BE */ + val &= ~IPROC_NAND_APB_LE_MODE; + } + + brcmnand_writel(val, mmio); + + spin_unlock_irqrestore(&priv->idm_lock, flags); +} + +static int iproc_nand_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct iproc_nand_soc *priv; + struct brcmnand_soc *soc; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + soc = &priv->soc; + + spin_lock_init(&priv->idm_lock); + + priv->idm_base = devm_platform_ioremap_resource_byname(pdev, "iproc-idm"); + if (IS_ERR(priv->idm_base)) + return PTR_ERR(priv->idm_base); + + priv->ext_base = devm_platform_ioremap_resource_byname(pdev, "iproc-ext"); + if (IS_ERR(priv->ext_base)) + return PTR_ERR(priv->ext_base); + + soc->ctlrdy_ack = iproc_nand_intc_ack; + soc->ctlrdy_set_enabled = iproc_nand_intc_set; + soc->prepare_data_bus = iproc_nand_apb_access; + + return brcmnand_probe(pdev, soc); +} + +static const struct of_device_id iproc_nand_of_match[] = { + { .compatible = "brcm,nand-iproc" }, + {}, +}; +MODULE_DEVICE_TABLE(of, iproc_nand_of_match); + +static struct platform_driver iproc_nand_driver = { + .probe = iproc_nand_probe, + .remove = brcmnand_remove, + .driver = { + .name = "iproc_nand", + .pm = &brcmnand_pm_ops, + .of_match_table = iproc_nand_of_match, + } +}; +module_platform_driver(iproc_nand_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Brian Norris"); +MODULE_AUTHOR("Ray Jui"); +MODULE_DESCRIPTION("NAND driver for Broadcom IPROC-based SoCs"); diff --git a/drivers/mtd/nand/raw/cadence-nand-controller.c b/drivers/mtd/nand/raw/cadence-nand-controller.c new file mode 100644 index 000000000..d53b35a8b --- /dev/null +++ b/drivers/mtd/nand/raw/cadence-nand-controller.c @@ -0,0 +1,3348 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Cadence NAND flash controller driver + * + * Copyright (C) 2019 Cadence + * + * Author: Piotr Sroka + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * HPNFC can work in 3 modes: + * - PIO - can work in master or slave DMA + * - CDMA - needs Master DMA for accessing command descriptors. + * - Generic mode - can use only slave DMA. + * CDMA and PIO modes can be used to execute only base commands. + * Generic mode can be used to execute any command + * on NAND flash memory. Driver uses CDMA mode for + * block erasing, page reading, page programing. + * Generic mode is used for executing rest of commands. + */ + +#define MAX_ADDRESS_CYC 6 +#define MAX_ERASE_ADDRESS_CYC 3 +#define MAX_DATA_SIZE 0xFFFC +#define DMA_DATA_SIZE_ALIGN 8 + +/* Register definition. */ +/* + * Command register 0. + * Writing data to this register will initiate a new transaction + * of the NF controller. + */ +#define CMD_REG0 0x0000 +/* Command type field mask. */ +#define CMD_REG0_CT GENMASK(31, 30) +/* Command type CDMA. */ +#define CMD_REG0_CT_CDMA 0uL +/* Command type generic. */ +#define CMD_REG0_CT_GEN 3uL +/* Command thread number field mask. */ +#define CMD_REG0_TN GENMASK(27, 24) + +/* Command register 2. */ +#define CMD_REG2 0x0008 +/* Command register 3. */ +#define CMD_REG3 0x000C +/* Pointer register to select which thread status will be selected. */ +#define CMD_STATUS_PTR 0x0010 +/* Command status register for selected thread. */ +#define CMD_STATUS 0x0014 + +/* Interrupt status register. */ +#define INTR_STATUS 0x0110 +#define INTR_STATUS_SDMA_ERR BIT(22) +#define INTR_STATUS_SDMA_TRIGG BIT(21) +#define INTR_STATUS_UNSUPP_CMD BIT(19) +#define INTR_STATUS_DDMA_TERR BIT(18) +#define INTR_STATUS_CDMA_TERR BIT(17) +#define INTR_STATUS_CDMA_IDL BIT(16) + +/* Interrupt enable register. */ +#define INTR_ENABLE 0x0114 +#define INTR_ENABLE_INTR_EN BIT(31) +#define INTR_ENABLE_SDMA_ERR_EN BIT(22) +#define INTR_ENABLE_SDMA_TRIGG_EN BIT(21) +#define INTR_ENABLE_UNSUPP_CMD_EN BIT(19) +#define INTR_ENABLE_DDMA_TERR_EN BIT(18) +#define INTR_ENABLE_CDMA_TERR_EN BIT(17) +#define INTR_ENABLE_CDMA_IDLE_EN BIT(16) + +/* Controller internal state. */ +#define CTRL_STATUS 0x0118 +#define CTRL_STATUS_INIT_COMP BIT(9) +#define CTRL_STATUS_CTRL_BUSY BIT(8) + +/* Command Engine threads state. */ +#define TRD_STATUS 0x0120 + +/* Command Engine interrupt thread error status. */ +#define TRD_ERR_INT_STATUS 0x0128 +/* Command Engine interrupt thread error enable. */ +#define TRD_ERR_INT_STATUS_EN 0x0130 +/* Command Engine interrupt thread complete status. */ +#define TRD_COMP_INT_STATUS 0x0138 + +/* + * Transfer config 0 register. + * Configures data transfer parameters. + */ +#define TRAN_CFG_0 0x0400 +/* Offset value from the beginning of the page. */ +#define TRAN_CFG_0_OFFSET GENMASK(31, 16) +/* Numbers of sectors to transfer within singlNF device's page. */ +#define TRAN_CFG_0_SEC_CNT GENMASK(7, 0) + +/* + * Transfer config 1 register. + * Configures data transfer parameters. + */ +#define TRAN_CFG_1 0x0404 +/* Size of last data sector. */ +#define TRAN_CFG_1_LAST_SEC_SIZE GENMASK(31, 16) +/* Size of not-last data sector. */ +#define TRAN_CFG_1_SECTOR_SIZE GENMASK(15, 0) + +/* ECC engine configuration register 0. */ +#define ECC_CONFIG_0 0x0428 +/* Correction strength. */ +#define ECC_CONFIG_0_CORR_STR GENMASK(10, 8) +/* Enable erased pages detection mechanism. */ +#define ECC_CONFIG_0_ERASE_DET_EN BIT(1) +/* Enable controller ECC check bits generation and correction. */ +#define ECC_CONFIG_0_ECC_EN BIT(0) + +/* ECC engine configuration register 1. */ +#define ECC_CONFIG_1 0x042C + +/* Multiplane settings register. */ +#define MULTIPLANE_CFG 0x0434 +/* Cache operation settings. */ +#define CACHE_CFG 0x0438 + +/* DMA settings register. */ +#define DMA_SETINGS 0x043C +/* Enable SDMA error report on access unprepared slave DMA interface. */ +#define DMA_SETINGS_SDMA_ERR_RSP BIT(17) + +/* Transferred data block size for the slave DMA module. */ +#define SDMA_SIZE 0x0440 + +/* Thread number associated with transferred data block + * for the slave DMA module. + */ +#define SDMA_TRD_NUM 0x0444 +/* Thread number mask. */ +#define SDMA_TRD_NUM_SDMA_TRD GENMASK(2, 0) + +#define CONTROL_DATA_CTRL 0x0494 +/* Thread number mask. */ +#define CONTROL_DATA_CTRL_SIZE GENMASK(15, 0) + +#define CTRL_VERSION 0x800 +#define CTRL_VERSION_REV GENMASK(7, 0) + +/* Available hardware features of the controller. */ +#define CTRL_FEATURES 0x804 +/* Support for NV-DDR2/3 work mode. */ +#define CTRL_FEATURES_NVDDR_2_3 BIT(28) +/* Support for NV-DDR work mode. */ +#define CTRL_FEATURES_NVDDR BIT(27) +/* Support for asynchronous work mode. */ +#define CTRL_FEATURES_ASYNC BIT(26) +/* Support for asynchronous work mode. */ +#define CTRL_FEATURES_N_BANKS GENMASK(25, 24) +/* Slave and Master DMA data width. */ +#define CTRL_FEATURES_DMA_DWITH64 BIT(21) +/* Availability of Control Data feature.*/ +#define CTRL_FEATURES_CONTROL_DATA BIT(10) + +/* BCH Engine identification register 0 - correction strengths. */ +#define BCH_CFG_0 0x838 +#define BCH_CFG_0_CORR_CAP_0 GENMASK(7, 0) +#define BCH_CFG_0_CORR_CAP_1 GENMASK(15, 8) +#define BCH_CFG_0_CORR_CAP_2 GENMASK(23, 16) +#define BCH_CFG_0_CORR_CAP_3 GENMASK(31, 24) + +/* BCH Engine identification register 1 - correction strengths. */ +#define BCH_CFG_1 0x83C +#define BCH_CFG_1_CORR_CAP_4 GENMASK(7, 0) +#define BCH_CFG_1_CORR_CAP_5 GENMASK(15, 8) +#define BCH_CFG_1_CORR_CAP_6 GENMASK(23, 16) +#define BCH_CFG_1_CORR_CAP_7 GENMASK(31, 24) + +/* BCH Engine identification register 2 - sector sizes. */ +#define BCH_CFG_2 0x840 +#define BCH_CFG_2_SECT_0 GENMASK(15, 0) +#define BCH_CFG_2_SECT_1 GENMASK(31, 16) + +/* BCH Engine identification register 3. */ +#define BCH_CFG_3 0x844 +#define BCH_CFG_3_METADATA_SIZE GENMASK(23, 16) + +/* Ready/Busy# line status. */ +#define RBN_SETINGS 0x1004 + +/* 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) + +/* Skip_bytes registers. */ +#define SKIP_BYTES_CONF 0x100C +#define SKIP_BYTES_MARKER_VALUE GENMASK(31, 16) +#define SKIP_BYTES_NUM_OF_BYTES GENMASK(7, 0) + +#define SKIP_BYTES_OFFSET 0x1010 +#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) +#define TIMINGS0_TWHR GENMASK(15, 8) +#define TIMINGS0_TRHW GENMASK(7, 0) + +#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 +#define TIMINGS2_TFEAT GENMASK(25, 16) +#define TIMINGS2_CS_HOLD_TIME GENMASK(13, 8) +#define TIMINGS2_CS_SETUP_TIME GENMASK(5, 0) + +/* Configuration of the resynchronization of slave DLL of PHY. */ +#define DLL_PHY_CTRL 0x1034 +#define DLL_PHY_CTRL_DLL_RST_N BIT(24) +#define DLL_PHY_CTRL_EXTENDED_WR_MODE BIT(17) +#define DLL_PHY_CTRL_EXTENDED_RD_MODE BIT(16) +#define DLL_PHY_CTRL_RS_HIGH_WAIT_CNT GENMASK(11, 8) +#define DLL_PHY_CTRL_RS_IDLE_CNT GENMASK(7, 0) + +/* 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. */ +#define PHY_DLL_MASTER_CTRL 0x200C +#define PHY_DLL_MASTER_CTRL_BYPASS_MODE BIT(23) + +/* 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) +#define PHY_CTRL_PHONY_DQS GENMASK(9, 4) + +/* + * This register handles the global control settings + * for the termination selects for reads. + */ +#define PHY_TSEL 0x2084 + +/* Generic command layout. */ +#define GCMD_LAY_CS GENMASK_ULL(11, 8) +/* + * This bit informs the minicotroller if it has to wait for tWB + * after sending the last CMD/ADDR/DATA in the sequence. + */ +#define GCMD_LAY_TWB BIT_ULL(6) +/* Type of generic instruction. */ +#define GCMD_LAY_INSTR GENMASK_ULL(5, 0) + +/* Generic CMD sequence type. */ +#define GCMD_LAY_INSTR_CMD 0 +/* Generic ADDR sequence type. */ +#define GCMD_LAY_INSTR_ADDR 1 +/* Generic data transfer sequence type. */ +#define GCMD_LAY_INSTR_DATA 2 + +/* Input part of generic command type of input is command. */ +#define GCMD_LAY_INPUT_CMD GENMASK_ULL(23, 16) + +/* Generic command address sequence - address fields. */ +#define GCMD_LAY_INPUT_ADDR GENMASK_ULL(63, 16) +/* Generic command address sequence - address size. */ +#define GCMD_LAY_INPUT_ADDR_SIZE GENMASK_ULL(13, 11) + +/* Transfer direction field of generic command data sequence. */ +#define GCMD_DIR BIT_ULL(11) +/* Read transfer direction of generic command data sequence. */ +#define GCMD_DIR_READ 0 +/* Write transfer direction of generic command data sequence. */ +#define GCMD_DIR_WRITE 1 + +/* ECC enabled flag of generic command data sequence - ECC enabled. */ +#define GCMD_ECC_EN BIT_ULL(12) +/* Generic command data sequence - sector size. */ +#define GCMD_SECT_SIZE GENMASK_ULL(31, 16) +/* Generic command data sequence - sector count. */ +#define GCMD_SECT_CNT GENMASK_ULL(39, 32) +/* Generic command data sequence - last sector size. */ +#define GCMD_LAST_SIZE GENMASK_ULL(55, 40) + +/* CDMA descriptor fields. */ +/* Erase command type of CDMA descriptor. */ +#define CDMA_CT_ERASE 0x1000 +/* Program page command type of CDMA descriptor. */ +#define CDMA_CT_WR 0x2100 +/* Read page command type of CDMA descriptor. */ +#define CDMA_CT_RD 0x2200 + +/* Flash pointer memory shift. */ +#define CDMA_CFPTR_MEM_SHIFT 24 +/* Flash pointer memory mask. */ +#define CDMA_CFPTR_MEM GENMASK(26, 24) + +/* + * Command DMA descriptor flags. If set causes issue interrupt after + * the completion of descriptor processing. + */ +#define CDMA_CF_INT BIT(8) +/* + * Command DMA descriptor flags - the next descriptor + * address field is valid and descriptor processing should continue. + */ +#define CDMA_CF_CONT BIT(9) +/* DMA master flag of command DMA descriptor. */ +#define CDMA_CF_DMA_MASTER BIT(10) + +/* Operation complete status of command descriptor. */ +#define CDMA_CS_COMP BIT(15) +/* Operation complete status of command descriptor. */ +/* Command descriptor status - operation fail. */ +#define CDMA_CS_FAIL BIT(14) +/* Command descriptor status - page erased. */ +#define CDMA_CS_ERP BIT(11) +/* Command descriptor status - timeout occurred. */ +#define CDMA_CS_TOUT BIT(10) +/* + * Maximum amount of correction applied to one ECC sector. + * It is part of command descriptor status. + */ +#define CDMA_CS_MAXERR GENMASK(9, 2) +/* Command descriptor status - uncorrectable ECC error. */ +#define CDMA_CS_UNCE BIT(1) +/* Command descriptor status - descriptor error. */ +#define CDMA_CS_ERR BIT(0) + +/* Status of operation - OK. */ +#define STAT_OK 0 +/* Status of operation - FAIL. */ +#define STAT_FAIL 2 +/* Status of operation - uncorrectable ECC error. */ +#define STAT_ECC_UNCORR 3 +/* Status of operation - page erased. */ +#define STAT_ERASED 5 +/* Status of operation - correctable ECC error. */ +#define STAT_ECC_CORR 6 +/* Status of operation - unsuspected state. */ +#define STAT_UNKNOWN 7 +/* Status of operation - operation is not completed yet. */ +#define STAT_BUSY 0xFF + +#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. */ +struct cadence_nand_cdma_desc { + /* Next descriptor address. */ + u64 next_pointer; + + /* Flash address is a 32-bit address comprising of BANK and ROW ADDR. */ + u32 flash_pointer; + /*field appears in HPNFC version 13*/ + u16 bank; + u16 rsvd0; + + /* Operation the controller needs to perform. */ + u16 command_type; + u16 rsvd1; + /* Flags for operation of this command. */ + u16 command_flags; + u16 rsvd2; + + /* System/host memory address required for data DMA commands. */ + u64 memory_pointer; + + /* Status of operation. */ + u32 status; + u32 rsvd3; + + /* Address pointer to sync buffer location. */ + u64 sync_flag_pointer; + + /* Controls the buffer sync mechanism. */ + u32 sync_arguments; + u32 rsvd4; + + /* Control data pointer. */ + u64 ctrl_data_ptr; +}; + +/* Interrupt status. */ +struct cadence_nand_irq_status { + /* Thread operation complete status. */ + u32 trd_status; + /* Thread operation error. */ + u32 trd_error; + /* Controller status. */ + u32 status; +}; + +/* Cadence NAND flash controller capabilities get from driver data. */ +struct cadence_nand_dt_devdata { + /* Skew value of the output signals of the NAND Flash interface. */ + u32 if_skew; + /* It informs if slave DMA interface is connected to DMA engine. */ + unsigned int has_dma:1; +}; + +/* Cadence NAND flash controller capabilities read from registers. */ +struct cdns_nand_caps { + /* Maximum number of banks supported by hardware. */ + u8 max_banks; + /* Slave and Master DMA data width in bytes (4 or 8). */ + u8 data_dma_width; + /* Control Data feature supported. */ + bool data_control_supp; + /* Is PHY type DLL. */ + bool is_phy_type_dll; +}; + +struct cdns_nand_ctrl { + struct device *dev; + struct nand_controller controller; + struct cadence_nand_cdma_desc *cdma_desc; + /* IP capability. */ + const struct cadence_nand_dt_devdata *caps1; + struct cdns_nand_caps caps2; + u8 ctrl_rev; + dma_addr_t dma_cdma_desc; + u8 *buf; + u32 buf_size; + u8 curr_corr_str_idx; + + /* Register interface. */ + void __iomem *reg; + + struct { + void __iomem *virt; + dma_addr_t dma; + dma_addr_t iova_dma; + u32 size; + } io; + + int irq; + /* Interrupts that have happened. */ + struct cadence_nand_irq_status irq_status; + /* Interrupts we are waiting for. */ + struct cadence_nand_irq_status irq_mask; + struct completion complete; + /* Protect irq_mask and irq_status. */ + spinlock_t irq_lock; + + int ecc_strengths[BCH_MAX_NUM_CORR_CAPS]; + struct nand_ecc_step_info ecc_stepinfos[BCH_MAX_NUM_SECTOR_SIZES]; + struct nand_ecc_caps ecc_caps; + + int curr_trans_type; + + struct dma_chan *dmac; + + u32 nf_clk_rate; + /* + * Estimated Board delay. The value includes the total + * round trip delay for the signals and is used for deciding on values + * associated with data read capture. + */ + u32 board_delay; + + struct nand_chip *selected_chip; + + unsigned long assigned_cs; + struct list_head chips; + u8 bch_metadata_size; +}; + +struct cdns_nand_chip { + struct cadence_nand_timings timings; + struct nand_chip chip; + u8 nsels; + struct list_head node; + + /* + * part of oob area of NAND flash memory page. + * This part is available for user to read or write. + */ + u32 avail_oob_size; + + /* Sector size. There are few sectors per mtd->writesize */ + u32 sector_size; + u32 sector_count; + + /* Offset of BBM. */ + u8 bbm_offs; + /* Number of bytes reserved for BBM. */ + u8 bbm_len; + /* ECC strength index. */ + u8 corr_str_idx; + + u8 cs[] __counted_by(nsels); +}; + +static inline struct +cdns_nand_chip *to_cdns_nand_chip(struct nand_chip *chip) +{ + return container_of(chip, struct cdns_nand_chip, chip); +} + +static inline struct +cdns_nand_ctrl *to_cdns_nand_ctrl(struct nand_controller *controller) +{ + return container_of(controller, struct cdns_nand_ctrl, controller); +} + +static bool +cadence_nand_dma_buf_ok(struct cdns_nand_ctrl *cdns_ctrl, const void *buf, + u32 buf_len) +{ + u8 data_dma_width = cdns_ctrl->caps2.data_dma_width; + + return buf && virt_addr_valid(buf) && + likely(IS_ALIGNED((uintptr_t)buf, data_dma_width)) && + likely(IS_ALIGNED(buf_len, DMA_DATA_SIZE_ALIGN)); +} + +static int cadence_nand_wait_for_value(struct cdns_nand_ctrl *cdns_ctrl, + u32 reg_offset, u32 timeout_us, + u32 mask, bool is_clear) +{ + u32 val; + int ret; + + ret = readl_relaxed_poll_timeout(cdns_ctrl->reg + reg_offset, + val, !(val & mask) == is_clear, + 10, timeout_us); + + if (ret < 0) { + dev_err(cdns_ctrl->dev, + "Timeout while waiting for reg %x with mask %x is clear %d\n", + reg_offset, mask, is_clear); + } + + return ret; +} + +static int cadence_nand_set_ecc_enable(struct cdns_nand_ctrl *cdns_ctrl, + bool enable) +{ + u32 reg; + + if (cadence_nand_wait_for_value(cdns_ctrl, CTRL_STATUS, + 1000000, + CTRL_STATUS_CTRL_BUSY, true)) + return -ETIMEDOUT; + + reg = readl_relaxed(cdns_ctrl->reg + ECC_CONFIG_0); + + if (enable) + reg |= ECC_CONFIG_0_ECC_EN; + else + reg &= ~ECC_CONFIG_0_ECC_EN; + + writel_relaxed(reg, cdns_ctrl->reg + ECC_CONFIG_0); + + return 0; +} + +static void cadence_nand_set_ecc_strength(struct cdns_nand_ctrl *cdns_ctrl, + u8 corr_str_idx) +{ + u32 reg; + + if (cdns_ctrl->curr_corr_str_idx == corr_str_idx) + return; + + reg = readl_relaxed(cdns_ctrl->reg + ECC_CONFIG_0); + reg &= ~ECC_CONFIG_0_CORR_STR; + reg |= FIELD_PREP(ECC_CONFIG_0_CORR_STR, corr_str_idx); + writel_relaxed(reg, cdns_ctrl->reg + ECC_CONFIG_0); + + cdns_ctrl->curr_corr_str_idx = corr_str_idx; +} + +static int cadence_nand_get_ecc_strength_idx(struct cdns_nand_ctrl *cdns_ctrl, + u8 strength) +{ + int i, corr_str_idx = -1; + + for (i = 0; i < BCH_MAX_NUM_CORR_CAPS; i++) { + if (cdns_ctrl->ecc_strengths[i] == strength) { + corr_str_idx = i; + break; + } + } + + return corr_str_idx; +} + +static int cadence_nand_set_skip_marker_val(struct cdns_nand_ctrl *cdns_ctrl, + u16 marker_value) +{ + u32 reg; + + if (cadence_nand_wait_for_value(cdns_ctrl, CTRL_STATUS, + 1000000, + CTRL_STATUS_CTRL_BUSY, true)) + return -ETIMEDOUT; + + reg = readl_relaxed(cdns_ctrl->reg + SKIP_BYTES_CONF); + reg &= ~SKIP_BYTES_MARKER_VALUE; + reg |= FIELD_PREP(SKIP_BYTES_MARKER_VALUE, + marker_value); + + writel_relaxed(reg, cdns_ctrl->reg + SKIP_BYTES_CONF); + + return 0; +} + +static int cadence_nand_set_skip_bytes_conf(struct cdns_nand_ctrl *cdns_ctrl, + u8 num_of_bytes, + u32 offset_value, + int enable) +{ + u32 reg, skip_bytes_offset; + + if (cadence_nand_wait_for_value(cdns_ctrl, CTRL_STATUS, + 1000000, + CTRL_STATUS_CTRL_BUSY, true)) + return -ETIMEDOUT; + + if (!enable) { + num_of_bytes = 0; + offset_value = 0; + } + + reg = readl_relaxed(cdns_ctrl->reg + SKIP_BYTES_CONF); + reg &= ~SKIP_BYTES_NUM_OF_BYTES; + reg |= FIELD_PREP(SKIP_BYTES_NUM_OF_BYTES, + num_of_bytes); + skip_bytes_offset = FIELD_PREP(SKIP_BYTES_OFFSET_VALUE, + offset_value); + + writel_relaxed(reg, cdns_ctrl->reg + SKIP_BYTES_CONF); + writel_relaxed(skip_bytes_offset, cdns_ctrl->reg + SKIP_BYTES_OFFSET); + + return 0; +} + +/* Functions enables/disables hardware detection of erased data */ +static void cadence_nand_set_erase_detection(struct cdns_nand_ctrl *cdns_ctrl, + bool enable, + u8 bitflips_threshold) +{ + u32 reg; + + reg = readl_relaxed(cdns_ctrl->reg + ECC_CONFIG_0); + + if (enable) + reg |= ECC_CONFIG_0_ERASE_DET_EN; + else + reg &= ~ECC_CONFIG_0_ERASE_DET_EN; + + writel_relaxed(reg, cdns_ctrl->reg + ECC_CONFIG_0); + writel_relaxed(bitflips_threshold, cdns_ctrl->reg + ECC_CONFIG_1); +} + +static int cadence_nand_set_access_width16(struct cdns_nand_ctrl *cdns_ctrl, + bool bit_bus16) +{ + u32 reg; + + if (cadence_nand_wait_for_value(cdns_ctrl, CTRL_STATUS, + 1000000, + CTRL_STATUS_CTRL_BUSY, true)) + return -ETIMEDOUT; + + reg = readl_relaxed(cdns_ctrl->reg + COMMON_SET); + + if (!bit_bus16) + reg &= ~COMMON_SET_DEVICE_16BIT; + else + reg |= COMMON_SET_DEVICE_16BIT; + writel_relaxed(reg, cdns_ctrl->reg + COMMON_SET); + + return 0; +} + +static void +cadence_nand_clear_interrupt(struct cdns_nand_ctrl *cdns_ctrl, + struct cadence_nand_irq_status *irq_status) +{ + writel_relaxed(irq_status->status, cdns_ctrl->reg + INTR_STATUS); + writel_relaxed(irq_status->trd_status, + cdns_ctrl->reg + TRD_COMP_INT_STATUS); + writel_relaxed(irq_status->trd_error, + cdns_ctrl->reg + TRD_ERR_INT_STATUS); +} + +static void +cadence_nand_read_int_status(struct cdns_nand_ctrl *cdns_ctrl, + struct cadence_nand_irq_status *irq_status) +{ + irq_status->status = readl_relaxed(cdns_ctrl->reg + INTR_STATUS); + irq_status->trd_status = readl_relaxed(cdns_ctrl->reg + + TRD_COMP_INT_STATUS); + irq_status->trd_error = readl_relaxed(cdns_ctrl->reg + + TRD_ERR_INT_STATUS); +} + +static u32 irq_detected(struct cdns_nand_ctrl *cdns_ctrl, + struct cadence_nand_irq_status *irq_status) +{ + cadence_nand_read_int_status(cdns_ctrl, irq_status); + + return irq_status->status || irq_status->trd_status || + irq_status->trd_error; +} + +static void cadence_nand_reset_irq(struct cdns_nand_ctrl *cdns_ctrl) +{ + unsigned long flags; + + spin_lock_irqsave(&cdns_ctrl->irq_lock, flags); + memset(&cdns_ctrl->irq_status, 0, sizeof(cdns_ctrl->irq_status)); + memset(&cdns_ctrl->irq_mask, 0, sizeof(cdns_ctrl->irq_mask)); + spin_unlock_irqrestore(&cdns_ctrl->irq_lock, flags); +} + +/* + * This is the interrupt service routine. It handles all interrupts + * sent to this device. + */ +static irqreturn_t cadence_nand_isr(int irq, void *dev_id) +{ + struct cdns_nand_ctrl *cdns_ctrl = dev_id; + struct cadence_nand_irq_status irq_status; + irqreturn_t result = IRQ_NONE; + + spin_lock(&cdns_ctrl->irq_lock); + + if (irq_detected(cdns_ctrl, &irq_status)) { + /* Handle interrupt. */ + /* First acknowledge it. */ + cadence_nand_clear_interrupt(cdns_ctrl, &irq_status); + /* Status in the device context for someone to read. */ + cdns_ctrl->irq_status.status |= irq_status.status; + cdns_ctrl->irq_status.trd_status |= irq_status.trd_status; + cdns_ctrl->irq_status.trd_error |= irq_status.trd_error; + /* Notify anyone who cares that it happened. */ + complete(&cdns_ctrl->complete); + /* Tell the OS that we've handled this. */ + result = IRQ_HANDLED; + } + spin_unlock(&cdns_ctrl->irq_lock); + + return result; +} + +static void cadence_nand_set_irq_mask(struct cdns_nand_ctrl *cdns_ctrl, + struct cadence_nand_irq_status *irq_mask) +{ + writel_relaxed(INTR_ENABLE_INTR_EN | irq_mask->status, + cdns_ctrl->reg + INTR_ENABLE); + + writel_relaxed(irq_mask->trd_error, + cdns_ctrl->reg + TRD_ERR_INT_STATUS_EN); +} + +static void +cadence_nand_wait_for_irq(struct cdns_nand_ctrl *cdns_ctrl, + struct cadence_nand_irq_status *irq_mask, + struct cadence_nand_irq_status *irq_status) +{ + unsigned long timeout = msecs_to_jiffies(10000); + unsigned long time_left; + + time_left = wait_for_completion_timeout(&cdns_ctrl->complete, + timeout); + + *irq_status = cdns_ctrl->irq_status; + if (time_left == 0) { + /* Timeout error. */ + dev_err(cdns_ctrl->dev, "timeout occurred:\n"); + dev_err(cdns_ctrl->dev, "\tstatus = 0x%x, mask = 0x%x\n", + irq_status->status, irq_mask->status); + dev_err(cdns_ctrl->dev, + "\ttrd_status = 0x%x, trd_status mask = 0x%x\n", + irq_status->trd_status, irq_mask->trd_status); + dev_err(cdns_ctrl->dev, + "\t trd_error = 0x%x, trd_error mask = 0x%x\n", + irq_status->trd_error, irq_mask->trd_error); + } +} + +/* Execute generic command on NAND controller. */ +static int cadence_nand_generic_cmd_send(struct cdns_nand_ctrl *cdns_ctrl, + u8 chip_nr, + u64 mini_ctrl_cmd) +{ + u32 mini_ctrl_cmd_l, mini_ctrl_cmd_h, reg; + + mini_ctrl_cmd |= FIELD_PREP(GCMD_LAY_CS, chip_nr); + mini_ctrl_cmd_l = mini_ctrl_cmd & 0xFFFFFFFF; + mini_ctrl_cmd_h = mini_ctrl_cmd >> 32; + + if (cadence_nand_wait_for_value(cdns_ctrl, CTRL_STATUS, + 1000000, + CTRL_STATUS_CTRL_BUSY, true)) + return -ETIMEDOUT; + + cadence_nand_reset_irq(cdns_ctrl); + + writel_relaxed(mini_ctrl_cmd_l, cdns_ctrl->reg + CMD_REG2); + writel_relaxed(mini_ctrl_cmd_h, cdns_ctrl->reg + CMD_REG3); + + /* Select generic command. */ + reg = FIELD_PREP(CMD_REG0_CT, CMD_REG0_CT_GEN); + /* Thread number. */ + reg |= FIELD_PREP(CMD_REG0_TN, 0); + + /* Issue command. */ + writel_relaxed(reg, cdns_ctrl->reg + CMD_REG0); + + return 0; +} + +/* Wait for data on slave DMA interface. */ +static int cadence_nand_wait_on_sdma(struct cdns_nand_ctrl *cdns_ctrl, + u8 *out_sdma_trd, + u32 *out_sdma_size) +{ + struct cadence_nand_irq_status irq_mask, irq_status; + + irq_mask.trd_status = 0; + irq_mask.trd_error = 0; + irq_mask.status = INTR_STATUS_SDMA_TRIGG + | INTR_STATUS_SDMA_ERR + | INTR_STATUS_UNSUPP_CMD; + + cadence_nand_set_irq_mask(cdns_ctrl, &irq_mask); + cadence_nand_wait_for_irq(cdns_ctrl, &irq_mask, &irq_status); + if (irq_status.status == 0) { + dev_err(cdns_ctrl->dev, "Timeout while waiting for SDMA\n"); + return -ETIMEDOUT; + } + + if (irq_status.status & INTR_STATUS_SDMA_TRIGG) { + *out_sdma_size = readl_relaxed(cdns_ctrl->reg + SDMA_SIZE); + *out_sdma_trd = readl_relaxed(cdns_ctrl->reg + SDMA_TRD_NUM); + *out_sdma_trd = + FIELD_GET(SDMA_TRD_NUM_SDMA_TRD, *out_sdma_trd); + } else { + dev_err(cdns_ctrl->dev, "SDMA error - irq_status %x\n", + irq_status.status); + return -EIO; + } + + return 0; +} + +static void cadence_nand_get_caps(struct cdns_nand_ctrl *cdns_ctrl) +{ + u32 reg; + + reg = readl_relaxed(cdns_ctrl->reg + CTRL_FEATURES); + + cdns_ctrl->caps2.max_banks = 1 << FIELD_GET(CTRL_FEATURES_N_BANKS, reg); + + if (FIELD_GET(CTRL_FEATURES_DMA_DWITH64, reg)) + cdns_ctrl->caps2.data_dma_width = 8; + else + cdns_ctrl->caps2.data_dma_width = 4; + + if (reg & CTRL_FEATURES_CONTROL_DATA) + cdns_ctrl->caps2.data_control_supp = true; + + if (reg & (CTRL_FEATURES_NVDDR_2_3 + | CTRL_FEATURES_NVDDR)) + cdns_ctrl->caps2.is_phy_type_dll = true; +} + +/* Prepare CDMA descriptor. */ +static void +cadence_nand_cdma_desc_prepare(struct cdns_nand_ctrl *cdns_ctrl, + char nf_mem, u32 flash_ptr, dma_addr_t mem_ptr, + dma_addr_t ctrl_data_ptr, u16 ctype) +{ + struct cadence_nand_cdma_desc *cdma_desc = cdns_ctrl->cdma_desc; + + memset(cdma_desc, 0, sizeof(struct cadence_nand_cdma_desc)); + + /* Set fields for one descriptor. */ + cdma_desc->flash_pointer = flash_ptr; + if (cdns_ctrl->ctrl_rev >= 13) + cdma_desc->bank = nf_mem; + else + cdma_desc->flash_pointer |= (nf_mem << CDMA_CFPTR_MEM_SHIFT); + + cdma_desc->command_flags |= CDMA_CF_DMA_MASTER; + cdma_desc->command_flags |= CDMA_CF_INT; + + cdma_desc->memory_pointer = mem_ptr; + cdma_desc->status = 0; + cdma_desc->sync_flag_pointer = 0; + cdma_desc->sync_arguments = 0; + + cdma_desc->command_type = ctype; + cdma_desc->ctrl_data_ptr = ctrl_data_ptr; +} + +static u8 cadence_nand_check_desc_error(struct cdns_nand_ctrl *cdns_ctrl, + u32 desc_status) +{ + if (desc_status & CDMA_CS_ERP) + return STAT_ERASED; + + if (desc_status & CDMA_CS_UNCE) + return STAT_ECC_UNCORR; + + if (desc_status & CDMA_CS_ERR) { + dev_err(cdns_ctrl->dev, ":CDMA desc error flag detected.\n"); + return STAT_FAIL; + } + + if (FIELD_GET(CDMA_CS_MAXERR, desc_status)) + return STAT_ECC_CORR; + + return STAT_FAIL; +} + +static int cadence_nand_cdma_finish(struct cdns_nand_ctrl *cdns_ctrl) +{ + struct cadence_nand_cdma_desc *desc_ptr = cdns_ctrl->cdma_desc; + u8 status = STAT_BUSY; + + if (desc_ptr->status & CDMA_CS_FAIL) { + status = cadence_nand_check_desc_error(cdns_ctrl, + desc_ptr->status); + dev_err(cdns_ctrl->dev, ":CDMA error %x\n", desc_ptr->status); + } else if (desc_ptr->status & CDMA_CS_COMP) { + /* Descriptor finished with no errors. */ + if (desc_ptr->command_flags & CDMA_CF_CONT) { + dev_info(cdns_ctrl->dev, "DMA unsupported flag is set"); + status = STAT_UNKNOWN; + } else { + /* Last descriptor. */ + status = STAT_OK; + } + } + + return status; +} + +static int cadence_nand_cdma_send(struct cdns_nand_ctrl *cdns_ctrl, + u8 thread) +{ + u32 reg; + int status; + + /* Wait for thread ready. */ + status = cadence_nand_wait_for_value(cdns_ctrl, TRD_STATUS, + 1000000, + BIT(thread), true); + if (status) + return status; + + cadence_nand_reset_irq(cdns_ctrl); + reinit_completion(&cdns_ctrl->complete); + + writel_relaxed((u32)cdns_ctrl->dma_cdma_desc, + cdns_ctrl->reg + CMD_REG2); + writel_relaxed(0, cdns_ctrl->reg + CMD_REG3); + + /* Select CDMA mode. */ + reg = FIELD_PREP(CMD_REG0_CT, CMD_REG0_CT_CDMA); + /* Thread number. */ + reg |= FIELD_PREP(CMD_REG0_TN, thread); + /* Issue command. */ + writel_relaxed(reg, cdns_ctrl->reg + CMD_REG0); + + return 0; +} + +/* Send SDMA command and wait for finish. */ +static int +cadence_nand_cdma_send_and_wait(struct cdns_nand_ctrl *cdns_ctrl, + u8 thread) +{ + struct cadence_nand_irq_status irq_mask, irq_status = {0}; + int status; + + irq_mask.trd_status = BIT(thread); + irq_mask.trd_error = BIT(thread); + irq_mask.status = INTR_STATUS_CDMA_TERR; + + cadence_nand_set_irq_mask(cdns_ctrl, &irq_mask); + + status = cadence_nand_cdma_send(cdns_ctrl, thread); + if (status) + return status; + + cadence_nand_wait_for_irq(cdns_ctrl, &irq_mask, &irq_status); + + if (irq_status.status == 0 && irq_status.trd_status == 0 && + irq_status.trd_error == 0) { + dev_err(cdns_ctrl->dev, "CDMA command timeout\n"); + return -ETIMEDOUT; + } + if (irq_status.status & irq_mask.status) { + dev_err(cdns_ctrl->dev, "CDMA command failed\n"); + return -EIO; + } + + return 0; +} + +/* + * ECC size depends on configured ECC strength and on maximum supported + * ECC step size. + */ +static int cadence_nand_calc_ecc_bytes(int max_step_size, int strength) +{ + int nbytes = DIV_ROUND_UP(fls(8 * max_step_size) * strength, 8); + + return ALIGN(nbytes, 2); +} + +#define CADENCE_NAND_CALC_ECC_BYTES(max_step_size) \ + static int \ + cadence_nand_calc_ecc_bytes_##max_step_size(int step_size, \ + int strength)\ + {\ + return cadence_nand_calc_ecc_bytes(max_step_size, strength);\ + } + +CADENCE_NAND_CALC_ECC_BYTES(256) +CADENCE_NAND_CALC_ECC_BYTES(512) +CADENCE_NAND_CALC_ECC_BYTES(1024) +CADENCE_NAND_CALC_ECC_BYTES(2048) +CADENCE_NAND_CALC_ECC_BYTES(4096) + +/* Function reads BCH capabilities. */ +static int cadence_nand_read_bch_caps(struct cdns_nand_ctrl *cdns_ctrl) +{ + struct nand_ecc_caps *ecc_caps = &cdns_ctrl->ecc_caps; + int max_step_size = 0, nstrengths, i; + u32 reg; + + reg = readl_relaxed(cdns_ctrl->reg + BCH_CFG_3); + cdns_ctrl->bch_metadata_size = FIELD_GET(BCH_CFG_3_METADATA_SIZE, reg); + if (cdns_ctrl->bch_metadata_size < 4) { + dev_err(cdns_ctrl->dev, + "Driver needs at least 4 bytes of BCH meta data\n"); + return -EIO; + } + + reg = readl_relaxed(cdns_ctrl->reg + BCH_CFG_0); + cdns_ctrl->ecc_strengths[0] = FIELD_GET(BCH_CFG_0_CORR_CAP_0, reg); + cdns_ctrl->ecc_strengths[1] = FIELD_GET(BCH_CFG_0_CORR_CAP_1, reg); + cdns_ctrl->ecc_strengths[2] = FIELD_GET(BCH_CFG_0_CORR_CAP_2, reg); + cdns_ctrl->ecc_strengths[3] = FIELD_GET(BCH_CFG_0_CORR_CAP_3, reg); + + reg = readl_relaxed(cdns_ctrl->reg + BCH_CFG_1); + cdns_ctrl->ecc_strengths[4] = FIELD_GET(BCH_CFG_1_CORR_CAP_4, reg); + cdns_ctrl->ecc_strengths[5] = FIELD_GET(BCH_CFG_1_CORR_CAP_5, reg); + cdns_ctrl->ecc_strengths[6] = FIELD_GET(BCH_CFG_1_CORR_CAP_6, reg); + cdns_ctrl->ecc_strengths[7] = FIELD_GET(BCH_CFG_1_CORR_CAP_7, reg); + + reg = readl_relaxed(cdns_ctrl->reg + BCH_CFG_2); + cdns_ctrl->ecc_stepinfos[0].stepsize = + FIELD_GET(BCH_CFG_2_SECT_0, reg); + + cdns_ctrl->ecc_stepinfos[1].stepsize = + FIELD_GET(BCH_CFG_2_SECT_1, reg); + + nstrengths = 0; + for (i = 0; i < BCH_MAX_NUM_CORR_CAPS; i++) { + if (cdns_ctrl->ecc_strengths[i] != 0) + nstrengths++; + } + + ecc_caps->nstepinfos = 0; + for (i = 0; i < BCH_MAX_NUM_SECTOR_SIZES; i++) { + /* ECC strengths are common for all step infos. */ + cdns_ctrl->ecc_stepinfos[i].nstrengths = nstrengths; + cdns_ctrl->ecc_stepinfos[i].strengths = + cdns_ctrl->ecc_strengths; + + if (cdns_ctrl->ecc_stepinfos[i].stepsize != 0) + ecc_caps->nstepinfos++; + + if (cdns_ctrl->ecc_stepinfos[i].stepsize > max_step_size) + max_step_size = cdns_ctrl->ecc_stepinfos[i].stepsize; + } + ecc_caps->stepinfos = &cdns_ctrl->ecc_stepinfos[0]; + + switch (max_step_size) { + case 256: + ecc_caps->calc_ecc_bytes = &cadence_nand_calc_ecc_bytes_256; + break; + case 512: + ecc_caps->calc_ecc_bytes = &cadence_nand_calc_ecc_bytes_512; + break; + case 1024: + ecc_caps->calc_ecc_bytes = &cadence_nand_calc_ecc_bytes_1024; + break; + case 2048: + ecc_caps->calc_ecc_bytes = &cadence_nand_calc_ecc_bytes_2048; + break; + case 4096: + ecc_caps->calc_ecc_bytes = &cadence_nand_calc_ecc_bytes_4096; + break; + default: + dev_err(cdns_ctrl->dev, + "Unsupported sector size(ecc step size) %d\n", + max_step_size); + return -EIO; + } + + return 0; +} + +/* Hardware initialization. */ +static int cadence_nand_hw_init(struct cdns_nand_ctrl *cdns_ctrl) +{ + int status; + u32 reg; + + status = cadence_nand_wait_for_value(cdns_ctrl, CTRL_STATUS, + 1000000, + CTRL_STATUS_INIT_COMP, false); + if (status) + return status; + + reg = readl_relaxed(cdns_ctrl->reg + CTRL_VERSION); + cdns_ctrl->ctrl_rev = FIELD_GET(CTRL_VERSION_REV, reg); + + dev_info(cdns_ctrl->dev, + "%s: cadence nand controller version reg %x\n", + __func__, reg); + + /* Disable cache and multiplane. */ + writel_relaxed(0, cdns_ctrl->reg + MULTIPLANE_CFG); + writel_relaxed(0, cdns_ctrl->reg + CACHE_CFG); + + /* Clear all interrupts. */ + writel_relaxed(0xFFFFFFFF, cdns_ctrl->reg + INTR_STATUS); + + cadence_nand_get_caps(cdns_ctrl); + if (cadence_nand_read_bch_caps(cdns_ctrl)) + return -EIO; + +#ifndef CONFIG_64BIT + if (cdns_ctrl->caps2.data_dma_width == 8) { + dev_err(cdns_ctrl->dev, + "cannot access 64-bit dma on !64-bit architectures"); + return -EIO; + } +#endif + + /* + * Set IO width access to 8. + * It is because during SW device discovering width access + * is expected to be 8. + */ + status = cadence_nand_set_access_width16(cdns_ctrl, false); + + return status; +} + +#define TT_MAIN_OOB_AREAS 2 +#define TT_RAW_PAGE 3 +#define TT_BBM 4 +#define TT_MAIN_OOB_AREA_EXT 5 + +/* Prepare size of data to transfer. */ +static void +cadence_nand_prepare_data_size(struct nand_chip *chip, + int transfer_type) +{ + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + u32 sec_size = 0, offset = 0, sec_cnt = 1; + u32 last_sec_size = cdns_chip->sector_size; + u32 data_ctrl_size = 0; + u32 reg = 0; + + if (cdns_ctrl->curr_trans_type == transfer_type) + return; + + switch (transfer_type) { + case TT_MAIN_OOB_AREA_EXT: + sec_cnt = cdns_chip->sector_count; + sec_size = cdns_chip->sector_size; + data_ctrl_size = cdns_chip->avail_oob_size; + break; + case TT_MAIN_OOB_AREAS: + sec_cnt = cdns_chip->sector_count; + last_sec_size = cdns_chip->sector_size + + cdns_chip->avail_oob_size; + sec_size = cdns_chip->sector_size; + break; + case TT_RAW_PAGE: + last_sec_size = mtd->writesize + mtd->oobsize; + break; + case TT_BBM: + offset = mtd->writesize + cdns_chip->bbm_offs; + last_sec_size = 8; + break; + } + + reg = 0; + reg |= FIELD_PREP(TRAN_CFG_0_OFFSET, offset); + reg |= FIELD_PREP(TRAN_CFG_0_SEC_CNT, sec_cnt); + writel_relaxed(reg, cdns_ctrl->reg + TRAN_CFG_0); + + reg = 0; + reg |= FIELD_PREP(TRAN_CFG_1_LAST_SEC_SIZE, last_sec_size); + reg |= FIELD_PREP(TRAN_CFG_1_SECTOR_SIZE, sec_size); + writel_relaxed(reg, cdns_ctrl->reg + TRAN_CFG_1); + + if (cdns_ctrl->caps2.data_control_supp) { + reg = readl_relaxed(cdns_ctrl->reg + CONTROL_DATA_CTRL); + reg &= ~CONTROL_DATA_CTRL_SIZE; + reg |= FIELD_PREP(CONTROL_DATA_CTRL_SIZE, data_ctrl_size); + writel_relaxed(reg, cdns_ctrl->reg + CONTROL_DATA_CTRL); + } + + cdns_ctrl->curr_trans_type = transfer_type; +} + +static int +cadence_nand_cdma_transfer(struct cdns_nand_ctrl *cdns_ctrl, u8 chip_nr, + int page, void *buf, void *ctrl_dat, u32 buf_size, + u32 ctrl_dat_size, enum dma_data_direction dir, + bool with_ecc) +{ + dma_addr_t dma_buf, dma_ctrl_dat = 0; + u8 thread_nr = chip_nr; + int status; + u16 ctype; + + if (dir == DMA_FROM_DEVICE) + ctype = CDMA_CT_RD; + else + ctype = CDMA_CT_WR; + + cadence_nand_set_ecc_enable(cdns_ctrl, with_ecc); + + dma_buf = dma_map_single(cdns_ctrl->dev, buf, buf_size, dir); + if (dma_mapping_error(cdns_ctrl->dev, dma_buf)) { + dev_err(cdns_ctrl->dev, "Failed to map DMA buffer\n"); + return -EIO; + } + + if (ctrl_dat && ctrl_dat_size) { + dma_ctrl_dat = dma_map_single(cdns_ctrl->dev, ctrl_dat, + ctrl_dat_size, dir); + if (dma_mapping_error(cdns_ctrl->dev, dma_ctrl_dat)) { + dma_unmap_single(cdns_ctrl->dev, dma_buf, + buf_size, dir); + dev_err(cdns_ctrl->dev, "Failed to map DMA buffer\n"); + return -EIO; + } + } + + cadence_nand_cdma_desc_prepare(cdns_ctrl, chip_nr, page, + dma_buf, dma_ctrl_dat, ctype); + + status = cadence_nand_cdma_send_and_wait(cdns_ctrl, thread_nr); + + dma_unmap_single(cdns_ctrl->dev, dma_buf, + buf_size, dir); + + if (ctrl_dat && ctrl_dat_size) + dma_unmap_single(cdns_ctrl->dev, dma_ctrl_dat, + ctrl_dat_size, dir); + if (status) + return status; + + return cadence_nand_cdma_finish(cdns_ctrl); +} + +static void cadence_nand_set_timings(struct cdns_nand_ctrl *cdns_ctrl, + struct cadence_nand_timings *t) +{ + writel_relaxed(t->async_toggle_timings, + cdns_ctrl->reg + ASYNC_TOGGLE_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); + + if (cdns_ctrl->caps2.is_phy_type_dll) + writel_relaxed(t->dll_phy_ctrl, cdns_ctrl->reg + DLL_PHY_CTRL); + + writel_relaxed(t->phy_ctrl, cdns_ctrl->reg + PHY_CTRL); + + if (cdns_ctrl->caps2.is_phy_type_dll) { + writel_relaxed(0, cdns_ctrl->reg + PHY_TSEL); + writel_relaxed(2, 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(PHY_DLL_MASTER_CTRL_BYPASS_MODE, + cdns_ctrl->reg + PHY_DLL_MASTER_CTRL); + writel_relaxed(0, cdns_ctrl->reg + PHY_DLL_SLAVE_CTRL); + } +} + +static int cadence_nand_select_target(struct nand_chip *chip) +{ + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + + if (chip == cdns_ctrl->selected_chip) + return 0; + + if (cadence_nand_wait_for_value(cdns_ctrl, CTRL_STATUS, + 1000000, + CTRL_STATUS_CTRL_BUSY, true)) + return -ETIMEDOUT; + + cadence_nand_set_timings(cdns_ctrl, &cdns_chip->timings); + + cadence_nand_set_ecc_strength(cdns_ctrl, + cdns_chip->corr_str_idx); + + cadence_nand_set_erase_detection(cdns_ctrl, true, + chip->ecc.strength); + + cdns_ctrl->curr_trans_type = -1; + cdns_ctrl->selected_chip = chip; + + return 0; +} + +static int cadence_nand_erase(struct nand_chip *chip, u32 page) +{ + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + int status; + u8 thread_nr = cdns_chip->cs[chip->cur_cs]; + + cadence_nand_cdma_desc_prepare(cdns_ctrl, + cdns_chip->cs[chip->cur_cs], + page, 0, 0, + CDMA_CT_ERASE); + status = cadence_nand_cdma_send_and_wait(cdns_ctrl, thread_nr); + if (status) { + dev_err(cdns_ctrl->dev, "erase operation failed\n"); + return -EIO; + } + + status = cadence_nand_cdma_finish(cdns_ctrl); + if (status) + return status; + + return 0; +} + +static int cadence_nand_read_bbm(struct nand_chip *chip, int page, u8 *buf) +{ + int status; + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + + cadence_nand_prepare_data_size(chip, TT_BBM); + + cadence_nand_set_skip_bytes_conf(cdns_ctrl, 0, 0, 0); + + /* + * Read only bad block marker from offset + * defined by a memory manufacturer. + */ + status = cadence_nand_cdma_transfer(cdns_ctrl, + cdns_chip->cs[chip->cur_cs], + page, cdns_ctrl->buf, NULL, + mtd->oobsize, + 0, DMA_FROM_DEVICE, false); + if (status) { + dev_err(cdns_ctrl->dev, "read BBM failed\n"); + return -EIO; + } + + memcpy(buf + cdns_chip->bbm_offs, cdns_ctrl->buf, cdns_chip->bbm_len); + + return 0; +} + +static int cadence_nand_write_page(struct nand_chip *chip, + const u8 *buf, int oob_required, + int page) +{ + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int status; + u16 marker_val = 0xFFFF; + + status = cadence_nand_select_target(chip); + if (status) + return status; + + cadence_nand_set_skip_bytes_conf(cdns_ctrl, cdns_chip->bbm_len, + mtd->writesize + + cdns_chip->bbm_offs, + 1); + + if (oob_required) { + marker_val = *(u16 *)(chip->oob_poi + + cdns_chip->bbm_offs); + } else { + /* Set oob data to 0xFF. */ + memset(cdns_ctrl->buf + mtd->writesize, 0xFF, + cdns_chip->avail_oob_size); + } + + cadence_nand_set_skip_marker_val(cdns_ctrl, marker_val); + + cadence_nand_prepare_data_size(chip, TT_MAIN_OOB_AREA_EXT); + + if (cadence_nand_dma_buf_ok(cdns_ctrl, buf, mtd->writesize) && + cdns_ctrl->caps2.data_control_supp) { + u8 *oob; + + if (oob_required) + oob = chip->oob_poi; + else + oob = cdns_ctrl->buf + mtd->writesize; + + status = cadence_nand_cdma_transfer(cdns_ctrl, + cdns_chip->cs[chip->cur_cs], + page, (void *)buf, oob, + mtd->writesize, + cdns_chip->avail_oob_size, + DMA_TO_DEVICE, true); + if (status) { + dev_err(cdns_ctrl->dev, "write page failed\n"); + return -EIO; + } + + return 0; + } + + if (oob_required) { + /* Transfer the data to the oob area. */ + memcpy(cdns_ctrl->buf + mtd->writesize, chip->oob_poi, + cdns_chip->avail_oob_size); + } + + memcpy(cdns_ctrl->buf, buf, mtd->writesize); + + cadence_nand_prepare_data_size(chip, TT_MAIN_OOB_AREAS); + + return cadence_nand_cdma_transfer(cdns_ctrl, + cdns_chip->cs[chip->cur_cs], + page, cdns_ctrl->buf, NULL, + mtd->writesize + + cdns_chip->avail_oob_size, + 0, DMA_TO_DEVICE, true); +} + +static int cadence_nand_write_oob(struct nand_chip *chip, int page) +{ + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + struct mtd_info *mtd = nand_to_mtd(chip); + + memset(cdns_ctrl->buf, 0xFF, mtd->writesize); + + return cadence_nand_write_page(chip, cdns_ctrl->buf, 1, page); +} + +static int cadence_nand_write_page_raw(struct nand_chip *chip, + const u8 *buf, int oob_required, + int page) +{ + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int writesize = mtd->writesize; + int oobsize = mtd->oobsize; + int ecc_steps = chip->ecc.steps; + int ecc_size = chip->ecc.size; + int ecc_bytes = chip->ecc.bytes; + void *tmp_buf = cdns_ctrl->buf; + int oob_skip = cdns_chip->bbm_len; + size_t size = writesize + oobsize; + int i, pos, len; + int status = 0; + + status = cadence_nand_select_target(chip); + if (status) + return status; + + /* + * Fill the buffer with 0xff first except the full page transfer. + * This simplifies the logic. + */ + if (!buf || !oob_required) + memset(tmp_buf, 0xff, size); + + cadence_nand_set_skip_bytes_conf(cdns_ctrl, 0, 0, 0); + + /* Arrange the buffer for syndrome payload/ecc layout. */ + if (buf) { + for (i = 0; i < ecc_steps; i++) { + pos = i * (ecc_size + ecc_bytes); + len = ecc_size; + + if (pos >= writesize) + pos += oob_skip; + else if (pos + len > writesize) + len = writesize - pos; + + memcpy(tmp_buf + pos, buf, len); + buf += len; + if (len < ecc_size) { + len = ecc_size - len; + memcpy(tmp_buf + writesize + oob_skip, buf, + len); + buf += len; + } + } + } + + if (oob_required) { + const u8 *oob = chip->oob_poi; + u32 oob_data_offset = (cdns_chip->sector_count - 1) * + (cdns_chip->sector_size + chip->ecc.bytes) + + cdns_chip->sector_size + oob_skip; + + /* BBM at the beginning of the OOB area. */ + memcpy(tmp_buf + writesize, oob, oob_skip); + + /* OOB free. */ + memcpy(tmp_buf + oob_data_offset, oob, + cdns_chip->avail_oob_size); + oob += cdns_chip->avail_oob_size; + + /* OOB ECC. */ + for (i = 0; i < ecc_steps; i++) { + pos = ecc_size + i * (ecc_size + ecc_bytes); + if (i == (ecc_steps - 1)) + pos += cdns_chip->avail_oob_size; + + len = ecc_bytes; + + if (pos >= writesize) + pos += oob_skip; + else if (pos + len > writesize) + len = writesize - pos; + + memcpy(tmp_buf + pos, oob, len); + oob += len; + if (len < ecc_bytes) { + len = ecc_bytes - len; + memcpy(tmp_buf + writesize + oob_skip, oob, + len); + oob += len; + } + } + } + + cadence_nand_prepare_data_size(chip, TT_RAW_PAGE); + + return cadence_nand_cdma_transfer(cdns_ctrl, + cdns_chip->cs[chip->cur_cs], + page, cdns_ctrl->buf, NULL, + mtd->writesize + + mtd->oobsize, + 0, DMA_TO_DEVICE, false); +} + +static int cadence_nand_write_oob_raw(struct nand_chip *chip, + int page) +{ + return cadence_nand_write_page_raw(chip, NULL, true, page); +} + +static int cadence_nand_read_page(struct nand_chip *chip, + u8 *buf, int oob_required, int page) +{ + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int status = 0; + int ecc_err_count = 0; + + status = cadence_nand_select_target(chip); + if (status) + return status; + + cadence_nand_set_skip_bytes_conf(cdns_ctrl, cdns_chip->bbm_len, + mtd->writesize + + cdns_chip->bbm_offs, 1); + + /* + * If data buffer can be accessed by DMA and data_control feature + * is supported then transfer data and oob directly. + */ + if (cadence_nand_dma_buf_ok(cdns_ctrl, buf, mtd->writesize) && + cdns_ctrl->caps2.data_control_supp) { + u8 *oob; + + if (oob_required) + oob = chip->oob_poi; + else + oob = cdns_ctrl->buf + mtd->writesize; + + cadence_nand_prepare_data_size(chip, TT_MAIN_OOB_AREA_EXT); + status = cadence_nand_cdma_transfer(cdns_ctrl, + cdns_chip->cs[chip->cur_cs], + page, buf, oob, + mtd->writesize, + cdns_chip->avail_oob_size, + DMA_FROM_DEVICE, true); + /* Otherwise use bounce buffer. */ + } else { + cadence_nand_prepare_data_size(chip, TT_MAIN_OOB_AREAS); + status = cadence_nand_cdma_transfer(cdns_ctrl, + cdns_chip->cs[chip->cur_cs], + page, cdns_ctrl->buf, + NULL, mtd->writesize + + cdns_chip->avail_oob_size, + 0, DMA_FROM_DEVICE, true); + + memcpy(buf, cdns_ctrl->buf, mtd->writesize); + if (oob_required) + memcpy(chip->oob_poi, + cdns_ctrl->buf + mtd->writesize, + mtd->oobsize); + } + + switch (status) { + case STAT_ECC_UNCORR: + mtd->ecc_stats.failed++; + ecc_err_count++; + break; + case STAT_ECC_CORR: + ecc_err_count = FIELD_GET(CDMA_CS_MAXERR, + cdns_ctrl->cdma_desc->status); + mtd->ecc_stats.corrected += ecc_err_count; + break; + case STAT_ERASED: + case STAT_OK: + break; + default: + dev_err(cdns_ctrl->dev, "read page failed\n"); + return -EIO; + } + + if (oob_required) + if (cadence_nand_read_bbm(chip, page, chip->oob_poi)) + return -EIO; + + return ecc_err_count; +} + +/* Reads OOB data from the device. */ +static int cadence_nand_read_oob(struct nand_chip *chip, int page) +{ + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + + return cadence_nand_read_page(chip, cdns_ctrl->buf, 1, page); +} + +static int cadence_nand_read_page_raw(struct nand_chip *chip, + u8 *buf, int oob_required, int page) +{ + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int oob_skip = cdns_chip->bbm_len; + int writesize = mtd->writesize; + int ecc_steps = chip->ecc.steps; + int ecc_size = chip->ecc.size; + int ecc_bytes = chip->ecc.bytes; + void *tmp_buf = cdns_ctrl->buf; + int i, pos, len; + int status = 0; + + status = cadence_nand_select_target(chip); + if (status) + return status; + + cadence_nand_set_skip_bytes_conf(cdns_ctrl, 0, 0, 0); + + cadence_nand_prepare_data_size(chip, TT_RAW_PAGE); + status = cadence_nand_cdma_transfer(cdns_ctrl, + cdns_chip->cs[chip->cur_cs], + page, cdns_ctrl->buf, NULL, + mtd->writesize + + mtd->oobsize, + 0, DMA_FROM_DEVICE, false); + + switch (status) { + case STAT_ERASED: + case STAT_OK: + break; + default: + dev_err(cdns_ctrl->dev, "read raw page failed\n"); + return -EIO; + } + + /* Arrange the buffer for syndrome payload/ecc layout. */ + if (buf) { + for (i = 0; i < ecc_steps; i++) { + pos = i * (ecc_size + ecc_bytes); + len = ecc_size; + + if (pos >= writesize) + pos += oob_skip; + else if (pos + len > writesize) + len = writesize - pos; + + memcpy(buf, tmp_buf + pos, len); + buf += len; + if (len < ecc_size) { + len = ecc_size - len; + memcpy(buf, tmp_buf + writesize + oob_skip, + len); + buf += len; + } + } + } + + if (oob_required) { + u8 *oob = chip->oob_poi; + u32 oob_data_offset = (cdns_chip->sector_count - 1) * + (cdns_chip->sector_size + chip->ecc.bytes) + + cdns_chip->sector_size + oob_skip; + + /* OOB free. */ + memcpy(oob, tmp_buf + oob_data_offset, + cdns_chip->avail_oob_size); + + /* BBM at the beginning of the OOB area. */ + memcpy(oob, tmp_buf + writesize, oob_skip); + + oob += cdns_chip->avail_oob_size; + + /* OOB ECC */ + for (i = 0; i < ecc_steps; i++) { + pos = ecc_size + i * (ecc_size + ecc_bytes); + len = ecc_bytes; + + if (i == (ecc_steps - 1)) + pos += cdns_chip->avail_oob_size; + + if (pos >= writesize) + pos += oob_skip; + else if (pos + len > writesize) + len = writesize - pos; + + memcpy(oob, tmp_buf + pos, len); + oob += len; + if (len < ecc_bytes) { + len = ecc_bytes - len; + memcpy(oob, tmp_buf + writesize + oob_skip, + len); + oob += len; + } + } + } + + return 0; +} + +static int cadence_nand_read_oob_raw(struct nand_chip *chip, + int page) +{ + return cadence_nand_read_page_raw(chip, NULL, true, page); +} + +static void cadence_nand_slave_dma_transfer_finished(void *data) +{ + struct completion *finished = data; + + complete(finished); +} + +static int cadence_nand_slave_dma_transfer(struct cdns_nand_ctrl *cdns_ctrl, + void *buf, + dma_addr_t dev_dma, size_t len, + enum dma_data_direction dir) +{ + DECLARE_COMPLETION_ONSTACK(finished); + struct dma_chan *chan; + struct dma_device *dma_dev; + dma_addr_t src_dma, dst_dma, buf_dma; + struct dma_async_tx_descriptor *tx; + dma_cookie_t cookie; + + chan = cdns_ctrl->dmac; + dma_dev = chan->device; + + buf_dma = dma_map_single(dma_dev->dev, buf, len, dir); + if (dma_mapping_error(dma_dev->dev, buf_dma)) { + dev_err(cdns_ctrl->dev, "Failed to map DMA buffer\n"); + goto err; + } + + if (dir == DMA_FROM_DEVICE) { + src_dma = cdns_ctrl->io.iova_dma; + dst_dma = buf_dma; + } else { + src_dma = buf_dma; + dst_dma = cdns_ctrl->io.iova_dma; + } + + tx = dmaengine_prep_dma_memcpy(cdns_ctrl->dmac, dst_dma, src_dma, len, + DMA_CTRL_ACK | DMA_PREP_INTERRUPT); + if (!tx) { + dev_err(cdns_ctrl->dev, "Failed to prepare DMA memcpy\n"); + goto err_unmap; + } + + tx->callback = cadence_nand_slave_dma_transfer_finished; + tx->callback_param = &finished; + + cookie = dmaengine_submit(tx); + if (dma_submit_error(cookie)) { + dev_err(cdns_ctrl->dev, "Failed to do DMA tx_submit\n"); + goto err_unmap; + } + + dma_async_issue_pending(cdns_ctrl->dmac); + wait_for_completion(&finished); + + dma_unmap_single(dma_dev->dev, buf_dma, len, dir); + + return 0; + +err_unmap: + dma_unmap_single(dma_dev->dev, buf_dma, len, dir); + +err: + dev_dbg(cdns_ctrl->dev, "Fall back to CPU I/O\n"); + + return -EIO; +} + +static int cadence_nand_read_buf(struct cdns_nand_ctrl *cdns_ctrl, + u8 *buf, int len) +{ + u8 thread_nr = 0; + u32 sdma_size; + int status; + + /* Wait until slave DMA interface is ready to data transfer. */ + status = cadence_nand_wait_on_sdma(cdns_ctrl, &thread_nr, &sdma_size); + if (status) + return status; + + if (!cdns_ctrl->caps1->has_dma) { + u8 data_dma_width = cdns_ctrl->caps2.data_dma_width; + + int len_in_words = (data_dma_width == 4) ? len >> 2 : len >> 3; + + /* read alignment data */ + if (data_dma_width == 4) + ioread32_rep(cdns_ctrl->io.virt, buf, len_in_words); +#ifdef CONFIG_64BIT + else + readsq(cdns_ctrl->io.virt, buf, len_in_words); +#endif + + if (sdma_size > len) { + int read_bytes = (data_dma_width == 4) ? + len_in_words << 2 : len_in_words << 3; + + /* read rest data from slave DMA interface if any */ + if (data_dma_width == 4) + ioread32_rep(cdns_ctrl->io.virt, + cdns_ctrl->buf, + sdma_size / 4 - len_in_words); +#ifdef CONFIG_64BIT + else + readsq(cdns_ctrl->io.virt, cdns_ctrl->buf, + sdma_size / 8 - len_in_words); +#endif + + /* copy rest of data */ + memcpy(buf + read_bytes, cdns_ctrl->buf, + len - read_bytes); + } + return 0; + } + + if (cadence_nand_dma_buf_ok(cdns_ctrl, buf, len)) { + status = cadence_nand_slave_dma_transfer(cdns_ctrl, buf, + cdns_ctrl->io.dma, + len, DMA_FROM_DEVICE); + if (status == 0) + return 0; + + dev_warn(cdns_ctrl->dev, + "Slave DMA transfer failed. Try again using bounce buffer."); + } + + /* If DMA transfer is not possible or failed then use bounce buffer. */ + status = cadence_nand_slave_dma_transfer(cdns_ctrl, cdns_ctrl->buf, + cdns_ctrl->io.dma, + sdma_size, DMA_FROM_DEVICE); + + if (status) { + dev_err(cdns_ctrl->dev, "Slave DMA transfer failed"); + return status; + } + + memcpy(buf, cdns_ctrl->buf, len); + + return 0; +} + +static int cadence_nand_write_buf(struct cdns_nand_ctrl *cdns_ctrl, + const u8 *buf, int len) +{ + u8 thread_nr = 0; + u32 sdma_size; + int status; + + /* Wait until slave DMA interface is ready to data transfer. */ + status = cadence_nand_wait_on_sdma(cdns_ctrl, &thread_nr, &sdma_size); + if (status) + return status; + + if (!cdns_ctrl->caps1->has_dma) { + u8 data_dma_width = cdns_ctrl->caps2.data_dma_width; + + int len_in_words = (data_dma_width == 4) ? len >> 2 : len >> 3; + + if (data_dma_width == 4) + iowrite32_rep(cdns_ctrl->io.virt, buf, len_in_words); +#ifdef CONFIG_64BIT + else + writesq(cdns_ctrl->io.virt, buf, len_in_words); +#endif + + if (sdma_size > len) { + int written_bytes = (data_dma_width == 4) ? + len_in_words << 2 : len_in_words << 3; + + /* copy rest of data */ + memcpy(cdns_ctrl->buf, buf + written_bytes, + len - written_bytes); + + /* write all expected by nand controller data */ + if (data_dma_width == 4) + iowrite32_rep(cdns_ctrl->io.virt, + cdns_ctrl->buf, + sdma_size / 4 - len_in_words); +#ifdef CONFIG_64BIT + else + writesq(cdns_ctrl->io.virt, cdns_ctrl->buf, + sdma_size / 8 - len_in_words); +#endif + } + + return 0; + } + + if (cadence_nand_dma_buf_ok(cdns_ctrl, buf, len)) { + status = cadence_nand_slave_dma_transfer(cdns_ctrl, (void *)buf, + cdns_ctrl->io.dma, + len, DMA_TO_DEVICE); + if (status == 0) + return 0; + + dev_warn(cdns_ctrl->dev, + "Slave DMA transfer failed. Try again using bounce buffer."); + } + + /* If DMA transfer is not possible or failed then use bounce buffer. */ + memcpy(cdns_ctrl->buf, buf, len); + + status = cadence_nand_slave_dma_transfer(cdns_ctrl, cdns_ctrl->buf, + cdns_ctrl->io.dma, + sdma_size, DMA_TO_DEVICE); + + if (status) + dev_err(cdns_ctrl->dev, "Slave DMA transfer failed"); + + return status; +} + +static int cadence_nand_force_byte_access(struct nand_chip *chip, + bool force_8bit) +{ + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + + /* + * Callers of this function do not verify if the NAND is using a 16-bit + * an 8-bit bus for normal operations, so we need to take care of that + * here by leaving the configuration unchanged if the NAND does not have + * the NAND_BUSWIDTH_16 flag set. + */ + if (!(chip->options & NAND_BUSWIDTH_16)) + return 0; + + return cadence_nand_set_access_width16(cdns_ctrl, !force_8bit); +} + +static int cadence_nand_cmd_opcode(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + const struct nand_op_instr *instr; + unsigned int op_id = 0; + u64 mini_ctrl_cmd = 0; + int ret; + + instr = &subop->instrs[op_id]; + + if (instr->delay_ns > 0) + mini_ctrl_cmd |= GCMD_LAY_TWB; + + mini_ctrl_cmd |= FIELD_PREP(GCMD_LAY_INSTR, + GCMD_LAY_INSTR_CMD); + mini_ctrl_cmd |= FIELD_PREP(GCMD_LAY_INPUT_CMD, + instr->ctx.cmd.opcode); + + ret = cadence_nand_generic_cmd_send(cdns_ctrl, + cdns_chip->cs[chip->cur_cs], + mini_ctrl_cmd); + if (ret) + dev_err(cdns_ctrl->dev, "send cmd %x failed\n", + instr->ctx.cmd.opcode); + + return ret; +} + +static int cadence_nand_cmd_address(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + const struct nand_op_instr *instr; + unsigned int op_id = 0; + u64 mini_ctrl_cmd = 0; + unsigned int offset, naddrs; + u64 address = 0; + const u8 *addrs; + int ret; + int i; + + instr = &subop->instrs[op_id]; + + if (instr->delay_ns > 0) + mini_ctrl_cmd |= GCMD_LAY_TWB; + + mini_ctrl_cmd |= FIELD_PREP(GCMD_LAY_INSTR, + GCMD_LAY_INSTR_ADDR); + + offset = nand_subop_get_addr_start_off(subop, op_id); + naddrs = nand_subop_get_num_addr_cyc(subop, op_id); + addrs = &instr->ctx.addr.addrs[offset]; + + for (i = 0; i < naddrs; i++) + address |= (u64)addrs[i] << (8 * i); + + mini_ctrl_cmd |= FIELD_PREP(GCMD_LAY_INPUT_ADDR, + address); + mini_ctrl_cmd |= FIELD_PREP(GCMD_LAY_INPUT_ADDR_SIZE, + naddrs - 1); + + ret = cadence_nand_generic_cmd_send(cdns_ctrl, + cdns_chip->cs[chip->cur_cs], + mini_ctrl_cmd); + if (ret) + dev_err(cdns_ctrl->dev, "send address %llx failed\n", address); + + return ret; +} + +static int cadence_nand_cmd_erase(struct nand_chip *chip, + const struct nand_subop *subop) +{ + unsigned int op_id; + + if (subop->instrs[0].ctx.cmd.opcode == NAND_CMD_ERASE1) { + int i; + const struct nand_op_instr *instr = NULL; + unsigned int offset, naddrs; + const u8 *addrs; + u32 page = 0; + + instr = &subop->instrs[1]; + offset = nand_subop_get_addr_start_off(subop, 1); + naddrs = nand_subop_get_num_addr_cyc(subop, 1); + addrs = &instr->ctx.addr.addrs[offset]; + + for (i = 0; i < naddrs; i++) + page |= (u32)addrs[i] << (8 * i); + + return cadence_nand_erase(chip, page); + } + + /* + * If it is not an erase operation then handle operation + * by calling exec_op function. + */ + for (op_id = 0; op_id < subop->ninstrs; op_id++) { + int ret; + const struct nand_operation nand_op = { + .cs = chip->cur_cs, + .instrs = &subop->instrs[op_id], + .ninstrs = 1}; + ret = chip->controller->ops->exec_op(chip, &nand_op, false); + if (ret) + return ret; + } + + return 0; +} + +static int cadence_nand_cmd_data(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + const struct nand_op_instr *instr; + unsigned int offset, op_id = 0; + u64 mini_ctrl_cmd = 0; + int len = 0; + int ret; + + instr = &subop->instrs[op_id]; + + if (instr->delay_ns > 0) + mini_ctrl_cmd |= GCMD_LAY_TWB; + + mini_ctrl_cmd |= FIELD_PREP(GCMD_LAY_INSTR, + GCMD_LAY_INSTR_DATA); + + if (instr->type == NAND_OP_DATA_OUT_INSTR) + mini_ctrl_cmd |= FIELD_PREP(GCMD_DIR, + GCMD_DIR_WRITE); + + len = nand_subop_get_data_len(subop, op_id); + offset = nand_subop_get_data_start_off(subop, op_id); + mini_ctrl_cmd |= FIELD_PREP(GCMD_SECT_CNT, 1); + mini_ctrl_cmd |= FIELD_PREP(GCMD_LAST_SIZE, len); + if (instr->ctx.data.force_8bit) { + ret = cadence_nand_force_byte_access(chip, true); + if (ret) { + dev_err(cdns_ctrl->dev, + "cannot change byte access generic data cmd failed\n"); + return ret; + } + } + + ret = cadence_nand_generic_cmd_send(cdns_ctrl, + cdns_chip->cs[chip->cur_cs], + mini_ctrl_cmd); + if (ret) { + dev_err(cdns_ctrl->dev, "send generic data cmd failed\n"); + return ret; + } + + if (instr->type == NAND_OP_DATA_IN_INSTR) { + void *buf = instr->ctx.data.buf.in + offset; + + ret = cadence_nand_read_buf(cdns_ctrl, buf, len); + } else { + const void *buf = instr->ctx.data.buf.out + offset; + + ret = cadence_nand_write_buf(cdns_ctrl, buf, len); + } + + if (ret) { + dev_err(cdns_ctrl->dev, "data transfer failed for generic command\n"); + return ret; + } + + if (instr->ctx.data.force_8bit) { + ret = cadence_nand_force_byte_access(chip, false); + if (ret) { + dev_err(cdns_ctrl->dev, + "cannot change byte access generic data cmd failed\n"); + } + } + + return ret; +} + +static int cadence_nand_cmd_waitrdy(struct nand_chip *chip, + const struct nand_subop *subop) +{ + int status; + unsigned int op_id = 0; + struct cdns_nand_ctrl *cdns_ctrl = to_cdns_nand_ctrl(chip->controller); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + const struct nand_op_instr *instr = &subop->instrs[op_id]; + u32 timeout_us = instr->ctx.waitrdy.timeout_ms * 1000; + + status = cadence_nand_wait_for_value(cdns_ctrl, RBN_SETINGS, + timeout_us, + BIT(cdns_chip->cs[chip->cur_cs]), + false); + return status; +} + +static const struct nand_op_parser cadence_nand_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN( + cadence_nand_cmd_erase, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ERASE_ADDRESS_CYC), + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + NAND_OP_PARSER_PATTERN( + cadence_nand_cmd_opcode, + NAND_OP_PARSER_PAT_CMD_ELEM(false)), + NAND_OP_PARSER_PATTERN( + cadence_nand_cmd_address, + NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYC)), + NAND_OP_PARSER_PATTERN( + cadence_nand_cmd_data, + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, MAX_DATA_SIZE)), + NAND_OP_PARSER_PATTERN( + cadence_nand_cmd_data, + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, MAX_DATA_SIZE)), + NAND_OP_PARSER_PATTERN( + cadence_nand_cmd_waitrdy, + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)) + ); + +static int cadence_nand_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + if (!check_only) { + int status = cadence_nand_select_target(chip); + + if (status) + return status; + } + + return nand_op_parser_exec_op(chip, &cadence_nand_op_parser, op, + check_only); +} + +static int cadence_nand_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + + if (section) + return -ERANGE; + + oobregion->offset = cdns_chip->bbm_len; + oobregion->length = cdns_chip->avail_oob_size + - cdns_chip->bbm_len; + + return 0; +} + +static int cadence_nand_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + + if (section) + return -ERANGE; + + oobregion->offset = cdns_chip->avail_oob_size; + oobregion->length = chip->ecc.total; + + return 0; +} + +static const struct mtd_ooblayout_ops cadence_nand_ooblayout_ops = { + .free = cadence_nand_ooblayout_free, + .ecc = cadence_nand_ooblayout_ecc, +}; + +static int calc_cycl(u32 timing, u32 clock) +{ + if (timing == 0 || clock == 0) + return 0; + + if ((timing % clock) > 0) + return timing / clock; + else + return timing / clock - 1; +} + +/* Calculate max data valid window. */ +static inline u32 calc_tdvw_max(u32 trp_cnt, u32 clk_period, u32 trhoh_min, + u32 board_delay_skew_min, u32 ext_mode) +{ + if (ext_mode == 0) + clk_period /= 2; + + return (trp_cnt + 1) * clk_period + trhoh_min + + board_delay_skew_min; +} + +/* Calculate data valid window. */ +static inline u32 calc_tdvw(u32 trp_cnt, u32 clk_period, u32 trhoh_min, + u32 trea_max, u32 ext_mode) +{ + if (ext_mode == 0) + clk_period /= 2; + + return (trp_cnt + 1) * clk_period + trhoh_min - trea_max; +} + +static int cadence_nand_setup_sdr_interface(struct nand_chip *chip, + 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; + u32 reg; + u32 board_delay = cdns_ctrl->board_delay; + u32 clk_period = DIV_ROUND_DOWN_ULL(1000000000000ULL, + cdns_ctrl->nf_clk_rate); + u32 tceh_cnt, tcs_cnt, tadl_cnt, tccs_cnt; + u32 tfeat_cnt, trhz_cnt, tvdly_cnt; + u32 trhw_cnt, twb_cnt, twh_cnt = 0, twhr_cnt; + u32 twp_cnt = 0, trp_cnt = 0, trh_cnt = 0; + u32 if_skew = cdns_ctrl->caps1->if_skew; + u32 board_delay_skew_min = board_delay - if_skew; + u32 board_delay_skew_max = board_delay + if_skew; + u32 dqs_sampl_res, phony_dqs_mod; + u32 tdvw, tdvw_min, tdvw_max; + u32 ext_rd_mode, ext_wr_mode; + u32 dll_phy_dqs_timing = 0, phony_dqs_timing = 0, rd_del_sel = 0; + u32 sampling_point; + + memset(t, 0, sizeof(*t)); + /* Sampling point calculation. */ + if (cdns_ctrl->caps2.is_phy_type_dll) + phony_dqs_mod = 2; + else + phony_dqs_mod = 1; + + dqs_sampl_res = clk_period / phony_dqs_mod; + + tdvw_min = sdr->tREA_max + board_delay_skew_max; + /* + * The idea of those calculation is to get the optimum value + * for tRP and tRH timings. If it is NOT possible to sample data + * with optimal tRP/tRH settings, the parameters will be extended. + * If clk_period is 50ns (the lowest value) this condition is met + * for SDR timing modes 1, 2, 3, 4 and 5. + * If clk_period is 20ns the condition is met only for SDR timing + * mode 5. + */ + if (sdr->tRC_min <= clk_period && + sdr->tRP_min <= (clk_period / 2) && + sdr->tREH_min <= (clk_period / 2)) { + /* Performance mode. */ + ext_rd_mode = 0; + tdvw = calc_tdvw(trp_cnt, clk_period, sdr->tRHOH_min, + sdr->tREA_max, ext_rd_mode); + tdvw_max = calc_tdvw_max(trp_cnt, clk_period, sdr->tRHOH_min, + board_delay_skew_min, + ext_rd_mode); + /* + * Check if data valid window and sampling point can be found + * and is not on the edge (ie. we have hold margin). + * If not extend the tRP timings. + */ + if (tdvw > 0) { + if (tdvw_max <= tdvw_min || + (tdvw_max % dqs_sampl_res) == 0) { + /* + * No valid sampling point so the RE pulse need + * to be widen widening by half clock cycle. + */ + ext_rd_mode = 1; + } + } else { + /* + * There is no valid window + * to be able to sample data the tRP need to be widen. + * Very safe calculations are performed here. + */ + trp_cnt = (sdr->tREA_max + board_delay_skew_max + + dqs_sampl_res) / clk_period; + ext_rd_mode = 1; + } + + } else { + /* Extended read mode. */ + u32 trh; + + ext_rd_mode = 1; + trp_cnt = calc_cycl(sdr->tRP_min, clk_period); + trh = sdr->tRC_min - ((trp_cnt + 1) * clk_period); + if (sdr->tREH_min >= trh) + trh_cnt = calc_cycl(sdr->tREH_min, clk_period); + else + trh_cnt = calc_cycl(trh, clk_period); + + tdvw = calc_tdvw(trp_cnt, clk_period, sdr->tRHOH_min, + sdr->tREA_max, ext_rd_mode); + /* + * Check if data valid window and sampling point can be found + * or if it is at the edge check if previous is valid + * - if not extend the tRP timings. + */ + if (tdvw > 0) { + tdvw_max = calc_tdvw_max(trp_cnt, clk_period, + sdr->tRHOH_min, + board_delay_skew_min, + ext_rd_mode); + + if ((((tdvw_max / dqs_sampl_res) + * dqs_sampl_res) <= tdvw_min) || + (((tdvw_max % dqs_sampl_res) == 0) && + (((tdvw_max / dqs_sampl_res - 1) + * dqs_sampl_res) <= tdvw_min))) { + /* + * Data valid window width is lower than + * sampling resolution and do not hit any + * sampling point to be sure the sampling point + * will be found the RE low pulse width will be + * extended by one clock cycle. + */ + trp_cnt = trp_cnt + 1; + } + } else { + /* + * There is no valid window to be able to sample data. + * The tRP need to be widen. + * Very safe calculations are performed here. + */ + trp_cnt = (sdr->tREA_max + board_delay_skew_max + + dqs_sampl_res) / clk_period; + } + } + + tdvw_max = calc_tdvw_max(trp_cnt, clk_period, + sdr->tRHOH_min, + board_delay_skew_min, ext_rd_mode); + + if (sdr->tWC_min <= clk_period && + (sdr->tWP_min + if_skew) <= (clk_period / 2) && + (sdr->tWH_min + if_skew) <= (clk_period / 2)) { + ext_wr_mode = 0; + } else { + u32 twh; + + ext_wr_mode = 1; + twp_cnt = calc_cycl(sdr->tWP_min + if_skew, clk_period); + if ((twp_cnt + 1) * clk_period < (sdr->tALS_min + if_skew)) + twp_cnt = calc_cycl(sdr->tALS_min + if_skew, + clk_period); + + twh = (sdr->tWC_min - (twp_cnt + 1) * clk_period); + if (sdr->tWH_min >= twh) + twh = sdr->tWH_min; + + twh_cnt = calc_cycl(twh + if_skew, clk_period); + } + + reg = FIELD_PREP(ASYNC_TOGGLE_TIMINGS_TRH, trh_cnt); + reg |= FIELD_PREP(ASYNC_TOGGLE_TIMINGS_TRP, trp_cnt); + reg |= FIELD_PREP(ASYNC_TOGGLE_TIMINGS_TWH, twh_cnt); + reg |= FIELD_PREP(ASYNC_TOGGLE_TIMINGS_TWP, twp_cnt); + t->async_toggle_timings = reg; + dev_dbg(cdns_ctrl->dev, "ASYNC_TOGGLE_TIMINGS_SDR\t%x\n", reg); + + tadl_cnt = calc_cycl((sdr->tADL_min + if_skew), clk_period); + tccs_cnt = calc_cycl((sdr->tCCS_min + if_skew), clk_period); + twhr_cnt = calc_cycl((sdr->tWHR_min + if_skew), clk_period); + trhw_cnt = calc_cycl((sdr->tRHW_min + if_skew), clk_period); + reg = FIELD_PREP(TIMINGS0_TADL, tadl_cnt); + + /* + * If timing exceeds delay field in timing register + * then use maximum value. + */ + if (FIELD_FIT(TIMINGS0_TCCS, tccs_cnt)) + reg |= FIELD_PREP(TIMINGS0_TCCS, tccs_cnt); + else + reg |= TIMINGS0_TCCS; + + reg |= FIELD_PREP(TIMINGS0_TWHR, twhr_cnt); + reg |= FIELD_PREP(TIMINGS0_TRHW, trhw_cnt); + t->timings0 = reg; + dev_dbg(cdns_ctrl->dev, "TIMINGS0_SDR\t%x\n", reg); + + /* The following is related to single signal so skew is not needed. */ + trhz_cnt = calc_cycl(sdr->tRHZ_max, clk_period); + trhz_cnt = trhz_cnt + 1; + twb_cnt = calc_cycl((sdr->tWB_max + board_delay), clk_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; + /* + * The following is related to the we edge of the random data input + * sequence so skew is not needed. + */ + tvdly_cnt = calc_cycl(500000 + if_skew, clk_period); + reg = FIELD_PREP(TIMINGS1_TRHZ, trhz_cnt); + reg |= FIELD_PREP(TIMINGS1_TWB, twb_cnt); + reg |= FIELD_PREP(TIMINGS1_TVDLY, tvdly_cnt); + t->timings1 = reg; + dev_dbg(cdns_ctrl->dev, "TIMINGS1_SDR\t%x\n", reg); + + tfeat_cnt = calc_cycl(sdr->tFEAT_max, clk_period); + if (tfeat_cnt < twb_cnt) + tfeat_cnt = twb_cnt; + + tceh_cnt = calc_cycl(sdr->tCEH_min, clk_period); + tcs_cnt = calc_cycl((sdr->tCS_min + if_skew), clk_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_SDR\t%x\n", reg); + + if (cdns_ctrl->caps2.is_phy_type_dll) { + reg = DLL_PHY_CTRL_DLL_RST_N; + if (ext_wr_mode) + reg |= DLL_PHY_CTRL_EXTENDED_WR_MODE; + if (ext_rd_mode) + reg |= DLL_PHY_CTRL_EXTENDED_RD_MODE; + + reg |= FIELD_PREP(DLL_PHY_CTRL_RS_HIGH_WAIT_CNT, 7); + reg |= FIELD_PREP(DLL_PHY_CTRL_RS_IDLE_CNT, 7); + t->dll_phy_ctrl = reg; + dev_dbg(cdns_ctrl->dev, "DLL_PHY_CTRL_SDR\t%x\n", reg); + } + + /* Sampling point calculation. */ + if ((tdvw_max % dqs_sampl_res) > 0) + sampling_point = tdvw_max / dqs_sampl_res; + else + sampling_point = (tdvw_max / dqs_sampl_res - 1); + + if (sampling_point * dqs_sampl_res > tdvw_min) { + dll_phy_dqs_timing = + FIELD_PREP(PHY_DQS_TIMING_DQS_SEL_OE_END, 4); + dll_phy_dqs_timing |= PHY_DQS_TIMING_USE_PHONY_DQS; + phony_dqs_timing = sampling_point / phony_dqs_mod; + + if ((sampling_point % 2) > 0) { + dll_phy_dqs_timing |= PHY_DQS_TIMING_PHONY_DQS_SEL; + if ((tdvw_max % dqs_sampl_res) == 0) + /* + * Calculation for sampling point at the edge + * of data and being odd number. + */ + phony_dqs_timing = (tdvw_max / dqs_sampl_res) + / phony_dqs_mod - 1; + + if (!cdns_ctrl->caps2.is_phy_type_dll) + phony_dqs_timing--; + + } else { + phony_dqs_timing--; + } + rd_del_sel = phony_dqs_timing + 3; + } else { + dev_warn(cdns_ctrl->dev, + "ERROR : cannot find valid sampling point\n"); + } + + reg = FIELD_PREP(PHY_CTRL_PHONY_DQS, phony_dqs_timing); + if (cdns_ctrl->caps2.is_phy_type_dll) + reg |= PHY_CTRL_SDR_DQS; + t->phy_ctrl = reg; + dev_dbg(cdns_ctrl->dev, "PHY_CTRL_REG_SDR\t%x\n", reg); + + if (cdns_ctrl->caps2.is_phy_type_dll) { + dev_dbg(cdns_ctrl->dev, "PHY_TSEL_REG_SDR\t%x\n", 0); + dev_dbg(cdns_ctrl->dev, "PHY_DQ_TIMING_REG_SDR\t%x\n", 2); + dev_dbg(cdns_ctrl->dev, "PHY_DQS_TIMING_REG_SDR\t%x\n", + dll_phy_dqs_timing); + t->phy_dqs_timing = dll_phy_dqs_timing; + + reg = FIELD_PREP(PHY_GATE_LPBK_CTRL_RDS, rd_del_sel); + dev_dbg(cdns_ctrl->dev, "PHY_GATE_LPBK_CTRL_REG_SDR\t%x\n", + reg); + t->phy_gate_lpbk_ctrl = reg; + + dev_dbg(cdns_ctrl->dev, "PHY_DLL_MASTER_CTRL_REG_SDR\t%lx\n", + 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); + struct cdns_nand_chip *cdns_chip = to_cdns_nand_chip(chip); + u32 ecc_size; + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + if (chip->options & NAND_BUSWIDTH_16) { + ret = cadence_nand_set_access_width16(cdns_ctrl, true); + if (ret) + return ret; + } + + chip->bbt_options |= NAND_BBT_USE_FLASH; + chip->bbt_options |= NAND_BBT_NO_OOB; + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + + chip->options |= NAND_NO_SUBPAGE_WRITE; + + cdns_chip->bbm_offs = chip->badblockpos; + cdns_chip->bbm_offs &= ~0x01; + /* this value should be even number */ + cdns_chip->bbm_len = 2; + + ret = nand_ecc_choose_conf(chip, + &cdns_ctrl->ecc_caps, + mtd->oobsize - cdns_chip->bbm_len); + if (ret) { + dev_err(cdns_ctrl->dev, "ECC configuration failed\n"); + return ret; + } + + dev_dbg(cdns_ctrl->dev, + "chosen ECC settings: step=%d, strength=%d, bytes=%d\n", + chip->ecc.size, chip->ecc.strength, chip->ecc.bytes); + + /* Error correction configuration. */ + cdns_chip->sector_size = chip->ecc.size; + cdns_chip->sector_count = mtd->writesize / cdns_chip->sector_size; + ecc_size = cdns_chip->sector_count * chip->ecc.bytes; + + cdns_chip->avail_oob_size = mtd->oobsize - ecc_size; + + if (cdns_chip->avail_oob_size > cdns_ctrl->bch_metadata_size) + cdns_chip->avail_oob_size = cdns_ctrl->bch_metadata_size; + + if ((cdns_chip->avail_oob_size + cdns_chip->bbm_len + ecc_size) + > mtd->oobsize) + cdns_chip->avail_oob_size -= 4; + + ret = cadence_nand_get_ecc_strength_idx(cdns_ctrl, chip->ecc.strength); + if (ret < 0) + return -EINVAL; + + cdns_chip->corr_str_idx = (u8)ret; + + if (cadence_nand_wait_for_value(cdns_ctrl, CTRL_STATUS, + 1000000, + CTRL_STATUS_CTRL_BUSY, true)) + return -ETIMEDOUT; + + cadence_nand_set_ecc_strength(cdns_ctrl, + cdns_chip->corr_str_idx); + + cadence_nand_set_erase_detection(cdns_ctrl, true, + chip->ecc.strength); + + /* Override the default read operations. */ + chip->ecc.read_page = cadence_nand_read_page; + chip->ecc.read_page_raw = cadence_nand_read_page_raw; + chip->ecc.write_page = cadence_nand_write_page; + chip->ecc.write_page_raw = cadence_nand_write_page_raw; + chip->ecc.read_oob = cadence_nand_read_oob; + chip->ecc.write_oob = cadence_nand_write_oob; + chip->ecc.read_oob_raw = cadence_nand_read_oob_raw; + chip->ecc.write_oob_raw = cadence_nand_write_oob_raw; + + if ((mtd->writesize + mtd->oobsize) > cdns_ctrl->buf_size) + cdns_ctrl->buf_size = mtd->writesize + mtd->oobsize; + + /* Is 32-bit DMA supported? */ + ret = dma_set_mask(cdns_ctrl->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(cdns_ctrl->dev, "no usable DMA configuration\n"); + return ret; + } + + mtd_set_ooblayout(mtd, &cadence_nand_ooblayout_ops); + + return 0; +} + +static const struct nand_controller_ops cadence_nand_controller_ops = { + .attach_chip = cadence_nand_attach_chip, + .exec_op = cadence_nand_exec_op, + .setup_interface = cadence_nand_setup_interface, +}; + +static int cadence_nand_chip_init(struct cdns_nand_ctrl *cdns_ctrl, + struct device_node *np) +{ + struct cdns_nand_chip *cdns_chip; + struct mtd_info *mtd; + struct nand_chip *chip; + int nsels, ret, i; + u32 cs; + + nsels = of_property_count_elems_of_size(np, "reg", sizeof(u32)); + if (nsels <= 0) { + dev_err(cdns_ctrl->dev, "missing/invalid reg property\n"); + return -EINVAL; + } + + /* Allocate the nand chip structure. */ + cdns_chip = devm_kzalloc(cdns_ctrl->dev, sizeof(*cdns_chip) + + (nsels * sizeof(u8)), + GFP_KERNEL); + if (!cdns_chip) { + dev_err(cdns_ctrl->dev, "could not allocate chip structure\n"); + return -ENOMEM; + } + + cdns_chip->nsels = nsels; + + for (i = 0; i < nsels; i++) { + /* Retrieve CS id. */ + ret = of_property_read_u32_index(np, "reg", i, &cs); + if (ret) { + dev_err(cdns_ctrl->dev, + "could not retrieve reg property: %d\n", + ret); + return ret; + } + + if (cs >= cdns_ctrl->caps2.max_banks) { + dev_err(cdns_ctrl->dev, + "invalid reg value: %u (max CS = %d)\n", + cs, cdns_ctrl->caps2.max_banks); + return -EINVAL; + } + + if (test_and_set_bit(cs, &cdns_ctrl->assigned_cs)) { + dev_err(cdns_ctrl->dev, + "CS %d already assigned\n", cs); + return -EINVAL; + } + + cdns_chip->cs[i] = cs; + } + + chip = &cdns_chip->chip; + chip->controller = &cdns_ctrl->controller; + nand_set_flash_node(chip, np); + + mtd = nand_to_mtd(chip); + mtd->dev.parent = cdns_ctrl->dev; + + /* + * Default to HW ECC engine mode. If the nand-ecc-mode property is given + * in the DT node, this entry will be overwritten in nand_scan_ident(). + */ + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + + ret = nand_scan(chip, cdns_chip->nsels); + if (ret) { + dev_err(cdns_ctrl->dev, "could not scan the nand chip\n"); + return ret; + } + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(cdns_ctrl->dev, + "failed to register mtd device: %d\n", ret); + nand_cleanup(chip); + return ret; + } + + list_add_tail(&cdns_chip->node, &cdns_ctrl->chips); + + return 0; +} + +static void cadence_nand_chips_cleanup(struct cdns_nand_ctrl *cdns_ctrl) +{ + struct cdns_nand_chip *entry, *temp; + struct nand_chip *chip; + int ret; + + list_for_each_entry_safe(entry, temp, &cdns_ctrl->chips, node) { + chip = &entry->chip; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + list_del(&entry->node); + } +} + +static int cadence_nand_chips_init(struct cdns_nand_ctrl *cdns_ctrl) +{ + struct device_node *np = cdns_ctrl->dev->of_node; + int max_cs = cdns_ctrl->caps2.max_banks; + int nchips, ret; + + nchips = of_get_child_count(np); + + if (nchips > max_cs) { + dev_err(cdns_ctrl->dev, + "too many NAND chips: %d (max = %d CS)\n", + nchips, max_cs); + return -EINVAL; + } + + for_each_child_of_node_scoped(np, nand_np) { + ret = cadence_nand_chip_init(cdns_ctrl, nand_np); + if (ret) { + cadence_nand_chips_cleanup(cdns_ctrl); + return ret; + } + } + + return 0; +} + +static void +cadence_nand_irq_cleanup(int irqnum, struct cdns_nand_ctrl *cdns_ctrl) +{ + /* Disable interrupts. */ + writel_relaxed(INTR_ENABLE_INTR_EN, cdns_ctrl->reg + INTR_ENABLE); +} + +static int cadence_nand_init(struct cdns_nand_ctrl *cdns_ctrl) +{ + dma_cap_mask_t mask; + 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->cdma_desc) + return -ENOMEM; + + cdns_ctrl->buf_size = SZ_16K; + cdns_ctrl->buf = kmalloc(cdns_ctrl->buf_size, GFP_KERNEL); + if (!cdns_ctrl->buf) { + ret = -ENOMEM; + goto free_buf_desc; + } + + if (devm_request_irq(cdns_ctrl->dev, cdns_ctrl->irq, cadence_nand_isr, + IRQF_SHARED, "cadence-nand-controller", + cdns_ctrl)) { + dev_err(cdns_ctrl->dev, "Unable to allocate IRQ\n"); + ret = -ENODEV; + goto free_buf; + } + + spin_lock_init(&cdns_ctrl->irq_lock); + init_completion(&cdns_ctrl->complete); + + ret = cadence_nand_hw_init(cdns_ctrl); + if (ret) + goto disable_irq; + + dma_cap_zero(mask); + dma_cap_set(DMA_MEMCPY, mask); + + if (cdns_ctrl->caps1->has_dma) { + cdns_ctrl->dmac = dma_request_chan_by_mask(&mask); + if (IS_ERR(cdns_ctrl->dmac)) { + ret = dev_err_probe(cdns_ctrl->dev, PTR_ERR(cdns_ctrl->dmac), + "%d: Failed to get a DMA channel\n", ret); + goto disable_irq; + } + } + + 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); + + ret = dma_mapping_error(dma_dev->dev, cdns_ctrl->io.iova_dma); + if (ret) { + dev_err(cdns_ctrl->dev, "Failed to map I/O resource to DMA\n"); + goto dma_release_chnl; + } + + nand_controller_init(&cdns_ctrl->controller); + INIT_LIST_HEAD(&cdns_ctrl->chips); + + cdns_ctrl->controller.ops = &cadence_nand_controller_ops; + cdns_ctrl->curr_corr_str_idx = 0xFF; + + ret = cadence_nand_chips_init(cdns_ctrl); + if (ret) { + dev_err(cdns_ctrl->dev, "Failed to register MTD: %d\n", + ret); + goto unmap_dma_resource; + } + + kfree(cdns_ctrl->buf); + cdns_ctrl->buf = kzalloc(cdns_ctrl->buf_size, GFP_KERNEL); + if (!cdns_ctrl->buf) { + ret = -ENOMEM; + goto unmap_dma_resource; + } + + return 0; + +unmap_dma_resource: + dma_unmap_resource(dma_dev->dev, cdns_ctrl->io.iova_dma, + cdns_ctrl->io.size, DMA_BIDIRECTIONAL, 0); + +dma_release_chnl: + if (cdns_ctrl->dmac) + dma_release_channel(cdns_ctrl->dmac); + +disable_irq: + cadence_nand_irq_cleanup(cdns_ctrl->irq, cdns_ctrl); + +free_buf: + kfree(cdns_ctrl->buf); + +free_buf_desc: + dma_free_coherent(cdns_ctrl->dev, sizeof(struct cadence_nand_cdma_desc), + cdns_ctrl->cdma_desc, cdns_ctrl->dma_cdma_desc); + + return ret; +} + +/* Driver exit point. */ +static void cadence_nand_remove(struct cdns_nand_ctrl *cdns_ctrl) +{ + cadence_nand_chips_cleanup(cdns_ctrl); + if (cdns_ctrl->dmac) + dma_unmap_resource(cdns_ctrl->dmac->device->dev, + cdns_ctrl->io.iova_dma, cdns_ctrl->io.size, + DMA_BIDIRECTIONAL, 0); + cadence_nand_irq_cleanup(cdns_ctrl->irq, cdns_ctrl); + kfree(cdns_ctrl->buf); + dma_free_coherent(cdns_ctrl->dev, sizeof(struct cadence_nand_cdma_desc), + cdns_ctrl->cdma_desc, cdns_ctrl->dma_cdma_desc); + + if (cdns_ctrl->dmac) + dma_release_channel(cdns_ctrl->dmac); +} + +struct cadence_nand_dt { + struct cdns_nand_ctrl cdns_ctrl; + struct clk *clk; +}; + +static const struct cadence_nand_dt_devdata cadence_nand_default = { + .if_skew = 0, + .has_dma = 1, +}; + +static const struct of_device_id cadence_nand_dt_ids[] = { + { + .compatible = "cdns,hp-nfc", + .data = &cadence_nand_default + }, {} +}; + +MODULE_DEVICE_TABLE(of, cadence_nand_dt_ids); + +static int cadence_nand_dt_probe(struct platform_device *ofdev) +{ + struct resource *res; + struct cadence_nand_dt *dt; + struct cdns_nand_ctrl *cdns_ctrl; + int ret; + const struct cadence_nand_dt_devdata *devdata; + u32 val; + + devdata = device_get_match_data(&ofdev->dev); + if (!devdata) { + pr_err("Failed to find the right device id.\n"); + return -ENOMEM; + } + + dt = devm_kzalloc(&ofdev->dev, sizeof(*dt), GFP_KERNEL); + if (!dt) + return -ENOMEM; + + cdns_ctrl = &dt->cdns_ctrl; + cdns_ctrl->caps1 = devdata; + + cdns_ctrl->dev = &ofdev->dev; + cdns_ctrl->irq = platform_get_irq(ofdev, 0); + if (cdns_ctrl->irq < 0) + return cdns_ctrl->irq; + + dev_info(cdns_ctrl->dev, "IRQ: nr %d\n", cdns_ctrl->irq); + + cdns_ctrl->reg = devm_platform_ioremap_resource(ofdev, 0); + if (IS_ERR(cdns_ctrl->reg)) + return PTR_ERR(cdns_ctrl->reg); + + cdns_ctrl->io.virt = devm_platform_get_and_ioremap_resource(ofdev, 1, &res); + if (IS_ERR(cdns_ctrl->io.virt)) + return PTR_ERR(cdns_ctrl->io.virt); + + cdns_ctrl->io.dma = res->start; + cdns_ctrl->io.size = resource_size(res); + + dt->clk = devm_clk_get(cdns_ctrl->dev, "nf_clk"); + if (IS_ERR(dt->clk)) + return PTR_ERR(dt->clk); + + cdns_ctrl->nf_clk_rate = clk_get_rate(dt->clk); + + ret = of_property_read_u32(ofdev->dev.of_node, + "cdns,board-delay-ps", &val); + if (ret) { + val = 4830; + dev_info(cdns_ctrl->dev, + "missing cdns,board-delay-ps property, %d was set\n", + val); + } + cdns_ctrl->board_delay = val; + + ret = cadence_nand_init(cdns_ctrl); + if (ret) + return ret; + + platform_set_drvdata(ofdev, dt); + return 0; +} + +static void cadence_nand_dt_remove(struct platform_device *ofdev) +{ + struct cadence_nand_dt *dt = platform_get_drvdata(ofdev); + + cadence_nand_remove(&dt->cdns_ctrl); +} + +static struct platform_driver cadence_nand_dt_driver = { + .probe = cadence_nand_dt_probe, + .remove = cadence_nand_dt_remove, + .driver = { + .name = "cadence-nand-controller", + .of_match_table = cadence_nand_dt_ids, + }, +}; + +module_platform_driver(cadence_nand_dt_driver); + +MODULE_AUTHOR("Piotr Sroka "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Driver for Cadence NAND flash controller"); + diff --git a/drivers/mtd/nand/raw/cafe_nand.c b/drivers/mtd/nand/raw/cafe_nand.c new file mode 100644 index 000000000..f3117b031 --- /dev/null +++ b/drivers/mtd/nand/raw/cafe_nand.c @@ -0,0 +1,894 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for One Laptop Per Child ‘CAFÉ’ controller, aka Marvell 88ALP01 + * + * The data sheet for this device can be found at: + * http://wiki.laptop.org/go/Datasheets + * + * Copyright © 2006 Red Hat, Inc. + * Copyright © 2006 David Woodhouse + */ + +#define DEBUG + +#include +#undef DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CAFE_NAND_CTRL1 0x00 +#define CAFE_NAND_CTRL2 0x04 +#define CAFE_NAND_CTRL3 0x08 +#define CAFE_NAND_STATUS 0x0c +#define CAFE_NAND_IRQ 0x10 +#define CAFE_NAND_IRQ_MASK 0x14 +#define CAFE_NAND_DATA_LEN 0x18 +#define CAFE_NAND_ADDR1 0x1c +#define CAFE_NAND_ADDR2 0x20 +#define CAFE_NAND_TIMING1 0x24 +#define CAFE_NAND_TIMING2 0x28 +#define CAFE_NAND_TIMING3 0x2c +#define CAFE_NAND_NONMEM 0x30 +#define CAFE_NAND_ECC_RESULT 0x3C +#define CAFE_NAND_DMA_CTRL 0x40 +#define CAFE_NAND_DMA_ADDR0 0x44 +#define CAFE_NAND_DMA_ADDR1 0x48 +#define CAFE_NAND_ECC_SYN01 0x50 +#define CAFE_NAND_ECC_SYN23 0x54 +#define CAFE_NAND_ECC_SYN45 0x58 +#define CAFE_NAND_ECC_SYN67 0x5c +#define CAFE_NAND_READ_DATA 0x1000 +#define CAFE_NAND_WRITE_DATA 0x2000 + +#define CAFE_GLOBAL_CTRL 0x3004 +#define CAFE_GLOBAL_IRQ 0x3008 +#define CAFE_GLOBAL_IRQ_MASK 0x300c +#define CAFE_NAND_RESET 0x3034 + +/* Missing from the datasheet: bit 19 of CTRL1 sets CE0 vs. CE1 */ +#define CTRL1_CHIPSELECT (1<<19) + +struct cafe_priv { + struct nand_chip nand; + struct pci_dev *pdev; + void __iomem *mmio; + struct rs_control *rs; + uint32_t ctl1; + uint32_t ctl2; + int datalen; + int nr_data; + int data_pos; + int page_addr; + bool usedma; + dma_addr_t dmaaddr; + unsigned char *dmabuf; +}; + +static int usedma = 1; +module_param(usedma, int, 0644); + +static int skipbbt = 0; +module_param(skipbbt, int, 0644); + +static int debug = 0; +module_param(debug, int, 0644); + +static int regdebug = 0; +module_param(regdebug, int, 0644); + +static int checkecc = 1; +module_param(checkecc, int, 0644); + +static unsigned int numtimings; +static int timing[3]; +module_param_array(timing, int, &numtimings, 0644); + +static const char *part_probes[] = { "cmdlinepart", "RedBoot", NULL }; + +/* Hrm. Why isn't this already conditional on something in the struct device? */ +#define cafe_dev_dbg(dev, args...) do { if (debug) dev_dbg(dev, ##args); } while(0) + +/* Make it easier to switch to PIO if we need to */ +#define cafe_readl(cafe, addr) readl((cafe)->mmio + CAFE_##addr) +#define cafe_writel(cafe, datum, addr) writel(datum, (cafe)->mmio + CAFE_##addr) + +static int cafe_device_ready(struct nand_chip *chip) +{ + struct cafe_priv *cafe = nand_get_controller_data(chip); + int result = !!(cafe_readl(cafe, NAND_STATUS) & 0x40000000); + uint32_t irqs = cafe_readl(cafe, NAND_IRQ); + + cafe_writel(cafe, irqs, NAND_IRQ); + + cafe_dev_dbg(&cafe->pdev->dev, "NAND device is%s ready, IRQ %x (%x) (%x,%x)\n", + result?"":" not", irqs, cafe_readl(cafe, NAND_IRQ), + cafe_readl(cafe, GLOBAL_IRQ), cafe_readl(cafe, GLOBAL_IRQ_MASK)); + + return result; +} + + +static void cafe_write_buf(struct nand_chip *chip, const uint8_t *buf, int len) +{ + struct cafe_priv *cafe = nand_get_controller_data(chip); + + if (cafe->usedma) + memcpy(cafe->dmabuf + cafe->datalen, buf, len); + else + memcpy_toio(cafe->mmio + CAFE_NAND_WRITE_DATA + cafe->datalen, buf, len); + + cafe->datalen += len; + + cafe_dev_dbg(&cafe->pdev->dev, "Copy 0x%x bytes to write buffer. datalen 0x%x\n", + len, cafe->datalen); +} + +static void cafe_read_buf(struct nand_chip *chip, uint8_t *buf, int len) +{ + struct cafe_priv *cafe = nand_get_controller_data(chip); + + if (cafe->usedma) + memcpy(buf, cafe->dmabuf + cafe->datalen, len); + else + memcpy_fromio(buf, cafe->mmio + CAFE_NAND_READ_DATA + cafe->datalen, len); + + cafe_dev_dbg(&cafe->pdev->dev, "Copy 0x%x bytes from position 0x%x in read buffer.\n", + len, cafe->datalen); + cafe->datalen += len; +} + +static uint8_t cafe_read_byte(struct nand_chip *chip) +{ + struct cafe_priv *cafe = nand_get_controller_data(chip); + uint8_t d; + + cafe_read_buf(chip, &d, 1); + cafe_dev_dbg(&cafe->pdev->dev, "Read %02x\n", d); + + return d; +} + +static void cafe_nand_cmdfunc(struct nand_chip *chip, unsigned command, + int column, int page_addr) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct cafe_priv *cafe = nand_get_controller_data(chip); + int adrbytes = 0; + uint32_t ctl1; + uint32_t doneint = 0x80000000; + + cafe_dev_dbg(&cafe->pdev->dev, "cmdfunc %02x, 0x%x, 0x%x\n", + command, column, page_addr); + + if (command == NAND_CMD_ERASE2 || command == NAND_CMD_PAGEPROG) { + /* Second half of a command we already calculated */ + cafe_writel(cafe, cafe->ctl2 | 0x100 | command, NAND_CTRL2); + ctl1 = cafe->ctl1; + cafe->ctl2 &= ~(1<<30); + cafe_dev_dbg(&cafe->pdev->dev, "Continue command, ctl1 %08x, #data %d\n", + cafe->ctl1, cafe->nr_data); + goto do_command; + } + /* Reset ECC engine */ + cafe_writel(cafe, 0, NAND_CTRL2); + + /* Emulate NAND_CMD_READOOB on large-page chips */ + if (mtd->writesize > 512 && + command == NAND_CMD_READOOB) { + column += mtd->writesize; + command = NAND_CMD_READ0; + } + + /* FIXME: Do we need to send read command before sending data + for small-page chips, to position the buffer correctly? */ + + if (column != -1) { + cafe_writel(cafe, column, NAND_ADDR1); + adrbytes = 2; + if (page_addr != -1) + goto write_adr2; + } else if (page_addr != -1) { + cafe_writel(cafe, page_addr & 0xffff, NAND_ADDR1); + page_addr >>= 16; + write_adr2: + cafe_writel(cafe, page_addr, NAND_ADDR2); + adrbytes += 2; + if (mtd->size > mtd->writesize << 16) + adrbytes++; + } + + cafe->data_pos = cafe->datalen = 0; + + /* Set command valid bit, mask in the chip select bit */ + ctl1 = 0x80000000 | command | (cafe->ctl1 & CTRL1_CHIPSELECT); + + /* Set RD or WR bits as appropriate */ + if (command == NAND_CMD_READID || command == NAND_CMD_STATUS) { + ctl1 |= (1<<26); /* rd */ + /* Always 5 bytes, for now */ + cafe->datalen = 4; + /* And one address cycle -- even for STATUS, since the controller doesn't work without */ + adrbytes = 1; + } else if (command == NAND_CMD_READ0 || command == NAND_CMD_READ1 || + command == NAND_CMD_READOOB || command == NAND_CMD_RNDOUT) { + ctl1 |= 1<<26; /* rd */ + /* For now, assume just read to end of page */ + cafe->datalen = mtd->writesize + mtd->oobsize - column; + } else if (command == NAND_CMD_SEQIN) + ctl1 |= 1<<25; /* wr */ + + /* Set number of address bytes */ + if (adrbytes) + ctl1 |= ((adrbytes-1)|8) << 27; + + if (command == NAND_CMD_SEQIN || command == NAND_CMD_ERASE1) { + /* Ignore the first command of a pair; the hardware + deals with them both at once, later */ + cafe->ctl1 = ctl1; + cafe_dev_dbg(&cafe->pdev->dev, "Setup for delayed command, ctl1 %08x, dlen %x\n", + cafe->ctl1, cafe->datalen); + return; + } + /* RNDOUT and READ0 commands need a following byte */ + if (command == NAND_CMD_RNDOUT) + cafe_writel(cafe, cafe->ctl2 | 0x100 | NAND_CMD_RNDOUTSTART, NAND_CTRL2); + else if (command == NAND_CMD_READ0 && mtd->writesize > 512) + cafe_writel(cafe, cafe->ctl2 | 0x100 | NAND_CMD_READSTART, NAND_CTRL2); + + do_command: + cafe_dev_dbg(&cafe->pdev->dev, "dlen %x, ctl1 %x, ctl2 %x\n", + cafe->datalen, ctl1, cafe_readl(cafe, NAND_CTRL2)); + + /* NB: The datasheet lies -- we really should be subtracting 1 here */ + cafe_writel(cafe, cafe->datalen, NAND_DATA_LEN); + cafe_writel(cafe, 0x90000000, NAND_IRQ); + if (cafe->usedma && (ctl1 & (3<<25))) { + uint32_t dmactl = 0xc0000000 + cafe->datalen; + /* If WR or RD bits set, set up DMA */ + if (ctl1 & (1<<26)) { + /* It's a read */ + dmactl |= (1<<29); + /* ... so it's done when the DMA is done, not just + the command. */ + doneint = 0x10000000; + } + cafe_writel(cafe, dmactl, NAND_DMA_CTRL); + } + cafe->datalen = 0; + + if (unlikely(regdebug)) { + int i; + printk("About to write command %08x to register 0\n", ctl1); + for (i=4; i< 0x5c; i+=4) + printk("Register %x: %08x\n", i, readl(cafe->mmio + i)); + } + + cafe_writel(cafe, ctl1, NAND_CTRL1); + /* Apply this short delay always to ensure that we do wait tWB in + * any case on any machine. */ + ndelay(100); + + if (1) { + int c; + uint32_t irqs; + + for (c = 500000; c != 0; c--) { + irqs = cafe_readl(cafe, NAND_IRQ); + if (irqs & doneint) + break; + udelay(1); + if (!(c % 100000)) + cafe_dev_dbg(&cafe->pdev->dev, "Wait for ready, IRQ %x\n", irqs); + cpu_relax(); + } + cafe_writel(cafe, doneint, NAND_IRQ); + cafe_dev_dbg(&cafe->pdev->dev, "Command %x completed after %d usec, irqs %x (%x)\n", + command, 500000-c, irqs, cafe_readl(cafe, NAND_IRQ)); + } + + WARN_ON(cafe->ctl2 & (1<<30)); + + switch (command) { + + case NAND_CMD_CACHEDPROG: + case NAND_CMD_PAGEPROG: + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + case NAND_CMD_SEQIN: + case NAND_CMD_RNDIN: + case NAND_CMD_STATUS: + case NAND_CMD_RNDOUT: + cafe_writel(cafe, cafe->ctl2, NAND_CTRL2); + return; + } + nand_wait_ready(chip); + cafe_writel(cafe, cafe->ctl2, NAND_CTRL2); +} + +static void cafe_select_chip(struct nand_chip *chip, int chipnr) +{ + struct cafe_priv *cafe = nand_get_controller_data(chip); + + cafe_dev_dbg(&cafe->pdev->dev, "select_chip %d\n", chipnr); + + /* Mask the appropriate bit into the stored value of ctl1 + which will be used by cafe_nand_cmdfunc() */ + if (chipnr) + cafe->ctl1 |= CTRL1_CHIPSELECT; + else + cafe->ctl1 &= ~CTRL1_CHIPSELECT; +} + +static irqreturn_t cafe_nand_interrupt(int irq, void *id) +{ + struct mtd_info *mtd = id; + struct nand_chip *chip = mtd_to_nand(mtd); + struct cafe_priv *cafe = nand_get_controller_data(chip); + uint32_t irqs = cafe_readl(cafe, NAND_IRQ); + cafe_writel(cafe, irqs & ~0x90000000, NAND_IRQ); + if (!irqs) + return IRQ_NONE; + + cafe_dev_dbg(&cafe->pdev->dev, "irq, bits %x (%x)\n", irqs, cafe_readl(cafe, NAND_IRQ)); + return IRQ_HANDLED; +} + +static int cafe_nand_write_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + return nand_prog_page_op(chip, page, mtd->writesize, chip->oob_poi, + mtd->oobsize); +} + +/* Don't use -- use nand_read_oob_std for now */ +static int cafe_nand_read_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize); +} +/** + * cafe_nand_read_page - [REPLACEABLE] hardware ecc syndrome based page read + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller expects OOB data read to chip->oob_poi + * @page: page number to read + * + * The hw generator calculates the error syndrome automatically. Therefore + * we need a special oob layout and handling. + */ +static int cafe_nand_read_page(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct cafe_priv *cafe = nand_get_controller_data(chip); + unsigned int max_bitflips = 0; + + cafe_dev_dbg(&cafe->pdev->dev, "ECC result %08x SYN1,2 %08x\n", + cafe_readl(cafe, NAND_ECC_RESULT), + cafe_readl(cafe, NAND_ECC_SYN01)); + + nand_read_page_op(chip, page, 0, buf, mtd->writesize); + chip->legacy.read_buf(chip, chip->oob_poi, mtd->oobsize); + + if (checkecc && cafe_readl(cafe, NAND_ECC_RESULT) & (1<<18)) { + unsigned short syn[8], pat[4]; + int pos[4]; + u8 *oob = chip->oob_poi; + int i, n; + + for (i=0; i<8; i+=2) { + uint32_t tmp = cafe_readl(cafe, NAND_ECC_SYN01 + (i*2)); + + syn[i] = cafe->rs->codec->index_of[tmp & 0xfff]; + syn[i+1] = cafe->rs->codec->index_of[(tmp >> 16) & 0xfff]; + } + + n = decode_rs16(cafe->rs, NULL, NULL, 1367, syn, 0, pos, 0, + pat); + + for (i = 0; i < n; i++) { + int p = pos[i]; + + /* The 12-bit symbols are mapped to bytes here */ + + if (p > 1374) { + /* out of range */ + n = -1374; + } else if (p == 0) { + /* high four bits do not correspond to data */ + if (pat[i] > 0xff) + n = -2048; + else + buf[0] ^= pat[i]; + } else if (p == 1365) { + buf[2047] ^= pat[i] >> 4; + oob[0] ^= pat[i] << 4; + } else if (p > 1365) { + if ((p & 1) == 1) { + oob[3*p/2 - 2048] ^= pat[i] >> 4; + oob[3*p/2 - 2047] ^= pat[i] << 4; + } else { + oob[3*p/2 - 2049] ^= pat[i] >> 8; + oob[3*p/2 - 2048] ^= pat[i]; + } + } else if ((p & 1) == 1) { + buf[3*p/2] ^= pat[i] >> 4; + buf[3*p/2 + 1] ^= pat[i] << 4; + } else { + buf[3*p/2 - 1] ^= pat[i] >> 8; + buf[3*p/2] ^= pat[i]; + } + } + + if (n < 0) { + dev_dbg(&cafe->pdev->dev, "Failed to correct ECC at %08x\n", + cafe_readl(cafe, NAND_ADDR2) * 2048); + for (i = 0; i < 0x5c; i += 4) + printk("Register %x: %08x\n", i, readl(cafe->mmio + i)); + mtd->ecc_stats.failed++; + } else { + dev_dbg(&cafe->pdev->dev, "Corrected %d symbol errors\n", n); + mtd->ecc_stats.corrected += n; + max_bitflips = max_t(unsigned int, max_bitflips, n); + } + } + + return max_bitflips; +} + +static int cafe_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section) + return -ERANGE; + + oobregion->offset = 0; + oobregion->length = chip->ecc.total; + + return 0; +} + +static int cafe_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section) + return -ERANGE; + + oobregion->offset = chip->ecc.total; + oobregion->length = mtd->oobsize - chip->ecc.total; + + return 0; +} + +static const struct mtd_ooblayout_ops cafe_ooblayout_ops = { + .ecc = cafe_ooblayout_ecc, + .free = cafe_ooblayout_free, +}; + +/* Ick. The BBT code really ought to be able to work this bit out + for itself from the above, at least for the 2KiB case */ +static uint8_t cafe_bbt_pattern_2048[] = { 'B', 'b', 't', '0' }; +static uint8_t cafe_mirror_pattern_2048[] = { '1', 't', 'b', 'B' }; + +static uint8_t cafe_bbt_pattern_512[] = { 0xBB }; +static uint8_t cafe_mirror_pattern_512[] = { 0xBC }; + + +static struct nand_bbt_descr cafe_bbt_main_descr_2048 = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 14, + .len = 4, + .veroffs = 18, + .maxblocks = 4, + .pattern = cafe_bbt_pattern_2048 +}; + +static struct nand_bbt_descr cafe_bbt_mirror_descr_2048 = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 14, + .len = 4, + .veroffs = 18, + .maxblocks = 4, + .pattern = cafe_mirror_pattern_2048 +}; + +static struct nand_bbt_descr cafe_bbt_main_descr_512 = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 14, + .len = 1, + .veroffs = 15, + .maxblocks = 4, + .pattern = cafe_bbt_pattern_512 +}; + +static struct nand_bbt_descr cafe_bbt_mirror_descr_512 = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 14, + .len = 1, + .veroffs = 15, + .maxblocks = 4, + .pattern = cafe_mirror_pattern_512 +}; + + +static int cafe_nand_write_page_lowlevel(struct nand_chip *chip, + const uint8_t *buf, int oob_required, + int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct cafe_priv *cafe = nand_get_controller_data(chip); + + nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize); + chip->legacy.write_buf(chip, chip->oob_poi, mtd->oobsize); + + /* Set up ECC autogeneration */ + cafe->ctl2 |= (1<<30); + + return nand_prog_page_end_op(chip); +} + +/* F_2[X]/(X**6+X+1) */ +static unsigned short gf64_mul(u8 a, u8 b) +{ + u8 c; + unsigned int i; + + c = 0; + for (i = 0; i < 6; i++) { + if (a & 1) + c ^= b; + a >>= 1; + b <<= 1; + if ((b & 0x40) != 0) + b ^= 0x43; + } + + return c; +} + +/* F_64[X]/(X**2+X+A**-1) with A the generator of F_64[X] */ +static u16 gf4096_mul(u16 a, u16 b) +{ + u8 ah, al, bh, bl, ch, cl; + + ah = a >> 6; + al = a & 0x3f; + bh = b >> 6; + bl = b & 0x3f; + + ch = gf64_mul(ah ^ al, bh ^ bl) ^ gf64_mul(al, bl); + cl = gf64_mul(gf64_mul(ah, bh), 0x21) ^ gf64_mul(al, bl); + + return (ch << 6) ^ cl; +} + +static int cafe_mul(int x) +{ + if (x == 0) + return 1; + return gf4096_mul(x, 0xe01); +} + +static int cafe_nand_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct cafe_priv *cafe = nand_get_controller_data(chip); + int err = 0; + + cafe->dmabuf = dma_alloc_coherent(&cafe->pdev->dev, 2112, + &cafe->dmaaddr, GFP_KERNEL); + if (!cafe->dmabuf) + return -ENOMEM; + + /* Set up DMA address */ + cafe_writel(cafe, lower_32_bits(cafe->dmaaddr), NAND_DMA_ADDR0); + cafe_writel(cafe, upper_32_bits(cafe->dmaaddr), NAND_DMA_ADDR1); + + cafe_dev_dbg(&cafe->pdev->dev, "Set DMA address to %x (virt %p)\n", + cafe_readl(cafe, NAND_DMA_ADDR0), cafe->dmabuf); + + /* Restore the DMA flag */ + cafe->usedma = usedma; + + cafe->ctl2 = BIT(27); /* Reed-Solomon ECC */ + if (mtd->writesize == 2048) + cafe->ctl2 |= BIT(29); /* 2KiB page size */ + + /* Set up ECC according to the type of chip we found */ + mtd_set_ooblayout(mtd, &cafe_ooblayout_ops); + if (mtd->writesize == 2048) { + cafe->nand.bbt_td = &cafe_bbt_main_descr_2048; + cafe->nand.bbt_md = &cafe_bbt_mirror_descr_2048; + } else if (mtd->writesize == 512) { + cafe->nand.bbt_td = &cafe_bbt_main_descr_512; + cafe->nand.bbt_md = &cafe_bbt_mirror_descr_512; + } else { + dev_warn(&cafe->pdev->dev, + "Unexpected NAND flash writesize %d. Aborting\n", + mtd->writesize); + err = -ENOTSUPP; + goto out_free_dma; + } + + cafe->nand.ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + cafe->nand.ecc.placement = NAND_ECC_PLACEMENT_INTERLEAVED; + cafe->nand.ecc.size = mtd->writesize; + cafe->nand.ecc.bytes = 14; + cafe->nand.ecc.strength = 4; + cafe->nand.ecc.write_page = cafe_nand_write_page_lowlevel; + cafe->nand.ecc.write_oob = cafe_nand_write_oob; + cafe->nand.ecc.read_page = cafe_nand_read_page; + cafe->nand.ecc.read_oob = cafe_nand_read_oob; + + return 0; + + out_free_dma: + dma_free_coherent(&cafe->pdev->dev, 2112, cafe->dmabuf, cafe->dmaaddr); + + return err; +} + +static void cafe_nand_detach_chip(struct nand_chip *chip) +{ + struct cafe_priv *cafe = nand_get_controller_data(chip); + + dma_free_coherent(&cafe->pdev->dev, 2112, cafe->dmabuf, cafe->dmaaddr); +} + +static const struct nand_controller_ops cafe_nand_controller_ops = { + .attach_chip = cafe_nand_attach_chip, + .detach_chip = cafe_nand_detach_chip, +}; + +static int cafe_nand_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct mtd_info *mtd; + struct cafe_priv *cafe; + uint32_t ctrl; + int err = 0; + + /* Very old versions shared the same PCI ident for all three + functions on the chip. Verify the class too... */ + if ((pdev->class >> 8) != PCI_CLASS_MEMORY_FLASH) + return -ENODEV; + + err = pci_enable_device(pdev); + if (err) + return err; + + pci_set_master(pdev); + + cafe = kzalloc_obj(*cafe); + if (!cafe) { + err = -ENOMEM; + goto out_disable_device; + } + + mtd = nand_to_mtd(&cafe->nand); + mtd->dev.parent = &pdev->dev; + nand_set_controller_data(&cafe->nand, cafe); + + cafe->pdev = pdev; + cafe->mmio = pci_iomap(pdev, 0, 0); + if (!cafe->mmio) { + dev_warn(&pdev->dev, "failed to iomap\n"); + err = -ENOMEM; + goto out_free_mtd; + } + + cafe->rs = init_rs_non_canonical(12, &cafe_mul, 0, 1, 8); + if (!cafe->rs) { + err = -ENOMEM; + goto out_ior; + } + + cafe->nand.legacy.cmdfunc = cafe_nand_cmdfunc; + cafe->nand.legacy.dev_ready = cafe_device_ready; + cafe->nand.legacy.read_byte = cafe_read_byte; + cafe->nand.legacy.read_buf = cafe_read_buf; + cafe->nand.legacy.write_buf = cafe_write_buf; + cafe->nand.legacy.select_chip = cafe_select_chip; + cafe->nand.legacy.set_features = nand_get_set_features_notsupp; + cafe->nand.legacy.get_features = nand_get_set_features_notsupp; + + cafe->nand.legacy.chip_delay = 0; + + /* Enable the following for a flash based bad block table */ + cafe->nand.bbt_options = NAND_BBT_USE_FLASH; + + if (skipbbt) + cafe->nand.options |= NAND_SKIP_BBTSCAN | NAND_NO_BBM_QUIRK; + + if (numtimings && numtimings != 3) { + dev_warn(&cafe->pdev->dev, "%d timing register values ignored; precisely three are required\n", numtimings); + } + + if (numtimings == 3) { + cafe_dev_dbg(&cafe->pdev->dev, "Using provided timings (%08x %08x %08x)\n", + timing[0], timing[1], timing[2]); + } else { + timing[0] = cafe_readl(cafe, NAND_TIMING1); + timing[1] = cafe_readl(cafe, NAND_TIMING2); + timing[2] = cafe_readl(cafe, NAND_TIMING3); + + if (timing[0] | timing[1] | timing[2]) { + cafe_dev_dbg(&cafe->pdev->dev, "Timing registers already set (%08x %08x %08x)\n", + timing[0], timing[1], timing[2]); + } else { + dev_warn(&cafe->pdev->dev, "Timing registers unset; using most conservative defaults\n"); + timing[0] = timing[1] = timing[2] = 0xffffffff; + } + } + + /* Start off by resetting the NAND controller completely */ + cafe_writel(cafe, 1, NAND_RESET); + cafe_writel(cafe, 0, NAND_RESET); + + cafe_writel(cafe, timing[0], NAND_TIMING1); + cafe_writel(cafe, timing[1], NAND_TIMING2); + cafe_writel(cafe, timing[2], NAND_TIMING3); + + cafe_writel(cafe, 0xffffffff, NAND_IRQ_MASK); + err = request_irq(pdev->irq, &cafe_nand_interrupt, IRQF_SHARED, + "CAFE NAND", mtd); + if (err) { + dev_warn(&pdev->dev, "Could not register IRQ %d\n", pdev->irq); + goto out_free_rs; + } + + /* Disable master reset, enable NAND clock */ + ctrl = cafe_readl(cafe, GLOBAL_CTRL); + ctrl &= 0xffffeff0; + ctrl |= 0x00007000; + cafe_writel(cafe, ctrl | 0x05, GLOBAL_CTRL); + cafe_writel(cafe, ctrl | 0x0a, GLOBAL_CTRL); + cafe_writel(cafe, 0, NAND_DMA_CTRL); + + cafe_writel(cafe, 0x7006, GLOBAL_CTRL); + cafe_writel(cafe, 0x700a, GLOBAL_CTRL); + + /* Enable NAND IRQ in global IRQ mask register */ + cafe_writel(cafe, 0x80000007, GLOBAL_IRQ_MASK); + cafe_dev_dbg(&cafe->pdev->dev, "Control %x, IRQ mask %x\n", + cafe_readl(cafe, GLOBAL_CTRL), + cafe_readl(cafe, GLOBAL_IRQ_MASK)); + + /* Do not use the DMA during the NAND identification */ + cafe->usedma = 0; + + /* Scan to find existence of the device */ + cafe->nand.legacy.dummy_controller.ops = &cafe_nand_controller_ops; + err = nand_scan(&cafe->nand, 2); + if (err) + goto out_irq; + + pci_set_drvdata(pdev, mtd); + + mtd->name = "cafe_nand"; + err = mtd_device_parse_register(mtd, part_probes, NULL, NULL, 0); + if (err) + goto out_cleanup_nand; + + goto out; + + out_cleanup_nand: + nand_cleanup(&cafe->nand); + out_irq: + /* Disable NAND IRQ in global IRQ mask register */ + cafe_writel(cafe, ~1 & cafe_readl(cafe, GLOBAL_IRQ_MASK), GLOBAL_IRQ_MASK); + free_irq(pdev->irq, mtd); + out_free_rs: + free_rs(cafe->rs); + out_ior: + pci_iounmap(pdev, cafe->mmio); + out_free_mtd: + kfree(cafe); + out_disable_device: + pci_disable_device(pdev); + out: + return err; +} + +static void cafe_nand_remove(struct pci_dev *pdev) +{ + 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); + int ret; + + /* Disable NAND IRQ in global IRQ mask register */ + cafe_writel(cafe, ~1 & cafe_readl(cafe, GLOBAL_IRQ_MASK), GLOBAL_IRQ_MASK); + free_irq(pdev->irq, mtd); + ret = mtd_device_unregister(mtd); + WARN_ON(ret); + nand_cleanup(chip); + free_rs(cafe->rs); + pci_iounmap(pdev, cafe->mmio); + dma_free_coherent(&cafe->pdev->dev, 2112, cafe->dmabuf, cafe->dmaaddr); + kfree(cafe); + pci_disable_device(pdev); +} + +static const struct pci_device_id cafe_nand_tbl[] = { + { 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 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); + + /* Start off by resetting the NAND controller completely */ + cafe_writel(cafe, 1, NAND_RESET); + cafe_writel(cafe, 0, NAND_RESET); + cafe_writel(cafe, 0xffffffff, NAND_IRQ_MASK); + + /* Restore timing configuration */ + cafe_writel(cafe, timing[0], NAND_TIMING1); + cafe_writel(cafe, timing[1], NAND_TIMING2); + cafe_writel(cafe, timing[2], NAND_TIMING3); + + /* Disable master reset, enable NAND clock */ + ctrl = cafe_readl(cafe, GLOBAL_CTRL); + ctrl &= 0xffffeff0; + ctrl |= 0x00007000; + cafe_writel(cafe, ctrl | 0x05, GLOBAL_CTRL); + cafe_writel(cafe, ctrl | 0x0a, GLOBAL_CTRL); + cafe_writel(cafe, 0, NAND_DMA_CTRL); + cafe_writel(cafe, 0x7006, GLOBAL_CTRL); + cafe_writel(cafe, 0x700a, GLOBAL_CTRL); + + /* Set up DMA address */ + cafe_writel(cafe, cafe->dmaaddr & 0xffffffff, NAND_DMA_ADDR0); + if (sizeof(cafe->dmaaddr) > 4) + /* Shift in two parts to shut the compiler up */ + cafe_writel(cafe, (cafe->dmaaddr >> 16) >> 16, NAND_DMA_ADDR1); + else + cafe_writel(cafe, 0, NAND_DMA_ADDR1); + + /* Enable NAND IRQ in global IRQ mask register */ + cafe_writel(cafe, 0x80000007, GLOBAL_IRQ_MASK); + 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, + .driver.pm = &cafe_nand_ops, +}; + +module_pci_driver(cafe_nand_pci_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Woodhouse "); +MODULE_DESCRIPTION("NAND flash driver for OLPC CAFÉ chip"); diff --git a/drivers/mtd/nand/raw/cs553x_nand.c b/drivers/mtd/nand/raw/cs553x_nand.c new file mode 100644 index 000000000..0b872b1e3 --- /dev/null +++ b/drivers/mtd/nand/raw/cs553x_nand.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * (C) 2005, 2006 Red Hat Inc. + * + * Author: David Woodhouse + * Tom Sylla + * + * Overview: + * This is a device driver for the NAND flash controller found on + * the AMD CS5535/CS5536 companion chipsets for the Geode processor. + * mtd-id for command line partitioning is cs553x_nand_cs[0-3] + * where 0-3 reflects the chip select for NAND. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define NR_CS553X_CONTROLLERS 4 + +#define MSR_DIVIL_GLD_CAP 0x51400000 /* DIVIL capabilities */ +#define CAP_CS5535 0x2df000ULL +#define CAP_CS5536 0x5df500ULL + +/* NAND Timing MSRs */ +#define MSR_NANDF_DATA 0x5140001b /* NAND Flash Data Timing MSR */ +#define MSR_NANDF_CTL 0x5140001c /* NAND Flash Control Timing */ +#define MSR_NANDF_RSVD 0x5140001d /* Reserved */ + +/* NAND BAR MSRs */ +#define MSR_DIVIL_LBAR_FLSH0 0x51400010 /* Flash Chip Select 0 */ +#define MSR_DIVIL_LBAR_FLSH1 0x51400011 /* Flash Chip Select 1 */ +#define MSR_DIVIL_LBAR_FLSH2 0x51400012 /* Flash Chip Select 2 */ +#define MSR_DIVIL_LBAR_FLSH3 0x51400013 /* Flash Chip Select 3 */ + /* Each made up of... */ +#define FLSH_LBAR_EN (1ULL<<32) +#define FLSH_NOR_NAND (1ULL<<33) /* 1 for NAND */ +#define FLSH_MEM_IO (1ULL<<34) /* 1 for MMIO */ + /* I/O BARs have BASE_ADDR in bits 15:4, IO_MASK in 47:36 */ + /* MMIO BARs have BASE_ADDR in bits 31:12, MEM_MASK in 63:44 */ + +/* Pin function selection MSR (IDE vs. flash on the IDE pins) */ +#define MSR_DIVIL_BALL_OPTS 0x51400015 +#define PIN_OPT_IDE (1<<0) /* 0 for flash, 1 for IDE */ + +/* Registers within the NAND flash controller BAR -- memory mapped */ +#define MM_NAND_DATA 0x00 /* 0 to 0x7ff, in fact */ +#define MM_NAND_CTL 0x800 /* Any even address 0x800-0x80e */ +#define MM_NAND_IO 0x801 /* Any odd address 0x801-0x80f */ +#define MM_NAND_STS 0x810 +#define MM_NAND_ECC_LSB 0x811 +#define MM_NAND_ECC_MSB 0x812 +#define MM_NAND_ECC_COL 0x813 +#define MM_NAND_LAC 0x814 +#define MM_NAND_ECC_CTL 0x815 + +/* Registers within the NAND flash controller BAR -- I/O mapped */ +#define IO_NAND_DATA 0x00 /* 0 to 3, in fact */ +#define IO_NAND_CTL 0x04 +#define IO_NAND_IO 0x05 +#define IO_NAND_STS 0x06 +#define IO_NAND_ECC_CTL 0x08 +#define IO_NAND_ECC_LSB 0x09 +#define IO_NAND_ECC_MSB 0x0a +#define IO_NAND_ECC_COL 0x0b +#define IO_NAND_LAC 0x0c + +#define CS_NAND_CTL_DIST_EN (1<<4) /* Enable NAND Distract interrupt */ +#define CS_NAND_CTL_RDY_INT_MASK (1<<3) /* Enable RDY/BUSY# interrupt */ +#define CS_NAND_CTL_ALE (1<<2) +#define CS_NAND_CTL_CLE (1<<1) +#define CS_NAND_CTL_CE (1<<0) /* Keep low; 1 to reset */ + +#define CS_NAND_STS_FLASH_RDY (1<<3) +#define CS_NAND_CTLR_BUSY (1<<2) +#define CS_NAND_CMD_COMP (1<<1) +#define CS_NAND_DIST_ST (1<<0) + +#define CS_NAND_ECC_PARITY (1<<2) +#define CS_NAND_ECC_CLRECC (1<<1) +#define CS_NAND_ECC_ENECC (1<<0) + +struct cs553x_nand_controller { + struct nand_controller base; + struct nand_chip chip; + void __iomem *mmio; +}; + +static struct cs553x_nand_controller * +to_cs553x(struct nand_controller *controller) +{ + return container_of(controller, struct cs553x_nand_controller, base); +} + +static int cs553x_write_ctrl_byte(struct cs553x_nand_controller *cs553x, + u32 ctl, u8 data) +{ + u8 status; + + writeb(ctl, cs553x->mmio + MM_NAND_CTL); + writeb(data, cs553x->mmio + MM_NAND_IO); + return readb_poll_timeout_atomic(cs553x->mmio + MM_NAND_STS, status, + !(status & CS_NAND_CTLR_BUSY), 1, + 100000); +} + +static void cs553x_data_in(struct cs553x_nand_controller *cs553x, void *buf, + unsigned int len) +{ + writeb(0, cs553x->mmio + MM_NAND_CTL); + while (unlikely(len > 0x800)) { + memcpy_fromio(buf, cs553x->mmio, 0x800); + buf += 0x800; + len -= 0x800; + } + memcpy_fromio(buf, cs553x->mmio, len); +} + +static void cs553x_data_out(struct cs553x_nand_controller *cs553x, + const void *buf, unsigned int len) +{ + writeb(0, cs553x->mmio + MM_NAND_CTL); + while (unlikely(len > 0x800)) { + memcpy_toio(cs553x->mmio, buf, 0x800); + buf += 0x800; + len -= 0x800; + } + memcpy_toio(cs553x->mmio, buf, len); +} + +static int cs553x_wait_ready(struct cs553x_nand_controller *cs553x, + unsigned int timeout_ms) +{ + u8 mask = CS_NAND_CTLR_BUSY | CS_NAND_STS_FLASH_RDY; + u8 status; + + return readb_poll_timeout(cs553x->mmio + MM_NAND_STS, status, + (status & mask) == CS_NAND_STS_FLASH_RDY, 100, + timeout_ms * 1000); +} + +static int cs553x_exec_instr(struct cs553x_nand_controller *cs553x, + const struct nand_op_instr *instr) +{ + unsigned int i; + int ret = 0; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + ret = cs553x_write_ctrl_byte(cs553x, CS_NAND_CTL_CLE, + instr->ctx.cmd.opcode); + break; + + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) { + ret = cs553x_write_ctrl_byte(cs553x, CS_NAND_CTL_ALE, + instr->ctx.addr.addrs[i]); + if (ret) + break; + } + break; + + case NAND_OP_DATA_IN_INSTR: + cs553x_data_in(cs553x, instr->ctx.data.buf.in, + instr->ctx.data.len); + break; + + case NAND_OP_DATA_OUT_INSTR: + cs553x_data_out(cs553x, instr->ctx.data.buf.out, + instr->ctx.data.len); + break; + + case NAND_OP_WAITRDY_INSTR: + ret = cs553x_wait_ready(cs553x, instr->ctx.waitrdy.timeout_ms); + break; + } + + if (instr->delay_ns) + ndelay(instr->delay_ns); + + return ret; +} + +static int cs553x_exec_op(struct nand_chip *this, + const struct nand_operation *op, + bool check_only) +{ + struct cs553x_nand_controller *cs553x = to_cs553x(this->controller); + unsigned int i; + int ret; + + if (check_only) + return true; + + /* De-assert the CE pin */ + writeb(0, cs553x->mmio + MM_NAND_CTL); + for (i = 0; i < op->ninstrs; i++) { + ret = cs553x_exec_instr(cs553x, &op->instrs[i]); + if (ret) + break; + } + + /* Re-assert the CE pin. */ + writeb(CS_NAND_CTL_CE, cs553x->mmio + MM_NAND_CTL); + + return ret; +} + +static void cs_enable_hwecc(struct nand_chip *this, int mode) +{ + struct cs553x_nand_controller *cs553x = to_cs553x(this->controller); + + writeb(0x07, cs553x->mmio + MM_NAND_ECC_CTL); +} + +static int cs_calculate_ecc(struct nand_chip *this, const u_char *dat, + u_char *ecc_code) +{ + struct cs553x_nand_controller *cs553x = to_cs553x(this->controller); + uint32_t ecc; + + ecc = readl(cs553x->mmio + MM_NAND_STS); + + ecc_code[1] = ecc >> 8; + ecc_code[0] = ecc >> 16; + ecc_code[2] = ecc >> 24; + return 0; +} + +static struct cs553x_nand_controller *controllers[4]; + +static int cs553x_attach_chip(struct nand_chip *chip) +{ + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return 0; + + chip->ecc.size = 256; + chip->ecc.bytes = 3; + chip->ecc.hwctl = cs_enable_hwecc; + chip->ecc.calculate = cs_calculate_ecc; + chip->ecc.correct = rawnand_sw_hamming_correct; + chip->ecc.strength = 1; + + return 0; +} + +static const struct nand_controller_ops cs553x_nand_controller_ops = { + .exec_op = cs553x_exec_op, + .attach_chip = cs553x_attach_chip, +}; + +static int __init cs553x_init_one(int cs, int mmio, unsigned long adr) +{ + struct cs553x_nand_controller *controller; + int err = 0; + struct nand_chip *this; + struct mtd_info *new_mtd; + + pr_notice("Probing CS553x NAND controller CS#%d at %sIO 0x%08lx\n", + cs, mmio ? "MM" : "P", adr); + + if (!mmio) { + pr_notice("PIO mode not yet implemented for CS553X NAND controller\n"); + return -ENXIO; + } + + /* Allocate memory for MTD device structure and private data */ + controller = kzalloc_obj(*controller); + if (!controller) { + err = -ENOMEM; + goto out; + } + + this = &controller->chip; + nand_controller_init(&controller->base); + controller->base.ops = &cs553x_nand_controller_ops; + this->controller = &controller->base; + new_mtd = nand_to_mtd(this); + + /* Link the private data with the MTD structure */ + new_mtd->owner = THIS_MODULE; + + /* map physical address */ + controller->mmio = ioremap(adr, 4096); + if (!controller->mmio) { + pr_warn("ioremap cs553x NAND @0x%08lx failed\n", adr); + err = -EIO; + goto out_mtd; + } + + /* Enable the following for a flash based bad block table */ + this->bbt_options = NAND_BBT_USE_FLASH; + + new_mtd->name = kasprintf(GFP_KERNEL, "cs553x_nand_cs%d", cs); + if (!new_mtd->name) { + err = -ENOMEM; + goto out_ior; + } + + /* Scan to find existence of the device */ + err = nand_scan(this, 1); + if (err) + goto out_free; + + controllers[cs] = controller; + goto out; + +out_free: + kfree(new_mtd->name); +out_ior: + iounmap(controller->mmio); +out_mtd: + kfree(controller); +out: + return err; +} + +static int is_geode(void) +{ + /* These are the CPUs which will have a CS553[56] companion chip */ + if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD && + boot_cpu_data.x86 == 5 && + boot_cpu_data.x86_model == 10) + return 1; /* Geode LX */ + + if ((boot_cpu_data.x86_vendor == X86_VENDOR_NSC || + boot_cpu_data.x86_vendor == X86_VENDOR_CYRIX) && + boot_cpu_data.x86 == 5 && + boot_cpu_data.x86_model == 5) + return 1; /* Geode GX (née GX2) */ + + return 0; +} + +static int __init cs553x_init(void) +{ + int err = -ENXIO; + int i; + uint64_t val; + + /* If the CPU isn't a Geode GX or LX, abort */ + if (!is_geode()) + return -ENXIO; + + /* If it doesn't have the CS553[56], abort */ + 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 */ + 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++) { + 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); + } + + /* Register all devices together here. This means we can easily hack it to + do mtdconcat etc. if we want to. */ + for (i = 0; i < NR_CS553X_CONTROLLERS; i++) { + if (controllers[i]) { + /* If any devices registered, return success. Else the last error. */ + mtd_device_register(nand_to_mtd(&controllers[i]->chip), + NULL, 0); + err = 0; + } + } + + return err; +} + +module_init(cs553x_init); + +static void __exit cs553x_cleanup(void) +{ + int i; + + for (i = 0; i < NR_CS553X_CONTROLLERS; i++) { + struct cs553x_nand_controller *controller = controllers[i]; + struct nand_chip *this = &controller->chip; + struct mtd_info *mtd = nand_to_mtd(this); + int ret; + + if (!mtd) + continue; + + /* Release resources, unregister device */ + ret = mtd_device_unregister(mtd); + WARN_ON(ret); + nand_cleanup(this); + kfree(mtd->name); + controllers[i] = NULL; + + /* unmap physical address */ + iounmap(controller->mmio); + + /* Free the MTD device structure */ + kfree(controller); + } +} + +module_exit(cs553x_cleanup); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Woodhouse "); +MODULE_DESCRIPTION("NAND controller driver for AMD CS5535/CS5536 companion chip"); diff --git a/drivers/mtd/nand/raw/davinci_nand.c b/drivers/mtd/nand/raw/davinci_nand.c new file mode 100644 index 000000000..398655388 --- /dev/null +++ b/drivers/mtd/nand/raw/davinci_nand.c @@ -0,0 +1,1048 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * davinci_nand.c - NAND Flash Driver for DaVinci family chips + * + * Copyright © 2006 Texas Instruments. + * + * Port to 2.6.23 Copyright © 2008 by: + * Sander Huijsen + * Troy Kisky + * Dirk Behme + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NRCSR_OFFSET 0x00 +#define NANDFCR_OFFSET 0x60 +#define NANDFSR_OFFSET 0x64 +#define NANDF1ECC_OFFSET 0x70 + +/* 4-bit ECC syndrome registers */ +#define NAND_4BIT_ECC_LOAD_OFFSET 0xbc +#define NAND_4BIT_ECC1_OFFSET 0xc0 +#define NAND_4BIT_ECC2_OFFSET 0xc4 +#define NAND_4BIT_ECC3_OFFSET 0xc8 +#define NAND_4BIT_ECC4_OFFSET 0xcc +#define NAND_ERR_ADD1_OFFSET 0xd0 +#define NAND_ERR_ADD2_OFFSET 0xd4 +#define NAND_ERR_ERRVAL1_OFFSET 0xd8 +#define NAND_ERR_ERRVAL2_OFFSET 0xdc + +/* NOTE: boards don't need to use these address bits + * for ALE/CLE unless they support booting from NAND. + * They're used unless platform data overrides them. + */ +#define MASK_ALE 0x08 +#define MASK_CLE 0x10 + +#define MAX_TSU_PS 3000 /* Input setup time in ps */ +#define MAX_TH_PS 1600 /* Input hold time in ps */ + +struct davinci_nand_pdata { + uint32_t mask_ale; + uint32_t mask_cle; + + /* + * 0-indexed chip-select number of the asynchronous + * interface to which the NAND device has been connected. + * + * So, if you have NAND connected to CS3 of DA850, you + * will pass '1' here. Since the asynchronous interface + * on DA850 starts from CS2. + */ + uint32_t core_chipsel; + + /* for packages using two chipselects */ + uint32_t mask_chipsel; + + /* board's default static partition info */ + struct mtd_partition *parts; + unsigned int nr_parts; + + /* none == NAND_ECC_ENGINE_TYPE_NONE (strongly *not* advised!!) + * soft == NAND_ECC_ENGINE_TYPE_SOFT + * on-die == NAND_ECC_ENGINE_TYPE_ON_DIE + * else == NAND_ECC_ENGINE_TYPE_ON_HOST, according to ecc_bits + * + * All DaVinci-family chips support 1-bit hardware ECC. + * Newer ones also support 4-bit ECC, but are awkward + * using it with large page chips. + */ + enum nand_ecc_engine_type engine_type; + enum nand_ecc_placement ecc_placement; + u8 ecc_bits; + + /* e.g. NAND_BUSWIDTH_16 */ + unsigned int options; + /* e.g. NAND_BBT_USE_FLASH */ + unsigned int bbt_options; + + /* Main and mirror bbt descriptor overrides */ + struct nand_bbt_descr *bbt_td; + struct nand_bbt_descr *bbt_md; +}; + +/* + * This is a device driver for the NAND flash controller found on the + * various DaVinci family chips. It handles up to four SoC chipselects, + * and some flavors of secondary chipselect (e.g. based on A12) as used + * with multichip packages. + * + * The 1-bit ECC hardware is supported, as well as the newer 4-bit ECC + * available on chips like the DM355 and OMAP-L137 and needed with the + * more error-prone MLC NAND chips. + * + * This driver assumes EM_WAIT connects all the NAND devices' RDY/nBUSY + * outputs in a "wire-AND" configuration, with no per-chip signals. + */ +struct davinci_nand_info { + struct nand_controller controller; + struct nand_chip chip; + + struct platform_device *pdev; + + bool is_readmode; + + void __iomem *base; + void __iomem *vaddr; + + void __iomem *current_cs; + + uint32_t mask_chipsel; + uint32_t mask_ale; + uint32_t mask_cle; + + uint32_t core_chipsel; + + struct clk *clk; + struct aemif_device *aemif; +}; + +static DEFINE_SPINLOCK(davinci_nand_lock); +static bool ecc4_busy; + +static inline struct davinci_nand_info *to_davinci_nand(struct mtd_info *mtd) +{ + return container_of(mtd_to_nand(mtd), struct davinci_nand_info, chip); +} + +static inline unsigned int davinci_nand_readl(struct davinci_nand_info *info, + int offset) +{ + return __raw_readl(info->base + offset); +} + +static inline void davinci_nand_writel(struct davinci_nand_info *info, + int offset, unsigned long value) +{ + __raw_writel(value, info->base + offset); +} + +/*----------------------------------------------------------------------*/ + +/* + * 1-bit hardware ECC ... context maintained for each core chipselect + */ + +static inline uint32_t nand_davinci_readecc_1bit(struct mtd_info *mtd) +{ + struct davinci_nand_info *info = to_davinci_nand(mtd); + + return davinci_nand_readl(info, NANDF1ECC_OFFSET + + 4 * info->core_chipsel); +} + +static void nand_davinci_hwctl_1bit(struct nand_chip *chip, int mode) +{ + struct davinci_nand_info *info; + uint32_t nandcfr; + unsigned long flags; + + info = to_davinci_nand(nand_to_mtd(chip)); + + /* Reset ECC hardware */ + nand_davinci_readecc_1bit(nand_to_mtd(chip)); + + spin_lock_irqsave(&davinci_nand_lock, flags); + + /* Restart ECC hardware */ + nandcfr = davinci_nand_readl(info, NANDFCR_OFFSET); + nandcfr |= BIT(8 + info->core_chipsel); + davinci_nand_writel(info, NANDFCR_OFFSET, nandcfr); + + spin_unlock_irqrestore(&davinci_nand_lock, flags); +} + +/* + * Read hardware ECC value and pack into three bytes + */ +static int nand_davinci_calculate_1bit(struct nand_chip *chip, + const u_char *dat, u_char *ecc_code) +{ + unsigned int ecc_val = nand_davinci_readecc_1bit(nand_to_mtd(chip)); + unsigned int ecc24 = (ecc_val & 0x0fff) | ((ecc_val & 0x0fff0000) >> 4); + + /* invert so that erased block ecc is correct */ + ecc24 = ~ecc24; + ecc_code[0] = (u_char)(ecc24); + ecc_code[1] = (u_char)(ecc24 >> 8); + ecc_code[2] = (u_char)(ecc24 >> 16); + + return 0; +} + +static int nand_davinci_correct_1bit(struct nand_chip *chip, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + uint32_t eccNand = read_ecc[0] | (read_ecc[1] << 8) | + (read_ecc[2] << 16); + uint32_t eccCalc = calc_ecc[0] | (calc_ecc[1] << 8) | + (calc_ecc[2] << 16); + uint32_t diff = eccCalc ^ eccNand; + + if (diff) { + if ((((diff >> 12) ^ diff) & 0xfff) == 0xfff) { + /* Correctable error */ + if ((diff >> (12 + 3)) < chip->ecc.size) { + dat[diff >> (12 + 3)] ^= BIT((diff >> 12) & 7); + return 1; + } else { + return -EBADMSG; + } + } else if (!(diff & (diff - 1))) { + /* Single bit ECC error in the ECC itself, + * nothing to fix */ + return 1; + } else { + /* Uncorrectable error */ + return -EBADMSG; + } + + } + return 0; +} + +/*----------------------------------------------------------------------*/ + +/* + * 4-bit hardware ECC ... context maintained over entire AEMIF + * + * This is a syndrome engine, but we avoid NAND_ECC_PLACEMENT_INTERLEAVED + * since that forces use of a problematic "infix OOB" layout. + * Among other things, it trashes manufacturer bad block markers. + * Also, and specific to this hardware, it ECC-protects the "prepad" + * in the OOB ... while having ECC protection for parts of OOB would + * seem useful, the current MTD stack sometimes wants to update the + * OOB without recomputing ECC. + */ + +static void nand_davinci_hwctl_4bit(struct nand_chip *chip, int mode) +{ + struct davinci_nand_info *info = to_davinci_nand(nand_to_mtd(chip)); + unsigned long flags; + u32 val; + + /* Reset ECC hardware */ + davinci_nand_readl(info, NAND_4BIT_ECC1_OFFSET); + + spin_lock_irqsave(&davinci_nand_lock, flags); + + /* Start 4-bit ECC calculation for read/write */ + val = davinci_nand_readl(info, NANDFCR_OFFSET); + val &= ~(0x03 << 4); + val |= (info->core_chipsel << 4) | BIT(12); + davinci_nand_writel(info, NANDFCR_OFFSET, val); + + info->is_readmode = (mode == NAND_ECC_READ); + + spin_unlock_irqrestore(&davinci_nand_lock, flags); +} + +/* Read raw ECC code after writing to NAND. */ +static void +nand_davinci_readecc_4bit(struct davinci_nand_info *info, u32 code[4]) +{ + const u32 mask = 0x03ff03ff; + + code[0] = davinci_nand_readl(info, NAND_4BIT_ECC1_OFFSET) & mask; + code[1] = davinci_nand_readl(info, NAND_4BIT_ECC2_OFFSET) & mask; + code[2] = davinci_nand_readl(info, NAND_4BIT_ECC3_OFFSET) & mask; + code[3] = davinci_nand_readl(info, NAND_4BIT_ECC4_OFFSET) & mask; +} + +/* Terminate read ECC; or return ECC (as bytes) of data written to NAND. */ +static int nand_davinci_calculate_4bit(struct nand_chip *chip, + const u_char *dat, u_char *ecc_code) +{ + struct davinci_nand_info *info = to_davinci_nand(nand_to_mtd(chip)); + u32 raw_ecc[4], *p; + unsigned i; + + /* After a read, terminate ECC calculation by a dummy read + * of some 4-bit ECC register. ECC covers everything that + * was read; correct() just uses the hardware state, so + * ecc_code is not needed. + */ + if (info->is_readmode) { + davinci_nand_readl(info, NAND_4BIT_ECC1_OFFSET); + return 0; + } + + /* Pack eight raw 10-bit ecc values into ten bytes, making + * two passes which each convert four values (in upper and + * lower halves of two 32-bit words) into five bytes. The + * ROM boot loader uses this same packing scheme. + */ + nand_davinci_readecc_4bit(info, raw_ecc); + for (i = 0, p = raw_ecc; i < 2; i++, p += 2) { + *ecc_code++ = p[0] & 0xff; + *ecc_code++ = ((p[0] >> 8) & 0x03) | ((p[0] >> 14) & 0xfc); + *ecc_code++ = ((p[0] >> 22) & 0x0f) | ((p[1] << 4) & 0xf0); + *ecc_code++ = ((p[1] >> 4) & 0x3f) | ((p[1] >> 10) & 0xc0); + *ecc_code++ = (p[1] >> 18) & 0xff; + } + + return 0; +} + +/* Correct up to 4 bits in data we just read, using state left in the + * hardware plus the ecc_code computed when it was first written. + */ +static int nand_davinci_correct_4bit(struct nand_chip *chip, u_char *data, + u_char *ecc_code, u_char *null) +{ + int i; + struct davinci_nand_info *info = to_davinci_nand(nand_to_mtd(chip)); + unsigned short ecc10[8]; + unsigned short *ecc16; + u32 syndrome[4]; + u32 ecc_state; + unsigned num_errors, corrected; + unsigned long timeo; + + /* Unpack ten bytes into eight 10 bit values. We know we're + * little-endian, and use type punning for less shifting/masking. + */ + if (WARN_ON(0x01 & (uintptr_t)ecc_code)) + return -EINVAL; + ecc16 = (unsigned short *)ecc_code; + + ecc10[0] = (ecc16[0] >> 0) & 0x3ff; + ecc10[1] = ((ecc16[0] >> 10) & 0x3f) | ((ecc16[1] << 6) & 0x3c0); + ecc10[2] = (ecc16[1] >> 4) & 0x3ff; + ecc10[3] = ((ecc16[1] >> 14) & 0x3) | ((ecc16[2] << 2) & 0x3fc); + ecc10[4] = (ecc16[2] >> 8) | ((ecc16[3] << 8) & 0x300); + ecc10[5] = (ecc16[3] >> 2) & 0x3ff; + ecc10[6] = ((ecc16[3] >> 12) & 0xf) | ((ecc16[4] << 4) & 0x3f0); + ecc10[7] = (ecc16[4] >> 6) & 0x3ff; + + /* Tell ECC controller about the expected ECC codes. */ + for (i = 7; i >= 0; i--) + davinci_nand_writel(info, NAND_4BIT_ECC_LOAD_OFFSET, ecc10[i]); + + /* Allow time for syndrome calculation ... then read it. + * A syndrome of all zeroes 0 means no detected errors. + */ + davinci_nand_readl(info, NANDFSR_OFFSET); + nand_davinci_readecc_4bit(info, syndrome); + if (!(syndrome[0] | syndrome[1] | syndrome[2] | syndrome[3])) + return 0; + + /* + * Clear any previous address calculation by doing a dummy read of an + * error address register. + */ + davinci_nand_readl(info, NAND_ERR_ADD1_OFFSET); + + /* Start address calculation, and wait for it to complete. + * We _could_ start reading more data while this is working, + * to speed up the overall page read. + */ + davinci_nand_writel(info, NANDFCR_OFFSET, + davinci_nand_readl(info, NANDFCR_OFFSET) | BIT(13)); + + /* + * ECC_STATE field reads 0x3 (Error correction complete) immediately + * after setting the 4BITECC_ADD_CALC_START bit. So if you immediately + * begin trying to poll for the state, you may fall right out of your + * loop without any of the correction calculations having taken place. + * The recommendation from the hardware team is to initially delay as + * long as ECC_STATE reads less than 4. After that, ECC HW has entered + * correction state. + */ + timeo = jiffies + usecs_to_jiffies(100); + do { + ecc_state = (davinci_nand_readl(info, + NANDFSR_OFFSET) >> 8) & 0x0f; + cpu_relax(); + } while ((ecc_state < 4) && time_before(jiffies, timeo)); + + for (;;) { + u32 fsr = davinci_nand_readl(info, NANDFSR_OFFSET); + + switch ((fsr >> 8) & 0x0f) { + case 0: /* no error, should not happen */ + davinci_nand_readl(info, NAND_ERR_ERRVAL1_OFFSET); + return 0; + case 1: /* five or more errors detected */ + davinci_nand_readl(info, NAND_ERR_ERRVAL1_OFFSET); + return -EBADMSG; + case 2: /* error addresses computed */ + case 3: + num_errors = 1 + ((fsr >> 16) & 0x03); + goto correct; + default: /* still working on it */ + cpu_relax(); + continue; + } + } + +correct: + /* correct each error */ + for (i = 0, corrected = 0; i < num_errors; i++) { + int error_address, error_value; + + if (i > 1) { + error_address = davinci_nand_readl(info, + NAND_ERR_ADD2_OFFSET); + error_value = davinci_nand_readl(info, + NAND_ERR_ERRVAL2_OFFSET); + } else { + error_address = davinci_nand_readl(info, + NAND_ERR_ADD1_OFFSET); + error_value = davinci_nand_readl(info, + NAND_ERR_ERRVAL1_OFFSET); + } + + if (i & 1) { + error_address >>= 16; + error_value >>= 16; + } + error_address &= 0x3ff; + error_address = (512 + 7) - error_address; + + if (error_address < 512) { + data[error_address] ^= error_value; + corrected++; + } + } + + return corrected; +} + +/*----------------------------------------------------------------------*/ + +/* An ECC layout for using 4-bit ECC with small-page flash, storing + * ten ECC bytes plus the manufacturer's bad block marker byte, and + * and not overlapping the default BBT markers. + */ +static int hwecc4_ooblayout_small_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 2) + return -ERANGE; + + if (!section) { + oobregion->offset = 0; + oobregion->length = 5; + } else if (section == 1) { + oobregion->offset = 6; + oobregion->length = 2; + } else { + oobregion->offset = 13; + oobregion->length = 3; + } + + return 0; +} + +static int hwecc4_ooblayout_small_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 1) + return -ERANGE; + + if (!section) { + oobregion->offset = 8; + oobregion->length = 5; + } else { + oobregion->offset = 16; + oobregion->length = mtd->oobsize - 16; + } + + return 0; +} + +static const struct mtd_ooblayout_ops hwecc4_small_ooblayout_ops = { + .ecc = hwecc4_ooblayout_small_ecc, + .free = hwecc4_ooblayout_small_free, +}; + +static int hwecc4_ooblayout_large_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + unsigned int total_ecc_bytes = nand->ecc.ctx.total; + int nregions = total_ecc_bytes / 10; /* 10 bytes per chunk */ + + if (section >= nregions) + return -ERANGE; + + oobregion->offset = (section * 16) + 6; + oobregion->length = 10; + + return 0; +} + +static int hwecc4_ooblayout_large_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + unsigned int total_ecc_bytes = nand->ecc.ctx.total; + int nregions = total_ecc_bytes / 10; /* 10 bytes per chunk */ + + /* First region is used for BBT */ + if (section >= (nregions - 1)) + return -ERANGE; + + oobregion->offset = ((section + 1) * 16); + oobregion->length = 6; + + return 0; +} + +static const struct mtd_ooblayout_ops hwecc4_large_ooblayout_ops = { + .ecc = hwecc4_ooblayout_large_ecc, + .free = hwecc4_ooblayout_large_free, +}; + +#if defined(CONFIG_OF) +static const struct of_device_id davinci_nand_of_match[] = { + {.compatible = "ti,davinci-nand", }, + {.compatible = "ti,keystone-nand", }, + {}, +}; +MODULE_DEVICE_TABLE(of, davinci_nand_of_match); + +static struct davinci_nand_pdata * +nand_davinci_get_pdata(struct platform_device *pdev) +{ + if (!dev_get_platdata(&pdev->dev)) { + struct davinci_nand_pdata *pdata; + const char *mode; + u32 prop; + + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct davinci_nand_pdata), + GFP_KERNEL); + pdev->dev.platform_data = pdata; + if (!pdata) + return ERR_PTR(-ENOMEM); + if (!device_property_read_u32(&pdev->dev, + "ti,davinci-chipselect", &prop)) + pdata->core_chipsel = prop; + else + return ERR_PTR(-EINVAL); + + if (!device_property_read_u32(&pdev->dev, + "ti,davinci-mask-ale", &prop)) + pdata->mask_ale = prop; + if (!device_property_read_u32(&pdev->dev, + "ti,davinci-mask-cle", &prop)) + pdata->mask_cle = prop; + if (!device_property_read_u32(&pdev->dev, + "ti,davinci-mask-chipsel", &prop)) + pdata->mask_chipsel = prop; + if (!device_property_read_string(&pdev->dev, + "ti,davinci-ecc-mode", + &mode)) { + if (!strncmp("none", mode, 4)) + pdata->engine_type = NAND_ECC_ENGINE_TYPE_NONE; + if (!strncmp("soft", mode, 4)) + pdata->engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + if (!strncmp("hw", mode, 2)) + pdata->engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + if (!strncmp("on-die", mode, 6)) + pdata->engine_type = NAND_ECC_ENGINE_TYPE_ON_DIE; + } + if (!device_property_read_u32(&pdev->dev, + "ti,davinci-ecc-bits", &prop)) + pdata->ecc_bits = prop; + + if (!device_property_read_u32(&pdev->dev, + "ti,davinci-nand-buswidth", + &prop) && prop == 16) + pdata->options |= NAND_BUSWIDTH_16; + + if (device_property_read_bool(&pdev->dev, + "ti,davinci-nand-use-bbt")) + pdata->bbt_options = NAND_BBT_USE_FLASH; + + /* + * Since kernel v4.8, this driver has been fixed to enable + * use of 4-bit hardware ECC with subpages and verified on + * TI's keystone EVMs (K2L, K2HK and K2E). + * However, in the interest of not breaking systems using + * existing UBI partitions, sub-page writes are not being + * (re)enabled. If you want to use subpage writes on Keystone + * platforms (i.e. do not have any existing UBI partitions), + * then use "ti,davinci-nand" as the compatible in your + * device-tree file. + */ + if (device_is_compatible(&pdev->dev, "ti,keystone-nand")) + pdata->options |= NAND_NO_SUBPAGE_WRITE; + } + + return dev_get_platdata(&pdev->dev); +} +#else +static struct davinci_nand_pdata * +nand_davinci_get_pdata(struct platform_device *pdev) +{ + return dev_get_platdata(&pdev->dev); +} +#endif + +static int davinci_nand_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct davinci_nand_info *info = to_davinci_nand(mtd); + struct davinci_nand_pdata *pdata = nand_davinci_get_pdata(info->pdev); + int ret = 0; + + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + + /* Use board-specific ECC config */ + chip->ecc.engine_type = pdata->engine_type; + chip->ecc.placement = pdata->ecc_placement; + + switch (chip->ecc.engine_type) { + case NAND_ECC_ENGINE_TYPE_NONE: + case NAND_ECC_ENGINE_TYPE_ON_DIE: + pdata->ecc_bits = 0; + break; + case NAND_ECC_ENGINE_TYPE_SOFT: + pdata->ecc_bits = 0; + /* + * 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 davinci_nand_pdata. + */ + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + break; + case NAND_ECC_ENGINE_TYPE_ON_HOST: + if (pdata->ecc_bits == 4) { + int chunks = mtd->writesize / 512; + + if (!chunks || mtd->oobsize < 16) { + dev_dbg(&info->pdev->dev, "too small\n"); + return -EINVAL; + } + + /* + * No sanity checks: CPUs must support this, + * and the chips may not use NAND_BUSWIDTH_16. + */ + + /* No sharing 4-bit hardware between chipselects yet */ + spin_lock_irq(&davinci_nand_lock); + if (ecc4_busy) + ret = -EBUSY; + else + ecc4_busy = true; + spin_unlock_irq(&davinci_nand_lock); + + if (ret == -EBUSY) + return ret; + + chip->ecc.calculate = nand_davinci_calculate_4bit; + chip->ecc.correct = nand_davinci_correct_4bit; + chip->ecc.hwctl = nand_davinci_hwctl_4bit; + chip->ecc.bytes = 10; + chip->ecc.options = NAND_ECC_GENERIC_ERASED_CHECK; + chip->ecc.algo = NAND_ECC_ALGO_BCH; + + /* + * Update ECC layout if needed ... for 1-bit HW ECC, the + * default is OK, but it allocates 6 bytes when only 3 + * are needed (for each 512 bytes). For 4-bit HW ECC, + * the default is not usable: 10 bytes needed, not 6. + * + * For small page chips, preserve the manufacturer's + * badblock marking data ... and make sure a flash BBT + * table marker fits in the free bytes. + */ + if (chunks == 1) { + mtd_set_ooblayout(mtd, + &hwecc4_small_ooblayout_ops); + } else if (chunks == 4 || chunks == 8) { + chip->ecc.read_page = nand_read_page_hwecc_oob_first; + + if (chip->options & NAND_IS_BOOT_MEDIUM) + mtd_set_ooblayout(mtd, &hwecc4_large_ooblayout_ops); + else + mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout()); + } else { + return -EIO; + } + } else { + /* 1bit ecc hamming */ + chip->ecc.calculate = nand_davinci_calculate_1bit; + chip->ecc.correct = nand_davinci_correct_1bit; + chip->ecc.hwctl = nand_davinci_hwctl_1bit; + chip->ecc.bytes = 3; + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + } + chip->ecc.size = 512; + chip->ecc.strength = pdata->ecc_bits; + break; + default: + return -EINVAL; + } + + return ret; +} + +static void nand_davinci_data_in(struct davinci_nand_info *info, void *buf, + unsigned int len, bool force_8bit) +{ + u32 alignment = ((uintptr_t)buf | len) & 3; + + if (force_8bit || (alignment & 1)) + ioread8_rep(info->current_cs, buf, len); + else if (alignment & 3) + ioread16_rep(info->current_cs, buf, len >> 1); + else + ioread32_rep(info->current_cs, buf, len >> 2); +} + +static void nand_davinci_data_out(struct davinci_nand_info *info, + const void *buf, unsigned int len, + bool force_8bit) +{ + u32 alignment = ((uintptr_t)buf | len) & 3; + + if (force_8bit || (alignment & 1)) + iowrite8_rep(info->current_cs, buf, len); + else if (alignment & 3) + iowrite16_rep(info->current_cs, buf, len >> 1); + else + iowrite32_rep(info->current_cs, buf, len >> 2); +} + +static int davinci_nand_exec_instr(struct davinci_nand_info *info, + const struct nand_op_instr *instr) +{ + unsigned int i, timeout_us; + u32 status; + int ret; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + iowrite8(instr->ctx.cmd.opcode, + info->current_cs + info->mask_cle); + break; + + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) { + iowrite8(instr->ctx.addr.addrs[i], + info->current_cs + info->mask_ale); + } + break; + + case NAND_OP_DATA_IN_INSTR: + nand_davinci_data_in(info, instr->ctx.data.buf.in, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + break; + + case NAND_OP_DATA_OUT_INSTR: + nand_davinci_data_out(info, instr->ctx.data.buf.out, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + break; + + case NAND_OP_WAITRDY_INSTR: + timeout_us = instr->ctx.waitrdy.timeout_ms * 1000; + ret = readl_relaxed_poll_timeout(info->base + NANDFSR_OFFSET, + status, status & BIT(0), 5, + timeout_us); + if (ret) + return ret; + + break; + } + + if (instr->delay_ns) { + /* Dummy read to be sure that command is sent before ndelay starts */ + davinci_nand_readl(info, 0); + ndelay(instr->delay_ns); + } + + return 0; +} + +static int davinci_nand_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + struct davinci_nand_info *info = to_davinci_nand(nand_to_mtd(chip)); + unsigned int i; + + if (check_only) + return 0; + + info->current_cs = info->vaddr + (op->cs * info->mask_chipsel); + + for (i = 0; i < op->ninstrs; i++) { + int ret; + + ret = davinci_nand_exec_instr(info, &op->instrs[i]); + if (ret) + return ret; + } + + return 0; +} + +#define TO_CYCLES(ps, period_ns) (DIV_ROUND_UP((ps) / 1000, (period_ns))) + +static int davinci_nand_setup_interface(struct nand_chip *chip, int chipnr, + const struct nand_interface_config *conf) +{ + struct davinci_nand_info *info = to_davinci_nand(nand_to_mtd(chip)); + const struct nand_sdr_timings *sdr; + struct aemif_cs_timings timings; + s32 cfg, min, cyc_ns; + int ret; + + cyc_ns = 1000000000 / clk_get_rate(info->clk); + + sdr = nand_get_sdr_timings(conf); + if (IS_ERR(sdr)) + return PTR_ERR(sdr); + + cfg = TO_CYCLES(sdr->tCLR_min, cyc_ns) - 1; + timings.rsetup = cfg > 0 ? cfg : 0; + + cfg = max_t(s32, TO_CYCLES(sdr->tREA_max + MAX_TSU_PS, cyc_ns), + TO_CYCLES(sdr->tRP_min, cyc_ns)) - 1; + timings.rstrobe = cfg > 0 ? cfg : 0; + + min = TO_CYCLES(sdr->tCEA_max + MAX_TSU_PS, cyc_ns) - 2; + while ((s32)(timings.rsetup + timings.rstrobe) < min) + timings.rstrobe++; + + cfg = TO_CYCLES((s32)(MAX_TH_PS - sdr->tCHZ_max), cyc_ns) - 1; + timings.rhold = cfg > 0 ? cfg : 0; + + min = TO_CYCLES(sdr->tRC_min, cyc_ns) - 3; + while ((s32)(timings.rsetup + timings.rstrobe + timings.rhold) < min) + timings.rhold++; + + cfg = TO_CYCLES((s32)(sdr->tRHZ_max - (timings.rhold + 1) * cyc_ns * 1000), cyc_ns); + cfg = max_t(s32, cfg, TO_CYCLES(sdr->tCHZ_max, cyc_ns)) - 1; + timings.ta = cfg > 0 ? cfg : 0; + + cfg = TO_CYCLES(sdr->tWP_min, cyc_ns) - 1; + timings.wstrobe = cfg > 0 ? cfg : 0; + + cfg = max_t(s32, TO_CYCLES(sdr->tCLS_min, cyc_ns), TO_CYCLES(sdr->tALS_min, cyc_ns)); + cfg = max_t(s32, cfg, TO_CYCLES(sdr->tCS_min, cyc_ns)) - 1; + timings.wsetup = cfg > 0 ? cfg : 0; + + min = TO_CYCLES(sdr->tDS_min, cyc_ns) - 2; + while ((s32)(timings.wsetup + timings.wstrobe) < min) + timings.wstrobe++; + + cfg = max_t(s32, TO_CYCLES(sdr->tCLH_min, cyc_ns), TO_CYCLES(sdr->tALH_min, cyc_ns)); + cfg = max_t(s32, cfg, TO_CYCLES(sdr->tCH_min, cyc_ns)); + cfg = max_t(s32, cfg, TO_CYCLES(sdr->tDH_min, cyc_ns)) - 1; + timings.whold = cfg > 0 ? cfg : 0; + + min = TO_CYCLES(sdr->tWC_min, cyc_ns) - 2; + while ((s32)(timings.wsetup + timings.wstrobe + timings.whold) < min) + timings.whold++; + + dev_dbg(&info->pdev->dev, "RSETUP %x RSTROBE %x RHOLD %x\n", + timings.rsetup, timings.rstrobe, timings.rhold); + dev_dbg(&info->pdev->dev, "TA %x\n", timings.ta); + dev_dbg(&info->pdev->dev, "WSETUP %x WSTROBE %x WHOLD %x\n", + timings.wsetup, timings.wstrobe, timings.whold); + + ret = aemif_check_cs_timings(&timings); + if (ret || chipnr == NAND_DATA_IFACE_CHECK_ONLY) + return ret; + + return aemif_set_cs_timings(info->aemif, info->core_chipsel, &timings); +} + +static const struct nand_controller_ops davinci_nand_controller_ops = { + .attach_chip = davinci_nand_attach_chip, + .exec_op = davinci_nand_exec_op, + .setup_interface = davinci_nand_setup_interface, +}; + +static int nand_davinci_probe(struct platform_device *pdev) +{ + struct davinci_nand_pdata *pdata; + struct davinci_nand_info *info; + struct resource *res1; + struct resource *res2; + void __iomem *vaddr; + void __iomem *base; + int ret; + uint32_t val; + struct mtd_info *mtd; + + pdata = nand_davinci_get_pdata(pdev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + + /* insist on board-specific configuration */ + if (!pdata) + return -ENODEV; + + /* which external chipselect will we be managing? */ + if (pdata->core_chipsel > 3) + return -ENODEV; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + platform_set_drvdata(pdev, info); + + res1 = platform_get_resource(pdev, IORESOURCE_MEM, 0); + res2 = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!res1 || !res2) { + dev_err(&pdev->dev, "resource missing\n"); + return -EINVAL; + } + + vaddr = devm_ioremap_resource(&pdev->dev, res1); + if (IS_ERR(vaddr)) + return PTR_ERR(vaddr); + + /* + * This registers range is used to setup NAND settings. In case with + * TI AEMIF driver, the same memory address range is requested already + * by AEMIF, so we cannot request it twice, just ioremap. + * The AEMIF and NAND drivers not use the same registers in this range. + */ + base = devm_ioremap(&pdev->dev, res2->start, resource_size(res2)); + if (!base) { + dev_err(&pdev->dev, "ioremap failed for resource %pR\n", res2); + return -EADDRNOTAVAIL; + } + + info->clk = devm_clk_get_enabled(&pdev->dev, "aemif"); + if (IS_ERR(info->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(info->clk), "failed to get clock"); + + info->pdev = pdev; + info->base = base; + info->vaddr = vaddr; + info->aemif = dev_get_drvdata(pdev->dev.parent); + + mtd = nand_to_mtd(&info->chip); + mtd->dev.parent = &pdev->dev; + nand_set_flash_node(&info->chip, pdev->dev.of_node); + + /* options such as NAND_BBT_USE_FLASH */ + info->chip.bbt_options = pdata->bbt_options; + /* options such as 16-bit widths */ + info->chip.options = pdata->options; + info->chip.bbt_td = pdata->bbt_td; + info->chip.bbt_md = pdata->bbt_md; + + info->current_cs = info->vaddr; + info->core_chipsel = pdata->core_chipsel; + info->mask_chipsel = pdata->mask_chipsel; + + /* use nandboot-capable ALE/CLE masks by default */ + info->mask_ale = pdata->mask_ale ? : MASK_ALE; + info->mask_cle = pdata->mask_cle ? : MASK_CLE; + + spin_lock_irq(&davinci_nand_lock); + + /* put CSxNAND into NAND mode */ + val = davinci_nand_readl(info, NANDFCR_OFFSET); + val |= BIT(info->core_chipsel); + davinci_nand_writel(info, NANDFCR_OFFSET, val); + + spin_unlock_irq(&davinci_nand_lock); + + /* Scan to find existence of the device(s) */ + nand_controller_init(&info->controller); + info->controller.ops = &davinci_nand_controller_ops; + info->chip.controller = &info->controller; + ret = nand_scan(&info->chip, pdata->mask_chipsel ? 2 : 1); + if (ret < 0) { + dev_dbg(&pdev->dev, "no NAND chip(s) found\n"); + return ret; + } + + if (pdata->parts) + ret = mtd_device_register(mtd, pdata->parts, pdata->nr_parts); + else + ret = mtd_device_register(mtd, NULL, 0); + if (ret < 0) + goto err_cleanup_nand; + + val = davinci_nand_readl(info, NRCSR_OFFSET); + dev_info(&pdev->dev, "controller rev. %d.%d\n", + (val >> 8) & 0xff, val & 0xff); + + return 0; + +err_cleanup_nand: + nand_cleanup(&info->chip); + + return ret; +} + +static void nand_davinci_remove(struct platform_device *pdev) +{ + struct davinci_nand_info *info = platform_get_drvdata(pdev); + struct nand_chip *chip = &info->chip; + int ret; + + spin_lock_irq(&davinci_nand_lock); + if (chip->ecc.placement == NAND_ECC_PLACEMENT_INTERLEAVED) + ecc4_busy = false; + spin_unlock_irq(&davinci_nand_lock); + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); +} + +static struct platform_driver nand_davinci_driver = { + .probe = nand_davinci_probe, + .remove = nand_davinci_remove, + .driver = { + .name = "davinci_nand", + .of_match_table = of_match_ptr(davinci_nand_of_match), + }, +}; +MODULE_ALIAS("platform:davinci_nand"); + +module_platform_driver(nand_davinci_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("Davinci NAND flash driver"); + diff --git a/drivers/mtd/nand/raw/denali.c b/drivers/mtd/nand/raw/denali.c new file mode 100644 index 000000000..fa2439cb4 --- /dev/null +++ b/drivers/mtd/nand/raw/denali.c @@ -0,0 +1,1381 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NAND Flash Controller Device Driver + * Copyright © 2009-2010, Intel Corporation and its suppliers. + * + * Copyright (c) 2017-2019 Socionext Inc. + * Reworked by Masahiro Yamada + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "denali.h" + +#define DENALI_NAND_NAME "denali-nand" + +/* for Indexed Addressing */ +#define DENALI_INDEXED_CTRL 0x00 +#define DENALI_INDEXED_DATA 0x10 + +#define DENALI_MAP00 (0 << 26) /* direct access to buffer */ +#define DENALI_MAP01 (1 << 26) /* read/write pages in PIO */ +#define DENALI_MAP10 (2 << 26) /* high-level control plane */ +#define DENALI_MAP11 (3 << 26) /* direct controller access */ + +/* MAP11 access cycle type */ +#define DENALI_MAP11_CMD ((DENALI_MAP11) | 0) /* command cycle */ +#define DENALI_MAP11_ADDR ((DENALI_MAP11) | 1) /* address cycle */ +#define DENALI_MAP11_DATA ((DENALI_MAP11) | 2) /* data cycle */ + +#define DENALI_BANK(denali) ((denali)->active_bank << 24) + +#define DENALI_INVALID_BANK -1 + +static struct denali_chip *to_denali_chip(struct nand_chip *chip) +{ + return container_of(chip, struct denali_chip, chip); +} + +static struct denali_controller *to_denali_controller(struct nand_chip *chip) +{ + return container_of(chip->controller, struct denali_controller, + controller); +} + +/* + * Direct Addressing - the slave address forms the control information (command + * type, bank, block, and page address). The slave data is the actual data to + * be transferred. This mode requires 28 bits of address region allocated. + */ +static u32 denali_direct_read(struct denali_controller *denali, u32 addr) +{ + return ioread32(denali->host + addr); +} + +static void denali_direct_write(struct denali_controller *denali, u32 addr, + u32 data) +{ + iowrite32(data, denali->host + addr); +} + +/* + * Indexed Addressing - address translation module intervenes in passing the + * control information. This mode reduces the required address range. The + * control information and transferred data are latched by the registers in + * the translation module. + */ +static u32 denali_indexed_read(struct denali_controller *denali, u32 addr) +{ + iowrite32(addr, denali->host + DENALI_INDEXED_CTRL); + return ioread32(denali->host + DENALI_INDEXED_DATA); +} + +static void denali_indexed_write(struct denali_controller *denali, u32 addr, + u32 data) +{ + iowrite32(addr, denali->host + DENALI_INDEXED_CTRL); + iowrite32(data, denali->host + DENALI_INDEXED_DATA); +} + +static void denali_enable_irq(struct denali_controller *denali) +{ + int i; + + for (i = 0; i < denali->nbanks; i++) + iowrite32(U32_MAX, denali->reg + INTR_EN(i)); + iowrite32(GLOBAL_INT_EN_FLAG, denali->reg + GLOBAL_INT_ENABLE); +} + +static void denali_disable_irq(struct denali_controller *denali) +{ + int i; + + for (i = 0; i < denali->nbanks; i++) + iowrite32(0, denali->reg + INTR_EN(i)); + iowrite32(0, denali->reg + GLOBAL_INT_ENABLE); +} + +static void denali_clear_irq(struct denali_controller *denali, + int bank, u32 irq_status) +{ + /* write one to clear bits */ + iowrite32(irq_status, denali->reg + INTR_STATUS(bank)); +} + +static void denali_clear_irq_all(struct denali_controller *denali) +{ + int i; + + for (i = 0; i < denali->nbanks; i++) + denali_clear_irq(denali, i, U32_MAX); +} + +static irqreturn_t denali_isr(int irq, void *dev_id) +{ + struct denali_controller *denali = dev_id; + irqreturn_t ret = IRQ_NONE; + u32 irq_status; + int i; + + spin_lock(&denali->irq_lock); + + for (i = 0; i < denali->nbanks; i++) { + irq_status = ioread32(denali->reg + INTR_STATUS(i)); + if (irq_status) + ret = IRQ_HANDLED; + + denali_clear_irq(denali, i, irq_status); + + if (i != denali->active_bank) + continue; + + denali->irq_status |= irq_status; + + if (denali->irq_status & denali->irq_mask) + complete(&denali->complete); + } + + spin_unlock(&denali->irq_lock); + + return ret; +} + +static void denali_reset_irq(struct denali_controller *denali) +{ + unsigned long flags; + + spin_lock_irqsave(&denali->irq_lock, flags); + denali->irq_status = 0; + denali->irq_mask = 0; + spin_unlock_irqrestore(&denali->irq_lock, flags); +} + +static u32 denali_wait_for_irq(struct denali_controller *denali, u32 irq_mask) +{ + unsigned long time_left, flags; + u32 irq_status; + + spin_lock_irqsave(&denali->irq_lock, flags); + + irq_status = denali->irq_status; + + if (irq_mask & irq_status) { + /* return immediately if the IRQ has already happened. */ + spin_unlock_irqrestore(&denali->irq_lock, flags); + return irq_status; + } + + denali->irq_mask = irq_mask; + reinit_completion(&denali->complete); + spin_unlock_irqrestore(&denali->irq_lock, flags); + + time_left = wait_for_completion_timeout(&denali->complete, + msecs_to_jiffies(1000)); + if (!time_left) { + dev_err(denali->dev, "timeout while waiting for irq 0x%x\n", + irq_mask); + return 0; + } + + return denali->irq_status; +} + +static void denali_select_target(struct nand_chip *chip, int cs) +{ + struct denali_controller *denali = to_denali_controller(chip); + struct denali_chip_sel *sel = &to_denali_chip(chip)->sels[cs]; + struct mtd_info *mtd = nand_to_mtd(chip); + + denali->active_bank = sel->bank; + + iowrite32(1 << (chip->phys_erase_shift - chip->page_shift), + denali->reg + PAGES_PER_BLOCK); + iowrite32(chip->options & NAND_BUSWIDTH_16 ? 1 : 0, + denali->reg + DEVICE_WIDTH); + iowrite32(mtd->writesize, denali->reg + DEVICE_MAIN_AREA_SIZE); + iowrite32(mtd->oobsize, denali->reg + DEVICE_SPARE_AREA_SIZE); + iowrite32(chip->options & NAND_ROW_ADDR_3 ? + 0 : TWO_ROW_ADDR_CYCLES__FLAG, + denali->reg + TWO_ROW_ADDR_CYCLES); + iowrite32(FIELD_PREP(ECC_CORRECTION__ERASE_THRESHOLD, 1) | + FIELD_PREP(ECC_CORRECTION__VALUE, chip->ecc.strength), + denali->reg + ECC_CORRECTION); + iowrite32(chip->ecc.size, denali->reg + CFG_DATA_BLOCK_SIZE); + iowrite32(chip->ecc.size, denali->reg + CFG_LAST_DATA_BLOCK_SIZE); + iowrite32(chip->ecc.steps, denali->reg + CFG_NUM_DATA_BLOCKS); + + if (chip->options & NAND_KEEP_TIMINGS) + return; + + /* update timing registers unless NAND_KEEP_TIMINGS is set */ + iowrite32(sel->hwhr2_and_we_2_re, denali->reg + TWHR2_AND_WE_2_RE); + iowrite32(sel->tcwaw_and_addr_2_data, + denali->reg + TCWAW_AND_ADDR_2_DATA); + iowrite32(sel->re_2_we, denali->reg + RE_2_WE); + iowrite32(sel->acc_clks, denali->reg + ACC_CLKS); + iowrite32(sel->rdwr_en_lo_cnt, denali->reg + RDWR_EN_LO_CNT); + iowrite32(sel->rdwr_en_hi_cnt, denali->reg + RDWR_EN_HI_CNT); + iowrite32(sel->cs_setup_cnt, denali->reg + CS_SETUP_CNT); + iowrite32(sel->re_2_re, denali->reg + RE_2_RE); +} + +static int denali_change_column(struct nand_chip *chip, unsigned int offset, + void *buf, unsigned int len, bool write) +{ + if (write) + return nand_change_write_column_op(chip, offset, buf, len, + false); + else + return nand_change_read_column_op(chip, offset, buf, len, + false); +} + +static int denali_payload_xfer(struct nand_chip *chip, void *buf, bool write) +{ + struct denali_controller *denali = to_denali_controller(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int writesize = mtd->writesize; + int oob_skip = denali->oob_skip_bytes; + int ret, i, pos, len; + + for (i = 0; i < ecc->steps; i++) { + pos = i * (ecc->size + ecc->bytes); + len = ecc->size; + + if (pos >= writesize) { + pos += oob_skip; + } else if (pos + len > writesize) { + /* This chunk overwraps the BBM area. Must be split */ + ret = denali_change_column(chip, pos, buf, + writesize - pos, write); + if (ret) + return ret; + + buf += writesize - pos; + len -= writesize - pos; + pos = writesize + oob_skip; + } + + ret = denali_change_column(chip, pos, buf, len, write); + if (ret) + return ret; + + buf += len; + } + + return 0; +} + +static int denali_oob_xfer(struct nand_chip *chip, void *buf, bool write) +{ + struct denali_controller *denali = to_denali_controller(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int writesize = mtd->writesize; + int oobsize = mtd->oobsize; + int oob_skip = denali->oob_skip_bytes; + int ret, i, pos, len; + + /* BBM at the beginning of the OOB area */ + ret = denali_change_column(chip, writesize, buf, oob_skip, write); + if (ret) + return ret; + + buf += oob_skip; + + for (i = 0; i < ecc->steps; i++) { + pos = ecc->size + i * (ecc->size + ecc->bytes); + + if (i == ecc->steps - 1) + /* The last chunk includes OOB free */ + len = writesize + oobsize - pos - oob_skip; + else + len = ecc->bytes; + + if (pos >= writesize) { + pos += oob_skip; + } else if (pos + len > writesize) { + /* This chunk overwraps the BBM area. Must be split */ + ret = denali_change_column(chip, pos, buf, + writesize - pos, write); + if (ret) + return ret; + + buf += writesize - pos; + len -= writesize - pos; + pos = writesize + oob_skip; + } + + ret = denali_change_column(chip, pos, buf, len, write); + if (ret) + return ret; + + buf += len; + } + + return 0; +} + +static int denali_read_raw(struct nand_chip *chip, void *buf, void *oob_buf, + int page) +{ + int ret; + + if (!buf && !oob_buf) + return -EINVAL; + + ret = nand_read_page_op(chip, page, 0, NULL, 0); + if (ret) + return ret; + + if (buf) { + ret = denali_payload_xfer(chip, buf, false); + if (ret) + return ret; + } + + if (oob_buf) { + ret = denali_oob_xfer(chip, oob_buf, false); + if (ret) + return ret; + } + + return 0; +} + +static int denali_write_raw(struct nand_chip *chip, const void *buf, + const void *oob_buf, int page) +{ + int ret; + + if (!buf && !oob_buf) + return -EINVAL; + + ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0); + if (ret) + return ret; + + if (buf) { + ret = denali_payload_xfer(chip, (void *)buf, true); + if (ret) + return ret; + } + + if (oob_buf) { + ret = denali_oob_xfer(chip, (void *)oob_buf, true); + if (ret) + return ret; + } + + return nand_prog_page_end_op(chip); +} + +static int denali_read_page_raw(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + return denali_read_raw(chip, buf, oob_required ? chip->oob_poi : NULL, + page); +} + +static int denali_write_page_raw(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + return denali_write_raw(chip, buf, oob_required ? chip->oob_poi : NULL, + page); +} + +static int denali_read_oob(struct nand_chip *chip, int page) +{ + return denali_read_raw(chip, NULL, chip->oob_poi, page); +} + +static int denali_write_oob(struct nand_chip *chip, int page) +{ + return denali_write_raw(chip, NULL, chip->oob_poi, page); +} + +static int denali_check_erased_page(struct nand_chip *chip, u8 *buf, + unsigned long uncor_ecc_flags, + unsigned int max_bitflips) +{ + struct denali_controller *denali = to_denali_controller(chip); + struct mtd_ecc_stats *ecc_stats = &nand_to_mtd(chip)->ecc_stats; + struct nand_ecc_ctrl *ecc = &chip->ecc; + u8 *ecc_code = chip->oob_poi + denali->oob_skip_bytes; + int i, stat; + + for (i = 0; i < ecc->steps; i++) { + if (!(uncor_ecc_flags & BIT(i))) + continue; + + stat = nand_check_erased_ecc_chunk(buf, ecc->size, ecc_code, + ecc->bytes, NULL, 0, + ecc->strength); + if (stat < 0) { + ecc_stats->failed++; + } else { + ecc_stats->corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + + buf += ecc->size; + ecc_code += ecc->bytes; + } + + return max_bitflips; +} + +static int denali_hw_ecc_fixup(struct nand_chip *chip, + unsigned long *uncor_ecc_flags) +{ + struct denali_controller *denali = to_denali_controller(chip); + struct mtd_ecc_stats *ecc_stats = &nand_to_mtd(chip)->ecc_stats; + int bank = denali->active_bank; + u32 ecc_cor; + unsigned int max_bitflips; + + ecc_cor = ioread32(denali->reg + ECC_COR_INFO(bank)); + ecc_cor >>= ECC_COR_INFO__SHIFT(bank); + + if (ecc_cor & ECC_COR_INFO__UNCOR_ERR) { + /* + * This flag is set when uncorrectable error occurs at least in + * one ECC sector. We can not know "how many sectors", or + * "which sector(s)". We need erase-page check for all sectors. + */ + *uncor_ecc_flags = GENMASK(chip->ecc.steps - 1, 0); + return 0; + } + + max_bitflips = FIELD_GET(ECC_COR_INFO__MAX_ERRORS, ecc_cor); + + /* + * The register holds the maximum of per-sector corrected bitflips. + * This is suitable for the return value of the ->read_page() callback. + * Unfortunately, we can not know the total number of corrected bits in + * the page. Increase the stats by max_bitflips. (compromised solution) + */ + ecc_stats->corrected += max_bitflips; + + return max_bitflips; +} + +static int denali_sw_ecc_fixup(struct nand_chip *chip, + unsigned long *uncor_ecc_flags, u8 *buf) +{ + struct denali_controller *denali = to_denali_controller(chip); + struct mtd_ecc_stats *ecc_stats = &nand_to_mtd(chip)->ecc_stats; + unsigned int ecc_size = chip->ecc.size; + unsigned int bitflips = 0; + unsigned int max_bitflips = 0; + u32 err_addr, err_cor_info; + unsigned int err_byte, err_sector, err_device; + u8 err_cor_value; + unsigned int prev_sector = 0; + u32 irq_status; + + denali_reset_irq(denali); + + do { + err_addr = ioread32(denali->reg + ECC_ERROR_ADDRESS); + err_sector = FIELD_GET(ECC_ERROR_ADDRESS__SECTOR, err_addr); + err_byte = FIELD_GET(ECC_ERROR_ADDRESS__OFFSET, err_addr); + + err_cor_info = ioread32(denali->reg + ERR_CORRECTION_INFO); + err_cor_value = FIELD_GET(ERR_CORRECTION_INFO__BYTE, + err_cor_info); + err_device = FIELD_GET(ERR_CORRECTION_INFO__DEVICE, + err_cor_info); + + /* reset the bitflip counter when crossing ECC sector */ + if (err_sector != prev_sector) + bitflips = 0; + + if (err_cor_info & ERR_CORRECTION_INFO__UNCOR) { + /* + * Check later if this is a real ECC error, or + * an erased sector. + */ + *uncor_ecc_flags |= BIT(err_sector); + } else if (err_byte < ecc_size) { + /* + * If err_byte is larger than ecc_size, means error + * happened in OOB, so we ignore it. It's no need for + * us to correct it err_device is represented the NAND + * error bits are happened in if there are more than + * one NAND connected. + */ + int offset; + unsigned int flips_in_byte; + + offset = (err_sector * ecc_size + err_byte) * + denali->devs_per_cs + err_device; + + /* correct the ECC error */ + flips_in_byte = hweight8(buf[offset] ^ err_cor_value); + buf[offset] ^= err_cor_value; + ecc_stats->corrected += flips_in_byte; + bitflips += flips_in_byte; + + max_bitflips = max(max_bitflips, bitflips); + } + + prev_sector = err_sector; + } while (!(err_cor_info & ERR_CORRECTION_INFO__LAST_ERR)); + + /* + * Once handle all ECC errors, controller will trigger an + * ECC_TRANSACTION_DONE interrupt. + */ + irq_status = denali_wait_for_irq(denali, INTR__ECC_TRANSACTION_DONE); + if (!(irq_status & INTR__ECC_TRANSACTION_DONE)) + return -EIO; + + return max_bitflips; +} + +static void denali_setup_dma64(struct denali_controller *denali, + dma_addr_t dma_addr, int page, bool write) +{ + u32 mode; + const int page_count = 1; + + mode = DENALI_MAP10 | DENALI_BANK(denali) | page; + + /* DMA is a three step process */ + + /* + * 1. setup transfer type, interrupt when complete, + * burst len = 64 bytes, the number of pages + */ + denali->host_write(denali, mode, + 0x01002000 | (64 << 16) | + (write ? BIT(8) : 0) | page_count); + + /* 2. set memory low address */ + denali->host_write(denali, mode, lower_32_bits(dma_addr)); + + /* 3. set memory high address */ + denali->host_write(denali, mode, upper_32_bits(dma_addr)); +} + +static void denali_setup_dma32(struct denali_controller *denali, + dma_addr_t dma_addr, int page, bool write) +{ + u32 mode; + const int page_count = 1; + + mode = DENALI_MAP10 | DENALI_BANK(denali); + + /* DMA is a four step process */ + + /* 1. setup transfer type and # of pages */ + denali->host_write(denali, mode | page, + 0x2000 | (write ? BIT(8) : 0) | page_count); + + /* 2. set memory high address bits 23:8 */ + denali->host_write(denali, mode | ((dma_addr >> 16) << 8), 0x2200); + + /* 3. set memory low address bits 23:8 */ + denali->host_write(denali, mode | ((dma_addr & 0xffff) << 8), 0x2300); + + /* 4. interrupt when complete, burst len = 64 bytes */ + denali->host_write(denali, mode | 0x14000, 0x2400); +} + +static int denali_pio_read(struct denali_controller *denali, u32 *buf, + size_t size, int page) +{ + u32 addr = DENALI_MAP01 | DENALI_BANK(denali) | page; + u32 irq_status, ecc_err_mask; + int i; + + if (denali->caps & DENALI_CAP_HW_ECC_FIXUP) + ecc_err_mask = INTR__ECC_UNCOR_ERR; + else + ecc_err_mask = INTR__ECC_ERR; + + denali_reset_irq(denali); + + for (i = 0; i < size / 4; i++) + buf[i] = denali->host_read(denali, addr); + + irq_status = denali_wait_for_irq(denali, INTR__PAGE_XFER_INC); + if (!(irq_status & INTR__PAGE_XFER_INC)) + return -EIO; + + if (irq_status & INTR__ERASED_PAGE) + memset(buf, 0xff, size); + + return irq_status & ecc_err_mask ? -EBADMSG : 0; +} + +static int denali_pio_write(struct denali_controller *denali, const u32 *buf, + size_t size, int page) +{ + u32 addr = DENALI_MAP01 | DENALI_BANK(denali) | page; + u32 irq_status; + int i; + + denali_reset_irq(denali); + + for (i = 0; i < size / 4; i++) + denali->host_write(denali, addr, buf[i]); + + irq_status = denali_wait_for_irq(denali, + INTR__PROGRAM_COMP | + INTR__PROGRAM_FAIL); + if (!(irq_status & INTR__PROGRAM_COMP)) + return -EIO; + + return 0; +} + +static int denali_pio_xfer(struct denali_controller *denali, void *buf, + size_t size, int page, bool write) +{ + if (write) + return denali_pio_write(denali, buf, size, page); + else + return denali_pio_read(denali, buf, size, page); +} + +static int denali_dma_xfer(struct denali_controller *denali, void *buf, + size_t size, int page, bool write) +{ + dma_addr_t dma_addr; + u32 irq_mask, irq_status, ecc_err_mask; + enum dma_data_direction dir = write ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + int ret = 0; + + dma_addr = dma_map_single(denali->dev, buf, size, dir); + if (dma_mapping_error(denali->dev, dma_addr)) { + dev_dbg(denali->dev, "Failed to DMA-map buffer. Trying PIO.\n"); + return denali_pio_xfer(denali, buf, size, page, write); + } + + if (write) { + /* + * INTR__PROGRAM_COMP is never asserted for the DMA transfer. + * We can use INTR__DMA_CMD_COMP instead. This flag is asserted + * when the page program is completed. + */ + irq_mask = INTR__DMA_CMD_COMP | INTR__PROGRAM_FAIL; + ecc_err_mask = 0; + } else if (denali->caps & DENALI_CAP_HW_ECC_FIXUP) { + irq_mask = INTR__DMA_CMD_COMP; + ecc_err_mask = INTR__ECC_UNCOR_ERR; + } else { + irq_mask = INTR__DMA_CMD_COMP; + ecc_err_mask = INTR__ECC_ERR; + } + + iowrite32(DMA_ENABLE__FLAG, denali->reg + DMA_ENABLE); + /* + * The ->setup_dma() hook kicks DMA by using the data/command + * interface, which belongs to a different AXI port from the + * register interface. Read back the register to avoid a race. + */ + ioread32(denali->reg + DMA_ENABLE); + + denali_reset_irq(denali); + denali->setup_dma(denali, dma_addr, page, write); + + irq_status = denali_wait_for_irq(denali, irq_mask); + if (!(irq_status & INTR__DMA_CMD_COMP)) + ret = -EIO; + else if (irq_status & ecc_err_mask) + ret = -EBADMSG; + + iowrite32(0, denali->reg + DMA_ENABLE); + + dma_unmap_single(denali->dev, dma_addr, size, dir); + + if (irq_status & INTR__ERASED_PAGE) + memset(buf, 0xff, size); + + return ret; +} + +static int denali_page_xfer(struct nand_chip *chip, void *buf, size_t size, + int page, bool write) +{ + struct denali_controller *denali = to_denali_controller(chip); + + denali_select_target(chip, chip->cur_cs); + + if (denali->dma_avail) + return denali_dma_xfer(denali, buf, size, page, write); + else + return denali_pio_xfer(denali, buf, size, page, write); +} + +static int denali_read_page(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct denali_controller *denali = to_denali_controller(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned long uncor_ecc_flags = 0; + int stat = 0; + int ret; + + ret = denali_page_xfer(chip, buf, mtd->writesize, page, false); + if (ret && ret != -EBADMSG) + return ret; + + if (denali->caps & DENALI_CAP_HW_ECC_FIXUP) + stat = denali_hw_ecc_fixup(chip, &uncor_ecc_flags); + else if (ret == -EBADMSG) + stat = denali_sw_ecc_fixup(chip, &uncor_ecc_flags, buf); + + if (stat < 0) + return stat; + + if (uncor_ecc_flags) { + ret = denali_read_oob(chip, page); + if (ret) + return ret; + + stat = denali_check_erased_page(chip, buf, + uncor_ecc_flags, stat); + } + + return stat; +} + +static int denali_write_page(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + return denali_page_xfer(chip, (void *)buf, mtd->writesize, page, true); +} + +static int denali_setup_interface(struct nand_chip *chip, int chipnr, + const struct nand_interface_config *conf) +{ + static const unsigned int data_setup_on_host = 10000; + struct denali_controller *denali = to_denali_controller(chip); + struct denali_chip_sel *sel; + const struct nand_sdr_timings *timings; + unsigned long t_x, mult_x; + int acc_clks, re_2_we, re_2_re, we_2_re, addr_2_data; + int rdwr_en_lo, rdwr_en_hi, rdwr_en_lo_hi, cs_setup; + int addr_2_data_mask; + u32 tmp; + + timings = nand_get_sdr_timings(conf); + if (IS_ERR(timings)) + return PTR_ERR(timings); + + /* clk_x period in picoseconds */ + t_x = DIV_ROUND_DOWN_ULL(1000000000000ULL, denali->clk_x_rate); + if (!t_x) + return -EINVAL; + + /* + * The bus interface clock, clk_x, is phase aligned with the core clock. + * The clk_x is an integral multiple N of the core clk. The value N is + * configured at IP delivery time, and its available value is 4, 5, 6. + */ + mult_x = DIV_ROUND_CLOSEST_ULL(denali->clk_x_rate, denali->clk_rate); + if (mult_x < 4 || mult_x > 6) + return -EINVAL; + + if (chipnr == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + sel = &to_denali_chip(chip)->sels[chipnr]; + + /* tRWH -> RE_2_WE */ + re_2_we = DIV_ROUND_UP(timings->tRHW_min, t_x); + re_2_we = min_t(int, re_2_we, RE_2_WE__VALUE); + + tmp = ioread32(denali->reg + RE_2_WE); + tmp &= ~RE_2_WE__VALUE; + tmp |= FIELD_PREP(RE_2_WE__VALUE, re_2_we); + sel->re_2_we = tmp; + + /* tRHZ -> RE_2_RE */ + re_2_re = DIV_ROUND_UP(timings->tRHZ_max, t_x); + re_2_re = min_t(int, re_2_re, RE_2_RE__VALUE); + + tmp = ioread32(denali->reg + RE_2_RE); + tmp &= ~RE_2_RE__VALUE; + tmp |= FIELD_PREP(RE_2_RE__VALUE, re_2_re); + sel->re_2_re = tmp; + + /* + * tCCS, tWHR -> WE_2_RE + * + * With WE_2_RE properly set, the Denali controller automatically takes + * care of the delay; the driver need not set NAND_WAIT_TCCS. + */ + we_2_re = DIV_ROUND_UP(max(timings->tCCS_min, timings->tWHR_min), t_x); + we_2_re = min_t(int, we_2_re, TWHR2_AND_WE_2_RE__WE_2_RE); + + tmp = ioread32(denali->reg + TWHR2_AND_WE_2_RE); + tmp &= ~TWHR2_AND_WE_2_RE__WE_2_RE; + tmp |= FIELD_PREP(TWHR2_AND_WE_2_RE__WE_2_RE, we_2_re); + sel->hwhr2_and_we_2_re = tmp; + + /* tADL -> ADDR_2_DATA */ + + /* for older versions, ADDR_2_DATA is only 6 bit wide */ + addr_2_data_mask = TCWAW_AND_ADDR_2_DATA__ADDR_2_DATA; + if (denali->revision < 0x0501) + addr_2_data_mask >>= 1; + + addr_2_data = DIV_ROUND_UP(timings->tADL_min, t_x); + addr_2_data = min_t(int, addr_2_data, addr_2_data_mask); + + tmp = ioread32(denali->reg + TCWAW_AND_ADDR_2_DATA); + tmp &= ~TCWAW_AND_ADDR_2_DATA__ADDR_2_DATA; + tmp |= FIELD_PREP(TCWAW_AND_ADDR_2_DATA__ADDR_2_DATA, addr_2_data); + sel->tcwaw_and_addr_2_data = tmp; + + /* tREH, tWH -> RDWR_EN_HI_CNT */ + rdwr_en_hi = DIV_ROUND_UP(max(timings->tREH_min, timings->tWH_min), + t_x); + rdwr_en_hi = min_t(int, rdwr_en_hi, RDWR_EN_HI_CNT__VALUE); + + tmp = ioread32(denali->reg + RDWR_EN_HI_CNT); + tmp &= ~RDWR_EN_HI_CNT__VALUE; + tmp |= FIELD_PREP(RDWR_EN_HI_CNT__VALUE, rdwr_en_hi); + sel->rdwr_en_hi_cnt = tmp; + + /* + * tREA -> ACC_CLKS + * tRP, tWP, tRHOH, tRC, tWC -> RDWR_EN_LO_CNT + */ + + /* + * Determine the minimum of acc_clks to meet the setup timing when + * capturing the incoming data. + * + * The delay on the chip side is well-defined as tREA, but we need to + * take additional delay into account. This includes a certain degree + * of unknowledge, such as signal propagation delays on the PCB and + * in the SoC, load capacity of the I/O pins, etc. + */ + acc_clks = DIV_ROUND_UP(timings->tREA_max + data_setup_on_host, t_x); + + /* Determine the minimum of rdwr_en_lo_cnt from RE#/WE# pulse width */ + rdwr_en_lo = DIV_ROUND_UP(max(timings->tRP_min, timings->tWP_min), t_x); + + /* Extend rdwr_en_lo to meet the data hold timing */ + rdwr_en_lo = max_t(int, rdwr_en_lo, + acc_clks - timings->tRHOH_min / t_x); + + /* Extend rdwr_en_lo to meet the requirement for RE#/WE# cycle time */ + rdwr_en_lo_hi = DIV_ROUND_UP(max(timings->tRC_min, timings->tWC_min), + t_x); + rdwr_en_lo = max(rdwr_en_lo, rdwr_en_lo_hi - rdwr_en_hi); + rdwr_en_lo = min_t(int, rdwr_en_lo, RDWR_EN_LO_CNT__VALUE); + + /* Center the data latch timing for extra safety */ + acc_clks = (acc_clks + rdwr_en_lo + + DIV_ROUND_UP(timings->tRHOH_min, t_x)) / 2; + acc_clks = min_t(int, acc_clks, ACC_CLKS__VALUE); + + tmp = ioread32(denali->reg + ACC_CLKS); + tmp &= ~ACC_CLKS__VALUE; + tmp |= FIELD_PREP(ACC_CLKS__VALUE, acc_clks); + sel->acc_clks = tmp; + + tmp = ioread32(denali->reg + RDWR_EN_LO_CNT); + tmp &= ~RDWR_EN_LO_CNT__VALUE; + tmp |= FIELD_PREP(RDWR_EN_LO_CNT__VALUE, rdwr_en_lo); + sel->rdwr_en_lo_cnt = tmp; + + /* tCS, tCEA -> CS_SETUP_CNT */ + cs_setup = max3((int)DIV_ROUND_UP(timings->tCS_min, t_x) - rdwr_en_lo, + (int)DIV_ROUND_UP(timings->tCEA_max, t_x) - acc_clks, + 0); + cs_setup = min_t(int, cs_setup, CS_SETUP_CNT__VALUE); + + tmp = ioread32(denali->reg + CS_SETUP_CNT); + tmp &= ~CS_SETUP_CNT__VALUE; + tmp |= FIELD_PREP(CS_SETUP_CNT__VALUE, cs_setup); + sel->cs_setup_cnt = tmp; + + return 0; +} + +int denali_calc_ecc_bytes(int step_size, int strength) +{ + /* BCH code. Denali requires ecc.bytes to be multiple of 2 */ + return DIV_ROUND_UP(strength * fls(step_size * 8), 16) * 2; +} +EXPORT_SYMBOL(denali_calc_ecc_bytes); + +static int denali_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct denali_controller *denali = to_denali_controller(chip); + + if (section > 0) + return -ERANGE; + + oobregion->offset = denali->oob_skip_bytes; + oobregion->length = chip->ecc.total; + + return 0; +} + +static int denali_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct denali_controller *denali = to_denali_controller(chip); + + if (section > 0) + return -ERANGE; + + oobregion->offset = chip->ecc.total + denali->oob_skip_bytes; + oobregion->length = mtd->oobsize - oobregion->offset; + + return 0; +} + +static const struct mtd_ooblayout_ops denali_ooblayout_ops = { + .ecc = denali_ooblayout_ecc, + .free = denali_ooblayout_free, +}; + +static int denali_multidev_fixup(struct nand_chip *chip) +{ + struct denali_controller *denali = to_denali_controller(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_memory_organization *memorg; + + memorg = nanddev_get_memorg(&chip->base); + + /* + * Support for multi device: + * When the IP configuration is x16 capable and two x8 chips are + * connected in parallel, DEVICES_CONNECTED should be set to 2. + * In this case, the core framework knows nothing about this fact, + * so we should tell it the _logical_ pagesize and anything necessary. + */ + denali->devs_per_cs = ioread32(denali->reg + DEVICES_CONNECTED); + + /* + * On some SoCs, DEVICES_CONNECTED is not auto-detected. + * For those, DEVICES_CONNECTED is left to 0. Set 1 if it is the case. + */ + if (denali->devs_per_cs == 0) { + denali->devs_per_cs = 1; + iowrite32(1, denali->reg + DEVICES_CONNECTED); + } + + if (denali->devs_per_cs == 1) + return 0; + + if (denali->devs_per_cs != 2) { + dev_err(denali->dev, "unsupported number of devices %d\n", + denali->devs_per_cs); + return -EINVAL; + } + + /* 2 chips in parallel */ + memorg->pagesize <<= 1; + memorg->oobsize <<= 1; + mtd->size <<= 1; + mtd->erasesize <<= 1; + mtd->writesize <<= 1; + mtd->oobsize <<= 1; + chip->page_shift += 1; + chip->phys_erase_shift += 1; + chip->bbt_erase_shift += 1; + chip->chip_shift += 1; + chip->pagemask <<= 1; + chip->ecc.size <<= 1; + chip->ecc.bytes <<= 1; + chip->ecc.strength <<= 1; + denali->oob_skip_bytes <<= 1; + + return 0; +} + +static int denali_attach_chip(struct nand_chip *chip) +{ + struct denali_controller *denali = to_denali_controller(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + ret = nand_ecc_choose_conf(chip, denali->ecc_caps, + mtd->oobsize - denali->oob_skip_bytes); + if (ret) { + dev_err(denali->dev, "Failed to setup ECC settings.\n"); + return ret; + } + + dev_dbg(denali->dev, + "chosen ECC settings: step=%d, strength=%d, bytes=%d\n", + chip->ecc.size, chip->ecc.strength, chip->ecc.bytes); + + ret = denali_multidev_fixup(chip); + if (ret) + return ret; + + return 0; +} + +static void denali_exec_in8(struct denali_controller *denali, u32 type, + u8 *buf, unsigned int len) +{ + int i; + + for (i = 0; i < len; i++) + buf[i] = denali->host_read(denali, type | DENALI_BANK(denali)); +} + +static void denali_exec_in16(struct denali_controller *denali, u32 type, + u8 *buf, unsigned int len) +{ + u32 data; + int i; + + for (i = 0; i < len; i += 2) { + data = denali->host_read(denali, type | DENALI_BANK(denali)); + /* bit 31:24 and 15:8 are used for DDR */ + buf[i] = data; + buf[i + 1] = data >> 16; + } +} + +static void denali_exec_in(struct denali_controller *denali, u32 type, + u8 *buf, unsigned int len, bool width16) +{ + if (width16) + denali_exec_in16(denali, type, buf, len); + else + denali_exec_in8(denali, type, buf, len); +} + +static void denali_exec_out8(struct denali_controller *denali, u32 type, + const u8 *buf, unsigned int len) +{ + int i; + + for (i = 0; i < len; i++) + denali->host_write(denali, type | DENALI_BANK(denali), buf[i]); +} + +static void denali_exec_out16(struct denali_controller *denali, u32 type, + const u8 *buf, unsigned int len) +{ + int i; + + for (i = 0; i < len; i += 2) + denali->host_write(denali, type | DENALI_BANK(denali), + buf[i + 1] << 16 | buf[i]); +} + +static void denali_exec_out(struct denali_controller *denali, u32 type, + const u8 *buf, unsigned int len, bool width16) +{ + if (width16) + denali_exec_out16(denali, type, buf, len); + else + denali_exec_out8(denali, type, buf, len); +} + +static int denali_exec_waitrdy(struct denali_controller *denali) +{ + u32 irq_stat; + + /* R/B# pin transitioned from low to high? */ + irq_stat = denali_wait_for_irq(denali, INTR__INT_ACT); + + /* Just in case nand_operation has multiple NAND_OP_WAITRDY_INSTR. */ + denali_reset_irq(denali); + + return irq_stat & INTR__INT_ACT ? 0 : -EIO; +} + +static int denali_exec_instr(struct nand_chip *chip, + const struct nand_op_instr *instr) +{ + struct denali_controller *denali = to_denali_controller(chip); + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + denali_exec_out8(denali, DENALI_MAP11_CMD, + &instr->ctx.cmd.opcode, 1); + return 0; + case NAND_OP_ADDR_INSTR: + denali_exec_out8(denali, DENALI_MAP11_ADDR, + instr->ctx.addr.addrs, + instr->ctx.addr.naddrs); + return 0; + case NAND_OP_DATA_IN_INSTR: + denali_exec_in(denali, DENALI_MAP11_DATA, + instr->ctx.data.buf.in, + instr->ctx.data.len, + !instr->ctx.data.force_8bit && + chip->options & NAND_BUSWIDTH_16); + return 0; + case NAND_OP_DATA_OUT_INSTR: + denali_exec_out(denali, DENALI_MAP11_DATA, + instr->ctx.data.buf.out, + instr->ctx.data.len, + !instr->ctx.data.force_8bit && + chip->options & NAND_BUSWIDTH_16); + return 0; + case NAND_OP_WAITRDY_INSTR: + return denali_exec_waitrdy(denali); + default: + WARN_ONCE(1, "unsupported NAND instruction type: %d\n", + instr->type); + + return -EINVAL; + } +} + +static int denali_exec_op(struct nand_chip *chip, + const struct nand_operation *op, bool check_only) +{ + int i, ret; + + if (check_only) + return 0; + + denali_select_target(chip, op->cs); + + /* + * Some commands contain NAND_OP_WAITRDY_INSTR. + * irq must be cleared here to catch the R/B# interrupt there. + */ + denali_reset_irq(to_denali_controller(chip)); + + for (i = 0; i < op->ninstrs; i++) { + ret = denali_exec_instr(chip, &op->instrs[i]); + if (ret) + return ret; + } + + return 0; +} + +static const struct nand_controller_ops denali_controller_ops = { + .attach_chip = denali_attach_chip, + .exec_op = denali_exec_op, + .setup_interface = denali_setup_interface, +}; + +int denali_chip_init(struct denali_controller *denali, + struct denali_chip *dchip) +{ + struct nand_chip *chip = &dchip->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + struct denali_chip *dchip2; + int i, j, ret; + + chip->controller = &denali->controller; + + /* sanity checks for bank numbers */ + for (i = 0; i < dchip->nsels; i++) { + unsigned int bank = dchip->sels[i].bank; + + if (bank >= denali->nbanks) { + dev_err(denali->dev, "unsupported bank %d\n", bank); + return -EINVAL; + } + + for (j = 0; j < i; j++) { + if (bank == dchip->sels[j].bank) { + dev_err(denali->dev, + "bank %d is assigned twice in the same chip\n", + bank); + return -EINVAL; + } + } + + list_for_each_entry(dchip2, &denali->chips, node) { + for (j = 0; j < dchip2->nsels; j++) { + if (bank == dchip2->sels[j].bank) { + dev_err(denali->dev, + "bank %d is already used\n", + bank); + return -EINVAL; + } + } + } + } + + mtd->dev.parent = denali->dev; + + /* + * Fallback to the default name if DT did not give "label" property. + * Use "label" property if multiple chips are connected. + */ + if (!mtd->name && list_empty(&denali->chips)) + mtd->name = "denali-nand"; + + if (denali->dma_avail) { + chip->options |= NAND_USES_DMA; + chip->buf_align = 16; + } + + /* clk rate info is needed for setup_interface */ + if (!denali->clk_rate || !denali->clk_x_rate) + chip->options |= NAND_KEEP_TIMINGS; + + chip->bbt_options |= NAND_BBT_USE_FLASH; + chip->bbt_options |= NAND_BBT_NO_OOB; + chip->options |= NAND_NO_SUBPAGE_WRITE; + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + chip->ecc.placement = NAND_ECC_PLACEMENT_INTERLEAVED; + chip->ecc.read_page = denali_read_page; + chip->ecc.write_page = denali_write_page; + chip->ecc.read_page_raw = denali_read_page_raw; + chip->ecc.write_page_raw = denali_write_page_raw; + chip->ecc.read_oob = denali_read_oob; + chip->ecc.write_oob = denali_write_oob; + + mtd_set_ooblayout(mtd, &denali_ooblayout_ops); + + ret = nand_scan(chip, dchip->nsels); + if (ret) + return ret; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(denali->dev, "Failed to register MTD: %d\n", ret); + goto cleanup_nand; + } + + list_add_tail(&dchip->node, &denali->chips); + + return 0; + +cleanup_nand: + nand_cleanup(chip); + + return ret; +} +EXPORT_SYMBOL_GPL(denali_chip_init); + +int denali_init(struct denali_controller *denali) +{ + u32 features = ioread32(denali->reg + FEATURES); + int ret; + + nand_controller_init(&denali->controller); + denali->controller.ops = &denali_controller_ops; + init_completion(&denali->complete); + spin_lock_init(&denali->irq_lock); + INIT_LIST_HEAD(&denali->chips); + denali->active_bank = DENALI_INVALID_BANK; + + /* + * The REVISION register may not be reliable. Platforms are allowed to + * override it. + */ + if (!denali->revision) + denali->revision = swab16(ioread32(denali->reg + REVISION)); + + denali->nbanks = 1 << FIELD_GET(FEATURES__N_BANKS, features); + + /* the encoding changed from rev 5.0 to 5.1 */ + if (denali->revision < 0x0501) + denali->nbanks <<= 1; + + if (features & FEATURES__DMA) + denali->dma_avail = true; + + if (denali->dma_avail) { + int dma_bit = denali->caps & DENALI_CAP_DMA_64BIT ? 64 : 32; + + ret = dma_set_mask(denali->dev, DMA_BIT_MASK(dma_bit)); + if (ret) { + dev_info(denali->dev, + "Failed to set DMA mask. Disabling DMA.\n"); + denali->dma_avail = false; + } + } + + if (denali->dma_avail) { + if (denali->caps & DENALI_CAP_DMA_64BIT) + denali->setup_dma = denali_setup_dma64; + else + denali->setup_dma = denali_setup_dma32; + } + + if (features & FEATURES__INDEX_ADDR) { + denali->host_read = denali_indexed_read; + denali->host_write = denali_indexed_write; + } else { + denali->host_read = denali_direct_read; + denali->host_write = denali_direct_write; + } + + /* + * Set how many bytes should be skipped before writing data in OOB. + * If a platform requests a non-zero value, set it to the register. + * Otherwise, read the value out, expecting it has already been set up + * by firmware. + */ + if (denali->oob_skip_bytes) + iowrite32(denali->oob_skip_bytes, + denali->reg + SPARE_AREA_SKIP_BYTES); + else + denali->oob_skip_bytes = ioread32(denali->reg + + SPARE_AREA_SKIP_BYTES); + + iowrite32(0, denali->reg + TRANSFER_SPARE_REG); + iowrite32(GENMASK(denali->nbanks - 1, 0), denali->reg + RB_PIN_ENABLED); + iowrite32(CHIP_EN_DONT_CARE__FLAG, denali->reg + CHIP_ENABLE_DONT_CARE); + iowrite32(ECC_ENABLE__FLAG, denali->reg + ECC_ENABLE); + iowrite32(0xffff, denali->reg + SPARE_AREA_MARKER); + iowrite32(WRITE_PROTECT__FLAG, denali->reg + WRITE_PROTECT); + + denali_clear_irq_all(denali); + + ret = devm_request_irq(denali->dev, denali->irq, denali_isr, + IRQF_SHARED, DENALI_NAND_NAME, denali); + if (ret) { + dev_err(denali->dev, "Unable to request IRQ\n"); + return ret; + } + + denali_enable_irq(denali); + + return 0; +} +EXPORT_SYMBOL(denali_init); + +void denali_remove(struct denali_controller *denali) +{ + struct denali_chip *dchip, *tmp; + struct nand_chip *chip; + int ret; + + list_for_each_entry_safe(dchip, tmp, &denali->chips, node) { + chip = &dchip->chip; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + list_del(&dchip->node); + } + + denali_disable_irq(denali); +} +EXPORT_SYMBOL(denali_remove); + +MODULE_DESCRIPTION("Driver core for Denali NAND controller"); +MODULE_AUTHOR("Intel Corporation and its suppliers"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/raw/denali.h b/drivers/mtd/nand/raw/denali.h new file mode 100644 index 000000000..5f2fab022 --- /dev/null +++ b/drivers/mtd/nand/raw/denali.h @@ -0,0 +1,398 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * NAND Flash Controller Device Driver + * Copyright (c) 2009 - 2010, Intel Corporation and its suppliers. + */ + +#ifndef __DENALI_H__ +#define __DENALI_H__ + +#include +#include +#include +#include +#include +#include + +#define DEVICE_RESET 0x0 +#define DEVICE_RESET__BANK(bank) BIT(bank) + +#define TRANSFER_SPARE_REG 0x10 +#define TRANSFER_SPARE_REG__FLAG BIT(0) + +#define LOAD_WAIT_CNT 0x20 +#define LOAD_WAIT_CNT__VALUE GENMASK(15, 0) + +#define PROGRAM_WAIT_CNT 0x30 +#define PROGRAM_WAIT_CNT__VALUE GENMASK(15, 0) + +#define ERASE_WAIT_CNT 0x40 +#define ERASE_WAIT_CNT__VALUE GENMASK(15, 0) + +#define INT_MON_CYCCNT 0x50 +#define INT_MON_CYCCNT__VALUE GENMASK(15, 0) + +#define RB_PIN_ENABLED 0x60 +#define RB_PIN_ENABLED__BANK(bank) BIT(bank) + +#define MULTIPLANE_OPERATION 0x70 +#define MULTIPLANE_OPERATION__FLAG BIT(0) + +#define MULTIPLANE_READ_ENABLE 0x80 +#define MULTIPLANE_READ_ENABLE__FLAG BIT(0) + +#define COPYBACK_DISABLE 0x90 +#define COPYBACK_DISABLE__FLAG BIT(0) + +#define CACHE_WRITE_ENABLE 0xa0 +#define CACHE_WRITE_ENABLE__FLAG BIT(0) + +#define CACHE_READ_ENABLE 0xb0 +#define CACHE_READ_ENABLE__FLAG BIT(0) + +#define PREFETCH_MODE 0xc0 +#define PREFETCH_MODE__PREFETCH_EN BIT(0) +#define PREFETCH_MODE__PREFETCH_BURST_LENGTH GENMASK(15, 4) + +#define CHIP_ENABLE_DONT_CARE 0xd0 +#define CHIP_EN_DONT_CARE__FLAG BIT(0) + +#define ECC_ENABLE 0xe0 +#define ECC_ENABLE__FLAG BIT(0) + +#define GLOBAL_INT_ENABLE 0xf0 +#define GLOBAL_INT_EN_FLAG BIT(0) + +#define TWHR2_AND_WE_2_RE 0x100 +#define TWHR2_AND_WE_2_RE__WE_2_RE GENMASK(5, 0) +#define TWHR2_AND_WE_2_RE__TWHR2 GENMASK(13, 8) + +#define TCWAW_AND_ADDR_2_DATA 0x110 +/* The width of ADDR_2_DATA is 6 bit for old IP, 7 bit for new IP */ +#define TCWAW_AND_ADDR_2_DATA__ADDR_2_DATA GENMASK(6, 0) +#define TCWAW_AND_ADDR_2_DATA__TCWAW GENMASK(13, 8) + +#define RE_2_WE 0x120 +#define RE_2_WE__VALUE GENMASK(5, 0) + +#define ACC_CLKS 0x130 +#define ACC_CLKS__VALUE GENMASK(3, 0) + +#define NUMBER_OF_PLANES 0x140 +#define NUMBER_OF_PLANES__VALUE GENMASK(2, 0) + +#define PAGES_PER_BLOCK 0x150 +#define PAGES_PER_BLOCK__VALUE GENMASK(15, 0) + +#define DEVICE_WIDTH 0x160 +#define DEVICE_WIDTH__VALUE GENMASK(1, 0) + +#define DEVICE_MAIN_AREA_SIZE 0x170 +#define DEVICE_MAIN_AREA_SIZE__VALUE GENMASK(15, 0) + +#define DEVICE_SPARE_AREA_SIZE 0x180 +#define DEVICE_SPARE_AREA_SIZE__VALUE GENMASK(15, 0) + +#define TWO_ROW_ADDR_CYCLES 0x190 +#define TWO_ROW_ADDR_CYCLES__FLAG BIT(0) + +#define MULTIPLANE_ADDR_RESTRICT 0x1a0 +#define MULTIPLANE_ADDR_RESTRICT__FLAG BIT(0) + +#define ECC_CORRECTION 0x1b0 +#define ECC_CORRECTION__VALUE GENMASK(4, 0) +#define ECC_CORRECTION__ERASE_THRESHOLD GENMASK(31, 16) + +#define READ_MODE 0x1c0 +#define READ_MODE__VALUE GENMASK(3, 0) + +#define WRITE_MODE 0x1d0 +#define WRITE_MODE__VALUE GENMASK(3, 0) + +#define COPYBACK_MODE 0x1e0 +#define COPYBACK_MODE__VALUE GENMASK(3, 0) + +#define RDWR_EN_LO_CNT 0x1f0 +#define RDWR_EN_LO_CNT__VALUE GENMASK(4, 0) + +#define RDWR_EN_HI_CNT 0x200 +#define RDWR_EN_HI_CNT__VALUE GENMASK(4, 0) + +#define MAX_RD_DELAY 0x210 +#define MAX_RD_DELAY__VALUE GENMASK(3, 0) + +#define CS_SETUP_CNT 0x220 +#define CS_SETUP_CNT__VALUE GENMASK(4, 0) +#define CS_SETUP_CNT__TWB GENMASK(17, 12) + +#define SPARE_AREA_SKIP_BYTES 0x230 +#define SPARE_AREA_SKIP_BYTES__VALUE GENMASK(5, 0) + +#define SPARE_AREA_MARKER 0x240 +#define SPARE_AREA_MARKER__VALUE GENMASK(15, 0) + +#define DEVICES_CONNECTED 0x250 +#define DEVICES_CONNECTED__VALUE GENMASK(2, 0) + +#define DIE_MASK 0x260 +#define DIE_MASK__VALUE GENMASK(7, 0) + +#define FIRST_BLOCK_OF_NEXT_PLANE 0x270 +#define FIRST_BLOCK_OF_NEXT_PLANE__VALUE GENMASK(15, 0) + +#define WRITE_PROTECT 0x280 +#define WRITE_PROTECT__FLAG BIT(0) + +#define RE_2_RE 0x290 +#define RE_2_RE__VALUE GENMASK(5, 0) + +#define MANUFACTURER_ID 0x300 +#define MANUFACTURER_ID__VALUE GENMASK(7, 0) + +#define DEVICE_ID 0x310 +#define DEVICE_ID__VALUE GENMASK(7, 0) + +#define DEVICE_PARAM_0 0x320 +#define DEVICE_PARAM_0__VALUE GENMASK(7, 0) + +#define DEVICE_PARAM_1 0x330 +#define DEVICE_PARAM_1__VALUE GENMASK(7, 0) + +#define DEVICE_PARAM_2 0x340 +#define DEVICE_PARAM_2__VALUE GENMASK(7, 0) + +#define LOGICAL_PAGE_DATA_SIZE 0x350 +#define LOGICAL_PAGE_DATA_SIZE__VALUE GENMASK(15, 0) + +#define LOGICAL_PAGE_SPARE_SIZE 0x360 +#define LOGICAL_PAGE_SPARE_SIZE__VALUE GENMASK(15, 0) + +#define REVISION 0x370 +#define REVISION__VALUE GENMASK(15, 0) + +#define ONFI_DEVICE_FEATURES 0x380 +#define ONFI_DEVICE_FEATURES__VALUE GENMASK(5, 0) + +#define ONFI_OPTIONAL_COMMANDS 0x390 +#define ONFI_OPTIONAL_COMMANDS__VALUE GENMASK(5, 0) + +#define ONFI_TIMING_MODE 0x3a0 +#define ONFI_TIMING_MODE__VALUE GENMASK(5, 0) + +#define ONFI_PGM_CACHE_TIMING_MODE 0x3b0 +#define ONFI_PGM_CACHE_TIMING_MODE__VALUE GENMASK(5, 0) + +#define ONFI_DEVICE_NO_OF_LUNS 0x3c0 +#define ONFI_DEVICE_NO_OF_LUNS__NO_OF_LUNS GENMASK(7, 0) +#define ONFI_DEVICE_NO_OF_LUNS__ONFI_DEVICE BIT(8) + +#define ONFI_DEVICE_NO_OF_BLOCKS_PER_LUN_L 0x3d0 +#define ONFI_DEVICE_NO_OF_BLOCKS_PER_LUN_L__VALUE GENMASK(15, 0) + +#define ONFI_DEVICE_NO_OF_BLOCKS_PER_LUN_U 0x3e0 +#define ONFI_DEVICE_NO_OF_BLOCKS_PER_LUN_U__VALUE GENMASK(15, 0) + +#define FEATURES 0x3f0 +#define FEATURES__N_BANKS GENMASK(1, 0) +#define FEATURES__ECC_MAX_ERR GENMASK(5, 2) +#define FEATURES__DMA BIT(6) +#define FEATURES__CMD_DMA BIT(7) +#define FEATURES__PARTITION BIT(8) +#define FEATURES__XDMA_SIDEBAND BIT(9) +#define FEATURES__GPREG BIT(10) +#define FEATURES__INDEX_ADDR BIT(11) + +#define TRANSFER_MODE 0x400 +#define TRANSFER_MODE__VALUE GENMASK(1, 0) + +#define INTR_STATUS(bank) (0x410 + (bank) * 0x50) +#define INTR_EN(bank) (0x420 + (bank) * 0x50) +/* bit[1:0] is used differently depending on IP version */ +#define INTR__ECC_UNCOR_ERR BIT(0) /* new IP */ +#define INTR__ECC_TRANSACTION_DONE BIT(0) /* old IP */ +#define INTR__ECC_ERR BIT(1) /* old IP */ +#define INTR__DMA_CMD_COMP BIT(2) +#define INTR__TIME_OUT BIT(3) +#define INTR__PROGRAM_FAIL BIT(4) +#define INTR__ERASE_FAIL BIT(5) +#define INTR__LOAD_COMP BIT(6) +#define INTR__PROGRAM_COMP BIT(7) +#define INTR__ERASE_COMP BIT(8) +#define INTR__PIPE_CPYBCK_CMD_COMP BIT(9) +#define INTR__LOCKED_BLK BIT(10) +#define INTR__UNSUP_CMD BIT(11) +#define INTR__INT_ACT BIT(12) +#define INTR__RST_COMP BIT(13) +#define INTR__PIPE_CMD_ERR BIT(14) +#define INTR__PAGE_XFER_INC BIT(15) +#define INTR__ERASED_PAGE BIT(16) + +#define PAGE_CNT(bank) (0x430 + (bank) * 0x50) +#define ERR_PAGE_ADDR(bank) (0x440 + (bank) * 0x50) +#define ERR_BLOCK_ADDR(bank) (0x450 + (bank) * 0x50) + +#define ECC_THRESHOLD 0x600 +#define ECC_THRESHOLD__VALUE GENMASK(9, 0) + +#define ECC_ERROR_BLOCK_ADDRESS 0x610 +#define ECC_ERROR_BLOCK_ADDRESS__VALUE GENMASK(15, 0) + +#define ECC_ERROR_PAGE_ADDRESS 0x620 +#define ECC_ERROR_PAGE_ADDRESS__VALUE GENMASK(11, 0) +#define ECC_ERROR_PAGE_ADDRESS__BANK GENMASK(15, 12) + +#define ECC_ERROR_ADDRESS 0x630 +#define ECC_ERROR_ADDRESS__OFFSET GENMASK(11, 0) +#define ECC_ERROR_ADDRESS__SECTOR GENMASK(15, 12) + +#define ERR_CORRECTION_INFO 0x640 +#define ERR_CORRECTION_INFO__BYTE GENMASK(7, 0) +#define ERR_CORRECTION_INFO__DEVICE GENMASK(11, 8) +#define ERR_CORRECTION_INFO__UNCOR BIT(14) +#define ERR_CORRECTION_INFO__LAST_ERR BIT(15) + +#define ECC_COR_INFO(bank) (0x650 + (bank) / 2 * 0x10) +#define ECC_COR_INFO__SHIFT(bank) ((bank) % 2 * 8) +#define ECC_COR_INFO__MAX_ERRORS GENMASK(6, 0) +#define ECC_COR_INFO__UNCOR_ERR BIT(7) + +#define CFG_DATA_BLOCK_SIZE 0x6b0 + +#define CFG_LAST_DATA_BLOCK_SIZE 0x6c0 + +#define CFG_NUM_DATA_BLOCKS 0x6d0 + +#define CFG_META_DATA_SIZE 0x6e0 + +#define DMA_ENABLE 0x700 +#define DMA_ENABLE__FLAG BIT(0) + +#define IGNORE_ECC_DONE 0x710 +#define IGNORE_ECC_DONE__FLAG BIT(0) + +#define DMA_INTR 0x720 +#define DMA_INTR_EN 0x730 +#define DMA_INTR__TARGET_ERROR BIT(0) +#define DMA_INTR__DESC_COMP_CHANNEL0 BIT(1) +#define DMA_INTR__DESC_COMP_CHANNEL1 BIT(2) +#define DMA_INTR__DESC_COMP_CHANNEL2 BIT(3) +#define DMA_INTR__DESC_COMP_CHANNEL3 BIT(4) +#define DMA_INTR__MEMCOPY_DESC_COMP BIT(5) + +#define TARGET_ERR_ADDR_LO 0x740 +#define TARGET_ERR_ADDR_LO__VALUE GENMASK(15, 0) + +#define TARGET_ERR_ADDR_HI 0x750 +#define TARGET_ERR_ADDR_HI__VALUE GENMASK(15, 0) + +#define CHNL_ACTIVE 0x760 +#define CHNL_ACTIVE__CHANNEL0 BIT(0) +#define CHNL_ACTIVE__CHANNEL1 BIT(1) +#define CHNL_ACTIVE__CHANNEL2 BIT(2) +#define CHNL_ACTIVE__CHANNEL3 BIT(3) + +/** + * struct denali_chip_sel - per-CS data of Denali NAND + * + * @bank: bank id of the controller this CS is connected to + * @hwhr2_and_we_2_re: value of timing register HWHR2_AND_WE_2_RE + * @tcwaw_and_addr_2_data: value of timing register TCWAW_AND_ADDR_2_DATA + * @re_2_we: value of timing register RE_2_WE + * @acc_clks: value of timing register ACC_CLKS + * @rdwr_en_lo_cnt: value of timing register RDWR_EN_LO_CNT + * @rdwr_en_hi_cnt: value of timing register RDWR_EN_HI_CNT + * @cs_setup_cnt: value of timing register CS_SETUP_CNT + * @re_2_re: value of timing register RE_2_RE + */ +struct denali_chip_sel { + int bank; + u32 hwhr2_and_we_2_re; + u32 tcwaw_and_addr_2_data; + u32 re_2_we; + u32 acc_clks; + u32 rdwr_en_lo_cnt; + u32 rdwr_en_hi_cnt; + u32 cs_setup_cnt; + u32 re_2_re; +}; + +/** + * struct denali_chip - per-chip data of Denali NAND + * + * @chip: base NAND chip structure + * @node: node to be used to associate this chip with the controller + * @nsels: the number of CS lines of this chip + * @sels: the array of per-cs data + */ +struct denali_chip { + struct nand_chip chip; + struct list_head node; + unsigned int nsels; + struct denali_chip_sel sels[] __counted_by(nsels); +}; + +/** + * struct denali_controller - Denali NAND controller data + * + * @controller: base NAND controller structure + * @dev: device + * @chips: the list of chips attached to this controller + * @clk_rate: frequency of core clock + * @clk_x_rate: frequency of bus interface clock + * @reg: base of Register Interface + * @host: base of Host Data/Command interface + * @complete: completion used to wait for interrupts + * @irq: interrupt number + * @irq_mask: interrupt bits the controller is waiting for + * @irq_status: interrupt bits of events that have happened + * @irq_lock: lock to protect @irq_mask and @irq_status + * @dma_avail: set if DMA engine is available + * @devs_per_cs: number of devices connected in parallel + * @oob_skip_bytes: number of bytes in OOB skipped by the ECC engine + * @active_bank: active bank id + * @nbanks: the number of banks supported by this controller + * @revision: IP revision + * @caps: controller capabilities that cannot be detected run-time + * @ecc_caps: ECC engine capabilities + * @host_read: callback for read access of Host Data/Command Interface + * @host_write: callback for write access of Host Data/Command Interface + * @setup_dma: callback for setup of the Data DMA + */ +struct denali_controller { + struct nand_controller controller; + struct device *dev; + struct list_head chips; + unsigned long clk_rate; + unsigned long clk_x_rate; + void __iomem *reg; + void __iomem *host; + struct completion complete; + int irq; + u32 irq_mask; + u32 irq_status; + spinlock_t irq_lock; + bool dma_avail; + int devs_per_cs; + int oob_skip_bytes; + int active_bank; + int nbanks; + unsigned int revision; + unsigned int caps; + const struct nand_ecc_caps *ecc_caps; + u32 (*host_read)(struct denali_controller *denali, u32 addr); + void (*host_write)(struct denali_controller *denali, u32 addr, + u32 data); + void (*setup_dma)(struct denali_controller *denali, dma_addr_t dma_addr, + int page, bool write); +}; + +#define DENALI_CAP_HW_ECC_FIXUP BIT(0) +#define DENALI_CAP_DMA_64BIT BIT(1) + +int denali_calc_ecc_bytes(int step_size, int strength); +int denali_chip_init(struct denali_controller *denali, + struct denali_chip *dchip); +int denali_init(struct denali_controller *denali); +void denali_remove(struct denali_controller *denali); + +#endif /* __DENALI_H__ */ diff --git a/drivers/mtd/nand/raw/denali_dt.c b/drivers/mtd/nand/raw/denali_dt.c new file mode 100644 index 000000000..8c822eae7 --- /dev/null +++ b/drivers/mtd/nand/raw/denali_dt.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NAND Flash Controller Device Driver for DT + * + * Copyright © 2011, Picochip. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "denali.h" + +struct denali_dt { + struct denali_controller controller; + struct clk *clk; /* core clock */ + struct clk *clk_x; /* bus interface clock */ + struct clk *clk_ecc; /* ECC circuit clock */ + struct reset_control *rst; /* core reset */ + struct reset_control *rst_reg; /* register reset */ +}; + +struct denali_dt_data { + unsigned int revision; + unsigned int caps; + unsigned int oob_skip_bytes; + const struct nand_ecc_caps *ecc_caps; +}; + +NAND_ECC_CAPS_SINGLE(denali_socfpga_ecc_caps, denali_calc_ecc_bytes, + 512, 8, 15); +static const struct denali_dt_data denali_socfpga_data = { + .caps = DENALI_CAP_HW_ECC_FIXUP, + .oob_skip_bytes = 2, + .ecc_caps = &denali_socfpga_ecc_caps, +}; + +NAND_ECC_CAPS_SINGLE(denali_uniphier_v5a_ecc_caps, denali_calc_ecc_bytes, + 1024, 8, 16, 24); +static const struct denali_dt_data denali_uniphier_v5a_data = { + .caps = DENALI_CAP_HW_ECC_FIXUP | + DENALI_CAP_DMA_64BIT, + .oob_skip_bytes = 8, + .ecc_caps = &denali_uniphier_v5a_ecc_caps, +}; + +NAND_ECC_CAPS_SINGLE(denali_uniphier_v5b_ecc_caps, denali_calc_ecc_bytes, + 1024, 8, 16); +static const struct denali_dt_data denali_uniphier_v5b_data = { + .revision = 0x0501, + .caps = DENALI_CAP_HW_ECC_FIXUP | + DENALI_CAP_DMA_64BIT, + .oob_skip_bytes = 8, + .ecc_caps = &denali_uniphier_v5b_ecc_caps, +}; + +static const struct of_device_id denali_nand_dt_ids[] = { + { + .compatible = "altr,socfpga-denali-nand", + .data = &denali_socfpga_data, + }, + { + .compatible = "socionext,uniphier-denali-nand-v5a", + .data = &denali_uniphier_v5a_data, + }, + { + .compatible = "socionext,uniphier-denali-nand-v5b", + .data = &denali_uniphier_v5b_data, + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, denali_nand_dt_ids); + +static int denali_dt_chip_init(struct denali_controller *denali, + struct device_node *chip_np) +{ + struct denali_chip *dchip; + u32 bank; + int nsels, i, ret; + + nsels = of_property_count_u32_elems(chip_np, "reg"); + if (nsels < 0) + return nsels; + + dchip = devm_kzalloc(denali->dev, struct_size(dchip, sels, nsels), + GFP_KERNEL); + if (!dchip) + return -ENOMEM; + + dchip->nsels = nsels; + + for (i = 0; i < nsels; i++) { + ret = of_property_read_u32_index(chip_np, "reg", i, &bank); + if (ret) + return ret; + + dchip->sels[i].bank = bank; + + nand_set_flash_node(&dchip->chip, chip_np); + } + + return denali_chip_init(denali, dchip); +} + +static int denali_dt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct denali_dt *dt; + const struct denali_dt_data *data; + struct denali_controller *denali; + int ret; + + dt = devm_kzalloc(dev, sizeof(*dt), GFP_KERNEL); + if (!dt) + return -ENOMEM; + denali = &dt->controller; + + data = of_device_get_match_data(dev); + if (WARN_ON(!data)) + return -EINVAL; + + denali->revision = data->revision; + denali->caps = data->caps; + denali->oob_skip_bytes = data->oob_skip_bytes; + denali->ecc_caps = data->ecc_caps; + + denali->dev = dev; + denali->irq = platform_get_irq(pdev, 0); + if (denali->irq < 0) + return denali->irq; + + denali->reg = devm_platform_ioremap_resource_byname(pdev, "denali_reg"); + if (IS_ERR(denali->reg)) + return PTR_ERR(denali->reg); + + denali->host = devm_platform_ioremap_resource_byname(pdev, "nand_data"); + if (IS_ERR(denali->host)) + return PTR_ERR(denali->host); + + dt->clk = devm_clk_get_enabled(dev, "nand"); + if (IS_ERR(dt->clk)) + return PTR_ERR(dt->clk); + + dt->clk_x = devm_clk_get_enabled(dev, "nand_x"); + if (IS_ERR(dt->clk_x)) + return PTR_ERR(dt->clk_x); + + dt->clk_ecc = devm_clk_get_enabled(dev, "ecc"); + if (IS_ERR(dt->clk_ecc)) + return PTR_ERR(dt->clk_ecc); + + dt->rst = devm_reset_control_get_optional_shared(dev, "nand"); + if (IS_ERR(dt->rst)) + return PTR_ERR(dt->rst); + + dt->rst_reg = devm_reset_control_get_optional_shared(dev, "reg"); + if (IS_ERR(dt->rst_reg)) + return PTR_ERR(dt->rst_reg); + + denali->clk_rate = clk_get_rate(dt->clk); + denali->clk_x_rate = clk_get_rate(dt->clk_x); + + /* + * Deassert the register reset, and the core reset in this order. + * Deasserting the core reset while the register reset is asserted + * will cause unpredictable behavior in the controller. + */ + ret = reset_control_deassert(dt->rst_reg); + if (ret) + return ret; + + ret = reset_control_deassert(dt->rst); + if (ret) + goto out_assert_rst_reg; + + /* + * When the reset is deasserted, the initialization sequence is kicked + * (bootstrap process). The driver must wait until it finished. + * Otherwise, it will result in unpredictable behavior. + */ + usleep_range(200, 1000); + + ret = denali_init(denali); + if (ret) + goto out_assert_rst; + + for_each_child_of_node_scoped(dev->of_node, np) { + ret = denali_dt_chip_init(denali, np); + if (ret) + goto out_remove_denali; + } + + platform_set_drvdata(pdev, dt); + + return 0; + +out_remove_denali: + denali_remove(denali); +out_assert_rst: + reset_control_assert(dt->rst); +out_assert_rst_reg: + reset_control_assert(dt->rst_reg); + + return ret; +} + +static void denali_dt_remove(struct platform_device *pdev) +{ + struct denali_dt *dt = platform_get_drvdata(pdev); + + denali_remove(&dt->controller); + reset_control_assert(dt->rst); + reset_control_assert(dt->rst_reg); +} + +static struct platform_driver denali_dt_driver = { + .probe = denali_dt_probe, + .remove = denali_dt_remove, + .driver = { + .name = "denali-nand-dt", + .of_match_table = denali_nand_dt_ids, + }, +}; +module_platform_driver(denali_dt_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Jamie Iles"); +MODULE_DESCRIPTION("DT driver for Denali NAND controller"); diff --git a/drivers/mtd/nand/raw/denali_pci.c b/drivers/mtd/nand/raw/denali_pci.c new file mode 100644 index 000000000..f53df1b32 --- /dev/null +++ b/drivers/mtd/nand/raw/denali_pci.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * NAND Flash Controller Device Driver + * Copyright © 2009-2010, Intel Corporation and its suppliers. + */ + +#include +#include +#include +#include +#include + +#include "denali.h" + +#define DENALI_NAND_NAME "denali-nand-pci" + +#define INTEL_CE4100 1 +#define INTEL_MRST 2 + +/* List of platforms this NAND controller has be integrated into */ +static const struct pci_device_id denali_pci_ids[] = { + { 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); + +NAND_ECC_CAPS_SINGLE(denali_pci_ecc_caps, denali_calc_ecc_bytes, 512, 8, 15); + +static int denali_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + resource_size_t csr_base, mem_base; + unsigned long csr_len, mem_len; + struct denali_controller *denali; + struct denali_chip *dchip; + int nsels, ret, i; + + denali = devm_kzalloc(&dev->dev, sizeof(*denali), GFP_KERNEL); + if (!denali) + return -ENOMEM; + + ret = pcim_enable_device(dev); + if (ret) { + dev_err(&dev->dev, "Spectra: pci_enable_device failed.\n"); + return ret; + } + + if (id->driver_data == INTEL_CE4100) { + mem_base = pci_resource_start(dev, 0); + mem_len = pci_resource_len(dev, 1); + csr_base = pci_resource_start(dev, 1); + csr_len = pci_resource_len(dev, 1); + } else { + csr_base = pci_resource_start(dev, 0); + csr_len = pci_resource_len(dev, 0); + mem_base = pci_resource_start(dev, 1); + mem_len = pci_resource_len(dev, 1); + if (!mem_len) { + mem_base = csr_base + csr_len; + mem_len = csr_len; + } + } + + pci_set_master(dev); + denali->dev = &dev->dev; + denali->irq = dev->irq; + denali->ecc_caps = &denali_pci_ecc_caps; + denali->clk_rate = 50000000; /* 50 MHz */ + denali->clk_x_rate = 200000000; /* 200 MHz */ + + ret = pcim_request_all_regions(dev, DENALI_NAND_NAME); + if (ret) { + dev_err(&dev->dev, "Spectra: Unable to request memory regions\n"); + return ret; + } + + denali->reg = devm_ioremap(denali->dev, csr_base, csr_len); + if (!denali->reg) { + dev_err(&dev->dev, "Spectra: Unable to remap memory region\n"); + return -ENOMEM; + } + + denali->host = devm_ioremap(denali->dev, mem_base, mem_len); + if (!denali->host) { + dev_err(&dev->dev, "Spectra: ioremap failed!"); + return -ENOMEM; + } + + ret = denali_init(denali); + if (ret) + return ret; + + nsels = denali->nbanks; + + dchip = devm_kzalloc(denali->dev, struct_size(dchip, sels, nsels), + GFP_KERNEL); + if (!dchip) { + ret = -ENOMEM; + goto out_remove_denali; + } + + dchip->chip.base.ecc.user_conf.flags |= NAND_ECC_MAXIMIZE_STRENGTH; + + dchip->nsels = nsels; + + for (i = 0; i < nsels; i++) + dchip->sels[i].bank = i; + + ret = denali_chip_init(denali, dchip); + if (ret) + goto out_remove_denali; + + pci_set_drvdata(dev, denali); + + return 0; + +out_remove_denali: + denali_remove(denali); + return ret; +} + +static void denali_pci_remove(struct pci_dev *dev) +{ + struct denali_controller *denali = pci_get_drvdata(dev); + + denali_remove(denali); +} + +static struct pci_driver denali_pci_driver = { + .name = DENALI_NAND_NAME, + .id_table = denali_pci_ids, + .probe = denali_pci_probe, + .remove = denali_pci_remove, +}; +module_pci_driver(denali_pci_driver); + +MODULE_DESCRIPTION("PCI driver for Denali NAND controller"); +MODULE_AUTHOR("Intel Corporation and its suppliers"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/raw/diskonchip.c b/drivers/mtd/nand/raw/diskonchip.c new file mode 100644 index 000000000..540b6baf8 --- /dev/null +++ b/drivers/mtd/nand/raw/diskonchip.c @@ -0,0 +1,1581 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * (C) 2003 Red Hat, Inc. + * (C) 2004 Dan Brown + * (C) 2004 Kalev Lember + * + * Author: David Woodhouse + * Additional Diskonchip 2000 and Millennium support by Dan Brown + * Diskonchip Millennium Plus support by Kalev Lember + * + * 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 + * + * Interface to generic NAND code for M-Systems DiskOnChip devices + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* Where to look for the devices? */ +#ifndef CONFIG_MTD_NAND_DISKONCHIP_PROBE_ADDRESS +#define CONFIG_MTD_NAND_DISKONCHIP_PROBE_ADDRESS 0 +#endif + +static unsigned long doc_locations[] __initdata = { +#if defined (__alpha__) || defined(__i386__) || defined(__x86_64__) +#ifdef CONFIG_MTD_NAND_DISKONCHIP_PROBE_HIGH + 0xfffc8000, 0xfffca000, 0xfffcc000, 0xfffce000, + 0xfffd0000, 0xfffd2000, 0xfffd4000, 0xfffd6000, + 0xfffd8000, 0xfffda000, 0xfffdc000, 0xfffde000, + 0xfffe0000, 0xfffe2000, 0xfffe4000, 0xfffe6000, + 0xfffe8000, 0xfffea000, 0xfffec000, 0xfffee000, +#else + 0xc8000, 0xca000, 0xcc000, 0xce000, + 0xd0000, 0xd2000, 0xd4000, 0xd6000, + 0xd8000, 0xda000, 0xdc000, 0xde000, + 0xe0000, 0xe2000, 0xe4000, 0xe6000, + 0xe8000, 0xea000, 0xec000, 0xee000, +#endif +#endif +}; + +static struct mtd_info *doclist = NULL; + +struct doc_priv { + struct nand_controller base; + void __iomem *virtadr; + unsigned long physadr; + u_char ChipID; + u_char CDSNControl; + int chips_per_floor; /* The number of chips detected on each floor */ + int curfloor; + int curchip; + int mh0_page; + int mh1_page; + struct rs_control *rs_decoder; + struct mtd_info *nextdoc; + bool supports_32b_reads; + + /* Handle the last stage of initialization (BBT scan, partitioning) */ + int (*late_init)(struct mtd_info *mtd); +}; + +/* This is the ecc value computed by the HW ecc generator upon writing an empty + page, one with all 0xff for data. */ +static u_char empty_write_ecc[6] = { 0x4b, 0x00, 0xe2, 0x0e, 0x93, 0xf7 }; + +#define INFTL_BBT_RESERVED_BLOCKS 4 + +#define DoC_is_MillenniumPlus(doc) ((doc)->ChipID == DOC_ChipID_DocMilPlus16 || (doc)->ChipID == DOC_ChipID_DocMilPlus32) +#define DoC_is_Millennium(doc) ((doc)->ChipID == DOC_ChipID_DocMil) +#define DoC_is_2000(doc) ((doc)->ChipID == DOC_ChipID_Doc2k) + +static int debug = 0; +module_param(debug, int, 0); + +static int try_dword = 1; +module_param(try_dword, int, 0); + +static int no_ecc_failures = 0; +module_param(no_ecc_failures, int, 0); + +static int no_autopart = 0; +module_param(no_autopart, int, 0); + +static int show_firmware_partition = 0; +module_param(show_firmware_partition, int, 0); + +#ifdef CONFIG_MTD_NAND_DISKONCHIP_BBTWRITE +static int inftl_bbt_write = 1; +#else +static int inftl_bbt_write = 0; +#endif +module_param(inftl_bbt_write, int, 0); + +static unsigned long doc_config_location = CONFIG_MTD_NAND_DISKONCHIP_PROBE_ADDRESS; +module_param(doc_config_location, ulong, 0); +MODULE_PARM_DESC(doc_config_location, "Physical memory address at which to probe for DiskOnChip"); + +/* Sector size for HW ECC */ +#define SECTOR_SIZE 512 +/* The sector bytes are packed into NB_DATA 10 bit words */ +#define NB_DATA (((SECTOR_SIZE + 1) * 8 + 6) / 10) +/* Number of roots */ +#define NROOTS 4 +/* First consective root */ +#define FCR 510 +/* Number of symbols */ +#define NN 1023 + +/* + * The HW decoder in the DoC ASIC's provides us a error syndrome, + * which we must convert to a standard syndrome usable by the generic + * Reed-Solomon library code. + * + * Fabrice Bellard figured this out in the old docecc code. I added + * some comments, improved a minor bit and converted it to make use + * of the generic Reed-Solomon library. tglx + */ +static int doc_ecc_decode(struct rs_control *rs, uint8_t *data, uint8_t *ecc) +{ + int i, j, nerr, errpos[8]; + uint8_t parity; + uint16_t ds[4], s[5], tmp, errval[8], syn[4]; + struct rs_codec *cd = rs->codec; + + memset(syn, 0, sizeof(syn)); + /* Convert the ecc bytes into words */ + ds[0] = ((ecc[4] & 0xff) >> 0) | ((ecc[5] & 0x03) << 8); + ds[1] = ((ecc[5] & 0xfc) >> 2) | ((ecc[2] & 0x0f) << 6); + ds[2] = ((ecc[2] & 0xf0) >> 4) | ((ecc[3] & 0x3f) << 4); + ds[3] = ((ecc[3] & 0xc0) >> 6) | ((ecc[0] & 0xff) << 2); + parity = ecc[1]; + + /* Initialize the syndrome buffer */ + for (i = 0; i < NROOTS; i++) + s[i] = ds[0]; + /* + * Evaluate + * s[i] = ds[3]x^3 + ds[2]x^2 + ds[1]x^1 + ds[0] + * where x = alpha^(FCR + i) + */ + for (j = 1; j < NROOTS; j++) { + if (ds[j] == 0) + continue; + tmp = cd->index_of[ds[j]]; + for (i = 0; i < NROOTS; i++) + s[i] ^= cd->alpha_to[rs_modnn(cd, tmp + (FCR + i) * j)]; + } + + /* Calc syn[i] = s[i] / alpha^(v + i) */ + for (i = 0; i < NROOTS; i++) { + if (s[i]) + syn[i] = rs_modnn(cd, cd->index_of[s[i]] + (NN - FCR - i)); + } + /* Call the decoder library */ + nerr = decode_rs16(rs, NULL, NULL, 1019, syn, 0, errpos, 0, errval); + + /* Incorrectable errors ? */ + if (nerr < 0) + return nerr; + + /* + * Correct the errors. The bitpositions are a bit of magic, + * but they are given by the design of the de/encoder circuit + * in the DoC ASIC's. + */ + for (i = 0; i < nerr; i++) { + int index, bitpos, pos = 1015 - errpos[i]; + uint8_t val; + if (pos >= NB_DATA && pos < 1019) + continue; + if (pos < NB_DATA) { + /* extract bit position (MSB first) */ + pos = 10 * (NB_DATA - 1 - pos) - 6; + /* now correct the following 10 bits. At most two bytes + can be modified since pos is even */ + index = (pos >> 3) ^ 1; + bitpos = pos & 7; + if ((index >= 0 && index < SECTOR_SIZE) || index == (SECTOR_SIZE + 1)) { + val = (uint8_t) (errval[i] >> (2 + bitpos)); + parity ^= val; + if (index < SECTOR_SIZE) + data[index] ^= val; + } + index = ((pos >> 3) + 1) ^ 1; + bitpos = (bitpos + 10) & 7; + if (bitpos == 0) + bitpos = 8; + if ((index >= 0 && index < SECTOR_SIZE) || index == (SECTOR_SIZE + 1)) { + val = (uint8_t) (errval[i] << (8 - bitpos)); + parity ^= val; + if (index < SECTOR_SIZE) + data[index] ^= val; + } + } + } + /* If the parity is wrong, no rescue possible */ + return parity ? -EBADMSG : nerr; +} + +static void DoC_Delay(struct doc_priv *doc, unsigned short cycles) +{ + volatile char __always_unused dummy; + int i; + + for (i = 0; i < cycles; i++) { + if (DoC_is_Millennium(doc)) + dummy = ReadDOC(doc->virtadr, NOP); + else if (DoC_is_MillenniumPlus(doc)) + dummy = ReadDOC(doc->virtadr, Mplus_NOP); + else + dummy = ReadDOC(doc->virtadr, DOCStatus); + } + +} + +#define CDSN_CTRL_FR_B_MASK (CDSN_CTRL_FR_B0 | CDSN_CTRL_FR_B1) + +/* DOC_WaitReady: Wait for RDY line to be asserted by the flash chip */ +static int _DoC_WaitReady(struct doc_priv *doc) +{ + void __iomem *docptr = doc->virtadr; + unsigned long timeo = jiffies + (HZ * 10); + + if (debug) + printk("_DoC_WaitReady...\n"); + /* Out-of-line routine to wait for chip response */ + if (DoC_is_MillenniumPlus(doc)) { + while ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) { + if (time_after(jiffies, timeo)) { + printk("_DoC_WaitReady timed out.\n"); + return -EIO; + } + udelay(1); + cond_resched(); + } + } else { + while (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) { + if (time_after(jiffies, timeo)) { + printk("_DoC_WaitReady timed out.\n"); + return -EIO; + } + udelay(1); + cond_resched(); + } + } + + return 0; +} + +static inline int DoC_WaitReady(struct doc_priv *doc) +{ + void __iomem *docptr = doc->virtadr; + int ret = 0; + + if (DoC_is_MillenniumPlus(doc)) { + DoC_Delay(doc, 4); + + if ((ReadDOC(docptr, Mplus_FlashControl) & CDSN_CTRL_FR_B_MASK) != CDSN_CTRL_FR_B_MASK) + /* Call the out-of-line routine to wait */ + ret = _DoC_WaitReady(doc); + } else { + DoC_Delay(doc, 4); + + if (!(ReadDOC(docptr, CDSNControl) & CDSN_CTRL_FR_B)) + /* Call the out-of-line routine to wait */ + ret = _DoC_WaitReady(doc); + DoC_Delay(doc, 2); + } + + if (debug) + printk("DoC_WaitReady OK\n"); + return ret; +} + +static void doc2000_write_byte(struct nand_chip *this, u_char datum) +{ + struct doc_priv *doc = nand_get_controller_data(this); + void __iomem *docptr = doc->virtadr; + + if (debug) + printk("write_byte %02x\n", datum); + WriteDOC(datum, docptr, CDSNSlowIO); + WriteDOC(datum, docptr, 2k_CDSN_IO); +} + +static void doc2000_writebuf(struct nand_chip *this, const u_char *buf, + int len) +{ + struct doc_priv *doc = nand_get_controller_data(this); + void __iomem *docptr = doc->virtadr; + int i; + if (debug) + printk("writebuf of %d bytes: ", len); + for (i = 0; i < len; i++) { + WriteDOC_(buf[i], docptr, DoC_2k_CDSN_IO + i); + if (debug && i < 16) + printk("%02x ", buf[i]); + } + if (debug) + printk("\n"); +} + +static void doc2000_readbuf(struct nand_chip *this, u_char *buf, int len) +{ + struct doc_priv *doc = nand_get_controller_data(this); + void __iomem *docptr = doc->virtadr; + u32 *buf32 = (u32 *)buf; + int i; + + if (debug) + printk("readbuf of %d bytes: ", len); + + if (!doc->supports_32b_reads || + ((((unsigned long)buf) | len) & 3)) { + for (i = 0; i < len; i++) + buf[i] = ReadDOC(docptr, 2k_CDSN_IO + i); + } else { + for (i = 0; i < len / 4; i++) + buf32[i] = readl(docptr + DoC_2k_CDSN_IO + i); + } +} + +/* + * We need our own readid() here because it's called before the NAND chip + * has been initialized, and calling nand_op_readid() would lead to a NULL + * pointer exception when dereferencing the NAND timings. + */ +static void doc200x_readid(struct nand_chip *this, unsigned int cs, u8 *id) +{ + u8 addr = 0; + struct nand_op_instr instrs[] = { + NAND_OP_CMD(NAND_CMD_READID, 0), + NAND_OP_ADDR(1, &addr, 50), + NAND_OP_8BIT_DATA_IN(2, id, 0), + }; + + struct nand_operation op = NAND_OPERATION(cs, instrs); + + if (!id) + op.ninstrs--; + + this->controller->ops->exec_op(this, &op, false); +} + +static uint16_t __init doc200x_ident_chip(struct mtd_info *mtd, int nr) +{ + struct nand_chip *this = mtd_to_nand(mtd); + struct doc_priv *doc = nand_get_controller_data(this); + uint16_t ret; + u8 id[2]; + + doc200x_readid(this, nr, id); + + ret = ((u16)id[0] << 8) | id[1]; + + if (doc->ChipID == DOC_ChipID_Doc2k && try_dword && !nr) { + /* First chip probe. See if we get same results by 32-bit access */ + union { + uint32_t dword; + uint8_t byte[4]; + } ident; + void __iomem *docptr = doc->virtadr; + + doc200x_readid(this, nr, NULL); + + ident.dword = readl(docptr + DoC_2k_CDSN_IO); + if (((ident.byte[0] << 8) | ident.byte[1]) == ret) { + pr_info("DiskOnChip 2000 responds to DWORD access\n"); + doc->supports_32b_reads = true; + } + } + + return ret; +} + +static void __init doc2000_count_chips(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd_to_nand(mtd); + struct doc_priv *doc = nand_get_controller_data(this); + uint16_t mfrid; + int i; + + /* Max 4 chips per floor on DiskOnChip 2000 */ + doc->chips_per_floor = 4; + + /* Find out what the first chip is */ + mfrid = doc200x_ident_chip(mtd, 0); + + /* Find how many chips in each floor. */ + for (i = 1; i < 4; i++) { + if (doc200x_ident_chip(mtd, i) != mfrid) + break; + } + doc->chips_per_floor = i; + pr_debug("Detected %d chips per floor.\n", i); +} + +static void doc2001_write_byte(struct nand_chip *this, u_char datum) +{ + struct doc_priv *doc = nand_get_controller_data(this); + void __iomem *docptr = doc->virtadr; + + WriteDOC(datum, docptr, CDSNSlowIO); + WriteDOC(datum, docptr, Mil_CDSN_IO); + WriteDOC(datum, docptr, WritePipeTerm); +} + +static void doc2001_writebuf(struct nand_chip *this, const u_char *buf, int len) +{ + struct doc_priv *doc = nand_get_controller_data(this); + void __iomem *docptr = doc->virtadr; + int i; + + for (i = 0; i < len; i++) + WriteDOC_(buf[i], docptr, DoC_Mil_CDSN_IO + i); + /* Terminate write pipeline */ + WriteDOC(0x00, docptr, WritePipeTerm); +} + +static void doc2001_readbuf(struct nand_chip *this, u_char *buf, int len) +{ + struct doc_priv *doc = nand_get_controller_data(this); + void __iomem *docptr = doc->virtadr; + int i; + + /* Start read pipeline */ + ReadDOC(docptr, ReadPipeInit); + + for (i = 0; i < len - 1; i++) + buf[i] = ReadDOC(docptr, Mil_CDSN_IO + (i & 0xff)); + + /* Terminate read pipeline */ + buf[i] = ReadDOC(docptr, LastDataRead); +} + +static void doc2001plus_writebuf(struct nand_chip *this, const u_char *buf, int len) +{ + struct doc_priv *doc = nand_get_controller_data(this); + void __iomem *docptr = doc->virtadr; + int i; + + if (debug) + printk("writebuf of %d bytes: ", len); + for (i = 0; i < len; i++) { + WriteDOC_(buf[i], docptr, DoC_Mil_CDSN_IO + i); + if (debug && i < 16) + printk("%02x ", buf[i]); + } + if (debug) + printk("\n"); +} + +static void doc2001plus_readbuf(struct nand_chip *this, u_char *buf, int len) +{ + struct doc_priv *doc = nand_get_controller_data(this); + void __iomem *docptr = doc->virtadr; + int i; + + if (debug) + printk("readbuf of %d bytes: ", len); + + /* Start read pipeline */ + ReadDOC(docptr, Mplus_ReadPipeInit); + ReadDOC(docptr, Mplus_ReadPipeInit); + + for (i = 0; i < len - 2; i++) { + buf[i] = ReadDOC(docptr, Mil_CDSN_IO); + if (debug && i < 16) + printk("%02x ", buf[i]); + } + + /* Terminate read pipeline */ + if (len >= 2) { + buf[len - 2] = ReadDOC(docptr, Mplus_LastDataRead); + if (debug && i < 16) + printk("%02x ", buf[len - 2]); + } + + buf[len - 1] = ReadDOC(docptr, Mplus_LastDataRead); + if (debug && i < 16) + printk("%02x ", buf[len - 1]); + if (debug) + printk("\n"); +} + +static void doc200x_write_control(struct doc_priv *doc, u8 value) +{ + WriteDOC(value, doc->virtadr, CDSNControl); + /* 11.4.3 -- 4 NOPs after CSDNControl write */ + DoC_Delay(doc, 4); +} + +static void doc200x_exec_instr(struct nand_chip *this, + const struct nand_op_instr *instr) +{ + struct doc_priv *doc = nand_get_controller_data(this); + unsigned int i; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + doc200x_write_control(doc, CDSN_CTRL_CE | CDSN_CTRL_CLE); + doc2000_write_byte(this, instr->ctx.cmd.opcode); + break; + + case NAND_OP_ADDR_INSTR: + doc200x_write_control(doc, CDSN_CTRL_CE | CDSN_CTRL_ALE); + for (i = 0; i < instr->ctx.addr.naddrs; i++) { + u8 addr = instr->ctx.addr.addrs[i]; + + if (DoC_is_2000(doc)) + doc2000_write_byte(this, addr); + else + doc2001_write_byte(this, addr); + } + break; + + case NAND_OP_DATA_IN_INSTR: + doc200x_write_control(doc, CDSN_CTRL_CE); + if (DoC_is_2000(doc)) + doc2000_readbuf(this, instr->ctx.data.buf.in, + instr->ctx.data.len); + else + doc2001_readbuf(this, instr->ctx.data.buf.in, + instr->ctx.data.len); + break; + + case NAND_OP_DATA_OUT_INSTR: + doc200x_write_control(doc, CDSN_CTRL_CE); + if (DoC_is_2000(doc)) + doc2000_writebuf(this, instr->ctx.data.buf.out, + instr->ctx.data.len); + else + doc2001_writebuf(this, instr->ctx.data.buf.out, + instr->ctx.data.len); + break; + + case NAND_OP_WAITRDY_INSTR: + DoC_WaitReady(doc); + break; + } + + if (instr->delay_ns) + ndelay(instr->delay_ns); +} + +static int doc200x_exec_op(struct nand_chip *this, + const struct nand_operation *op, + bool check_only) +{ + struct doc_priv *doc = nand_get_controller_data(this); + unsigned int i; + + if (check_only) + return true; + + doc->curchip = op->cs % doc->chips_per_floor; + doc->curfloor = op->cs / doc->chips_per_floor; + + WriteDOC(doc->curfloor, doc->virtadr, FloorSelect); + WriteDOC(doc->curchip, doc->virtadr, CDSNDeviceSelect); + + /* Assert CE pin */ + doc200x_write_control(doc, CDSN_CTRL_CE); + + for (i = 0; i < op->ninstrs; i++) + doc200x_exec_instr(this, &op->instrs[i]); + + /* De-assert CE pin */ + doc200x_write_control(doc, 0); + + return 0; +} + +static void doc2001plus_write_pipe_term(struct doc_priv *doc) +{ + WriteDOC(0x00, doc->virtadr, Mplus_WritePipeTerm); + WriteDOC(0x00, doc->virtadr, Mplus_WritePipeTerm); +} + +static void doc2001plus_exec_instr(struct nand_chip *this, + const struct nand_op_instr *instr) +{ + struct doc_priv *doc = nand_get_controller_data(this); + unsigned int i; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + WriteDOC(instr->ctx.cmd.opcode, doc->virtadr, Mplus_FlashCmd); + doc2001plus_write_pipe_term(doc); + break; + + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) { + u8 addr = instr->ctx.addr.addrs[i]; + + WriteDOC(addr, doc->virtadr, Mplus_FlashAddress); + } + doc2001plus_write_pipe_term(doc); + /* deassert ALE */ + WriteDOC(0, doc->virtadr, Mplus_FlashControl); + break; + + case NAND_OP_DATA_IN_INSTR: + doc2001plus_readbuf(this, instr->ctx.data.buf.in, + instr->ctx.data.len); + break; + case NAND_OP_DATA_OUT_INSTR: + doc2001plus_writebuf(this, instr->ctx.data.buf.out, + instr->ctx.data.len); + doc2001plus_write_pipe_term(doc); + break; + case NAND_OP_WAITRDY_INSTR: + DoC_WaitReady(doc); + break; + } + + if (instr->delay_ns) + ndelay(instr->delay_ns); +} + +static int doc2001plus_exec_op(struct nand_chip *this, + const struct nand_operation *op, + bool check_only) +{ + struct doc_priv *doc = nand_get_controller_data(this); + unsigned int i; + + if (check_only) + return true; + + doc->curchip = op->cs % doc->chips_per_floor; + doc->curfloor = op->cs / doc->chips_per_floor; + + /* Assert ChipEnable and deassert WriteProtect */ + WriteDOC(DOC_FLASH_CE, doc->virtadr, Mplus_FlashSelect); + + for (i = 0; i < op->ninstrs; i++) + doc2001plus_exec_instr(this, &op->instrs[i]); + + /* De-assert ChipEnable */ + WriteDOC(0, doc->virtadr, Mplus_FlashSelect); + + return 0; +} + +static void doc200x_enable_hwecc(struct nand_chip *this, int mode) +{ + struct doc_priv *doc = nand_get_controller_data(this); + void __iomem *docptr = doc->virtadr; + + /* Prime the ECC engine */ + switch (mode) { + case NAND_ECC_READ: + WriteDOC(DOC_ECC_RESET, docptr, ECCConf); + WriteDOC(DOC_ECC_EN, docptr, ECCConf); + break; + case NAND_ECC_WRITE: + WriteDOC(DOC_ECC_RESET, docptr, ECCConf); + WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, ECCConf); + break; + } +} + +static void doc2001plus_enable_hwecc(struct nand_chip *this, int mode) +{ + struct doc_priv *doc = nand_get_controller_data(this); + void __iomem *docptr = doc->virtadr; + + /* Prime the ECC engine */ + switch (mode) { + case NAND_ECC_READ: + WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); + WriteDOC(DOC_ECC_EN, docptr, Mplus_ECCConf); + break; + case NAND_ECC_WRITE: + WriteDOC(DOC_ECC_RESET, docptr, Mplus_ECCConf); + WriteDOC(DOC_ECC_EN | DOC_ECC_RW, docptr, Mplus_ECCConf); + break; + } +} + +/* This code is only called on write */ +static int doc200x_calculate_ecc(struct nand_chip *this, const u_char *dat, + unsigned char *ecc_code) +{ + struct doc_priv *doc = nand_get_controller_data(this); + void __iomem *docptr = doc->virtadr; + int i; + int __always_unused emptymatch = 1; + + /* flush the pipeline */ + if (DoC_is_2000(doc)) { + WriteDOC(doc->CDSNControl & ~CDSN_CTRL_FLASH_IO, docptr, CDSNControl); + WriteDOC(0, docptr, 2k_CDSN_IO); + WriteDOC(0, docptr, 2k_CDSN_IO); + WriteDOC(0, docptr, 2k_CDSN_IO); + WriteDOC(doc->CDSNControl, docptr, CDSNControl); + } else if (DoC_is_MillenniumPlus(doc)) { + WriteDOC(0, docptr, Mplus_NOP); + WriteDOC(0, docptr, Mplus_NOP); + WriteDOC(0, docptr, Mplus_NOP); + } else { + WriteDOC(0, docptr, NOP); + WriteDOC(0, docptr, NOP); + WriteDOC(0, docptr, NOP); + } + + for (i = 0; i < 6; i++) { + if (DoC_is_MillenniumPlus(doc)) + ecc_code[i] = ReadDOC_(docptr, DoC_Mplus_ECCSyndrome0 + i); + else + ecc_code[i] = ReadDOC_(docptr, DoC_ECCSyndrome0 + i); + if (ecc_code[i] != empty_write_ecc[i]) + emptymatch = 0; + } + if (DoC_is_MillenniumPlus(doc)) + WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf); + else + WriteDOC(DOC_ECC_DIS, docptr, ECCConf); +#if 0 + /* If emptymatch=1, we might have an all-0xff data buffer. Check. */ + if (emptymatch) { + /* Note: this somewhat expensive test should not be triggered + often. It could be optimized away by examining the data in + the writebuf routine, and remembering the result. */ + for (i = 0; i < 512; i++) { + if (dat[i] == 0xff) + continue; + emptymatch = 0; + break; + } + } + /* If emptymatch still =1, we do have an all-0xff data buffer. + Return all-0xff ecc value instead of the computed one, so + it'll look just like a freshly-erased page. */ + if (emptymatch) + memset(ecc_code, 0xff, 6); +#endif + return 0; +} + +static int doc200x_correct_data(struct nand_chip *this, u_char *dat, + u_char *read_ecc, u_char *isnull) +{ + int i, ret = 0; + struct doc_priv *doc = nand_get_controller_data(this); + void __iomem *docptr = doc->virtadr; + uint8_t calc_ecc[6]; + volatile u_char dummy; + + /* flush the pipeline */ + if (DoC_is_2000(doc)) { + dummy = ReadDOC(docptr, 2k_ECCStatus); + dummy = ReadDOC(docptr, 2k_ECCStatus); + dummy = ReadDOC(docptr, 2k_ECCStatus); + } else if (DoC_is_MillenniumPlus(doc)) { + dummy = ReadDOC(docptr, Mplus_ECCConf); + dummy = ReadDOC(docptr, Mplus_ECCConf); + dummy = ReadDOC(docptr, Mplus_ECCConf); + } else { + dummy = ReadDOC(docptr, ECCConf); + dummy = ReadDOC(docptr, ECCConf); + dummy = ReadDOC(docptr, ECCConf); + } + + /* Error occurred ? */ + if (dummy & 0x80) { + for (i = 0; i < 6; i++) { + if (DoC_is_MillenniumPlus(doc)) + calc_ecc[i] = ReadDOC_(docptr, DoC_Mplus_ECCSyndrome0 + i); + else + calc_ecc[i] = ReadDOC_(docptr, DoC_ECCSyndrome0 + i); + } + + ret = doc_ecc_decode(doc->rs_decoder, dat, calc_ecc); + if (ret > 0) + pr_err("doc200x_correct_data corrected %d errors\n", + ret); + } + if (DoC_is_MillenniumPlus(doc)) + WriteDOC(DOC_ECC_DIS, docptr, Mplus_ECCConf); + else + WriteDOC(DOC_ECC_DIS, docptr, ECCConf); + if (no_ecc_failures && mtd_is_eccerr(ret)) { + pr_err("suppressing ECC failure\n"); + ret = 0; + } + return ret; +} + +//u_char mydatabuf[528]; + +static int doc200x_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section) + return -ERANGE; + + oobregion->offset = 0; + oobregion->length = 6; + + return 0; +} + +static int doc200x_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 1) + return -ERANGE; + + /* + * The strange out-of-order free bytes definition is a (possibly + * unneeded) attempt to retain compatibility. It used to read: + * .oobfree = { {8, 8} } + * Since that leaves two bytes unusable, it was changed. But the + * following scheme might affect existing jffs2 installs by moving the + * cleanmarker: + * .oobfree = { {6, 10} } + * jffs2 seems to handle the above gracefully, but the current scheme + * seems safer. The only problem with it is that any code retrieving + * free bytes position must be able to handle out-of-order segments. + */ + if (!section) { + oobregion->offset = 8; + oobregion->length = 8; + } else { + oobregion->offset = 6; + oobregion->length = 2; + } + + return 0; +} + +static const struct mtd_ooblayout_ops doc200x_ooblayout_ops = { + .ecc = doc200x_ooblayout_ecc, + .free = doc200x_ooblayout_free, +}; + +/* Find the (I)NFTL Media Header, and optionally also the mirror media header. + On successful return, buf will contain a copy of the media header for + further processing. id is the string to scan for, and will presumably be + either "ANAND" or "BNAND". If findmirror=1, also look for the mirror media + header. The page #s of the found media headers are placed in mh0_page and + mh1_page in the DOC private structure. */ +static int __init find_media_headers(struct mtd_info *mtd, u_char *buf, const char *id, int findmirror) +{ + struct nand_chip *this = mtd_to_nand(mtd); + struct doc_priv *doc = nand_get_controller_data(this); + unsigned offs; + int ret; + size_t retlen; + + for (offs = 0; offs < mtd->size; offs += mtd->erasesize) { + ret = mtd_read(mtd, offs, mtd->writesize, &retlen, buf); + if (retlen != mtd->writesize) + continue; + if (ret) { + pr_warn("ECC error scanning DOC at 0x%x\n", offs); + } + if (memcmp(buf, id, 6)) + continue; + pr_info("Found DiskOnChip %s Media Header at 0x%x\n", id, offs); + if (doc->mh0_page == -1) { + doc->mh0_page = offs >> this->page_shift; + if (!findmirror) + return 1; + continue; + } + doc->mh1_page = offs >> this->page_shift; + return 2; + } + if (doc->mh0_page == -1) { + pr_warn("DiskOnChip %s Media Header not found.\n", id); + return 0; + } + /* Only one mediaheader was found. We want buf to contain a + mediaheader on return, so we'll have to re-read the one we found. */ + offs = doc->mh0_page << this->page_shift; + ret = mtd_read(mtd, offs, mtd->writesize, &retlen, buf); + if (retlen != mtd->writesize) { + /* Insanity. Give up. */ + pr_err("Read DiskOnChip Media Header once, but can't reread it???\n"); + return 0; + } + return 1; +} + +static inline int __init nftl_partscan(struct mtd_info *mtd, struct mtd_partition *parts) +{ + struct nand_chip *this = mtd_to_nand(mtd); + struct doc_priv *doc = nand_get_controller_data(this); + struct nand_memory_organization *memorg; + int ret = 0; + u_char *buf; + struct NFTLMediaHeader *mh; + const unsigned psize = 1 << this->page_shift; + int numparts = 0; + unsigned blocks, maxblocks; + int offs, numheaders; + + memorg = nanddev_get_memorg(&this->base); + + buf = kmalloc(mtd->writesize, GFP_KERNEL); + if (!buf) { + return 0; + } + if (!(numheaders = find_media_headers(mtd, buf, "ANAND", 1))) + goto out; + mh = (struct NFTLMediaHeader *)buf; + + le16_to_cpus(&mh->NumEraseUnits); + le16_to_cpus(&mh->FirstPhysicalEUN); + le32_to_cpus(&mh->FormattedSize); + + pr_info(" DataOrgID = %s\n" + " NumEraseUnits = %d\n" + " FirstPhysicalEUN = %d\n" + " FormattedSize = %d\n" + " UnitSizeFactor = %d\n", + mh->DataOrgID, mh->NumEraseUnits, + mh->FirstPhysicalEUN, mh->FormattedSize, + mh->UnitSizeFactor); + + blocks = mtd->size >> this->phys_erase_shift; + maxblocks = min(32768U, mtd->erasesize - psize); + + if (mh->UnitSizeFactor == 0x00) { + /* Auto-determine UnitSizeFactor. The constraints are: + - There can be at most 32768 virtual blocks. + - There can be at most (virtual block size - page size) + virtual blocks (because MediaHeader+BBT must fit in 1). + */ + mh->UnitSizeFactor = 0xff; + while (blocks > maxblocks) { + blocks >>= 1; + maxblocks = min(32768U, (maxblocks << 1) + psize); + mh->UnitSizeFactor--; + } + pr_warn("UnitSizeFactor=0x00 detected. Correct value is assumed to be 0x%02x.\n", mh->UnitSizeFactor); + } + + /* NOTE: The lines below modify internal variables of the NAND and MTD + layers; variables with have already been configured by nand_scan. + Unfortunately, we didn't know before this point what these values + should be. Thus, this code is somewhat dependent on the exact + implementation of the NAND layer. */ + if (mh->UnitSizeFactor != 0xff) { + this->bbt_erase_shift += (0xff - mh->UnitSizeFactor); + memorg->pages_per_eraseblock <<= (0xff - mh->UnitSizeFactor); + mtd->erasesize <<= (0xff - mh->UnitSizeFactor); + pr_info("Setting virtual erase size to %d\n", mtd->erasesize); + blocks = mtd->size >> this->bbt_erase_shift; + maxblocks = min(32768U, mtd->erasesize - psize); + } + + if (blocks > maxblocks) { + pr_err("UnitSizeFactor of 0x%02x is inconsistent with device size. Aborting.\n", mh->UnitSizeFactor); + goto out; + } + + /* Skip past the media headers. */ + offs = max(doc->mh0_page, doc->mh1_page); + offs <<= this->page_shift; + offs += mtd->erasesize; + + if (show_firmware_partition == 1) { + parts[0].name = " DiskOnChip Firmware / Media Header partition"; + parts[0].offset = 0; + parts[0].size = offs; + numparts = 1; + } + + parts[numparts].name = " DiskOnChip BDTL partition"; + parts[numparts].offset = offs; + parts[numparts].size = (mh->NumEraseUnits - numheaders) << this->bbt_erase_shift; + + offs += parts[numparts].size; + numparts++; + + if (offs < mtd->size) { + parts[numparts].name = " DiskOnChip Remainder partition"; + parts[numparts].offset = offs; + parts[numparts].size = mtd->size - offs; + numparts++; + } + + ret = numparts; + out: + kfree(buf); + return ret; +} + +/* This is a stripped-down copy of the code in inftlmount.c */ +static inline int __init inftl_partscan(struct mtd_info *mtd, struct mtd_partition *parts) +{ + struct nand_chip *this = mtd_to_nand(mtd); + struct doc_priv *doc = nand_get_controller_data(this); + int ret = 0; + u_char *buf; + struct INFTLMediaHeader *mh; + struct INFTLPartition *ip; + int numparts = 0; + int blocks; + int vshift, lastvunit = 0; + int i; + int end = mtd->size; + + if (inftl_bbt_write) + end -= (INFTL_BBT_RESERVED_BLOCKS << this->phys_erase_shift); + + buf = kmalloc(mtd->writesize, GFP_KERNEL); + if (!buf) { + return 0; + } + + if (!find_media_headers(mtd, buf, "BNAND", 0)) + goto out; + doc->mh1_page = doc->mh0_page + (4096 >> this->page_shift); + mh = (struct INFTLMediaHeader *)buf; + + le32_to_cpus(&mh->NoOfBootImageBlocks); + le32_to_cpus(&mh->NoOfBinaryPartitions); + le32_to_cpus(&mh->NoOfBDTLPartitions); + le32_to_cpus(&mh->BlockMultiplierBits); + le32_to_cpus(&mh->FormatFlags); + le32_to_cpus(&mh->PercentUsed); + + pr_info(" bootRecordID = %s\n" + " NoOfBootImageBlocks = %d\n" + " NoOfBinaryPartitions = %d\n" + " NoOfBDTLPartitions = %d\n" + " BlockMultiplierBits = %d\n" + " FormatFlgs = %d\n" + " OsakVersion = %d.%d.%d.%d\n" + " PercentUsed = %d\n", + mh->bootRecordID, mh->NoOfBootImageBlocks, + mh->NoOfBinaryPartitions, + mh->NoOfBDTLPartitions, + mh->BlockMultiplierBits, mh->FormatFlags, + ((unsigned char *) &mh->OsakVersion)[0] & 0xf, + ((unsigned char *) &mh->OsakVersion)[1] & 0xf, + ((unsigned char *) &mh->OsakVersion)[2] & 0xf, + ((unsigned char *) &mh->OsakVersion)[3] & 0xf, + mh->PercentUsed); + + vshift = this->phys_erase_shift + mh->BlockMultiplierBits; + + blocks = mtd->size >> vshift; + if (blocks > 32768) { + pr_err("BlockMultiplierBits=%d is inconsistent with device size. Aborting.\n", mh->BlockMultiplierBits); + goto out; + } + + blocks = doc->chips_per_floor << (this->chip_shift - this->phys_erase_shift); + if (inftl_bbt_write && (blocks > mtd->erasesize)) { + pr_err("Writeable BBTs spanning more than one erase block are not yet supported. FIX ME!\n"); + goto out; + } + + /* Scan the partitions */ + for (i = 0; (i < 4); i++) { + ip = &(mh->Partitions[i]); + le32_to_cpus(&ip->virtualUnits); + le32_to_cpus(&ip->firstUnit); + le32_to_cpus(&ip->lastUnit); + le32_to_cpus(&ip->flags); + le32_to_cpus(&ip->spareUnits); + le32_to_cpus(&ip->Reserved0); + + pr_info(" PARTITION[%d] ->\n" + " virtualUnits = %d\n" + " firstUnit = %d\n" + " lastUnit = %d\n" + " flags = 0x%x\n" + " spareUnits = %d\n", + i, ip->virtualUnits, ip->firstUnit, + ip->lastUnit, ip->flags, + ip->spareUnits); + + if ((show_firmware_partition == 1) && + (i == 0) && (ip->firstUnit > 0)) { + parts[0].name = " DiskOnChip IPL / Media Header partition"; + parts[0].offset = 0; + parts[0].size = (uint64_t)mtd->erasesize * ip->firstUnit; + numparts = 1; + } + + if (ip->flags & INFTL_BINARY) + parts[numparts].name = " DiskOnChip BDK partition"; + else + parts[numparts].name = " DiskOnChip BDTL partition"; + parts[numparts].offset = ip->firstUnit << vshift; + parts[numparts].size = (1 + ip->lastUnit - ip->firstUnit) << vshift; + numparts++; + if (ip->lastUnit > lastvunit) + lastvunit = ip->lastUnit; + if (ip->flags & INFTL_LAST) + break; + } + lastvunit++; + if ((lastvunit << vshift) < end) { + parts[numparts].name = " DiskOnChip Remainder partition"; + parts[numparts].offset = lastvunit << vshift; + parts[numparts].size = end - parts[numparts].offset; + numparts++; + } + ret = numparts; + out: + kfree(buf); + return ret; +} + +static int __init nftl_scan_bbt(struct mtd_info *mtd) +{ + int ret, numparts; + struct nand_chip *this = mtd_to_nand(mtd); + struct doc_priv *doc = nand_get_controller_data(this); + struct mtd_partition parts[2]; + + memset((char *)parts, 0, sizeof(parts)); + /* On NFTL, we have to find the media headers before we can read the + BBTs, since they're stored in the media header eraseblocks. */ + numparts = nftl_partscan(mtd, parts); + if (!numparts) + return -EIO; + this->bbt_td->options = NAND_BBT_ABSPAGE | NAND_BBT_8BIT | + NAND_BBT_SAVECONTENT | NAND_BBT_WRITE | + NAND_BBT_VERSION; + this->bbt_td->veroffs = 7; + this->bbt_td->pages[0] = doc->mh0_page + 1; + if (doc->mh1_page != -1) { + this->bbt_md->options = NAND_BBT_ABSPAGE | NAND_BBT_8BIT | + NAND_BBT_SAVECONTENT | NAND_BBT_WRITE | + NAND_BBT_VERSION; + this->bbt_md->veroffs = 7; + this->bbt_md->pages[0] = doc->mh1_page + 1; + } else { + this->bbt_md = NULL; + } + + ret = nand_create_bbt(this); + if (ret) + return ret; + + return mtd_device_register(mtd, parts, no_autopart ? 0 : numparts); +} + +static int __init inftl_scan_bbt(struct mtd_info *mtd) +{ + int ret, numparts; + struct nand_chip *this = mtd_to_nand(mtd); + struct doc_priv *doc = nand_get_controller_data(this); + struct mtd_partition parts[5]; + + if (nanddev_ntargets(&this->base) > doc->chips_per_floor) { + pr_err("Multi-floor INFTL devices not yet supported.\n"); + return -EIO; + } + + if (DoC_is_MillenniumPlus(doc)) { + this->bbt_td->options = NAND_BBT_2BIT | NAND_BBT_ABSPAGE; + if (inftl_bbt_write) + this->bbt_td->options |= NAND_BBT_WRITE; + this->bbt_td->pages[0] = 2; + this->bbt_md = NULL; + } else { + this->bbt_td->options = NAND_BBT_LASTBLOCK | NAND_BBT_8BIT | NAND_BBT_VERSION; + if (inftl_bbt_write) + this->bbt_td->options |= NAND_BBT_WRITE; + this->bbt_td->offs = 8; + this->bbt_td->len = 8; + this->bbt_td->veroffs = 7; + this->bbt_td->maxblocks = INFTL_BBT_RESERVED_BLOCKS; + this->bbt_td->reserved_block_code = 0x01; + this->bbt_td->pattern = "MSYS_BBT"; + + this->bbt_md->options = NAND_BBT_LASTBLOCK | NAND_BBT_8BIT | NAND_BBT_VERSION; + if (inftl_bbt_write) + this->bbt_md->options |= NAND_BBT_WRITE; + this->bbt_md->offs = 8; + this->bbt_md->len = 8; + this->bbt_md->veroffs = 7; + this->bbt_md->maxblocks = INFTL_BBT_RESERVED_BLOCKS; + this->bbt_md->reserved_block_code = 0x01; + this->bbt_md->pattern = "TBB_SYSM"; + } + + ret = nand_create_bbt(this); + if (ret) + return ret; + + memset((char *)parts, 0, sizeof(parts)); + numparts = inftl_partscan(mtd, parts); + /* At least for now, require the INFTL Media Header. We could probably + do without it for non-INFTL use, since all it gives us is + autopartitioning, but I want to give it more thought. */ + if (!numparts) + return -EIO; + return mtd_device_register(mtd, parts, no_autopart ? 0 : numparts); +} + +static inline int __init doc2000_init(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd_to_nand(mtd); + struct doc_priv *doc = nand_get_controller_data(this); + + doc->late_init = nftl_scan_bbt; + + doc->CDSNControl = CDSN_CTRL_FLASH_IO | CDSN_CTRL_ECC_IO; + doc2000_count_chips(mtd); + mtd->name = "DiskOnChip 2000 (NFTL Model)"; + return (4 * doc->chips_per_floor); +} + +static inline int __init doc2001_init(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd_to_nand(mtd); + struct doc_priv *doc = nand_get_controller_data(this); + + ReadDOC(doc->virtadr, ChipID); + ReadDOC(doc->virtadr, ChipID); + ReadDOC(doc->virtadr, ChipID); + if (ReadDOC(doc->virtadr, ChipID) != DOC_ChipID_DocMil) { + /* It's not a Millennium; it's one of the newer + DiskOnChip 2000 units with a similar ASIC. + Treat it like a Millennium, except that it + can have multiple chips. */ + doc2000_count_chips(mtd); + mtd->name = "DiskOnChip 2000 (INFTL Model)"; + doc->late_init = inftl_scan_bbt; + return (4 * doc->chips_per_floor); + } else { + /* Bog-standard Millennium */ + doc->chips_per_floor = 1; + mtd->name = "DiskOnChip Millennium"; + doc->late_init = nftl_scan_bbt; + return 1; + } +} + +static inline int __init doc2001plus_init(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd_to_nand(mtd); + struct doc_priv *doc = nand_get_controller_data(this); + + doc->late_init = inftl_scan_bbt; + this->ecc.hwctl = doc2001plus_enable_hwecc; + + doc->chips_per_floor = 1; + mtd->name = "DiskOnChip Millennium Plus"; + + return 1; +} + +static int doc200x_attach_chip(struct nand_chip *chip) +{ + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return 0; + + chip->ecc.placement = NAND_ECC_PLACEMENT_INTERLEAVED; + chip->ecc.size = 512; + chip->ecc.bytes = 6; + chip->ecc.strength = 2; + chip->ecc.options = NAND_ECC_GENERIC_ERASED_CHECK; + chip->ecc.hwctl = doc200x_enable_hwecc; + chip->ecc.calculate = doc200x_calculate_ecc; + chip->ecc.correct = doc200x_correct_data; + + return 0; +} + +static const struct nand_controller_ops doc200x_ops = { + .exec_op = doc200x_exec_op, + .attach_chip = doc200x_attach_chip, +}; + +static const struct nand_controller_ops doc2001plus_ops = { + .exec_op = doc2001plus_exec_op, + .attach_chip = doc200x_attach_chip, +}; + +static int __init doc_probe(unsigned long physadr) +{ + struct nand_chip *nand = NULL; + struct doc_priv *doc = NULL; + unsigned char ChipID; + struct mtd_info *mtd; + void __iomem *virtadr; + unsigned char save_control; + unsigned char tmp, tmpb, tmpc; + int reg, len, numchips; + int ret = 0; + + if (!request_mem_region(physadr, DOC_IOREMAP_LEN, "DiskOnChip")) + return -EBUSY; + virtadr = ioremap(physadr, DOC_IOREMAP_LEN); + if (!virtadr) { + pr_err("Diskonchip ioremap failed: 0x%x bytes at 0x%lx\n", + DOC_IOREMAP_LEN, physadr); + ret = -EIO; + goto error_ioremap; + } + + /* It's not possible to cleanly detect the DiskOnChip - the + * bootup procedure will put the device into reset mode, and + * it's not possible to talk to it without actually writing + * to the DOCControl register. So we store the current contents + * of the DOCControl register's location, in case we later decide + * that it's not a DiskOnChip, and want to put it back how we + * found it. + */ + save_control = ReadDOC(virtadr, DOCControl); + + /* Reset the DiskOnChip ASIC */ + WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET, virtadr, DOCControl); + WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_RESET, virtadr, DOCControl); + + /* Enable the DiskOnChip ASIC */ + WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL, virtadr, DOCControl); + WriteDOC(DOC_MODE_CLR_ERR | DOC_MODE_MDWREN | DOC_MODE_NORMAL, virtadr, DOCControl); + + ChipID = ReadDOC(virtadr, ChipID); + + switch (ChipID) { + case DOC_ChipID_Doc2k: + reg = DoC_2k_ECCStatus; + break; + case DOC_ChipID_DocMil: + reg = DoC_ECCConf; + break; + case DOC_ChipID_DocMilPlus16: + case DOC_ChipID_DocMilPlus32: + case 0: + /* Possible Millennium Plus, need to do more checks */ + /* Possibly release from power down mode */ + for (tmp = 0; (tmp < 4); tmp++) + ReadDOC(virtadr, Mplus_Power); + + /* Reset the Millennium Plus ASIC */ + tmp = DOC_MODE_RESET | DOC_MODE_MDWREN | DOC_MODE_RST_LAT | DOC_MODE_BDECT; + WriteDOC(tmp, virtadr, Mplus_DOCControl); + WriteDOC(~tmp, virtadr, Mplus_CtrlConfirm); + + usleep_range(1000, 2000); + /* Enable the Millennium Plus ASIC */ + tmp = DOC_MODE_NORMAL | DOC_MODE_MDWREN | DOC_MODE_RST_LAT | DOC_MODE_BDECT; + WriteDOC(tmp, virtadr, Mplus_DOCControl); + WriteDOC(~tmp, virtadr, Mplus_CtrlConfirm); + usleep_range(1000, 2000); + + ChipID = ReadDOC(virtadr, ChipID); + + switch (ChipID) { + case DOC_ChipID_DocMilPlus16: + reg = DoC_Mplus_Toggle; + break; + case DOC_ChipID_DocMilPlus32: + pr_err("DiskOnChip Millennium Plus 32MB is not supported, ignoring.\n"); + fallthrough; + default: + ret = -ENODEV; + goto notfound; + } + break; + + default: + ret = -ENODEV; + goto notfound; + } + /* Check the TOGGLE bit in the ECC register */ + tmp = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT; + tmpb = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT; + tmpc = ReadDOC_(virtadr, reg) & DOC_TOGGLE_BIT; + if ((tmp == tmpb) || (tmp != tmpc)) { + pr_warn("Possible DiskOnChip at 0x%lx failed TOGGLE test, dropping.\n", physadr); + ret = -ENODEV; + goto notfound; + } + + for (mtd = doclist; mtd; mtd = doc->nextdoc) { + unsigned char oldval; + unsigned char newval; + nand = mtd_to_nand(mtd); + doc = nand_get_controller_data(nand); + /* Use the alias resolution register to determine if this is + in fact the same DOC aliased to a new address. If writes + to one chip's alias resolution register change the value on + the other chip, they're the same chip. */ + if (ChipID == DOC_ChipID_DocMilPlus16) { + oldval = ReadDOC(doc->virtadr, Mplus_AliasResolution); + newval = ReadDOC(virtadr, Mplus_AliasResolution); + } else { + oldval = ReadDOC(doc->virtadr, AliasResolution); + newval = ReadDOC(virtadr, AliasResolution); + } + if (oldval != newval) + continue; + if (ChipID == DOC_ChipID_DocMilPlus16) { + WriteDOC(~newval, virtadr, Mplus_AliasResolution); + oldval = ReadDOC(doc->virtadr, Mplus_AliasResolution); + WriteDOC(newval, virtadr, Mplus_AliasResolution); // restore it + } else { + WriteDOC(~newval, virtadr, AliasResolution); + oldval = ReadDOC(doc->virtadr, AliasResolution); + WriteDOC(newval, virtadr, AliasResolution); // restore it + } + newval = ~newval; + if (oldval == newval) { + pr_debug("Found alias of DOC at 0x%lx to 0x%lx\n", + doc->physadr, physadr); + goto notfound; + } + } + + pr_notice("DiskOnChip found at 0x%lx\n", physadr); + + len = sizeof(struct nand_chip) + sizeof(struct doc_priv) + + (2 * sizeof(struct nand_bbt_descr)); + nand = kzalloc(len, GFP_KERNEL); + if (!nand) { + ret = -ENOMEM; + goto fail; + } + + /* + * Allocate a RS codec instance + * + * Symbolsize is 10 (bits) + * Primitve polynomial is x^10+x^3+1 + * First consecutive root is 510 + * Primitve element to generate roots = 1 + * Generator polinomial degree = 4 + */ + doc = (struct doc_priv *) (nand + 1); + doc->rs_decoder = init_rs(10, 0x409, FCR, 1, NROOTS); + if (!doc->rs_decoder) { + pr_err("DiskOnChip: Could not create a RS codec\n"); + ret = -ENOMEM; + goto fail; + } + + nand_controller_init(&doc->base); + if (ChipID == DOC_ChipID_DocMilPlus16) + doc->base.ops = &doc2001plus_ops; + else + doc->base.ops = &doc200x_ops; + + mtd = nand_to_mtd(nand); + nand->bbt_td = (struct nand_bbt_descr *) (doc + 1); + nand->bbt_md = nand->bbt_td + 1; + + mtd->owner = THIS_MODULE; + mtd_set_ooblayout(mtd, &doc200x_ooblayout_ops); + + nand->controller = &doc->base; + nand_set_controller_data(nand, doc); + nand->bbt_options = NAND_BBT_USE_FLASH; + /* Skip the automatic BBT scan so we can run it manually */ + nand->options |= NAND_SKIP_BBTSCAN | NAND_NO_BBM_QUIRK; + + doc->physadr = physadr; + doc->virtadr = virtadr; + doc->ChipID = ChipID; + doc->curfloor = -1; + doc->curchip = -1; + doc->mh0_page = -1; + doc->mh1_page = -1; + doc->nextdoc = doclist; + + if (ChipID == DOC_ChipID_Doc2k) + numchips = doc2000_init(mtd); + else if (ChipID == DOC_ChipID_DocMilPlus16) + numchips = doc2001plus_init(mtd); + else + numchips = doc2001_init(mtd); + + ret = nand_scan(nand, numchips); + if (ret) + goto fail; + + ret = doc->late_init(mtd); + if (ret) { + nand_cleanup(nand); + goto fail; + } + + /* Success! */ + doclist = mtd; + return 0; + + notfound: + /* Put back the contents of the DOCControl register, in case it's not + actually a DiskOnChip. */ + WriteDOC(save_control, virtadr, DOCControl); + fail: + if (doc) + free_rs(doc->rs_decoder); + kfree(nand); + iounmap(virtadr); + +error_ioremap: + release_mem_region(physadr, DOC_IOREMAP_LEN); + + return ret; +} + +static void release_nanddoc(void) +{ + struct mtd_info *mtd, *nextmtd; + struct nand_chip *nand; + struct doc_priv *doc; + int ret; + + for (mtd = doclist; mtd; mtd = nextmtd) { + nand = mtd_to_nand(mtd); + doc = nand_get_controller_data(nand); + + nextmtd = doc->nextdoc; + ret = mtd_device_unregister(mtd); + WARN_ON(ret); + nand_cleanup(nand); + iounmap(doc->virtadr); + release_mem_region(doc->physadr, DOC_IOREMAP_LEN); + free_rs(doc->rs_decoder); + kfree(nand); + } +} + +static int __init init_nanddoc(void) +{ + int i, ret = 0; + + if (doc_config_location) { + pr_info("Using configured DiskOnChip probe address 0x%lx\n", + doc_config_location); + ret = doc_probe(doc_config_location); + if (ret < 0) + return ret; + } else { + for (i = 0; i < ARRAY_SIZE(doc_locations); i++) { + doc_probe(doc_locations[i]); + } + } + /* No banner message any more. Print a message if no DiskOnChip + found, so the user knows we at least tried. */ + if (!doclist) { + pr_info("No valid DiskOnChip devices found\n"); + ret = -ENODEV; + } + return ret; +} + +static void __exit cleanup_nanddoc(void) +{ + /* Cleanup the nand/DoC resources */ + release_nanddoc(); +} + +module_init(init_nanddoc); +module_exit(cleanup_nanddoc); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Woodhouse "); +MODULE_DESCRIPTION("M-Systems DiskOnChip 2000, Millennium and Millennium Plus device driver"); diff --git a/drivers/mtd/nand/raw/fsl_elbc_nand.c b/drivers/mtd/nand/raw/fsl_elbc_nand.c new file mode 100644 index 000000000..c655d27bc --- /dev/null +++ b/drivers/mtd/nand/raw/fsl_elbc_nand.c @@ -0,0 +1,1009 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Freescale Enhanced Local Bus Controller NAND driver + * + * Copyright © 2006-2007, 2010 Freescale Semiconductor + * + * Authors: Nick Spence , + * Scott Wood + * Jack Lan + * Roy Zang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#define MAX_BANKS 8 +#define ERR_BYTE 0xFF /* Value returned for read bytes when read failed */ +#define FCM_TIMEOUT_MSECS 500 /* Maximum number of mSecs to wait for FCM */ + +/* mtd information per set */ + +struct fsl_elbc_mtd { + struct nand_chip chip; + struct fsl_lbc_ctrl *ctrl; + + struct device *dev; + int bank; /* Chip select bank number */ + u8 __iomem *vbase; /* Chip select base virtual address */ + int page_size; /* NAND page size (0=512, 1=2048) */ + unsigned int fmr; /* FCM Flash Mode Register value */ +}; + +/* Freescale eLBC FCM controller information */ + +struct fsl_elbc_fcm_ctrl { + struct nand_controller controller; + struct fsl_elbc_mtd *chips[MAX_BANKS]; + + u8 __iomem *addr; /* Address of assigned FCM buffer */ + unsigned int page; /* Last page written to / read from */ + unsigned int read_bytes; /* Number of bytes read during command */ + unsigned int column; /* Saved column from SEQIN */ + unsigned int index; /* Pointer to next byte to 'read' */ + unsigned int status; /* status read from LTESR after last op */ + unsigned int mdr; /* UPM/FCM Data Register value */ + unsigned int use_mdr; /* Non zero if the MDR is to be set */ + unsigned int oob; /* Non zero if operating on OOB data */ + unsigned int counter; /* counter for the initializations */ + unsigned int max_bitflips; /* Saved during READ0 cmd */ +}; + +/* These map to the positions used by the FCM hardware ECC generator */ + +static int fsl_elbc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct fsl_elbc_mtd *priv = nand_get_controller_data(chip); + + if (section >= chip->ecc.steps) + return -ERANGE; + + oobregion->offset = (16 * section) + 6; + if (priv->fmr & FMR_ECCM) + oobregion->offset += 2; + + oobregion->length = chip->ecc.bytes; + + return 0; +} + +static int fsl_elbc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct fsl_elbc_mtd *priv = nand_get_controller_data(chip); + + if (section > chip->ecc.steps) + return -ERANGE; + + if (!section) { + oobregion->offset = 0; + if (mtd->writesize > 512) + oobregion->offset++; + oobregion->length = (priv->fmr & FMR_ECCM) ? 7 : 5; + } else { + oobregion->offset = (16 * section) - + ((priv->fmr & FMR_ECCM) ? 5 : 7); + if (section < chip->ecc.steps) + oobregion->length = 13; + else + oobregion->length = mtd->oobsize - oobregion->offset; + } + + return 0; +} + +static const struct mtd_ooblayout_ops fsl_elbc_ooblayout_ops = { + .ecc = fsl_elbc_ooblayout_ecc, + .free = fsl_elbc_ooblayout_free, +}; + +/* + * ELBC may use HW ECC, so that OOB offsets, that NAND core uses for bbt, + * interfere with ECC positions, that's why we implement our own descriptors. + * OOB {11, 5}, works for both SP and LP chips, with ECCM = 1 and ECCM = 0. + */ +static u8 bbt_pattern[] = {'B', 'b', 't', '0' }; +static u8 mirror_pattern[] = {'1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | + NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 11, + .len = 4, + .veroffs = 15, + .maxblocks = 4, + .pattern = bbt_pattern, +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | + NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 11, + .len = 4, + .veroffs = 15, + .maxblocks = 4, + .pattern = mirror_pattern, +}; + +/*=================================*/ + +/* + * Set up the FCM hardware block and page address fields, and the fcm + * structure addr field to point to the correct FCM buffer in memory + */ +static void set_addr(struct mtd_info *mtd, int column, int page_addr, int oob) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct fsl_elbc_mtd *priv = nand_get_controller_data(chip); + struct fsl_lbc_ctrl *ctrl = priv->ctrl; + struct fsl_lbc_regs __iomem *lbc = ctrl->regs; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand; + int buf_num; + + elbc_fcm_ctrl->page = page_addr; + + if (priv->page_size) { + /* + * large page size chip : FPAR[PI] save the lowest 6 bits, + * FBAR[BLK] save the other bits. + */ + out_be32(&lbc->fbar, page_addr >> 6); + out_be32(&lbc->fpar, + ((page_addr << FPAR_LP_PI_SHIFT) & FPAR_LP_PI) | + (oob ? FPAR_LP_MS : 0) | column); + buf_num = (page_addr & 1) << 2; + } else { + /* + * small page size chip : FPAR[PI] save the lowest 5 bits, + * FBAR[BLK] save the other bits. + */ + out_be32(&lbc->fbar, page_addr >> 5); + out_be32(&lbc->fpar, + ((page_addr << FPAR_SP_PI_SHIFT) & FPAR_SP_PI) | + (oob ? FPAR_SP_MS : 0) | column); + buf_num = page_addr & 7; + } + + elbc_fcm_ctrl->addr = priv->vbase + buf_num * 1024; + elbc_fcm_ctrl->index = column; + + /* for OOB data point to the second half of the buffer */ + if (oob) + elbc_fcm_ctrl->index += priv->page_size ? 2048 : 512; + + dev_vdbg(priv->dev, "set_addr: bank=%d, " + "elbc_fcm_ctrl->addr=0x%p (0x%p), " + "index %x, pes %d ps %d\n", + buf_num, elbc_fcm_ctrl->addr, priv->vbase, + elbc_fcm_ctrl->index, + chip->phys_erase_shift, chip->page_shift); +} + +/* + * execute FCM command and wait for it to complete + */ +static int fsl_elbc_run_command(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct fsl_elbc_mtd *priv = nand_get_controller_data(chip); + struct fsl_lbc_ctrl *ctrl = priv->ctrl; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand; + struct fsl_lbc_regs __iomem *lbc = ctrl->regs; + + /* Setup the FMR[OP] to execute without write protection */ + out_be32(&lbc->fmr, priv->fmr | 3); + if (elbc_fcm_ctrl->use_mdr) + out_be32(&lbc->mdr, elbc_fcm_ctrl->mdr); + + dev_vdbg(priv->dev, + "fsl_elbc_run_command: fmr=%08x fir=%08x fcr=%08x\n", + in_be32(&lbc->fmr), in_be32(&lbc->fir), in_be32(&lbc->fcr)); + dev_vdbg(priv->dev, + "fsl_elbc_run_command: fbar=%08x fpar=%08x " + "fbcr=%08x bank=%d\n", + in_be32(&lbc->fbar), in_be32(&lbc->fpar), + in_be32(&lbc->fbcr), priv->bank); + + ctrl->irq_status = 0; + /* execute special operation */ + out_be32(&lbc->lsor, priv->bank); + + /* wait for FCM complete flag or timeout */ + wait_event_timeout(ctrl->irq_wait, ctrl->irq_status, + FCM_TIMEOUT_MSECS * HZ/1000); + elbc_fcm_ctrl->status = ctrl->irq_status; + /* store mdr value in case it was needed */ + if (elbc_fcm_ctrl->use_mdr) + elbc_fcm_ctrl->mdr = in_be32(&lbc->mdr); + + elbc_fcm_ctrl->use_mdr = 0; + + if (elbc_fcm_ctrl->status != LTESR_CC) { + dev_info(priv->dev, + "command failed: fir %x fcr %x status %x mdr %x\n", + in_be32(&lbc->fir), in_be32(&lbc->fcr), + elbc_fcm_ctrl->status, elbc_fcm_ctrl->mdr); + return -EIO; + } + + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return 0; + + elbc_fcm_ctrl->max_bitflips = 0; + + if (elbc_fcm_ctrl->read_bytes == mtd->writesize + mtd->oobsize) { + uint32_t lteccr = in_be32(&lbc->lteccr); + /* + * if command was a full page read and the ELBC + * has the LTECCR register, then bits 12-15 (ppc order) of + * LTECCR indicates which 512 byte sub-pages had fixed errors. + * bits 28-31 are uncorrectable errors, marked elsewhere. + * for small page nand only 1 bit is used. + * if the ELBC doesn't have the lteccr register it reads 0 + * FIXME: 4 bits can be corrected on NANDs with 2k pages, so + * count the number of sub-pages with bitflips and update + * ecc_stats.corrected accordingly. + */ + if (lteccr & 0x000F000F) + out_be32(&lbc->lteccr, 0x000F000F); /* clear lteccr */ + if (lteccr & 0x000F0000) { + mtd->ecc_stats.corrected++; + elbc_fcm_ctrl->max_bitflips = 1; + } + } + + return 0; +} + +static void fsl_elbc_do_read(struct nand_chip *chip, int oob) +{ + struct fsl_elbc_mtd *priv = nand_get_controller_data(chip); + struct fsl_lbc_ctrl *ctrl = priv->ctrl; + struct fsl_lbc_regs __iomem *lbc = ctrl->regs; + + if (priv->page_size) { + out_be32(&lbc->fir, + (FIR_OP_CM0 << FIR_OP0_SHIFT) | + (FIR_OP_CA << FIR_OP1_SHIFT) | + (FIR_OP_PA << FIR_OP2_SHIFT) | + (FIR_OP_CM1 << FIR_OP3_SHIFT) | + (FIR_OP_RBW << FIR_OP4_SHIFT)); + + out_be32(&lbc->fcr, (NAND_CMD_READ0 << FCR_CMD0_SHIFT) | + (NAND_CMD_READSTART << FCR_CMD1_SHIFT)); + } else { + out_be32(&lbc->fir, + (FIR_OP_CM0 << FIR_OP0_SHIFT) | + (FIR_OP_CA << FIR_OP1_SHIFT) | + (FIR_OP_PA << FIR_OP2_SHIFT) | + (FIR_OP_RBW << FIR_OP3_SHIFT)); + + if (oob) + out_be32(&lbc->fcr, NAND_CMD_READOOB << FCR_CMD0_SHIFT); + else + out_be32(&lbc->fcr, NAND_CMD_READ0 << FCR_CMD0_SHIFT); + } +} + +/* cmdfunc send commands to the FCM */ +static void fsl_elbc_cmdfunc(struct nand_chip *chip, unsigned int command, + int column, int page_addr) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct fsl_elbc_mtd *priv = nand_get_controller_data(chip); + struct fsl_lbc_ctrl *ctrl = priv->ctrl; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand; + struct fsl_lbc_regs __iomem *lbc = ctrl->regs; + + elbc_fcm_ctrl->use_mdr = 0; + + /* clear the read buffer */ + elbc_fcm_ctrl->read_bytes = 0; + if (command != NAND_CMD_PAGEPROG) + elbc_fcm_ctrl->index = 0; + + switch (command) { + /* READ0 and READ1 read the entire buffer to use hardware ECC. */ + case NAND_CMD_READ1: + column += 256; + fallthrough; + case NAND_CMD_READ0: + dev_dbg(priv->dev, + "fsl_elbc_cmdfunc: NAND_CMD_READ0, page_addr:" + " 0x%x, column: 0x%x.\n", page_addr, column); + + + out_be32(&lbc->fbcr, 0); /* read entire page to enable ECC */ + set_addr(mtd, 0, page_addr, 0); + + elbc_fcm_ctrl->read_bytes = mtd->writesize + mtd->oobsize; + elbc_fcm_ctrl->index += column; + + fsl_elbc_do_read(chip, 0); + fsl_elbc_run_command(mtd); + return; + + /* RNDOUT moves the pointer inside the page */ + case NAND_CMD_RNDOUT: + dev_dbg(priv->dev, + "fsl_elbc_cmdfunc: NAND_CMD_RNDOUT, column: 0x%x.\n", + column); + + elbc_fcm_ctrl->index = column; + return; + + /* READOOB reads only the OOB because no ECC is performed. */ + case NAND_CMD_READOOB: + dev_vdbg(priv->dev, + "fsl_elbc_cmdfunc: NAND_CMD_READOOB, page_addr:" + " 0x%x, column: 0x%x.\n", page_addr, column); + + out_be32(&lbc->fbcr, mtd->oobsize - column); + set_addr(mtd, column, page_addr, 1); + + elbc_fcm_ctrl->read_bytes = mtd->writesize + mtd->oobsize; + + fsl_elbc_do_read(chip, 1); + fsl_elbc_run_command(mtd); + return; + + case NAND_CMD_READID: + case NAND_CMD_PARAM: + dev_vdbg(priv->dev, "fsl_elbc_cmdfunc: NAND_CMD %x\n", command); + + out_be32(&lbc->fir, (FIR_OP_CM0 << FIR_OP0_SHIFT) | + (FIR_OP_UA << FIR_OP1_SHIFT) | + (FIR_OP_RBW << FIR_OP2_SHIFT)); + out_be32(&lbc->fcr, command << FCR_CMD0_SHIFT); + /* + * although currently it's 8 bytes for READID, we always read + * the maximum 256 bytes(for PARAM) + */ + out_be32(&lbc->fbcr, 256); + elbc_fcm_ctrl->read_bytes = 256; + elbc_fcm_ctrl->use_mdr = 1; + elbc_fcm_ctrl->mdr = column; + set_addr(mtd, 0, 0, 0); + fsl_elbc_run_command(mtd); + return; + + /* ERASE1 stores the block and page address */ + case NAND_CMD_ERASE1: + dev_vdbg(priv->dev, + "fsl_elbc_cmdfunc: NAND_CMD_ERASE1, " + "page_addr: 0x%x.\n", page_addr); + set_addr(mtd, 0, page_addr, 0); + return; + + /* ERASE2 uses the block and page address from ERASE1 */ + case NAND_CMD_ERASE2: + dev_vdbg(priv->dev, "fsl_elbc_cmdfunc: NAND_CMD_ERASE2.\n"); + + out_be32(&lbc->fir, + (FIR_OP_CM0 << FIR_OP0_SHIFT) | + (FIR_OP_PA << FIR_OP1_SHIFT) | + (FIR_OP_CM2 << FIR_OP2_SHIFT) | + (FIR_OP_CW1 << FIR_OP3_SHIFT) | + (FIR_OP_RS << FIR_OP4_SHIFT)); + + out_be32(&lbc->fcr, + (NAND_CMD_ERASE1 << FCR_CMD0_SHIFT) | + (NAND_CMD_STATUS << FCR_CMD1_SHIFT) | + (NAND_CMD_ERASE2 << FCR_CMD2_SHIFT)); + + out_be32(&lbc->fbcr, 0); + elbc_fcm_ctrl->read_bytes = 0; + elbc_fcm_ctrl->use_mdr = 1; + + fsl_elbc_run_command(mtd); + return; + + /* SEQIN sets up the addr buffer and all registers except the length */ + case NAND_CMD_SEQIN: { + __be32 fcr; + dev_vdbg(priv->dev, + "fsl_elbc_cmdfunc: NAND_CMD_SEQIN/PAGE_PROG, " + "page_addr: 0x%x, column: 0x%x.\n", + page_addr, column); + + elbc_fcm_ctrl->column = column; + elbc_fcm_ctrl->use_mdr = 1; + + if (column >= mtd->writesize) { + /* OOB area */ + column -= mtd->writesize; + elbc_fcm_ctrl->oob = 1; + } else { + WARN_ON(column != 0); + elbc_fcm_ctrl->oob = 0; + } + + fcr = (NAND_CMD_STATUS << FCR_CMD1_SHIFT) | + (NAND_CMD_SEQIN << FCR_CMD2_SHIFT) | + (NAND_CMD_PAGEPROG << FCR_CMD3_SHIFT); + + if (priv->page_size) { + out_be32(&lbc->fir, + (FIR_OP_CM2 << FIR_OP0_SHIFT) | + (FIR_OP_CA << FIR_OP1_SHIFT) | + (FIR_OP_PA << FIR_OP2_SHIFT) | + (FIR_OP_WB << FIR_OP3_SHIFT) | + (FIR_OP_CM3 << FIR_OP4_SHIFT) | + (FIR_OP_CW1 << FIR_OP5_SHIFT) | + (FIR_OP_RS << FIR_OP6_SHIFT)); + } else { + out_be32(&lbc->fir, + (FIR_OP_CM0 << FIR_OP0_SHIFT) | + (FIR_OP_CM2 << FIR_OP1_SHIFT) | + (FIR_OP_CA << FIR_OP2_SHIFT) | + (FIR_OP_PA << FIR_OP3_SHIFT) | + (FIR_OP_WB << FIR_OP4_SHIFT) | + (FIR_OP_CM3 << FIR_OP5_SHIFT) | + (FIR_OP_CW1 << FIR_OP6_SHIFT) | + (FIR_OP_RS << FIR_OP7_SHIFT)); + + if (elbc_fcm_ctrl->oob) + /* OOB area --> READOOB */ + fcr |= NAND_CMD_READOOB << FCR_CMD0_SHIFT; + else + /* First 256 bytes --> READ0 */ + fcr |= NAND_CMD_READ0 << FCR_CMD0_SHIFT; + } + + out_be32(&lbc->fcr, fcr); + set_addr(mtd, column, page_addr, elbc_fcm_ctrl->oob); + return; + } + + /* PAGEPROG reuses all of the setup from SEQIN and adds the length */ + case NAND_CMD_PAGEPROG: { + dev_vdbg(priv->dev, + "fsl_elbc_cmdfunc: NAND_CMD_PAGEPROG " + "writing %d bytes.\n", elbc_fcm_ctrl->index); + + /* if the write did not start at 0 or is not a full page + * then set the exact length, otherwise use a full page + * write so the HW generates the ECC. + */ + if (elbc_fcm_ctrl->oob || elbc_fcm_ctrl->column != 0 || + elbc_fcm_ctrl->index != mtd->writesize + mtd->oobsize) + out_be32(&lbc->fbcr, + elbc_fcm_ctrl->index - elbc_fcm_ctrl->column); + else + out_be32(&lbc->fbcr, 0); + + fsl_elbc_run_command(mtd); + return; + } + + /* CMD_STATUS must read the status byte while CEB is active */ + /* Note - it does not wait for the ready line */ + case NAND_CMD_STATUS: + out_be32(&lbc->fir, + (FIR_OP_CM0 << FIR_OP0_SHIFT) | + (FIR_OP_RBW << FIR_OP1_SHIFT)); + out_be32(&lbc->fcr, NAND_CMD_STATUS << FCR_CMD0_SHIFT); + out_be32(&lbc->fbcr, 1); + set_addr(mtd, 0, 0, 0); + elbc_fcm_ctrl->read_bytes = 1; + + fsl_elbc_run_command(mtd); + + /* The chip always seems to report that it is + * write-protected, even when it is not. + */ + setbits8(elbc_fcm_ctrl->addr, NAND_STATUS_WP); + return; + + /* RESET without waiting for the ready line */ + case NAND_CMD_RESET: + dev_dbg(priv->dev, "fsl_elbc_cmdfunc: NAND_CMD_RESET.\n"); + out_be32(&lbc->fir, FIR_OP_CM0 << FIR_OP0_SHIFT); + out_be32(&lbc->fcr, NAND_CMD_RESET << FCR_CMD0_SHIFT); + fsl_elbc_run_command(mtd); + return; + + default: + dev_err(priv->dev, + "fsl_elbc_cmdfunc: error, unsupported command 0x%x.\n", + command); + } +} + +static void fsl_elbc_select_chip(struct nand_chip *chip, int cs) +{ + /* The hardware does not seem to support multiple + * chips per bank. + */ +} + +/* + * Write buf to the FCM Controller Data Buffer + */ +static void fsl_elbc_write_buf(struct nand_chip *chip, const u8 *buf, int len) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct fsl_elbc_mtd *priv = nand_get_controller_data(chip); + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand; + unsigned int bufsize = mtd->writesize + mtd->oobsize; + + if (len <= 0) { + dev_err(priv->dev, "write_buf of %d bytes", len); + elbc_fcm_ctrl->status = 0; + return; + } + + if ((unsigned int)len > bufsize - elbc_fcm_ctrl->index) { + dev_err(priv->dev, + "write_buf beyond end of buffer " + "(%d requested, %u available)\n", + len, bufsize - elbc_fcm_ctrl->index); + len = bufsize - elbc_fcm_ctrl->index; + } + + memcpy_toio(&elbc_fcm_ctrl->addr[elbc_fcm_ctrl->index], buf, len); + /* + * This is workaround for the weird elbc hangs during nand write, + * Scott Wood says: "...perhaps difference in how long it takes a + * write to make it through the localbus compared to a write to IMMR + * is causing problems, and sync isn't helping for some reason." + * Reading back the last byte helps though. + */ + in_8(&elbc_fcm_ctrl->addr[elbc_fcm_ctrl->index] + len - 1); + + elbc_fcm_ctrl->index += len; +} + +/* + * read a byte from either the FCM hardware buffer if it has any data left + * otherwise issue a command to read a single byte. + */ +static u8 fsl_elbc_read_byte(struct nand_chip *chip) +{ + struct fsl_elbc_mtd *priv = nand_get_controller_data(chip); + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand; + + /* If there are still bytes in the FCM, then use the next byte. */ + if (elbc_fcm_ctrl->index < elbc_fcm_ctrl->read_bytes) + return in_8(&elbc_fcm_ctrl->addr[elbc_fcm_ctrl->index++]); + + dev_err(priv->dev, "read_byte beyond end of buffer\n"); + return ERR_BYTE; +} + +/* + * Read from the FCM Controller Data Buffer + */ +static void fsl_elbc_read_buf(struct nand_chip *chip, u8 *buf, int len) +{ + struct fsl_elbc_mtd *priv = nand_get_controller_data(chip); + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand; + int avail; + + if (len < 0) + return; + + avail = min((unsigned int)len, + elbc_fcm_ctrl->read_bytes - elbc_fcm_ctrl->index); + memcpy_fromio(buf, &elbc_fcm_ctrl->addr[elbc_fcm_ctrl->index], avail); + elbc_fcm_ctrl->index += avail; + + if (len > avail) + dev_err(priv->dev, + "read_buf beyond end of buffer " + "(%d requested, %d available)\n", + len, avail); +} + +/* This function is called after Program and Erase Operations to + * check for success or failure. + */ +static int fsl_elbc_wait(struct nand_chip *chip) +{ + struct fsl_elbc_mtd *priv = nand_get_controller_data(chip); + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand; + + if (elbc_fcm_ctrl->status != LTESR_CC) + return NAND_STATUS_FAIL; + + /* The chip always seems to report that it is + * write-protected, even when it is not. + */ + return (elbc_fcm_ctrl->mdr & 0xff) | NAND_STATUS_WP; +} + +static int fsl_elbc_read_page(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct fsl_elbc_mtd *priv = nand_get_controller_data(chip); + struct fsl_lbc_ctrl *ctrl = priv->ctrl; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand; + + nand_read_page_op(chip, page, 0, buf, mtd->writesize); + if (oob_required) + fsl_elbc_read_buf(chip, chip->oob_poi, mtd->oobsize); + + if (fsl_elbc_wait(chip) & NAND_STATUS_FAIL) + mtd->ecc_stats.failed++; + + return elbc_fcm_ctrl->max_bitflips; +} + +/* ECC will be calculated automatically, and errors will be detected in + * waitfunc. + */ +static int fsl_elbc_write_page(struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize); + fsl_elbc_write_buf(chip, chip->oob_poi, mtd->oobsize); + + return nand_prog_page_end_op(chip); +} + +/* ECC will be calculated automatically, and errors will be detected in + * waitfunc. + */ +static int fsl_elbc_write_subpage(struct nand_chip *chip, uint32_t offset, + uint32_t data_len, const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + fsl_elbc_write_buf(chip, buf, mtd->writesize); + fsl_elbc_write_buf(chip, chip->oob_poi, mtd->oobsize); + return nand_prog_page_end_op(chip); +} + +static int fsl_elbc_chip_init(struct fsl_elbc_mtd *priv) +{ + struct fsl_lbc_ctrl *ctrl = priv->ctrl; + struct fsl_lbc_regs __iomem *lbc = ctrl->regs; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = ctrl->nand; + struct nand_chip *chip = &priv->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + + dev_dbg(priv->dev, "eLBC Set Information for bank %d\n", priv->bank); + + /* Fill in fsl_elbc_mtd structure */ + mtd->dev.parent = priv->dev; + nand_set_flash_node(chip, priv->dev->of_node); + + /* set timeout to maximum */ + priv->fmr = 15 << FMR_CWTO_SHIFT; + if (in_be32(&lbc->bank[priv->bank].or) & OR_FCM_PGS) + priv->fmr |= FMR_ECCM; + + /* fill in nand_chip structure */ + /* set up function call table */ + chip->legacy.read_byte = fsl_elbc_read_byte; + chip->legacy.write_buf = fsl_elbc_write_buf; + chip->legacy.read_buf = fsl_elbc_read_buf; + chip->legacy.select_chip = fsl_elbc_select_chip; + chip->legacy.cmdfunc = fsl_elbc_cmdfunc; + chip->legacy.waitfunc = fsl_elbc_wait; + chip->legacy.set_features = nand_get_set_features_notsupp; + chip->legacy.get_features = nand_get_set_features_notsupp; + + chip->bbt_td = &bbt_main_descr; + chip->bbt_md = &bbt_mirror_descr; + + /* set up nand options */ + chip->bbt_options = NAND_BBT_USE_FLASH; + + chip->controller = &elbc_fcm_ctrl->controller; + nand_set_controller_data(chip, priv); + + return 0; +} + +static int fsl_elbc_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct fsl_elbc_mtd *priv = nand_get_controller_data(chip); + struct fsl_lbc_ctrl *ctrl = priv->ctrl; + struct fsl_lbc_regs __iomem *lbc = ctrl->regs; + unsigned int al; + u32 br; + + /* + * if ECC was not chosen in DT, decide whether to use HW or SW ECC from + * CS Base Register + */ + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_INVALID) { + /* If CS Base Register selects full hardware ECC then use it */ + if ((in_be32(&lbc->bank[priv->bank].br) & BR_DECC) == + BR_DECC_CHK_GEN) { + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + } else { + /* otherwise fall back to default software ECC */ + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + } + } + + switch (chip->ecc.engine_type) { + /* if HW ECC was chosen, setup ecc and oob layout */ + case NAND_ECC_ENGINE_TYPE_ON_HOST: + chip->ecc.read_page = fsl_elbc_read_page; + chip->ecc.write_page = fsl_elbc_write_page; + chip->ecc.write_subpage = fsl_elbc_write_subpage; + mtd_set_ooblayout(mtd, &fsl_elbc_ooblayout_ops); + chip->ecc.size = 512; + chip->ecc.bytes = 3; + chip->ecc.strength = 1; + break; + + /* if none or SW ECC was chosen, we do not need to set anything here */ + case NAND_ECC_ENGINE_TYPE_NONE: + case NAND_ECC_ENGINE_TYPE_SOFT: + case NAND_ECC_ENGINE_TYPE_ON_DIE: + break; + + default: + return -EINVAL; + } + + /* enable/disable HW ECC checking and generating based on if HW ECC was chosen */ + br = in_be32(&lbc->bank[priv->bank].br) & ~BR_DECC; + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) + out_be32(&lbc->bank[priv->bank].br, br | BR_DECC_CHK_GEN); + else + out_be32(&lbc->bank[priv->bank].br, br | BR_DECC_OFF); + + /* calculate FMR Address Length field */ + al = 0; + if (chip->pagemask & 0xffff0000) + al++; + if (chip->pagemask & 0xff000000) + al++; + + priv->fmr |= al << FMR_AL_SHIFT; + + dev_dbg(priv->dev, "fsl_elbc_init: nand->numchips = %d\n", + nanddev_ntargets(&chip->base)); + dev_dbg(priv->dev, "fsl_elbc_init: nand->chipsize = %lld\n", + nanddev_target_size(&chip->base)); + dev_dbg(priv->dev, "fsl_elbc_init: nand->pagemask = %8x\n", + chip->pagemask); + dev_dbg(priv->dev, "fsl_elbc_init: nand->legacy.chip_delay = %d\n", + chip->legacy.chip_delay); + dev_dbg(priv->dev, "fsl_elbc_init: nand->badblockpos = %d\n", + chip->badblockpos); + dev_dbg(priv->dev, "fsl_elbc_init: nand->chip_shift = %d\n", + chip->chip_shift); + dev_dbg(priv->dev, "fsl_elbc_init: nand->page_shift = %d\n", + chip->page_shift); + dev_dbg(priv->dev, "fsl_elbc_init: nand->phys_erase_shift = %d\n", + chip->phys_erase_shift); + dev_dbg(priv->dev, "fsl_elbc_init: nand->ecc.engine_type = %d\n", + chip->ecc.engine_type); + dev_dbg(priv->dev, "fsl_elbc_init: nand->ecc.steps = %d\n", + chip->ecc.steps); + dev_dbg(priv->dev, "fsl_elbc_init: nand->ecc.bytes = %d\n", + chip->ecc.bytes); + dev_dbg(priv->dev, "fsl_elbc_init: nand->ecc.total = %d\n", + chip->ecc.total); + dev_dbg(priv->dev, "fsl_elbc_init: mtd->ooblayout = %p\n", + mtd->ooblayout); + dev_dbg(priv->dev, "fsl_elbc_init: mtd->flags = %08x\n", mtd->flags); + dev_dbg(priv->dev, "fsl_elbc_init: mtd->size = %lld\n", mtd->size); + dev_dbg(priv->dev, "fsl_elbc_init: mtd->erasesize = %d\n", + mtd->erasesize); + dev_dbg(priv->dev, "fsl_elbc_init: mtd->writesize = %d\n", + mtd->writesize); + dev_dbg(priv->dev, "fsl_elbc_init: mtd->oobsize = %d\n", + mtd->oobsize); + + /* adjust Option Register and ECC to match Flash page size */ + if (mtd->writesize == 512) { + priv->page_size = 0; + clrbits32(&lbc->bank[priv->bank].or, OR_FCM_PGS); + } else if (mtd->writesize == 2048) { + priv->page_size = 1; + setbits32(&lbc->bank[priv->bank].or, OR_FCM_PGS); + } else { + dev_err(priv->dev, + "fsl_elbc_init: page size %d is not supported\n", + mtd->writesize); + return -ENOTSUPP; + } + + return 0; +} + +static const struct nand_controller_ops fsl_elbc_controller_ops = { + .attach_chip = fsl_elbc_attach_chip, +}; + +static int fsl_elbc_chip_remove(struct fsl_elbc_mtd *priv) +{ + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = priv->ctrl->nand; + struct mtd_info *mtd = nand_to_mtd(&priv->chip); + + kfree(mtd->name); + + if (priv->vbase) + iounmap(priv->vbase); + + elbc_fcm_ctrl->chips[priv->bank] = NULL; + kfree(priv); + return 0; +} + +static DEFINE_MUTEX(fsl_elbc_nand_mutex); + +static int fsl_elbc_nand_probe(struct platform_device *pdev) +{ + struct fsl_lbc_regs __iomem *lbc; + struct fsl_elbc_mtd *priv; + struct resource res; + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl; + static const char *part_probe_types[] + = { "cmdlinepart", "RedBoot", "ofpart", NULL }; + int ret; + int bank; + struct device *dev; + struct device_node *node = pdev->dev.of_node; + struct mtd_info *mtd; + + if (!fsl_lbc_ctrl_dev || !fsl_lbc_ctrl_dev->regs) + return dev_err_probe(&pdev->dev, -EPROBE_DEFER, "lbc_ctrl_dev missing\n"); + + lbc = fsl_lbc_ctrl_dev->regs; + dev = fsl_lbc_ctrl_dev->dev; + + /* get, allocate and map the memory resource */ + ret = of_address_to_resource(node, 0, &res); + if (ret) { + dev_err(dev, "failed to get resource\n"); + return ret; + } + + /* find which chip select it is connected to */ + for (bank = 0; bank < MAX_BANKS; bank++) + if ((in_be32(&lbc->bank[bank].br) & BR_V) && + (in_be32(&lbc->bank[bank].br) & BR_MSEL) == BR_MS_FCM && + (in_be32(&lbc->bank[bank].br) & + in_be32(&lbc->bank[bank].or) & BR_BA) + == fsl_lbc_addr(res.start)) + break; + + if (bank >= MAX_BANKS) { + dev_err(dev, "address did not match any chip selects\n"); + return -ENODEV; + } + + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; + + mutex_lock(&fsl_elbc_nand_mutex); + if (!fsl_lbc_ctrl_dev->nand) { + elbc_fcm_ctrl = kzalloc_obj(*elbc_fcm_ctrl); + if (!elbc_fcm_ctrl) { + mutex_unlock(&fsl_elbc_nand_mutex); + ret = -ENOMEM; + goto err; + } + elbc_fcm_ctrl->counter++; + + nand_controller_init(&elbc_fcm_ctrl->controller); + fsl_lbc_ctrl_dev->nand = elbc_fcm_ctrl; + } else { + elbc_fcm_ctrl = fsl_lbc_ctrl_dev->nand; + } + mutex_unlock(&fsl_elbc_nand_mutex); + + elbc_fcm_ctrl->chips[bank] = priv; + priv->bank = bank; + priv->ctrl = fsl_lbc_ctrl_dev; + priv->dev = &pdev->dev; + dev_set_drvdata(priv->dev, priv); + + priv->vbase = ioremap(res.start, resource_size(&res)); + if (!priv->vbase) { + dev_err(dev, "failed to map chip region\n"); + ret = -ENOMEM; + goto err; + } + + mtd = nand_to_mtd(&priv->chip); + mtd->name = kasprintf(GFP_KERNEL, "%llx.flash", (u64)res.start); + if (!nand_to_mtd(&priv->chip)->name) { + ret = -ENOMEM; + goto err; + } + + ret = fsl_elbc_chip_init(priv); + if (ret) + goto err; + + priv->chip.controller->ops = &fsl_elbc_controller_ops; + ret = nand_scan(&priv->chip, 1); + if (ret) + goto err; + + /* First look for RedBoot table or partitions on the command + * line, these take precedence over device tree information */ + ret = mtd_device_parse_register(mtd, part_probe_types, NULL, NULL, 0); + if (ret) + goto cleanup_nand; + + pr_info("eLBC NAND device at 0x%llx, bank %d\n", + (unsigned long long)res.start, priv->bank); + + return 0; + +cleanup_nand: + nand_cleanup(&priv->chip); +err: + fsl_elbc_chip_remove(priv); + + return ret; +} + +static void fsl_elbc_nand_remove(struct platform_device *pdev) +{ + struct fsl_elbc_fcm_ctrl *elbc_fcm_ctrl = fsl_lbc_ctrl_dev->nand; + struct fsl_elbc_mtd *priv = dev_get_drvdata(&pdev->dev); + struct nand_chip *chip = &priv->chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + + fsl_elbc_chip_remove(priv); + + mutex_lock(&fsl_elbc_nand_mutex); + elbc_fcm_ctrl->counter--; + if (!elbc_fcm_ctrl->counter) { + fsl_lbc_ctrl_dev->nand = NULL; + kfree(elbc_fcm_ctrl); + } + mutex_unlock(&fsl_elbc_nand_mutex); + +} + +static const struct of_device_id fsl_elbc_nand_match[] = { + { .compatible = "fsl,elbc-fcm-nand", }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_elbc_nand_match); + +static struct platform_driver fsl_elbc_nand_driver = { + .driver = { + .name = "fsl,elbc-fcm-nand", + .of_match_table = fsl_elbc_nand_match, + }, + .probe = fsl_elbc_nand_probe, + .remove = fsl_elbc_nand_remove, +}; + +module_platform_driver(fsl_elbc_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale"); +MODULE_DESCRIPTION("Freescale Enhanced Local Bus Controller MTD NAND driver"); diff --git a/drivers/mtd/nand/raw/fsl_ifc_nand.c b/drivers/mtd/nand/raw/fsl_ifc_nand.c new file mode 100644 index 000000000..a88ac2cfa --- /dev/null +++ b/drivers/mtd/nand/raw/fsl_ifc_nand.c @@ -0,0 +1,1155 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Freescale Integrated Flash Controller NAND driver + * + * Copyright 2011-2012 Freescale Semiconductor, Inc + * + * Author: Dipen Dudhat + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ERR_BYTE 0xFF /* Value returned for read + bytes when read failed */ +#define IFC_TIMEOUT_MSECS 1000 /* Maximum timeout to wait + for IFC NAND Machine */ + +struct fsl_ifc_ctrl; + +/* mtd information per set */ +struct fsl_ifc_mtd { + struct nand_chip chip; + struct fsl_ifc_ctrl *ctrl; + + struct device *dev; + int bank; /* Chip select bank number */ + unsigned int bufnum_mask; /* bufnum = page & bufnum_mask */ + u8 __iomem *vbase; /* Chip select base virtual address */ +}; + +/* overview of the fsl ifc controller */ +struct fsl_ifc_nand_ctrl { + struct nand_controller controller; + struct fsl_ifc_mtd *chips[FSL_IFC_BANK_COUNT]; + + void __iomem *addr; /* Address of assigned IFC buffer */ + unsigned int page; /* Last page written to / read from */ + unsigned int read_bytes;/* Number of bytes read during command */ + unsigned int column; /* Saved column from SEQIN */ + unsigned int index; /* Pointer to next byte to 'read' */ + unsigned int oob; /* Non zero if operating on OOB data */ + unsigned int eccread; /* Non zero for a full-page ECC read */ + unsigned int counter; /* counter for the initializations */ + unsigned int max_bitflips; /* Saved during READ0 cmd */ +}; + +static struct fsl_ifc_nand_ctrl *ifc_nand_ctrl; + +/* + * Generic flash bbt descriptors + */ +static u8 bbt_pattern[] = {'B', 'b', 't', '0' }; +static u8 mirror_pattern[] = {'1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | + NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 2, /* 0 on 8-bit small page */ + .len = 4, + .veroffs = 6, + .maxblocks = 4, + .pattern = bbt_pattern, +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | + NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 2, /* 0 on 8-bit small page */ + .len = 4, + .veroffs = 6, + .maxblocks = 4, + .pattern = mirror_pattern, +}; + +static int fsl_ifc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section) + return -ERANGE; + + oobregion->offset = 8; + oobregion->length = chip->ecc.total; + + return 0; +} + +static int fsl_ifc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section > 1) + return -ERANGE; + + if (mtd->writesize == 512 && + !(chip->options & NAND_BUSWIDTH_16)) { + if (!section) { + oobregion->offset = 0; + oobregion->length = 5; + } else { + oobregion->offset = 6; + oobregion->length = 2; + } + + return 0; + } + + if (!section) { + oobregion->offset = 2; + oobregion->length = 6; + } else { + oobregion->offset = chip->ecc.total + 8; + oobregion->length = mtd->oobsize - oobregion->offset; + } + + return 0; +} + +static const struct mtd_ooblayout_ops fsl_ifc_ooblayout_ops = { + .ecc = fsl_ifc_ooblayout_ecc, + .free = fsl_ifc_ooblayout_free, +}; + +/* + * Set up the IFC hardware block and page address fields, and the ifc nand + * structure addr field to point to the correct IFC buffer in memory + */ +static void set_addr(struct mtd_info *mtd, int column, int page_addr, int oob) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct fsl_ifc_mtd *priv = nand_get_controller_data(chip); + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs; + int buf_num; + + ifc_nand_ctrl->page = page_addr; + /* Program ROW0/COL0 */ + ifc_out32(page_addr, &ifc->ifc_nand.row0); + ifc_out32((oob ? IFC_NAND_COL_MS : 0) | column, &ifc->ifc_nand.col0); + + buf_num = page_addr & priv->bufnum_mask; + + ifc_nand_ctrl->addr = priv->vbase + buf_num * (mtd->writesize * 2); + ifc_nand_ctrl->index = column; + + /* for OOB data point to the second half of the buffer */ + if (oob) + ifc_nand_ctrl->index += mtd->writesize; +} + +/* returns nonzero if entire page is blank */ +static int check_read_ecc(struct mtd_info *mtd, struct fsl_ifc_ctrl *ctrl, + u32 eccstat, unsigned int bufnum) +{ + return (eccstat >> ((3 - bufnum % 4) * 8)) & 15; +} + +/* + * execute IFC NAND command and wait for it to complete + */ +static void fsl_ifc_run_command(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct fsl_ifc_mtd *priv = nand_get_controller_data(chip); + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_nand_ctrl *nctrl = ifc_nand_ctrl; + struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs; + u32 eccstat; + int i; + + /* set the chip select for NAND Transaction */ + ifc_out32(priv->bank << IFC_NAND_CSEL_SHIFT, + &ifc->ifc_nand.nand_csel); + + dev_vdbg(priv->dev, + "%s: fir0=%08x fcr0=%08x\n", + __func__, + ifc_in32(&ifc->ifc_nand.nand_fir0), + ifc_in32(&ifc->ifc_nand.nand_fcr0)); + + ctrl->nand_stat = 0; + + /* start read/write seq */ + ifc_out32(IFC_NAND_SEQ_STRT_FIR_STRT, &ifc->ifc_nand.nandseq_strt); + + /* wait for command complete flag or timeout */ + wait_event_timeout(ctrl->nand_wait, ctrl->nand_stat, + msecs_to_jiffies(IFC_TIMEOUT_MSECS)); + + /* ctrl->nand_stat will be updated from IRQ context */ + if (!ctrl->nand_stat) + dev_err(priv->dev, "Controller is not responding\n"); + if (ctrl->nand_stat & IFC_NAND_EVTER_STAT_FTOER) + dev_err(priv->dev, "NAND Flash Timeout Error\n"); + if (ctrl->nand_stat & IFC_NAND_EVTER_STAT_WPER) + dev_err(priv->dev, "NAND Flash Write Protect Error\n"); + + nctrl->max_bitflips = 0; + + if (nctrl->eccread) { + int errors; + int bufnum = nctrl->page & priv->bufnum_mask; + int sector_start = bufnum * chip->ecc.steps; + int sector_end = sector_start + chip->ecc.steps - 1; + __be32 __iomem *eccstat_regs; + + eccstat_regs = ifc->ifc_nand.nand_eccstat; + eccstat = ifc_in32(&eccstat_regs[sector_start / 4]); + + for (i = sector_start; i <= sector_end; i++) { + if (i != sector_start && !(i % 4)) + eccstat = ifc_in32(&eccstat_regs[i / 4]); + + errors = check_read_ecc(mtd, ctrl, eccstat, i); + + if (errors == 15) { + /* + * Uncorrectable error. + * We'll check for blank pages later. + * + * We disable ECCER reporting due to... + * erratum IFC-A002770 -- so report it now if we + * see an uncorrectable error in ECCSTAT. + */ + ctrl->nand_stat |= IFC_NAND_EVTER_STAT_ECCER; + continue; + } + + mtd->ecc_stats.corrected += errors; + nctrl->max_bitflips = max_t(unsigned int, + nctrl->max_bitflips, + errors); + } + + nctrl->eccread = 0; + } +} + +static void fsl_ifc_do_read(struct nand_chip *chip, + int oob, + struct mtd_info *mtd) +{ + struct fsl_ifc_mtd *priv = nand_get_controller_data(chip); + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs; + + /* Program FIR/IFC_NAND_FCR0 for Small/Large page */ + if (mtd->writesize > 512) { + ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) | + (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP2_SHIFT) | + (IFC_FIR_OP_CMD1 << IFC_NAND_FIR0_OP3_SHIFT) | + (IFC_FIR_OP_RBCD << IFC_NAND_FIR0_OP4_SHIFT), + &ifc->ifc_nand.nand_fir0); + ifc_out32(0x0, &ifc->ifc_nand.nand_fir1); + + ifc_out32((NAND_CMD_READ0 << IFC_NAND_FCR0_CMD0_SHIFT) | + (NAND_CMD_READSTART << IFC_NAND_FCR0_CMD1_SHIFT), + &ifc->ifc_nand.nand_fcr0); + } else { + ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) | + (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP2_SHIFT) | + (IFC_FIR_OP_RBCD << IFC_NAND_FIR0_OP3_SHIFT), + &ifc->ifc_nand.nand_fir0); + ifc_out32(0x0, &ifc->ifc_nand.nand_fir1); + + if (oob) + ifc_out32(NAND_CMD_READOOB << + IFC_NAND_FCR0_CMD0_SHIFT, + &ifc->ifc_nand.nand_fcr0); + else + ifc_out32(NAND_CMD_READ0 << + IFC_NAND_FCR0_CMD0_SHIFT, + &ifc->ifc_nand.nand_fcr0); + } +} + +/* cmdfunc send commands to the IFC NAND Machine */ +static void fsl_ifc_cmdfunc(struct nand_chip *chip, unsigned int command, + int column, int page_addr) { + struct mtd_info *mtd = nand_to_mtd(chip); + struct fsl_ifc_mtd *priv = nand_get_controller_data(chip); + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs; + + /* clear the read buffer */ + ifc_nand_ctrl->read_bytes = 0; + if (command != NAND_CMD_PAGEPROG) + ifc_nand_ctrl->index = 0; + + switch (command) { + /* READ0 read the entire buffer to use hardware ECC. */ + case NAND_CMD_READ0: + ifc_out32(0, &ifc->ifc_nand.nand_fbcr); + set_addr(mtd, 0, page_addr, 0); + + ifc_nand_ctrl->read_bytes = mtd->writesize + mtd->oobsize; + ifc_nand_ctrl->index += column; + + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) + ifc_nand_ctrl->eccread = 1; + + fsl_ifc_do_read(chip, 0, mtd); + fsl_ifc_run_command(mtd); + return; + + /* READOOB reads only the OOB because no ECC is performed. */ + case NAND_CMD_READOOB: + ifc_out32(mtd->oobsize - column, &ifc->ifc_nand.nand_fbcr); + set_addr(mtd, column, page_addr, 1); + + ifc_nand_ctrl->read_bytes = mtd->writesize + mtd->oobsize; + + fsl_ifc_do_read(chip, 1, mtd); + fsl_ifc_run_command(mtd); + + return; + + case NAND_CMD_READID: + case NAND_CMD_PARAM: { + /* + * For READID, read 8 bytes that are currently used. + * For PARAM, read all 3 copies of 256-bytes pages. + */ + int len = 8; + int timing = IFC_FIR_OP_RB; + if (command == NAND_CMD_PARAM) { + timing = IFC_FIR_OP_RBCD; + len = 256 * 3; + } + + ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_UA << IFC_NAND_FIR0_OP1_SHIFT) | + (timing << IFC_NAND_FIR0_OP2_SHIFT), + &ifc->ifc_nand.nand_fir0); + ifc_out32(command << IFC_NAND_FCR0_CMD0_SHIFT, + &ifc->ifc_nand.nand_fcr0); + ifc_out32(column, &ifc->ifc_nand.row3); + + ifc_out32(len, &ifc->ifc_nand.nand_fbcr); + ifc_nand_ctrl->read_bytes = len; + + set_addr(mtd, 0, 0, 0); + fsl_ifc_run_command(mtd); + return; + } + + /* ERASE1 stores the block and page address */ + case NAND_CMD_ERASE1: + set_addr(mtd, 0, page_addr, 0); + return; + + /* ERASE2 uses the block and page address from ERASE1 */ + case NAND_CMD_ERASE2: + ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP1_SHIFT) | + (IFC_FIR_OP_CMD1 << IFC_NAND_FIR0_OP2_SHIFT), + &ifc->ifc_nand.nand_fir0); + + ifc_out32((NAND_CMD_ERASE1 << IFC_NAND_FCR0_CMD0_SHIFT) | + (NAND_CMD_ERASE2 << IFC_NAND_FCR0_CMD1_SHIFT), + &ifc->ifc_nand.nand_fcr0); + + ifc_out32(0, &ifc->ifc_nand.nand_fbcr); + ifc_nand_ctrl->read_bytes = 0; + fsl_ifc_run_command(mtd); + return; + + /* SEQIN sets up the addr buffer and all registers except the length */ + case NAND_CMD_SEQIN: { + u32 nand_fcr0; + ifc_nand_ctrl->column = column; + ifc_nand_ctrl->oob = 0; + + if (mtd->writesize > 512) { + nand_fcr0 = + (NAND_CMD_SEQIN << IFC_NAND_FCR0_CMD0_SHIFT) | + (NAND_CMD_STATUS << IFC_NAND_FCR0_CMD1_SHIFT) | + (NAND_CMD_PAGEPROG << IFC_NAND_FCR0_CMD2_SHIFT); + + ifc_out32( + (IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP1_SHIFT) | + (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP2_SHIFT) | + (IFC_FIR_OP_WBCD << IFC_NAND_FIR0_OP3_SHIFT) | + (IFC_FIR_OP_CMD2 << IFC_NAND_FIR0_OP4_SHIFT), + &ifc->ifc_nand.nand_fir0); + ifc_out32( + (IFC_FIR_OP_CW1 << IFC_NAND_FIR1_OP5_SHIFT) | + (IFC_FIR_OP_RDSTAT << IFC_NAND_FIR1_OP6_SHIFT) | + (IFC_FIR_OP_NOP << IFC_NAND_FIR1_OP7_SHIFT), + &ifc->ifc_nand.nand_fir1); + } else { + nand_fcr0 = ((NAND_CMD_PAGEPROG << + IFC_NAND_FCR0_CMD1_SHIFT) | + (NAND_CMD_SEQIN << + IFC_NAND_FCR0_CMD2_SHIFT) | + (NAND_CMD_STATUS << + IFC_NAND_FCR0_CMD3_SHIFT)); + + ifc_out32( + (IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_CMD2 << IFC_NAND_FIR0_OP1_SHIFT) | + (IFC_FIR_OP_CA0 << IFC_NAND_FIR0_OP2_SHIFT) | + (IFC_FIR_OP_RA0 << IFC_NAND_FIR0_OP3_SHIFT) | + (IFC_FIR_OP_WBCD << IFC_NAND_FIR0_OP4_SHIFT), + &ifc->ifc_nand.nand_fir0); + ifc_out32( + (IFC_FIR_OP_CMD1 << IFC_NAND_FIR1_OP5_SHIFT) | + (IFC_FIR_OP_CW3 << IFC_NAND_FIR1_OP6_SHIFT) | + (IFC_FIR_OP_RDSTAT << IFC_NAND_FIR1_OP7_SHIFT) | + (IFC_FIR_OP_NOP << IFC_NAND_FIR1_OP8_SHIFT), + &ifc->ifc_nand.nand_fir1); + + if (column >= mtd->writesize) + nand_fcr0 |= + NAND_CMD_READOOB << IFC_NAND_FCR0_CMD0_SHIFT; + else + nand_fcr0 |= + NAND_CMD_READ0 << IFC_NAND_FCR0_CMD0_SHIFT; + } + + if (column >= mtd->writesize) { + /* OOB area --> READOOB */ + column -= mtd->writesize; + ifc_nand_ctrl->oob = 1; + } + ifc_out32(nand_fcr0, &ifc->ifc_nand.nand_fcr0); + set_addr(mtd, column, page_addr, ifc_nand_ctrl->oob); + return; + } + + /* PAGEPROG reuses all of the setup from SEQIN and adds the length */ + case NAND_CMD_PAGEPROG: { + if (ifc_nand_ctrl->oob) { + ifc_out32(ifc_nand_ctrl->index - + ifc_nand_ctrl->column, + &ifc->ifc_nand.nand_fbcr); + } else { + ifc_out32(0, &ifc->ifc_nand.nand_fbcr); + } + + fsl_ifc_run_command(mtd); + return; + } + + case NAND_CMD_STATUS: { + void __iomem *addr; + + ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_RB << IFC_NAND_FIR0_OP1_SHIFT), + &ifc->ifc_nand.nand_fir0); + ifc_out32(NAND_CMD_STATUS << IFC_NAND_FCR0_CMD0_SHIFT, + &ifc->ifc_nand.nand_fcr0); + ifc_out32(1, &ifc->ifc_nand.nand_fbcr); + set_addr(mtd, 0, 0, 0); + ifc_nand_ctrl->read_bytes = 1; + + fsl_ifc_run_command(mtd); + + /* + * The chip always seems to report that it is + * write-protected, even when it is not. + */ + addr = ifc_nand_ctrl->addr; + if (chip->options & NAND_BUSWIDTH_16) + ifc_out16(ifc_in16(addr) | (NAND_STATUS_WP), addr); + else + ifc_out8(ifc_in8(addr) | (NAND_STATUS_WP), addr); + return; + } + + case NAND_CMD_RESET: + ifc_out32(IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT, + &ifc->ifc_nand.nand_fir0); + ifc_out32(NAND_CMD_RESET << IFC_NAND_FCR0_CMD0_SHIFT, + &ifc->ifc_nand.nand_fcr0); + fsl_ifc_run_command(mtd); + return; + + default: + dev_err(priv->dev, "%s: error, unsupported command 0x%x.\n", + __func__, command); + } +} + +static void fsl_ifc_select_chip(struct nand_chip *chip, int cs) +{ + /* The hardware does not seem to support multiple + * chips per bank. + */ +} + +/* + * Write buf to the IFC NAND Controller Data Buffer + */ +static void fsl_ifc_write_buf(struct nand_chip *chip, const u8 *buf, int len) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct fsl_ifc_mtd *priv = nand_get_controller_data(chip); + unsigned int bufsize = mtd->writesize + mtd->oobsize; + + if (len <= 0) { + dev_err(priv->dev, "%s: len %d bytes", __func__, len); + return; + } + + if ((unsigned int)len > bufsize - ifc_nand_ctrl->index) { + dev_err(priv->dev, + "%s: beyond end of buffer (%d requested, %u available)\n", + __func__, len, bufsize - ifc_nand_ctrl->index); + len = bufsize - ifc_nand_ctrl->index; + } + + memcpy_toio(ifc_nand_ctrl->addr + ifc_nand_ctrl->index, buf, len); + ifc_nand_ctrl->index += len; +} + +/* + * Read a byte from either the IFC hardware buffer + * read function for 8-bit buswidth + */ +static uint8_t fsl_ifc_read_byte(struct nand_chip *chip) +{ + struct fsl_ifc_mtd *priv = nand_get_controller_data(chip); + unsigned int offset; + + /* + * If there are still bytes in the IFC buffer, then use the + * next byte. + */ + if (ifc_nand_ctrl->index < ifc_nand_ctrl->read_bytes) { + offset = ifc_nand_ctrl->index++; + return ifc_in8(ifc_nand_ctrl->addr + offset); + } + + dev_err(priv->dev, "%s: beyond end of buffer\n", __func__); + return ERR_BYTE; +} + +/* + * Read two bytes from the IFC hardware buffer + * read function for 16-bit buswith + */ +static uint8_t fsl_ifc_read_byte16(struct nand_chip *chip) +{ + struct fsl_ifc_mtd *priv = nand_get_controller_data(chip); + uint16_t data; + + /* + * If there are still bytes in the IFC buffer, then use the + * next byte. + */ + if (ifc_nand_ctrl->index < ifc_nand_ctrl->read_bytes) { + data = ifc_in16(ifc_nand_ctrl->addr + ifc_nand_ctrl->index); + ifc_nand_ctrl->index += 2; + return (uint8_t) data; + } + + dev_err(priv->dev, "%s: beyond end of buffer\n", __func__); + return ERR_BYTE; +} + +/* + * Read from the IFC Controller Data Buffer + */ +static void fsl_ifc_read_buf(struct nand_chip *chip, u8 *buf, int len) +{ + struct fsl_ifc_mtd *priv = nand_get_controller_data(chip); + int avail; + + if (len < 0) { + dev_err(priv->dev, "%s: len %d bytes", __func__, len); + return; + } + + avail = min((unsigned int)len, + ifc_nand_ctrl->read_bytes - ifc_nand_ctrl->index); + memcpy_fromio(buf, ifc_nand_ctrl->addr + ifc_nand_ctrl->index, avail); + ifc_nand_ctrl->index += avail; + + if (len > avail) + dev_err(priv->dev, + "%s: beyond end of buffer (%d requested, %d available)\n", + __func__, len, avail); +} + +/* + * This function is called after Program and Erase Operations to + * check for success or failure. + */ +static int fsl_ifc_wait(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct fsl_ifc_mtd *priv = nand_get_controller_data(chip); + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_runtime __iomem *ifc = ctrl->rregs; + u32 nand_fsr; + int status; + + /* Use READ_STATUS command, but wait for the device to be ready */ + ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_RDSTAT << IFC_NAND_FIR0_OP1_SHIFT), + &ifc->ifc_nand.nand_fir0); + ifc_out32(NAND_CMD_STATUS << IFC_NAND_FCR0_CMD0_SHIFT, + &ifc->ifc_nand.nand_fcr0); + ifc_out32(1, &ifc->ifc_nand.nand_fbcr); + set_addr(mtd, 0, 0, 0); + ifc_nand_ctrl->read_bytes = 1; + + fsl_ifc_run_command(mtd); + + nand_fsr = ifc_in32(&ifc->ifc_nand.nand_fsr); + status = nand_fsr >> 24; + /* + * The chip always seems to report that it is + * write-protected, even when it is not. + */ + return status | NAND_STATUS_WP; +} + +/* + * The controller does not check for bitflips in erased pages, + * therefore software must check instead. + */ +static int check_erased_page(struct nand_chip *chip, u8 *buf) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + u8 *ecc = chip->oob_poi; + const int ecc_size = chip->ecc.bytes; + const int pkt_size = chip->ecc.size; + int i, res, bitflips = 0; + struct mtd_oob_region oobregion = { }; + + mtd_ooblayout_ecc(mtd, 0, &oobregion); + ecc += oobregion.offset; + + for (i = 0; i < chip->ecc.steps; ++i) { + res = nand_check_erased_ecc_chunk(buf, pkt_size, ecc, ecc_size, + NULL, 0, + chip->ecc.strength); + if (res < 0) + mtd->ecc_stats.failed++; + else + mtd->ecc_stats.corrected += res; + + bitflips = max(res, bitflips); + buf += pkt_size; + ecc += ecc_size; + } + + return bitflips; +} + +static int fsl_ifc_read_page(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct fsl_ifc_mtd *priv = nand_get_controller_data(chip); + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_nand_ctrl *nctrl = ifc_nand_ctrl; + + nand_read_page_op(chip, page, 0, buf, mtd->writesize); + if (oob_required) + fsl_ifc_read_buf(chip, chip->oob_poi, mtd->oobsize); + + if (ctrl->nand_stat & IFC_NAND_EVTER_STAT_ECCER) { + if (!oob_required) + fsl_ifc_read_buf(chip, chip->oob_poi, mtd->oobsize); + + return check_erased_page(chip, buf); + } + + 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; +} + +/* ECC will be calculated automatically, and errors will be detected in + * waitfunc. + */ +static int fsl_ifc_write_page(struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize); + fsl_ifc_write_buf(chip, chip->oob_poi, mtd->oobsize); + + return nand_prog_page_end_op(chip); +} + +static int fsl_ifc_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct fsl_ifc_mtd *priv = nand_get_controller_data(chip); + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_global __iomem *ifc_global = ctrl->gregs; + u32 csor; + + csor = ifc_in32(&ifc_global->csor_cs[priv->bank].csor); + + /* Must also set CSOR_NAND_ECC_ENC_EN if DEC_EN set */ + if (csor & CSOR_NAND_ECC_DEC_EN) { + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + mtd_set_ooblayout(mtd, &fsl_ifc_ooblayout_ops); + + /* Hardware generates ECC per 512 Bytes */ + chip->ecc.size = 512; + if ((csor & CSOR_NAND_ECC_MODE_MASK) == CSOR_NAND_ECC_MODE_4) { + chip->ecc.bytes = 8; + chip->ecc.strength = 4; + } else { + chip->ecc.bytes = 16; + chip->ecc.strength = 8; + } + } else { + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + } + + dev_dbg(priv->dev, "%s: nand->numchips = %d\n", __func__, + nanddev_ntargets(&chip->base)); + dev_dbg(priv->dev, "%s: nand->chipsize = %lld\n", __func__, + nanddev_target_size(&chip->base)); + dev_dbg(priv->dev, "%s: nand->pagemask = %8x\n", __func__, + chip->pagemask); + dev_dbg(priv->dev, "%s: nand->legacy.chip_delay = %d\n", __func__, + chip->legacy.chip_delay); + dev_dbg(priv->dev, "%s: nand->badblockpos = %d\n", __func__, + chip->badblockpos); + dev_dbg(priv->dev, "%s: nand->chip_shift = %d\n", __func__, + chip->chip_shift); + dev_dbg(priv->dev, "%s: nand->page_shift = %d\n", __func__, + chip->page_shift); + dev_dbg(priv->dev, "%s: nand->phys_erase_shift = %d\n", __func__, + chip->phys_erase_shift); + dev_dbg(priv->dev, "%s: nand->ecc.engine_type = %d\n", __func__, + chip->ecc.engine_type); + dev_dbg(priv->dev, "%s: nand->ecc.steps = %d\n", __func__, + chip->ecc.steps); + dev_dbg(priv->dev, "%s: nand->ecc.bytes = %d\n", __func__, + chip->ecc.bytes); + dev_dbg(priv->dev, "%s: nand->ecc.total = %d\n", __func__, + chip->ecc.total); + dev_dbg(priv->dev, "%s: mtd->ooblayout = %p\n", __func__, + mtd->ooblayout); + dev_dbg(priv->dev, "%s: mtd->flags = %08x\n", __func__, mtd->flags); + dev_dbg(priv->dev, "%s: mtd->size = %lld\n", __func__, mtd->size); + dev_dbg(priv->dev, "%s: mtd->erasesize = %d\n", __func__, + mtd->erasesize); + dev_dbg(priv->dev, "%s: mtd->writesize = %d\n", __func__, + mtd->writesize); + dev_dbg(priv->dev, "%s: mtd->oobsize = %d\n", __func__, + mtd->oobsize); + + return 0; +} + +static const struct nand_controller_ops fsl_ifc_controller_ops = { + .attach_chip = fsl_ifc_attach_chip, +}; + +static int fsl_ifc_sram_init(struct fsl_ifc_mtd *priv) +{ + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_runtime __iomem *ifc_runtime = ctrl->rregs; + struct fsl_ifc_global __iomem *ifc_global = ctrl->gregs; + uint32_t csor = 0, csor_8k = 0, csor_ext = 0; + uint32_t cs = priv->bank; + + if (ctrl->version < FSL_IFC_VERSION_1_1_0) + return 0; + + if (ctrl->version > FSL_IFC_VERSION_1_1_0) { + u32 ncfgr, status; + int ret; + + /* Trigger auto initialization */ + ncfgr = ifc_in32(&ifc_runtime->ifc_nand.ncfgr); + ifc_out32(ncfgr | IFC_NAND_NCFGR_SRAM_INIT_EN, &ifc_runtime->ifc_nand.ncfgr); + + /* Wait until done */ + ret = readx_poll_timeout(ifc_in32, &ifc_runtime->ifc_nand.ncfgr, + status, !(status & IFC_NAND_NCFGR_SRAM_INIT_EN), + 10, IFC_TIMEOUT_MSECS * 1000); + if (ret) + dev_err(priv->dev, "Failed to initialize SRAM!\n"); + + return ret; + } + + /* Save CSOR and CSOR_ext */ + csor = ifc_in32(&ifc_global->csor_cs[cs].csor); + csor_ext = ifc_in32(&ifc_global->csor_cs[cs].csor_ext); + + /* chage PageSize 8K and SpareSize 1K*/ + csor_8k = (csor & ~(CSOR_NAND_PGS_MASK)) | 0x0018C000; + ifc_out32(csor_8k, &ifc_global->csor_cs[cs].csor); + ifc_out32(0x0000400, &ifc_global->csor_cs[cs].csor_ext); + + /* READID */ + ifc_out32((IFC_FIR_OP_CW0 << IFC_NAND_FIR0_OP0_SHIFT) | + (IFC_FIR_OP_UA << IFC_NAND_FIR0_OP1_SHIFT) | + (IFC_FIR_OP_RB << IFC_NAND_FIR0_OP2_SHIFT), + &ifc_runtime->ifc_nand.nand_fir0); + ifc_out32(NAND_CMD_READID << IFC_NAND_FCR0_CMD0_SHIFT, + &ifc_runtime->ifc_nand.nand_fcr0); + ifc_out32(0x0, &ifc_runtime->ifc_nand.row3); + + ifc_out32(0x0, &ifc_runtime->ifc_nand.nand_fbcr); + + /* Program ROW0/COL0 */ + ifc_out32(0x0, &ifc_runtime->ifc_nand.row0); + ifc_out32(0x0, &ifc_runtime->ifc_nand.col0); + + /* set the chip select for NAND Transaction */ + ifc_out32(cs << IFC_NAND_CSEL_SHIFT, + &ifc_runtime->ifc_nand.nand_csel); + + /* start read seq */ + ifc_out32(IFC_NAND_SEQ_STRT_FIR_STRT, + &ifc_runtime->ifc_nand.nandseq_strt); + + /* wait for command complete flag or timeout */ + wait_event_timeout(ctrl->nand_wait, ctrl->nand_stat, + msecs_to_jiffies(IFC_TIMEOUT_MSECS)); + + if (ctrl->nand_stat != IFC_NAND_EVTER_STAT_OPC) { + pr_err("fsl-ifc: Failed to Initialise SRAM\n"); + return -ETIMEDOUT; + } + + /* Restore CSOR and CSOR_ext */ + ifc_out32(csor, &ifc_global->csor_cs[cs].csor); + ifc_out32(csor_ext, &ifc_global->csor_cs[cs].csor_ext); + + return 0; +} + +static int fsl_ifc_chip_init(struct fsl_ifc_mtd *priv) +{ + struct fsl_ifc_ctrl *ctrl = priv->ctrl; + struct fsl_ifc_global __iomem *ifc_global = ctrl->gregs; + struct fsl_ifc_runtime __iomem *ifc_runtime = ctrl->rregs; + struct nand_chip *chip = &priv->chip; + struct mtd_info *mtd = nand_to_mtd(&priv->chip); + u32 csor; + int ret; + + /* Fill in fsl_ifc_mtd structure */ + mtd->dev.parent = priv->dev; + + 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 */ + if ((ifc_in32(&ifc_global->cspr_cs[priv->bank].cspr)) + & CSPR_PORT_SIZE_16) + chip->legacy.read_byte = fsl_ifc_read_byte16; + else + chip->legacy.read_byte = fsl_ifc_read_byte; + + chip->legacy.write_buf = fsl_ifc_write_buf; + chip->legacy.read_buf = fsl_ifc_read_buf; + chip->legacy.select_chip = fsl_ifc_select_chip; + chip->legacy.cmdfunc = fsl_ifc_cmdfunc; + chip->legacy.waitfunc = fsl_ifc_wait; + chip->legacy.set_features = nand_get_set_features_notsupp; + chip->legacy.get_features = nand_get_set_features_notsupp; + + chip->bbt_td = &bbt_main_descr; + chip->bbt_md = &bbt_mirror_descr; + + ifc_out32(0x0, &ifc_runtime->ifc_nand.ncfgr); + + /* set up nand options */ + chip->bbt_options = NAND_BBT_USE_FLASH; + chip->options = NAND_NO_SUBPAGE_WRITE; + + if (ifc_in32(&ifc_global->cspr_cs[priv->bank].cspr) + & CSPR_PORT_SIZE_16) { + chip->legacy.read_byte = fsl_ifc_read_byte16; + chip->options |= NAND_BUSWIDTH_16; + } else { + chip->legacy.read_byte = fsl_ifc_read_byte; + } + + chip->controller = &ifc_nand_ctrl->controller; + nand_set_controller_data(chip, priv); + + chip->ecc.read_page = fsl_ifc_read_page; + chip->ecc.write_page = fsl_ifc_write_page; + + csor = ifc_in32(&ifc_global->csor_cs[priv->bank].csor); + + switch (csor & CSOR_NAND_PGS_MASK) { + case CSOR_NAND_PGS_512: + if (!(chip->options & NAND_BUSWIDTH_16)) { + /* Avoid conflict with bad block marker */ + bbt_main_descr.offs = 0; + bbt_mirror_descr.offs = 0; + } + + priv->bufnum_mask = 15; + break; + + case CSOR_NAND_PGS_2K: + priv->bufnum_mask = 3; + break; + + case CSOR_NAND_PGS_4K: + priv->bufnum_mask = 1; + break; + + case CSOR_NAND_PGS_8K: + priv->bufnum_mask = 0; + break; + + default: + dev_err(priv->dev, "bad csor %#x: bad page size\n", csor); + return -ENODEV; + } + + ret = fsl_ifc_sram_init(priv); + if (ret) + return ret; + + /* + * As IFC version 2.0.0 has 16KB of internal SRAM as compared to older + * versions which had 8KB. Hence bufnum mask needs to be updated. + */ + if (ctrl->version >= FSL_IFC_VERSION_2_0_0) + priv->bufnum_mask = (priv->bufnum_mask * 2) + 1; + + return 0; +} + +static int fsl_ifc_chip_remove(struct fsl_ifc_mtd *priv) +{ + struct mtd_info *mtd = nand_to_mtd(&priv->chip); + + kfree(mtd->name); + + if (priv->vbase) + iounmap(priv->vbase); + + ifc_nand_ctrl->chips[priv->bank] = NULL; + + return 0; +} + +static int match_bank(struct fsl_ifc_global __iomem *ifc_global, int bank, + phys_addr_t addr) +{ + u32 cspr = ifc_in32(&ifc_global->cspr_cs[bank].cspr); + + if (!(cspr & CSPR_V)) + return 0; + if ((cspr & CSPR_MSEL) != CSPR_MSEL_NAND) + return 0; + + return (cspr & CSPR_BA) == convert_ifc_address(addr); +} + +static DEFINE_MUTEX(fsl_ifc_nand_mutex); + +static int fsl_ifc_nand_probe(struct platform_device *dev) +{ + struct fsl_ifc_runtime __iomem *ifc; + struct fsl_ifc_mtd *priv; + struct resource res; + static const char *part_probe_types[] + = { "cmdlinepart", "RedBoot", "ofpart", NULL }; + int ret; + int bank; + struct device_node *node = dev->dev.of_node; + struct mtd_info *mtd; + + if (!fsl_ifc_ctrl_dev || !fsl_ifc_ctrl_dev->rregs) + return -ENODEV; + ifc = fsl_ifc_ctrl_dev->rregs; + + /* get, allocate and map the memory resource */ + ret = of_address_to_resource(node, 0, &res); + if (ret) { + dev_err(&dev->dev, "%s: failed to get resource\n", __func__); + return ret; + } + + /* find which chip select it is connected to */ + for (bank = 0; bank < fsl_ifc_ctrl_dev->banks; bank++) { + if (match_bank(fsl_ifc_ctrl_dev->gregs, bank, res.start)) + break; + } + + if (bank >= fsl_ifc_ctrl_dev->banks) { + dev_err(&dev->dev, "%s: address did not match any chip selects\n", + __func__); + return -ENODEV; + } + + priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + mutex_lock(&fsl_ifc_nand_mutex); + if (!fsl_ifc_ctrl_dev->nand) { + ifc_nand_ctrl = kzalloc_obj(*ifc_nand_ctrl); + if (!ifc_nand_ctrl) { + mutex_unlock(&fsl_ifc_nand_mutex); + return -ENOMEM; + } + + ifc_nand_ctrl->read_bytes = 0; + ifc_nand_ctrl->index = 0; + ifc_nand_ctrl->addr = NULL; + fsl_ifc_ctrl_dev->nand = ifc_nand_ctrl; + + nand_controller_init(&ifc_nand_ctrl->controller); + } else { + ifc_nand_ctrl = fsl_ifc_ctrl_dev->nand; + } + mutex_unlock(&fsl_ifc_nand_mutex); + + ifc_nand_ctrl->chips[bank] = priv; + priv->bank = bank; + priv->ctrl = fsl_ifc_ctrl_dev; + priv->dev = &dev->dev; + + priv->vbase = ioremap(res.start, resource_size(&res)); + if (!priv->vbase) { + dev_err(priv->dev, "%s: failed to map chip region\n", __func__); + ret = -ENOMEM; + goto err; + } + + dev_set_drvdata(priv->dev, priv); + + ifc_out32(IFC_NAND_EVTER_EN_OPC_EN | + IFC_NAND_EVTER_EN_FTOER_EN | + IFC_NAND_EVTER_EN_WPER_EN, + &ifc->ifc_nand.nand_evter_en); + + /* enable NAND Machine Interrupts */ + ifc_out32(IFC_NAND_EVTER_INTR_OPCIR_EN | + IFC_NAND_EVTER_INTR_FTOERIR_EN | + IFC_NAND_EVTER_INTR_WPERIR_EN, + &ifc->ifc_nand.nand_evter_intr_en); + + mtd = nand_to_mtd(&priv->chip); + mtd->name = kasprintf(GFP_KERNEL, "%llx.flash", (u64)res.start); + if (!mtd->name) { + ret = -ENOMEM; + goto err; + } + + ret = fsl_ifc_chip_init(priv); + if (ret) + goto err; + + priv->chip.controller->ops = &fsl_ifc_controller_ops; + ret = nand_scan(&priv->chip, 1); + if (ret) + goto err; + + /* First look for RedBoot table or partitions on the command + * line, these take precedence over device tree information */ + ret = mtd_device_parse_register(mtd, part_probe_types, NULL, NULL, 0); + if (ret) + goto cleanup_nand; + + dev_info(priv->dev, "IFC NAND device at 0x%llx, bank %d\n", + (unsigned long long)res.start, priv->bank); + + return 0; + +cleanup_nand: + nand_cleanup(&priv->chip); +err: + fsl_ifc_chip_remove(priv); + + return ret; +} + +static void fsl_ifc_nand_remove(struct platform_device *dev) +{ + struct fsl_ifc_mtd *priv = dev_get_drvdata(&dev->dev); + struct nand_chip *chip = &priv->chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + + fsl_ifc_chip_remove(priv); + + mutex_lock(&fsl_ifc_nand_mutex); + ifc_nand_ctrl->counter--; + if (!ifc_nand_ctrl->counter) { + fsl_ifc_ctrl_dev->nand = NULL; + kfree(ifc_nand_ctrl); + } + mutex_unlock(&fsl_ifc_nand_mutex); +} + +static const struct of_device_id fsl_ifc_nand_match[] = { + { + .compatible = "fsl,ifc-nand", + }, + {} +}; +MODULE_DEVICE_TABLE(of, fsl_ifc_nand_match); + +static struct platform_driver fsl_ifc_nand_driver = { + .driver = { + .name = "fsl,ifc-nand", + .of_match_table = fsl_ifc_nand_match, + }, + .probe = fsl_ifc_nand_probe, + .remove = fsl_ifc_nand_remove, +}; + +module_platform_driver(fsl_ifc_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale"); +MODULE_DESCRIPTION("Freescale Integrated Flash Controller MTD NAND driver"); diff --git a/drivers/mtd/nand/raw/fsl_upm.c b/drivers/mtd/nand/raw/fsl_upm.c new file mode 100644 index 000000000..f4dc990a8 --- /dev/null +++ b/drivers/mtd/nand/raw/fsl_upm.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Freescale UPM NAND driver. + * + * Copyright © 2007-2008 MontaVista Software, Inc. + * + * Author: Anton Vorontsov + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct fsl_upm_nand { + struct nand_controller base; + struct device *dev; + struct nand_chip chip; + struct fsl_upm upm; + uint8_t upm_addr_offset; + uint8_t upm_cmd_offset; + void __iomem *io_base; + struct gpio_desc *rnb_gpio[NAND_MAX_CHIPS]; + uint32_t mchip_offsets[NAND_MAX_CHIPS]; + uint32_t mchip_count; + uint32_t mchip_number; +}; + +static inline struct fsl_upm_nand *to_fsl_upm_nand(struct mtd_info *mtdinfo) +{ + return container_of(mtd_to_nand(mtdinfo), struct fsl_upm_nand, + chip); +} + +static int fun_chip_init(struct fsl_upm_nand *fun, + const struct device_node *upm_np, + const struct resource *io_res) +{ + struct mtd_info *mtd = nand_to_mtd(&fun->chip); + int ret; + struct device_node *flash_np; + + fun->chip.ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + fun->chip.ecc.algo = NAND_ECC_ALGO_HAMMING; + fun->chip.controller = &fun->base; + mtd->dev.parent = fun->dev; + + flash_np = of_get_next_child(upm_np, NULL); + if (!flash_np) + return -ENODEV; + + nand_set_flash_node(&fun->chip, flash_np); + mtd->name = devm_kasprintf(fun->dev, GFP_KERNEL, "0x%llx.%pOFn", + (u64)io_res->start, + flash_np); + if (!mtd->name) { + ret = -ENOMEM; + goto err; + } + + ret = nand_scan(&fun->chip, fun->mchip_count); + if (ret) + goto err; + + ret = mtd_device_register(mtd, NULL, 0); +err: + of_node_put(flash_np); + return ret; +} + +static int func_exec_instr(struct nand_chip *chip, + const struct nand_op_instr *instr) +{ + struct fsl_upm_nand *fun = to_fsl_upm_nand(nand_to_mtd(chip)); + u32 mar, reg_offs = fun->mchip_offsets[fun->mchip_number]; + unsigned int i; + const u8 *out; + u8 *in; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + fsl_upm_start_pattern(&fun->upm, fun->upm_cmd_offset); + mar = (instr->ctx.cmd.opcode << (32 - fun->upm.width)) | + reg_offs; + fsl_upm_run_pattern(&fun->upm, fun->io_base + reg_offs, mar); + fsl_upm_end_pattern(&fun->upm); + return 0; + + case NAND_OP_ADDR_INSTR: + fsl_upm_start_pattern(&fun->upm, fun->upm_addr_offset); + for (i = 0; i < instr->ctx.addr.naddrs; i++) { + mar = (instr->ctx.addr.addrs[i] << (32 - fun->upm.width)) | + reg_offs; + fsl_upm_run_pattern(&fun->upm, fun->io_base + reg_offs, mar); + } + fsl_upm_end_pattern(&fun->upm); + return 0; + + case NAND_OP_DATA_IN_INSTR: + in = instr->ctx.data.buf.in; + for (i = 0; i < instr->ctx.data.len; i++) + in[i] = in_8(fun->io_base + reg_offs); + return 0; + + case NAND_OP_DATA_OUT_INSTR: + out = instr->ctx.data.buf.out; + for (i = 0; i < instr->ctx.data.len; i++) + out_8(fun->io_base + reg_offs, out[i]); + return 0; + + case NAND_OP_WAITRDY_INSTR: + if (!fun->rnb_gpio[fun->mchip_number]) + return nand_soft_waitrdy(chip, instr->ctx.waitrdy.timeout_ms); + + return nand_gpio_waitrdy(chip, fun->rnb_gpio[fun->mchip_number], + instr->ctx.waitrdy.timeout_ms); + + default: + return -EINVAL; + } + + return 0; +} + +static int fun_exec_op(struct nand_chip *chip, const struct nand_operation *op, + bool check_only) +{ + struct fsl_upm_nand *fun = to_fsl_upm_nand(nand_to_mtd(chip)); + unsigned int i; + int ret; + + if (op->cs >= NAND_MAX_CHIPS) + return -EINVAL; + + if (check_only) + return 0; + + fun->mchip_number = op->cs; + + for (i = 0; i < op->ninstrs; i++) { + ret = func_exec_instr(chip, &op->instrs[i]); + if (ret) + return ret; + + if (op->instrs[i].delay_ns) + ndelay(op->instrs[i].delay_ns); + } + + return 0; +} + +static const struct nand_controller_ops fun_ops = { + .exec_op = fun_exec_op, +}; + +static int fun_probe(struct platform_device *ofdev) +{ + struct fsl_upm_nand *fun; + struct resource *io_res; + const __be32 *prop; + int ret; + int size; + int i; + + fun = devm_kzalloc(&ofdev->dev, sizeof(*fun), GFP_KERNEL); + if (!fun) + return -ENOMEM; + + fun->io_base = devm_platform_get_and_ioremap_resource(ofdev, 0, &io_res); + if (IS_ERR(fun->io_base)) + return PTR_ERR(fun->io_base); + + ret = fsl_upm_find(io_res->start, &fun->upm); + if (ret) { + dev_err(&ofdev->dev, "can't find UPM\n"); + return ret; + } + + prop = of_get_property(ofdev->dev.of_node, "fsl,upm-addr-offset", + &size); + if (!prop || size != sizeof(uint32_t)) { + dev_err(&ofdev->dev, "can't get UPM address offset\n"); + return -EINVAL; + } + fun->upm_addr_offset = *prop; + + prop = of_get_property(ofdev->dev.of_node, "fsl,upm-cmd-offset", &size); + if (!prop || size != sizeof(uint32_t)) { + dev_err(&ofdev->dev, "can't get UPM command offset\n"); + return -EINVAL; + } + fun->upm_cmd_offset = *prop; + + prop = of_get_property(ofdev->dev.of_node, + "fsl,upm-addr-line-cs-offsets", &size); + if (prop && (size / sizeof(uint32_t)) > 0) { + fun->mchip_count = size / sizeof(uint32_t); + if (fun->mchip_count >= NAND_MAX_CHIPS) { + dev_err(&ofdev->dev, "too much multiple chips\n"); + return -EINVAL; + } + for (i = 0; i < fun->mchip_count; i++) + fun->mchip_offsets[i] = be32_to_cpu(prop[i]); + } else { + fun->mchip_count = 1; + } + + for (i = 0; i < fun->mchip_count; i++) { + fun->rnb_gpio[i] = devm_gpiod_get_index_optional(&ofdev->dev, + NULL, i, + GPIOD_IN); + if (IS_ERR(fun->rnb_gpio[i])) { + dev_err(&ofdev->dev, "RNB gpio #%d is invalid\n", i); + return PTR_ERR(fun->rnb_gpio[i]); + } + } + + nand_controller_init(&fun->base); + fun->base.ops = &fun_ops; + fun->dev = &ofdev->dev; + + ret = fun_chip_init(fun, ofdev->dev.of_node, io_res); + if (ret) + return ret; + + dev_set_drvdata(&ofdev->dev, fun); + + return 0; +} + +static void fun_remove(struct platform_device *ofdev) +{ + struct fsl_upm_nand *fun = dev_get_drvdata(&ofdev->dev); + struct nand_chip *chip = &fun->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + ret = mtd_device_unregister(mtd); + WARN_ON(ret); + nand_cleanup(chip); +} + +static const struct of_device_id of_fun_match[] = { + { .compatible = "fsl,upm-nand" }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_fun_match); + +static struct platform_driver of_fun_driver = { + .driver = { + .name = "fsl,upm-nand", + .of_match_table = of_fun_match, + }, + .probe = fun_probe, + .remove = fun_remove, +}; + +module_platform_driver(of_fun_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Anton Vorontsov "); +MODULE_DESCRIPTION("Driver for NAND chips working through Freescale " + "LocalBus User-Programmable Machine"); diff --git a/drivers/mtd/nand/raw/fsmc_nand.c b/drivers/mtd/nand/raw/fsmc_nand.c new file mode 100644 index 000000000..b13b2b0c3 --- /dev/null +++ b/drivers/mtd/nand/raw/fsmc_nand.c @@ -0,0 +1,1242 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ST Microelectronics + * Flexible Static Memory Controller (FSMC) + * Driver for NAND portions + * + * Copyright © 2010 ST Microelectronics + * Vipin Kumar + * Ashish Priyadarshi + * + * Based on drivers/mtd/nand/nomadik_nand.c (removed in v3.8) + * Copyright © 2007 STMicroelectronics Pvt. Ltd. + * Copyright © 2009 Alessandro Rubini + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* fsmc controller registers for NOR flash */ +#define CTRL 0x0 + /* ctrl register definitions */ + #define BANK_ENABLE BIT(0) + #define MUXED BIT(1) + #define NOR_DEV (2 << 2) + #define WIDTH_16 BIT(4) + #define RSTPWRDWN BIT(6) + #define WPROT BIT(7) + #define WRT_ENABLE BIT(12) + #define WAIT_ENB BIT(13) + +#define CTRL_TIM 0x4 + /* ctrl_tim register definitions */ + +#define FSMC_NOR_BANK_SZ 0x8 +#define FSMC_NOR_REG_SIZE 0x40 + +#define FSMC_NOR_REG(base, bank, reg) ((base) + \ + (FSMC_NOR_BANK_SZ * (bank)) + \ + (reg)) + +/* fsmc controller registers for NAND flash */ +#define FSMC_PC 0x00 + /* pc register definitions */ + #define FSMC_RESET BIT(0) + #define FSMC_WAITON BIT(1) + #define FSMC_ENABLE BIT(2) + #define FSMC_DEVTYPE_NAND BIT(3) + #define FSMC_DEVWID_16 BIT(4) + #define FSMC_ECCEN BIT(6) + #define FSMC_ECCPLEN_256 BIT(7) + #define FSMC_TCLR_SHIFT (9) + #define FSMC_TCLR_MASK (0xF) + #define FSMC_TAR_SHIFT (13) + #define FSMC_TAR_MASK (0xF) +#define STS 0x04 + /* sts register definitions */ + #define FSMC_CODE_RDY BIT(15) +#define COMM 0x08 + /* comm register definitions */ + #define FSMC_TSET_SHIFT 0 + #define FSMC_TSET_MASK 0xFF + #define FSMC_TWAIT_SHIFT 8 + #define FSMC_TWAIT_MASK 0xFF + #define FSMC_THOLD_SHIFT 16 + #define FSMC_THOLD_MASK 0xFF + #define FSMC_THIZ_SHIFT 24 + #define FSMC_THIZ_MASK 0xFF +#define ATTRIB 0x0C +#define IOATA 0x10 +#define ECC1 0x14 +#define ECC2 0x18 +#define ECC3 0x1C +#define FSMC_NAND_BANK_SZ 0x20 + +#define FSMC_BUSY_WAIT_TIMEOUT (1 * HZ) + +/* + * According to SPEAr300 Reference Manual (RM0082) + * TOUDEL = 7ns (Output delay from the flip-flops to the board) + * TINDEL = 5ns (Input delay from the board to the flipflop) + */ +#define TOUTDEL 7000 +#define TINDEL 5000 + +struct fsmc_nand_timings { + u8 tclr; + u8 tar; + u8 thiz; + u8 thold; + u8 twait; + u8 tset; +}; + +enum access_mode { + USE_DMA_ACCESS = 1, + USE_WORD_ACCESS, +}; + +/** + * struct fsmc_nand_data - structure for FSMC NAND device state + * + * @base: Inherit from the nand_controller struct + * @pid: Part ID on the AMBA PrimeCell format + * @nand: Chip related info for a NAND flash. + * + * @bank: Bank number for probed device. + * @dev: Parent device + * @mode: Access mode + * @clk: Clock structure for FSMC. + * + * @read_dma_chan: DMA channel for read access + * @write_dma_chan: DMA channel for write access to NAND + * @dma_access_complete: Completion structure + * + * @dev_timings: NAND timings + * + * @data_pa: NAND Physical port for Data. + * @data_va: NAND port for Data. + * @cmd_va: NAND port for Command. + * @addr_va: NAND port for Address. + * @regs_va: Registers base address for a given bank. + */ +struct fsmc_nand_data { + struct nand_controller base; + u32 pid; + struct nand_chip nand; + + unsigned int bank; + struct device *dev; + enum access_mode mode; + struct clk *clk; + + /* DMA related objects */ + struct dma_chan *read_dma_chan; + struct dma_chan *write_dma_chan; + struct completion dma_access_complete; + + struct fsmc_nand_timings *dev_timings; + + dma_addr_t data_pa; + void __iomem *data_va; + void __iomem *cmd_va; + void __iomem *addr_va; + void __iomem *regs_va; +}; + +static int fsmc_ecc1_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section >= chip->ecc.steps) + return -ERANGE; + + oobregion->offset = (section * 16) + 2; + oobregion->length = 3; + + return 0; +} + +static int fsmc_ecc1_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section >= chip->ecc.steps) + return -ERANGE; + + oobregion->offset = (section * 16) + 8; + + if (section < chip->ecc.steps - 1) + oobregion->length = 8; + else + oobregion->length = mtd->oobsize - oobregion->offset; + + return 0; +} + +static const struct mtd_ooblayout_ops fsmc_ecc1_ooblayout_ops = { + .ecc = fsmc_ecc1_ooblayout_ecc, + .free = fsmc_ecc1_ooblayout_free, +}; + +/* + * ECC placement definitions in oobfree type format. + * There are 13 bytes of ecc for every 512 byte block and it has to be read + * consecutively and immediately after the 512 byte data block for hardware to + * generate the error bit offsets in 512 byte data. + */ +static int fsmc_ecc4_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section >= chip->ecc.steps) + return -ERANGE; + + oobregion->length = chip->ecc.bytes; + + if (!section && mtd->writesize <= 512) + oobregion->offset = 0; + else + oobregion->offset = (section * 16) + 2; + + return 0; +} + +static int fsmc_ecc4_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section >= chip->ecc.steps) + return -ERANGE; + + oobregion->offset = (section * 16) + 15; + + if (section < chip->ecc.steps - 1) + oobregion->length = 3; + else + oobregion->length = mtd->oobsize - oobregion->offset; + + return 0; +} + +static const struct mtd_ooblayout_ops fsmc_ecc4_ooblayout_ops = { + .ecc = fsmc_ecc4_ooblayout_ecc, + .free = fsmc_ecc4_ooblayout_free, +}; + +static inline struct fsmc_nand_data *nand_to_fsmc(struct nand_chip *chip) +{ + return container_of(chip, struct fsmc_nand_data, nand); +} + +/* + * fsmc_nand_setup - FSMC (Flexible Static Memory Controller) init routine + * + * This routine initializes timing parameters related to NAND memory access in + * FSMC registers + */ +static void fsmc_nand_setup(struct fsmc_nand_data *host, + struct fsmc_nand_timings *tims) +{ + u32 value = FSMC_DEVTYPE_NAND | FSMC_ENABLE | FSMC_WAITON; + u32 tclr, tar, thiz, thold, twait, tset; + + tclr = (tims->tclr & FSMC_TCLR_MASK) << FSMC_TCLR_SHIFT; + tar = (tims->tar & FSMC_TAR_MASK) << FSMC_TAR_SHIFT; + thiz = (tims->thiz & FSMC_THIZ_MASK) << FSMC_THIZ_SHIFT; + thold = (tims->thold & FSMC_THOLD_MASK) << FSMC_THOLD_SHIFT; + twait = (tims->twait & FSMC_TWAIT_MASK) << FSMC_TWAIT_SHIFT; + tset = (tims->tset & FSMC_TSET_MASK) << FSMC_TSET_SHIFT; + + if (host->nand.options & NAND_BUSWIDTH_16) + value |= FSMC_DEVWID_16; + + writel_relaxed(value | tclr | tar, host->regs_va + FSMC_PC); + writel_relaxed(thiz | thold | twait | tset, host->regs_va + COMM); + writel_relaxed(thiz | thold | twait | tset, host->regs_va + ATTRIB); +} + +static int fsmc_calc_timings(struct fsmc_nand_data *host, + const struct nand_sdr_timings *sdrt, + struct fsmc_nand_timings *tims) +{ + unsigned long hclk = clk_get_rate(host->clk); + unsigned long hclkn = NSEC_PER_SEC / hclk; + u32 thiz, thold, twait, tset, twait_min; + + if (sdrt->tRC_min < 30000) + return -EOPNOTSUPP; + + tims->tar = DIV_ROUND_UP(sdrt->tAR_min / 1000, hclkn) - 1; + if (tims->tar > FSMC_TAR_MASK) + tims->tar = FSMC_TAR_MASK; + tims->tclr = DIV_ROUND_UP(sdrt->tCLR_min / 1000, hclkn) - 1; + if (tims->tclr > FSMC_TCLR_MASK) + tims->tclr = FSMC_TCLR_MASK; + + thiz = sdrt->tCS_min - sdrt->tWP_min; + tims->thiz = DIV_ROUND_UP(thiz / 1000, hclkn); + + thold = sdrt->tDH_min; + if (thold < sdrt->tCH_min) + thold = sdrt->tCH_min; + if (thold < sdrt->tCLH_min) + thold = sdrt->tCLH_min; + if (thold < sdrt->tWH_min) + thold = sdrt->tWH_min; + if (thold < sdrt->tALH_min) + thold = sdrt->tALH_min; + if (thold < sdrt->tREH_min) + thold = sdrt->tREH_min; + tims->thold = DIV_ROUND_UP(thold / 1000, hclkn); + if (tims->thold == 0) + tims->thold = 1; + else if (tims->thold > FSMC_THOLD_MASK) + tims->thold = FSMC_THOLD_MASK; + + tset = max(sdrt->tCS_min - sdrt->tWP_min, + sdrt->tCEA_max - sdrt->tREA_max); + tims->tset = DIV_ROUND_UP(tset / 1000, hclkn) - 1; + if (tims->tset == 0) + tims->tset = 1; + else if (tims->tset > FSMC_TSET_MASK) + tims->tset = FSMC_TSET_MASK; + + /* + * According to SPEAr300 Reference Manual (RM0082) which gives more + * information related to FSMSC timings than the SPEAr600 one (RM0305), + * twait >= tCEA - (tset * TCLK) + TOUTDEL + TINDEL + */ + twait_min = sdrt->tCEA_max - ((tims->tset + 1) * hclkn * 1000) + + TOUTDEL + TINDEL; + twait = max3(sdrt->tRP_min, sdrt->tWP_min, twait_min); + + tims->twait = DIV_ROUND_UP(twait / 1000, hclkn) - 1; + if (tims->twait == 0) + tims->twait = 1; + else if (tims->twait > FSMC_TWAIT_MASK) + tims->twait = FSMC_TWAIT_MASK; + + return 0; +} + +static int fsmc_setup_interface(struct nand_chip *nand, int csline, + const struct nand_interface_config *conf) +{ + struct fsmc_nand_data *host = nand_to_fsmc(nand); + struct fsmc_nand_timings tims; + const struct nand_sdr_timings *sdrt; + int ret; + + sdrt = nand_get_sdr_timings(conf); + if (IS_ERR(sdrt)) + return PTR_ERR(sdrt); + + ret = fsmc_calc_timings(host, sdrt, &tims); + if (ret) + return ret; + + if (csline == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + fsmc_nand_setup(host, &tims); + + return 0; +} + +/* + * fsmc_enable_hwecc - Enables Hardware ECC through FSMC registers + */ +static void fsmc_enable_hwecc(struct nand_chip *chip, int mode) +{ + struct fsmc_nand_data *host = nand_to_fsmc(chip); + + writel_relaxed(readl(host->regs_va + FSMC_PC) & ~FSMC_ECCPLEN_256, + host->regs_va + FSMC_PC); + writel_relaxed(readl(host->regs_va + FSMC_PC) & ~FSMC_ECCEN, + host->regs_va + FSMC_PC); + writel_relaxed(readl(host->regs_va + FSMC_PC) | FSMC_ECCEN, + host->regs_va + FSMC_PC); +} + +/* + * fsmc_read_hwecc_ecc4 - Hardware ECC calculator for ecc4 option supported by + * FSMC. ECC is 13 bytes for 512 bytes of data (supports error correction up to + * max of 8-bits) + */ +static int fsmc_read_hwecc_ecc4(struct nand_chip *chip, const u8 *data, + u8 *ecc) +{ + struct fsmc_nand_data *host = nand_to_fsmc(chip); + u32 ecc_tmp; + unsigned long deadline = jiffies + FSMC_BUSY_WAIT_TIMEOUT; + + do { + if (readl_relaxed(host->regs_va + STS) & FSMC_CODE_RDY) + break; + + cond_resched(); + } while (!time_after_eq(jiffies, deadline)); + + if (time_after_eq(jiffies, deadline)) { + dev_err(host->dev, "calculate ecc timed out\n"); + return -ETIMEDOUT; + } + + ecc_tmp = readl_relaxed(host->regs_va + ECC1); + ecc[0] = ecc_tmp; + ecc[1] = ecc_tmp >> 8; + ecc[2] = ecc_tmp >> 16; + ecc[3] = ecc_tmp >> 24; + + ecc_tmp = readl_relaxed(host->regs_va + ECC2); + ecc[4] = ecc_tmp; + ecc[5] = ecc_tmp >> 8; + ecc[6] = ecc_tmp >> 16; + ecc[7] = ecc_tmp >> 24; + + ecc_tmp = readl_relaxed(host->regs_va + ECC3); + ecc[8] = ecc_tmp; + ecc[9] = ecc_tmp >> 8; + ecc[10] = ecc_tmp >> 16; + ecc[11] = ecc_tmp >> 24; + + ecc_tmp = readl_relaxed(host->regs_va + STS); + ecc[12] = ecc_tmp >> 16; + + return 0; +} + +/* + * fsmc_read_hwecc_ecc1 - Hardware ECC calculator for ecc1 option supported by + * FSMC. ECC is 3 bytes for 512 bytes of data (supports error correction up to + * max of 1-bit) + */ +static int fsmc_read_hwecc_ecc1(struct nand_chip *chip, const u8 *data, + u8 *ecc) +{ + struct fsmc_nand_data *host = nand_to_fsmc(chip); + u32 ecc_tmp; + + ecc_tmp = readl_relaxed(host->regs_va + ECC1); + ecc[0] = ecc_tmp; + ecc[1] = ecc_tmp >> 8; + ecc[2] = ecc_tmp >> 16; + + return 0; +} + +static int fsmc_correct_ecc1(struct nand_chip *chip, + unsigned char *buf, + unsigned char *read_ecc, + unsigned char *calc_ecc) +{ + bool sm_order = chip->ecc.options & NAND_ECC_SOFT_HAMMING_SM_ORDER; + + return ecc_sw_hamming_correct(buf, read_ecc, calc_ecc, + chip->ecc.size, sm_order); +} + +/* Count the number of 0's in buff upto a max of max_bits */ +static int count_written_bits(u8 *buff, int size, int max_bits) +{ + int k, written_bits = 0; + + for (k = 0; k < size; k++) { + written_bits += hweight8(~buff[k]); + if (written_bits > max_bits) + break; + } + + return written_bits; +} + +static void dma_complete(void *param) +{ + struct fsmc_nand_data *host = param; + + complete(&host->dma_access_complete); +} + +static int dma_xfer(struct fsmc_nand_data *host, void *buffer, int len, + enum dma_data_direction direction) +{ + struct dma_chan *chan; + struct dma_device *dma_dev; + struct dma_async_tx_descriptor *tx; + dma_addr_t dma_dst, dma_src, dma_addr; + dma_cookie_t cookie; + unsigned long flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; + int ret; + unsigned long time_left; + + if (direction == DMA_TO_DEVICE) + chan = host->write_dma_chan; + else if (direction == DMA_FROM_DEVICE) + chan = host->read_dma_chan; + else + return -EINVAL; + + 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; + dma_dst = host->data_pa; + } else { + dma_src = host->data_pa; + dma_dst = dma_addr; + } + + tx = dma_dev->device_prep_dma_memcpy(chan, dma_dst, dma_src, + len, flags); + if (!tx) { + dev_err(host->dev, "device_prep_dma_memcpy error\n"); + ret = -EIO; + goto unmap_dma; + } + + tx->callback = dma_complete; + tx->callback_param = host; + cookie = tx->tx_submit(tx); + + ret = dma_submit_error(cookie); + if (ret) { + dev_err(host->dev, "dma_submit_error %d\n", cookie); + goto unmap_dma; + } + + dma_async_issue_pending(chan); + + time_left = + wait_for_completion_timeout(&host->dma_access_complete, + msecs_to_jiffies(3000)); + if (time_left == 0) { + dmaengine_terminate_all(chan); + dev_err(host->dev, "wait_for_completion_timeout\n"); + ret = -ETIMEDOUT; + goto unmap_dma; + } + + ret = 0; + +unmap_dma: + dma_unmap_single(dma_dev->dev, dma_addr, len, direction); + + return ret; +} + +/* + * fsmc_write_buf - write buffer to chip + * @host: FSMC NAND controller + * @buf: data buffer + * @len: number of bytes to write + */ +static void fsmc_write_buf(struct fsmc_nand_data *host, const u8 *buf, + int len) +{ + int i; + + if (IS_ALIGNED((uintptr_t)buf, sizeof(u32)) && + IS_ALIGNED(len, sizeof(u32))) { + u32 *p = (u32 *)buf; + + len = len >> 2; + for (i = 0; i < len; i++) + writel_relaxed(p[i], host->data_va); + } else { + for (i = 0; i < len; i++) + writeb_relaxed(buf[i], host->data_va); + } +} + +/* + * fsmc_read_buf - read chip data into buffer + * @host: FSMC NAND controller + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void fsmc_read_buf(struct fsmc_nand_data *host, u8 *buf, int len) +{ + int i; + + if (IS_ALIGNED((uintptr_t)buf, sizeof(u32)) && + IS_ALIGNED(len, sizeof(u32))) { + u32 *p = (u32 *)buf; + + len = len >> 2; + for (i = 0; i < len; i++) + p[i] = readl_relaxed(host->data_va); + } else { + for (i = 0; i < len; i++) + buf[i] = readb_relaxed(host->data_va); + } +} + +/* + * fsmc_read_buf_dma - read chip data into buffer + * @host: FSMC NAND controller + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void fsmc_read_buf_dma(struct fsmc_nand_data *host, u8 *buf, + int len) +{ + dma_xfer(host, buf, len, DMA_FROM_DEVICE); +} + +/* + * fsmc_write_buf_dma - write buffer to chip + * @host: FSMC NAND controller + * @buf: data buffer + * @len: number of bytes to write + */ +static void fsmc_write_buf_dma(struct fsmc_nand_data *host, const u8 *buf, + int len) +{ + dma_xfer(host, (void *)buf, len, DMA_TO_DEVICE); +} + +/* + * fsmc_exec_op - hook called by the core to execute NAND operations + * + * This controller is simple enough and thus does not need to use the parser + * provided by the core, instead, handle every situation here. + */ +static int fsmc_exec_op(struct nand_chip *chip, const struct nand_operation *op, + bool check_only) +{ + struct fsmc_nand_data *host = nand_to_fsmc(chip); + const struct nand_op_instr *instr = NULL; + int ret = 0; + unsigned int op_id; + int i; + + if (check_only) + return 0; + + pr_debug("Executing operation [%d instructions]:\n", op->ninstrs); + + for (op_id = 0; op_id < op->ninstrs; op_id++) { + instr = &op->instrs[op_id]; + + nand_op_trace(" ", instr); + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + writeb_relaxed(instr->ctx.cmd.opcode, host->cmd_va); + break; + + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) + writeb_relaxed(instr->ctx.addr.addrs[i], + host->addr_va); + break; + + case NAND_OP_DATA_IN_INSTR: + if (host->mode == USE_DMA_ACCESS) + fsmc_read_buf_dma(host, instr->ctx.data.buf.in, + instr->ctx.data.len); + else + fsmc_read_buf(host, instr->ctx.data.buf.in, + instr->ctx.data.len); + break; + + case NAND_OP_DATA_OUT_INSTR: + if (host->mode == USE_DMA_ACCESS) + fsmc_write_buf_dma(host, + instr->ctx.data.buf.out, + instr->ctx.data.len); + else + fsmc_write_buf(host, instr->ctx.data.buf.out, + instr->ctx.data.len); + break; + + case NAND_OP_WAITRDY_INSTR: + ret = nand_soft_waitrdy(chip, + instr->ctx.waitrdy.timeout_ms); + break; + } + + if (instr->delay_ns) + ndelay(instr->delay_ns); + } + + return ret; +} + +/* + * fsmc_read_page_hwecc + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller expects OOB data read to chip->oob_poi + * @page: page number to read + * + * This routine is needed for fsmc version 8 as reading from NAND chip has to be + * performed in a strict sequence as follows: + * data(512 byte) -> ecc(13 byte) + * After this read, fsmc hardware generates and reports error data bits(up to a + * max of 8 bits) + */ +static int fsmc_read_page_hwecc(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int i, j, s, stat, eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + u8 *p = buf; + u8 *ecc_calc = chip->ecc.calc_buf; + u8 *ecc_code = chip->ecc.code_buf; + int off, len, ret, group = 0; + /* + * ecc_oob is intentionally taken as u16. In 16bit devices, we + * end up reading 14 bytes (7 words) from oob. The local array is + * to maintain word alignment + */ + u16 ecc_oob[7]; + u8 *oob = (u8 *)&ecc_oob[0]; + unsigned int max_bitflips = 0; + + for (i = 0, s = 0; s < eccsteps; s++, i += eccbytes, p += eccsize) { + nand_read_page_op(chip, page, s * eccsize, NULL, 0); + chip->ecc.hwctl(chip, NAND_ECC_READ); + ret = nand_read_data_op(chip, p, eccsize, false, false); + if (ret) + return ret; + + for (j = 0; j < eccbytes;) { + struct mtd_oob_region oobregion; + + ret = mtd_ooblayout_ecc(mtd, group++, &oobregion); + if (ret) + return ret; + + off = oobregion.offset; + len = oobregion.length; + + /* + * length is intentionally kept a higher multiple of 2 + * to read at least 13 bytes even in case of 16 bit NAND + * devices + */ + if (chip->options & NAND_BUSWIDTH_16) + len = roundup(len, 2); + + nand_read_oob_op(chip, page, off, oob + j, len); + j += len; + } + + memcpy(&ecc_code[i], oob, chip->ecc.bytes); + chip->ecc.calculate(chip, p, &ecc_calc[i]); + + stat = chip->ecc.correct(chip, p, &ecc_code[i], &ecc_calc[i]); + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + + return max_bitflips; +} + +/* + * fsmc_bch8_correct_data + * @mtd: mtd info structure + * @dat: buffer of read data + * @read_ecc: ecc read from device spare area + * @calc_ecc: ecc calculated from read data + * + * calc_ecc is a 104 bit information containing maximum of 8 error + * offset information of 13 bits each in 512 bytes of read data. + */ +static int fsmc_bch8_correct_data(struct nand_chip *chip, u8 *dat, + u8 *read_ecc, u8 *calc_ecc) +{ + struct fsmc_nand_data *host = nand_to_fsmc(chip); + u32 err_idx[8]; + u32 num_err, i; + u32 ecc1, ecc2, ecc3, ecc4; + + num_err = (readl_relaxed(host->regs_va + STS) >> 10) & 0xF; + + /* no bit flipping */ + if (likely(num_err == 0)) + return 0; + + /* too many errors */ + if (unlikely(num_err > 8)) { + /* + * This is a temporary erase check. A newly erased page read + * would result in an ecc error because the oob data is also + * erased to FF and the calculated ecc for an FF data is not + * FF..FF. + * This is a workaround to skip performing correction in case + * data is FF..FF + * + * Logic: + * For every page, each bit written as 0 is counted until these + * number of bits are greater than 8 (the maximum correction + * capability of FSMC for each 512 + 13 bytes) + */ + + int bits_ecc = count_written_bits(read_ecc, chip->ecc.bytes, 8); + int bits_data = count_written_bits(dat, chip->ecc.size, 8); + + if ((bits_ecc + bits_data) <= 8) { + if (bits_data) + memset(dat, 0xff, chip->ecc.size); + return bits_data; + } + + return -EBADMSG; + } + + /* + * ------------------- calc_ecc[] bit wise -----------|--13 bits--| + * |---idx[7]--|--.....-----|---idx[2]--||---idx[1]--||---idx[0]--| + * + * calc_ecc is a 104 bit information containing maximum of 8 error + * offset information of 13 bits each. calc_ecc is copied into a + * u64 array and error offset indexes are populated in err_idx + * array + */ + ecc1 = readl_relaxed(host->regs_va + ECC1); + ecc2 = readl_relaxed(host->regs_va + ECC2); + ecc3 = readl_relaxed(host->regs_va + ECC3); + ecc4 = readl_relaxed(host->regs_va + STS); + + err_idx[0] = (ecc1 >> 0) & 0x1FFF; + err_idx[1] = (ecc1 >> 13) & 0x1FFF; + err_idx[2] = (((ecc2 >> 0) & 0x7F) << 6) | ((ecc1 >> 26) & 0x3F); + err_idx[3] = (ecc2 >> 7) & 0x1FFF; + err_idx[4] = (((ecc3 >> 0) & 0x1) << 12) | ((ecc2 >> 20) & 0xFFF); + err_idx[5] = (ecc3 >> 1) & 0x1FFF; + err_idx[6] = (ecc3 >> 14) & 0x1FFF; + err_idx[7] = (((ecc4 >> 16) & 0xFF) << 5) | ((ecc3 >> 27) & 0x1F); + + i = 0; + while (num_err--) { + err_idx[i] ^= 3; + + if (err_idx[i] < chip->ecc.size * 8) { + int err = err_idx[i]; + + dat[err >> 3] ^= BIT(err & 7); + i++; + } + } + return i; +} + +static bool filter(struct dma_chan *chan, void *slave) +{ + chan->private = slave; + return true; +} + +static int fsmc_nand_probe_config_dt(struct platform_device *pdev, + struct fsmc_nand_data *host, + struct nand_chip *nand) +{ + struct device_node *np = pdev->dev.of_node; + u32 val; + int ret; + + nand->options = 0; + + if (!of_property_read_u32(np, "bank-width", &val)) { + if (val == 2) { + nand->options |= NAND_BUSWIDTH_16; + } 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")) + nand->options |= NAND_SKIP_BBTSCAN; + + host->dev_timings = devm_kzalloc(&pdev->dev, + sizeof(*host->dev_timings), + GFP_KERNEL); + if (!host->dev_timings) + return -ENOMEM; + + ret = of_property_read_u8_array(np, "timings", (u8 *)host->dev_timings, + sizeof(*host->dev_timings)); + if (ret) + host->dev_timings = NULL; + + /* Set default NAND bank to 0 */ + host->bank = 0; + if (!of_property_read_u32(np, "bank", &val)) { + if (val > 3) { + dev_err(&pdev->dev, "invalid bank %u\n", val); + return -EINVAL; + } + host->bank = val; + } + return 0; +} + +static int fsmc_nand_attach_chip(struct nand_chip *nand) +{ + struct mtd_info *mtd = nand_to_mtd(nand); + struct fsmc_nand_data *host = nand_to_fsmc(nand); + + if (nand->ecc.engine_type == NAND_ECC_ENGINE_TYPE_INVALID) + nand->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + + if (!nand->ecc.size) + nand->ecc.size = 512; + + if (AMBA_REV_BITS(host->pid) >= 8) { + nand->ecc.read_page = fsmc_read_page_hwecc; + nand->ecc.calculate = fsmc_read_hwecc_ecc4; + nand->ecc.correct = fsmc_bch8_correct_data; + nand->ecc.bytes = 13; + nand->ecc.strength = 8; + } + + if (AMBA_REV_BITS(host->pid) >= 8) { + switch (mtd->oobsize) { + case 16: + case 64: + case 128: + case 224: + case 256: + break; + default: + dev_warn(host->dev, + "No oob scheme defined for oobsize %d\n", + mtd->oobsize); + return -EINVAL; + } + + mtd_set_ooblayout(mtd, &fsmc_ecc4_ooblayout_ops); + + return 0; + } + + switch (nand->ecc.engine_type) { + case NAND_ECC_ENGINE_TYPE_ON_HOST: + dev_info(host->dev, "Using 1-bit HW ECC scheme\n"); + nand->ecc.calculate = fsmc_read_hwecc_ecc1; + nand->ecc.correct = fsmc_correct_ecc1; + nand->ecc.hwctl = fsmc_enable_hwecc; + nand->ecc.bytes = 3; + nand->ecc.strength = 1; + nand->ecc.options |= NAND_ECC_SOFT_HAMMING_SM_ORDER; + break; + + case NAND_ECC_ENGINE_TYPE_SOFT: + if (nand->ecc.algo == NAND_ECC_ALGO_BCH) { + dev_info(host->dev, + "Using 4-bit SW BCH ECC scheme\n"); + break; + } + break; + + case NAND_ECC_ENGINE_TYPE_ON_DIE: + break; + + default: + dev_err(host->dev, "Unsupported ECC mode!\n"); + return -ENOTSUPP; + } + + /* + * Don't set layout for BCH4 SW ECC. This will be + * generated later during BCH initialization. + */ + if (nand->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) { + switch (mtd->oobsize) { + case 16: + case 64: + case 128: + mtd_set_ooblayout(mtd, + &fsmc_ecc1_ooblayout_ops); + break; + default: + dev_warn(host->dev, + "No oob scheme defined for oobsize %d\n", + mtd->oobsize); + return -EINVAL; + } + } + + return 0; +} + +static const struct nand_controller_ops fsmc_nand_controller_ops = { + .attach_chip = fsmc_nand_attach_chip, + .exec_op = fsmc_exec_op, + .setup_interface = fsmc_setup_interface, +}; + +/** + * fsmc_nand_disable() - Disables the NAND bank + * @host: The instance to disable + */ +static void fsmc_nand_disable(struct fsmc_nand_data *host) +{ + u32 val; + + val = readl(host->regs_va + FSMC_PC); + val &= ~FSMC_ENABLE; + writel(val, host->regs_va + FSMC_PC); +} + +/* + * fsmc_nand_probe - Probe function + * @pdev: platform device structure + */ +static int __init fsmc_nand_probe(struct platform_device *pdev) +{ + struct fsmc_nand_data *host; + struct mtd_info *mtd; + struct nand_chip *nand; + struct resource *res; + void __iomem *base; + dma_cap_mask_t mask; + int ret = 0; + u32 pid; + int i; + + /* Allocate memory for the device structure (and zero it) */ + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + nand = &host->nand; + + ret = fsmc_nand_probe_config_dt(pdev, host, nand); + if (ret) + return ret; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand_data"); + host->data_va = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->data_va)) + return PTR_ERR(host->data_va); + + host->data_pa = (dma_addr_t)res->start; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand_addr"); + host->addr_va = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->addr_va)) + return PTR_ERR(host->addr_va); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "nand_cmd"); + host->cmd_va = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(host->cmd_va)) + return PTR_ERR(host->cmd_va); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "fsmc_regs"); + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + host->regs_va = base + FSMC_NOR_REG_SIZE + + (host->bank * FSMC_NAND_BANK_SZ); + + host->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(host->clk)) { + dev_err(&pdev->dev, "failed to fetch block clock\n"); + return PTR_ERR(host->clk); + } + + /* + * This device ID is actually a common AMBA ID as used on the + * AMBA PrimeCell bus. However it is not a PrimeCell. + */ + for (pid = 0, i = 0; i < 4; i++) + pid |= (readl(base + resource_size(res) - 0x20 + 4 * i) & + 255) << (i * 8); + + host->pid = pid; + + dev_info(&pdev->dev, + "FSMC device partno %03x, manufacturer %02x, revision %02x, config %02x\n", + AMBA_PART_BITS(pid), AMBA_MANF_BITS(pid), + AMBA_REV_BITS(pid), AMBA_CONFIG_BITS(pid)); + + host->dev = &pdev->dev; + + if (host->mode == USE_DMA_ACCESS) + init_completion(&host->dma_access_complete); + + /* Link all private pointers */ + mtd = nand_to_mtd(&host->nand); + nand_set_flash_node(nand, pdev->dev.of_node); + + mtd->dev.parent = &pdev->dev; + + nand->badblockbits = 7; + + if (host->mode == USE_DMA_ACCESS) { + dma_cap_zero(mask); + dma_cap_set(DMA_MEMCPY, mask); + host->read_dma_chan = dma_request_channel(mask, filter, NULL); + if (!host->read_dma_chan) { + dev_err(&pdev->dev, "Unable to get read dma channel\n"); + ret = -ENODEV; + goto disable_fsmc; + } + host->write_dma_chan = dma_request_channel(mask, filter, NULL); + if (!host->write_dma_chan) { + dev_err(&pdev->dev, "Unable to get write dma channel\n"); + ret = -ENODEV; + goto release_dma_read_chan; + } + } + + if (host->dev_timings) { + fsmc_nand_setup(host, host->dev_timings); + nand->options |= NAND_KEEP_TIMINGS; + } + + nand_controller_init(&host->base); + host->base.ops = &fsmc_nand_controller_ops; + nand->controller = &host->base; + + /* + * Scan to find existence of the device + */ + ret = nand_scan(nand, 1); + if (ret) + goto release_dma_write_chan; + + mtd->name = "nand"; + ret = mtd_device_register(mtd, NULL, 0); + if (ret) + goto cleanup_nand; + + platform_set_drvdata(pdev, host); + dev_info(&pdev->dev, "FSMC NAND driver registration successful\n"); + + return 0; + +cleanup_nand: + nand_cleanup(nand); +release_dma_write_chan: + if (host->mode == USE_DMA_ACCESS) + dma_release_channel(host->write_dma_chan); +release_dma_read_chan: + if (host->mode == USE_DMA_ACCESS) + dma_release_channel(host->read_dma_chan); +disable_fsmc: + fsmc_nand_disable(host); + + return ret; +} + +/* + * Clean up routine + */ +static void fsmc_nand_remove(struct platform_device *pdev) +{ + struct fsmc_nand_data *host = platform_get_drvdata(pdev); + + if (host) { + struct nand_chip *chip = &host->nand; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + fsmc_nand_disable(host); + + if (host->mode == USE_DMA_ACCESS) { + dma_release_channel(host->write_dma_chan); + dma_release_channel(host->read_dma_chan); + } + } +} + +#ifdef CONFIG_PM_SLEEP +static int fsmc_nand_suspend(struct device *dev) +{ + struct fsmc_nand_data *host = dev_get_drvdata(dev); + + if (host) + clk_disable_unprepare(host->clk); + + return 0; +} + +static int fsmc_nand_resume(struct device *dev) +{ + struct fsmc_nand_data *host = dev_get_drvdata(dev); + int ret; + + if (host) { + ret = clk_prepare_enable(host->clk); + if (ret) { + dev_err(dev, "failed to enable clk\n"); + return ret; + } + if (host->dev_timings) + fsmc_nand_setup(host, host->dev_timings); + nand_reset(&host->nand, 0); + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(fsmc_nand_pm_ops, fsmc_nand_suspend, fsmc_nand_resume); + +static const struct of_device_id fsmc_nand_id_table[] = { + { .compatible = "st,spear600-fsmc-nand" }, + { .compatible = "stericsson,fsmc-nand" }, + {} +}; +MODULE_DEVICE_TABLE(of, fsmc_nand_id_table); + +static struct platform_driver fsmc_nand_driver = { + .remove = fsmc_nand_remove, + .driver = { + .name = "fsmc-nand", + .of_match_table = fsmc_nand_id_table, + .pm = &fsmc_nand_pm_ops, + }, +}; + +module_platform_driver_probe(fsmc_nand_driver, fsmc_nand_probe); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Vipin Kumar , Ashish Priyadarshi"); +MODULE_DESCRIPTION("NAND driver for SPEAr Platforms"); diff --git a/drivers/mtd/nand/raw/gpio.c b/drivers/mtd/nand/raw/gpio.c new file mode 100644 index 000000000..69e5e4353 --- /dev/null +++ b/drivers/mtd/nand/raw/gpio.c @@ -0,0 +1,406 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Updated, and converted to generic GPIO based driver by Russell King. + * + * Written by Ben Dooks + * Based on 2.4 version by Mark Whittaker + * + * © 2004 Simtec Electronics + * + * Device driver for NAND flash that uses a memory mapped interface to + * read/write the NAND commands and data, and GPIO pins for control signals + * (the DT binding refers to this as "GPIO assisted NAND flash") + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct gpiomtd { + struct nand_controller base; + void __iomem *io; + void __iomem *io_sync; + struct nand_chip nand_chip; + struct gpio_nand_platdata plat; + struct gpio_desc *nce; /* Optional chip enable */ + struct gpio_desc *cle; + struct gpio_desc *ale; + struct gpio_desc *rdy; + struct gpio_desc *nwp; /* Optional write protection */ +}; + +static inline struct gpiomtd *gpio_nand_getpriv(struct mtd_info *mtd) +{ + return container_of(mtd_to_nand(mtd), struct gpiomtd, nand_chip); +} + + +#ifdef CONFIG_ARM +/* gpio_nand_dosync() + * + * Make sure the GPIO state changes occur in-order with writes to NAND + * memory region. + * Needed on PXA due to bus-reordering within the SoC itself (see section on + * I/O ordering in PXA manual (section 2.3, p35) + */ +static void gpio_nand_dosync(struct gpiomtd *gpiomtd) +{ + unsigned long tmp; + + if (gpiomtd->io_sync) { + /* + * Linux memory barriers don't cater for what's required here. + * What's required is what's here - a read from a separate + * region with a dependency on that read. + */ + tmp = readl(gpiomtd->io_sync); + asm volatile("mov %1, %0\n" : "=r" (tmp) : "r" (tmp)); + } +} +#else +static inline void gpio_nand_dosync(struct gpiomtd *gpiomtd) {} +#endif + +static int gpio_nand_exec_instr(struct nand_chip *chip, + const struct nand_op_instr *instr) +{ + struct gpiomtd *gpiomtd = gpio_nand_getpriv(nand_to_mtd(chip)); + unsigned int i; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + gpio_nand_dosync(gpiomtd); + gpiod_set_value(gpiomtd->cle, 1); + gpio_nand_dosync(gpiomtd); + writeb(instr->ctx.cmd.opcode, gpiomtd->io); + gpio_nand_dosync(gpiomtd); + gpiod_set_value(gpiomtd->cle, 0); + return 0; + + case NAND_OP_ADDR_INSTR: + gpio_nand_dosync(gpiomtd); + gpiod_set_value(gpiomtd->ale, 1); + gpio_nand_dosync(gpiomtd); + for (i = 0; i < instr->ctx.addr.naddrs; i++) + writeb(instr->ctx.addr.addrs[i], gpiomtd->io); + gpio_nand_dosync(gpiomtd); + gpiod_set_value(gpiomtd->ale, 0); + return 0; + + case NAND_OP_DATA_IN_INSTR: + gpio_nand_dosync(gpiomtd); + if ((chip->options & NAND_BUSWIDTH_16) && + !instr->ctx.data.force_8bit) + ioread16_rep(gpiomtd->io, instr->ctx.data.buf.in, + instr->ctx.data.len / 2); + else + ioread8_rep(gpiomtd->io, instr->ctx.data.buf.in, + instr->ctx.data.len); + return 0; + + case NAND_OP_DATA_OUT_INSTR: + gpio_nand_dosync(gpiomtd); + if ((chip->options & NAND_BUSWIDTH_16) && + !instr->ctx.data.force_8bit) + iowrite16_rep(gpiomtd->io, instr->ctx.data.buf.out, + instr->ctx.data.len / 2); + else + iowrite8_rep(gpiomtd->io, instr->ctx.data.buf.out, + instr->ctx.data.len); + return 0; + + case NAND_OP_WAITRDY_INSTR: + if (!gpiomtd->rdy) + return nand_soft_waitrdy(chip, instr->ctx.waitrdy.timeout_ms); + + return nand_gpio_waitrdy(chip, gpiomtd->rdy, + instr->ctx.waitrdy.timeout_ms); + + default: + return -EINVAL; + } + + return 0; +} + +static int gpio_nand_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + struct gpiomtd *gpiomtd = gpio_nand_getpriv(nand_to_mtd(chip)); + unsigned int i; + int ret = 0; + + if (check_only) + return 0; + + gpio_nand_dosync(gpiomtd); + gpiod_set_value(gpiomtd->nce, 0); + for (i = 0; i < op->ninstrs; i++) { + ret = gpio_nand_exec_instr(chip, &op->instrs[i]); + if (ret) + break; + + if (op->instrs[i].delay_ns) + ndelay(op->instrs[i].delay_ns); + } + gpio_nand_dosync(gpiomtd); + gpiod_set_value(gpiomtd->nce, 1); + + return ret; +} + +static int gpio_nand_attach_chip(struct nand_chip *chip) +{ + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT && + chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + + return 0; +} + +static const struct nand_controller_ops gpio_nand_ops = { + .exec_op = gpio_nand_exec_op, + .attach_chip = gpio_nand_attach_chip, +}; + +#ifdef CONFIG_OF +static const struct of_device_id gpio_nand_id_table[] = { + { .compatible = "gpio-control-nand" }, + {} +}; +MODULE_DEVICE_TABLE(of, gpio_nand_id_table); + +static int gpio_nand_get_config_of(const struct device *dev, + struct gpio_nand_platdata *plat) +{ + u32 val; + + if (!dev->of_node) + return -ENODEV; + + if (!of_property_read_u32(dev->of_node, "bank-width", &val)) { + if (val == 2) { + plat->options |= NAND_BUSWIDTH_16; + } else if (val != 1) { + dev_err(dev, "invalid bank-width %u\n", val); + return -EINVAL; + } + } + + if (!of_property_read_u32(dev->of_node, "chip-delay", &val)) + plat->chip_delay = val; + + return 0; +} + +static struct resource *gpio_nand_get_io_sync_of(struct platform_device *pdev) +{ + struct resource *r; + u64 addr; + + if (of_property_read_u64(pdev->dev.of_node, + "gpio-control-nand,io-sync-reg", &addr)) + return NULL; + + r = devm_kzalloc(&pdev->dev, sizeof(*r), GFP_KERNEL); + if (!r) + return NULL; + + r->start = addr; + r->end = r->start + 0x3; + r->flags = IORESOURCE_MEM; + + return r; +} +#else /* CONFIG_OF */ +static inline int gpio_nand_get_config_of(const struct device *dev, + struct gpio_nand_platdata *plat) +{ + return -ENOSYS; +} + +static inline struct resource * +gpio_nand_get_io_sync_of(struct platform_device *pdev) +{ + return NULL; +} +#endif /* CONFIG_OF */ + +static inline int gpio_nand_get_config(const struct device *dev, + struct gpio_nand_platdata *plat) +{ + int ret = gpio_nand_get_config_of(dev, plat); + + if (!ret) + return ret; + + if (dev_get_platdata(dev)) { + memcpy(plat, dev_get_platdata(dev), sizeof(*plat)); + return 0; + } + + return -EINVAL; +} + +static inline struct resource * +gpio_nand_get_io_sync(struct platform_device *pdev) +{ + struct resource *r = gpio_nand_get_io_sync_of(pdev); + + if (r) + return r; + + return platform_get_resource(pdev, IORESOURCE_MEM, 1); +} + +static void gpio_nand_remove(struct platform_device *pdev) +{ + struct gpiomtd *gpiomtd = platform_get_drvdata(pdev); + struct nand_chip *chip = &gpiomtd->nand_chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + + /* Enable write protection and disable the chip */ + if (gpiomtd->nwp && !IS_ERR(gpiomtd->nwp)) + gpiod_set_value(gpiomtd->nwp, 0); + if (gpiomtd->nce && !IS_ERR(gpiomtd->nce)) + gpiod_set_value(gpiomtd->nce, 0); +} + +static int gpio_nand_probe(struct platform_device *pdev) +{ + struct gpiomtd *gpiomtd; + struct nand_chip *chip; + struct mtd_info *mtd; + struct resource *res; + struct device *dev = &pdev->dev; + int ret = 0; + + if (!dev->of_node && !dev_get_platdata(dev)) + return -EINVAL; + + gpiomtd = devm_kzalloc(dev, sizeof(*gpiomtd), GFP_KERNEL); + if (!gpiomtd) + return -ENOMEM; + + chip = &gpiomtd->nand_chip; + + gpiomtd->io = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(gpiomtd->io)) + return PTR_ERR(gpiomtd->io); + + res = gpio_nand_get_io_sync(pdev); + if (res) { + gpiomtd->io_sync = devm_ioremap_resource(dev, res); + if (IS_ERR(gpiomtd->io_sync)) + return PTR_ERR(gpiomtd->io_sync); + } + + ret = gpio_nand_get_config(dev, &gpiomtd->plat); + if (ret) + return ret; + + /* Just enable the chip */ + gpiomtd->nce = devm_gpiod_get_optional(dev, "nce", GPIOD_OUT_HIGH); + if (IS_ERR(gpiomtd->nce)) + return PTR_ERR(gpiomtd->nce); + + /* We disable write protection once we know probe() will succeed */ + gpiomtd->nwp = devm_gpiod_get_optional(dev, "nwp", GPIOD_OUT_LOW); + if (IS_ERR(gpiomtd->nwp)) { + ret = PTR_ERR(gpiomtd->nwp); + goto out_ce; + } + + gpiomtd->ale = devm_gpiod_get(dev, "ale", GPIOD_OUT_LOW); + if (IS_ERR(gpiomtd->ale)) { + ret = PTR_ERR(gpiomtd->ale); + goto out_ce; + } + + gpiomtd->cle = devm_gpiod_get(dev, "cle", GPIOD_OUT_LOW); + if (IS_ERR(gpiomtd->cle)) { + ret = PTR_ERR(gpiomtd->cle); + goto out_ce; + } + + gpiomtd->rdy = devm_gpiod_get_optional(dev, "rdy", GPIOD_IN); + if (IS_ERR(gpiomtd->rdy)) { + ret = PTR_ERR(gpiomtd->rdy); + goto out_ce; + } + + nand_controller_init(&gpiomtd->base); + gpiomtd->base.ops = &gpio_nand_ops; + + nand_set_flash_node(chip, pdev->dev.of_node); + chip->options = gpiomtd->plat.options; + chip->controller = &gpiomtd->base; + + mtd = nand_to_mtd(chip); + mtd->dev.parent = dev; + + platform_set_drvdata(pdev, gpiomtd); + + /* Disable write protection, if wired up */ + if (gpiomtd->nwp && !IS_ERR(gpiomtd->nwp)) + gpiod_direction_output(gpiomtd->nwp, 1); + + /* + * This driver assumes that the default ECC engine should be TYPE_SOFT. + * Set ->engine_type before registering the NAND devices in order to + * provide a driver specific default value. + */ + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + + ret = nand_scan(chip, 1); + if (ret) + goto err_wp; + + if (gpiomtd->plat.adjust_parts) + gpiomtd->plat.adjust_parts(&gpiomtd->plat, mtd->size); + + ret = mtd_device_register(mtd, gpiomtd->plat.parts, + gpiomtd->plat.num_parts); + if (!ret) + return 0; + +err_wp: + if (gpiomtd->nwp && !IS_ERR(gpiomtd->nwp)) + gpiod_set_value(gpiomtd->nwp, 0); +out_ce: + if (gpiomtd->nce && !IS_ERR(gpiomtd->nce)) + gpiod_set_value(gpiomtd->nce, 0); + + return ret; +} + +static struct platform_driver gpio_nand_driver = { + .probe = gpio_nand_probe, + .remove = gpio_nand_remove, + .driver = { + .name = "gpio-nand", + .of_match_table = of_match_ptr(gpio_nand_id_table), + }, +}; + +module_platform_driver(gpio_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ben Dooks "); +MODULE_DESCRIPTION("GPIO NAND Driver"); diff --git a/drivers/mtd/nand/raw/gpmi-nand/Makefile b/drivers/mtd/nand/raw/gpmi-nand/Makefile new file mode 100644 index 000000000..247cbfcea --- /dev/null +++ b/drivers/mtd/nand/raw/gpmi-nand/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_MTD_NAND_GPMI_NAND) += gpmi-nand.o diff --git a/drivers/mtd/nand/raw/gpmi-nand/bch-regs.h b/drivers/mtd/nand/raw/gpmi-nand/bch-regs.h new file mode 100644 index 000000000..a22b8a506 --- /dev/null +++ b/drivers/mtd/nand/raw/gpmi-nand/bch-regs.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Freescale GPMI NAND Flash Driver + * + * Copyright 2008-2011 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc. + */ +#ifndef __GPMI_NAND_BCH_REGS_H +#define __GPMI_NAND_BCH_REGS_H + +#define HW_BCH_CTRL 0x00000000 +#define HW_BCH_CTRL_SET 0x00000004 +#define HW_BCH_CTRL_CLR 0x00000008 +#define HW_BCH_CTRL_TOG 0x0000000c + +#define BM_BCH_CTRL_COMPLETE_IRQ_EN (1 << 8) +#define BM_BCH_CTRL_COMPLETE_IRQ (1 << 0) + +#define HW_BCH_STATUS0 0x00000010 +#define HW_BCH_MODE 0x00000020 +#define HW_BCH_ENCODEPTR 0x00000030 +#define HW_BCH_DATAPTR 0x00000040 +#define HW_BCH_METAPTR 0x00000050 +#define HW_BCH_LAYOUTSELECT 0x00000070 + +#define HW_BCH_FLASH0LAYOUT0 0x00000080 + +#define BP_BCH_FLASH0LAYOUT0_NBLOCKS 24 +#define BM_BCH_FLASH0LAYOUT0_NBLOCKS (0xff << BP_BCH_FLASH0LAYOUT0_NBLOCKS) +#define BF_BCH_FLASH0LAYOUT0_NBLOCKS(v) \ + (((v) << BP_BCH_FLASH0LAYOUT0_NBLOCKS) & BM_BCH_FLASH0LAYOUT0_NBLOCKS) + +#define BP_BCH_FLASH0LAYOUT0_META_SIZE 16 +#define BM_BCH_FLASH0LAYOUT0_META_SIZE (0xff << BP_BCH_FLASH0LAYOUT0_META_SIZE) +#define BF_BCH_FLASH0LAYOUT0_META_SIZE(v) \ + (((v) << BP_BCH_FLASH0LAYOUT0_META_SIZE)\ + & BM_BCH_FLASH0LAYOUT0_META_SIZE) + +#define BP_BCH_FLASH0LAYOUT0_ECC0 12 +#define BM_BCH_FLASH0LAYOUT0_ECC0 (0xf << BP_BCH_FLASH0LAYOUT0_ECC0) +#define MX6Q_BP_BCH_FLASH0LAYOUT0_ECC0 11 +#define MX6Q_BM_BCH_FLASH0LAYOUT0_ECC0 (0x1f << MX6Q_BP_BCH_FLASH0LAYOUT0_ECC0) +#define BF_BCH_FLASH0LAYOUT0_ECC0(v, x) \ + (GPMI_IS_MX6(x) \ + ? (((v) << MX6Q_BP_BCH_FLASH0LAYOUT0_ECC0) \ + & MX6Q_BM_BCH_FLASH0LAYOUT0_ECC0) \ + : (((v) << BP_BCH_FLASH0LAYOUT0_ECC0) \ + & BM_BCH_FLASH0LAYOUT0_ECC0) \ + ) + +#define MX6Q_BP_BCH_FLASH0LAYOUT0_GF_13_14 10 +#define MX6Q_BM_BCH_FLASH0LAYOUT0_GF_13_14 \ + (0x1 << MX6Q_BP_BCH_FLASH0LAYOUT0_GF_13_14) +#define BF_BCH_FLASH0LAYOUT0_GF(v, x) \ + ((GPMI_IS_MX6(x) && ((v) == 14)) \ + ? (((1) << MX6Q_BP_BCH_FLASH0LAYOUT0_GF_13_14) \ + & MX6Q_BM_BCH_FLASH0LAYOUT0_GF_13_14) \ + : 0 \ + ) + +#define BP_BCH_FLASH0LAYOUT0_DATA0_SIZE 0 +#define BM_BCH_FLASH0LAYOUT0_DATA0_SIZE \ + (0xfff << BP_BCH_FLASH0LAYOUT0_DATA0_SIZE) +#define MX6Q_BM_BCH_FLASH0LAYOUT0_DATA0_SIZE \ + (0x3ff << BP_BCH_FLASH0LAYOUT0_DATA0_SIZE) +#define BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(v, x) \ + (GPMI_IS_MX6(x) \ + ? (((v) >> 2) & MX6Q_BM_BCH_FLASH0LAYOUT0_DATA0_SIZE) \ + : ((v) & BM_BCH_FLASH0LAYOUT0_DATA0_SIZE) \ + ) + +#define HW_BCH_FLASH0LAYOUT1 0x00000090 + +#define BP_BCH_FLASH0LAYOUT1_PAGE_SIZE 16 +#define BM_BCH_FLASH0LAYOUT1_PAGE_SIZE \ + (0xffff << BP_BCH_FLASH0LAYOUT1_PAGE_SIZE) +#define BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(v) \ + (((v) << BP_BCH_FLASH0LAYOUT1_PAGE_SIZE) \ + & BM_BCH_FLASH0LAYOUT1_PAGE_SIZE) + +#define BP_BCH_FLASH0LAYOUT1_ECCN 12 +#define BM_BCH_FLASH0LAYOUT1_ECCN (0xf << BP_BCH_FLASH0LAYOUT1_ECCN) +#define MX6Q_BP_BCH_FLASH0LAYOUT1_ECCN 11 +#define MX6Q_BM_BCH_FLASH0LAYOUT1_ECCN (0x1f << MX6Q_BP_BCH_FLASH0LAYOUT1_ECCN) +#define BF_BCH_FLASH0LAYOUT1_ECCN(v, x) \ + (GPMI_IS_MX6(x) \ + ? (((v) << MX6Q_BP_BCH_FLASH0LAYOUT1_ECCN) \ + & MX6Q_BM_BCH_FLASH0LAYOUT1_ECCN) \ + : (((v) << BP_BCH_FLASH0LAYOUT1_ECCN) \ + & BM_BCH_FLASH0LAYOUT1_ECCN) \ + ) + +#define MX6Q_BP_BCH_FLASH0LAYOUT1_GF_13_14 10 +#define MX6Q_BM_BCH_FLASH0LAYOUT1_GF_13_14 \ + (0x1 << MX6Q_BP_BCH_FLASH0LAYOUT1_GF_13_14) +#define BF_BCH_FLASH0LAYOUT1_GF(v, x) \ + ((GPMI_IS_MX6(x) && ((v) == 14)) \ + ? (((1) << MX6Q_BP_BCH_FLASH0LAYOUT1_GF_13_14) \ + & MX6Q_BM_BCH_FLASH0LAYOUT1_GF_13_14) \ + : 0 \ + ) + +#define BP_BCH_FLASH0LAYOUT1_DATAN_SIZE 0 +#define BM_BCH_FLASH0LAYOUT1_DATAN_SIZE \ + (0xfff << BP_BCH_FLASH0LAYOUT1_DATAN_SIZE) +#define MX6Q_BM_BCH_FLASH0LAYOUT1_DATAN_SIZE \ + (0x3ff << BP_BCH_FLASH0LAYOUT1_DATAN_SIZE) +#define BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(v, x) \ + (GPMI_IS_MX6(x) \ + ? (((v) >> 2) & MX6Q_BM_BCH_FLASH0LAYOUT1_DATAN_SIZE) \ + : ((v) & BM_BCH_FLASH0LAYOUT1_DATAN_SIZE) \ + ) + +#define HW_BCH_VERSION 0x00000160 +#endif diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c new file mode 100644 index 000000000..527165ccc --- /dev/null +++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.c @@ -0,0 +1,2935 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Freescale GPMI NAND Flash Driver + * + * Copyright (C) 2010-2015 Freescale Semiconductor, Inc. + * Copyright (C) 2008 Embedded Alley Solutions, Inc. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "gpmi-nand.h" +#include "gpmi-regs.h" +#include "bch-regs.h" + +/* Resource names for the GPMI NAND driver. */ +#define GPMI_NAND_GPMI_REGS_ADDR_RES_NAME "gpmi-nand" +#define GPMI_NAND_BCH_REGS_ADDR_RES_NAME "bch" +#define GPMI_NAND_BCH_INTERRUPT_RES_NAME "bch" + +/* Converts time to clock cycles */ +#define TO_CYCLES(duration, period) DIV_ROUND_UP_ULL(duration, period) + +#define MXS_SET_ADDR 0x4 +#define MXS_CLR_ADDR 0x8 +/* + * Clear the bit and poll it cleared. This is usually called with + * a reset address and mask being either SFTRST(bit 31) or CLKGATE + * (bit 30). + */ +static int clear_poll_bit(void __iomem *addr, u32 mask) +{ + int timeout = 0x400; + + /* clear the bit */ + writel(mask, addr + MXS_CLR_ADDR); + + /* + * SFTRST needs 3 GPMI clocks to settle, the reference manual + * recommends to wait 1us. + */ + udelay(1); + + /* poll the bit becoming clear */ + while ((readl(addr) & mask) && --timeout) + /* nothing */; + + return !timeout; +} + +#define MODULE_CLKGATE (1 << 30) +#define MODULE_SFTRST (1 << 31) +/* + * The current mxs_reset_block() will do two things: + * [1] enable the module. + * [2] reset the module. + * + * In most of the cases, it's ok. + * But in MX23, there is a hardware bug in the BCH block (see erratum #2847). + * If you try to soft reset the BCH block, it becomes unusable until + * the next hard reset. This case occurs in the NAND boot mode. When the board + * boots by NAND, the ROM of the chip will initialize the BCH blocks itself. + * So If the driver tries to reset the BCH again, the BCH will not work anymore. + * You will see a DMA timeout in this case. The bug has been fixed + * in the following chips, such as MX28. + * + * To avoid this bug, just add a new parameter `just_enable` for + * the mxs_reset_block(), and rewrite it here. + */ +static int gpmi_reset_block(void __iomem *reset_addr, bool just_enable) +{ + int ret; + int timeout = 0x400; + + /* clear and poll SFTRST */ + ret = clear_poll_bit(reset_addr, MODULE_SFTRST); + if (unlikely(ret)) + goto error; + + /* clear CLKGATE */ + writel(MODULE_CLKGATE, reset_addr + MXS_CLR_ADDR); + + if (!just_enable) { + /* set SFTRST to reset the block */ + writel(MODULE_SFTRST, reset_addr + MXS_SET_ADDR); + udelay(1); + + /* poll CLKGATE becoming set */ + while ((!(readl(reset_addr) & MODULE_CLKGATE)) && --timeout) + /* nothing */; + if (unlikely(!timeout)) + goto error; + } + + /* clear and poll SFTRST */ + ret = clear_poll_bit(reset_addr, MODULE_SFTRST); + if (unlikely(ret)) + goto error; + + /* clear and poll CLKGATE */ + ret = clear_poll_bit(reset_addr, MODULE_CLKGATE); + if (unlikely(ret)) + goto error; + + return 0; + +error: + pr_err("%s(%p): module reset timeout\n", __func__, reset_addr); + return -ETIMEDOUT; +} + +static int __gpmi_enable_clk(struct gpmi_nand_data *this, bool v) +{ + struct clk *clk; + int ret; + int i; + + for (i = 0; i < GPMI_CLK_MAX; i++) { + clk = this->resources.clock[i]; + if (!clk) + break; + + if (v) { + ret = clk_prepare_enable(clk); + if (ret) + goto err_clk; + } else { + clk_disable_unprepare(clk); + } + } + return 0; + +err_clk: + for (; i > 0; i--) + clk_disable_unprepare(this->resources.clock[i - 1]); + 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; + int ret; + + ret = pm_runtime_resume_and_get(this->dev); + if (ret < 0) + return ret; + + ret = gpmi_reset_block(r->gpmi_regs, false); + if (ret) + goto err_out; + + /* + * Reset BCH here, too. We got failures otherwise :( + * See later BCH reset for explanation of MX23 and MX28 handling + */ + ret = gpmi_reset_block(r->bch_regs, GPMI_IS_MXS(this)); + if (ret) + goto err_out; + + /* Choose NAND mode. */ + writel(BM_GPMI_CTRL1_GPMI_MODE, r->gpmi_regs + HW_GPMI_CTRL1_CLR); + + /* Set the IRQ polarity. */ + writel(BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY, + r->gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Disable Write-Protection. */ + writel(BM_GPMI_CTRL1_DEV_RESET, r->gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Select BCH ECC. */ + writel(BM_GPMI_CTRL1_BCH_MODE, r->gpmi_regs + HW_GPMI_CTRL1_SET); + + /* + * Decouple the chip select from dma channel. We use dma0 for all + * the chips, force all NAND RDY_BUSY inputs to be sourced from + * RDY_BUSY0. + */ + writel(BM_GPMI_CTRL1_DECOUPLE_CS | BM_GPMI_CTRL1_GANGED_RDYBUSY, + r->gpmi_regs + HW_GPMI_CTRL1_SET); + +err_out: + pm_runtime_put_autosuspend(this->dev); + return ret; +} + +/* This function is very useful. It is called only when the bug occur. */ +static void gpmi_dump_info(struct gpmi_nand_data *this) +{ + struct resources *r = &this->resources; + struct bch_geometry *geo = &this->bch_geometry; + u32 reg; + int i; + + dev_err(this->dev, "Show GPMI registers :\n"); + for (i = 0; i <= HW_GPMI_DEBUG / 0x10 + 1; i++) { + reg = readl(r->gpmi_regs + i * 0x10); + dev_err(this->dev, "offset 0x%.3x : 0x%.8x\n", i * 0x10, reg); + } + + /* start to print out the BCH info */ + dev_err(this->dev, "Show BCH registers :\n"); + for (i = 0; i <= HW_BCH_VERSION / 0x10 + 1; i++) { + reg = readl(r->bch_regs + i * 0x10); + dev_err(this->dev, "offset 0x%.3x : 0x%.8x\n", i * 0x10, reg); + } + dev_err(this->dev, "BCH Geometry :\n" + "GF length : %u\n" + "ECC Strength : %u\n" + "Page Size in Bytes : %u\n" + "Metadata Size in Bytes : %u\n" + "ECC0 Chunk Size in Bytes: %u\n" + "ECCn Chunk Size in Bytes: %u\n" + "ECC Chunk Count : %u\n" + "Payload Size in Bytes : %u\n" + "Auxiliary Size in Bytes: %u\n" + "Auxiliary Status Offset: %u\n" + "Block Mark Byte Offset : %u\n" + "Block Mark Bit Offset : %u\n", + geo->gf_len, + geo->ecc_strength, + geo->page_size, + geo->metadata_size, + geo->ecc0_chunk_size, + geo->eccn_chunk_size, + geo->ecc_chunk_count, + geo->payload_size, + geo->auxiliary_size, + geo->auxiliary_status_offset, + geo->block_mark_byte_offset, + geo->block_mark_bit_offset); +} + +static bool gpmi_check_ecc(struct gpmi_nand_data *this) +{ + struct nand_chip *chip = &this->nand; + struct bch_geometry *geo = &this->bch_geometry; + struct nand_device *nand = &chip->base; + struct nand_ecc_props *conf = &nand->ecc.ctx.conf; + + conf->step_size = geo->eccn_chunk_size; + conf->strength = geo->ecc_strength; + + /* Do the sanity check. */ + if (GPMI_IS_MXS(this)) { + /* The mx23/mx28 only support the GF13. */ + if (geo->gf_len == 14) + return false; + } + + if (geo->ecc_strength > this->devdata->bch_max_ecc_strength) + return false; + + if (!nand_ecc_is_strong_enough(nand)) + return false; + + return true; +} + +/* check if bbm locates in data chunk rather than ecc chunk */ +static bool bbm_in_data_chunk(struct gpmi_nand_data *this, + unsigned int *chunk_num) +{ + struct bch_geometry *geo = &this->bch_geometry; + struct nand_chip *chip = &this->nand; + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int i, j; + + if (geo->ecc0_chunk_size != geo->eccn_chunk_size) { + dev_err(this->dev, + "The size of ecc0_chunk must equal to eccn_chunk\n"); + return false; + } + + i = (mtd->writesize * 8 - geo->metadata_size * 8) / + (geo->gf_len * geo->ecc_strength + + geo->eccn_chunk_size * 8); + + j = (mtd->writesize * 8 - geo->metadata_size * 8) - + (geo->gf_len * geo->ecc_strength + + geo->eccn_chunk_size * 8) * i; + + if (j < geo->eccn_chunk_size * 8) { + *chunk_num = i+1; + dev_dbg(this->dev, "Set ecc to %d and bbm in chunk %d\n", + geo->ecc_strength, *chunk_num); + return true; + } + + return false; +} + +/* + * If we can get the ECC information from the nand chip, we do not + * need to calculate them ourselves. + * + * We may have available oob space in this case. + */ +static int set_geometry_by_ecc_info(struct gpmi_nand_data *this, + unsigned int ecc_strength, + unsigned int ecc_step) +{ + struct bch_geometry *geo = &this->bch_geometry; + struct nand_chip *chip = &this->nand; + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int block_mark_bit_offset; + + switch (ecc_step) { + case SZ_512: + geo->gf_len = 13; + break; + case SZ_1K: + geo->gf_len = 14; + break; + default: + dev_err(this->dev, + "unsupported nand chip. ecc bits : %d, ecc size : %d\n", + nanddev_get_ecc_requirements(&chip->base)->strength, + nanddev_get_ecc_requirements(&chip->base)->step_size); + return -EINVAL; + } + geo->ecc0_chunk_size = ecc_step; + geo->eccn_chunk_size = ecc_step; + geo->ecc_strength = round_up(ecc_strength, 2); + if (!gpmi_check_ecc(this)) + return -EINVAL; + + /* Keep the C >= O */ + if (geo->eccn_chunk_size < mtd->oobsize) { + dev_err(this->dev, + "unsupported nand chip. ecc size: %d, oob size : %d\n", + ecc_step, mtd->oobsize); + return -EINVAL; + } + + /* The default value, see comment in the legacy_set_geometry(). */ + geo->metadata_size = 10; + + geo->ecc_chunk_count = mtd->writesize / geo->eccn_chunk_size; + + /* + * Now, the NAND chip with 2K page(data chunk is 512byte) shows below: + * + * | P | + * |<----------------------------------------------------->| + * | | + * | (Block Mark) | + * | P' | | | | + * |<-------------------------------------------->| D | | O' | + * | |<---->| |<--->| + * V V V V V + * +---+----------+-+----------+-+----------+-+----------+-+-----+ + * | M | data |E| data |E| data |E| data |E| | + * +---+----------+-+----------+-+----------+-+----------+-+-----+ + * ^ ^ + * | O | + * |<------------>| + * | | + * + * P : the page size for BCH module. + * E : The ECC strength. + * G : the length of Galois Field. + * N : The chunk count of per page. + * M : the metasize of per page. + * C : the ecc chunk size, aka the "data" above. + * P': the nand chip's page size. + * O : the nand chip's oob size. + * O': the free oob. + * + * The formula for P is : + * + * E * G * N + * P = ------------ + P' + M + * 8 + * + * The position of block mark moves forward in the ECC-based view + * of page, and the delta is: + * + * E * G * (N - 1) + * D = (---------------- + M) + * 8 + * + * Please see the comment in legacy_set_geometry(). + * With the condition C >= O , we still can get same result. + * So the bit position of the physical block mark within the ECC-based + * view of the page is : + * (P' - D) * 8 + */ + geo->page_size = mtd->writesize + geo->metadata_size + + (geo->gf_len * geo->ecc_strength * geo->ecc_chunk_count) / 8; + + geo->payload_size = mtd->writesize; + + geo->auxiliary_status_offset = ALIGN(geo->metadata_size, 4); + geo->auxiliary_size = ALIGN(geo->metadata_size, 4) + + ALIGN(geo->ecc_chunk_count, 4); + + if (!this->swap_block_mark) + return 0; + + /* For bit swap. */ + block_mark_bit_offset = mtd->writesize * 8 - + (geo->ecc_strength * geo->gf_len * (geo->ecc_chunk_count - 1) + + geo->metadata_size * 8); + + geo->block_mark_byte_offset = block_mark_bit_offset / 8; + geo->block_mark_bit_offset = block_mark_bit_offset % 8; + return 0; +} + +/* + * Calculate the ECC strength by hand: + * E : The ECC strength. + * G : the length of Galois Field. + * N : The chunk count of per page. + * O : the oobsize of the NAND chip. + * M : the metasize of per page. + * + * The formula is : + * E * G * N + * ------------ <= (O - M) + * 8 + * + * So, we get E by: + * (O - M) * 8 + * E <= ------------- + * G * N + */ +static inline int get_ecc_strength(struct gpmi_nand_data *this) +{ + struct bch_geometry *geo = &this->bch_geometry; + struct mtd_info *mtd = nand_to_mtd(&this->nand); + int ecc_strength; + + ecc_strength = ((mtd->oobsize - geo->metadata_size) * 8) + / (geo->gf_len * geo->ecc_chunk_count); + + /* We need the minor even number. */ + return round_down(ecc_strength, 2); +} + +static int set_geometry_for_large_oob(struct gpmi_nand_data *this) +{ + struct bch_geometry *geo = &this->bch_geometry; + struct nand_chip *chip = &this->nand; + struct mtd_info *mtd = nand_to_mtd(chip); + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&chip->base); + unsigned int block_mark_bit_offset; + unsigned int max_ecc; + unsigned int bbm_chunk; + unsigned int i; + + /* sanity check for the minimum ecc nand required */ + if (!(requirements->strength > 0 && + requirements->step_size > 0)) + return -EINVAL; + geo->ecc_strength = requirements->strength; + + /* check if platform can support this nand */ + if (!gpmi_check_ecc(this)) { + dev_err(this->dev, + "unsupported NAND chip, minimum ecc required %d\n", + geo->ecc_strength); + return -EINVAL; + } + + /* calculate the maximum ecc platform can support*/ + geo->metadata_size = 10; + geo->gf_len = 14; + geo->ecc0_chunk_size = 1024; + geo->eccn_chunk_size = 1024; + geo->ecc_chunk_count = mtd->writesize / geo->eccn_chunk_size; + max_ecc = min(get_ecc_strength(this), + this->devdata->bch_max_ecc_strength); + + /* + * search a supported ecc strength that makes bbm + * located in data chunk + */ + geo->ecc_strength = max_ecc; + while (!(geo->ecc_strength < requirements->strength)) { + if (bbm_in_data_chunk(this, &bbm_chunk)) + goto geo_setting; + geo->ecc_strength -= 2; + } + + /* if none of them works, keep using the minimum ecc */ + /* nand required but changing ecc page layout */ + geo->ecc_strength = requirements->strength; + /* add extra ecc for meta data */ + geo->ecc0_chunk_size = 0; + geo->ecc_chunk_count = (mtd->writesize / geo->eccn_chunk_size) + 1; + geo->ecc_for_meta = 1; + /* check if oob can afford this extra ecc chunk */ + if (mtd->oobsize * 8 < geo->metadata_size * 8 + + geo->gf_len * geo->ecc_strength * geo->ecc_chunk_count) { + dev_err(this->dev, "unsupported NAND chip with new layout\n"); + return -EINVAL; + } + + /* calculate in which chunk bbm located */ + bbm_chunk = (mtd->writesize * 8 - geo->metadata_size * 8 - + geo->gf_len * geo->ecc_strength) / + (geo->gf_len * geo->ecc_strength + + geo->eccn_chunk_size * 8) + 1; + +geo_setting: + + geo->page_size = mtd->writesize + geo->metadata_size + + (geo->gf_len * geo->ecc_strength * geo->ecc_chunk_count) / 8; + geo->payload_size = mtd->writesize; + + /* + * The auxiliary buffer contains the metadata and the ECC status. The + * metadata is padded to the nearest 32-bit boundary. The ECC status + * contains one byte for every ECC chunk, and is also padded to the + * nearest 32-bit boundary. + */ + geo->auxiliary_status_offset = ALIGN(geo->metadata_size, 4); + geo->auxiliary_size = ALIGN(geo->metadata_size, 4) + + ALIGN(geo->ecc_chunk_count, 4); + + if (!this->swap_block_mark) + return 0; + + /* calculate the number of ecc chunk behind the bbm */ + i = (mtd->writesize / geo->eccn_chunk_size) - bbm_chunk + 1; + + block_mark_bit_offset = mtd->writesize * 8 - + (geo->ecc_strength * geo->gf_len * (geo->ecc_chunk_count - i) + + geo->metadata_size * 8); + + geo->block_mark_byte_offset = block_mark_bit_offset / 8; + geo->block_mark_bit_offset = block_mark_bit_offset % 8; + + dev_dbg(this->dev, "BCH Geometry :\n" + "GF length : %u\n" + "ECC Strength : %u\n" + "Page Size in Bytes : %u\n" + "Metadata Size in Bytes : %u\n" + "ECC0 Chunk Size in Bytes: %u\n" + "ECCn Chunk Size in Bytes: %u\n" + "ECC Chunk Count : %u\n" + "Payload Size in Bytes : %u\n" + "Auxiliary Size in Bytes: %u\n" + "Auxiliary Status Offset: %u\n" + "Block Mark Byte Offset : %u\n" + "Block Mark Bit Offset : %u\n" + "Block Mark in chunk : %u\n" + "Ecc for Meta data : %u\n", + geo->gf_len, + geo->ecc_strength, + geo->page_size, + geo->metadata_size, + geo->ecc0_chunk_size, + geo->eccn_chunk_size, + geo->ecc_chunk_count, + geo->payload_size, + geo->auxiliary_size, + geo->auxiliary_status_offset, + geo->block_mark_byte_offset, + geo->block_mark_bit_offset, + bbm_chunk, + geo->ecc_for_meta); + + return 0; +} + +static int legacy_set_geometry(struct gpmi_nand_data *this) +{ + struct bch_geometry *geo = &this->bch_geometry; + struct mtd_info *mtd = nand_to_mtd(&this->nand); + unsigned int metadata_size; + unsigned int status_size; + unsigned int block_mark_bit_offset; + + /* + * The size of the metadata can be changed, though we set it to 10 + * bytes now. But it can't be too large, because we have to save + * enough space for BCH. + */ + geo->metadata_size = 10; + + /* The default for the length of Galois Field. */ + geo->gf_len = 13; + + /* The default for chunk size. */ + geo->ecc0_chunk_size = 512; + geo->eccn_chunk_size = 512; + while (geo->eccn_chunk_size < mtd->oobsize) { + geo->ecc0_chunk_size *= 2; /* keep C >= O */ + geo->eccn_chunk_size *= 2; /* keep C >= O */ + geo->gf_len = 14; + } + + geo->ecc_chunk_count = mtd->writesize / geo->eccn_chunk_size; + + /* We use the same ECC strength for all chunks. */ + geo->ecc_strength = get_ecc_strength(this); + if (!gpmi_check_ecc(this)) { + dev_err(this->dev, + "ecc strength: %d cannot be supported by the controller (%d)\n" + "try to use minimum ecc strength that NAND chip required\n", + geo->ecc_strength, + this->devdata->bch_max_ecc_strength); + return -EINVAL; + } + + geo->page_size = mtd->writesize + geo->metadata_size + + (geo->gf_len * geo->ecc_strength * geo->ecc_chunk_count) / 8; + geo->payload_size = mtd->writesize; + + /* + * The auxiliary buffer contains the metadata and the ECC status. The + * metadata is padded to the nearest 32-bit boundary. The ECC status + * contains one byte for every ECC chunk, and is also padded to the + * nearest 32-bit boundary. + */ + metadata_size = ALIGN(geo->metadata_size, 4); + status_size = ALIGN(geo->ecc_chunk_count, 4); + + geo->auxiliary_size = metadata_size + status_size; + geo->auxiliary_status_offset = metadata_size; + + if (!this->swap_block_mark) + return 0; + + /* + * We need to compute the byte and bit offsets of + * the physical block mark within the ECC-based view of the page. + * + * NAND chip with 2K page shows below: + * (Block Mark) + * | | + * | D | + * |<---->| + * V V + * +---+----------+-+----------+-+----------+-+----------+-+ + * | M | data |E| data |E| data |E| data |E| + * +---+----------+-+----------+-+----------+-+----------+-+ + * + * The position of block mark moves forward in the ECC-based view + * of page, and the delta is: + * + * E * G * (N - 1) + * D = (---------------- + M) + * 8 + * + * With the formula to compute the ECC strength, and the condition + * : C >= O (C is the ecc chunk size) + * + * It's easy to deduce to the following result: + * + * E * G (O - M) C - M C - M + * ----------- <= ------- <= -------- < --------- + * 8 N N (N - 1) + * + * So, we get: + * + * E * G * (N - 1) + * D = (---------------- + M) < C + * 8 + * + * The above inequality means the position of block mark + * within the ECC-based view of the page is still in the data chunk, + * and it's NOT in the ECC bits of the chunk. + * + * Use the following to compute the bit position of the + * physical block mark within the ECC-based view of the page: + * (page_size - D) * 8 + * + * --Huang Shijie + */ + block_mark_bit_offset = mtd->writesize * 8 - + (geo->ecc_strength * geo->gf_len * (geo->ecc_chunk_count - 1) + + geo->metadata_size * 8); + + geo->block_mark_byte_offset = block_mark_bit_offset / 8; + geo->block_mark_bit_offset = block_mark_bit_offset % 8; + return 0; +} + +static int common_nfc_set_geometry(struct gpmi_nand_data *this) +{ + struct nand_chip *chip = &this->nand; + struct mtd_info *mtd = nand_to_mtd(&this->nand); + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&chip->base); + bool use_minimun_ecc; + int err; + + use_minimun_ecc = of_property_read_bool(this->dev->of_node, + "fsl,use-minimum-ecc"); + + /* use legacy bch geometry settings by default*/ + if ((!use_minimun_ecc && mtd->oobsize < 1024) || + !(requirements->strength > 0 && requirements->step_size > 0)) { + dev_dbg(this->dev, "use legacy bch geometry\n"); + err = legacy_set_geometry(this); + if (!err) + return 0; + } + + /* for large oob nand */ + if (mtd->oobsize > 1024) { + dev_dbg(this->dev, "use large oob bch geometry\n"); + err = set_geometry_for_large_oob(this); + if (!err) + return 0; + } + + /* otherwise use the minimum ecc nand chip required */ + dev_dbg(this->dev, "use minimum ecc bch geometry\n"); + err = set_geometry_by_ecc_info(this, requirements->strength, + requirements->step_size); + if (err) + dev_err(this->dev, "none of the bch geometry setting works\n"); + + return err; +} + +#ifdef CONFIG_DEBUG_FS +static void bch_remove_debugfs(void *data) +{ + struct gpmi_nand_data *this = data; + + debugfs_remove_recursive(this->dbg_root); + this->dbg_root = NULL; +} + +static void bch_create_debugfs(struct gpmi_nand_data *this) +{ + struct bch_geometry *bch_geo = &this->bch_geometry; + + this->dbg_root = debugfs_create_dir("gpmi-nand", NULL); + this->dbg_bch_geo.data = (void *)bch_geo; + this->dbg_bch_geo.size = sizeof(struct bch_geometry); + this->raw_mode = true; + debugfs_create_blob("bch_geometry", 0444, this->dbg_root, &this->dbg_bch_geo); + debugfs_create_bool("raw_mode", 0444, this->dbg_root, &this->raw_mode); + devm_add_action_or_reset(this->dev, bch_remove_debugfs, this); +} +#else +static void bch_create_debugfs(struct gpmi_nand_data *this) {} +#endif /* CONFIG_DEBUG_FS */ + +/* Configures the geometry for BCH. */ +static int bch_set_geometry(struct gpmi_nand_data *this) +{ + struct resources *r = &this->resources; + int ret; + + ret = common_nfc_set_geometry(this); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(this->dev); + if (ret < 0) { + return ret; + } + + /* + * Due to erratum #2847 of the MX23, the BCH cannot be soft reset on this + * chip, otherwise it will lock up. So we skip resetting BCH on the MX23. + * and MX28. + */ + ret = gpmi_reset_block(r->bch_regs, GPMI_IS_MXS(this)); + if (ret) + goto err_out; + + /* Set *all* chip selects to use layout 0. */ + writel(0, r->bch_regs + HW_BCH_LAYOUTSELECT); + + ret = 0; +err_out: + pm_runtime_put_autosuspend(this->dev); + + return ret; +} + +/* + * <1> Firstly, we should know what's the GPMI-clock means. + * The GPMI-clock is the internal clock in the gpmi nand controller. + * If you set 100MHz to gpmi nand controller, the GPMI-clock's period + * is 10ns. Mark the GPMI-clock's period as GPMI-clock-period. + * + * <2> Secondly, we should know what's the frequency on the nand chip pins. + * The frequency on the nand chip pins is derived from the GPMI-clock. + * We can get it from the following equation: + * + * F = G / (DS + DH) + * + * F : the frequency on the nand chip pins. + * G : the GPMI clock, such as 100MHz. + * DS : GPMI_HW_GPMI_TIMING0:DATA_SETUP + * DH : GPMI_HW_GPMI_TIMING0:DATA_HOLD + * + * <3> Thirdly, when the frequency on the nand chip pins is above 33MHz, + * the nand EDO(extended Data Out) timing could be applied. + * The GPMI implements a feedback read strobe to sample the read data. + * The feedback read strobe can be delayed to support the nand EDO timing + * where the read strobe may deasserts before the read data is valid, and + * read data is valid for some time after read strobe. + * + * The following figure illustrates some aspects of a NAND Flash read: + * + * |<---tREA---->| + * | | + * | | | + * |<--tRP-->| | + * | | | + * __ ___|__________________________________ + * RDN \________/ | + * | + * /---------\ + * Read Data --------------< >--------- + * \---------/ + * | | + * |<-D->| + * FeedbackRDN ________ ____________ + * \___________/ + * + * D stands for delay, set in the HW_GPMI_CTRL1:RDN_DELAY. + * + * + * <4> Now, we begin to describe how to compute the right RDN_DELAY. + * + * 4.1) From the aspect of the nand chip pins: + * Delay = (tREA + C - tRP) {1} + * + * tREA : the maximum read access time. + * C : a constant to adjust the delay. default is 4000ps. + * tRP : the read pulse width, which is exactly: + * tRP = (GPMI-clock-period) * DATA_SETUP + * + * 4.2) From the aspect of the GPMI nand controller: + * Delay = RDN_DELAY * 0.125 * RP {2} + * + * RP : the DLL reference period. + * if (GPMI-clock-period > DLL_THRETHOLD) + * RP = GPMI-clock-period / 2; + * else + * RP = GPMI-clock-period; + * + * Set the HW_GPMI_CTRL1:HALF_PERIOD if GPMI-clock-period + * is greater DLL_THRETHOLD. In other SOCs, the DLL_THRETHOLD + * is 16000ps, but in mx6q, we use 12000ps. + * + * 4.3) since {1} equals {2}, we get: + * + * (tREA + 4000 - tRP) * 8 + * RDN_DELAY = ----------------------- {3} + * RP + */ +static int gpmi_nfc_compute_timings(struct gpmi_nand_data *this, + const struct nand_sdr_timings *sdr) +{ + struct gpmi_nfc_hardware_timing *hw = &this->hw; + struct resources *r = &this->resources; + unsigned int dll_threshold_ps = this->devdata->max_chain_delay; + unsigned int period_ps, reference_period_ps; + unsigned int data_setup_cycles, data_hold_cycles, addr_setup_cycles; + unsigned int tRP_ps; + bool use_half_period; + int sample_delay_ps, sample_delay_factor; + unsigned int busy_timeout_cycles; + u8 wrn_dly_sel; + unsigned long clk_rate, min_rate; + u64 busy_timeout_ps; + + if (sdr->tRC_min >= 30000) { + /* ONFI non-EDO modes [0-3] */ + hw->clk_rate = 22000000; + min_rate = 0; + wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_4_TO_8NS; + } else if (sdr->tRC_min >= 25000) { + /* ONFI EDO mode 4 */ + hw->clk_rate = 80000000; + min_rate = 22000000; + wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_NO_DELAY; + } else { + /* ONFI EDO mode 5 */ + hw->clk_rate = 100000000; + min_rate = 80000000; + wrn_dly_sel = BV_GPMI_CTRL1_WRN_DLY_SEL_NO_DELAY; + } + + clk_rate = clk_round_rate(r->clock[0], hw->clk_rate); + if (clk_rate <= min_rate) { + dev_err(this->dev, "clock setting: expected %ld, got %ld\n", + hw->clk_rate, clk_rate); + return -ENOTSUPP; + } + + hw->clk_rate = clk_rate; + /* SDR core timings are given in picoseconds */ + period_ps = div_u64((u64)NSEC_PER_SEC * 1000, hw->clk_rate); + + addr_setup_cycles = TO_CYCLES(sdr->tALS_min, period_ps); + data_setup_cycles = TO_CYCLES(sdr->tDS_min, period_ps); + data_hold_cycles = TO_CYCLES(sdr->tDH_min, period_ps); + busy_timeout_ps = max(sdr->tBERS_max, sdr->tPROG_max); + busy_timeout_cycles = TO_CYCLES(busy_timeout_ps, period_ps); + + hw->timing0 = BF_GPMI_TIMING0_ADDRESS_SETUP(addr_setup_cycles) | + BF_GPMI_TIMING0_DATA_HOLD(data_hold_cycles) | + BF_GPMI_TIMING0_DATA_SETUP(data_setup_cycles); + hw->timing1 = BF_GPMI_TIMING1_BUSY_TIMEOUT(DIV_ROUND_UP(busy_timeout_cycles, 4096)); + + /* + * Derive NFC ideal delay from {3}: + * + * (tREA + 4000 - tRP) * 8 + * RDN_DELAY = ----------------------- + * RP + */ + if (period_ps > dll_threshold_ps) { + use_half_period = true; + reference_period_ps = period_ps / 2; + } else { + use_half_period = false; + reference_period_ps = period_ps; + } + + tRP_ps = data_setup_cycles * period_ps; + sample_delay_ps = (sdr->tREA_max + 4000 - tRP_ps) * 8; + if (sample_delay_ps > 0) + sample_delay_factor = sample_delay_ps / reference_period_ps; + else + sample_delay_factor = 0; + + hw->ctrl1n = BF_GPMI_CTRL1_WRN_DLY_SEL(wrn_dly_sel); + if (sample_delay_factor) + hw->ctrl1n |= BF_GPMI_CTRL1_RDN_DELAY(sample_delay_factor) | + BM_GPMI_CTRL1_DLL_ENABLE | + (use_half_period ? BM_GPMI_CTRL1_HALF_PERIOD : 0); + return 0; +} + +static int gpmi_nfc_apply_timings(struct gpmi_nand_data *this) +{ + struct gpmi_nfc_hardware_timing *hw = &this->hw; + struct resources *r = &this->resources; + void __iomem *gpmi_regs = r->gpmi_regs; + unsigned int dll_wait_time_us; + int ret; + + /* Clock dividers do NOT guarantee a clean clock signal on its output + * during the change of the divide factor on i.MX6Q/UL/SX. On i.MX7/8, + * all clock dividers provide these guarantee. + */ + if (GPMI_IS_MX6Q(this) || GPMI_IS_MX6SX(this)) + clk_disable_unprepare(r->clock[0]); + + ret = clk_set_rate(r->clock[0], hw->clk_rate); + if (ret) { + dev_err(this->dev, "cannot set clock rate to %lu Hz: %d\n", hw->clk_rate, ret); + return ret; + } + + if (GPMI_IS_MX6Q(this) || GPMI_IS_MX6SX(this)) { + ret = clk_prepare_enable(r->clock[0]); + if (ret) + return ret; + } + + writel(hw->timing0, gpmi_regs + HW_GPMI_TIMING0); + writel(hw->timing1, gpmi_regs + HW_GPMI_TIMING1); + + /* + * Clear several CTRL1 fields, DLL must be disabled when setting + * RDN_DELAY or HALF_PERIOD. + */ + writel(BM_GPMI_CTRL1_CLEAR_MASK, gpmi_regs + HW_GPMI_CTRL1_CLR); + writel(hw->ctrl1n, gpmi_regs + HW_GPMI_CTRL1_SET); + + /* Wait 64 clock cycles before using the GPMI after enabling the DLL */ + dll_wait_time_us = USEC_PER_SEC / hw->clk_rate * 64; + if (!dll_wait_time_us) + dll_wait_time_us = 1; + + /* Wait for the DLL to settle. */ + udelay(dll_wait_time_us); + + return 0; +} + +static int gpmi_setup_interface(struct nand_chip *chip, int chipnr, + const struct nand_interface_config *conf) +{ + struct gpmi_nand_data *this = nand_get_controller_data(chip); + const struct nand_sdr_timings *sdr; + int ret; + + /* Retrieve required NAND timings */ + sdr = nand_get_sdr_timings(conf); + if (IS_ERR(sdr)) + return PTR_ERR(sdr); + + /* Only MX28/MX6 GPMI controller can reach EDO timings */ + if (sdr->tRC_min <= 25000 && !this->devdata->support_edo_timing) + return -ENOTSUPP; + + /* Stop here if this call was just a check */ + if (chipnr < 0) + return 0; + + /* Do the actual derivation of the controller timings */ + ret = gpmi_nfc_compute_timings(this, sdr); + if (ret) + return ret; + + this->hw.must_apply_timings = true; + + return 0; +} + +/* Clears a BCH interrupt. */ +static void gpmi_clear_bch(struct gpmi_nand_data *this) +{ + struct resources *r = &this->resources; + writel(BM_BCH_CTRL_COMPLETE_IRQ, r->bch_regs + HW_BCH_CTRL_CLR); +} + +static struct dma_chan *get_dma_chan(struct gpmi_nand_data *this) +{ + /* We use the DMA channel 0 to access all the nand chips. */ + return this->dma_chans[0]; +} + +/* This will be called after the DMA operation is finished. */ +static void dma_irq_callback(void *param) +{ + struct gpmi_nand_data *this = param; + struct completion *dma_c = &this->dma_done; + + complete(dma_c); +} + +static irqreturn_t bch_irq(int irq, void *cookie) +{ + struct gpmi_nand_data *this = cookie; + + gpmi_clear_bch(this); + complete(&this->bch_done); + return IRQ_HANDLED; +} + +static int gpmi_raw_len_to_len(struct gpmi_nand_data *this, int raw_len) +{ + /* + * raw_len is the length to read/write including bch data which + * we are passed in exec_op. Calculate the data length from it. + */ + if (this->bch) + return ALIGN_DOWN(raw_len, this->bch_geometry.eccn_chunk_size); + else + return raw_len; +} + +/* Can we use the upper's buffer directly for DMA? */ +static bool prepare_data_dma(struct gpmi_nand_data *this, const void *buf, + int raw_len, struct scatterlist *sgl, + enum dma_data_direction dr) +{ + int ret; + int len = gpmi_raw_len_to_len(this, raw_len); + + /* first try to map the upper buffer directly */ + if (virt_addr_valid(buf) && !object_is_on_stack(buf)) { + sg_init_one(sgl, buf, len); + ret = dma_map_sg(this->dev, sgl, 1, dr); + if (ret == 0) + goto map_fail; + + return true; + } + +map_fail: + /* We have to use our own DMA buffer. */ + sg_init_one(sgl, this->data_buffer_dma, len); + + if (dr == DMA_TO_DEVICE && buf != this->data_buffer_dma) + memcpy(this->data_buffer_dma, buf, len); + + dma_map_sg(this->dev, sgl, 1, dr); + + return false; +} + +/* add our owner bbt descriptor */ +static uint8_t scan_ff_pattern[] = { 0xff }; +static struct nand_bbt_descr gpmi_bbt_descr = { + .options = 0, + .offs = 0, + .len = 1, + .pattern = scan_ff_pattern +}; + +/* + * We may change the layout if we can get the ECC info from the datasheet, + * else we will use all the (page + OOB). + */ +static int gpmi_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct gpmi_nand_data *this = nand_get_controller_data(chip); + struct bch_geometry *geo = &this->bch_geometry; + + if (section) + return -ERANGE; + + oobregion->offset = 0; + oobregion->length = geo->page_size - mtd->writesize; + + return 0; +} + +static int gpmi_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct gpmi_nand_data *this = nand_get_controller_data(chip); + struct bch_geometry *geo = &this->bch_geometry; + + if (section) + return -ERANGE; + + /* The available oob size we have. */ + if (geo->page_size < mtd->writesize + mtd->oobsize) { + oobregion->offset = geo->page_size - mtd->writesize; + oobregion->length = mtd->oobsize - oobregion->offset; + } + + return 0; +} + +static const char * const gpmi_clks_for_mx2x[] = { + "gpmi_io", +}; + +static const struct mtd_ooblayout_ops gpmi_ooblayout_ops = { + .ecc = gpmi_ooblayout_ecc, + .free = gpmi_ooblayout_free, +}; + +static const struct gpmi_devdata gpmi_devdata_imx23 = { + .type = IS_MX23, + .bch_max_ecc_strength = 20, + .max_chain_delay = 16000, + .clks = gpmi_clks_for_mx2x, + .clks_count = ARRAY_SIZE(gpmi_clks_for_mx2x), +}; + +static const struct gpmi_devdata gpmi_devdata_imx28 = { + .type = IS_MX28, + .bch_max_ecc_strength = 20, + .max_chain_delay = 16000, + .support_edo_timing = true, + .clks = gpmi_clks_for_mx2x, + .clks_count = ARRAY_SIZE(gpmi_clks_for_mx2x), +}; + +static const char * const gpmi_clks_for_mx6[] = { + "gpmi_io", "gpmi_apb", "gpmi_bch", "gpmi_bch_apb", "per1_bch", +}; + +static const struct gpmi_devdata gpmi_devdata_imx6q = { + .type = IS_MX6Q, + .bch_max_ecc_strength = 40, + .max_chain_delay = 12000, + .support_edo_timing = true, + .clks = gpmi_clks_for_mx6, + .clks_count = ARRAY_SIZE(gpmi_clks_for_mx6), +}; + +static const struct gpmi_devdata gpmi_devdata_imx6sx = { + .type = IS_MX6SX, + .bch_max_ecc_strength = 62, + .max_chain_delay = 12000, + .support_edo_timing = true, + .clks = gpmi_clks_for_mx6, + .clks_count = ARRAY_SIZE(gpmi_clks_for_mx6), +}; + +static const char * const gpmi_clks_for_mx7d[] = { + "gpmi_io", "gpmi_bch_apb", +}; + +static const struct gpmi_devdata gpmi_devdata_imx7d = { + .type = IS_MX7D, + .bch_max_ecc_strength = 62, + .max_chain_delay = 12000, + .support_edo_timing = true, + .clks = gpmi_clks_for_mx7d, + .clks_count = ARRAY_SIZE(gpmi_clks_for_mx7d), +}; + +static const char *gpmi_clks_for_mx8qxp[GPMI_CLK_MAX] = { + "gpmi_io", "gpmi_apb", "gpmi_bch", "gpmi_bch_apb", +}; + +static const struct gpmi_devdata gpmi_devdata_imx8qxp = { + .type = IS_MX8QXP, + .bch_max_ecc_strength = 62, + .max_chain_delay = 12000, + .support_edo_timing = true, + .clks = gpmi_clks_for_mx8qxp, + .clks_count = ARRAY_SIZE(gpmi_clks_for_mx8qxp), +}; + +static int acquire_register_block(struct gpmi_nand_data *this, + const char *res_name) +{ + struct platform_device *pdev = this->pdev; + struct resources *res = &this->resources; + void __iomem *p; + + p = devm_platform_ioremap_resource_byname(pdev, res_name); + if (IS_ERR(p)) + return PTR_ERR(p); + + if (!strcmp(res_name, GPMI_NAND_GPMI_REGS_ADDR_RES_NAME)) + res->gpmi_regs = p; + else if (!strcmp(res_name, GPMI_NAND_BCH_REGS_ADDR_RES_NAME)) + res->bch_regs = p; + else + dev_err(this->dev, "unknown resource name : %s\n", res_name); + + return 0; +} + +static int acquire_bch_irq(struct gpmi_nand_data *this, irq_handler_t irq_h) +{ + struct platform_device *pdev = this->pdev; + const char *res_name = GPMI_NAND_BCH_INTERRUPT_RES_NAME; + int err; + + err = platform_get_irq_byname(pdev, res_name); + if (err < 0) + return err; + + err = devm_request_irq(this->dev, err, irq_h, 0, res_name, this); + if (err) + dev_err(this->dev, "error requesting BCH IRQ\n"); + + return err; +} + +static void release_dma_channels(struct gpmi_nand_data *this) +{ + unsigned int i; + for (i = 0; i < DMA_CHANS; i++) + if (this->dma_chans[i]) { + dma_release_channel(this->dma_chans[i]); + this->dma_chans[i] = NULL; + } +} + +static int acquire_dma_channels(struct gpmi_nand_data *this) +{ + struct platform_device *pdev = this->pdev; + struct dma_chan *dma_chan; + int ret = 0; + + /* request dma channel */ + dma_chan = dma_request_chan(&pdev->dev, "rx-tx"); + if (IS_ERR(dma_chan)) { + ret = dev_err_probe(this->dev, PTR_ERR(dma_chan), + "DMA channel request failed\n"); + release_dma_channels(this); + } else { + this->dma_chans[0] = dma_chan; + } + + return ret; +} + +static int gpmi_get_clks(struct gpmi_nand_data *this) +{ + struct resources *r = &this->resources; + struct clk *clk; + int err, i; + + for (i = 0; i < this->devdata->clks_count; i++) { + clk = devm_clk_get(this->dev, this->devdata->clks[i]); + if (IS_ERR(clk)) { + err = PTR_ERR(clk); + goto err_clock; + } + + r->clock[i] = clk; + } + + return 0; + +err_clock: + dev_dbg(this->dev, "failed in finding the clocks.\n"); + return err; +} + +static int acquire_resources(struct gpmi_nand_data *this) +{ + int ret; + + ret = acquire_register_block(this, GPMI_NAND_GPMI_REGS_ADDR_RES_NAME); + if (ret) + goto exit_regs; + + ret = acquire_register_block(this, GPMI_NAND_BCH_REGS_ADDR_RES_NAME); + if (ret) + goto exit_regs; + + ret = acquire_bch_irq(this, bch_irq); + if (ret) + goto exit_regs; + + ret = acquire_dma_channels(this); + if (ret) + goto exit_regs; + + ret = gpmi_get_clks(this); + if (ret) + goto exit_clock; + return 0; + +exit_clock: + release_dma_channels(this); +exit_regs: + return ret; +} + +static void release_resources(struct gpmi_nand_data *this) +{ + release_dma_channels(this); +} + +static void gpmi_free_dma_buffer(struct gpmi_nand_data *this) +{ + struct device *dev = this->dev; + struct bch_geometry *geo = &this->bch_geometry; + + if (this->auxiliary_virt && virt_addr_valid(this->auxiliary_virt)) + dma_free_coherent(dev, geo->auxiliary_size, + this->auxiliary_virt, + this->auxiliary_phys); + kfree(this->data_buffer_dma); + kfree(this->raw_buffer); + + this->data_buffer_dma = NULL; + this->raw_buffer = NULL; +} + +/* Allocate the DMA buffers */ +static int gpmi_alloc_dma_buffer(struct gpmi_nand_data *this) +{ + struct bch_geometry *geo = &this->bch_geometry; + struct device *dev = this->dev; + struct mtd_info *mtd = nand_to_mtd(&this->nand); + + /* + * [2] Allocate a read/write data buffer. + * The gpmi_alloc_dma_buffer can be called twice. + * We allocate a PAGE_SIZE length buffer if gpmi_alloc_dma_buffer + * is called before the NAND identification; and we allocate a + * buffer of the real NAND page size when the gpmi_alloc_dma_buffer + * is called after. + */ + this->data_buffer_dma = kzalloc(mtd->writesize ?: PAGE_SIZE, + GFP_DMA | GFP_KERNEL); + if (this->data_buffer_dma == NULL) + goto error_alloc; + + this->auxiliary_virt = dma_alloc_coherent(dev, geo->auxiliary_size, + &this->auxiliary_phys, GFP_DMA); + if (!this->auxiliary_virt) + goto error_alloc; + + this->raw_buffer = kzalloc((mtd->writesize ?: PAGE_SIZE) + mtd->oobsize, GFP_KERNEL); + if (!this->raw_buffer) + goto error_alloc; + + return 0; + +error_alloc: + gpmi_free_dma_buffer(this); + return -ENOMEM; +} + +/* + * Handles block mark swapping. + * It can be called in swapping the block mark, or swapping it back, + * because the operations are the same. + */ +static void block_mark_swapping(struct gpmi_nand_data *this, + void *payload, void *auxiliary) +{ + struct bch_geometry *nfc_geo = &this->bch_geometry; + unsigned char *p; + unsigned char *a; + unsigned int bit; + unsigned char mask; + unsigned char from_data; + unsigned char from_oob; + + if (!this->swap_block_mark) + return; + + /* + * If control arrives here, we're swapping. Make some convenience + * variables. + */ + bit = nfc_geo->block_mark_bit_offset; + p = payload + nfc_geo->block_mark_byte_offset; + a = auxiliary; + + /* + * Get the byte from the data area that overlays the block mark. Since + * the ECC engine applies its own view to the bits in the page, the + * physical block mark won't (in general) appear on a byte boundary in + * the data. + */ + from_data = (p[0] >> bit) | (p[1] << (8 - bit)); + + /* Get the byte from the OOB. */ + from_oob = a[0]; + + /* Swap them. */ + a[0] = from_data; + + mask = (0x1 << bit) - 1; + p[0] = (p[0] & mask) | (from_oob << bit); + + mask = ~0 << bit; + p[1] = (p[1] & mask) | (from_oob >> (8 - bit)); +} + +static int gpmi_count_bitflips(struct nand_chip *chip, void *buf, int first, + int last, int meta) +{ + struct gpmi_nand_data *this = nand_get_controller_data(chip); + struct bch_geometry *nfc_geo = &this->bch_geometry; + struct mtd_info *mtd = nand_to_mtd(chip); + int i; + unsigned char *status; + unsigned int max_bitflips = 0; + + /* Loop over status bytes, accumulating ECC status. */ + status = this->auxiliary_virt + ALIGN(meta, 4); + + for (i = first; i < last; i++, status++) { + if ((*status == STATUS_GOOD) || (*status == STATUS_ERASED)) + continue; + + if (*status == STATUS_UNCORRECTABLE) { + int eccbits = nfc_geo->ecc_strength * nfc_geo->gf_len; + u8 *eccbuf = this->raw_buffer; + int offset, bitoffset; + int eccbytes; + int flips; + + /* Read ECC bytes into our internal raw_buffer */ + offset = nfc_geo->metadata_size * 8; + offset += ((8 * nfc_geo->eccn_chunk_size) + eccbits) * (i + 1); + offset -= eccbits; + bitoffset = offset % 8; + eccbytes = DIV_ROUND_UP(offset + eccbits, 8); + offset /= 8; + eccbytes -= offset; + nand_change_read_column_op(chip, offset, eccbuf, + eccbytes, false); + + /* + * ECC data are not byte aligned and we may have + * in-band data in the first and last byte of + * eccbuf. Set non-eccbits to one so that + * nand_check_erased_ecc_chunk() does not count them + * as bitflips. + */ + if (bitoffset) + eccbuf[0] |= GENMASK(bitoffset - 1, 0); + + bitoffset = (bitoffset + eccbits) % 8; + if (bitoffset) + eccbuf[eccbytes - 1] |= GENMASK(7, bitoffset); + + /* + * The ECC hardware has an uncorrectable ECC status + * code in case we have bitflips in an erased page. As + * nothing was written into this subpage the ECC is + * obviously wrong and we can not trust it. We assume + * at this point that we are reading an erased page and + * try to correct the bitflips in buffer up to + * ecc_strength bitflips. If this is a page with random + * data, we exceed this number of bitflips and have a + * ECC failure. Otherwise we use the corrected buffer. + */ + if (i == 0) { + /* The first block includes metadata */ + flips = nand_check_erased_ecc_chunk( + buf + i * nfc_geo->eccn_chunk_size, + nfc_geo->eccn_chunk_size, + eccbuf, eccbytes, + this->auxiliary_virt, + nfc_geo->metadata_size, + nfc_geo->ecc_strength); + } else { + flips = nand_check_erased_ecc_chunk( + buf + i * nfc_geo->eccn_chunk_size, + nfc_geo->eccn_chunk_size, + eccbuf, eccbytes, + NULL, 0, + nfc_geo->ecc_strength); + } + + if (flips > 0) { + max_bitflips = max_t(unsigned int, max_bitflips, + flips); + mtd->ecc_stats.corrected += flips; + continue; + } + + mtd->ecc_stats.failed++; + continue; + } + + mtd->ecc_stats.corrected += *status; + max_bitflips = max_t(unsigned int, max_bitflips, *status); + } + + return max_bitflips; +} + +static void gpmi_bch_layout_std(struct gpmi_nand_data *this) +{ + struct bch_geometry *geo = &this->bch_geometry; + unsigned int ecc_strength = geo->ecc_strength >> 1; + unsigned int gf_len = geo->gf_len; + unsigned int block0_size = geo->ecc0_chunk_size; + unsigned int blockn_size = geo->eccn_chunk_size; + + this->bch_flashlayout0 = + BF_BCH_FLASH0LAYOUT0_NBLOCKS(geo->ecc_chunk_count - 1) | + BF_BCH_FLASH0LAYOUT0_META_SIZE(geo->metadata_size) | + BF_BCH_FLASH0LAYOUT0_ECC0(ecc_strength, this) | + BF_BCH_FLASH0LAYOUT0_GF(gf_len, this) | + BF_BCH_FLASH0LAYOUT0_DATA0_SIZE(block0_size, this); + + this->bch_flashlayout1 = + BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(geo->page_size) | + BF_BCH_FLASH0LAYOUT1_ECCN(ecc_strength, this) | + BF_BCH_FLASH0LAYOUT1_GF(gf_len, this) | + BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(blockn_size, this); +} + +static int gpmi_ecc_read_page(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct gpmi_nand_data *this = nand_get_controller_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct bch_geometry *geo = &this->bch_geometry; + unsigned int max_bitflips; + int ret; + + gpmi_bch_layout_std(this); + this->bch = true; + + ret = nand_read_page_op(chip, page, 0, buf, geo->page_size); + if (ret) + return ret; + + max_bitflips = gpmi_count_bitflips(chip, buf, 0, + geo->ecc_chunk_count, + geo->auxiliary_status_offset); + + /* handle the block mark swapping */ + block_mark_swapping(this, buf, this->auxiliary_virt); + + if (oob_required) { + /* + * It's time to deliver the OOB bytes. See gpmi_ecc_read_oob() + * for details about our policy for delivering the OOB. + * + * We fill the caller's buffer with set bits, and then copy the + * block mark to th caller's buffer. Note that, if block mark + * swapping was necessary, it has already been done, so we can + * rely on the first byte of the auxiliary buffer to contain + * the block mark. + */ + memset(chip->oob_poi, ~0, mtd->oobsize); + chip->oob_poi[0] = ((uint8_t *)this->auxiliary_virt)[0]; + } + + return max_bitflips; +} + +/* Fake a virtual small page for the subpage read */ +static int gpmi_ecc_read_subpage(struct nand_chip *chip, uint32_t offs, + uint32_t len, uint8_t *buf, int page) +{ + struct gpmi_nand_data *this = nand_get_controller_data(chip); + struct bch_geometry *geo = &this->bch_geometry; + int size = chip->ecc.size; /* ECC chunk size */ + int meta, n, page_size; + unsigned int max_bitflips; + unsigned int ecc_strength; + int first, last, marker_pos; + int ecc_parity_size; + int col = 0; + int ret; + + /* The size of ECC parity */ + ecc_parity_size = geo->gf_len * geo->ecc_strength / 8; + + /* Align it with the chunk size */ + first = offs / size; + last = (offs + len - 1) / size; + + if (this->swap_block_mark) { + /* + * Find the chunk which contains the Block Marker. + * If this chunk is in the range of [first, last], + * we have to read out the whole page. + * Why? since we had swapped the data at the position of Block + * Marker to the metadata which is bound with the chunk 0. + */ + marker_pos = geo->block_mark_byte_offset / size; + if (last >= marker_pos && first <= marker_pos) { + dev_dbg(this->dev, + "page:%d, first:%d, last:%d, marker at:%d\n", + page, first, last, marker_pos); + return gpmi_ecc_read_page(chip, buf, 0, page); + } + } + + /* + * if there is an ECC dedicate for meta: + * - need to add an extra ECC size when calculating col and page_size, + * if the meta size is NOT zero. + * - ecc0_chunk size need to set to the same size as other chunks, + * if the meta size is zero. + */ + + meta = geo->metadata_size; + if (first) { + if (geo->ecc_for_meta) + col = meta + ecc_parity_size + + (size + ecc_parity_size) * first; + else + col = meta + (size + ecc_parity_size) * first; + + meta = 0; + buf = buf + first * size; + } + + ecc_parity_size = geo->gf_len * geo->ecc_strength / 8; + n = last - first + 1; + + if (geo->ecc_for_meta && meta) + page_size = meta + ecc_parity_size + + (size + ecc_parity_size) * n; + else + page_size = meta + (size + ecc_parity_size) * n; + + ecc_strength = geo->ecc_strength >> 1; + + this->bch_flashlayout0 = BF_BCH_FLASH0LAYOUT0_NBLOCKS( + (geo->ecc_for_meta ? n : n - 1)) | + BF_BCH_FLASH0LAYOUT0_META_SIZE(meta) | + BF_BCH_FLASH0LAYOUT0_ECC0(ecc_strength, this) | + BF_BCH_FLASH0LAYOUT0_GF(geo->gf_len, this) | + BF_BCH_FLASH0LAYOUT0_DATA0_SIZE((geo->ecc_for_meta ? + 0 : geo->ecc0_chunk_size), this); + + this->bch_flashlayout1 = BF_BCH_FLASH0LAYOUT1_PAGE_SIZE(page_size) | + BF_BCH_FLASH0LAYOUT1_ECCN(ecc_strength, this) | + BF_BCH_FLASH0LAYOUT1_GF(geo->gf_len, this) | + BF_BCH_FLASH0LAYOUT1_DATAN_SIZE(geo->eccn_chunk_size, this); + + this->bch = true; + + ret = nand_read_page_op(chip, page, col, buf, page_size); + if (ret) + return ret; + + dev_dbg(this->dev, "page:%d(%d:%d)%d, chunk:(%d:%d), BCH PG size:%d\n", + page, offs, len, col, first, n, page_size); + + max_bitflips = gpmi_count_bitflips(chip, buf, first, last, meta); + + return max_bitflips; +} + +static int gpmi_ecc_write_page(struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct gpmi_nand_data *this = nand_get_controller_data(chip); + struct bch_geometry *nfc_geo = &this->bch_geometry; + + dev_dbg(this->dev, "ecc write page.\n"); + + gpmi_bch_layout_std(this); + this->bch = true; + + memcpy(this->auxiliary_virt, chip->oob_poi, nfc_geo->auxiliary_size); + + if (this->swap_block_mark) { + /* + * When doing bad block marker swapping we must always copy the + * input buffer as we can't modify the const buffer. + */ + memcpy(this->data_buffer_dma, buf, mtd->writesize); + buf = this->data_buffer_dma; + block_mark_swapping(this, this->data_buffer_dma, + this->auxiliary_virt); + } + + return nand_prog_page_op(chip, page, 0, buf, nfc_geo->page_size); +} + +/* + * There are several places in this driver where we have to handle the OOB and + * block marks. This is the function where things are the most complicated, so + * this is where we try to explain it all. All the other places refer back to + * here. + * + * These are the rules, in order of decreasing importance: + * + * 1) Nothing the caller does can be allowed to imperil the block mark. + * + * 2) In read operations, the first byte of the OOB we return must reflect the + * true state of the block mark, no matter where that block mark appears in + * the physical page. + * + * 3) ECC-based read operations return an OOB full of set bits (since we never + * allow ECC-based writes to the OOB, it doesn't matter what ECC-based reads + * return). + * + * 4) "Raw" read operations return a direct view of the physical bytes in the + * page, using the conventional definition of which bytes are data and which + * are OOB. This gives the caller a way to see the actual, physical bytes + * in the page, without the distortions applied by our ECC engine. + * + * + * What we do for this specific read operation depends on two questions: + * + * 1) Are we doing a "raw" read, or an ECC-based read? + * + * 2) Are we using block mark swapping or transcription? + * + * There are four cases, illustrated by the following Karnaugh map: + * + * | Raw | ECC-based | + * -------------+-------------------------+-------------------------+ + * | Read the conventional | | + * | OOB at the end of the | | + * Swapping | page and return it. It | | + * | contains exactly what | | + * | we want. | Read the block mark and | + * -------------+-------------------------+ return it in a buffer | + * | Read the conventional | full of set bits. | + * | OOB at the end of the | | + * | page and also the block | | + * Transcribing | mark in the metadata. | | + * | Copy the block mark | | + * | into the first byte of | | + * | the OOB. | | + * -------------+-------------------------+-------------------------+ + * + * Note that we break rule #4 in the Transcribing/Raw case because we're not + * giving an accurate view of the actual, physical bytes in the page (we're + * overwriting the block mark). That's OK because it's more important to follow + * rule #2. + * + * It turns out that knowing whether we want an "ECC-based" or "raw" read is not + * easy. When reading a page, for example, the NAND Flash MTD code calls our + * ecc.read_page or ecc.read_page_raw function. Thus, the fact that MTD wants an + * ECC-based or raw view of the page is implicit in which function it calls + * (there is a similar pair of ECC-based/raw functions for writing). + */ +static int gpmi_ecc_read_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct gpmi_nand_data *this = nand_get_controller_data(chip); + int ret; + + /* clear the OOB buffer */ + memset(chip->oob_poi, ~0, mtd->oobsize); + + /* Read out the conventional OOB. */ + ret = nand_read_page_op(chip, page, mtd->writesize, chip->oob_poi, + mtd->oobsize); + if (ret) + return ret; + + /* + * Now, we want to make sure the block mark is correct. In the + * non-transcribing case (!GPMI_IS_MX23()), we already have it. + * Otherwise, we need to explicitly read it. + */ + if (GPMI_IS_MX23(this)) { + /* Read the block mark into the first byte of the OOB buffer. */ + ret = nand_read_page_op(chip, page, 0, chip->oob_poi, 1); + if (ret) + return ret; + } + + return 0; +} + +static int gpmi_ecc_write_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mtd_oob_region of = { }; + + /* Do we have available oob area? */ + mtd_ooblayout_free(mtd, 0, &of); + if (!of.length) + return -EPERM; + + if (!nand_is_slc(chip)) + return -EPERM; + + return nand_prog_page_op(chip, page, mtd->writesize + of.offset, + chip->oob_poi + of.offset, of.length); +} + +/* + * This function reads a NAND page without involving the ECC engine (no HW + * ECC correction). + * The tricky part in the GPMI/BCH controller is that it stores ECC bits + * inline (interleaved with payload DATA), and do not align data chunk on + * byte boundaries. + * We thus need to take care moving the payload data and ECC bits stored in the + * page into the provided buffers, which is why we're using nand_extract_bits(). + * + * See set_geometry_by_ecc_info inline comments to have a full description + * of the layout used by the GPMI controller. + */ +static int gpmi_ecc_read_page_raw(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct gpmi_nand_data *this = nand_get_controller_data(chip); + struct bch_geometry *nfc_geo = &this->bch_geometry; + int eccsize = nfc_geo->eccn_chunk_size; + int eccbits = nfc_geo->ecc_strength * nfc_geo->gf_len; + u8 *tmp_buf = this->raw_buffer; + size_t src_bit_off; + size_t oob_bit_off; + size_t oob_byte_off; + uint8_t *oob = chip->oob_poi; + int step; + int ret; + + ret = nand_read_page_op(chip, page, 0, tmp_buf, + mtd->writesize + mtd->oobsize); + if (ret) + return ret; + + /* + * If required, swap the bad block marker and the data stored in the + * metadata section, so that we don't wrongly consider a block as bad. + * + * See the layout description for a detailed explanation on why this + * is needed. + */ + if (this->swap_block_mark) + swap(tmp_buf[0], tmp_buf[mtd->writesize]); + + /* + * Copy the metadata section into the oob buffer (this section is + * guaranteed to be aligned on a byte boundary). + */ + if (oob_required) + memcpy(oob, tmp_buf, nfc_geo->metadata_size); + + oob_bit_off = nfc_geo->metadata_size * 8; + src_bit_off = oob_bit_off; + + /* Extract interleaved payload data and ECC bits */ + for (step = 0; step < nfc_geo->ecc_chunk_count; step++) { + if (buf) + nand_extract_bits(buf, step * eccsize * 8, tmp_buf, + src_bit_off, eccsize * 8); + src_bit_off += eccsize * 8; + + /* Align last ECC block to align a byte boundary */ + if (step == nfc_geo->ecc_chunk_count - 1 && + (oob_bit_off + eccbits) % 8) + eccbits += 8 - ((oob_bit_off + eccbits) % 8); + + if (oob_required) + nand_extract_bits(oob, oob_bit_off, tmp_buf, + src_bit_off, eccbits); + + src_bit_off += eccbits; + oob_bit_off += eccbits; + } + + if (oob_required) { + oob_byte_off = oob_bit_off / 8; + + if (oob_byte_off < mtd->oobsize) + memcpy(oob + oob_byte_off, + tmp_buf + mtd->writesize + oob_byte_off, + mtd->oobsize - oob_byte_off); + } + + return 0; +} + +/* + * This function writes a NAND page without involving the ECC engine (no HW + * ECC generation). + * The tricky part in the GPMI/BCH controller is that it stores ECC bits + * inline (interleaved with payload DATA), and do not align data chunk on + * byte boundaries. + * We thus need to take care moving the OOB area at the right place in the + * final page, which is why we're using nand_extract_bits(). + * + * See set_geometry_by_ecc_info inline comments to have a full description + * of the layout used by the GPMI controller. + */ +static int gpmi_ecc_write_page_raw(struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct gpmi_nand_data *this = nand_get_controller_data(chip); + struct bch_geometry *nfc_geo = &this->bch_geometry; + int eccsize = nfc_geo->eccn_chunk_size; + int eccbits = nfc_geo->ecc_strength * nfc_geo->gf_len; + u8 *tmp_buf = this->raw_buffer; + uint8_t *oob = chip->oob_poi; + size_t dst_bit_off; + size_t oob_bit_off; + size_t oob_byte_off; + int step; + + /* + * Initialize all bits to 1 in case we don't have a buffer for the + * payload or oob data in order to leave unspecified bits of data + * to their initial state. + */ + if (!buf || !oob_required) + memset(tmp_buf, 0xff, mtd->writesize + mtd->oobsize); + + /* + * First copy the metadata section (stored in oob buffer) at the + * beginning of the page, as imposed by the GPMI layout. + */ + memcpy(tmp_buf, oob, nfc_geo->metadata_size); + oob_bit_off = nfc_geo->metadata_size * 8; + dst_bit_off = oob_bit_off; + + /* Interleave payload data and ECC bits */ + for (step = 0; step < nfc_geo->ecc_chunk_count; step++) { + if (buf) + nand_extract_bits(tmp_buf, dst_bit_off, buf, + step * eccsize * 8, eccsize * 8); + dst_bit_off += eccsize * 8; + + /* Align last ECC block to align a byte boundary */ + if (step == nfc_geo->ecc_chunk_count - 1 && + (oob_bit_off + eccbits) % 8) + eccbits += 8 - ((oob_bit_off + eccbits) % 8); + + if (oob_required) + nand_extract_bits(tmp_buf, dst_bit_off, oob, + oob_bit_off, eccbits); + + dst_bit_off += eccbits; + oob_bit_off += eccbits; + } + + oob_byte_off = oob_bit_off / 8; + + if (oob_required && oob_byte_off < mtd->oobsize) + memcpy(tmp_buf + mtd->writesize + oob_byte_off, + oob + oob_byte_off, mtd->oobsize - oob_byte_off); + + /* + * If required, swap the bad block marker and the first byte of the + * metadata section, so that we don't modify the bad block marker. + * + * See the layout description for a detailed explanation on why this + * is needed. + */ + if (this->swap_block_mark) + swap(tmp_buf[0], tmp_buf[mtd->writesize]); + + return nand_prog_page_op(chip, page, 0, tmp_buf, + mtd->writesize + mtd->oobsize); +} + +static int gpmi_ecc_read_oob_raw(struct nand_chip *chip, int page) +{ + return gpmi_ecc_read_page_raw(chip, NULL, 1, page); +} + +static int gpmi_ecc_write_oob_raw(struct nand_chip *chip, int page) +{ + return gpmi_ecc_write_page_raw(chip, NULL, 1, page); +} + +static int gpmi_block_markbad(struct nand_chip *chip, loff_t ofs) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct gpmi_nand_data *this = nand_get_controller_data(chip); + int ret = 0; + uint8_t *block_mark; + int column, page, chipnr; + + chipnr = (int)(ofs >> chip->chip_shift); + nand_select_target(chip, chipnr); + + column = !GPMI_IS_MX23(this) ? mtd->writesize : 0; + + /* Write the block mark. */ + block_mark = this->data_buffer_dma; + block_mark[0] = 0; /* bad block marker */ + + /* Shift to get page */ + page = (int)(ofs >> chip->page_shift); + + ret = nand_prog_page_op(chip, page, column, block_mark, 1); + + nand_deselect_target(chip); + + return ret; +} + +static int nand_boot_set_geometry(struct gpmi_nand_data *this) +{ + struct boot_rom_geometry *geometry = &this->rom_geometry; + + /* + * Set the boot block stride size. + * + * In principle, we should be reading this from the OTP bits, since + * that's where the ROM is going to get it. In fact, we don't have any + * way to read the OTP bits, so we go with the default and hope for the + * best. + */ + geometry->stride_size_in_pages = 64; + + /* + * Set the search area stride exponent. + * + * In principle, we should be reading this from the OTP bits, since + * that's where the ROM is going to get it. In fact, we don't have any + * way to read the OTP bits, so we go with the default and hope for the + * best. + */ + geometry->search_area_stride_exponent = 2; + return 0; +} + +static const char *fingerprint = "STMP"; +static int mx23_check_transcription_stamp(struct gpmi_nand_data *this) +{ + struct boot_rom_geometry *rom_geo = &this->rom_geometry; + struct device *dev = this->dev; + struct nand_chip *chip = &this->nand; + unsigned int search_area_size_in_strides; + unsigned int stride; + unsigned int page; + u8 *buffer = nand_get_data_buf(chip); + int found_an_ncb_fingerprint = false; + int ret; + + /* Compute the number of strides in a search area. */ + search_area_size_in_strides = 1 << rom_geo->search_area_stride_exponent; + + nand_select_target(chip, 0); + + /* + * Loop through the first search area, looking for the NCB fingerprint. + */ + dev_dbg(dev, "Scanning for an NCB fingerprint...\n"); + + for (stride = 0; stride < search_area_size_in_strides; stride++) { + /* Compute the page addresses. */ + page = stride * rom_geo->stride_size_in_pages; + + dev_dbg(dev, "Looking for a fingerprint in page 0x%x\n", page); + + /* + * Read the NCB fingerprint. The fingerprint is four bytes long + * and starts in the 12th byte of the page. + */ + ret = nand_read_page_op(chip, page, 12, buffer, + strlen(fingerprint)); + if (ret) + continue; + + /* Look for the fingerprint. */ + if (!memcmp(buffer, fingerprint, strlen(fingerprint))) { + found_an_ncb_fingerprint = true; + break; + } + + } + + nand_deselect_target(chip); + + if (found_an_ncb_fingerprint) + dev_dbg(dev, "\tFound a fingerprint\n"); + else + dev_dbg(dev, "\tNo fingerprint found\n"); + return found_an_ncb_fingerprint; +} + +/* Writes a transcription stamp. */ +static int mx23_write_transcription_stamp(struct gpmi_nand_data *this) +{ + struct device *dev = this->dev; + struct boot_rom_geometry *rom_geo = &this->rom_geometry; + struct nand_chip *chip = &this->nand; + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int block_size_in_pages; + unsigned int search_area_size_in_strides; + unsigned int search_area_size_in_pages; + unsigned int search_area_size_in_blocks; + unsigned int block; + unsigned int stride; + unsigned int page; + u8 *buffer = nand_get_data_buf(chip); + int status; + + /* Compute the search area geometry. */ + block_size_in_pages = mtd->erasesize / mtd->writesize; + search_area_size_in_strides = 1 << rom_geo->search_area_stride_exponent; + search_area_size_in_pages = search_area_size_in_strides * + rom_geo->stride_size_in_pages; + search_area_size_in_blocks = + (search_area_size_in_pages + (block_size_in_pages - 1)) / + block_size_in_pages; + + dev_dbg(dev, "Search Area Geometry :\n"); + dev_dbg(dev, "\tin Blocks : %u\n", search_area_size_in_blocks); + dev_dbg(dev, "\tin Strides: %u\n", search_area_size_in_strides); + dev_dbg(dev, "\tin Pages : %u\n", search_area_size_in_pages); + + nand_select_target(chip, 0); + + /* Loop over blocks in the first search area, erasing them. */ + dev_dbg(dev, "Erasing the search area...\n"); + + for (block = 0; block < search_area_size_in_blocks; block++) { + /* Erase this block. */ + dev_dbg(dev, "\tErasing block 0x%x\n", block); + status = nand_erase_op(chip, block); + if (status) + dev_err(dev, "[%s] Erase failed.\n", __func__); + } + + /* Write the NCB fingerprint into the page buffer. */ + memset(buffer, ~0, mtd->writesize); + memcpy(buffer + 12, fingerprint, strlen(fingerprint)); + + /* Loop through the first search area, writing NCB fingerprints. */ + dev_dbg(dev, "Writing NCB fingerprints...\n"); + for (stride = 0; stride < search_area_size_in_strides; stride++) { + /* Compute the page addresses. */ + page = stride * rom_geo->stride_size_in_pages; + + /* Write the first page of the current stride. */ + dev_dbg(dev, "Writing an NCB fingerprint in page 0x%x\n", page); + + status = chip->ecc.write_page_raw(chip, buffer, 0, page); + if (status) + dev_err(dev, "[%s] Write failed.\n", __func__); + } + + nand_deselect_target(chip); + + return 0; +} + +static int mx23_boot_init(struct gpmi_nand_data *this) +{ + struct device *dev = this->dev; + struct nand_chip *chip = &this->nand; + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int block_count; + unsigned int block; + int chipnr; + int page; + loff_t byte; + uint8_t block_mark; + int ret = 0; + + /* + * If control arrives here, we can't use block mark swapping, which + * means we're forced to use transcription. First, scan for the + * transcription stamp. If we find it, then we don't have to do + * anything -- the block marks are already transcribed. + */ + if (mx23_check_transcription_stamp(this)) + return 0; + + /* + * If control arrives here, we couldn't find a transcription stamp, so + * so we presume the block marks are in the conventional location. + */ + dev_dbg(dev, "Transcribing bad block marks...\n"); + + /* Compute the number of blocks in the entire medium. */ + block_count = nanddev_eraseblocks_per_target(&chip->base); + + /* + * Loop over all the blocks in the medium, transcribing block marks as + * we go. + */ + for (block = 0; block < block_count; block++) { + /* + * Compute the chip, page and byte addresses for this block's + * conventional mark. + */ + chipnr = block >> (chip->chip_shift - chip->phys_erase_shift); + page = block << (chip->phys_erase_shift - chip->page_shift); + byte = block << chip->phys_erase_shift; + + /* Send the command to read the conventional block mark. */ + nand_select_target(chip, chipnr); + ret = nand_read_page_op(chip, page, mtd->writesize, &block_mark, + 1); + nand_deselect_target(chip); + + if (ret) + continue; + + /* + * Check if the block is marked bad. If so, we need to mark it + * again, but this time the result will be a mark in the + * location where we transcribe block marks. + */ + if (block_mark != 0xff) { + dev_dbg(dev, "Transcribing mark in block %u\n", block); + ret = chip->legacy.block_markbad(chip, byte); + if (ret) + dev_err(dev, + "Failed to mark block bad with ret %d\n", + ret); + } + } + + /* Write the stamp that indicates we've transcribed the block marks. */ + mx23_write_transcription_stamp(this); + return 0; +} + +static int nand_boot_init(struct gpmi_nand_data *this) +{ + nand_boot_set_geometry(this); + + /* This is ROM arch-specific initilization before the BBT scanning. */ + if (GPMI_IS_MX23(this)) + return mx23_boot_init(this); + return 0; +} + +static int gpmi_set_geometry(struct gpmi_nand_data *this) +{ + int ret; + + /* Free the temporary DMA memory for reading ID. */ + gpmi_free_dma_buffer(this); + + /* Set up the NFC geometry which is used by BCH. */ + ret = bch_set_geometry(this); + if (ret) { + dev_err(this->dev, "Error setting BCH geometry : %d\n", ret); + return ret; + } + + /* Alloc the new DMA buffers according to the pagesize and oobsize */ + return gpmi_alloc_dma_buffer(this); +} + +static int gpmi_init_last(struct gpmi_nand_data *this) +{ + struct nand_chip *chip = &this->nand; + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + struct bch_geometry *bch_geo = &this->bch_geometry; + int ret; + + /* Set up the medium geometry */ + ret = gpmi_set_geometry(this); + if (ret) + return ret; + + /* save BCH geometry to debugfs if CONFIG_DEBUG_FS is enabled */ + bch_create_debugfs(this); + + /* Init the nand_ecc_ctrl{} */ + ecc->read_page = gpmi_ecc_read_page; + ecc->write_page = gpmi_ecc_write_page; + ecc->read_oob = gpmi_ecc_read_oob; + ecc->write_oob = gpmi_ecc_write_oob; + ecc->read_page_raw = gpmi_ecc_read_page_raw; + ecc->write_page_raw = gpmi_ecc_write_page_raw; + ecc->read_oob_raw = gpmi_ecc_read_oob_raw; + ecc->write_oob_raw = gpmi_ecc_write_oob_raw; + ecc->engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + ecc->size = bch_geo->eccn_chunk_size; + ecc->strength = bch_geo->ecc_strength; + mtd_set_ooblayout(mtd, &gpmi_ooblayout_ops); + + /* + * We only enable the subpage read when: + * (1) the chip is imx6, and + * (2) the size of the ECC parity is byte aligned. + */ + if (GPMI_IS_MX6(this) && + ((bch_geo->gf_len * bch_geo->ecc_strength) % 8) == 0) { + ecc->read_subpage = gpmi_ecc_read_subpage; + chip->options |= NAND_SUBPAGE_READ; + } + + return 0; +} + +static int gpmi_nand_attach_chip(struct nand_chip *chip) +{ + struct gpmi_nand_data *this = nand_get_controller_data(chip); + int ret; + + if (chip->bbt_options & NAND_BBT_USE_FLASH) { + chip->bbt_options |= NAND_BBT_NO_OOB; + + if (of_property_read_bool(this->dev->of_node, + "fsl,no-blockmark-swap")) + this->swap_block_mark = false; + } + dev_dbg(this->dev, "Blockmark swapping %s\n", + str_enabled_disabled(this->swap_block_mark)); + + ret = gpmi_init_last(this); + if (ret) + return ret; + + chip->options |= NAND_SKIP_BBTSCAN; + + return 0; +} + +static struct gpmi_transfer *get_next_transfer(struct gpmi_nand_data *this) +{ + struct gpmi_transfer *transfer = &this->transfers[this->ntransfers]; + + this->ntransfers++; + + if (this->ntransfers == GPMI_MAX_TRANSFERS) + return NULL; + + return transfer; +} + +static struct dma_async_tx_descriptor *gpmi_chain_command( + struct gpmi_nand_data *this, u8 cmd, const u8 *addr, int naddr) +{ + struct dma_chan *channel = get_dma_chan(this); + struct dma_async_tx_descriptor *desc; + struct gpmi_transfer *transfer; + int chip = this->nand.cur_cs; + u32 pio[3]; + + /* [1] send out the PIO words */ + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__WRITE) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(chip, this) + | BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this) + | BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_CLE) + | BM_GPMI_CTRL0_ADDRESS_INCREMENT + | BF_GPMI_CTRL0_XFER_COUNT(naddr + 1); + pio[1] = 0; + pio[2] = 0; + desc = mxs_dmaengine_prep_pio(channel, pio, ARRAY_SIZE(pio), + DMA_TRANS_NONE, 0); + if (!desc) + return NULL; + + transfer = get_next_transfer(this); + if (!transfer) + return NULL; + + transfer->cmdbuf[0] = cmd; + if (naddr) + memcpy(&transfer->cmdbuf[1], addr, naddr); + + sg_init_one(&transfer->sgl, transfer->cmdbuf, naddr + 1); + dma_map_sg(this->dev, &transfer->sgl, 1, DMA_TO_DEVICE); + + transfer->direction = DMA_TO_DEVICE; + + desc = dmaengine_prep_slave_sg(channel, &transfer->sgl, 1, DMA_MEM_TO_DEV, + MXS_DMA_CTRL_WAIT4END); + return desc; +} + +static struct dma_async_tx_descriptor *gpmi_chain_wait_ready( + struct gpmi_nand_data *this) +{ + struct dma_chan *channel = get_dma_chan(this); + u32 pio[2]; + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(this->nand.cur_cs, this) + | BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this) + | BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_DATA) + | BF_GPMI_CTRL0_XFER_COUNT(0); + pio[1] = 0; + + return mxs_dmaengine_prep_pio(channel, pio, 2, DMA_TRANS_NONE, + MXS_DMA_CTRL_WAIT4END | MXS_DMA_CTRL_WAIT4RDY); +} + +static struct dma_async_tx_descriptor *gpmi_chain_data_read( + struct gpmi_nand_data *this, void *buf, int raw_len, bool *direct) +{ + struct dma_async_tx_descriptor *desc; + struct dma_chan *channel = get_dma_chan(this); + struct gpmi_transfer *transfer; + u32 pio[6] = {}; + + transfer = get_next_transfer(this); + if (!transfer) + return NULL; + + transfer->direction = DMA_FROM_DEVICE; + + *direct = prepare_data_dma(this, buf, raw_len, &transfer->sgl, + DMA_FROM_DEVICE); + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__READ) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(this->nand.cur_cs, this) + | BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this) + | BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_DATA) + | BF_GPMI_CTRL0_XFER_COUNT(raw_len); + + if (this->bch) { + pio[2] = BM_GPMI_ECCCTRL_ENABLE_ECC + | BF_GPMI_ECCCTRL_ECC_CMD(BV_GPMI_ECCCTRL_ECC_CMD__BCH_DECODE) + | BF_GPMI_ECCCTRL_BUFFER_MASK(BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE + | BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY); + pio[3] = raw_len; + pio[4] = transfer->sgl.dma_address; + pio[5] = this->auxiliary_phys; + } + + desc = mxs_dmaengine_prep_pio(channel, pio, ARRAY_SIZE(pio), + DMA_TRANS_NONE, 0); + if (!desc) + return NULL; + + if (!this->bch) + desc = dmaengine_prep_slave_sg(channel, &transfer->sgl, 1, + DMA_DEV_TO_MEM, + MXS_DMA_CTRL_WAIT4END); + + return desc; +} + +static struct dma_async_tx_descriptor *gpmi_chain_data_write( + struct gpmi_nand_data *this, const void *buf, int raw_len) +{ + struct dma_chan *channel = get_dma_chan(this); + struct dma_async_tx_descriptor *desc; + struct gpmi_transfer *transfer; + u32 pio[6] = {}; + + transfer = get_next_transfer(this); + if (!transfer) + return NULL; + + transfer->direction = DMA_TO_DEVICE; + + prepare_data_dma(this, buf, raw_len, &transfer->sgl, DMA_TO_DEVICE); + + pio[0] = BF_GPMI_CTRL0_COMMAND_MODE(BV_GPMI_CTRL0_COMMAND_MODE__WRITE) + | BM_GPMI_CTRL0_WORD_LENGTH + | BF_GPMI_CTRL0_CS(this->nand.cur_cs, this) + | BF_GPMI_CTRL0_LOCK_CS(LOCK_CS_ENABLE, this) + | BF_GPMI_CTRL0_ADDRESS(BV_GPMI_CTRL0_ADDRESS__NAND_DATA) + | BF_GPMI_CTRL0_XFER_COUNT(raw_len); + + if (this->bch) { + pio[2] = BM_GPMI_ECCCTRL_ENABLE_ECC + | BF_GPMI_ECCCTRL_ECC_CMD(BV_GPMI_ECCCTRL_ECC_CMD__BCH_ENCODE) + | BF_GPMI_ECCCTRL_BUFFER_MASK(BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE | + BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY); + pio[3] = raw_len; + pio[4] = transfer->sgl.dma_address; + pio[5] = this->auxiliary_phys; + } + + desc = mxs_dmaengine_prep_pio(channel, pio, ARRAY_SIZE(pio), + DMA_TRANS_NONE, + (this->bch ? MXS_DMA_CTRL_WAIT4END : 0)); + if (!desc) + return NULL; + + if (!this->bch) + desc = dmaengine_prep_slave_sg(channel, &transfer->sgl, 1, + DMA_MEM_TO_DEV, + MXS_DMA_CTRL_WAIT4END); + + return desc; +} + +static int gpmi_nfc_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + const struct nand_op_instr *instr; + struct gpmi_nand_data *this = nand_get_controller_data(chip); + struct dma_async_tx_descriptor *desc = NULL; + int i, ret, buf_len = 0, nbufs = 0; + u8 cmd = 0; + void *buf_read = NULL; + const void *buf_write = NULL; + bool direct = false; + struct completion *dma_completion, *bch_completion; + unsigned long to; + + if (check_only) + return 0; + + this->ntransfers = 0; + for (i = 0; i < GPMI_MAX_TRANSFERS; i++) + this->transfers[i].direction = DMA_NONE; + + ret = pm_runtime_resume_and_get(this->dev); + if (ret < 0) + return ret; + + /* + * This driver currently supports only one NAND chip. Plus, dies share + * the same configuration. So once timings have been applied on the + * controller side, they will not change anymore. When the time will + * come, the check on must_apply_timings will have to be dropped. + */ + if (this->hw.must_apply_timings) { + this->hw.must_apply_timings = false; + ret = gpmi_nfc_apply_timings(this); + if (ret) + goto out_pm; + } + + dev_dbg(this->dev, "%s: %d instructions\n", __func__, op->ninstrs); + + for (i = 0; i < op->ninstrs; i++) { + instr = &op->instrs[i]; + + nand_op_trace(" ", instr); + + switch (instr->type) { + case NAND_OP_WAITRDY_INSTR: + desc = gpmi_chain_wait_ready(this); + break; + case NAND_OP_CMD_INSTR: + cmd = instr->ctx.cmd.opcode; + + /* + * When this command has an address cycle chain it + * together with the address cycle + */ + if (i + 1 != op->ninstrs && + op->instrs[i + 1].type == NAND_OP_ADDR_INSTR) + continue; + + desc = gpmi_chain_command(this, cmd, NULL, 0); + + break; + case NAND_OP_ADDR_INSTR: + desc = gpmi_chain_command(this, cmd, instr->ctx.addr.addrs, + instr->ctx.addr.naddrs); + break; + case NAND_OP_DATA_OUT_INSTR: + buf_write = instr->ctx.data.buf.out; + buf_len = instr->ctx.data.len; + nbufs++; + + desc = gpmi_chain_data_write(this, buf_write, buf_len); + + break; + case NAND_OP_DATA_IN_INSTR: + if (!instr->ctx.data.len) + break; + buf_read = instr->ctx.data.buf.in; + buf_len = instr->ctx.data.len; + nbufs++; + + desc = gpmi_chain_data_read(this, buf_read, buf_len, + &direct); + break; + } + + if (!desc) { + ret = -ENXIO; + goto unmap; + } + } + + dev_dbg(this->dev, "%s setup done\n", __func__); + + if (nbufs > 1) { + dev_err(this->dev, "Multiple data instructions not supported\n"); + ret = -EINVAL; + goto unmap; + } + + if (this->bch) { + writel(this->bch_flashlayout0, + this->resources.bch_regs + HW_BCH_FLASH0LAYOUT0); + writel(this->bch_flashlayout1, + this->resources.bch_regs + HW_BCH_FLASH0LAYOUT1); + } + + desc->callback = dma_irq_callback; + desc->callback_param = this; + dma_completion = &this->dma_done; + bch_completion = NULL; + + init_completion(dma_completion); + + if (this->bch && buf_read) { + writel(BM_BCH_CTRL_COMPLETE_IRQ_EN, + this->resources.bch_regs + HW_BCH_CTRL_SET); + bch_completion = &this->bch_done; + init_completion(bch_completion); + } + + dmaengine_submit(desc); + dma_async_issue_pending(get_dma_chan(this)); + + to = wait_for_completion_timeout(dma_completion, msecs_to_jiffies(1000)); + if (!to) { + dev_err(this->dev, "DMA timeout, last DMA\n"); + gpmi_dump_info(this); + ret = -ETIMEDOUT; + goto unmap; + } + + if (this->bch && buf_read) { + to = wait_for_completion_timeout(bch_completion, msecs_to_jiffies(1000)); + if (!to) { + dev_err(this->dev, "BCH timeout, last DMA\n"); + gpmi_dump_info(this); + ret = -ETIMEDOUT; + goto unmap; + } + } + + writel(BM_BCH_CTRL_COMPLETE_IRQ_EN, + this->resources.bch_regs + HW_BCH_CTRL_CLR); + gpmi_clear_bch(this); + + ret = 0; + +unmap: + for (i = 0; i < this->ntransfers; i++) { + struct gpmi_transfer *transfer = &this->transfers[i]; + + if (transfer->direction != DMA_NONE) + dma_unmap_sg(this->dev, &transfer->sgl, 1, + transfer->direction); + } + + if (!ret && buf_read && !direct) + memcpy(buf_read, this->data_buffer_dma, + gpmi_raw_len_to_len(this, buf_len)); + + this->bch = false; + +out_pm: + pm_runtime_put_autosuspend(this->dev); + + return ret; +} + +static const struct nand_controller_ops gpmi_nand_controller_ops = { + .attach_chip = gpmi_nand_attach_chip, + .setup_interface = gpmi_setup_interface, + .exec_op = gpmi_nfc_exec_op, +}; + +static int gpmi_nand_init(struct gpmi_nand_data *this) +{ + struct nand_chip *chip = &this->nand; + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + /* init the MTD data structures */ + mtd->name = "gpmi-nand"; + mtd->dev.parent = this->dev; + + /* init the nand_chip{}, we don't support a 16-bit NAND Flash bus. */ + nand_set_controller_data(chip, this); + + 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; + + /* Set up swap_block_mark, must be set before the gpmi_set_geometry() */ + this->swap_block_mark = !GPMI_IS_MX23(this); + + /* + * Allocate a temporary DMA buffer for reading ID in the + * nand_scan_ident(). + */ + this->bch_geometry.payload_size = 1024; + this->bch_geometry.auxiliary_size = 128; + ret = gpmi_alloc_dma_buffer(this); + if (ret) + return ret; + + nand_controller_init(&this->base); + this->base.ops = &gpmi_nand_controller_ops; + chip->controller = &this->base; + + ret = nand_scan(chip, GPMI_IS_MX6(this) ? 2 : 1); + if (ret) + goto err_out; + + ret = nand_boot_init(this); + if (ret) + goto err_nand_cleanup; + ret = nand_create_bbt(chip); + if (ret) + goto err_nand_cleanup; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) + goto err_nand_cleanup; + return 0; + +err_nand_cleanup: + nand_cleanup(chip); +err_out: + gpmi_free_dma_buffer(this); + return ret; +} + +static const struct of_device_id gpmi_nand_id_table[] = { + { .compatible = "fsl,imx23-gpmi-nand", .data = &gpmi_devdata_imx23, }, + { .compatible = "fsl,imx28-gpmi-nand", .data = &gpmi_devdata_imx28, }, + { .compatible = "fsl,imx6q-gpmi-nand", .data = &gpmi_devdata_imx6q, }, + { .compatible = "fsl,imx6sx-gpmi-nand", .data = &gpmi_devdata_imx6sx, }, + { .compatible = "fsl,imx7d-gpmi-nand", .data = &gpmi_devdata_imx7d,}, + { .compatible = "fsl,imx8qxp-gpmi-nand", .data = &gpmi_devdata_imx8qxp, }, + {} +}; +MODULE_DEVICE_TABLE(of, gpmi_nand_id_table); + +static int gpmi_nand_probe(struct platform_device *pdev) +{ + struct gpmi_nand_data *this; + int ret; + + this = devm_kzalloc(&pdev->dev, sizeof(*this), GFP_KERNEL); + if (!this) + return -ENOMEM; + + this->devdata = of_device_get_match_data(&pdev->dev); + platform_set_drvdata(pdev, this); + this->pdev = pdev; + this->dev = &pdev->dev; + + ret = acquire_resources(this); + if (ret) + goto exit_acquire_resources; + + 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) + goto exit_nfc_init; + + ret = gpmi_nand_init(this); + if (ret) + goto exit_nfc_init; + + dev_info(this->dev, "driver registered.\n"); + + return 0; + +exit_nfc_init: + pm_runtime_dont_use_autosuspend(&pdev->dev); + pm_runtime_disable(&pdev->dev); + release_resources(this); +exit_acquire_resources: + + return ret; +} + +static void gpmi_nand_remove(struct platform_device *pdev) +{ + struct gpmi_nand_data *this = platform_get_drvdata(pdev); + struct nand_chip *chip = &this->nand; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + gpmi_free_dma_buffer(this); + 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) +{ + int ret; + + pinctrl_pm_select_sleep_state(dev); + ret = pm_runtime_force_suspend(dev); + + return ret; +} + +static int gpmi_pm_resume(struct device *dev) +{ + struct gpmi_nand_data *this = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + if (ret) { + dev_err(this->dev, "Error in resume %d\n", ret); + return ret; + } + + pinctrl_pm_select_default_state(dev); + + /* re-init the GPMI registers */ + ret = gpmi_init(this); + if (ret) { + dev_err(this->dev, "Error setting GPMI : %d\n", ret); + return ret; + } + + /* Set flag to get timing setup restored for next exec_op */ + if (this->hw.clk_rate) + this->hw.must_apply_timings = true; + + /* re-init the BCH registers */ + ret = bch_set_geometry(this); + if (ret) { + dev_err(this->dev, "Error setting BCH : %d\n", ret); + return ret; + } + + return 0; +} + +static int gpmi_runtime_suspend(struct device *dev) +{ + struct gpmi_nand_data *this = dev_get_drvdata(dev); + + gpmi_disable_clk(this); + + return 0; +} + +static int gpmi_runtime_resume(struct device *dev) +{ + struct gpmi_nand_data *this = dev_get_drvdata(dev); + int ret; + + ret = gpmi_enable_clk(this); + if (ret) + return ret; + + return 0; + +} + +static const struct dev_pm_ops gpmi_pm_ops = { + SYSTEM_SLEEP_PM_OPS(gpmi_pm_suspend, gpmi_pm_resume) + RUNTIME_PM_OPS(gpmi_runtime_suspend, gpmi_runtime_resume, NULL) +}; + +static struct platform_driver gpmi_nand_driver = { + .driver = { + .name = "gpmi-nand", + .pm = pm_ptr(&gpmi_pm_ops), + .of_match_table = gpmi_nand_id_table, + }, + .probe = gpmi_nand_probe, + .remove = gpmi_nand_remove, +}; +module_platform_driver(gpmi_nand_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("i.MX GPMI NAND Flash Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h new file mode 100644 index 000000000..80c32efba --- /dev/null +++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-nand.h @@ -0,0 +1,189 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Freescale GPMI NAND Flash Driver + * + * Copyright (C) 2010-2011 Freescale Semiconductor, Inc. + * Copyright (C) 2008 Embedded Alley Solutions, Inc. + */ +#ifndef __DRIVERS_MTD_NAND_GPMI_NAND_H +#define __DRIVERS_MTD_NAND_GPMI_NAND_H + +#include +#include +#include +#include + +#define GPMI_CLK_MAX 5 /* MX6Q needs five clocks */ +struct resources { + void __iomem *gpmi_regs; + void __iomem *bch_regs; + unsigned int dma_low_channel; + unsigned int dma_high_channel; + struct clk *clock[GPMI_CLK_MAX]; +}; + +/** + * struct bch_geometry - BCH geometry description. + * @gf_len: The length of Galois Field. (e.g., 13 or 14) + * @ecc_strength: A number that describes the strength of the ECC + * algorithm. + * @page_size: The size, in bytes, of a physical page, including + * both data and OOB. + * @metadata_size: The size, in bytes, of the metadata. + * @ecc0_chunk_size: The size, in bytes, of a first ECC chunk. + * @eccn_chunk_size: The size, in bytes, of a single ECC chunk after + * the first chunk in the page. + * @ecc_chunk_count: The number of ECC chunks in the page, + * @payload_size: The size, in bytes, of the payload buffer. + * @auxiliary_size: The size, in bytes, of the auxiliary buffer. + * @auxiliary_status_offset: The offset into the auxiliary buffer at which + * the ECC status appears. + * @block_mark_byte_offset: The byte offset in the ECC-based page view at + * which the underlying physical block mark appears. + * @block_mark_bit_offset: The bit offset into the ECC-based page view at + * which the underlying physical block mark appears. + * @ecc_for_meta: The flag to indicate if there is a dedicate ecc + * for meta. + */ +struct bch_geometry { + unsigned int gf_len; + unsigned int ecc_strength; + unsigned int page_size; + unsigned int metadata_size; + unsigned int ecc0_chunk_size; + unsigned int eccn_chunk_size; + unsigned int ecc_chunk_count; + unsigned int payload_size; + unsigned int auxiliary_size; + unsigned int auxiliary_status_offset; + unsigned int block_mark_byte_offset; + unsigned int block_mark_bit_offset; + unsigned int ecc_for_meta; /* ECC for meta data */ +}; + +/** + * struct boot_rom_geometry - Boot ROM geometry description. + * @stride_size_in_pages: The size of a boot block stride, in pages. + * @search_area_stride_exponent: The logarithm to base 2 of the size of a + * search area in boot block strides. + */ +struct boot_rom_geometry { + unsigned int stride_size_in_pages; + unsigned int search_area_stride_exponent; +}; + +enum gpmi_type { + IS_MX23, + IS_MX28, + IS_MX6Q, + IS_MX6SX, + IS_MX7D, + IS_MX8QXP, +}; + +struct gpmi_devdata { + enum gpmi_type type; + int bch_max_ecc_strength; + int max_chain_delay; /* See the SDR EDO mode */ + const char * const *clks; + const int clks_count; + bool support_edo_timing; +}; + +/** + * struct gpmi_nfc_hardware_timing - GPMI hardware timing parameters. + * @must_apply_timings: Whether controller timings have already been + * applied or not (useful only while there is + * support for only one chip select) + * @clk_rate: The clock rate that must be used to derive the + * following parameters + * @timing0: HW_GPMI_TIMING0 register + * @timing1: HW_GPMI_TIMING1 register + * @ctrl1n: HW_GPMI_CTRL1n register + */ +struct gpmi_nfc_hardware_timing { + bool must_apply_timings; + unsigned long int clk_rate; + u32 timing0; + u32 timing1; + u32 ctrl1n; +}; + +#define GPMI_MAX_TRANSFERS 8 + +struct gpmi_transfer { + u8 cmdbuf[8]; + struct scatterlist sgl; + enum dma_data_direction direction; +}; + +struct gpmi_nand_data { + /* Devdata */ + const struct gpmi_devdata *devdata; + + /* System Interface */ + struct device *dev; + struct platform_device *pdev; + + /* Resources */ + struct resources resources; + + /* Flash Hardware */ + struct gpmi_nfc_hardware_timing hw; + + /* BCH */ + struct bch_geometry bch_geometry; + struct completion bch_done; + + /* NAND Boot issue */ + bool swap_block_mark; + struct boot_rom_geometry rom_geometry; + + /* MTD / NAND */ + struct nand_controller base; + struct nand_chip nand; + + struct gpmi_transfer transfers[GPMI_MAX_TRANSFERS]; + int ntransfers; + + bool bch; + uint32_t bch_flashlayout0; + uint32_t bch_flashlayout1; + + char *data_buffer_dma; + + void *auxiliary_virt; + dma_addr_t auxiliary_phys; + + void *raw_buffer; + + /* DMA channels */ +#define DMA_CHANS 8 + struct dma_chan *dma_chans[DMA_CHANS]; + struct completion dma_done; + +#ifdef CONFIG_DEBUG_FS + struct dentry *dbg_root; + struct debugfs_blob_wrapper dbg_bch_geo; + bool raw_mode; +#endif +}; + +/* BCH : Status Block Completion Codes */ +#define STATUS_GOOD 0x00 +#define STATUS_ERASED 0xff +#define STATUS_UNCORRECTABLE 0xfe + +/* Use the devdata to distinguish different Archs. */ +#define GPMI_IS_MX23(x) ((x)->devdata->type == IS_MX23) +#define GPMI_IS_MX28(x) ((x)->devdata->type == IS_MX28) +#define GPMI_IS_MX6Q(x) ((x)->devdata->type == IS_MX6Q) +#define GPMI_IS_MX6SX(x) ((x)->devdata->type == IS_MX6SX) +#define GPMI_IS_MX7D(x) ((x)->devdata->type == IS_MX7D) +#define GPMI_IS_MX8QXP(x) ((x)->devdata->type == IS_MX8QXP) + +#define GPMI_IS_MX6(x) (GPMI_IS_MX6Q(x) || GPMI_IS_MX6SX(x) || \ + GPMI_IS_MX7D(x) || GPMI_IS_MX8QXP(x)) + +#define GPMI_IS_MXS(x) (GPMI_IS_MX23(x) || GPMI_IS_MX28(x)) +#endif diff --git a/drivers/mtd/nand/raw/gpmi-nand/gpmi-regs.h b/drivers/mtd/nand/raw/gpmi-nand/gpmi-regs.h new file mode 100644 index 000000000..fc31fd084 --- /dev/null +++ b/drivers/mtd/nand/raw/gpmi-nand/gpmi-regs.h @@ -0,0 +1,180 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Freescale GPMI NAND Flash Driver + * + * Copyright 2008-2011 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc. + */ +#ifndef __GPMI_NAND_GPMI_REGS_H +#define __GPMI_NAND_GPMI_REGS_H + +#define HW_GPMI_CTRL0 0x00000000 +#define HW_GPMI_CTRL0_SET 0x00000004 +#define HW_GPMI_CTRL0_CLR 0x00000008 +#define HW_GPMI_CTRL0_TOG 0x0000000c + +#define BP_GPMI_CTRL0_COMMAND_MODE 24 +#define BM_GPMI_CTRL0_COMMAND_MODE (3 << BP_GPMI_CTRL0_COMMAND_MODE) +#define BF_GPMI_CTRL0_COMMAND_MODE(v) \ + (((v) << BP_GPMI_CTRL0_COMMAND_MODE) & BM_GPMI_CTRL0_COMMAND_MODE) +#define BV_GPMI_CTRL0_COMMAND_MODE__WRITE 0x0 +#define BV_GPMI_CTRL0_COMMAND_MODE__READ 0x1 +#define BV_GPMI_CTRL0_COMMAND_MODE__READ_AND_COMPARE 0x2 +#define BV_GPMI_CTRL0_COMMAND_MODE__WAIT_FOR_READY 0x3 + +#define BM_GPMI_CTRL0_WORD_LENGTH (1 << 23) +#define BV_GPMI_CTRL0_WORD_LENGTH__16_BIT 0x0 +#define BV_GPMI_CTRL0_WORD_LENGTH__8_BIT 0x1 + +/* + * Difference in LOCK_CS between imx23 and imx28 : + * This bit may impact the _POWER_ consumption. So some chips + * do not set it. + */ +#define MX23_BP_GPMI_CTRL0_LOCK_CS 22 +#define MX28_BP_GPMI_CTRL0_LOCK_CS 27 +#define LOCK_CS_ENABLE 0x1 +#define BF_GPMI_CTRL0_LOCK_CS(v, x) 0x0 + +/* Difference in CS between imx23 and imx28 */ +#define BP_GPMI_CTRL0_CS 20 +#define MX23_BM_GPMI_CTRL0_CS (3 << BP_GPMI_CTRL0_CS) +#define MX28_BM_GPMI_CTRL0_CS (7 << BP_GPMI_CTRL0_CS) +#define BF_GPMI_CTRL0_CS(v, x) (((v) << BP_GPMI_CTRL0_CS) & \ + (GPMI_IS_MX23((x)) \ + ? MX23_BM_GPMI_CTRL0_CS \ + : MX28_BM_GPMI_CTRL0_CS)) + +#define BP_GPMI_CTRL0_ADDRESS 17 +#define BM_GPMI_CTRL0_ADDRESS (3 << BP_GPMI_CTRL0_ADDRESS) +#define BF_GPMI_CTRL0_ADDRESS(v) \ + (((v) << BP_GPMI_CTRL0_ADDRESS) & BM_GPMI_CTRL0_ADDRESS) +#define BV_GPMI_CTRL0_ADDRESS__NAND_DATA 0x0 +#define BV_GPMI_CTRL0_ADDRESS__NAND_CLE 0x1 +#define BV_GPMI_CTRL0_ADDRESS__NAND_ALE 0x2 + +#define BM_GPMI_CTRL0_ADDRESS_INCREMENT (1 << 16) +#define BV_GPMI_CTRL0_ADDRESS_INCREMENT__DISABLED 0x0 +#define BV_GPMI_CTRL0_ADDRESS_INCREMENT__ENABLED 0x1 + +#define BP_GPMI_CTRL0_XFER_COUNT 0 +#define BM_GPMI_CTRL0_XFER_COUNT (0xffff << BP_GPMI_CTRL0_XFER_COUNT) +#define BF_GPMI_CTRL0_XFER_COUNT(v) \ + (((v) << BP_GPMI_CTRL0_XFER_COUNT) & BM_GPMI_CTRL0_XFER_COUNT) + +#define HW_GPMI_COMPARE 0x00000010 + +#define HW_GPMI_ECCCTRL 0x00000020 +#define HW_GPMI_ECCCTRL_SET 0x00000024 +#define HW_GPMI_ECCCTRL_CLR 0x00000028 +#define HW_GPMI_ECCCTRL_TOG 0x0000002c + +#define BP_GPMI_ECCCTRL_ECC_CMD 13 +#define BM_GPMI_ECCCTRL_ECC_CMD (3 << BP_GPMI_ECCCTRL_ECC_CMD) +#define BF_GPMI_ECCCTRL_ECC_CMD(v) \ + (((v) << BP_GPMI_ECCCTRL_ECC_CMD) & BM_GPMI_ECCCTRL_ECC_CMD) +#define BV_GPMI_ECCCTRL_ECC_CMD__BCH_DECODE 0x0 +#define BV_GPMI_ECCCTRL_ECC_CMD__BCH_ENCODE 0x1 + +#define BM_GPMI_ECCCTRL_ENABLE_ECC (1 << 12) +#define BV_GPMI_ECCCTRL_ENABLE_ECC__ENABLE 0x1 +#define BV_GPMI_ECCCTRL_ENABLE_ECC__DISABLE 0x0 + +#define BP_GPMI_ECCCTRL_BUFFER_MASK 0 +#define BM_GPMI_ECCCTRL_BUFFER_MASK (0x1ff << BP_GPMI_ECCCTRL_BUFFER_MASK) +#define BF_GPMI_ECCCTRL_BUFFER_MASK(v) \ + (((v) << BP_GPMI_ECCCTRL_BUFFER_MASK) & BM_GPMI_ECCCTRL_BUFFER_MASK) +#define BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_AUXONLY 0x100 +#define BV_GPMI_ECCCTRL_BUFFER_MASK__BCH_PAGE 0x1FF + +#define HW_GPMI_ECCCOUNT 0x00000030 +#define HW_GPMI_PAYLOAD 0x00000040 +#define HW_GPMI_AUXILIARY 0x00000050 +#define HW_GPMI_CTRL1 0x00000060 +#define HW_GPMI_CTRL1_SET 0x00000064 +#define HW_GPMI_CTRL1_CLR 0x00000068 +#define HW_GPMI_CTRL1_TOG 0x0000006c + +#define BP_GPMI_CTRL1_DECOUPLE_CS 24 +#define BM_GPMI_CTRL1_DECOUPLE_CS (1 << BP_GPMI_CTRL1_DECOUPLE_CS) + +#define BP_GPMI_CTRL1_WRN_DLY_SEL 22 +#define BM_GPMI_CTRL1_WRN_DLY_SEL (0x3 << BP_GPMI_CTRL1_WRN_DLY_SEL) +#define BF_GPMI_CTRL1_WRN_DLY_SEL(v) \ + (((v) << BP_GPMI_CTRL1_WRN_DLY_SEL) & BM_GPMI_CTRL1_WRN_DLY_SEL) +#define BV_GPMI_CTRL1_WRN_DLY_SEL_4_TO_8NS 0x0 +#define BV_GPMI_CTRL1_WRN_DLY_SEL_6_TO_10NS 0x1 +#define BV_GPMI_CTRL1_WRN_DLY_SEL_7_TO_12NS 0x2 +#define BV_GPMI_CTRL1_WRN_DLY_SEL_NO_DELAY 0x3 + +#define BM_GPMI_CTRL1_GANGED_RDYBUSY (1 << 19) +#define BM_GPMI_CTRL1_BCH_MODE (1 << 18) + +#define BP_GPMI_CTRL1_DLL_ENABLE 17 +#define BM_GPMI_CTRL1_DLL_ENABLE (1 << BP_GPMI_CTRL1_DLL_ENABLE) + +#define BP_GPMI_CTRL1_HALF_PERIOD 16 +#define BM_GPMI_CTRL1_HALF_PERIOD (1 << BP_GPMI_CTRL1_HALF_PERIOD) + +#define BP_GPMI_CTRL1_RDN_DELAY 12 +#define BM_GPMI_CTRL1_RDN_DELAY (0xf << BP_GPMI_CTRL1_RDN_DELAY) +#define BF_GPMI_CTRL1_RDN_DELAY(v) \ + (((v) << BP_GPMI_CTRL1_RDN_DELAY) & BM_GPMI_CTRL1_RDN_DELAY) + +#define BM_GPMI_CTRL1_DEV_RESET (1 << 3) +#define BV_GPMI_CTRL1_DEV_RESET__ENABLED 0x0 +#define BV_GPMI_CTRL1_DEV_RESET__DISABLED 0x1 + +#define BM_GPMI_CTRL1_ATA_IRQRDY_POLARITY (1 << 2) +#define BV_GPMI_CTRL1_ATA_IRQRDY_POLARITY__ACTIVELOW 0x0 +#define BV_GPMI_CTRL1_ATA_IRQRDY_POLARITY__ACTIVEHIGH 0x1 + +#define BM_GPMI_CTRL1_CAMERA_MODE (1 << 1) +#define BV_GPMI_CTRL1_GPMI_MODE__NAND 0x0 +#define BV_GPMI_CTRL1_GPMI_MODE__ATA 0x1 + +#define BM_GPMI_CTRL1_GPMI_MODE (1 << 0) + +#define BM_GPMI_CTRL1_CLEAR_MASK (BM_GPMI_CTRL1_WRN_DLY_SEL | \ + BM_GPMI_CTRL1_DLL_ENABLE | \ + BM_GPMI_CTRL1_RDN_DELAY | \ + BM_GPMI_CTRL1_HALF_PERIOD) + +#define HW_GPMI_TIMING0 0x00000070 + +#define BP_GPMI_TIMING0_ADDRESS_SETUP 16 +#define BM_GPMI_TIMING0_ADDRESS_SETUP (0xff << BP_GPMI_TIMING0_ADDRESS_SETUP) +#define BF_GPMI_TIMING0_ADDRESS_SETUP(v) \ + (((v) << BP_GPMI_TIMING0_ADDRESS_SETUP) & BM_GPMI_TIMING0_ADDRESS_SETUP) + +#define BP_GPMI_TIMING0_DATA_HOLD 8 +#define BM_GPMI_TIMING0_DATA_HOLD (0xff << BP_GPMI_TIMING0_DATA_HOLD) +#define BF_GPMI_TIMING0_DATA_HOLD(v) \ + (((v) << BP_GPMI_TIMING0_DATA_HOLD) & BM_GPMI_TIMING0_DATA_HOLD) + +#define BP_GPMI_TIMING0_DATA_SETUP 0 +#define BM_GPMI_TIMING0_DATA_SETUP (0xff << BP_GPMI_TIMING0_DATA_SETUP) +#define BF_GPMI_TIMING0_DATA_SETUP(v) \ + (((v) << BP_GPMI_TIMING0_DATA_SETUP) & BM_GPMI_TIMING0_DATA_SETUP) + +#define HW_GPMI_TIMING1 0x00000080 +#define BP_GPMI_TIMING1_BUSY_TIMEOUT 16 +#define BM_GPMI_TIMING1_BUSY_TIMEOUT (0xffff << BP_GPMI_TIMING1_BUSY_TIMEOUT) +#define BF_GPMI_TIMING1_BUSY_TIMEOUT(v) \ + (((v) << BP_GPMI_TIMING1_BUSY_TIMEOUT) & BM_GPMI_TIMING1_BUSY_TIMEOUT) + +#define HW_GPMI_TIMING2 0x00000090 +#define HW_GPMI_DATA 0x000000a0 + +/* MX28 uses this to detect READY. */ +#define HW_GPMI_STAT 0x000000b0 +#define MX28_BP_GPMI_STAT_READY_BUSY 24 +#define MX28_BM_GPMI_STAT_READY_BUSY (0xff << MX28_BP_GPMI_STAT_READY_BUSY) +#define MX28_BF_GPMI_STAT_READY_BUSY(v) \ + (((v) << MX28_BP_GPMI_STAT_READY_BUSY) & MX28_BM_GPMI_STAT_READY_BUSY) + +/* MX23 uses this to detect READY. */ +#define HW_GPMI_DEBUG 0x000000c0 +#define MX23_BP_GPMI_DEBUG_READY0 28 +#define MX23_BM_GPMI_DEBUG_READY0 (1 << MX23_BP_GPMI_DEBUG_READY0) +#endif diff --git a/drivers/mtd/nand/raw/hisi504_nand.c b/drivers/mtd/nand/raw/hisi504_nand.c new file mode 100644 index 000000000..d97270ec1 --- /dev/null +++ b/drivers/mtd/nand/raw/hisi504_nand.c @@ -0,0 +1,869 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hisilicon NAND Flash controller driver + * + * Copyright © 2012-2014 HiSilicon Technologies Co., Ltd. + * http://www.hisilicon.com + * + * Author: Zhou Wang + * The initial developer of the original code is Zhiyong Cai + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HINFC504_MAX_CHIP (4) +#define HINFC504_W_LATCH (5) +#define HINFC504_R_LATCH (7) +#define HINFC504_RW_LATCH (3) + +#define HINFC504_NFC_TIMEOUT (2 * HZ) +#define HINFC504_NFC_PM_TIMEOUT (1 * HZ) +#define HINFC504_NFC_DMA_TIMEOUT (5 * HZ) +#define HINFC504_CHIP_DELAY (25) + +#define HINFC504_REG_BASE_ADDRESS_LEN (0x100) +#define HINFC504_BUFFER_BASE_ADDRESS_LEN (2048 + 128) + +#define HINFC504_ADDR_CYCLE_MASK 0x4 + +#define HINFC504_CON 0x00 +#define HINFC504_CON_OP_MODE_NORMAL BIT(0) +#define HINFC504_CON_PAGEISZE_SHIFT (1) +#define HINFC504_CON_PAGESIZE_MASK (0x07) +#define HINFC504_CON_BUS_WIDTH BIT(4) +#define HINFC504_CON_READY_BUSY_SEL BIT(8) +#define HINFC504_CON_ECCTYPE_SHIFT (9) +#define HINFC504_CON_ECCTYPE_MASK (0x07) + +#define HINFC504_PWIDTH 0x04 +#define SET_HINFC504_PWIDTH(_w_lcnt, _r_lcnt, _rw_hcnt) \ + ((_w_lcnt) | (((_r_lcnt) & 0x0F) << 4) | (((_rw_hcnt) & 0x0F) << 8)) + +#define HINFC504_CMD 0x0C +#define HINFC504_ADDRL 0x10 +#define HINFC504_ADDRH 0x14 +#define HINFC504_DATA_NUM 0x18 + +#define HINFC504_OP 0x1C +#define HINFC504_OP_READ_DATA_EN BIT(1) +#define HINFC504_OP_WAIT_READY_EN BIT(2) +#define HINFC504_OP_CMD2_EN BIT(3) +#define HINFC504_OP_WRITE_DATA_EN BIT(4) +#define HINFC504_OP_ADDR_EN BIT(5) +#define HINFC504_OP_CMD1_EN BIT(6) +#define HINFC504_OP_NF_CS_SHIFT (7) +#define HINFC504_OP_NF_CS_MASK (3) +#define HINFC504_OP_ADDR_CYCLE_SHIFT (9) +#define HINFC504_OP_ADDR_CYCLE_MASK (7) + +#define HINFC504_STATUS 0x20 +#define HINFC504_READY BIT(0) + +#define HINFC504_INTEN 0x24 +#define HINFC504_INTEN_DMA BIT(9) +#define HINFC504_INTEN_UE BIT(6) +#define HINFC504_INTEN_CE BIT(5) + +#define HINFC504_INTS 0x28 +#define HINFC504_INTS_DMA BIT(9) +#define HINFC504_INTS_UE BIT(6) +#define HINFC504_INTS_CE BIT(5) + +#define HINFC504_INTCLR 0x2C +#define HINFC504_INTCLR_DMA BIT(9) +#define HINFC504_INTCLR_UE BIT(6) +#define HINFC504_INTCLR_CE BIT(5) + +#define HINFC504_ECC_STATUS 0x5C +#define HINFC504_ECC_16_BIT_SHIFT 12 + +#define HINFC504_DMA_CTRL 0x60 +#define HINFC504_DMA_CTRL_DMA_START BIT(0) +#define HINFC504_DMA_CTRL_WE BIT(1) +#define HINFC504_DMA_CTRL_DATA_AREA_EN BIT(2) +#define HINFC504_DMA_CTRL_OOB_AREA_EN BIT(3) +#define HINFC504_DMA_CTRL_BURST4_EN BIT(4) +#define HINFC504_DMA_CTRL_BURST8_EN BIT(5) +#define HINFC504_DMA_CTRL_BURST16_EN BIT(6) +#define HINFC504_DMA_CTRL_ADDR_NUM_SHIFT (7) +#define HINFC504_DMA_CTRL_ADDR_NUM_MASK (1) +#define HINFC504_DMA_CTRL_CS_SHIFT (8) +#define HINFC504_DMA_CTRL_CS_MASK (0x03) + +#define HINFC504_DMA_ADDR_DATA 0x64 +#define HINFC504_DMA_ADDR_OOB 0x68 + +#define HINFC504_DMA_LEN 0x6C +#define HINFC504_DMA_LEN_OOB_SHIFT (16) +#define HINFC504_DMA_LEN_OOB_MASK (0xFFF) + +#define HINFC504_DMA_PARA 0x70 +#define HINFC504_DMA_PARA_DATA_RW_EN BIT(0) +#define HINFC504_DMA_PARA_OOB_RW_EN BIT(1) +#define HINFC504_DMA_PARA_DATA_EDC_EN BIT(2) +#define HINFC504_DMA_PARA_OOB_EDC_EN BIT(3) +#define HINFC504_DMA_PARA_DATA_ECC_EN BIT(4) +#define HINFC504_DMA_PARA_OOB_ECC_EN BIT(5) + +#define HINFC_VERSION 0x74 +#define HINFC504_LOG_READ_ADDR 0x7C +#define HINFC504_LOG_READ_LEN 0x80 + +#define HINFC504_NANDINFO_LEN 0x10 + +struct hinfc_host { + struct nand_chip chip; + struct device *dev; + void __iomem *iobase; + void __iomem *mmio; + struct completion cmd_complete; + unsigned int offset; + unsigned int command; + int chipselect; + unsigned int addr_cycle; + u32 addr_value[2]; + u32 cache_addr_value[2]; + char *buffer; + dma_addr_t dma_buffer; + dma_addr_t dma_oob; + int version; + unsigned int irq_status; /* interrupt status */ +}; + +static inline unsigned int hinfc_read(struct hinfc_host *host, unsigned int reg) +{ + return readl(host->iobase + reg); +} + +static inline void hinfc_write(struct hinfc_host *host, unsigned int value, + unsigned int reg) +{ + writel(value, host->iobase + reg); +} + +static void wait_controller_finished(struct hinfc_host *host) +{ + unsigned long timeout = jiffies + HINFC504_NFC_TIMEOUT; + int val; + + while (time_before(jiffies, timeout)) { + val = hinfc_read(host, HINFC504_STATUS); + if (host->command == NAND_CMD_ERASE2) { + /* nfc is ready */ + while (!(val & HINFC504_READY)) { + usleep_range(500, 1000); + val = hinfc_read(host, HINFC504_STATUS); + } + return; + } + + if (val & HINFC504_READY) + return; + } + + /* wait cmd timeout */ + dev_err(host->dev, "Wait NAND controller exec cmd timeout.\n"); +} + +static void hisi_nfc_dma_transfer(struct hinfc_host *host, int todev) +{ + struct nand_chip *chip = &host->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned long val; + int ret; + + hinfc_write(host, host->dma_buffer, HINFC504_DMA_ADDR_DATA); + hinfc_write(host, host->dma_oob, HINFC504_DMA_ADDR_OOB); + + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_NONE) { + hinfc_write(host, ((mtd->oobsize & HINFC504_DMA_LEN_OOB_MASK) + << HINFC504_DMA_LEN_OOB_SHIFT), HINFC504_DMA_LEN); + + hinfc_write(host, HINFC504_DMA_PARA_DATA_RW_EN + | HINFC504_DMA_PARA_OOB_RW_EN, HINFC504_DMA_PARA); + } else { + if (host->command == NAND_CMD_READOOB) + hinfc_write(host, HINFC504_DMA_PARA_OOB_RW_EN + | HINFC504_DMA_PARA_OOB_EDC_EN + | HINFC504_DMA_PARA_OOB_ECC_EN, HINFC504_DMA_PARA); + else + hinfc_write(host, HINFC504_DMA_PARA_DATA_RW_EN + | HINFC504_DMA_PARA_OOB_RW_EN + | HINFC504_DMA_PARA_DATA_EDC_EN + | HINFC504_DMA_PARA_OOB_EDC_EN + | HINFC504_DMA_PARA_DATA_ECC_EN + | HINFC504_DMA_PARA_OOB_ECC_EN, HINFC504_DMA_PARA); + + } + + val = (HINFC504_DMA_CTRL_DMA_START | HINFC504_DMA_CTRL_BURST4_EN + | HINFC504_DMA_CTRL_BURST8_EN | HINFC504_DMA_CTRL_BURST16_EN + | HINFC504_DMA_CTRL_DATA_AREA_EN | HINFC504_DMA_CTRL_OOB_AREA_EN + | ((host->addr_cycle == 4 ? 1 : 0) + << HINFC504_DMA_CTRL_ADDR_NUM_SHIFT) + | ((host->chipselect & HINFC504_DMA_CTRL_CS_MASK) + << HINFC504_DMA_CTRL_CS_SHIFT)); + + if (todev) + val |= HINFC504_DMA_CTRL_WE; + + init_completion(&host->cmd_complete); + + hinfc_write(host, val, HINFC504_DMA_CTRL); + ret = wait_for_completion_timeout(&host->cmd_complete, + HINFC504_NFC_DMA_TIMEOUT); + + if (!ret) { + dev_err(host->dev, "DMA operation(irq) timeout!\n"); + /* sanity check */ + val = hinfc_read(host, HINFC504_DMA_CTRL); + if (!(val & HINFC504_DMA_CTRL_DMA_START)) + dev_err(host->dev, "DMA is already done but without irq ACK!\n"); + else + dev_err(host->dev, "DMA is really timeout!\n"); + } +} + +static int hisi_nfc_send_cmd_pageprog(struct hinfc_host *host) +{ + host->addr_value[0] &= 0xffff0000; + + hinfc_write(host, host->addr_value[0], HINFC504_ADDRL); + hinfc_write(host, host->addr_value[1], HINFC504_ADDRH); + hinfc_write(host, NAND_CMD_PAGEPROG << 8 | NAND_CMD_SEQIN, + HINFC504_CMD); + + hisi_nfc_dma_transfer(host, 1); + + return 0; +} + +static int hisi_nfc_send_cmd_readstart(struct hinfc_host *host) +{ + struct mtd_info *mtd = nand_to_mtd(&host->chip); + + if ((host->addr_value[0] == host->cache_addr_value[0]) && + (host->addr_value[1] == host->cache_addr_value[1])) + return 0; + + host->addr_value[0] &= 0xffff0000; + + hinfc_write(host, host->addr_value[0], HINFC504_ADDRL); + hinfc_write(host, host->addr_value[1], HINFC504_ADDRH); + hinfc_write(host, NAND_CMD_READSTART << 8 | NAND_CMD_READ0, + HINFC504_CMD); + + hinfc_write(host, 0, HINFC504_LOG_READ_ADDR); + hinfc_write(host, mtd->writesize + mtd->oobsize, + HINFC504_LOG_READ_LEN); + + hisi_nfc_dma_transfer(host, 0); + + host->cache_addr_value[0] = host->addr_value[0]; + host->cache_addr_value[1] = host->addr_value[1]; + + return 0; +} + +static int hisi_nfc_send_cmd_erase(struct hinfc_host *host) +{ + hinfc_write(host, host->addr_value[0], HINFC504_ADDRL); + hinfc_write(host, (NAND_CMD_ERASE2 << 8) | NAND_CMD_ERASE1, + HINFC504_CMD); + + hinfc_write(host, HINFC504_OP_WAIT_READY_EN + | HINFC504_OP_CMD2_EN + | HINFC504_OP_CMD1_EN + | HINFC504_OP_ADDR_EN + | ((host->chipselect & HINFC504_OP_NF_CS_MASK) + << HINFC504_OP_NF_CS_SHIFT) + | ((host->addr_cycle & HINFC504_OP_ADDR_CYCLE_MASK) + << HINFC504_OP_ADDR_CYCLE_SHIFT), + HINFC504_OP); + + wait_controller_finished(host); + + return 0; +} + +static int hisi_nfc_send_cmd_readid(struct hinfc_host *host) +{ + hinfc_write(host, HINFC504_NANDINFO_LEN, HINFC504_DATA_NUM); + hinfc_write(host, NAND_CMD_READID, HINFC504_CMD); + hinfc_write(host, 0, HINFC504_ADDRL); + + hinfc_write(host, HINFC504_OP_CMD1_EN | HINFC504_OP_ADDR_EN + | HINFC504_OP_READ_DATA_EN + | ((host->chipselect & HINFC504_OP_NF_CS_MASK) + << HINFC504_OP_NF_CS_SHIFT) + | 1 << HINFC504_OP_ADDR_CYCLE_SHIFT, HINFC504_OP); + + wait_controller_finished(host); + + return 0; +} + +static int hisi_nfc_send_cmd_status(struct hinfc_host *host) +{ + hinfc_write(host, HINFC504_NANDINFO_LEN, HINFC504_DATA_NUM); + hinfc_write(host, NAND_CMD_STATUS, HINFC504_CMD); + hinfc_write(host, HINFC504_OP_CMD1_EN + | HINFC504_OP_READ_DATA_EN + | ((host->chipselect & HINFC504_OP_NF_CS_MASK) + << HINFC504_OP_NF_CS_SHIFT), + HINFC504_OP); + + wait_controller_finished(host); + + return 0; +} + +static int hisi_nfc_send_cmd_reset(struct hinfc_host *host, int chipselect) +{ + hinfc_write(host, NAND_CMD_RESET, HINFC504_CMD); + + hinfc_write(host, HINFC504_OP_CMD1_EN + | ((chipselect & HINFC504_OP_NF_CS_MASK) + << HINFC504_OP_NF_CS_SHIFT) + | HINFC504_OP_WAIT_READY_EN, + HINFC504_OP); + + wait_controller_finished(host); + + return 0; +} + +static void hisi_nfc_select_chip(struct nand_chip *chip, int chipselect) +{ + struct hinfc_host *host = nand_get_controller_data(chip); + + if (chipselect < 0) + return; + + host->chipselect = chipselect; +} + +static uint8_t hisi_nfc_read_byte(struct nand_chip *chip) +{ + struct hinfc_host *host = nand_get_controller_data(chip); + + if (host->command == NAND_CMD_STATUS) + return *(uint8_t *)(host->mmio); + + host->offset++; + + if (host->command == NAND_CMD_READID) + return *(uint8_t *)(host->mmio + host->offset - 1); + + return *(uint8_t *)(host->buffer + host->offset - 1); +} + +static void +hisi_nfc_write_buf(struct nand_chip *chip, const uint8_t *buf, int len) +{ + struct hinfc_host *host = nand_get_controller_data(chip); + + memcpy(host->buffer + host->offset, buf, len); + host->offset += len; +} + +static void hisi_nfc_read_buf(struct nand_chip *chip, uint8_t *buf, int len) +{ + struct hinfc_host *host = nand_get_controller_data(chip); + + memcpy(buf, host->buffer + host->offset, len); + host->offset += len; +} + +static void set_addr(struct mtd_info *mtd, int column, int page_addr) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct hinfc_host *host = nand_get_controller_data(chip); + unsigned int command = host->command; + + host->addr_cycle = 0; + host->addr_value[0] = 0; + host->addr_value[1] = 0; + + /* Serially input address */ + if (column != -1) { + /* Adjust columns for 16 bit buswidth */ + if (chip->options & NAND_BUSWIDTH_16 && + !nand_opcode_8bits(command)) + column >>= 1; + + host->addr_value[0] = column & 0xffff; + host->addr_cycle = 2; + } + if (page_addr != -1) { + host->addr_value[0] |= (page_addr & 0xffff) + << (host->addr_cycle * 8); + host->addr_cycle += 2; + if (chip->options & NAND_ROW_ADDR_3) { + host->addr_cycle += 1; + if (host->command == NAND_CMD_ERASE1) + host->addr_value[0] |= ((page_addr >> 16) & 0xff) << 16; + else + host->addr_value[1] |= ((page_addr >> 16) & 0xff); + } + } +} + +static void hisi_nfc_cmdfunc(struct nand_chip *chip, unsigned command, + int column, int page_addr) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct hinfc_host *host = nand_get_controller_data(chip); + int is_cache_invalid = 1; + unsigned int flag = 0; + + host->command = command; + + switch (command) { + case NAND_CMD_READ0: + case NAND_CMD_READOOB: + if (command == NAND_CMD_READ0) + host->offset = column; + else + host->offset = column + mtd->writesize; + + is_cache_invalid = 0; + set_addr(mtd, column, page_addr); + hisi_nfc_send_cmd_readstart(host); + break; + + case NAND_CMD_SEQIN: + host->offset = column; + set_addr(mtd, column, page_addr); + break; + + case NAND_CMD_ERASE1: + set_addr(mtd, column, page_addr); + break; + + case NAND_CMD_PAGEPROG: + hisi_nfc_send_cmd_pageprog(host); + break; + + case NAND_CMD_ERASE2: + hisi_nfc_send_cmd_erase(host); + break; + + case NAND_CMD_READID: + host->offset = column; + memset(host->mmio, 0, 0x10); + hisi_nfc_send_cmd_readid(host); + break; + + case NAND_CMD_STATUS: + flag = hinfc_read(host, HINFC504_CON); + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) + hinfc_write(host, + flag & ~(HINFC504_CON_ECCTYPE_MASK << + HINFC504_CON_ECCTYPE_SHIFT), HINFC504_CON); + + host->offset = 0; + memset(host->mmio, 0, 0x10); + hisi_nfc_send_cmd_status(host); + hinfc_write(host, flag, HINFC504_CON); + break; + + case NAND_CMD_RESET: + hisi_nfc_send_cmd_reset(host, host->chipselect); + break; + + default: + dev_err(host->dev, "Error: unsupported cmd(cmd=%x, col=%x, page=%x)\n", + command, column, page_addr); + } + + if (is_cache_invalid) { + host->cache_addr_value[0] = ~0; + host->cache_addr_value[1] = ~0; + } +} + +static irqreturn_t hinfc_irq_handle(int irq, void *devid) +{ + struct hinfc_host *host = devid; + unsigned int flag; + + flag = hinfc_read(host, HINFC504_INTS); + /* store interrupts state */ + host->irq_status |= flag; + + if (flag & HINFC504_INTS_DMA) { + hinfc_write(host, HINFC504_INTCLR_DMA, HINFC504_INTCLR); + complete(&host->cmd_complete); + } else if (flag & HINFC504_INTS_CE) { + hinfc_write(host, HINFC504_INTCLR_CE, HINFC504_INTCLR); + } else if (flag & HINFC504_INTS_UE) { + hinfc_write(host, HINFC504_INTCLR_UE, HINFC504_INTCLR); + } + + return IRQ_HANDLED; +} + +static int hisi_nand_read_page_hwecc(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct hinfc_host *host = nand_get_controller_data(chip); + int max_bitflips = 0, stat = 0, stat_max = 0, status_ecc; + int stat_1, stat_2; + + nand_read_page_op(chip, page, 0, buf, mtd->writesize); + chip->legacy.read_buf(chip, chip->oob_poi, mtd->oobsize); + + /* errors which can not be corrected by ECC */ + if (host->irq_status & HINFC504_INTS_UE) { + mtd->ecc_stats.failed++; + } else if (host->irq_status & HINFC504_INTS_CE) { + /* TODO: need add other ECC modes! */ + switch (chip->ecc.strength) { + case 16: + status_ecc = hinfc_read(host, HINFC504_ECC_STATUS) >> + HINFC504_ECC_16_BIT_SHIFT & 0x0fff; + stat_2 = status_ecc & 0x3f; + stat_1 = status_ecc >> 6 & 0x3f; + stat = stat_1 + stat_2; + stat_max = max_t(int, stat_1, stat_2); + } + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(int, max_bitflips, stat_max); + } + host->irq_status = 0; + + return max_bitflips; +} + +static int hisi_nand_read_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct hinfc_host *host = nand_get_controller_data(chip); + + nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize); + + if (host->irq_status & HINFC504_INTS_UE) { + host->irq_status = 0; + return -EBADMSG; + } + + host->irq_status = 0; + return 0; +} + +static int hisi_nand_write_page_hwecc(struct nand_chip *chip, + const uint8_t *buf, int oob_required, + int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize); + if (oob_required) + chip->legacy.write_buf(chip, chip->oob_poi, mtd->oobsize); + + return nand_prog_page_end_op(chip); +} + +static void hisi_nfc_host_init(struct hinfc_host *host) +{ + struct nand_chip *chip = &host->chip; + unsigned int flag = 0; + + host->version = hinfc_read(host, HINFC_VERSION); + host->addr_cycle = 0; + host->addr_value[0] = 0; + host->addr_value[1] = 0; + host->cache_addr_value[0] = ~0; + host->cache_addr_value[1] = ~0; + host->chipselect = 0; + + /* default page size: 2K, ecc_none. need modify */ + flag = HINFC504_CON_OP_MODE_NORMAL | HINFC504_CON_READY_BUSY_SEL + | ((0x001 & HINFC504_CON_PAGESIZE_MASK) + << HINFC504_CON_PAGEISZE_SHIFT) + | ((0x0 & HINFC504_CON_ECCTYPE_MASK) + << HINFC504_CON_ECCTYPE_SHIFT) + | ((chip->options & NAND_BUSWIDTH_16) ? + HINFC504_CON_BUS_WIDTH : 0); + hinfc_write(host, flag, HINFC504_CON); + + memset(host->mmio, 0xff, HINFC504_BUFFER_BASE_ADDRESS_LEN); + + hinfc_write(host, SET_HINFC504_PWIDTH(HINFC504_W_LATCH, + HINFC504_R_LATCH, HINFC504_RW_LATCH), HINFC504_PWIDTH); + + /* enable DMA irq */ + hinfc_write(host, HINFC504_INTEN_DMA, HINFC504_INTEN); +} + +static int hisi_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + /* FIXME: add ECC bytes position */ + return -ENOTSUPP; +} + +static int hisi_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section) + return -ERANGE; + + oobregion->offset = 2; + oobregion->length = 6; + + return 0; +} + +static const struct mtd_ooblayout_ops hisi_ooblayout_ops = { + .ecc = hisi_ooblayout_ecc, + .free = hisi_ooblayout_free, +}; + +static int hisi_nfc_ecc_probe(struct hinfc_host *host) +{ + unsigned int flag; + int size, strength, ecc_bits; + struct device *dev = host->dev; + struct nand_chip *chip = &host->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + + size = chip->ecc.size; + strength = chip->ecc.strength; + if (size != 1024) { + dev_err(dev, "error ecc size: %d\n", size); + return -EINVAL; + } + + if ((size == 1024) && ((strength != 8) && (strength != 16) && + (strength != 24) && (strength != 40))) { + dev_err(dev, "ecc size and strength do not match\n"); + return -EINVAL; + } + + chip->ecc.size = size; + chip->ecc.strength = strength; + + chip->ecc.read_page = hisi_nand_read_page_hwecc; + chip->ecc.read_oob = hisi_nand_read_oob; + chip->ecc.write_page = hisi_nand_write_page_hwecc; + + switch (chip->ecc.strength) { + case 16: + ecc_bits = 6; + if (mtd->writesize == 2048) + mtd_set_ooblayout(mtd, &hisi_ooblayout_ops); + + /* TODO: add more page size support */ + break; + + /* TODO: add more ecc strength support */ + default: + dev_err(dev, "not support strength: %d\n", chip->ecc.strength); + return -EINVAL; + } + + flag = hinfc_read(host, HINFC504_CON); + /* add ecc type configure */ + flag |= ((ecc_bits & HINFC504_CON_ECCTYPE_MASK) + << HINFC504_CON_ECCTYPE_SHIFT); + hinfc_write(host, flag, HINFC504_CON); + + /* enable ecc irq */ + flag = hinfc_read(host, HINFC504_INTEN) & 0xfff; + hinfc_write(host, flag | HINFC504_INTEN_UE | HINFC504_INTEN_CE, + HINFC504_INTEN); + + return 0; +} + +static int hisi_nfc_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct hinfc_host *host = nand_get_controller_data(chip); + int flag; + + host->buffer = dmam_alloc_coherent(host->dev, + mtd->writesize + mtd->oobsize, + &host->dma_buffer, GFP_KERNEL); + if (!host->buffer) + return -ENOMEM; + + host->dma_oob = host->dma_buffer + mtd->writesize; + memset(host->buffer, 0xff, mtd->writesize + mtd->oobsize); + + flag = hinfc_read(host, HINFC504_CON); + flag &= ~(HINFC504_CON_PAGESIZE_MASK << HINFC504_CON_PAGEISZE_SHIFT); + switch (mtd->writesize) { + case 2048: + flag |= (0x001 << HINFC504_CON_PAGEISZE_SHIFT); + break; + /* + * TODO: add more pagesize support, + * default pagesize has been set in hisi_nfc_host_init + */ + default: + dev_err(host->dev, "NON-2KB page size nand flash\n"); + return -EINVAL; + } + hinfc_write(host, flag, HINFC504_CON); + + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) + hisi_nfc_ecc_probe(host); + + return 0; +} + +static const struct nand_controller_ops hisi_nfc_controller_ops = { + .attach_chip = hisi_nfc_attach_chip, +}; + +static int hisi_nfc_probe(struct platform_device *pdev) +{ + int ret = 0, irq, max_chips = HINFC504_MAX_CHIP; + struct device *dev = &pdev->dev; + struct hinfc_host *host; + struct nand_chip *chip; + struct mtd_info *mtd; + struct device_node *np = dev->of_node; + + host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + host->dev = dev; + + platform_set_drvdata(pdev, host); + chip = &host->chip; + mtd = nand_to_mtd(chip); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return -ENXIO; + + host->iobase = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(host->iobase)) + return PTR_ERR(host->iobase); + + host->mmio = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(host->mmio)) + return PTR_ERR(host->mmio); + + mtd->name = "hisi_nand"; + mtd->dev.parent = &pdev->dev; + + nand_set_controller_data(chip, host); + nand_set_flash_node(chip, np); + chip->legacy.cmdfunc = hisi_nfc_cmdfunc; + chip->legacy.select_chip = hisi_nfc_select_chip; + chip->legacy.read_byte = hisi_nfc_read_byte; + chip->legacy.write_buf = hisi_nfc_write_buf; + chip->legacy.read_buf = hisi_nfc_read_buf; + chip->legacy.chip_delay = HINFC504_CHIP_DELAY; + chip->legacy.set_features = nand_get_set_features_notsupp; + chip->legacy.get_features = nand_get_set_features_notsupp; + + hisi_nfc_host_init(host); + + ret = devm_request_irq(dev, irq, hinfc_irq_handle, 0x0, "nandc", host); + if (ret) { + dev_err(dev, "failed to request IRQ\n"); + return ret; + } + + chip->legacy.dummy_controller.ops = &hisi_nfc_controller_ops; + ret = nand_scan(chip, max_chips); + if (ret) + return ret; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(dev, "Err MTD partition=%d\n", ret); + nand_cleanup(chip); + return ret; + } + + return 0; +} + +static void hisi_nfc_remove(struct platform_device *pdev) +{ + struct hinfc_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); +} + +#ifdef CONFIG_PM_SLEEP +static int hisi_nfc_suspend(struct device *dev) +{ + struct hinfc_host *host = dev_get_drvdata(dev); + unsigned long timeout = jiffies + HINFC504_NFC_PM_TIMEOUT; + + while (time_before(jiffies, timeout)) { + if (((hinfc_read(host, HINFC504_STATUS) & 0x1) == 0x0) && + (hinfc_read(host, HINFC504_DMA_CTRL) & + HINFC504_DMA_CTRL_DMA_START)) { + cond_resched(); + return 0; + } + } + + dev_err(host->dev, "nand controller suspend timeout.\n"); + + return -EAGAIN; +} + +static int hisi_nfc_resume(struct device *dev) +{ + int cs; + struct hinfc_host *host = dev_get_drvdata(dev); + struct nand_chip *chip = &host->chip; + + for (cs = 0; cs < nanddev_ntargets(&chip->base); cs++) + hisi_nfc_send_cmd_reset(host, cs); + hinfc_write(host, SET_HINFC504_PWIDTH(HINFC504_W_LATCH, + HINFC504_R_LATCH, HINFC504_RW_LATCH), HINFC504_PWIDTH); + + return 0; +} +#endif +static SIMPLE_DEV_PM_OPS(hisi_nfc_pm_ops, hisi_nfc_suspend, hisi_nfc_resume); + +static const struct of_device_id nfc_id_table[] = { + { .compatible = "hisilicon,504-nfc" }, + {} +}; +MODULE_DEVICE_TABLE(of, nfc_id_table); + +static struct platform_driver hisi_nfc_driver = { + .driver = { + .name = "hisi_nand", + .of_match_table = nfc_id_table, + .pm = &hisi_nfc_pm_ops, + }, + .probe = hisi_nfc_probe, + .remove = hisi_nfc_remove, +}; + +module_platform_driver(hisi_nfc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Zhou Wang"); +MODULE_AUTHOR("Zhiyong Cai"); +MODULE_DESCRIPTION("Hisilicon Nand Flash Controller Driver"); diff --git a/drivers/mtd/nand/raw/ingenic/Kconfig b/drivers/mtd/nand/raw/ingenic/Kconfig new file mode 100644 index 000000000..96c5ae8b1 --- /dev/null +++ b/drivers/mtd/nand/raw/ingenic/Kconfig @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: GPL-2.0-only +config MTD_NAND_JZ4780 + tristate "JZ4780 NAND controller" + depends on MIPS || COMPILE_TEST + depends on JZ4780_NEMC + help + Enables support for NAND Flash connected to the NEMC on JZ4780 SoC + based boards, using the BCH controller for hardware error correction. + +if MTD_NAND_JZ4780 + +config MTD_NAND_INGENIC_ECC + bool + +config MTD_NAND_JZ4740_ECC + tristate "Hardware BCH support for JZ4740 SoC" + select MTD_NAND_INGENIC_ECC + help + Enable this driver to support the Reed-Solomon error-correction + hardware present on the JZ4740 SoC from Ingenic. + + This driver can also be built as a module. If so, the module + will be called jz4740-ecc. + +config MTD_NAND_JZ4725B_BCH + tristate "Hardware BCH support for JZ4725B SoC" + select MTD_NAND_INGENIC_ECC + help + Enable this driver to support the BCH error-correction hardware + present on the JZ4725B SoC from Ingenic. + + This driver can also be built as a module. If so, the module + will be called jz4725b-bch. + +config MTD_NAND_JZ4780_BCH + tristate "Hardware BCH support for JZ4780 SoC" + select MTD_NAND_INGENIC_ECC + help + Enable this driver to support the BCH error-correction hardware + present on the JZ4780 SoC from Ingenic. + + This driver can also be built as a module. If so, the module + will be called jz4780-bch. + +endif # MTD_NAND_JZ4780 diff --git a/drivers/mtd/nand/raw/ingenic/Makefile b/drivers/mtd/nand/raw/ingenic/Makefile new file mode 100644 index 000000000..4c53f5e75 --- /dev/null +++ b/drivers/mtd/nand/raw/ingenic/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_MTD_NAND_JZ4780) += ingenic_nand.o + +ingenic_nand-y += ingenic_nand_drv.o +ingenic_nand-$(CONFIG_MTD_NAND_INGENIC_ECC) += ingenic_ecc.o + +obj-$(CONFIG_MTD_NAND_JZ4740_ECC) += jz4740_ecc.o +obj-$(CONFIG_MTD_NAND_JZ4725B_BCH) += jz4725b_bch.o +obj-$(CONFIG_MTD_NAND_JZ4780_BCH) += jz4780_bch.o diff --git a/drivers/mtd/nand/raw/ingenic/ingenic_ecc.c b/drivers/mtd/nand/raw/ingenic/ingenic_ecc.c new file mode 100644 index 000000000..beb033705 --- /dev/null +++ b/drivers/mtd/nand/raw/ingenic/ingenic_ecc.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * JZ47xx ECC common code + * + * Copyright (c) 2015 Imagination Technologies + * Author: Alex Smith + */ + +#include +#include +#include +#include +#include +#include + +#include "ingenic_ecc.h" + +/** + * ingenic_ecc_calculate() - calculate ECC for a data buffer + * @ecc: ECC device. + * @params: ECC parameters. + * @buf: input buffer with raw data. + * @ecc_code: output buffer with ECC. + * + * Return: 0 on success, -ETIMEDOUT if timed out while waiting for ECC + * controller. + */ +int ingenic_ecc_calculate(struct ingenic_ecc *ecc, + struct ingenic_ecc_params *params, + const u8 *buf, u8 *ecc_code) +{ + return ecc->ops->calculate(ecc, params, buf, ecc_code); +} + +/** + * ingenic_ecc_correct() - detect and correct bit errors + * @ecc: ECC device. + * @params: ECC parameters. + * @buf: raw data read from the chip. + * @ecc_code: ECC read from the chip. + * + * Given the raw data and the ECC read from the NAND device, detects and + * corrects errors in the data. + * + * Return: the number of bit errors corrected, -EBADMSG if there are too many + * errors to correct or -ETIMEDOUT if we timed out waiting for the controller. + */ +int ingenic_ecc_correct(struct ingenic_ecc *ecc, + struct ingenic_ecc_params *params, + u8 *buf, u8 *ecc_code) +{ + return ecc->ops->correct(ecc, params, buf, ecc_code); +} + +/** + * ingenic_ecc_get() - get the ECC controller device + * @np: ECC device tree node. + * + * Gets the ECC controller device from the specified device tree node. The + * device must be released with ingenic_ecc_release() when it is no longer being + * used. + * + * Return: a pointer to ingenic_ecc, errors are encoded into the pointer. + * PTR_ERR(-EPROBE_DEFER) if the device hasn't been initialised yet. + */ +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) + return ERR_PTR(-EPROBE_DEFER); + + if (!platform_get_drvdata(pdev)) { + put_device(&pdev->dev); + return ERR_PTR(-EPROBE_DEFER); + } + + ecc = platform_get_drvdata(pdev); + ret = clk_prepare_enable(ecc->clk); + if (ret) { + put_device(&pdev->dev); + return ERR_PTR(ret); + } + + return ecc; +} + +/** + * of_ingenic_ecc_get() - get the ECC controller from a DT node + * @of_node: the node that contains an ecc-engine property. + * + * Get the ecc-engine property from the given device tree + * node and pass it to ingenic_ecc_get to do the work. + * + * Return: a pointer to ingenic_ecc, errors are encoded into the pointer. + * PTR_ERR(-EPROBE_DEFER) if the device hasn't been initialised yet. + */ +struct ingenic_ecc *of_ingenic_ecc_get(struct device_node *of_node) +{ + struct ingenic_ecc *ecc = NULL; + struct device_node *np; + + np = of_parse_phandle(of_node, "ecc-engine", 0); + + /* + * If the ecc-engine property is not found, check for the deprecated + * ingenic,bch-controller property + */ + if (!np) + np = of_parse_phandle(of_node, "ingenic,bch-controller", 0); + + if (np) { + ecc = ingenic_ecc_get(np); + of_node_put(np); + } + return ecc; +} + +/** + * ingenic_ecc_release() - release the ECC controller device + * @ecc: ECC device. + */ +void ingenic_ecc_release(struct ingenic_ecc *ecc) +{ + clk_disable_unprepare(ecc->clk); + put_device(ecc->dev); +} + +int ingenic_ecc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ingenic_ecc *ecc; + + ecc = devm_kzalloc(dev, sizeof(*ecc), GFP_KERNEL); + if (!ecc) + return -ENOMEM; + + ecc->ops = device_get_match_data(dev); + if (!ecc->ops) + return -EINVAL; + + ecc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ecc->base)) + return PTR_ERR(ecc->base); + + ecc->ops->disable(ecc); + + ecc->clk = devm_clk_get(dev, NULL); + if (IS_ERR(ecc->clk)) { + dev_err(dev, "failed to get clock: %ld\n", PTR_ERR(ecc->clk)); + return PTR_ERR(ecc->clk); + } + + mutex_init(&ecc->lock); + + ecc->dev = dev; + platform_set_drvdata(pdev, ecc); + + return 0; +} +EXPORT_SYMBOL(ingenic_ecc_probe); diff --git a/drivers/mtd/nand/raw/ingenic/ingenic_ecc.h b/drivers/mtd/nand/raw/ingenic/ingenic_ecc.h new file mode 100644 index 000000000..017868f59 --- /dev/null +++ b/drivers/mtd/nand/raw/ingenic/ingenic_ecc.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __DRIVERS_MTD_NAND_INGENIC_ECC_INTERNAL_H__ +#define __DRIVERS_MTD_NAND_INGENIC_ECC_INTERNAL_H__ + +#include +#include +#include +#include +#include + +struct clk; +struct device; +struct ingenic_ecc; +struct platform_device; + +/** + * struct ingenic_ecc_params - ECC parameters + * @size: data bytes per ECC step. + * @bytes: ECC bytes per step. + * @strength: number of correctable bits per ECC step. + */ +struct ingenic_ecc_params { + int size; + int bytes; + int strength; +}; + +#if IS_ENABLED(CONFIG_MTD_NAND_INGENIC_ECC) +int ingenic_ecc_calculate(struct ingenic_ecc *ecc, + struct ingenic_ecc_params *params, + const u8 *buf, u8 *ecc_code); +int ingenic_ecc_correct(struct ingenic_ecc *ecc, + struct ingenic_ecc_params *params, u8 *buf, + u8 *ecc_code); + +void ingenic_ecc_release(struct ingenic_ecc *ecc); +struct ingenic_ecc *of_ingenic_ecc_get(struct device_node *np); +#else /* CONFIG_MTD_NAND_INGENIC_ECC */ +static inline int ingenic_ecc_calculate(struct ingenic_ecc *ecc, + struct ingenic_ecc_params *params, + const u8 *buf, u8 *ecc_code) +{ + return -ENODEV; +} + +static inline int ingenic_ecc_correct(struct ingenic_ecc *ecc, + struct ingenic_ecc_params *params, u8 *buf, + u8 *ecc_code) +{ + return -ENODEV; +} + +static inline void ingenic_ecc_release(struct ingenic_ecc *ecc) +{ +} + +static inline struct ingenic_ecc *of_ingenic_ecc_get(struct device_node *np) +{ + return ERR_PTR(-ENODEV); +} +#endif /* CONFIG_MTD_NAND_INGENIC_ECC */ + +struct ingenic_ecc_ops { + void (*disable)(struct ingenic_ecc *ecc); + int (*calculate)(struct ingenic_ecc *ecc, + struct ingenic_ecc_params *params, + const u8 *buf, u8 *ecc_code); + int (*correct)(struct ingenic_ecc *ecc, + struct ingenic_ecc_params *params, + u8 *buf, u8 *ecc_code); +}; + +struct ingenic_ecc { + struct device *dev; + const struct ingenic_ecc_ops *ops; + void __iomem *base; + struct clk *clk; + struct mutex lock; +}; + +int ingenic_ecc_probe(struct platform_device *pdev); + +#endif /* __DRIVERS_MTD_NAND_INGENIC_ECC_INTERNAL_H__ */ diff --git a/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c b/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c new file mode 100644 index 000000000..f1e2c8293 --- /dev/null +++ b/drivers/mtd/nand/raw/ingenic/ingenic_nand_drv.c @@ -0,0 +1,561 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Ingenic JZ47xx NAND driver + * + * Copyright (c) 2015 Imagination Technologies + * Author: Alex Smith + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ingenic_ecc.h" + +#define DRV_NAME "ingenic-nand" + +struct jz_soc_info { + unsigned long data_offset; + unsigned long addr_offset; + unsigned long cmd_offset; + const struct mtd_ooblayout_ops *oob_layout; + bool oob_first; +}; + +struct ingenic_nand_cs { + unsigned int bank; + void __iomem *base; +}; + +struct ingenic_nfc { + struct device *dev; + struct ingenic_ecc *ecc; + const struct jz_soc_info *soc_info; + struct nand_controller controller; + unsigned int num_banks; + struct list_head chips; + struct ingenic_nand_cs cs[] __counted_by(num_banks); +}; + +struct ingenic_nand { + struct nand_chip chip; + struct list_head chip_list; + + struct gpio_desc *busy_gpio; + struct gpio_desc *wp_gpio; + unsigned int reading: 1; +}; + +static inline struct ingenic_nand *to_ingenic_nand(struct mtd_info *mtd) +{ + return container_of(mtd_to_nand(mtd), struct ingenic_nand, chip); +} + +static inline struct ingenic_nfc *to_ingenic_nfc(struct nand_controller *ctrl) +{ + return container_of(ctrl, struct ingenic_nfc, controller); +} + +static int qi_lb60_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct nand_ecc_ctrl *ecc = &chip->ecc; + + if (section || !ecc->total) + return -ERANGE; + + oobregion->length = ecc->total; + oobregion->offset = 12; + + return 0; +} + +static int qi_lb60_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct nand_ecc_ctrl *ecc = &chip->ecc; + + if (section) + return -ERANGE; + + oobregion->length = mtd->oobsize - ecc->total - 12; + oobregion->offset = 12 + ecc->total; + + return 0; +} + +static const struct mtd_ooblayout_ops qi_lb60_ooblayout_ops = { + .ecc = qi_lb60_ooblayout_ecc, + .free = qi_lb60_ooblayout_free, +}; + +static int jz4725b_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct nand_ecc_ctrl *ecc = &chip->ecc; + + if (section || !ecc->total) + return -ERANGE; + + oobregion->length = ecc->total; + oobregion->offset = 3; + + return 0; +} + +static int jz4725b_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct nand_ecc_ctrl *ecc = &chip->ecc; + + if (section) + return -ERANGE; + + oobregion->length = mtd->oobsize - ecc->total - 3; + oobregion->offset = 3 + ecc->total; + + return 0; +} + +static const struct mtd_ooblayout_ops jz4725b_ooblayout_ops = { + .ecc = jz4725b_ooblayout_ecc, + .free = jz4725b_ooblayout_free, +}; + +static void ingenic_nand_ecc_hwctl(struct nand_chip *chip, int mode) +{ + struct ingenic_nand *nand = to_ingenic_nand(nand_to_mtd(chip)); + + nand->reading = (mode == NAND_ECC_READ); +} + +static int ingenic_nand_ecc_calculate(struct nand_chip *chip, const u8 *dat, + u8 *ecc_code) +{ + struct ingenic_nand *nand = to_ingenic_nand(nand_to_mtd(chip)); + struct ingenic_nfc *nfc = to_ingenic_nfc(nand->chip.controller); + struct ingenic_ecc_params params; + + /* + * Don't need to generate the ECC when reading, the ECC engine does it + * for us as part of decoding/correction. + */ + if (nand->reading) + return 0; + + params.size = nand->chip.ecc.size; + params.bytes = nand->chip.ecc.bytes; + params.strength = nand->chip.ecc.strength; + + return ingenic_ecc_calculate(nfc->ecc, ¶ms, dat, ecc_code); +} + +static int ingenic_nand_ecc_correct(struct nand_chip *chip, u8 *dat, + u8 *read_ecc, u8 *calc_ecc) +{ + struct ingenic_nand *nand = to_ingenic_nand(nand_to_mtd(chip)); + struct ingenic_nfc *nfc = to_ingenic_nfc(nand->chip.controller); + struct ingenic_ecc_params params; + + params.size = nand->chip.ecc.size; + params.bytes = nand->chip.ecc.bytes; + params.strength = nand->chip.ecc.strength; + + return ingenic_ecc_correct(nfc->ecc, ¶ms, dat, read_ecc); +} + +static int ingenic_nand_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct ingenic_nfc *nfc = to_ingenic_nfc(chip->controller); + int eccbytes; + + if (chip->ecc.strength == 4) { + /* JZ4740 uses 9 bytes of ECC to correct maximum 4 errors */ + chip->ecc.bytes = 9; + } else { + chip->ecc.bytes = fls((1 + 8) * chip->ecc.size) * + (chip->ecc.strength / 8); + } + + switch (chip->ecc.engine_type) { + case NAND_ECC_ENGINE_TYPE_ON_HOST: + if (!nfc->ecc) { + dev_err(nfc->dev, "HW ECC selected, but ECC controller not found\n"); + return -ENODEV; + } + + chip->ecc.hwctl = ingenic_nand_ecc_hwctl; + chip->ecc.calculate = ingenic_nand_ecc_calculate; + chip->ecc.correct = ingenic_nand_ecc_correct; + fallthrough; + case NAND_ECC_ENGINE_TYPE_SOFT: + dev_info(nfc->dev, "using %s (strength %d, size %d, bytes %d)\n", + (nfc->ecc) ? "hardware ECC" : "software ECC", + chip->ecc.strength, chip->ecc.size, chip->ecc.bytes); + break; + case NAND_ECC_ENGINE_TYPE_NONE: + dev_info(nfc->dev, "not using ECC\n"); + break; + default: + dev_err(nfc->dev, "ECC mode %d not supported\n", + chip->ecc.engine_type); + return -EINVAL; + } + + /* The NAND core will generate the ECC layout for SW ECC */ + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return 0; + + /* Generate ECC layout. ECC codes are right aligned in the OOB area. */ + eccbytes = mtd->writesize / chip->ecc.size * chip->ecc.bytes; + + if (eccbytes > mtd->oobsize - 2) { + dev_err(nfc->dev, + "invalid ECC config: required %d ECC bytes, but only %d are available", + eccbytes, mtd->oobsize - 2); + return -EINVAL; + } + + /* + * The generic layout for BBT markers will most likely overlap with our + * ECC bytes in the OOB, so move the BBT markers outside the OOB area. + */ + if (chip->bbt_options & NAND_BBT_USE_FLASH) + chip->bbt_options |= NAND_BBT_NO_OOB; + + if (nfc->soc_info->oob_first) + chip->ecc.read_page = nand_read_page_hwecc_oob_first; + + /* For legacy reasons we use a different layout on the qi,lb60 board. */ + if (of_machine_is_compatible("qi,lb60")) + mtd_set_ooblayout(mtd, &qi_lb60_ooblayout_ops); + else if (nfc->soc_info->oob_layout) + mtd_set_ooblayout(mtd, nfc->soc_info->oob_layout); + else + mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout()); + + return 0; +} + +static int ingenic_nand_exec_instr(struct nand_chip *chip, + struct ingenic_nand_cs *cs, + const struct nand_op_instr *instr) +{ + struct ingenic_nand *nand = to_ingenic_nand(nand_to_mtd(chip)); + struct ingenic_nfc *nfc = to_ingenic_nfc(chip->controller); + unsigned int i; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + writeb(instr->ctx.cmd.opcode, + cs->base + nfc->soc_info->cmd_offset); + return 0; + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) + writeb(instr->ctx.addr.addrs[i], + cs->base + nfc->soc_info->addr_offset); + return 0; + case NAND_OP_DATA_IN_INSTR: + if (instr->ctx.data.force_8bit || + !(chip->options & NAND_BUSWIDTH_16)) + ioread8_rep(cs->base + nfc->soc_info->data_offset, + instr->ctx.data.buf.in, + instr->ctx.data.len); + else + ioread16_rep(cs->base + nfc->soc_info->data_offset, + instr->ctx.data.buf.in, + instr->ctx.data.len); + return 0; + case NAND_OP_DATA_OUT_INSTR: + if (instr->ctx.data.force_8bit || + !(chip->options & NAND_BUSWIDTH_16)) + iowrite8_rep(cs->base + nfc->soc_info->data_offset, + instr->ctx.data.buf.out, + instr->ctx.data.len); + else + iowrite16_rep(cs->base + nfc->soc_info->data_offset, + instr->ctx.data.buf.out, + instr->ctx.data.len); + return 0; + case NAND_OP_WAITRDY_INSTR: + if (!nand->busy_gpio) + return nand_soft_waitrdy(chip, + instr->ctx.waitrdy.timeout_ms); + + return nand_gpio_waitrdy(chip, nand->busy_gpio, + instr->ctx.waitrdy.timeout_ms); + default: + break; + } + + return -EINVAL; +} + +static int ingenic_nand_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + struct ingenic_nand *nand = to_ingenic_nand(nand_to_mtd(chip)); + struct ingenic_nfc *nfc = to_ingenic_nfc(nand->chip.controller); + struct ingenic_nand_cs *cs; + unsigned int i; + int ret = 0; + + if (check_only) + return 0; + + cs = &nfc->cs[op->cs]; + jz4780_nemc_assert(nfc->dev, cs->bank, true); + for (i = 0; i < op->ninstrs; i++) { + ret = ingenic_nand_exec_instr(chip, cs, &op->instrs[i]); + if (ret) + break; + + if (op->instrs[i].delay_ns) + ndelay(op->instrs[i].delay_ns); + } + jz4780_nemc_assert(nfc->dev, cs->bank, false); + + return ret; +} + +static const struct nand_controller_ops ingenic_nand_controller_ops = { + .attach_chip = ingenic_nand_attach_chip, + .exec_op = ingenic_nand_exec_op, +}; + +static int ingenic_nand_init_chip(struct platform_device *pdev, + struct ingenic_nfc *nfc, + struct device_node *np, + unsigned int chipnr) +{ + struct device *dev = &pdev->dev; + struct ingenic_nand *nand; + struct ingenic_nand_cs *cs; + struct nand_chip *chip; + struct mtd_info *mtd; + const __be32 *reg; + int ret = 0; + + cs = &nfc->cs[chipnr]; + + reg = of_get_property(np, "reg", NULL); + if (!reg) + return -EINVAL; + + cs->bank = be32_to_cpu(*reg); + + jz4780_nemc_set_type(nfc->dev, cs->bank, JZ4780_NEMC_BANK_NAND); + + cs->base = devm_platform_ioremap_resource(pdev, chipnr); + if (IS_ERR(cs->base)) + return PTR_ERR(cs->base); + + nand = devm_kzalloc(dev, sizeof(*nand), GFP_KERNEL); + if (!nand) + return -ENOMEM; + + nand->busy_gpio = devm_gpiod_get_optional(dev, "rb", GPIOD_IN); + + if (IS_ERR(nand->busy_gpio)) { + ret = PTR_ERR(nand->busy_gpio); + dev_err(dev, "failed to request busy GPIO: %d\n", ret); + return ret; + } + + nand->wp_gpio = devm_gpiod_get_optional(dev, "wp", GPIOD_OUT_LOW); + + if (IS_ERR(nand->wp_gpio)) { + ret = PTR_ERR(nand->wp_gpio); + dev_err(dev, "failed to request WP GPIO: %d\n", ret); + return ret; + } + + chip = &nand->chip; + mtd = nand_to_mtd(chip); + mtd->name = devm_kasprintf(dev, GFP_KERNEL, "%s.%d", dev_name(dev), + cs->bank); + if (!mtd->name) + return -ENOMEM; + mtd->dev.parent = dev; + + chip->options = NAND_NO_SUBPAGE_WRITE; + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + chip->controller = &nfc->controller; + nand_set_flash_node(chip, np); + + chip->controller->ops = &ingenic_nand_controller_ops; + ret = nand_scan(chip, 1); + if (ret) + return ret; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + nand_cleanup(chip); + return ret; + } + + list_add_tail(&nand->chip_list, &nfc->chips); + + return 0; +} + +static void ingenic_nand_cleanup_chips(struct ingenic_nfc *nfc) +{ + struct ingenic_nand *ingenic_chip; + struct nand_chip *chip; + int ret; + + while (!list_empty(&nfc->chips)) { + ingenic_chip = list_first_entry(&nfc->chips, + struct ingenic_nand, chip_list); + chip = &ingenic_chip->chip; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + list_del(&ingenic_chip->chip_list); + } +} + +static int ingenic_nand_init_chips(struct ingenic_nfc *nfc, + struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int i = 0; + int ret; + int num_chips = of_get_child_count(dev->of_node); + + if (num_chips > nfc->num_banks) { + dev_err(dev, "found %d chips but only %d banks\n", + num_chips, nfc->num_banks); + return -EINVAL; + } + + 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); + return ret; + } + + i++; + } + + return 0; +} + +static int ingenic_nand_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + unsigned int num_banks; + struct ingenic_nfc *nfc; + int ret; + + num_banks = jz4780_nemc_num_banks(dev); + if (num_banks == 0) { + dev_err(dev, "no banks found\n"); + return -ENODEV; + } + + nfc = devm_kzalloc(dev, struct_size(nfc, cs, num_banks), GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->soc_info = device_get_match_data(dev); + if (!nfc->soc_info) + return -EINVAL; + + /* + * Check for ECC HW before we call nand_scan_ident, to prevent us from + * having to call it again if the ECC driver returns -EPROBE_DEFER. + */ + nfc->ecc = of_ingenic_ecc_get(dev->of_node); + if (IS_ERR(nfc->ecc)) + return PTR_ERR(nfc->ecc); + + nfc->dev = dev; + nfc->num_banks = num_banks; + + nand_controller_init(&nfc->controller); + INIT_LIST_HEAD(&nfc->chips); + + ret = ingenic_nand_init_chips(nfc, pdev); + if (ret) { + if (nfc->ecc) + ingenic_ecc_release(nfc->ecc); + return ret; + } + + platform_set_drvdata(pdev, nfc); + return 0; +} + +static void ingenic_nand_remove(struct platform_device *pdev) +{ + struct ingenic_nfc *nfc = platform_get_drvdata(pdev); + + if (nfc->ecc) + ingenic_ecc_release(nfc->ecc); + + ingenic_nand_cleanup_chips(nfc); +} + +static const struct jz_soc_info jz4740_soc_info = { + .data_offset = 0x00000000, + .cmd_offset = 0x00008000, + .addr_offset = 0x00010000, + .oob_first = true, +}; + +static const struct jz_soc_info jz4725b_soc_info = { + .data_offset = 0x00000000, + .cmd_offset = 0x00008000, + .addr_offset = 0x00010000, + .oob_layout = &jz4725b_ooblayout_ops, +}; + +static const struct jz_soc_info jz4780_soc_info = { + .data_offset = 0x00000000, + .cmd_offset = 0x00400000, + .addr_offset = 0x00800000, +}; + +static const struct of_device_id ingenic_nand_dt_match[] = { + { .compatible = "ingenic,jz4740-nand", .data = &jz4740_soc_info }, + { .compatible = "ingenic,jz4725b-nand", .data = &jz4725b_soc_info }, + { .compatible = "ingenic,jz4780-nand", .data = &jz4780_soc_info }, + {}, +}; +MODULE_DEVICE_TABLE(of, ingenic_nand_dt_match); + +static struct platform_driver ingenic_nand_driver = { + .probe = ingenic_nand_probe, + .remove = ingenic_nand_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = ingenic_nand_dt_match, + }, +}; +module_platform_driver(ingenic_nand_driver); + +MODULE_AUTHOR("Alex Smith "); +MODULE_AUTHOR("Harvey Hunt "); +MODULE_DESCRIPTION("Ingenic JZ47xx NAND driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/raw/ingenic/jz4725b_bch.c b/drivers/mtd/nand/raw/ingenic/jz4725b_bch.c new file mode 100644 index 000000000..2d0e0a219 --- /dev/null +++ b/drivers/mtd/nand/raw/ingenic/jz4725b_bch.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * JZ4725B BCH controller driver + * + * Copyright (C) 2019 Paul Cercueil + * + * Based on jz4780_bch.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ingenic_ecc.h" + +#define BCH_BHCR 0x0 +#define BCH_BHCSR 0x4 +#define BCH_BHCCR 0x8 +#define BCH_BHCNT 0xc +#define BCH_BHDR 0x10 +#define BCH_BHPAR0 0x14 +#define BCH_BHERR0 0x28 +#define BCH_BHINT 0x24 +#define BCH_BHINTES 0x3c +#define BCH_BHINTEC 0x40 +#define BCH_BHINTE 0x38 + +#define BCH_BHCR_ENCE BIT(3) +#define BCH_BHCR_BSEL BIT(2) +#define BCH_BHCR_INIT BIT(1) +#define BCH_BHCR_BCHE BIT(0) + +#define BCH_BHCNT_DEC_COUNT_SHIFT 16 +#define BCH_BHCNT_DEC_COUNT_MASK (0x3ff << BCH_BHCNT_DEC_COUNT_SHIFT) +#define BCH_BHCNT_ENC_COUNT_SHIFT 0 +#define BCH_BHCNT_ENC_COUNT_MASK (0x3ff << BCH_BHCNT_ENC_COUNT_SHIFT) + +#define BCH_BHERR_INDEX0_SHIFT 0 +#define BCH_BHERR_INDEX0_MASK (0x1fff << BCH_BHERR_INDEX0_SHIFT) +#define BCH_BHERR_INDEX1_SHIFT 16 +#define BCH_BHERR_INDEX1_MASK (0x1fff << BCH_BHERR_INDEX1_SHIFT) + +#define BCH_BHINT_ERRC_SHIFT 28 +#define BCH_BHINT_ERRC_MASK (0xf << BCH_BHINT_ERRC_SHIFT) +#define BCH_BHINT_TERRC_SHIFT 16 +#define BCH_BHINT_TERRC_MASK (0x7f << BCH_BHINT_TERRC_SHIFT) +#define BCH_BHINT_ALL_0 BIT(5) +#define BCH_BHINT_ALL_F BIT(4) +#define BCH_BHINT_DECF BIT(3) +#define BCH_BHINT_ENCF BIT(2) +#define BCH_BHINT_UNCOR BIT(1) +#define BCH_BHINT_ERR BIT(0) + +/* Timeout for BCH calculation/correction. */ +#define BCH_TIMEOUT_US 100000 + +static inline void jz4725b_bch_config_set(struct ingenic_ecc *bch, u32 cfg) +{ + writel(cfg, bch->base + BCH_BHCSR); +} + +static inline void jz4725b_bch_config_clear(struct ingenic_ecc *bch, u32 cfg) +{ + writel(cfg, bch->base + BCH_BHCCR); +} + +static int jz4725b_bch_reset(struct ingenic_ecc *bch, + struct ingenic_ecc_params *params, bool calc_ecc) +{ + u32 reg, max_value; + + /* Clear interrupt status. */ + writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT); + + /* Initialise and enable BCH. */ + jz4725b_bch_config_clear(bch, 0x1f); + jz4725b_bch_config_set(bch, BCH_BHCR_BCHE); + + if (params->strength == 8) + jz4725b_bch_config_set(bch, BCH_BHCR_BSEL); + else + jz4725b_bch_config_clear(bch, BCH_BHCR_BSEL); + + if (calc_ecc) /* calculate ECC from data */ + jz4725b_bch_config_set(bch, BCH_BHCR_ENCE); + else /* correct data from ECC */ + jz4725b_bch_config_clear(bch, BCH_BHCR_ENCE); + + jz4725b_bch_config_set(bch, BCH_BHCR_INIT); + + max_value = BCH_BHCNT_ENC_COUNT_MASK >> BCH_BHCNT_ENC_COUNT_SHIFT; + if (params->size > max_value) + return -EINVAL; + + max_value = BCH_BHCNT_DEC_COUNT_MASK >> BCH_BHCNT_DEC_COUNT_SHIFT; + if (params->size + params->bytes > max_value) + return -EINVAL; + + /* Set up BCH count register. */ + reg = params->size << BCH_BHCNT_ENC_COUNT_SHIFT; + reg |= (params->size + params->bytes) << BCH_BHCNT_DEC_COUNT_SHIFT; + writel(reg, bch->base + BCH_BHCNT); + + return 0; +} + +static void jz4725b_bch_disable(struct ingenic_ecc *bch) +{ + /* Clear interrupts */ + writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT); + + /* Disable the hardware */ + jz4725b_bch_config_clear(bch, BCH_BHCR_BCHE); +} + +static void jz4725b_bch_write_data(struct ingenic_ecc *bch, const u8 *buf, + size_t size) +{ + while (size--) + writeb(*buf++, bch->base + BCH_BHDR); +} + +static void jz4725b_bch_read_parity(struct ingenic_ecc *bch, u8 *buf, + size_t size) +{ + size_t size32 = size / sizeof(u32); + size_t size8 = size % sizeof(u32); + u32 *dest32; + u8 *dest8; + u32 val, offset = 0; + + dest32 = (u32 *)buf; + while (size32--) { + *dest32++ = readl_relaxed(bch->base + BCH_BHPAR0 + offset); + offset += sizeof(u32); + } + + dest8 = (u8 *)dest32; + val = readl_relaxed(bch->base + BCH_BHPAR0 + offset); + switch (size8) { + case 3: + dest8[2] = (val >> 16) & 0xff; + fallthrough; + case 2: + dest8[1] = (val >> 8) & 0xff; + fallthrough; + case 1: + dest8[0] = val & 0xff; + break; + } +} + +static int jz4725b_bch_wait_complete(struct ingenic_ecc *bch, unsigned int irq, + u32 *status) +{ + u32 reg; + int ret; + + /* + * While we could use interrupts here and sleep until the operation + * completes, the controller works fairly quickly (usually a few + * microseconds) and so the overhead of sleeping until we get an + * interrupt quite noticeably decreases performance. + */ + ret = readl_relaxed_poll_timeout(bch->base + BCH_BHINT, reg, + reg & irq, 0, BCH_TIMEOUT_US); + if (ret) + return ret; + + if (status) + *status = reg; + + writel(reg, bch->base + BCH_BHINT); + + return 0; +} + +static int jz4725b_calculate(struct ingenic_ecc *bch, + struct ingenic_ecc_params *params, + const u8 *buf, u8 *ecc_code) +{ + int ret; + + mutex_lock(&bch->lock); + + ret = jz4725b_bch_reset(bch, params, true); + if (ret) { + dev_err(bch->dev, "Unable to init BCH with given parameters\n"); + goto out_disable; + } + + jz4725b_bch_write_data(bch, buf, params->size); + + ret = jz4725b_bch_wait_complete(bch, BCH_BHINT_ENCF, NULL); + if (ret) { + dev_err(bch->dev, "timed out while calculating ECC\n"); + goto out_disable; + } + + jz4725b_bch_read_parity(bch, ecc_code, params->bytes); + +out_disable: + jz4725b_bch_disable(bch); + mutex_unlock(&bch->lock); + + return ret; +} + +static int jz4725b_correct(struct ingenic_ecc *bch, + struct ingenic_ecc_params *params, + u8 *buf, u8 *ecc_code) +{ + u32 reg, errors, bit; + unsigned int i; + int ret; + + mutex_lock(&bch->lock); + + ret = jz4725b_bch_reset(bch, params, false); + if (ret) { + dev_err(bch->dev, "Unable to init BCH with given parameters\n"); + goto out; + } + + jz4725b_bch_write_data(bch, buf, params->size); + jz4725b_bch_write_data(bch, ecc_code, params->bytes); + + ret = jz4725b_bch_wait_complete(bch, BCH_BHINT_DECF, ®); + if (ret) { + dev_err(bch->dev, "timed out while correcting data\n"); + goto out; + } + + if (reg & (BCH_BHINT_ALL_F | BCH_BHINT_ALL_0)) { + /* Data and ECC is all 0xff or 0x00 - nothing to correct */ + ret = 0; + goto out; + } + + if (reg & BCH_BHINT_UNCOR) { + /* Uncorrectable ECC error */ + ret = -EBADMSG; + goto out; + } + + errors = (reg & BCH_BHINT_ERRC_MASK) >> BCH_BHINT_ERRC_SHIFT; + + /* Correct any detected errors. */ + for (i = 0; i < errors; i++) { + if (i & 1) { + bit = (reg & BCH_BHERR_INDEX1_MASK) >> BCH_BHERR_INDEX1_SHIFT; + } else { + reg = readl(bch->base + BCH_BHERR0 + (i * 4)); + bit = (reg & BCH_BHERR_INDEX0_MASK) >> BCH_BHERR_INDEX0_SHIFT; + } + + buf[(bit >> 3)] ^= BIT(bit & 0x7); + } + +out: + jz4725b_bch_disable(bch); + mutex_unlock(&bch->lock); + + return ret; +} + +static const struct ingenic_ecc_ops jz4725b_bch_ops = { + .disable = jz4725b_bch_disable, + .calculate = jz4725b_calculate, + .correct = jz4725b_correct, +}; + +static const struct of_device_id jz4725b_bch_dt_match[] = { + { .compatible = "ingenic,jz4725b-bch", .data = &jz4725b_bch_ops }, + {}, +}; +MODULE_DEVICE_TABLE(of, jz4725b_bch_dt_match); + +static struct platform_driver jz4725b_bch_driver = { + .probe = ingenic_ecc_probe, + .driver = { + .name = "jz4725b-bch", + .of_match_table = jz4725b_bch_dt_match, + }, +}; +module_platform_driver(jz4725b_bch_driver); + +MODULE_AUTHOR("Paul Cercueil "); +MODULE_DESCRIPTION("Ingenic JZ4725B BCH controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/raw/ingenic/jz4740_ecc.c b/drivers/mtd/nand/raw/ingenic/jz4740_ecc.c new file mode 100644 index 000000000..54e377754 --- /dev/null +++ b/drivers/mtd/nand/raw/ingenic/jz4740_ecc.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * JZ4740 ECC controller driver + * + * Copyright (c) 2019 Paul Cercueil + * + * based on jz4740-nand.c + */ + +#include +#include +#include +#include +#include +#include + +#include "ingenic_ecc.h" + +#define JZ_REG_NAND_ECC_CTRL 0x00 +#define JZ_REG_NAND_DATA 0x04 +#define JZ_REG_NAND_PAR0 0x08 +#define JZ_REG_NAND_PAR1 0x0C +#define JZ_REG_NAND_PAR2 0x10 +#define JZ_REG_NAND_IRQ_STAT 0x14 +#define JZ_REG_NAND_IRQ_CTRL 0x18 +#define JZ_REG_NAND_ERR(x) (0x1C + ((x) << 2)) + +#define JZ_NAND_ECC_CTRL_PAR_READY BIT(4) +#define JZ_NAND_ECC_CTRL_ENCODING BIT(3) +#define JZ_NAND_ECC_CTRL_RS BIT(2) +#define JZ_NAND_ECC_CTRL_RESET BIT(1) +#define JZ_NAND_ECC_CTRL_ENABLE BIT(0) + +#define JZ_NAND_STATUS_ERR_COUNT (BIT(31) | BIT(30) | BIT(29)) +#define JZ_NAND_STATUS_PAD_FINISH BIT(4) +#define JZ_NAND_STATUS_DEC_FINISH BIT(3) +#define JZ_NAND_STATUS_ENC_FINISH BIT(2) +#define JZ_NAND_STATUS_UNCOR_ERROR BIT(1) +#define JZ_NAND_STATUS_ERROR BIT(0) + +static const uint8_t empty_block_ecc[] = { + 0xcd, 0x9d, 0x90, 0x58, 0xf4, 0x8b, 0xff, 0xb7, 0x6f +}; + +static void jz4740_ecc_reset(struct ingenic_ecc *ecc, bool calc_ecc) +{ + uint32_t reg; + + /* Clear interrupt status */ + writel(0, ecc->base + JZ_REG_NAND_IRQ_STAT); + + /* Initialize and enable ECC hardware */ + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg |= JZ_NAND_ECC_CTRL_RESET; + reg |= JZ_NAND_ECC_CTRL_ENABLE; + reg |= JZ_NAND_ECC_CTRL_RS; + if (calc_ecc) /* calculate ECC from data */ + reg |= JZ_NAND_ECC_CTRL_ENCODING; + else /* correct data from ECC */ + reg &= ~JZ_NAND_ECC_CTRL_ENCODING; + + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); +} + +static int jz4740_ecc_calculate(struct ingenic_ecc *ecc, + struct ingenic_ecc_params *params, + const u8 *buf, u8 *ecc_code) +{ + uint32_t reg, status; + unsigned int timeout = 1000; + int i; + + jz4740_ecc_reset(ecc, true); + + do { + status = readl(ecc->base + JZ_REG_NAND_IRQ_STAT); + } while (!(status & JZ_NAND_STATUS_ENC_FINISH) && --timeout); + + if (timeout == 0) + return -ETIMEDOUT; + + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg &= ~JZ_NAND_ECC_CTRL_ENABLE; + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); + + for (i = 0; i < params->bytes; ++i) + ecc_code[i] = readb(ecc->base + JZ_REG_NAND_PAR0 + i); + + /* + * If the written data is completely 0xff, we also want to write 0xff as + * ECC, otherwise we will get in trouble when doing subpage writes. + */ + if (memcmp(ecc_code, empty_block_ecc, sizeof(empty_block_ecc)) == 0) + memset(ecc_code, 0xff, sizeof(empty_block_ecc)); + + return 0; +} + +static void jz_nand_correct_data(uint8_t *buf, int index, int mask) +{ + int offset = index & 0x7; + uint16_t data; + + index += (index >> 3); + + data = buf[index]; + data |= buf[index + 1] << 8; + + mask ^= (data >> offset) & 0x1ff; + data &= ~(0x1ff << offset); + data |= (mask << offset); + + buf[index] = data & 0xff; + buf[index + 1] = (data >> 8) & 0xff; +} + +static int jz4740_ecc_correct(struct ingenic_ecc *ecc, + struct ingenic_ecc_params *params, + u8 *buf, u8 *ecc_code) +{ + int i, error_count, index; + uint32_t reg, status, error; + unsigned int timeout = 1000; + + jz4740_ecc_reset(ecc, false); + + for (i = 0; i < params->bytes; ++i) + writeb(ecc_code[i], ecc->base + JZ_REG_NAND_PAR0 + i); + + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg |= JZ_NAND_ECC_CTRL_PAR_READY; + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); + + do { + status = readl(ecc->base + JZ_REG_NAND_IRQ_STAT); + } while (!(status & JZ_NAND_STATUS_DEC_FINISH) && --timeout); + + if (timeout == 0) + return -ETIMEDOUT; + + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg &= ~JZ_NAND_ECC_CTRL_ENABLE; + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); + + if (status & JZ_NAND_STATUS_ERROR) { + if (status & JZ_NAND_STATUS_UNCOR_ERROR) + return -EBADMSG; + + error_count = (status & JZ_NAND_STATUS_ERR_COUNT) >> 29; + + for (i = 0; i < error_count; ++i) { + error = readl(ecc->base + JZ_REG_NAND_ERR(i)); + index = ((error >> 16) & 0x1ff) - 1; + if (index >= 0 && index < params->size) + jz_nand_correct_data(buf, index, error & 0x1ff); + } + + return error_count; + } + + return 0; +} + +static void jz4740_ecc_disable(struct ingenic_ecc *ecc) +{ + u32 reg; + + writel(0, ecc->base + JZ_REG_NAND_IRQ_STAT); + reg = readl(ecc->base + JZ_REG_NAND_ECC_CTRL); + reg &= ~JZ_NAND_ECC_CTRL_ENABLE; + writel(reg, ecc->base + JZ_REG_NAND_ECC_CTRL); +} + +static const struct ingenic_ecc_ops jz4740_ecc_ops = { + .disable = jz4740_ecc_disable, + .calculate = jz4740_ecc_calculate, + .correct = jz4740_ecc_correct, +}; + +static const struct of_device_id jz4740_ecc_dt_match[] = { + { .compatible = "ingenic,jz4740-ecc", .data = &jz4740_ecc_ops }, + {}, +}; +MODULE_DEVICE_TABLE(of, jz4740_ecc_dt_match); + +static struct platform_driver jz4740_ecc_driver = { + .probe = ingenic_ecc_probe, + .driver = { + .name = "jz4740-ecc", + .of_match_table = jz4740_ecc_dt_match, + }, +}; +module_platform_driver(jz4740_ecc_driver); + +MODULE_AUTHOR("Paul Cercueil "); +MODULE_DESCRIPTION("Ingenic JZ4740 ECC controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/raw/ingenic/jz4780_bch.c b/drivers/mtd/nand/raw/ingenic/jz4780_bch.c new file mode 100644 index 000000000..12b5b0484 --- /dev/null +++ b/drivers/mtd/nand/raw/ingenic/jz4780_bch.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * JZ4780 BCH controller driver + * + * Copyright (c) 2015 Imagination Technologies + * Author: Alex Smith + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ingenic_ecc.h" + +#define BCH_BHCR 0x0 +#define BCH_BHCCR 0x8 +#define BCH_BHCNT 0xc +#define BCH_BHDR 0x10 +#define BCH_BHPAR0 0x14 +#define BCH_BHERR0 0x84 +#define BCH_BHINT 0x184 +#define BCH_BHINTES 0x188 +#define BCH_BHINTEC 0x18c +#define BCH_BHINTE 0x190 + +#define BCH_BHCR_BSEL_SHIFT 4 +#define BCH_BHCR_BSEL_MASK (0x7f << BCH_BHCR_BSEL_SHIFT) +#define BCH_BHCR_ENCE BIT(2) +#define BCH_BHCR_INIT BIT(1) +#define BCH_BHCR_BCHE BIT(0) + +#define BCH_BHCNT_PARITYSIZE_SHIFT 16 +#define BCH_BHCNT_PARITYSIZE_MASK (0x7f << BCH_BHCNT_PARITYSIZE_SHIFT) +#define BCH_BHCNT_BLOCKSIZE_SHIFT 0 +#define BCH_BHCNT_BLOCKSIZE_MASK (0x7ff << BCH_BHCNT_BLOCKSIZE_SHIFT) + +#define BCH_BHERR_MASK_SHIFT 16 +#define BCH_BHERR_MASK_MASK (0xffff << BCH_BHERR_MASK_SHIFT) +#define BCH_BHERR_INDEX_SHIFT 0 +#define BCH_BHERR_INDEX_MASK (0x7ff << BCH_BHERR_INDEX_SHIFT) + +#define BCH_BHINT_ERRC_SHIFT 24 +#define BCH_BHINT_ERRC_MASK (0x7f << BCH_BHINT_ERRC_SHIFT) +#define BCH_BHINT_TERRC_SHIFT 16 +#define BCH_BHINT_TERRC_MASK (0x7f << BCH_BHINT_TERRC_SHIFT) +#define BCH_BHINT_DECF BIT(3) +#define BCH_BHINT_ENCF BIT(2) +#define BCH_BHINT_UNCOR BIT(1) +#define BCH_BHINT_ERR BIT(0) + +#define BCH_CLK_RATE (200 * 1000 * 1000) + +/* Timeout for BCH calculation/correction. */ +#define BCH_TIMEOUT_US 100000 + +static void jz4780_bch_reset(struct ingenic_ecc *bch, + struct ingenic_ecc_params *params, bool encode) +{ + u32 reg; + + /* Clear interrupt status. */ + writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT); + + /* Set up BCH count register. */ + reg = params->size << BCH_BHCNT_BLOCKSIZE_SHIFT; + reg |= params->bytes << BCH_BHCNT_PARITYSIZE_SHIFT; + writel(reg, bch->base + BCH_BHCNT); + + /* Initialise and enable BCH. */ + reg = BCH_BHCR_BCHE | BCH_BHCR_INIT; + reg |= params->strength << BCH_BHCR_BSEL_SHIFT; + if (encode) + reg |= BCH_BHCR_ENCE; + writel(reg, bch->base + BCH_BHCR); +} + +static void jz4780_bch_disable(struct ingenic_ecc *bch) +{ + writel(readl(bch->base + BCH_BHINT), bch->base + BCH_BHINT); + writel(BCH_BHCR_BCHE, bch->base + BCH_BHCCR); +} + +static void jz4780_bch_write_data(struct ingenic_ecc *bch, const void *buf, + size_t size) +{ + size_t size32 = size / sizeof(u32); + size_t size8 = size % sizeof(u32); + const u32 *src32; + const u8 *src8; + + src32 = (const u32 *)buf; + while (size32--) + writel(*src32++, bch->base + BCH_BHDR); + + src8 = (const u8 *)src32; + while (size8--) + writeb(*src8++, bch->base + BCH_BHDR); +} + +static void jz4780_bch_read_parity(struct ingenic_ecc *bch, void *buf, + size_t size) +{ + size_t size32 = size / sizeof(u32); + size_t size8 = size % sizeof(u32); + u32 *dest32; + u8 *dest8; + u32 val, offset = 0; + + dest32 = (u32 *)buf; + while (size32--) { + *dest32++ = readl(bch->base + BCH_BHPAR0 + offset); + offset += sizeof(u32); + } + + dest8 = (u8 *)dest32; + val = readl(bch->base + BCH_BHPAR0 + offset); + switch (size8) { + case 3: + dest8[2] = (val >> 16) & 0xff; + fallthrough; + case 2: + dest8[1] = (val >> 8) & 0xff; + fallthrough; + case 1: + dest8[0] = val & 0xff; + break; + } +} + +static bool jz4780_bch_wait_complete(struct ingenic_ecc *bch, unsigned int irq, + u32 *status) +{ + u32 reg; + int ret; + + /* + * While we could use interrupts here and sleep until the operation + * completes, the controller works fairly quickly (usually a few + * microseconds) and so the overhead of sleeping until we get an + * interrupt quite noticeably decreases performance. + */ + ret = readl_poll_timeout(bch->base + BCH_BHINT, reg, + (reg & irq) == irq, 0, BCH_TIMEOUT_US); + if (ret) + return false; + + if (status) + *status = reg; + + writel(reg, bch->base + BCH_BHINT); + return true; +} + +static int jz4780_calculate(struct ingenic_ecc *bch, + struct ingenic_ecc_params *params, + const u8 *buf, u8 *ecc_code) +{ + int ret = 0; + + mutex_lock(&bch->lock); + + jz4780_bch_reset(bch, params, true); + jz4780_bch_write_data(bch, buf, params->size); + + if (jz4780_bch_wait_complete(bch, BCH_BHINT_ENCF, NULL)) { + jz4780_bch_read_parity(bch, ecc_code, params->bytes); + } else { + dev_err(bch->dev, "timed out while calculating ECC\n"); + ret = -ETIMEDOUT; + } + + jz4780_bch_disable(bch); + mutex_unlock(&bch->lock); + return ret; +} + +static int jz4780_correct(struct ingenic_ecc *bch, + struct ingenic_ecc_params *params, + u8 *buf, u8 *ecc_code) +{ + u32 reg, mask, index; + int i, ret, count; + + mutex_lock(&bch->lock); + + jz4780_bch_reset(bch, params, false); + jz4780_bch_write_data(bch, buf, params->size); + jz4780_bch_write_data(bch, ecc_code, params->bytes); + + if (!jz4780_bch_wait_complete(bch, BCH_BHINT_DECF, ®)) { + dev_err(bch->dev, "timed out while correcting data\n"); + ret = -ETIMEDOUT; + goto out; + } + + if (reg & BCH_BHINT_UNCOR) { + dev_warn(bch->dev, "uncorrectable ECC error\n"); + ret = -EBADMSG; + goto out; + } + + /* Correct any detected errors. */ + if (reg & BCH_BHINT_ERR) { + count = (reg & BCH_BHINT_ERRC_MASK) >> BCH_BHINT_ERRC_SHIFT; + ret = (reg & BCH_BHINT_TERRC_MASK) >> BCH_BHINT_TERRC_SHIFT; + + for (i = 0; i < count; i++) { + reg = readl(bch->base + BCH_BHERR0 + (i * 4)); + mask = (reg & BCH_BHERR_MASK_MASK) >> + BCH_BHERR_MASK_SHIFT; + index = (reg & BCH_BHERR_INDEX_MASK) >> + BCH_BHERR_INDEX_SHIFT; + buf[(index * 2) + 0] ^= mask; + buf[(index * 2) + 1] ^= mask >> 8; + } + } else { + ret = 0; + } + +out: + jz4780_bch_disable(bch); + mutex_unlock(&bch->lock); + return ret; +} + +static int jz4780_bch_probe(struct platform_device *pdev) +{ + struct ingenic_ecc *bch; + int ret; + + ret = ingenic_ecc_probe(pdev); + if (ret) + return ret; + + bch = platform_get_drvdata(pdev); + clk_set_rate(bch->clk, BCH_CLK_RATE); + + return 0; +} + +static const struct ingenic_ecc_ops jz4780_bch_ops = { + .disable = jz4780_bch_disable, + .calculate = jz4780_calculate, + .correct = jz4780_correct, +}; + +static const struct of_device_id jz4780_bch_dt_match[] = { + { .compatible = "ingenic,jz4780-bch", .data = &jz4780_bch_ops }, + {}, +}; +MODULE_DEVICE_TABLE(of, jz4780_bch_dt_match); + +static struct platform_driver jz4780_bch_driver = { + .probe = jz4780_bch_probe, + .driver = { + .name = "jz4780-bch", + .of_match_table = jz4780_bch_dt_match, + }, +}; +module_platform_driver(jz4780_bch_driver); + +MODULE_AUTHOR("Alex Smith "); +MODULE_AUTHOR("Harvey Hunt "); +MODULE_DESCRIPTION("Ingenic JZ4780 BCH error correction driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/raw/intel-nand-controller.c b/drivers/mtd/nand/raw/intel-nand-controller.c new file mode 100644 index 000000000..01cefdaf1 --- /dev/null +++ b/drivers/mtd/nand/raw/intel-nand-controller.c @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Copyright (c) 2020 Intel Corporation. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define EBU_CLC 0x000 +#define EBU_CLC_RST 0x00000000u + +#define EBU_ADDR_SEL(n) (0x020 + (n) * 4) +/* 5 bits 26:22 included for comparison in the ADDR_SELx */ +#define EBU_ADDR_MASK(x) ((x) << 4) +#define EBU_ADDR_SEL_REGEN 0x1 + +#define EBU_BUSCON(n) (0x060 + (n) * 4) +#define EBU_BUSCON_CMULT_V4 0x1 +#define EBU_BUSCON_RECOVC(n) ((n) << 2) +#define EBU_BUSCON_HOLDC(n) ((n) << 4) +#define EBU_BUSCON_WAITRDC(n) ((n) << 6) +#define EBU_BUSCON_WAITWRC(n) ((n) << 8) +#define EBU_BUSCON_BCGEN_CS 0x0 +#define EBU_BUSCON_SETUP_EN BIT(22) +#define EBU_BUSCON_ALEC 0xC000 + +#define EBU_CON 0x0B0 +#define EBU_CON_NANDM_EN BIT(0) +#define EBU_CON_NANDM_DIS 0x0 +#define EBU_CON_CSMUX_E_EN BIT(1) +#define EBU_CON_ALE_P_LOW BIT(2) +#define EBU_CON_CLE_P_LOW BIT(3) +#define EBU_CON_CS_P_LOW BIT(4) +#define EBU_CON_SE_P_LOW BIT(5) +#define EBU_CON_WP_P_LOW BIT(6) +#define EBU_CON_PRE_P_LOW BIT(7) +#define EBU_CON_IN_CS_S(n) ((n) << 8) +#define EBU_CON_OUT_CS_S(n) ((n) << 10) +#define EBU_CON_LAT_EN_CS_P ((0x3D) << 18) + +#define EBU_WAIT 0x0B4 +#define EBU_WAIT_RDBY BIT(0) +#define EBU_WAIT_WR_C BIT(3) + +#define HSNAND_CTL1 0x110 +#define HSNAND_CTL1_ADDR_SHIFT 24 + +#define HSNAND_CTL2 0x114 +#define HSNAND_CTL2_ADDR_SHIFT 8 +#define HSNAND_CTL2_CYC_N_V5 (0x2 << 16) + +#define HSNAND_INT_MSK_CTL 0x124 +#define HSNAND_INT_MSK_CTL_WR_C BIT(4) + +#define HSNAND_INT_STA 0x128 +#define HSNAND_INT_STA_WR_C BIT(4) + +#define HSNAND_CTL 0x130 +#define HSNAND_CTL_ENABLE_ECC BIT(0) +#define HSNAND_CTL_GO BIT(2) +#define HSNAND_CTL_CE_SEL_CS(n) BIT(3 + (n)) +#define HSNAND_CTL_RW_READ 0x0 +#define HSNAND_CTL_RW_WRITE BIT(10) +#define HSNAND_CTL_ECC_OFF_V8TH BIT(11) +#define HSNAND_CTL_CKFF_EN 0x0 +#define HSNAND_CTL_MSG_EN BIT(17) + +#define HSNAND_PARA0 0x13c +#define HSNAND_PARA0_PAGE_V8192 0x3 +#define HSNAND_PARA0_PIB_V256 (0x3 << 4) +#define HSNAND_PARA0_BYP_EN_NP 0x0 +#define HSNAND_PARA0_BYP_DEC_NP 0x0 +#define HSNAND_PARA0_TYPE_ONFI BIT(18) +#define HSNAND_PARA0_ADEP_EN BIT(21) + +#define HSNAND_CMSG_0 0x150 +#define HSNAND_CMSG_1 0x154 + +#define HSNAND_ALE_OFFS BIT(2) +#define HSNAND_CLE_OFFS BIT(3) +#define HSNAND_CS_OFFS BIT(4) + +#define HSNAND_ECC_OFFSET 0x008 + +#define MAX_CS 2 + +#define USEC_PER_SEC 1000000L + +struct ebu_nand_cs { + void __iomem *chipaddr; + u32 addr_sel; +}; + +struct ebu_nand_controller { + struct nand_controller controller; + struct nand_chip chip; + struct device *dev; + void __iomem *ebu; + void __iomem *hsnand; + struct dma_chan *dma_tx; + struct dma_chan *dma_rx; + struct completion dma_access_complete; + struct clk *clk; + u32 nd_para0; + u8 cs_num; + struct ebu_nand_cs cs[MAX_CS]; +}; + +static inline struct ebu_nand_controller *nand_to_ebu(struct nand_chip *chip) +{ + return container_of(chip, struct ebu_nand_controller, chip); +} + +static int ebu_nand_waitrdy(struct nand_chip *chip, int timeout_ms) +{ + struct ebu_nand_controller *ctrl = nand_to_ebu(chip); + u32 status; + + return readl_poll_timeout(ctrl->ebu + EBU_WAIT, status, + (status & EBU_WAIT_RDBY) || + (status & EBU_WAIT_WR_C), 20, timeout_ms); +} + +static u8 ebu_nand_readb(struct nand_chip *chip) +{ + struct ebu_nand_controller *ebu_host = nand_get_controller_data(chip); + u8 cs_num = ebu_host->cs_num; + u8 val; + + val = readb(ebu_host->cs[cs_num].chipaddr + HSNAND_CS_OFFS); + ebu_nand_waitrdy(chip, 1000); + return val; +} + +static void ebu_nand_writeb(struct nand_chip *chip, u32 offset, u8 value) +{ + struct ebu_nand_controller *ebu_host = nand_get_controller_data(chip); + u8 cs_num = ebu_host->cs_num; + + writeb(value, ebu_host->cs[cs_num].chipaddr + offset); + ebu_nand_waitrdy(chip, 1000); +} + +static void ebu_read_buf(struct nand_chip *chip, u_char *buf, unsigned int len) +{ + int i; + + for (i = 0; i < len; i++) + buf[i] = ebu_nand_readb(chip); +} + +static void ebu_write_buf(struct nand_chip *chip, const u_char *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) + ebu_nand_writeb(chip, HSNAND_CS_OFFS, buf[i]); +} + +static void ebu_nand_disable(struct nand_chip *chip) +{ + struct ebu_nand_controller *ebu_host = nand_get_controller_data(chip); + + writel(0, ebu_host->ebu + EBU_CON); +} + +static void ebu_select_chip(struct nand_chip *chip) +{ + struct ebu_nand_controller *ebu_host = nand_get_controller_data(chip); + void __iomem *nand_con = ebu_host->ebu + EBU_CON; + u32 cs = ebu_host->cs_num; + + writel(EBU_CON_NANDM_EN | EBU_CON_CSMUX_E_EN | EBU_CON_CS_P_LOW | + EBU_CON_SE_P_LOW | EBU_CON_WP_P_LOW | EBU_CON_PRE_P_LOW | + EBU_CON_IN_CS_S(cs) | EBU_CON_OUT_CS_S(cs) | + EBU_CON_LAT_EN_CS_P, nand_con); +} + +static int ebu_nand_set_timings(struct nand_chip *chip, int csline, + const struct nand_interface_config *conf) +{ + struct ebu_nand_controller *ctrl = nand_to_ebu(chip); + unsigned int rate = clk_get_rate(ctrl->clk) / HZ_PER_MHZ; + unsigned int period = DIV_ROUND_UP(USEC_PER_SEC, rate); + const struct nand_sdr_timings *timings; + u32 trecov, thold, twrwait, trdwait; + u32 reg = 0; + + timings = nand_get_sdr_timings(conf); + if (IS_ERR(timings)) + return PTR_ERR(timings); + + if (csline == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + trecov = DIV_ROUND_UP(max(timings->tREA_max, timings->tREH_min), + period); + reg |= EBU_BUSCON_RECOVC(trecov); + + thold = DIV_ROUND_UP(max(timings->tDH_min, timings->tDS_min), period); + reg |= EBU_BUSCON_HOLDC(thold); + + trdwait = DIV_ROUND_UP(max(timings->tRC_min, timings->tREH_min), + period); + reg |= EBU_BUSCON_WAITRDC(trdwait); + + twrwait = DIV_ROUND_UP(max(timings->tWC_min, timings->tWH_min), period); + reg |= EBU_BUSCON_WAITWRC(twrwait); + + reg |= EBU_BUSCON_CMULT_V4 | EBU_BUSCON_BCGEN_CS | EBU_BUSCON_ALEC | + EBU_BUSCON_SETUP_EN; + + writel(reg, ctrl->ebu + EBU_BUSCON(ctrl->cs_num)); + + return 0; +} + +static int ebu_nand_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section) + return -ERANGE; + + oobregion->offset = HSNAND_ECC_OFFSET; + oobregion->length = chip->ecc.total; + + return 0; +} + +static int ebu_nand_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section) + return -ERANGE; + + oobregion->offset = chip->ecc.total + HSNAND_ECC_OFFSET; + oobregion->length = mtd->oobsize - oobregion->offset; + + return 0; +} + +static const struct mtd_ooblayout_ops ebu_nand_ooblayout_ops = { + .ecc = ebu_nand_ooblayout_ecc, + .free = ebu_nand_ooblayout_free, +}; + +static void ebu_dma_rx_callback(void *cookie) +{ + struct ebu_nand_controller *ebu_host = cookie; + + dmaengine_terminate_async(ebu_host->dma_rx); + + complete(&ebu_host->dma_access_complete); +} + +static void ebu_dma_tx_callback(void *cookie) +{ + struct ebu_nand_controller *ebu_host = cookie; + + dmaengine_terminate_async(ebu_host->dma_tx); + + complete(&ebu_host->dma_access_complete); +} + +static int ebu_dma_start(struct ebu_nand_controller *ebu_host, u32 dir, + const u8 *buf, u32 len) +{ + struct dma_async_tx_descriptor *tx; + struct completion *dma_completion; + dma_async_tx_callback callback; + struct dma_chan *chan; + dma_cookie_t cookie; + unsigned long flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; + dma_addr_t buf_dma; + int ret; + unsigned long time_left; + + if (dir == DMA_DEV_TO_MEM) { + chan = ebu_host->dma_rx; + dma_completion = &ebu_host->dma_access_complete; + callback = ebu_dma_rx_callback; + } else { + chan = ebu_host->dma_tx; + dma_completion = &ebu_host->dma_access_complete; + callback = ebu_dma_tx_callback; + } + + buf_dma = dma_map_single(chan->device->dev, (void *)buf, len, dir); + if (dma_mapping_error(chan->device->dev, buf_dma)) { + dev_err(ebu_host->dev, "Failed to map DMA buffer\n"); + ret = -EIO; + goto err_unmap; + } + + tx = dmaengine_prep_slave_single(chan, buf_dma, len, dir, flags); + if (!tx) { + ret = -ENXIO; + goto err_unmap; + } + + tx->callback = callback; + tx->callback_param = ebu_host; + cookie = tx->tx_submit(tx); + + ret = dma_submit_error(cookie); + if (ret) { + dev_err(ebu_host->dev, "dma_submit_error %d\n", cookie); + ret = -EIO; + goto err_unmap; + } + + init_completion(dma_completion); + dma_async_issue_pending(chan); + + /* Wait DMA to finish the data transfer.*/ + time_left = wait_for_completion_timeout(dma_completion, msecs_to_jiffies(1000)); + if (!time_left) { + dev_err(ebu_host->dev, "I/O Error in DMA RX (status %d)\n", + dmaengine_tx_status(chan, cookie, NULL)); + dmaengine_terminate_sync(chan); + ret = -ETIMEDOUT; + goto err_unmap; + } + + return 0; + +err_unmap: + dma_unmap_single(ebu_host->dev, buf_dma, len, dir); + + return ret; +} + +static void ebu_nand_trigger(struct ebu_nand_controller *ebu_host, + int page, u32 cmd) +{ + unsigned int val; + + val = cmd | (page & 0xFF) << HSNAND_CTL1_ADDR_SHIFT; + writel(val, ebu_host->hsnand + HSNAND_CTL1); + val = (page & 0xFFFF00) >> 8 | HSNAND_CTL2_CYC_N_V5; + writel(val, ebu_host->hsnand + HSNAND_CTL2); + + writel(ebu_host->nd_para0, ebu_host->hsnand + HSNAND_PARA0); + + /* clear first, will update later */ + writel(0xFFFFFFFF, ebu_host->hsnand + HSNAND_CMSG_0); + writel(0xFFFFFFFF, ebu_host->hsnand + HSNAND_CMSG_1); + + writel(HSNAND_INT_MSK_CTL_WR_C, + ebu_host->hsnand + HSNAND_INT_MSK_CTL); + + if (!cmd) + val = HSNAND_CTL_RW_READ; + else + val = HSNAND_CTL_RW_WRITE; + + writel(HSNAND_CTL_MSG_EN | HSNAND_CTL_CKFF_EN | + HSNAND_CTL_ECC_OFF_V8TH | HSNAND_CTL_CE_SEL_CS(ebu_host->cs_num) | + HSNAND_CTL_ENABLE_ECC | HSNAND_CTL_GO | val, + ebu_host->hsnand + HSNAND_CTL); +} + +static int ebu_nand_read_page_hwecc(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct ebu_nand_controller *ebu_host = nand_get_controller_data(chip); + int ret, reg_data; + + ebu_nand_trigger(ebu_host, page, NAND_CMD_READ0); + + ret = ebu_dma_start(ebu_host, DMA_DEV_TO_MEM, buf, mtd->writesize); + if (ret) + return ret; + + if (oob_required) + chip->ecc.read_oob(chip, page); + + reg_data = readl(ebu_host->hsnand + HSNAND_CTL); + reg_data &= ~HSNAND_CTL_GO; + writel(reg_data, ebu_host->hsnand + HSNAND_CTL); + + return 0; +} + +static int ebu_nand_write_page_hwecc(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct ebu_nand_controller *ebu_host = nand_get_controller_data(chip); + void __iomem *int_sta = ebu_host->hsnand + HSNAND_INT_STA; + int reg_data, ret, val; + u32 reg; + + ebu_nand_trigger(ebu_host, page, NAND_CMD_SEQIN); + + ret = ebu_dma_start(ebu_host, DMA_MEM_TO_DEV, buf, mtd->writesize); + if (ret) + return ret; + + if (oob_required) { + reg = get_unaligned_le32(chip->oob_poi); + writel(reg, ebu_host->hsnand + HSNAND_CMSG_0); + + reg = get_unaligned_le32(chip->oob_poi + 4); + writel(reg, ebu_host->hsnand + HSNAND_CMSG_1); + } + + ret = readl_poll_timeout_atomic(int_sta, val, !(val & HSNAND_INT_STA_WR_C), + 10, 1000); + if (ret) + return ret; + + reg_data = readl(ebu_host->hsnand + HSNAND_CTL); + reg_data &= ~HSNAND_CTL_GO; + writel(reg_data, ebu_host->hsnand + HSNAND_CTL); + + return 0; +} + +static const u8 ecc_strength[] = { 1, 1, 4, 8, 24, 32, 40, 60, }; + +static int ebu_nand_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct ebu_nand_controller *ebu_host = nand_get_controller_data(chip); + u32 ecc_steps, ecc_bytes, ecc_total, pagesize, pg_per_blk; + u32 ecc_strength_ds = chip->ecc.strength; + u32 ecc_size = chip->ecc.size; + u32 writesize = mtd->writesize; + u32 blocksize = mtd->erasesize; + int bch_algo, start, val; + + /* Default to an ECC size of 512 */ + if (!chip->ecc.size) + chip->ecc.size = 512; + + switch (ecc_size) { + case 512: + start = 1; + if (!ecc_strength_ds) + ecc_strength_ds = 4; + break; + case 1024: + start = 4; + if (!ecc_strength_ds) + ecc_strength_ds = 32; + break; + default: + return -EINVAL; + } + + /* BCH ECC algorithm Settings for number of bits per 512B/1024B */ + bch_algo = round_up(start + 1, 4); + for (val = start; val < bch_algo; val++) { + if (ecc_strength_ds == ecc_strength[val]) + break; + } + if (val == bch_algo) + return -EINVAL; + + if (ecc_strength_ds == 8) + ecc_bytes = 14; + else + ecc_bytes = DIV_ROUND_UP(ecc_strength_ds * fls(8 * ecc_size), 8); + + ecc_steps = writesize / ecc_size; + ecc_total = ecc_steps * ecc_bytes; + if ((ecc_total + 8) > mtd->oobsize) + return -ERANGE; + + chip->ecc.total = ecc_total; + pagesize = fls(writesize >> 11); + if (pagesize > HSNAND_PARA0_PAGE_V8192) + return -ERANGE; + + pg_per_blk = fls((blocksize / writesize) >> 6) / 8; + if (pg_per_blk > HSNAND_PARA0_PIB_V256) + return -ERANGE; + + ebu_host->nd_para0 = pagesize | pg_per_blk | HSNAND_PARA0_BYP_EN_NP | + HSNAND_PARA0_BYP_DEC_NP | HSNAND_PARA0_ADEP_EN | + HSNAND_PARA0_TYPE_ONFI | (val << 29); + + mtd_set_ooblayout(mtd, &ebu_nand_ooblayout_ops); + chip->ecc.read_page = ebu_nand_read_page_hwecc; + chip->ecc.write_page = ebu_nand_write_page_hwecc; + + return 0; +} + +static int ebu_nand_exec_op(struct nand_chip *chip, + const struct nand_operation *op, bool check_only) +{ + const struct nand_op_instr *instr = NULL; + unsigned int op_id; + int i, timeout_ms, ret = 0; + + if (check_only) + return 0; + + ebu_select_chip(chip); + for (op_id = 0; op_id < op->ninstrs; op_id++) { + instr = &op->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + ebu_nand_writeb(chip, HSNAND_CLE_OFFS | HSNAND_CS_OFFS, + instr->ctx.cmd.opcode); + break; + + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) + ebu_nand_writeb(chip, + HSNAND_ALE_OFFS | HSNAND_CS_OFFS, + instr->ctx.addr.addrs[i]); + break; + + case NAND_OP_DATA_IN_INSTR: + ebu_read_buf(chip, instr->ctx.data.buf.in, + instr->ctx.data.len); + break; + + case NAND_OP_DATA_OUT_INSTR: + ebu_write_buf(chip, instr->ctx.data.buf.out, + instr->ctx.data.len); + break; + + case NAND_OP_WAITRDY_INSTR: + timeout_ms = instr->ctx.waitrdy.timeout_ms * 1000; + ret = ebu_nand_waitrdy(chip, timeout_ms); + break; + } + } + + return ret; +} + +static const struct nand_controller_ops ebu_nand_controller_ops = { + .attach_chip = ebu_nand_attach_chip, + .setup_interface = ebu_nand_set_timings, + .exec_op = ebu_nand_exec_op, +}; + +static void ebu_dma_cleanup(struct ebu_nand_controller *ebu_host) +{ + if (ebu_host->dma_rx) + dma_release_channel(ebu_host->dma_rx); + + if (ebu_host->dma_tx) + dma_release_channel(ebu_host->dma_tx); +} + +static int ebu_nand_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ebu_nand_controller *ebu_host; + struct device_node *chip_np; + struct nand_chip *nand; + struct mtd_info *mtd; + struct resource *res; + char *resname; + int ret; + u32 cs; + + ebu_host = devm_kzalloc(dev, sizeof(*ebu_host), GFP_KERNEL); + if (!ebu_host) + return -ENOMEM; + + ebu_host->dev = dev; + nand_controller_init(&ebu_host->controller); + + ebu_host->ebu = devm_platform_ioremap_resource_byname(pdev, "ebunand"); + if (IS_ERR(ebu_host->ebu)) + return PTR_ERR(ebu_host->ebu); + + ebu_host->hsnand = devm_platform_ioremap_resource_byname(pdev, "hsnand"); + if (IS_ERR(ebu_host->hsnand)) + return PTR_ERR(ebu_host->hsnand); + + chip_np = of_get_next_child(dev->of_node, NULL); + if (!chip_np) + return dev_err_probe(dev, -EINVAL, + "Could not find child node for the NAND chip\n"); + + ret = of_property_read_u32(chip_np, "reg", &cs); + if (ret) { + dev_err(dev, "failed to get chip select: %d\n", ret); + goto err_of_node_put; + } + if (cs >= MAX_CS) { + dev_err(dev, "got invalid chip select: %d\n", cs); + ret = -EINVAL; + goto err_of_node_put; + } + + ebu_host->cs_num = cs; + + resname = devm_kasprintf(dev, GFP_KERNEL, "nand_cs%d", cs); + if (!resname) { + ret = -ENOMEM; + goto err_of_node_put; + } + + ebu_host->cs[cs].chipaddr = devm_platform_ioremap_resource_byname(pdev, + resname); + if (IS_ERR(ebu_host->cs[cs].chipaddr)) { + ret = PTR_ERR(ebu_host->cs[cs].chipaddr); + goto err_of_node_put; + } + + ebu_host->clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(ebu_host->clk)) { + ret = dev_err_probe(dev, PTR_ERR(ebu_host->clk), + "failed to get and enable clock\n"); + goto err_of_node_put; + } + + ebu_host->dma_tx = dma_request_chan(dev, "tx"); + if (IS_ERR(ebu_host->dma_tx)) { + ret = dev_err_probe(dev, PTR_ERR(ebu_host->dma_tx), + "failed to request DMA tx chan!.\n"); + goto err_of_node_put; + } + + ebu_host->dma_rx = dma_request_chan(dev, "rx"); + if (IS_ERR(ebu_host->dma_rx)) { + ret = dev_err_probe(dev, PTR_ERR(ebu_host->dma_rx), + "failed to request DMA rx chan!.\n"); + ebu_host->dma_rx = NULL; + goto err_cleanup_dma; + } + + resname = devm_kasprintf(dev, GFP_KERNEL, "addr_sel%d", cs); + if (!resname) { + ret = -ENOMEM; + goto err_cleanup_dma; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, resname); + if (!res) { + ret = -EINVAL; + goto err_cleanup_dma; + } + ebu_host->cs[cs].addr_sel = res->start; + writel(ebu_host->cs[cs].addr_sel | EBU_ADDR_MASK(5) | EBU_ADDR_SEL_REGEN, + ebu_host->ebu + EBU_ADDR_SEL(cs)); + + nand_set_flash_node(&ebu_host->chip, chip_np); + + mtd = nand_to_mtd(&ebu_host->chip); + if (!mtd->name) { + dev_err(ebu_host->dev, "NAND label property is mandatory\n"); + ret = -EINVAL; + goto err_cleanup_dma; + } + + mtd->dev.parent = dev; + ebu_host->dev = dev; + + platform_set_drvdata(pdev, ebu_host); + nand_set_controller_data(&ebu_host->chip, ebu_host); + + nand = &ebu_host->chip; + nand->controller = &ebu_host->controller; + nand->controller->ops = &ebu_nand_controller_ops; + + /* Scan to find existence of the device */ + ret = nand_scan(&ebu_host->chip, 1); + if (ret) + goto err_cleanup_dma; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) + goto err_clean_nand; + + return 0; + +err_clean_nand: + nand_cleanup(&ebu_host->chip); +err_cleanup_dma: + ebu_dma_cleanup(ebu_host); +err_of_node_put: + of_node_put(chip_np); + + return ret; +} + +static void ebu_nand_remove(struct platform_device *pdev) +{ + struct ebu_nand_controller *ebu_host = platform_get_drvdata(pdev); + int ret; + + ret = mtd_device_unregister(nand_to_mtd(&ebu_host->chip)); + WARN_ON(ret); + nand_cleanup(&ebu_host->chip); + ebu_nand_disable(&ebu_host->chip); + ebu_dma_cleanup(ebu_host); +} + +static const struct of_device_id ebu_nand_match[] = { + { .compatible = "intel,lgm-ebunand" }, + {} +}; +MODULE_DEVICE_TABLE(of, ebu_nand_match); + +static struct platform_driver ebu_nand_driver = { + .probe = ebu_nand_probe, + .remove = ebu_nand_remove, + .driver = { + .name = "intel-nand-controller", + .of_match_table = ebu_nand_match, + }, + +}; +module_platform_driver(ebu_nand_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Vadivel Murugan R "); +MODULE_DESCRIPTION("Intel's LGM External Bus NAND Controller driver"); diff --git a/drivers/mtd/nand/raw/internals.h b/drivers/mtd/nand/raw/internals.h new file mode 100644 index 000000000..b7162ced9 --- /dev/null +++ b/drivers/mtd/nand/raw/internals.h @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2018 - Bootlin + * + * Author: Boris Brezillon + * + * Header containing internal definitions to be used only by core files. + * NAND controller drivers should not include this file. + */ + +#ifndef __LINUX_RAWNAND_INTERNALS +#define __LINUX_RAWNAND_INTERNALS + +#include + +/* + * NAND Flash Manufacturer ID Codes + */ +#define NAND_MFR_AMD 0x01 +#define NAND_MFR_ATO 0x9b +#define NAND_MFR_EON 0x92 +#define NAND_MFR_ESMT 0xc8 +#define NAND_MFR_FUJITSU 0x04 +#define NAND_MFR_HYNIX 0xad +#define NAND_MFR_INTEL 0x89 +#define NAND_MFR_MACRONIX 0xc2 +#define NAND_MFR_MICRON 0x2c +#define NAND_MFR_NATIONAL 0x8f +#define NAND_MFR_RENESAS 0x07 +#define NAND_MFR_SAMSUNG 0xec +#define NAND_MFR_SANDISK 0x45 +#define NAND_MFR_STMICRO 0x20 +/* Kioxia is new name of Toshiba memory. */ +#define NAND_MFR_TOSHIBA 0x98 +#define NAND_MFR_WINBOND 0xef + +/** + * struct nand_manufacturer_ops - NAND Manufacturer operations + * @detect: detect the NAND memory organization and capabilities + * @init: initialize all vendor specific fields (like the ->read_retry() + * implementation) if any. + * @cleanup: the ->init() function may have allocated resources, ->cleanup() + * is here to let vendor specific code release those resources. + * @fixup_onfi_param_page: apply vendor specific fixups to the ONFI parameter + * page. This is called after the checksum is verified. + */ +struct nand_manufacturer_ops { + void (*detect)(struct nand_chip *chip); + int (*init)(struct nand_chip *chip); + void (*cleanup)(struct nand_chip *chip); + void (*fixup_onfi_param_page)(struct nand_chip *chip, + struct nand_onfi_params *p); +}; + +/** + * struct nand_manufacturer_desc - NAND Flash Manufacturer descriptor + * @name: Manufacturer name + * @id: manufacturer ID code of device. + * @ops: manufacturer operations + */ +struct nand_manufacturer_desc { + int id; + char *name; + const struct nand_manufacturer_ops *ops; +}; + + +extern struct nand_flash_dev nand_flash_ids[]; + +extern const struct nand_manufacturer_ops amd_nand_manuf_ops; +extern const struct nand_manufacturer_ops esmt_nand_manuf_ops; +extern const struct nand_manufacturer_ops hynix_nand_manuf_ops; +extern const struct nand_manufacturer_ops macronix_nand_manuf_ops; +extern const struct nand_manufacturer_ops micron_nand_manuf_ops; +extern const struct nand_manufacturer_ops samsung_nand_manuf_ops; +extern const struct nand_manufacturer_ops sandisk_nand_manuf_ops; +extern const struct nand_manufacturer_ops toshiba_nand_manuf_ops; + +/* MLC pairing schemes */ +extern const struct mtd_pairing_scheme dist3_pairing_scheme; + +/* Core functions */ +const struct nand_manufacturer_desc *nand_get_manufacturer_desc(u8 id); +int nand_bbm_get_next_page(struct nand_chip *chip, int page); +int nand_markbad_bbm(struct nand_chip *chip, loff_t ofs); +int nand_erase_nand(struct nand_chip *chip, struct erase_info *instr, + int allowbbt); +void onfi_fill_interface_config(struct nand_chip *chip, + struct nand_interface_config *iface, + enum nand_interface_type type, + unsigned int timing_mode); +unsigned int +onfi_find_closest_sdr_mode(const struct nand_sdr_timings *spec_timings); +unsigned int +onfi_find_closest_nvddr_mode(const struct nand_nvddr_timings *spec_timings); +int nand_choose_best_sdr_timings(struct nand_chip *chip, + struct nand_interface_config *iface, + struct nand_sdr_timings *spec_timings); +int nand_choose_best_nvddr_timings(struct nand_chip *chip, + struct nand_interface_config *iface, + struct nand_nvddr_timings *spec_timings); +const struct nand_interface_config *nand_get_reset_interface_config(void); +int nand_get_features(struct nand_chip *chip, int addr, u8 *subfeature_param); +int nand_set_features(struct nand_chip *chip, int addr, u8 *subfeature_param); +int nand_read_page_raw_notsupp(struct nand_chip *chip, u8 *buf, + int oob_required, int page); +int nand_write_page_raw_notsupp(struct nand_chip *chip, const u8 *buf, + int oob_required, int page); +int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf, + unsigned int len); +void nand_decode_ext_id(struct nand_chip *chip); +void panic_nand_wait(struct nand_chip *chip, unsigned long timeo); +void sanitize_string(uint8_t *s, size_t len); + +static inline bool nand_has_exec_op(struct nand_chip *chip) +{ + if (!chip->controller || !chip->controller->ops || + !chip->controller->ops->exec_op) + return false; + + return true; +} + +static inline int nand_check_op(struct nand_chip *chip, + const struct nand_operation *op) +{ + if (!nand_has_exec_op(chip)) + return 0; + + return chip->controller->ops->exec_op(chip, op, true); +} + +static inline int nand_exec_op(struct nand_chip *chip, + const struct nand_operation *op) +{ + if (!nand_has_exec_op(chip)) + return -ENOTSUPP; + + if (WARN_ON(op->cs >= nanddev_ntargets(&chip->base))) + return -EINVAL; + + return chip->controller->ops->exec_op(chip, op, false); +} + +static inline bool nand_controller_can_setup_interface(struct nand_chip *chip) +{ + if (!chip->controller || !chip->controller->ops || + !chip->controller->ops->setup_interface) + return false; + + if (chip->options & NAND_KEEP_TIMINGS) + return false; + + return true; +} + +/* BBT functions */ +int nand_markbad_bbt(struct nand_chip *chip, loff_t offs); +int nand_isreserved_bbt(struct nand_chip *chip, loff_t offs); +int nand_isbad_bbt(struct nand_chip *chip, loff_t offs, int allowbbt); + +/* Legacy */ +void nand_legacy_set_defaults(struct nand_chip *chip); +void nand_legacy_adjust_cmdfunc(struct nand_chip *chip); +int nand_legacy_check_hooks(struct nand_chip *chip); + +/* ONFI functions */ +u16 onfi_crc16(u16 crc, u8 const *p, size_t len); +int nand_onfi_detect(struct nand_chip *chip); + +/* JEDEC functions */ +int nand_jedec_detect(struct nand_chip *chip); + +#endif /* __LINUX_RAWNAND_INTERNALS */ 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 000000000..8490412d5 --- /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 + * Copyright (C) 2025 Binbin Zhou + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 "); +MODULE_AUTHOR("Binbin Zhou "); +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 new file mode 100644 index 000000000..8f6a89d9b --- /dev/null +++ b/drivers/mtd/nand/raw/lpc32xx_mlc.c @@ -0,0 +1,915 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for NAND MLC Controller in LPC32xx + * + * Author: Roland Stigge + * + * Copyright © 2011 WORK Microwave GmbH + * Copyright © 2011, 2012 Roland Stigge + * + * NAND Flash Controller Operation: + * - Read: Auto Decode + * - Write: Auto Encode + * - Tested Page Sizes: 2048, 4096 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "lpc32xx_mlc" + +/********************************************************************** +* MLC NAND controller register offsets +**********************************************************************/ + +#define MLC_BUFF(x) (x + 0x00000) +#define MLC_DATA(x) (x + 0x08000) +#define MLC_CMD(x) (x + 0x10000) +#define MLC_ADDR(x) (x + 0x10004) +#define MLC_ECC_ENC_REG(x) (x + 0x10008) +#define MLC_ECC_DEC_REG(x) (x + 0x1000C) +#define MLC_ECC_AUTO_ENC_REG(x) (x + 0x10010) +#define MLC_ECC_AUTO_DEC_REG(x) (x + 0x10014) +#define MLC_RPR(x) (x + 0x10018) +#define MLC_WPR(x) (x + 0x1001C) +#define MLC_RUBP(x) (x + 0x10020) +#define MLC_ROBP(x) (x + 0x10024) +#define MLC_SW_WP_ADD_LOW(x) (x + 0x10028) +#define MLC_SW_WP_ADD_HIG(x) (x + 0x1002C) +#define MLC_ICR(x) (x + 0x10030) +#define MLC_TIME_REG(x) (x + 0x10034) +#define MLC_IRQ_MR(x) (x + 0x10038) +#define MLC_IRQ_SR(x) (x + 0x1003C) +#define MLC_LOCK_PR(x) (x + 0x10044) +#define MLC_ISR(x) (x + 0x10048) +#define MLC_CEH(x) (x + 0x1004C) + +/********************************************************************** +* MLC_CMD bit definitions +**********************************************************************/ +#define MLCCMD_RESET 0xFF + +/********************************************************************** +* MLC_ICR bit definitions +**********************************************************************/ +#define MLCICR_WPROT (1 << 3) +#define MLCICR_LARGEBLOCK (1 << 2) +#define MLCICR_LONGADDR (1 << 1) +#define MLCICR_16BIT (1 << 0) /* unsupported by LPC32x0! */ + +/********************************************************************** +* MLC_TIME_REG bit definitions +**********************************************************************/ +#define MLCTIMEREG_TCEA_DELAY(n) (((n) & 0x03) << 24) +#define MLCTIMEREG_BUSY_DELAY(n) (((n) & 0x1F) << 19) +#define MLCTIMEREG_NAND_TA(n) (((n) & 0x07) << 16) +#define MLCTIMEREG_RD_HIGH(n) (((n) & 0x0F) << 12) +#define MLCTIMEREG_RD_LOW(n) (((n) & 0x0F) << 8) +#define MLCTIMEREG_WR_HIGH(n) (((n) & 0x0F) << 4) +#define MLCTIMEREG_WR_LOW(n) (((n) & 0x0F) << 0) + +/********************************************************************** +* MLC_IRQ_MR and MLC_IRQ_SR bit definitions +**********************************************************************/ +#define MLCIRQ_NAND_READY (1 << 5) +#define MLCIRQ_CONTROLLER_READY (1 << 4) +#define MLCIRQ_DECODE_FAILURE (1 << 3) +#define MLCIRQ_DECODE_ERROR (1 << 2) +#define MLCIRQ_ECC_READY (1 << 1) +#define MLCIRQ_WRPROT_FAULT (1 << 0) + +/********************************************************************** +* MLC_LOCK_PR bit definitions +**********************************************************************/ +#define MLCLOCKPR_MAGIC 0xA25E + +/********************************************************************** +* MLC_ISR bit definitions +**********************************************************************/ +#define MLCISR_DECODER_FAILURE (1 << 6) +#define MLCISR_ERRORS ((1 << 4) | (1 << 5)) +#define MLCISR_ERRORS_DETECTED (1 << 3) +#define MLCISR_ECC_READY (1 << 2) +#define MLCISR_CONTROLLER_READY (1 << 1) +#define MLCISR_NAND_READY (1 << 0) + +/********************************************************************** +* MLC_CEH bit definitions +**********************************************************************/ +#define MLCCEH_NORMAL (1 << 0) + +struct lpc32xx_nand_cfg_mlc { + uint32_t tcea_delay; + uint32_t busy_delay; + uint32_t nand_ta; + uint32_t rd_high; + uint32_t rd_low; + uint32_t wr_high; + uint32_t wr_low; + struct mtd_partition *parts; + unsigned num_parts; +}; + +static int lpc32xx_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + + if (section >= nand_chip->ecc.steps) + return -ERANGE; + + oobregion->offset = ((section + 1) * 16) - nand_chip->ecc.bytes; + oobregion->length = nand_chip->ecc.bytes; + + return 0; +} + +static int lpc32xx_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + + if (section >= nand_chip->ecc.steps) + return -ERANGE; + + oobregion->offset = 16 * section; + oobregion->length = 16 - nand_chip->ecc.bytes; + + return 0; +} + +static const struct mtd_ooblayout_ops lpc32xx_ooblayout_ops = { + .ecc = lpc32xx_ooblayout_ecc, + .free = lpc32xx_ooblayout_free, +}; + +static struct nand_bbt_descr lpc32xx_nand_bbt = { + .options = NAND_BBT_ABSPAGE | NAND_BBT_2BIT | NAND_BBT_NO_OOB | + NAND_BBT_WRITE, + .pages = { 524224, 0, 0, 0, 0, 0, 0, 0 }, +}; + +static struct nand_bbt_descr lpc32xx_nand_bbt_mirror = { + .options = NAND_BBT_ABSPAGE | NAND_BBT_2BIT | NAND_BBT_NO_OOB | + NAND_BBT_WRITE, + .pages = { 524160, 0, 0, 0, 0, 0, 0, 0 }, +}; + +struct lpc32xx_nand_host { + struct platform_device *pdev; + struct nand_chip nand_chip; + struct lpc32xx_mlc_platform_data *pdata; + struct clk *clk; + struct gpio_desc *wp_gpio; + void __iomem *io_base; + int irq; + struct lpc32xx_nand_cfg_mlc *ncfg; + struct completion comp_nand; + struct completion comp_controller; + uint32_t llptr; + /* + * Physical addresses of ECC buffer, DMA data buffers, OOB data buffer + */ + dma_addr_t oob_buf_phy; + /* + * Virtual addresses of ECC buffer, DMA data buffers, OOB data buffer + */ + uint8_t *oob_buf; + /* Physical address of DMA base address */ + dma_addr_t io_base_phy; + + struct completion comp_dma; + struct dma_chan *dma_chan; + struct dma_slave_config dma_slave_config; + struct scatterlist sgl; + uint8_t *dma_buf; + uint8_t *dummy_buf; + int mlcsubpages; /* number of 512bytes-subpages */ +}; + +/* + * Activate/Deactivate DMA Operation: + * + * Using the PL080 DMA Controller for transferring the 512 byte subpages + * instead of doing readl() / writel() in a loop slows it down significantly. + * Measurements via getnstimeofday() upon 512 byte subpage reads reveal: + * + * - readl() of 128 x 32 bits in a loop: ~20us + * - DMA read of 512 bytes (32 bit, 4...128 words bursts): ~60us + * - DMA read of 512 bytes (32 bit, no bursts): ~100us + * + * This applies to the transfer itself. In the DMA case: only the + * wait_for_completion() (DMA setup _not_ included). + * + * Note that the 512 bytes subpage transfer is done directly from/to a + * FIFO/buffer inside the NAND controller. Most of the time (~400-800us for a + * 2048 bytes page) is spent waiting for the NAND IRQ, anyway. (The NAND + * controller transferring data between its internal buffer to/from the NAND + * chip.) + * + * Therefore, using the PL080 DMA is disabled by default, for now. + * + */ +static int use_dma; + +static void lpc32xx_nand_setup(struct lpc32xx_nand_host *host) +{ + uint32_t clkrate, tmp; + + /* Reset MLC controller */ + writel(MLCCMD_RESET, MLC_CMD(host->io_base)); + udelay(1000); + + /* Get base clock for MLC block */ + clkrate = clk_get_rate(host->clk); + if (clkrate == 0) + clkrate = 104000000; + + /* Unlock MLC_ICR + * (among others, will be locked again automatically) */ + writew(MLCLOCKPR_MAGIC, MLC_LOCK_PR(host->io_base)); + + /* Configure MLC Controller: Large Block, 5 Byte Address */ + tmp = MLCICR_LARGEBLOCK | MLCICR_LONGADDR; + writel(tmp, MLC_ICR(host->io_base)); + + /* Unlock MLC_TIME_REG + * (among others, will be locked again automatically) */ + writew(MLCLOCKPR_MAGIC, MLC_LOCK_PR(host->io_base)); + + /* Compute clock setup values, see LPC and NAND manual */ + tmp = 0; + tmp |= MLCTIMEREG_TCEA_DELAY(clkrate / host->ncfg->tcea_delay + 1); + tmp |= MLCTIMEREG_BUSY_DELAY(clkrate / host->ncfg->busy_delay + 1); + tmp |= MLCTIMEREG_NAND_TA(clkrate / host->ncfg->nand_ta + 1); + tmp |= MLCTIMEREG_RD_HIGH(clkrate / host->ncfg->rd_high + 1); + tmp |= MLCTIMEREG_RD_LOW(clkrate / host->ncfg->rd_low); + tmp |= MLCTIMEREG_WR_HIGH(clkrate / host->ncfg->wr_high + 1); + tmp |= MLCTIMEREG_WR_LOW(clkrate / host->ncfg->wr_low); + writel(tmp, MLC_TIME_REG(host->io_base)); + + /* Enable IRQ for CONTROLLER_READY and NAND_READY */ + writeb(MLCIRQ_CONTROLLER_READY | MLCIRQ_NAND_READY, + MLC_IRQ_MR(host->io_base)); + + /* Normal nCE operation: nCE controlled by controller */ + writel(MLCCEH_NORMAL, MLC_CEH(host->io_base)); +} + +/* + * Hardware specific access to control lines + */ +static void lpc32xx_nand_cmd_ctrl(struct nand_chip *nand_chip, int cmd, + unsigned int ctrl) +{ + struct lpc32xx_nand_host *host = nand_get_controller_data(nand_chip); + + if (cmd != NAND_CMD_NONE) { + if (ctrl & NAND_CLE) + writel(cmd, MLC_CMD(host->io_base)); + else + writel(cmd, MLC_ADDR(host->io_base)); + } +} + +/* + * Read Device Ready (NAND device _and_ controller ready) + */ +static int lpc32xx_nand_device_ready(struct nand_chip *nand_chip) +{ + struct lpc32xx_nand_host *host = nand_get_controller_data(nand_chip); + + if ((readb(MLC_ISR(host->io_base)) & + (MLCISR_CONTROLLER_READY | MLCISR_NAND_READY)) == + (MLCISR_CONTROLLER_READY | MLCISR_NAND_READY)) + return 1; + + return 0; +} + +static irqreturn_t lpc3xxx_nand_irq(int irq, void *data) +{ + struct lpc32xx_nand_host *host = data; + uint8_t sr; + + /* Clear interrupt flag by reading status */ + sr = readb(MLC_IRQ_SR(host->io_base)); + if (sr & MLCIRQ_NAND_READY) + complete(&host->comp_nand); + if (sr & MLCIRQ_CONTROLLER_READY) + complete(&host->comp_controller); + + return IRQ_HANDLED; +} + +static int lpc32xx_waitfunc_nand(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + + if (readb(MLC_ISR(host->io_base)) & MLCISR_NAND_READY) + goto exit; + + wait_for_completion(&host->comp_nand); + + while (!(readb(MLC_ISR(host->io_base)) & MLCISR_NAND_READY)) { + /* Seems to be delayed sometimes by controller */ + dev_dbg(&mtd->dev, "Warning: NAND not ready.\n"); + cpu_relax(); + } + +exit: + return NAND_STATUS_READY; +} + +static int lpc32xx_waitfunc_controller(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + + if (readb(MLC_ISR(host->io_base)) & MLCISR_CONTROLLER_READY) + goto exit; + + wait_for_completion(&host->comp_controller); + + while (!(readb(MLC_ISR(host->io_base)) & + MLCISR_CONTROLLER_READY)) { + dev_dbg(&mtd->dev, "Warning: Controller not ready.\n"); + cpu_relax(); + } + +exit: + return NAND_STATUS_READY; +} + +static int lpc32xx_waitfunc(struct nand_chip *chip) +{ + lpc32xx_waitfunc_nand(chip); + lpc32xx_waitfunc_controller(chip); + + return NAND_STATUS_READY; +} + +/* + * Enable NAND write protect + */ +static void lpc32xx_wp_enable(struct lpc32xx_nand_host *host) +{ + if (host->wp_gpio) + gpiod_set_value_cansleep(host->wp_gpio, 1); +} + +/* + * Disable NAND write protect + */ +static void lpc32xx_wp_disable(struct lpc32xx_nand_host *host) +{ + if (host->wp_gpio) + gpiod_set_value_cansleep(host->wp_gpio, 0); +} + +static void lpc32xx_dma_complete_func(void *completion) +{ + complete(completion); +} + +static int lpc32xx_xmit_dma(struct mtd_info *mtd, void *mem, int len, + enum dma_transfer_direction dir) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + 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); + + res = dma_map_sg(host->dma_chan->device->dev, &host->sgl, 1, + DMA_BIDIRECTIONAL); + if (res != 1) { + dev_err(mtd->dev.parent, "Failed to map sg list\n"); + return -ENXIO; + } + desc = dmaengine_prep_slave_sg(host->dma_chan, &host->sgl, 1, dir, + flags); + if (!desc) { + dev_err(mtd->dev.parent, "Failed to prepare slave sg\n"); + res = -ENXIO; + goto out1; + } + + init_completion(&host->comp_dma); + desc->callback = lpc32xx_dma_complete_func; + desc->callback_param = &host->comp_dma; + + dmaengine_submit(desc); + dma_async_issue_pending(host->dma_chan); + + 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); + return 0; +out1: + dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1, + DMA_BIDIRECTIONAL); + return res; +} + +static int lpc32xx_read_page(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + int i, j; + uint8_t *oobbuf = chip->oob_poi; + uint32_t mlc_isr; + int res; + uint8_t *dma_buf; + bool dma_mapped; + + if ((void *)buf <= high_memory) { + dma_buf = buf; + dma_mapped = true; + } else { + dma_buf = host->dma_buf; + dma_mapped = false; + } + + /* Writing Command and Address */ + nand_read_page_op(chip, page, 0, NULL, 0); + + /* For all sub-pages */ + for (i = 0; i < host->mlcsubpages; i++) { + /* Start Auto Decode Command */ + writeb(0x00, MLC_ECC_AUTO_DEC_REG(host->io_base)); + + /* Wait for Controller Ready */ + lpc32xx_waitfunc_controller(chip); + + /* Check ECC Error status */ + mlc_isr = readl(MLC_ISR(host->io_base)); + if (mlc_isr & MLCISR_DECODER_FAILURE) { + mtd->ecc_stats.failed++; + dev_warn(&mtd->dev, "%s: DECODER_FAILURE\n", __func__); + } else if (mlc_isr & MLCISR_ERRORS_DETECTED) { + mtd->ecc_stats.corrected += ((mlc_isr >> 4) & 0x3) + 1; + } + + /* Read 512 + 16 Bytes */ + if (use_dma) { + res = lpc32xx_xmit_dma(mtd, dma_buf + i * 512, 512, + DMA_DEV_TO_MEM); + if (res) + return res; + } else { + for (j = 0; j < (512 >> 2); j++) { + *((uint32_t *)(buf)) = + readl(MLC_BUFF(host->io_base)); + buf += 4; + } + } + for (j = 0; j < (16 >> 2); j++) { + *((uint32_t *)(oobbuf)) = + readl(MLC_BUFF(host->io_base)); + oobbuf += 4; + } + } + + if (use_dma && !dma_mapped) + memcpy(buf, dma_buf, mtd->writesize); + + return 0; +} + +static int lpc32xx_write_page_lowlevel(struct nand_chip *chip, + const uint8_t *buf, int oob_required, + int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + const uint8_t *oobbuf = chip->oob_poi; + uint8_t *dma_buf = (uint8_t *)buf; + int res; + int i, j; + + if (use_dma && (void *)buf >= high_memory) { + dma_buf = host->dma_buf; + memcpy(dma_buf, buf, mtd->writesize); + } + + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + + for (i = 0; i < host->mlcsubpages; i++) { + /* Start Encode */ + writeb(0x00, MLC_ECC_ENC_REG(host->io_base)); + + /* Write 512 + 6 Bytes to Buffer */ + if (use_dma) { + res = lpc32xx_xmit_dma(mtd, dma_buf + i * 512, 512, + DMA_MEM_TO_DEV); + if (res) + return res; + } else { + for (j = 0; j < (512 >> 2); j++) { + writel(*((uint32_t *)(buf)), + MLC_BUFF(host->io_base)); + buf += 4; + } + } + writel(*((uint32_t *)(oobbuf)), MLC_BUFF(host->io_base)); + oobbuf += 4; + writew(*((uint16_t *)(oobbuf)), MLC_BUFF(host->io_base)); + oobbuf += 12; + + /* Auto Encode w/ Bit 8 = 0 (see LPC MLC Controller manual) */ + writeb(0x00, MLC_ECC_AUTO_ENC_REG(host->io_base)); + + /* Wait for Controller Ready */ + lpc32xx_waitfunc_controller(chip); + } + + return nand_prog_page_end_op(chip); +} + +static int lpc32xx_read_oob(struct nand_chip *chip, int page) +{ + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + + /* Read whole page - necessary with MLC controller! */ + lpc32xx_read_page(chip, host->dummy_buf, 1, page); + + return 0; +} + +static int lpc32xx_write_oob(struct nand_chip *chip, int page) +{ + /* None, write_oob conflicts with the automatic LPC MLC ECC decoder! */ + return 0; +} + +/* Prepares MLC for transfers with H/W ECC enabled: always enabled anyway */ +static void lpc32xx_ecc_enable(struct nand_chip *chip, int mode) +{ + /* Always enabled! */ +} + +static int lpc32xx_dma_setup(struct lpc32xx_nand_host *host) +{ + struct mtd_info *mtd = nand_to_mtd(&host->nand_chip); + dma_cap_mask_t mask; + + host->dma_chan = dma_request_chan(mtd->dev.parent, "rx-tx"); + if (IS_ERR(host->dma_chan)) { + /* fallback to request using platform data */ + if (!host->pdata || !host->pdata->dma_filter) { + dev_err(mtd->dev.parent, "no DMA platform data\n"); + return -ENOENT; + } + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + host->dma_chan = dma_request_channel(mask, host->pdata->dma_filter, "nand-mlc"); + + if (!host->dma_chan) { + dev_err(mtd->dev.parent, "Failed to request DMA channel\n"); + return -EBUSY; + } + } + + /* + * Set direction to a sensible value even if the dmaengine driver + * should ignore it. With the default (DMA_MEM_TO_MEM), the amba-pl08x + * driver criticizes it as "alien transfer direction". + */ + host->dma_slave_config.direction = DMA_DEV_TO_MEM; + host->dma_slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_slave_config.src_maxburst = 128; + host->dma_slave_config.dst_maxburst = 128; + /* DMA controller does flow control: */ + host->dma_slave_config.device_fc = false; + host->dma_slave_config.src_addr = MLC_BUFF(host->io_base_phy); + host->dma_slave_config.dst_addr = MLC_BUFF(host->io_base_phy); + if (dmaengine_slave_config(host->dma_chan, &host->dma_slave_config)) { + dev_err(mtd->dev.parent, "Failed to setup DMA slave\n"); + goto out1; + } + + return 0; +out1: + dma_release_channel(host->dma_chan); + return -ENXIO; +} + +static struct lpc32xx_nand_cfg_mlc *lpc32xx_parse_dt(struct device *dev) +{ + struct lpc32xx_nand_cfg_mlc *ncfg; + struct device_node *np = dev->of_node; + + ncfg = devm_kzalloc(dev, sizeof(*ncfg), GFP_KERNEL); + if (!ncfg) + return NULL; + + of_property_read_u32(np, "nxp,tcea-delay", &ncfg->tcea_delay); + of_property_read_u32(np, "nxp,busy-delay", &ncfg->busy_delay); + of_property_read_u32(np, "nxp,nand-ta", &ncfg->nand_ta); + of_property_read_u32(np, "nxp,rd-high", &ncfg->rd_high); + of_property_read_u32(np, "nxp,rd-low", &ncfg->rd_low); + of_property_read_u32(np, "nxp,wr-high", &ncfg->wr_high); + of_property_read_u32(np, "nxp,wr-low", &ncfg->wr_low); + + if (!ncfg->tcea_delay || !ncfg->busy_delay || !ncfg->nand_ta || + !ncfg->rd_high || !ncfg->rd_low || !ncfg->wr_high || + !ncfg->wr_low) { + dev_err(dev, "chip parameters not specified correctly\n"); + return NULL; + } + + return ncfg; +} + +static int lpc32xx_nand_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + struct device *dev = &host->pdev->dev; + + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return 0; + + host->dma_buf = devm_kzalloc(dev, mtd->writesize, GFP_KERNEL); + if (!host->dma_buf) + return -ENOMEM; + + host->dummy_buf = devm_kzalloc(dev, mtd->writesize, GFP_KERNEL); + if (!host->dummy_buf) + return -ENOMEM; + + chip->ecc.size = 512; + chip->ecc.hwctl = lpc32xx_ecc_enable; + chip->ecc.read_page_raw = lpc32xx_read_page; + chip->ecc.read_page = lpc32xx_read_page; + chip->ecc.write_page_raw = lpc32xx_write_page_lowlevel; + chip->ecc.write_page = lpc32xx_write_page_lowlevel; + chip->ecc.write_oob = lpc32xx_write_oob; + chip->ecc.read_oob = lpc32xx_read_oob; + chip->ecc.strength = 4; + chip->ecc.bytes = 10; + + mtd_set_ooblayout(mtd, &lpc32xx_ooblayout_ops); + host->mlcsubpages = mtd->writesize / 512; + + return 0; +} + +static const struct nand_controller_ops lpc32xx_nand_controller_ops = { + .attach_chip = lpc32xx_nand_attach_chip, +}; + +/* + * Probe for NAND controller + */ +static int lpc32xx_nand_probe(struct platform_device *pdev) +{ + struct lpc32xx_nand_host *host; + struct mtd_info *mtd; + struct nand_chip *nand_chip; + struct resource *rc; + int res; + + /* Allocate memory for the device structure (and zero it) */ + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + host->pdev = pdev; + + host->io_base = devm_platform_get_and_ioremap_resource(pdev, 0, &rc); + if (IS_ERR(host->io_base)) + return PTR_ERR(host->io_base); + + host->io_base_phy = rc->start; + + nand_chip = &host->nand_chip; + mtd = nand_to_mtd(nand_chip); + if (pdev->dev.of_node) + host->ncfg = lpc32xx_parse_dt(&pdev->dev); + if (!host->ncfg) { + dev_err(&pdev->dev, + "Missing or bad NAND config from device tree\n"); + return -ENOENT; + } + + /* Start with WP disabled, if available */ + host->wp_gpio = gpiod_get_optional(&pdev->dev, NULL, GPIOD_OUT_LOW); + res = PTR_ERR_OR_ZERO(host->wp_gpio); + if (res) { + if (res != -EPROBE_DEFER) + dev_err(&pdev->dev, "WP GPIO is not available: %d\n", + res); + return res; + } + + gpiod_set_consumer_name(host->wp_gpio, "NAND WP"); + + host->pdata = dev_get_platdata(&pdev->dev); + + /* link the private data structures */ + nand_set_controller_data(nand_chip, host); + nand_set_flash_node(nand_chip, pdev->dev.of_node); + mtd->dev.parent = &pdev->dev; + + /* Get NAND clock */ + host->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(host->clk)) { + dev_err(&pdev->dev, "Clock initialization failure\n"); + res = -ENOENT; + goto free_gpio; + } + res = clk_prepare_enable(host->clk); + if (res) + goto put_clk; + + nand_chip->legacy.cmd_ctrl = lpc32xx_nand_cmd_ctrl; + nand_chip->legacy.dev_ready = lpc32xx_nand_device_ready; + nand_chip->legacy.chip_delay = 25; /* us */ + nand_chip->legacy.IO_ADDR_R = MLC_DATA(host->io_base); + nand_chip->legacy.IO_ADDR_W = MLC_DATA(host->io_base); + + /* Init NAND controller */ + lpc32xx_nand_setup(host); + + platform_set_drvdata(pdev, host); + + /* Initialize function pointers */ + nand_chip->legacy.waitfunc = lpc32xx_waitfunc; + + nand_chip->options = NAND_NO_SUBPAGE_WRITE; + nand_chip->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB; + nand_chip->bbt_td = &lpc32xx_nand_bbt; + nand_chip->bbt_md = &lpc32xx_nand_bbt_mirror; + + if (use_dma) { + res = lpc32xx_dma_setup(host); + if (res) { + res = -EIO; + goto unprepare_clk; + } + } + + /* initially clear interrupt status */ + readb(MLC_IRQ_SR(host->io_base)); + + init_completion(&host->comp_nand); + init_completion(&host->comp_controller); + + host->irq = platform_get_irq(pdev, 0); + if (host->irq < 0) { + res = -EINVAL; + goto release_dma_chan; + } + + if (request_irq(host->irq, &lpc3xxx_nand_irq, + IRQF_TRIGGER_HIGH, DRV_NAME, host)) { + dev_err(&pdev->dev, "Error requesting NAND IRQ\n"); + res = -ENXIO; + goto release_dma_chan; + } + + /* + * Scan to find existence of the device and get the type of NAND device: + * SMALL block or LARGE block. + */ + nand_chip->legacy.dummy_controller.ops = &lpc32xx_nand_controller_ops; + res = nand_scan(nand_chip, 1); + if (res) + goto free_irq; + + mtd->name = DRV_NAME; + + res = mtd_device_register(mtd, host->ncfg->parts, + host->ncfg->num_parts); + if (res) + goto cleanup_nand; + + return 0; + +cleanup_nand: + nand_cleanup(nand_chip); +free_irq: + free_irq(host->irq, host); +release_dma_chan: + if (use_dma) + dma_release_channel(host->dma_chan); +unprepare_clk: + clk_disable_unprepare(host->clk); +put_clk: + clk_put(host->clk); +free_gpio: + lpc32xx_wp_enable(host); + gpiod_put(host->wp_gpio); + + return res; +} + +/* + * Remove NAND device + */ +static void lpc32xx_nand_remove(struct platform_device *pdev) +{ + struct lpc32xx_nand_host *host = platform_get_drvdata(pdev); + struct nand_chip *chip = &host->nand_chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + + free_irq(host->irq, host); + if (use_dma) + dma_release_channel(host->dma_chan); + + clk_disable_unprepare(host->clk); + clk_put(host->clk); + + lpc32xx_wp_enable(host); + gpiod_put(host->wp_gpio); +} + +static int lpc32xx_nand_resume(struct platform_device *pdev) +{ + struct lpc32xx_nand_host *host = platform_get_drvdata(pdev); + int ret; + + /* Re-enable NAND clock */ + ret = clk_prepare_enable(host->clk); + if (ret) + return ret; + + /* Fresh init of NAND controller */ + lpc32xx_nand_setup(host); + + /* Disable write protect */ + lpc32xx_wp_disable(host); + + return 0; +} + +static int lpc32xx_nand_suspend(struct platform_device *pdev, pm_message_t pm) +{ + struct lpc32xx_nand_host *host = platform_get_drvdata(pdev); + + /* Enable write protect for safety */ + lpc32xx_wp_enable(host); + + /* Disable clock */ + clk_disable_unprepare(host->clk); + return 0; +} + +static const struct of_device_id lpc32xx_nand_match[] = { + { .compatible = "nxp,lpc3220-mlc" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, lpc32xx_nand_match); + +static struct platform_driver lpc32xx_nand_driver = { + .probe = lpc32xx_nand_probe, + .remove = lpc32xx_nand_remove, + .resume = pm_ptr(lpc32xx_nand_resume), + .suspend = pm_ptr(lpc32xx_nand_suspend), + .driver = { + .name = DRV_NAME, + .of_match_table = lpc32xx_nand_match, + }, +}; + +module_platform_driver(lpc32xx_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Roland Stigge "); +MODULE_DESCRIPTION("NAND driver for the NXP LPC32XX MLC controller"); diff --git a/drivers/mtd/nand/raw/lpc32xx_slc.c b/drivers/mtd/nand/raw/lpc32xx_slc.c new file mode 100644 index 000000000..10c808020 --- /dev/null +++ b/drivers/mtd/nand/raw/lpc32xx_slc.c @@ -0,0 +1,1035 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NXP LPC32XX NAND SLC driver + * + * Authors: + * Kevin Wells + * Roland Stigge + * + * Copyright © 2011 NXP Semiconductors + * Copyright © 2012 Roland Stigge + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LPC32XX_MODNAME "lpc32xx-nand" + +/********************************************************************** +* SLC NAND controller register offsets +**********************************************************************/ + +#define SLC_DATA(x) (x + 0x000) +#define SLC_ADDR(x) (x + 0x004) +#define SLC_CMD(x) (x + 0x008) +#define SLC_STOP(x) (x + 0x00C) +#define SLC_CTRL(x) (x + 0x010) +#define SLC_CFG(x) (x + 0x014) +#define SLC_STAT(x) (x + 0x018) +#define SLC_INT_STAT(x) (x + 0x01C) +#define SLC_IEN(x) (x + 0x020) +#define SLC_ISR(x) (x + 0x024) +#define SLC_ICR(x) (x + 0x028) +#define SLC_TAC(x) (x + 0x02C) +#define SLC_TC(x) (x + 0x030) +#define SLC_ECC(x) (x + 0x034) +#define SLC_DMA_DATA(x) (x + 0x038) + +/********************************************************************** +* slc_ctrl register definitions +**********************************************************************/ +#define SLCCTRL_SW_RESET (1 << 2) /* Reset the NAND controller bit */ +#define SLCCTRL_ECC_CLEAR (1 << 1) /* Reset ECC bit */ +#define SLCCTRL_DMA_START (1 << 0) /* Start DMA channel bit */ + +/********************************************************************** +* slc_cfg register definitions +**********************************************************************/ +#define SLCCFG_CE_LOW (1 << 5) /* Force CE low bit */ +#define SLCCFG_DMA_ECC (1 << 4) /* Enable DMA ECC bit */ +#define SLCCFG_ECC_EN (1 << 3) /* ECC enable bit */ +#define SLCCFG_DMA_BURST (1 << 2) /* DMA burst bit */ +#define SLCCFG_DMA_DIR (1 << 1) /* DMA write(0)/read(1) bit */ +#define SLCCFG_WIDTH (1 << 0) /* External device width, 0=8bit */ + +/********************************************************************** +* slc_stat register definitions +**********************************************************************/ +#define SLCSTAT_DMA_FIFO (1 << 2) /* DMA FIFO has data bit */ +#define SLCSTAT_SLC_FIFO (1 << 1) /* SLC FIFO has data bit */ +#define SLCSTAT_NAND_READY (1 << 0) /* NAND device is ready bit */ + +/********************************************************************** +* slc_int_stat, slc_ien, slc_isr, and slc_icr register definitions +**********************************************************************/ +#define SLCSTAT_INT_TC (1 << 1) /* Transfer count bit */ +#define SLCSTAT_INT_RDY_EN (1 << 0) /* Ready interrupt bit */ + +/********************************************************************** +* slc_tac register definitions +**********************************************************************/ +/* Computation of clock cycles on basis of controller and device clock rates */ +#define SLCTAC_CLOCKS(c, n, s) (min_t(u32, DIV_ROUND_UP(c, n) - 1, 0xF) << s) + +/* Clock setting for RDY write sample wait time in 2*n clocks */ +#define SLCTAC_WDR(n) (((n) & 0xF) << 28) +/* Write pulse width in clock cycles, 1 to 16 clocks */ +#define SLCTAC_WWIDTH(c, n) (SLCTAC_CLOCKS(c, n, 24)) +/* Write hold time of control and data signals, 1 to 16 clocks */ +#define SLCTAC_WHOLD(c, n) (SLCTAC_CLOCKS(c, n, 20)) +/* Write setup time of control and data signals, 1 to 16 clocks */ +#define SLCTAC_WSETUP(c, n) (SLCTAC_CLOCKS(c, n, 16)) +/* Clock setting for RDY read sample wait time in 2*n clocks */ +#define SLCTAC_RDR(n) (((n) & 0xF) << 12) +/* Read pulse width in clock cycles, 1 to 16 clocks */ +#define SLCTAC_RWIDTH(c, n) (SLCTAC_CLOCKS(c, n, 8)) +/* Read hold time of control and data signals, 1 to 16 clocks */ +#define SLCTAC_RHOLD(c, n) (SLCTAC_CLOCKS(c, n, 4)) +/* Read setup time of control and data signals, 1 to 16 clocks */ +#define SLCTAC_RSETUP(c, n) (SLCTAC_CLOCKS(c, n, 0)) + +/********************************************************************** +* slc_ecc register definitions +**********************************************************************/ +/* ECC line party fetch macro */ +#define SLCECC_TO_LINEPAR(n) (((n) >> 6) & 0x7FFF) +#define SLCECC_TO_COLPAR(n) ((n) & 0x3F) + +/* + * DMA requires storage space for the DMA local buffer and the hardware ECC + * storage area. The DMA local buffer is only used if DMA mapping fails + * during runtime. + */ +#define LPC32XX_DMA_DATA_SIZE 4096 +#define LPC32XX_ECC_SAVE_SIZE ((4096 / 256) * 4) + +/* Number of bytes used for ECC stored in NAND per 256 bytes */ +#define LPC32XX_SLC_DEV_ECC_BYTES 3 + +/* + * If the NAND base clock frequency can't be fetched, this frequency will be + * used instead as the base. This rate is used to setup the timing registers + * used for NAND accesses. + */ +#define LPC32XX_DEF_BUS_RATE 133250000 + +/* Milliseconds for DMA FIFO timeout (unlikely anyway) */ +#define LPC32XX_DMA_TIMEOUT 100 + +/* + * NAND ECC Layout for small page NAND devices + * Note: For large and huge page devices, the default layouts are used + */ +static int lpc32xx_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section) + return -ERANGE; + + oobregion->length = 6; + oobregion->offset = 10; + + return 0; +} + +static int lpc32xx_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 1) + return -ERANGE; + + if (!section) { + oobregion->offset = 0; + oobregion->length = 4; + } else { + oobregion->offset = 6; + oobregion->length = 4; + } + + return 0; +} + +static const struct mtd_ooblayout_ops lpc32xx_ooblayout_ops = { + .ecc = lpc32xx_ooblayout_ecc, + .free = lpc32xx_ooblayout_free, +}; + +static u8 bbt_pattern[] = {'B', 'b', 't', '0' }; +static u8 mirror_pattern[] = {'1', 't', 'b', 'B' }; + +/* + * Small page FLASH BBT descriptors, marker at offset 0, version at offset 6 + * Note: Large page devices used the default layout + */ +static struct nand_bbt_descr bbt_smallpage_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 6, + .maxblocks = 4, + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_smallpage_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 6, + .maxblocks = 4, + .pattern = mirror_pattern +}; + +/* + * NAND platform configuration structure + */ +struct lpc32xx_nand_cfg_slc { + uint32_t wdr_clks; + uint32_t wwidth; + uint32_t whold; + uint32_t wsetup; + uint32_t rdr_clks; + uint32_t rwidth; + uint32_t rhold; + uint32_t rsetup; + struct mtd_partition *parts; + unsigned num_parts; +}; + +struct lpc32xx_nand_host { + struct nand_chip nand_chip; + struct lpc32xx_slc_platform_data *pdata; + struct clk *clk; + struct gpio_desc *wp_gpio; + void __iomem *io_base; + struct lpc32xx_nand_cfg_slc *ncfg; + + struct completion comp; + struct dma_chan *dma_chan; + uint32_t dma_buf_len; + struct dma_slave_config dma_slave_config; + struct scatterlist sgl; + + /* + * DMA and CPU addresses of ECC work area and data buffer + */ + uint32_t *ecc_buf; + uint8_t *data_buf; + dma_addr_t io_base_dma; +}; + +static void lpc32xx_nand_setup(struct lpc32xx_nand_host *host) +{ + uint32_t clkrate, tmp; + + /* Reset SLC controller */ + writel(SLCCTRL_SW_RESET, SLC_CTRL(host->io_base)); + udelay(1000); + + /* Basic setup */ + writel(0, SLC_CFG(host->io_base)); + writel(0, SLC_IEN(host->io_base)); + writel((SLCSTAT_INT_TC | SLCSTAT_INT_RDY_EN), + SLC_ICR(host->io_base)); + + /* Get base clock for SLC block */ + clkrate = clk_get_rate(host->clk); + if (clkrate == 0) + clkrate = LPC32XX_DEF_BUS_RATE; + + /* Compute clock setup values */ + tmp = SLCTAC_WDR(host->ncfg->wdr_clks) | + SLCTAC_WWIDTH(clkrate, host->ncfg->wwidth) | + SLCTAC_WHOLD(clkrate, host->ncfg->whold) | + SLCTAC_WSETUP(clkrate, host->ncfg->wsetup) | + SLCTAC_RDR(host->ncfg->rdr_clks) | + SLCTAC_RWIDTH(clkrate, host->ncfg->rwidth) | + SLCTAC_RHOLD(clkrate, host->ncfg->rhold) | + SLCTAC_RSETUP(clkrate, host->ncfg->rsetup); + writel(tmp, SLC_TAC(host->io_base)); +} + +/* + * Hardware specific access to control lines + */ +static void lpc32xx_nand_cmd_ctrl(struct nand_chip *chip, int cmd, + unsigned int ctrl) +{ + uint32_t tmp; + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + + /* Does CE state need to be changed? */ + tmp = readl(SLC_CFG(host->io_base)); + if (ctrl & NAND_NCE) + tmp |= SLCCFG_CE_LOW; + else + tmp &= ~SLCCFG_CE_LOW; + writel(tmp, SLC_CFG(host->io_base)); + + if (cmd != NAND_CMD_NONE) { + if (ctrl & NAND_CLE) + writel(cmd, SLC_CMD(host->io_base)); + else + writel(cmd, SLC_ADDR(host->io_base)); + } +} + +/* + * Read the Device Ready pin + */ +static int lpc32xx_nand_device_ready(struct nand_chip *chip) +{ + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + int rdy = 0; + + if ((readl(SLC_STAT(host->io_base)) & SLCSTAT_NAND_READY) != 0) + rdy = 1; + + return rdy; +} + +/* + * Enable NAND write protect + */ +static void lpc32xx_wp_enable(struct lpc32xx_nand_host *host) +{ + if (host->wp_gpio) + gpiod_set_value_cansleep(host->wp_gpio, 1); +} + +/* + * Disable NAND write protect + */ +static void lpc32xx_wp_disable(struct lpc32xx_nand_host *host) +{ + if (host->wp_gpio) + gpiod_set_value_cansleep(host->wp_gpio, 0); +} + +/* + * Prepares SLC for transfers with H/W ECC enabled + */ +static void lpc32xx_nand_ecc_enable(struct nand_chip *chip, int mode) +{ + /* Hardware ECC is enabled automatically in hardware as needed */ +} + +/* + * Calculates the ECC for the data + */ +static int lpc32xx_nand_ecc_calculate(struct nand_chip *chip, + const unsigned char *buf, + unsigned char *code) +{ + /* + * ECC is calculated automatically in hardware during syndrome read + * and write operations, so it doesn't need to be calculated here. + */ + return 0; +} + +/* + * Read a single byte from NAND device + */ +static uint8_t lpc32xx_nand_read_byte(struct nand_chip *chip) +{ + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + + return (uint8_t)readl(SLC_DATA(host->io_base)); +} + +/* + * Simple device read without ECC + */ +static void lpc32xx_nand_read_buf(struct nand_chip *chip, u_char *buf, int len) +{ + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + + /* Direct device read with no ECC */ + while (len-- > 0) + *buf++ = (uint8_t)readl(SLC_DATA(host->io_base)); +} + +/* + * Simple device write without ECC + */ +static void lpc32xx_nand_write_buf(struct nand_chip *chip, const uint8_t *buf, + int len) +{ + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + + /* Direct device write with no ECC */ + while (len-- > 0) + writel((uint32_t)*buf++, SLC_DATA(host->io_base)); +} + +/* + * Read the OOB data from the device without ECC using FIFO method + */ +static int lpc32xx_nand_read_oob_syndrome(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize); +} + +/* + * Write the OOB data to the device without ECC using FIFO method + */ +static int lpc32xx_nand_write_oob_syndrome(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + return nand_prog_page_op(chip, page, mtd->writesize, chip->oob_poi, + mtd->oobsize); +} + +/* + * Fills in the ECC fields in the OOB buffer with the hardware generated ECC + */ +static void lpc32xx_slc_ecc_copy(uint8_t *spare, const uint32_t *ecc, int count) +{ + int i; + + for (i = 0; i < (count * 3); i += 3) { + uint32_t ce = ecc[i / 3]; + ce = ~(ce << 2) & 0xFFFFFF; + spare[i + 2] = (uint8_t)(ce & 0xFF); + ce >>= 8; + spare[i + 1] = (uint8_t)(ce & 0xFF); + ce >>= 8; + spare[i] = (uint8_t)(ce & 0xFF); + } +} + +static void lpc32xx_dma_complete_func(void *completion) +{ + complete(completion); +} + +static int lpc32xx_xmit_dma(struct mtd_info *mtd, dma_addr_t dma, + void *mem, int len, enum dma_transfer_direction dir) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + 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; + host->dma_slave_config.dst_addr = dma; + host->dma_slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + host->dma_slave_config.src_maxburst = 4; + host->dma_slave_config.dst_maxburst = 4; + /* DMA controller does flow control: */ + host->dma_slave_config.device_fc = false; + if (dmaengine_slave_config(host->dma_chan, &host->dma_slave_config)) { + dev_err(mtd->dev.parent, "Failed to setup DMA slave\n"); + return -ENXIO; + } + + sg_init_one(&host->sgl, mem, len); + + res = dma_map_sg(host->dma_chan->device->dev, &host->sgl, 1, + DMA_BIDIRECTIONAL); + if (res != 1) { + dev_err(mtd->dev.parent, "Failed to map sg list\n"); + return -ENXIO; + } + desc = dmaengine_prep_slave_sg(host->dma_chan, &host->sgl, 1, dir, + flags); + if (!desc) { + dev_err(mtd->dev.parent, "Failed to prepare slave sg\n"); + goto out1; + } + + init_completion(&host->comp); + desc->callback = lpc32xx_dma_complete_func; + desc->callback_param = &host->comp; + + dmaengine_submit(desc); + dma_async_issue_pending(host->dma_chan); + + 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 res; +out1: + dma_unmap_sg(host->dma_chan->device->dev, &host->sgl, 1, + DMA_BIDIRECTIONAL); + return -ENXIO; +} + +/* + * DMA read/write transfers with ECC support + */ +static int lpc32xx_xfer(struct mtd_info *mtd, uint8_t *buf, int eccsubpages, + int read) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + int i, status = 0; + unsigned long timeout; + int res; + enum dma_transfer_direction dir = + read ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV; + uint8_t *dma_buf; + bool dma_mapped; + + if ((void *)buf <= high_memory) { + dma_buf = buf; + dma_mapped = true; + } else { + dma_buf = host->data_buf; + dma_mapped = false; + if (!read) + memcpy(host->data_buf, buf, mtd->writesize); + } + + if (read) { + writel(readl(SLC_CFG(host->io_base)) | + SLCCFG_DMA_DIR | SLCCFG_ECC_EN | SLCCFG_DMA_ECC | + SLCCFG_DMA_BURST, SLC_CFG(host->io_base)); + } else { + writel((readl(SLC_CFG(host->io_base)) | + SLCCFG_ECC_EN | SLCCFG_DMA_ECC | SLCCFG_DMA_BURST) & + ~SLCCFG_DMA_DIR, + SLC_CFG(host->io_base)); + } + + /* Clear initial ECC */ + writel(SLCCTRL_ECC_CLEAR, SLC_CTRL(host->io_base)); + + /* Transfer size is data area only */ + writel(mtd->writesize, SLC_TC(host->io_base)); + + /* Start transfer in the NAND controller */ + writel(readl(SLC_CTRL(host->io_base)) | SLCCTRL_DMA_START, + SLC_CTRL(host->io_base)); + + for (i = 0; i < chip->ecc.steps; i++) { + /* Data */ + res = lpc32xx_xmit_dma(mtd, SLC_DMA_DATA(host->io_base_dma), + dma_buf + i * chip->ecc.size, + mtd->writesize / chip->ecc.steps, dir); + if (res) + return res; + + /* Always _read_ ECC */ + if (i == chip->ecc.steps - 1) + break; + if (!read) /* ECC availability delayed on write */ + udelay(10); + res = lpc32xx_xmit_dma(mtd, SLC_ECC(host->io_base_dma), + &host->ecc_buf[i], 4, DMA_DEV_TO_MEM); + if (res) + return res; + } + + /* + * According to NXP, the DMA can be finished here, but the NAND + * controller may still have buffered data. After porting to using the + * dmaengine DMA driver (amba-pl080), the condition (DMA_FIFO empty) + * appears to be always true, according to tests. Keeping the check for + * safety reasons for now. + */ + if (readl(SLC_STAT(host->io_base)) & SLCSTAT_DMA_FIFO) { + dev_warn(mtd->dev.parent, "FIFO not empty!\n"); + timeout = jiffies + msecs_to_jiffies(LPC32XX_DMA_TIMEOUT); + while ((readl(SLC_STAT(host->io_base)) & SLCSTAT_DMA_FIFO) && + time_before(jiffies, timeout)) + cpu_relax(); + if (!time_before(jiffies, timeout)) { + dev_err(mtd->dev.parent, "FIFO held data too long\n"); + status = -EIO; + } + } + + /* Read last calculated ECC value */ + if (!read) + udelay(10); + host->ecc_buf[chip->ecc.steps - 1] = + readl(SLC_ECC(host->io_base)); + + /* Flush DMA */ + dmaengine_terminate_all(host->dma_chan); + + if (readl(SLC_STAT(host->io_base)) & SLCSTAT_DMA_FIFO || + readl(SLC_TC(host->io_base))) { + /* Something is left in the FIFO, something is wrong */ + dev_err(mtd->dev.parent, "DMA FIFO failure\n"); + status = -EIO; + } + + /* Stop DMA & HW ECC */ + writel(readl(SLC_CTRL(host->io_base)) & ~SLCCTRL_DMA_START, + SLC_CTRL(host->io_base)); + writel(readl(SLC_CFG(host->io_base)) & + ~(SLCCFG_DMA_DIR | SLCCFG_ECC_EN | SLCCFG_DMA_ECC | + SLCCFG_DMA_BURST), SLC_CFG(host->io_base)); + + if (!dma_mapped && read) + memcpy(buf, host->data_buf, mtd->writesize); + + return status; +} + +/* + * Read the data and OOB data from the device, use ECC correction with the + * data, disable ECC for the OOB data + */ +static int lpc32xx_nand_read_page_syndrome(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + struct mtd_oob_region oobregion = { }; + int stat, i, status, error; + uint8_t *oobecc, tmpecc[LPC32XX_ECC_SAVE_SIZE]; + + /* Issue read command */ + nand_read_page_op(chip, page, 0, NULL, 0); + + /* Read data and oob, calculate ECC */ + status = lpc32xx_xfer(mtd, buf, chip->ecc.steps, 1); + + /* Get OOB data */ + chip->legacy.read_buf(chip, chip->oob_poi, mtd->oobsize); + + /* Convert to stored ECC format */ + lpc32xx_slc_ecc_copy(tmpecc, (uint32_t *) host->ecc_buf, chip->ecc.steps); + + /* Pointer to ECC data retrieved from NAND spare area */ + error = mtd_ooblayout_ecc(mtd, 0, &oobregion); + if (error) + return error; + + oobecc = chip->oob_poi + oobregion.offset; + + for (i = 0; i < chip->ecc.steps; i++) { + stat = chip->ecc.correct(chip, buf, oobecc, + &tmpecc[i * chip->ecc.bytes]); + if (stat < 0) + mtd->ecc_stats.failed++; + else + mtd->ecc_stats.corrected += stat; + + buf += chip->ecc.size; + oobecc += chip->ecc.bytes; + } + + return status; +} + +/* + * Read the data and OOB data from the device, no ECC correction with the + * data or OOB data + */ +static int lpc32xx_nand_read_page_raw_syndrome(struct nand_chip *chip, + uint8_t *buf, int oob_required, + int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + /* Issue read command */ + nand_read_page_op(chip, page, 0, NULL, 0); + + /* Raw reads can just use the FIFO interface */ + chip->legacy.read_buf(chip, buf, chip->ecc.size * chip->ecc.steps); + chip->legacy.read_buf(chip, chip->oob_poi, mtd->oobsize); + + return 0; +} + +/* + * Write the data and OOB data to the device, use ECC with the data, + * disable ECC for the OOB data + */ +static int lpc32xx_nand_write_page_syndrome(struct nand_chip *chip, + const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + struct mtd_oob_region oobregion = { }; + uint8_t *pb; + int error; + + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + + /* Write data, calculate ECC on outbound data */ + error = lpc32xx_xfer(mtd, (uint8_t *)buf, chip->ecc.steps, 0); + if (error) + return error; + + /* + * The calculated ECC needs some manual work done to it before + * committing it to NAND. Process the calculated ECC and place + * the resultant values directly into the OOB buffer. */ + error = mtd_ooblayout_ecc(mtd, 0, &oobregion); + if (error) + return error; + + pb = chip->oob_poi + oobregion.offset; + lpc32xx_slc_ecc_copy(pb, (uint32_t *)host->ecc_buf, chip->ecc.steps); + + /* Write ECC data to device */ + chip->legacy.write_buf(chip, chip->oob_poi, mtd->oobsize); + + return nand_prog_page_end_op(chip); +} + +/* + * Write the data and OOB data to the device, no ECC correction with the + * data or OOB data + */ +static int lpc32xx_nand_write_page_raw_syndrome(struct nand_chip *chip, + const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + /* Raw writes can just use the FIFO interface */ + nand_prog_page_begin_op(chip, page, 0, buf, + chip->ecc.size * chip->ecc.steps); + chip->legacy.write_buf(chip, chip->oob_poi, mtd->oobsize); + + return nand_prog_page_end_op(chip); +} + +static int lpc32xx_nand_dma_setup(struct lpc32xx_nand_host *host) +{ + struct mtd_info *mtd = nand_to_mtd(&host->nand_chip); + dma_cap_mask_t mask; + + host->dma_chan = dma_request_chan(mtd->dev.parent, "rx-tx"); + if (IS_ERR(host->dma_chan)) { + /* fallback to request using platform data */ + if (!host->pdata || !host->pdata->dma_filter) { + dev_err(mtd->dev.parent, "no DMA platform data\n"); + return -ENOENT; + } + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + host->dma_chan = dma_request_channel(mask, host->pdata->dma_filter, "nand-slc"); + + if (!host->dma_chan) { + dev_err(mtd->dev.parent, "Failed to request DMA channel\n"); + return -EBUSY; + } + } + + return 0; +} + +static struct lpc32xx_nand_cfg_slc *lpc32xx_parse_dt(struct device *dev) +{ + struct lpc32xx_nand_cfg_slc *ncfg; + struct device_node *np = dev->of_node; + + ncfg = devm_kzalloc(dev, sizeof(*ncfg), GFP_KERNEL); + if (!ncfg) + return NULL; + + of_property_read_u32(np, "nxp,wdr-clks", &ncfg->wdr_clks); + of_property_read_u32(np, "nxp,wwidth", &ncfg->wwidth); + of_property_read_u32(np, "nxp,whold", &ncfg->whold); + of_property_read_u32(np, "nxp,wsetup", &ncfg->wsetup); + of_property_read_u32(np, "nxp,rdr-clks", &ncfg->rdr_clks); + of_property_read_u32(np, "nxp,rwidth", &ncfg->rwidth); + of_property_read_u32(np, "nxp,rhold", &ncfg->rhold); + of_property_read_u32(np, "nxp,rsetup", &ncfg->rsetup); + + if (!ncfg->wdr_clks || !ncfg->wwidth || !ncfg->whold || + !ncfg->wsetup || !ncfg->rdr_clks || !ncfg->rwidth || + !ncfg->rhold || !ncfg->rsetup) { + dev_err(dev, "chip parameters not specified correctly\n"); + return NULL; + } + + return ncfg; +} + +static int lpc32xx_nand_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct lpc32xx_nand_host *host = nand_get_controller_data(chip); + + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return 0; + + /* OOB and ECC CPU and DMA work areas */ + host->ecc_buf = (uint32_t *)(host->data_buf + LPC32XX_DMA_DATA_SIZE); + + /* + * Small page FLASH has a unique OOB layout, but large and huge + * page FLASH use the standard layout. Small page FLASH uses a + * custom BBT marker layout. + */ + if (mtd->writesize <= 512) + mtd_set_ooblayout(mtd, &lpc32xx_ooblayout_ops); + + chip->ecc.placement = NAND_ECC_PLACEMENT_INTERLEAVED; + /* These sizes remain the same regardless of page size */ + chip->ecc.size = 256; + chip->ecc.strength = 1; + chip->ecc.bytes = LPC32XX_SLC_DEV_ECC_BYTES; + chip->ecc.prepad = 0; + chip->ecc.postpad = 0; + chip->ecc.read_page_raw = lpc32xx_nand_read_page_raw_syndrome; + chip->ecc.read_page = lpc32xx_nand_read_page_syndrome; + chip->ecc.write_page_raw = lpc32xx_nand_write_page_raw_syndrome; + chip->ecc.write_page = lpc32xx_nand_write_page_syndrome; + chip->ecc.write_oob = lpc32xx_nand_write_oob_syndrome; + chip->ecc.read_oob = lpc32xx_nand_read_oob_syndrome; + chip->ecc.calculate = lpc32xx_nand_ecc_calculate; + chip->ecc.correct = rawnand_sw_hamming_correct; + chip->ecc.hwctl = lpc32xx_nand_ecc_enable; + + /* + * Use a custom BBT marker setup for small page FLASH that + * won't interfere with the ECC layout. Large and huge page + * FLASH use the standard layout. + */ + if ((chip->bbt_options & NAND_BBT_USE_FLASH) && + mtd->writesize <= 512) { + chip->bbt_td = &bbt_smallpage_main_descr; + chip->bbt_md = &bbt_smallpage_mirror_descr; + } + + return 0; +} + +static const struct nand_controller_ops lpc32xx_nand_controller_ops = { + .attach_chip = lpc32xx_nand_attach_chip, +}; + +/* + * Probe for NAND controller + */ +static int lpc32xx_nand_probe(struct platform_device *pdev) +{ + struct lpc32xx_nand_host *host; + struct mtd_info *mtd; + struct nand_chip *chip; + struct resource *rc; + int res; + + /* Allocate memory for the device structure (and zero it) */ + host = devm_kzalloc(&pdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + host->io_base = devm_platform_get_and_ioremap_resource(pdev, 0, &rc); + if (IS_ERR(host->io_base)) + return PTR_ERR(host->io_base); + + host->io_base_dma = rc->start; + if (pdev->dev.of_node) + host->ncfg = lpc32xx_parse_dt(&pdev->dev); + if (!host->ncfg) { + dev_err(&pdev->dev, + "Missing or bad NAND config from device tree\n"); + return -ENOENT; + } + + /* Start with WP disabled, if available */ + 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) + dev_err(&pdev->dev, "WP GPIO is not available: %d\n", + res); + return res; + } + + gpiod_set_consumer_name(host->wp_gpio, "NAND WP"); + + host->pdata = dev_get_platdata(&pdev->dev); + + chip = &host->nand_chip; + mtd = nand_to_mtd(chip); + nand_set_controller_data(chip, host); + nand_set_flash_node(chip, pdev->dev.of_node); + mtd->owner = THIS_MODULE; + mtd->dev.parent = &pdev->dev; + + /* Get NAND clock */ + host->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(host->clk)) { + dev_err(&pdev->dev, "Clock failure\n"); + res = -ENOENT; + goto enable_wp; + } + + /* Set NAND IO addresses and command/ready functions */ + chip->legacy.IO_ADDR_R = SLC_DATA(host->io_base); + chip->legacy.IO_ADDR_W = SLC_DATA(host->io_base); + chip->legacy.cmd_ctrl = lpc32xx_nand_cmd_ctrl; + chip->legacy.dev_ready = lpc32xx_nand_device_ready; + chip->legacy.chip_delay = 20; /* 20us command delay time */ + + /* Init NAND controller */ + lpc32xx_nand_setup(host); + + platform_set_drvdata(pdev, host); + + /* NAND callbacks for LPC32xx SLC hardware */ + chip->legacy.read_byte = lpc32xx_nand_read_byte; + chip->legacy.read_buf = lpc32xx_nand_read_buf; + chip->legacy.write_buf = lpc32xx_nand_write_buf; + + /* + * Allocate a large enough buffer for a single huge page plus + * extra space for the spare area and ECC storage area + */ + host->dma_buf_len = LPC32XX_DMA_DATA_SIZE + LPC32XX_ECC_SAVE_SIZE; + host->data_buf = devm_kzalloc(&pdev->dev, host->dma_buf_len, + GFP_KERNEL); + if (host->data_buf == NULL) { + res = -ENOMEM; + goto enable_wp; + } + + res = lpc32xx_nand_dma_setup(host); + if (res) { + res = -EIO; + goto enable_wp; + } + + /* Find NAND device */ + chip->legacy.dummy_controller.ops = &lpc32xx_nand_controller_ops; + res = nand_scan(chip, 1); + if (res) + goto release_dma; + + mtd->name = "nxp_lpc3220_slc"; + res = mtd_device_register(mtd, host->ncfg->parts, + host->ncfg->num_parts); + if (res) + goto cleanup_nand; + + return 0; + +cleanup_nand: + nand_cleanup(chip); +release_dma: + dma_release_channel(host->dma_chan); +enable_wp: + lpc32xx_wp_enable(host); + + return res; +} + +/* + * Remove NAND device. + */ +static void lpc32xx_nand_remove(struct platform_device *pdev) +{ + uint32_t tmp; + struct lpc32xx_nand_host *host = platform_get_drvdata(pdev); + struct nand_chip *chip = &host->nand_chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + dma_release_channel(host->dma_chan); + + /* Force CE high */ + tmp = readl(SLC_CTRL(host->io_base)); + tmp &= ~SLCCFG_CE_LOW; + writel(tmp, SLC_CTRL(host->io_base)); + + lpc32xx_wp_enable(host); +} + +static int lpc32xx_nand_resume(struct platform_device *pdev) +{ + struct lpc32xx_nand_host *host = platform_get_drvdata(pdev); + int ret; + + /* Re-enable NAND clock */ + ret = clk_prepare_enable(host->clk); + if (ret) + return ret; + + /* Fresh init of NAND controller */ + lpc32xx_nand_setup(host); + + /* Disable write protect */ + lpc32xx_wp_disable(host); + + return 0; +} + +static int lpc32xx_nand_suspend(struct platform_device *pdev, pm_message_t pm) +{ + uint32_t tmp; + struct lpc32xx_nand_host *host = platform_get_drvdata(pdev); + + /* Force CE high */ + tmp = readl(SLC_CTRL(host->io_base)); + tmp &= ~SLCCFG_CE_LOW; + writel(tmp, SLC_CTRL(host->io_base)); + + /* Enable write protect for safety */ + lpc32xx_wp_enable(host); + + /* Disable clock */ + clk_disable_unprepare(host->clk); + + return 0; +} + +static const struct of_device_id lpc32xx_nand_match[] = { + { .compatible = "nxp,lpc3220-slc" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, lpc32xx_nand_match); + +static struct platform_driver lpc32xx_nand_driver = { + .probe = lpc32xx_nand_probe, + .remove = lpc32xx_nand_remove, + .resume = pm_ptr(lpc32xx_nand_resume), + .suspend = pm_ptr(lpc32xx_nand_suspend), + .driver = { + .name = LPC32XX_MODNAME, + .of_match_table = lpc32xx_nand_match, + }, +}; + +module_platform_driver(lpc32xx_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kevin Wells "); +MODULE_AUTHOR("Roland Stigge "); +MODULE_DESCRIPTION("NAND driver for the NXP LPC32XX SLC controller"); diff --git a/drivers/mtd/nand/raw/marvell_nand.c b/drivers/mtd/nand/raw/marvell_nand.c new file mode 100644 index 000000000..38b7eb5b9 --- /dev/null +++ b/drivers/mtd/nand/raw/marvell_nand.c @@ -0,0 +1,3194 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Marvell NAND flash controller driver + * + * Copyright (C) 2017 Marvell + * Author: Miquel RAYNAL + * + * + * This NAND controller driver handles two versions of the hardware, + * one is called NFCv1 and is available on PXA SoCs and the other is + * called NFCv2 and is available on Armada SoCs. + * + * The main visible difference is that NFCv1 only has Hamming ECC + * capabilities, while NFCv2 also embeds a BCH ECC engine. Also, DMA + * is not used with NFCv2. + * + * The ECC layouts are depicted in details in Marvell AN-379, but here + * is a brief description. + * + * When using Hamming, the data is split in 512B chunks (either 1, 2 + * or 4) and each chunk will have its own ECC "digest" of 6B at the + * beginning of the OOB area and eventually the remaining free OOB + * bytes (also called "spare" bytes in the driver). This engine + * corrects up to 1 bit per chunk and detects reliably an error if + * there are at most 2 bitflips. Here is the page layout used by the + * controller when Hamming is chosen: + * + * +-------------------------------------------------------------+ + * | Data 1 | ... | Data N | ECC 1 | ... | ECCN | Free OOB bytes | + * +-------------------------------------------------------------+ + * + * When using the BCH engine, there are N identical (data + free OOB + + * ECC) sections and potentially an extra one to deal with + * configurations where the chosen (data + free OOB + ECC) sizes do + * not align with the page (data + OOB) size. ECC bytes are always + * 30B per ECC chunk. Here is the page layout used by the controller + * when BCH is chosen: + * + * +----------------------------------------- + * | Data 1 | Free OOB bytes 1 | ECC 1 | ... + * +----------------------------------------- + * + * ------------------------------------------- + * ... | Data N | Free OOB bytes N | ECC N | + * ------------------------------------------- + * + * --------------------------------------------+ + * Last Data | Last Free OOB bytes | Last ECC | + * --------------------------------------------+ + * + * In both cases, the layout seen by the user is always: all data + * first, then all free OOB bytes and finally all ECC bytes. With BCH, + * ECC bytes are 30B long and are padded with 0xFF to align on 32 + * bytes. + * + * The controller has certain limitations that are handled by the + * driver: + * - It can only read 2k at a time. To overcome this limitation, the + * driver issues data cycles on the bus, without issuing new + * CMD + ADDR cycles. The Marvell term is "naked" operations. + * - The ECC strength in BCH mode cannot be tuned. It is fixed 16 + * bits. What can be tuned is the ECC block size as long as it + * stays between 512B and 2kiB. It's usually chosen based on the + * chip ECC requirements. For instance, using 2kiB ECC chunks + * provides 4b/512B correctability. + * - The controller will always treat data bytes, free OOB bytes + * and ECC bytes in that order, no matter what the real layout is + * (which is usually all data then all OOB bytes). The + * marvell_nfc_layouts array below contains the currently + * supported layouts. + * - Because of these weird layouts, the Bad Block Markers can be + * located in data section. In this case, the NAND_BBT_NO_OOB_BBM + * option must be set to prevent scanning/writing bad block + * markers. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Data FIFO granularity, FIFO reads/writes must be a multiple of this length */ +#define FIFO_DEPTH 8 +#define FIFO_REP(x) (x / sizeof(u32)) +#define BCH_SEQ_READS (32 / FIFO_DEPTH) +/* NFC does not support transfers of larger chunks at a time */ +#define MAX_CHUNK_SIZE 2112 +/* NFCv1 cannot read more that 7 bytes of ID */ +#define NFCV1_READID_LEN 7 +/* Polling is done at a pace of POLL_PERIOD us until POLL_TIMEOUT is reached */ +#define POLL_PERIOD 0 +#define POLL_TIMEOUT 100000 +/* Interrupt maximum wait period in ms */ +#define IRQ_TIMEOUT 1000 +/* Latency in clock cycles between SoC pins and NFC logic */ +#define MIN_RD_DEL_CNT 3 +/* Maximum number of contiguous address cycles */ +#define MAX_ADDRESS_CYC_NFCV1 5 +#define MAX_ADDRESS_CYC_NFCV2 7 +/* System control registers/bits to enable the NAND controller on some SoCs */ +#define GENCONF_SOC_DEVICE_MUX 0x208 +#define GENCONF_SOC_DEVICE_MUX_NFC_EN BIT(0) +#define GENCONF_SOC_DEVICE_MUX_ECC_CLK_RST BIT(20) +#define GENCONF_SOC_DEVICE_MUX_ECC_CORE_RST BIT(21) +#define GENCONF_SOC_DEVICE_MUX_NFC_INT_EN BIT(25) +#define GENCONF_SOC_DEVICE_MUX_NFC_DEVBUS_ARB_EN BIT(27) +#define GENCONF_CLK_GATING_CTRL 0x220 +#define GENCONF_CLK_GATING_CTRL_ND_GATE BIT(2) +#define GENCONF_ND_CLK_CTRL 0x700 +#define GENCONF_ND_CLK_CTRL_EN BIT(0) + +/* NAND controller data flash control register */ +#define NDCR 0x00 +#define NDCR_ALL_INT GENMASK(11, 0) +#define NDCR_CS1_CMDDM BIT(7) +#define NDCR_CS0_CMDDM BIT(8) +#define NDCR_RDYM BIT(11) +#define NDCR_ND_ARB_EN BIT(12) +#define NDCR_RA_START BIT(15) +#define NDCR_RD_ID_CNT(x) (min_t(unsigned int, x, 0x7) << 16) +#define NDCR_PAGE_SZ(x) (x >= 2048 ? BIT(24) : 0) +#define NDCR_DWIDTH_M BIT(26) +#define NDCR_DWIDTH_C BIT(27) +#define NDCR_ND_RUN BIT(28) +#define NDCR_DMA_EN BIT(29) +#define NDCR_ECC_EN BIT(30) +#define NDCR_SPARE_EN BIT(31) +#define NDCR_GENERIC_FIELDS_MASK (~(NDCR_RA_START | NDCR_PAGE_SZ(2048) | \ + NDCR_DWIDTH_M | NDCR_DWIDTH_C)) + +/* NAND interface timing parameter 0 register */ +#define NDTR0 0x04 +#define NDTR0_TRP(x) ((min_t(unsigned int, x, 0xF) & 0x7) << 0) +#define NDTR0_TRH(x) (min_t(unsigned int, x, 0x7) << 3) +#define NDTR0_ETRP(x) ((min_t(unsigned int, x, 0xF) & 0x8) << 3) +#define NDTR0_SEL_NRE_EDGE BIT(7) +#define NDTR0_TWP(x) (min_t(unsigned int, x, 0x7) << 8) +#define NDTR0_TWH(x) (min_t(unsigned int, x, 0x7) << 11) +#define NDTR0_TCS(x) (min_t(unsigned int, x, 0x7) << 16) +#define NDTR0_TCH(x) (min_t(unsigned int, x, 0x7) << 19) +#define NDTR0_RD_CNT_DEL(x) (min_t(unsigned int, x, 0xF) << 22) +#define NDTR0_SELCNTR BIT(26) +#define NDTR0_TADL(x) (min_t(unsigned int, x, 0x1F) << 27) + +/* NAND interface timing parameter 1 register */ +#define NDTR1 0x0C +#define NDTR1_TAR(x) (min_t(unsigned int, x, 0xF) << 0) +#define NDTR1_TWHR(x) (min_t(unsigned int, x, 0xF) << 4) +#define NDTR1_TRHW(x) (min_t(unsigned int, x / 16, 0x3) << 8) +#define NDTR1_PRESCALE BIT(14) +#define NDTR1_WAIT_MODE BIT(15) +#define NDTR1_TR(x) (min_t(unsigned int, x, 0xFFFF) << 16) + +/* NAND controller status register */ +#define NDSR 0x14 +#define NDSR_WRCMDREQ BIT(0) +#define NDSR_RDDREQ BIT(1) +#define NDSR_WRDREQ BIT(2) +#define NDSR_CORERR BIT(3) +#define NDSR_UNCERR BIT(4) +#define NDSR_CMDD(cs) BIT(8 - cs) +#define NDSR_RDY(rb) BIT(11 + rb) +#define NDSR_ERRCNT(x) ((x >> 16) & 0x1F) + +/* NAND ECC control register */ +#define NDECCCTRL 0x28 +#define NDECCCTRL_BCH_EN BIT(0) + +/* NAND controller data buffer register */ +#define NDDB 0x40 + +/* NAND controller command buffer 0 register */ +#define NDCB0 0x48 +#define NDCB0_CMD1(x) ((x & 0xFF) << 0) +#define NDCB0_CMD2(x) ((x & 0xFF) << 8) +#define NDCB0_ADDR_CYC(x) ((x & 0x7) << 16) +#define NDCB0_ADDR_GET_NUM_CYC(x) (((x) >> 16) & 0x7) +#define NDCB0_DBC BIT(19) +#define NDCB0_CMD_TYPE(x) ((x & 0x7) << 21) +#define NDCB0_CSEL BIT(24) +#define NDCB0_RDY_BYP BIT(27) +#define NDCB0_LEN_OVRD BIT(28) +#define NDCB0_CMD_XTYPE(x) ((x & 0x7) << 29) + +/* NAND controller command buffer 1 register */ +#define NDCB1 0x4C +#define NDCB1_COLS(x) ((x & 0xFFFF) << 0) +#define NDCB1_ADDRS_PAGE(x) (x << 16) + +/* NAND controller command buffer 2 register */ +#define NDCB2 0x50 +#define NDCB2_ADDR5_PAGE(x) (((x >> 16) & 0xFF) << 0) +#define NDCB2_ADDR5_CYC(x) ((x & 0xFF) << 0) + +/* NAND controller command buffer 3 register */ +#define NDCB3 0x54 +#define NDCB3_ADDR6_CYC(x) ((x & 0xFF) << 16) +#define NDCB3_ADDR7_CYC(x) ((x & 0xFF) << 24) + +/* NAND controller command buffer 0 register 'type' and 'xtype' fields */ +#define TYPE_READ 0 +#define TYPE_WRITE 1 +#define TYPE_ERASE 2 +#define TYPE_READ_ID 3 +#define TYPE_STATUS 4 +#define TYPE_RESET 5 +#define TYPE_NAKED_CMD 6 +#define TYPE_NAKED_ADDR 7 +#define TYPE_MASK 7 +#define XTYPE_MONOLITHIC_RW 0 +#define XTYPE_LAST_NAKED_RW 1 +#define XTYPE_FINAL_COMMAND 3 +#define XTYPE_READ 4 +#define XTYPE_WRITE_DISPATCH 4 +#define XTYPE_NAKED_RW 5 +#define XTYPE_COMMAND_DISPATCH 6 +#define XTYPE_MASK 7 + +/** + * struct marvell_hw_ecc_layout - layout of Marvell ECC + * + * Marvell ECC engine works differently than the others, in order to limit the + * size of the IP, hardware engineers chose to set a fixed strength at 16 bits + * per subpage, and depending on a the desired strength needed by the NAND chip, + * a particular layout mixing data/spare/ecc is defined, with a possible last + * chunk smaller that the others. + * + * @writesize: Full page size on which the layout applies + * @chunk: Desired ECC chunk size on which the layout applies + * @strength: Desired ECC strength (per chunk size bytes) on which the + * layout applies + * @nchunks: Total number of chunks + * @full_chunk_cnt: Number of full-sized chunks, which is the number of + * repetitions of the pattern: + * (data_bytes + spare_bytes + ecc_bytes). + * @data_bytes: Number of data bytes per chunk + * @spare_bytes: Number of spare bytes per chunk + * @ecc_bytes: Number of ecc bytes per chunk + * @last_data_bytes: Number of data bytes in the last chunk + * @last_spare_bytes: Number of spare bytes in the last chunk + * @last_ecc_bytes: Number of ecc bytes in the last chunk + */ +struct marvell_hw_ecc_layout { + /* Constraints */ + int writesize; + int chunk; + int strength; + /* Corresponding layout */ + int nchunks; + int full_chunk_cnt; + int data_bytes; + int spare_bytes; + int ecc_bytes; + int last_data_bytes; + int last_spare_bytes; + int last_ecc_bytes; +}; + +#define MARVELL_LAYOUT(ws, dc, ds, nc, fcc, db, sb, eb, ldb, lsb, leb) \ + { \ + .writesize = ws, \ + .chunk = dc, \ + .strength = ds, \ + .nchunks = nc, \ + .full_chunk_cnt = fcc, \ + .data_bytes = db, \ + .spare_bytes = sb, \ + .ecc_bytes = eb, \ + .last_data_bytes = ldb, \ + .last_spare_bytes = lsb, \ + .last_ecc_bytes = leb, \ + } + +/* Layouts explained in AN-379_Marvell_SoC_NFC_ECC */ +static const struct marvell_hw_ecc_layout marvell_nfc_layouts[] = { + MARVELL_LAYOUT( 512, 512, 1, 1, 1, 512, 8, 8, 0, 0, 0), + MARVELL_LAYOUT( 2048, 512, 1, 1, 1, 2048, 40, 24, 0, 0, 0), + 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, 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, 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, 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), +}; + +/** + * struct marvell_nand_chip_sel - CS line description + * + * The Nand Flash Controller has up to 4 CE and 2 RB pins. The CE selection + * is made by a field in NDCB0 register, and in another field in NDCB2 register. + * The datasheet describes the logic with an error: ADDR5 field is once + * declared at the beginning of NDCB2, and another time at its end. Because the + * ADDR5 field of NDCB2 may be used by other bytes, it would be more logical + * to use the last bit of this field instead of the first ones. + * + * @cs: Wanted CE lane. + * @ndcb0_csel: Value of the NDCB0 register with or without the flag + * selecting the wanted CE lane. This is set once when + * the Device Tree is probed. + * @rb: Ready/Busy pin for the flash chip + */ +struct marvell_nand_chip_sel { + unsigned int cs; + u32 ndcb0_csel; + unsigned int rb; +}; + +/** + * struct marvell_nand_chip - stores NAND chip device related information + * + * @chip: Base NAND chip structure + * @node: Used to store NAND chips into a list + * @layout: NAND layout when using hardware ECC + * @ndcr: Controller register value for this NAND chip + * @ndtr0: Timing registers 0 value for this NAND chip + * @ndtr1: Timing registers 1 value for this NAND chip + * @addr_cyc: Amount of cycles needed to pass column address + * @selected_die: Current active CS + * @nsels: Number of CS lines required by the NAND chip + * @sels: Array of CS lines descriptions + */ +struct marvell_nand_chip { + struct nand_chip chip; + struct list_head node; + const struct marvell_hw_ecc_layout *layout; + u32 ndcr; + u32 ndtr0; + u32 ndtr1; + int addr_cyc; + int selected_die; + unsigned int nsels; + struct marvell_nand_chip_sel sels[] __counted_by(nsels); +}; + +static inline struct marvell_nand_chip *to_marvell_nand(struct nand_chip *chip) +{ + return container_of(chip, struct marvell_nand_chip, chip); +} + +static inline struct marvell_nand_chip_sel *to_nand_sel(struct marvell_nand_chip + *nand) +{ + return &nand->sels[nand->selected_die]; +} + +/** + * struct marvell_nfc_caps - NAND controller capabilities for distinction + * between compatible strings + * + * @max_cs_nb: Number of Chip Select lines available + * @max_rb_nb: Number of Ready/Busy lines available + * @need_system_controller: Indicates if the SoC needs to have access to the + * system controller (ie. to enable the NAND controller) + * @legacy_of_bindings: Indicates if DT parsing must be done using the old + * fashion way + * @is_nfcv2: NFCv2 has numerous enhancements compared to NFCv1, ie. + * BCH error detection and correction algorithm, + * NDCB3 register has been added + * @use_dma: Use dma for data transfers + * @max_mode_number: Maximum timing mode supported by the controller + */ +struct marvell_nfc_caps { + unsigned int max_cs_nb; + unsigned int max_rb_nb; + bool need_system_controller; + bool legacy_of_bindings; + bool is_nfcv2; + bool use_dma; + unsigned int max_mode_number; +}; + +/** + * struct marvell_nfc - stores Marvell NAND controller information + * + * @controller: Base controller structure + * @dev: Parent device (used to print error messages) + * @regs: NAND controller registers + * @core_clk: Core clock + * @reg_clk: Registers clock + * @complete: Completion object to wait for NAND controller events + * @assigned_cs: Bitmask describing already assigned CS lines + * @chips: List containing all the NAND chips attached to + * this NAND controller + * @selected_chip: Currently selected target chip + * @caps: NAND controller capabilities for each compatible string + * @use_dma: Whetner DMA is used + * @dma_chan: DMA channel (NFCv1 only) + * @dma_buf: 32-bit aligned buffer for DMA transfers (NFCv1 only) + */ +struct marvell_nfc { + struct nand_controller controller; + struct device *dev; + void __iomem *regs; + struct clk *core_clk; + struct clk *reg_clk; + struct completion complete; + unsigned long assigned_cs; + struct list_head chips; + struct nand_chip *selected_chip; + const struct marvell_nfc_caps *caps; + + /* DMA (NFCv1 only) */ + bool use_dma; + struct dma_chan *dma_chan; + u8 *dma_buf; +}; + +static inline struct marvell_nfc *to_marvell_nfc(struct nand_controller *ctrl) +{ + return container_of(ctrl, struct marvell_nfc, controller); +} + +/** + * struct marvell_nfc_timings - NAND controller timings expressed in NAND + * Controller clock cycles + * + * @tRP: ND_nRE pulse width + * @tRH: ND_nRE high duration + * @tWP: ND_nWE pulse time + * @tWH: ND_nWE high duration + * @tCS: Enable signal setup time + * @tCH: Enable signal hold time + * @tADL: Address to write data delay + * @tAR: ND_ALE low to ND_nRE low delay + * @tWHR: ND_nWE high to ND_nRE low for status read + * @tRHW: ND_nRE high duration, read to write delay + * @tR: ND_nWE high to ND_nRE low for read + */ +struct marvell_nfc_timings { + /* NDTR0 fields */ + unsigned int tRP; + unsigned int tRH; + unsigned int tWP; + unsigned int tWH; + unsigned int tCS; + unsigned int tCH; + unsigned int tADL; + /* NDTR1 fields */ + unsigned int tAR; + unsigned int tWHR; + unsigned int tRHW; + unsigned int tR; +}; + +/** + * TO_CYCLES() - Derives a duration in numbers of clock cycles. + * + * @ps: Duration in pico-seconds + * @period_ns: Clock period in nano-seconds + * + * Convert the duration in nano-seconds, then divide by the period and + * return the number of clock periods. + */ +#define TO_CYCLES(ps, period_ns) (DIV_ROUND_UP(ps / 1000, period_ns)) +#define TO_CYCLES64(ps, period_ns) (DIV_ROUND_UP_ULL(div_u64(ps, 1000), \ + period_ns)) + +/** + * struct marvell_nfc_op - filled during the parsing of the ->exec_op() + * subop subset of instructions. + * + * @ndcb: Array of values written to NDCBx registers + * @cle_ale_delay_ns: Optional delay after the last CMD or ADDR cycle + * @rdy_timeout_ms: Timeout for waits on Ready/Busy pin + * @rdy_delay_ns: Optional delay after waiting for the RB pin + * @data_delay_ns: Optional delay after the data xfer + * @data_instr_idx: Index of the data instruction in the subop + * @data_instr: Pointer to the data instruction in the subop + */ +struct marvell_nfc_op { + u32 ndcb[4]; + unsigned int cle_ale_delay_ns; + unsigned int rdy_timeout_ms; + unsigned int rdy_delay_ns; + unsigned int data_delay_ns; + unsigned int data_instr_idx; + const struct nand_op_instr *data_instr; +}; + +/* + * Internal helper to conditionnally apply a delay (from the above structure, + * most of the time). + */ +static void cond_delay(unsigned int ns) +{ + if (!ns) + return; + + if (ns < 10000) + ndelay(ns); + else + udelay(DIV_ROUND_UP(ns, 1000)); +} + +/* + * The controller has many flags that could generate interrupts, most of them + * are disabled and polling is used. For the very slow signals, using interrupts + * may relax the CPU charge. + */ +static void marvell_nfc_disable_int(struct marvell_nfc *nfc, u32 int_mask) +{ + u32 reg; + + /* Writing 1 disables the interrupt */ + reg = readl_relaxed(nfc->regs + NDCR); + writel_relaxed(reg | int_mask, nfc->regs + NDCR); +} + +static void marvell_nfc_enable_int(struct marvell_nfc *nfc, u32 int_mask) +{ + u32 reg; + + /* Writing 0 enables the interrupt */ + reg = readl_relaxed(nfc->regs + NDCR); + writel_relaxed(reg & ~int_mask, nfc->regs + NDCR); +} + +static u32 marvell_nfc_clear_int(struct marvell_nfc *nfc, u32 int_mask) +{ + u32 reg; + + reg = readl_relaxed(nfc->regs + NDSR); + writel_relaxed(int_mask, nfc->regs + NDSR); + + return reg & int_mask; +} + +static void marvell_nfc_force_byte_access(struct nand_chip *chip, + bool force_8bit) +{ + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + u32 ndcr; + + /* + * Callers of this function do not verify if the NAND is using a 16-bit + * an 8-bit bus for normal operations, so we need to take care of that + * here by leaving the configuration unchanged if the NAND does not have + * the NAND_BUSWIDTH_16 flag set. + */ + if (!(chip->options & NAND_BUSWIDTH_16)) + return; + + ndcr = readl_relaxed(nfc->regs + NDCR); + + if (force_8bit) + ndcr &= ~(NDCR_DWIDTH_M | NDCR_DWIDTH_C); + else + ndcr |= NDCR_DWIDTH_M | NDCR_DWIDTH_C; + + writel_relaxed(ndcr, nfc->regs + NDCR); +} + +static int marvell_nfc_wait_ndrun(struct nand_chip *chip) +{ + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + u32 val; + int ret; + + /* + * The command is being processed, wait for the ND_RUN bit to be + * cleared by the NFC. If not, we must clear it by hand. + */ + ret = readl_relaxed_poll_timeout(nfc->regs + NDCR, val, + (val & NDCR_ND_RUN) == 0, + POLL_PERIOD, POLL_TIMEOUT); + if (ret) { + dev_err(nfc->dev, "Timeout on NAND controller run mode\n"); + writel_relaxed(readl(nfc->regs + NDCR) & ~NDCR_ND_RUN, + nfc->regs + NDCR); + return ret; + } + + return 0; +} + +/* + * Any time a command has to be sent to the controller, the following sequence + * has to be followed: + * - call marvell_nfc_prepare_cmd() + * -> activate the ND_RUN bit that will kind of 'start a job' + * -> wait the signal indicating the NFC is waiting for a command + * - send the command (cmd and address cycles) + * - enventually send or receive the data + * - call marvell_nfc_end_cmd() with the corresponding flag + * -> wait the flag to be triggered or cancel the job with a timeout + * + * The following helpers are here to factorize the code a bit so that + * specialized functions responsible for executing the actual NAND + * operations do not have to replicate the same code blocks. + */ +static int marvell_nfc_prepare_cmd(struct nand_chip *chip) +{ + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + u32 ndcr, val; + int ret; + + /* Poll ND_RUN and clear NDSR before issuing any command */ + ret = marvell_nfc_wait_ndrun(chip); + if (ret) { + dev_err(nfc->dev, "Last operation did not succeed\n"); + return ret; + } + + ndcr = readl_relaxed(nfc->regs + NDCR); + writel_relaxed(readl(nfc->regs + NDSR), nfc->regs + NDSR); + + /* Assert ND_RUN bit and wait the NFC to be ready */ + writel_relaxed(ndcr | NDCR_ND_RUN, nfc->regs + NDCR); + ret = readl_relaxed_poll_timeout(nfc->regs + NDSR, val, + val & NDSR_WRCMDREQ, + POLL_PERIOD, POLL_TIMEOUT); + if (ret) { + dev_err(nfc->dev, "Timeout on WRCMDRE\n"); + return -ETIMEDOUT; + } + + /* Command may be written, clear WRCMDREQ status bit */ + writel_relaxed(NDSR_WRCMDREQ, nfc->regs + NDSR); + + return 0; +} + +static void marvell_nfc_send_cmd(struct nand_chip *chip, + struct marvell_nfc_op *nfc_op) +{ + struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip); + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + + dev_dbg(nfc->dev, "\nNDCR: 0x%08x\n" + "NDCB0: 0x%08x\nNDCB1: 0x%08x\nNDCB2: 0x%08x\nNDCB3: 0x%08x\n", + (u32)readl_relaxed(nfc->regs + NDCR), nfc_op->ndcb[0], + nfc_op->ndcb[1], nfc_op->ndcb[2], nfc_op->ndcb[3]); + + writel_relaxed(to_nand_sel(marvell_nand)->ndcb0_csel | nfc_op->ndcb[0], + nfc->regs + NDCB0); + writel_relaxed(nfc_op->ndcb[1], nfc->regs + NDCB0); + writel(nfc_op->ndcb[2], nfc->regs + NDCB0); + + /* + * Write NDCB0 four times only if LEN_OVRD is set or if ADDR6 or ADDR7 + * fields are used (only available on NFCv2). + */ + if (nfc_op->ndcb[0] & NDCB0_LEN_OVRD || + NDCB0_ADDR_GET_NUM_CYC(nfc_op->ndcb[0]) >= 6) { + if (!WARN_ON_ONCE(!nfc->caps->is_nfcv2)) + writel(nfc_op->ndcb[3], nfc->regs + NDCB0); + } +} + +static int marvell_nfc_end_cmd(struct nand_chip *chip, int flag, + const char *label) +{ + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + u32 val; + int ret; + + ret = readl_relaxed_poll_timeout(nfc->regs + NDSR, val, + val & flag, + POLL_PERIOD, POLL_TIMEOUT); + + if (ret) { + dev_err(nfc->dev, "Timeout on %s (NDSR: 0x%08x)\n", + label, val); + if (nfc->dma_chan) + dmaengine_terminate_all(nfc->dma_chan); + return ret; + } + + /* + * DMA function uses this helper to poll on CMDD bits without wanting + * them to be cleared. + */ + if (nfc->use_dma && (readl_relaxed(nfc->regs + NDCR) & NDCR_DMA_EN)) + return 0; + + writel_relaxed(flag, nfc->regs + NDSR); + + return 0; +} + +static int marvell_nfc_wait_cmdd(struct nand_chip *chip) +{ + struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip); + int cs_flag = NDSR_CMDD(to_nand_sel(marvell_nand)->ndcb0_csel); + + return marvell_nfc_end_cmd(chip, cs_flag, "CMDD"); +} + +static int marvell_nfc_poll_status(struct marvell_nfc *nfc, u32 mask, + u32 expected_val, unsigned long timeout_ms) +{ + unsigned long limit; + u32 st; + + limit = jiffies + msecs_to_jiffies(timeout_ms); + do { + st = readl_relaxed(nfc->regs + NDSR); + if (st & NDSR_RDY(1)) + st |= NDSR_RDY(0); + + if ((st & mask) == expected_val) + return 0; + + cpu_relax(); + } while (time_after(limit, jiffies)); + + return -ETIMEDOUT; +} + +static int marvell_nfc_wait_op(struct nand_chip *chip, unsigned int timeout_ms) +{ + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + struct mtd_info *mtd = nand_to_mtd(chip); + u32 pending; + int ret; + + /* Timeout is expressed in ms */ + if (!timeout_ms) + timeout_ms = IRQ_TIMEOUT; + + if (mtd->oops_panic_write) { + ret = marvell_nfc_poll_status(nfc, NDSR_RDY(0), + NDSR_RDY(0), + timeout_ms); + } else { + init_completion(&nfc->complete); + + marvell_nfc_enable_int(nfc, NDCR_RDYM); + ret = wait_for_completion_timeout(&nfc->complete, + msecs_to_jiffies(timeout_ms)); + marvell_nfc_disable_int(nfc, NDCR_RDYM); + } + pending = marvell_nfc_clear_int(nfc, NDSR_RDY(0) | NDSR_RDY(1)); + + /* + * In case the interrupt was not served in the required time frame, + * check if the ISR was not served or if something went actually wrong. + */ + if (!ret && !pending) { + dev_err(nfc->dev, "Timeout waiting for RB signal\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static void marvell_nfc_select_target(struct nand_chip *chip, + unsigned int die_nr) +{ + struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip); + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + u32 ndcr_generic; + + /* + * Reset the NDCR register to a clean state for this particular chip, + * also clear ND_RUN bit. + */ + ndcr_generic = readl_relaxed(nfc->regs + NDCR) & + NDCR_GENERIC_FIELDS_MASK & ~NDCR_ND_RUN; + writel_relaxed(ndcr_generic | marvell_nand->ndcr, nfc->regs + NDCR); + + /* Also reset the interrupt status register */ + marvell_nfc_clear_int(nfc, NDCR_ALL_INT); + + if (chip == nfc->selected_chip && die_nr == marvell_nand->selected_die) + return; + + writel_relaxed(marvell_nand->ndtr0, nfc->regs + NDTR0); + writel_relaxed(marvell_nand->ndtr1, nfc->regs + NDTR1); + + nfc->selected_chip = chip; + marvell_nand->selected_die = die_nr; +} + +static irqreturn_t marvell_nfc_isr(int irq, void *dev_id) +{ + struct marvell_nfc *nfc = dev_id; + u32 st = readl_relaxed(nfc->regs + NDSR); + u32 ien = (~readl_relaxed(nfc->regs + NDCR)) & NDCR_ALL_INT; + + /* + * RDY interrupt mask is one bit in NDCR while there are two status + * bit in NDSR (RDY[cs0/cs2] and RDY[cs1/cs3]). + */ + if (st & NDSR_RDY(1)) + st |= NDSR_RDY(0); + + if (!(st & ien)) + return IRQ_NONE; + + marvell_nfc_disable_int(nfc, st & NDCR_ALL_INT); + + if (st & (NDSR_RDY(0) | NDSR_RDY(1))) + complete(&nfc->complete); + + return IRQ_HANDLED; +} + +/* HW ECC related functions */ +static void marvell_nfc_enable_hw_ecc(struct nand_chip *chip) +{ + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + u32 ndcr = readl_relaxed(nfc->regs + NDCR); + + if (!(ndcr & NDCR_ECC_EN)) { + writel_relaxed(ndcr | NDCR_ECC_EN, nfc->regs + NDCR); + + /* + * When enabling BCH, set threshold to 0 to always know the + * number of corrected bitflips. + */ + if (chip->ecc.algo == NAND_ECC_ALGO_BCH) + writel_relaxed(NDECCCTRL_BCH_EN, nfc->regs + NDECCCTRL); + } +} + +static void marvell_nfc_disable_hw_ecc(struct nand_chip *chip) +{ + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + u32 ndcr = readl_relaxed(nfc->regs + NDCR); + + if (ndcr & NDCR_ECC_EN) { + writel_relaxed(ndcr & ~NDCR_ECC_EN, nfc->regs + NDCR); + if (chip->ecc.algo == NAND_ECC_ALGO_BCH) + writel_relaxed(0, nfc->regs + NDECCCTRL); + } +} + +/* DMA related helpers */ +static void marvell_nfc_enable_dma(struct marvell_nfc *nfc) +{ + u32 reg; + + reg = readl_relaxed(nfc->regs + NDCR); + writel_relaxed(reg | NDCR_DMA_EN, nfc->regs + NDCR); +} + +static void marvell_nfc_disable_dma(struct marvell_nfc *nfc) +{ + u32 reg; + + reg = readl_relaxed(nfc->regs + NDCR); + writel_relaxed(reg & ~NDCR_DMA_EN, nfc->regs + NDCR); +} + +/* Read/write PIO/DMA accessors */ +static int marvell_nfc_xfer_data_dma(struct marvell_nfc *nfc, + enum dma_data_direction direction, + unsigned int len) +{ + unsigned int dma_len = min_t(int, ALIGN(len, 32), MAX_CHUNK_SIZE); + struct dma_async_tx_descriptor *tx; + struct scatterlist sg; + dma_cookie_t cookie; + int ret; + + marvell_nfc_enable_dma(nfc); + /* Prepare the DMA transfer */ + sg_init_one(&sg, nfc->dma_buf, dma_len); + ret = dma_map_sg(nfc->dma_chan->device->dev, &sg, 1, direction); + if (!ret) { + dev_err(nfc->dev, "Could not map DMA S/G list\n"); + return -ENXIO; + } + + tx = dmaengine_prep_slave_sg(nfc->dma_chan, &sg, 1, + direction == DMA_FROM_DEVICE ? + DMA_DEV_TO_MEM : DMA_MEM_TO_DEV, + DMA_PREP_INTERRUPT); + if (!tx) { + dev_err(nfc->dev, "Could not prepare DMA S/G list\n"); + dma_unmap_sg(nfc->dma_chan->device->dev, &sg, 1, direction); + return -ENXIO; + } + + /* Do the task and wait for it to finish */ + cookie = dmaengine_submit(tx); + ret = dma_submit_error(cookie); + if (ret) + return -EIO; + + dma_async_issue_pending(nfc->dma_chan); + ret = marvell_nfc_wait_cmdd(nfc->selected_chip); + dma_unmap_sg(nfc->dma_chan->device->dev, &sg, 1, direction); + marvell_nfc_disable_dma(nfc); + if (ret) { + dev_err(nfc->dev, "Timeout waiting for DMA (status: %d)\n", + dmaengine_tx_status(nfc->dma_chan, cookie, NULL)); + dmaengine_terminate_all(nfc->dma_chan); + return -ETIMEDOUT; + } + + return 0; +} + +static int marvell_nfc_xfer_data_in_pio(struct marvell_nfc *nfc, u8 *in, + unsigned int len) +{ + unsigned int last_len = len % FIFO_DEPTH; + unsigned int last_full_offset = round_down(len, FIFO_DEPTH); + int i; + + for (i = 0; i < last_full_offset; i += FIFO_DEPTH) + ioread32_rep(nfc->regs + NDDB, in + i, FIFO_REP(FIFO_DEPTH)); + + if (last_len) { + u8 tmp_buf[FIFO_DEPTH]; + + ioread32_rep(nfc->regs + NDDB, tmp_buf, FIFO_REP(FIFO_DEPTH)); + memcpy(in + last_full_offset, tmp_buf, last_len); + } + + return 0; +} + +static int marvell_nfc_xfer_data_out_pio(struct marvell_nfc *nfc, const u8 *out, + unsigned int len) +{ + unsigned int last_len = len % FIFO_DEPTH; + unsigned int last_full_offset = round_down(len, FIFO_DEPTH); + int i; + + for (i = 0; i < last_full_offset; i += FIFO_DEPTH) + iowrite32_rep(nfc->regs + NDDB, out + i, FIFO_REP(FIFO_DEPTH)); + + if (last_len) { + u8 tmp_buf[FIFO_DEPTH]; + + memcpy(tmp_buf, out + last_full_offset, last_len); + iowrite32_rep(nfc->regs + NDDB, tmp_buf, FIFO_REP(FIFO_DEPTH)); + } + + return 0; +} + +static void marvell_nfc_check_empty_chunk(struct nand_chip *chip, + u8 *data, int data_len, + u8 *spare, int spare_len, + u8 *ecc, int ecc_len, + unsigned int *max_bitflips) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int bf; + + /* + * Blank pages (all 0xFF) that have not been written may be recognized + * as bad if bitflips occur, so whenever an uncorrectable error occurs, + * check if the entire page (with ECC bytes) is actually blank or not. + */ + if (!data) + data_len = 0; + if (!spare) + spare_len = 0; + if (!ecc) + ecc_len = 0; + + bf = nand_check_erased_ecc_chunk(data, data_len, ecc, ecc_len, + spare, spare_len, chip->ecc.strength); + if (bf < 0) { + mtd->ecc_stats.failed++; + return; + } + + /* Update the stats and max_bitflips */ + mtd->ecc_stats.corrected += bf; + *max_bitflips = max_t(unsigned int, *max_bitflips, bf); +} + +/* + * Check if a chunk is correct or not according to the hardware ECC engine. + * mtd->ecc_stats.corrected is updated, as well as max_bitflips, however + * mtd->ecc_stats.failure is not, the function will instead return a non-zero + * value indicating that a check on the emptyness of the subpage must be + * performed before actually declaring the subpage as "corrupted". + */ +static int marvell_nfc_hw_ecc_check_bitflips(struct nand_chip *chip, + unsigned int *max_bitflips) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + int bf = 0; + u32 ndsr; + + ndsr = readl_relaxed(nfc->regs + NDSR); + + /* Check uncorrectable error flag */ + if (ndsr & NDSR_UNCERR) { + writel_relaxed(ndsr, nfc->regs + NDSR); + + /* + * Do not increment ->ecc_stats.failed now, instead, return a + * non-zero value to indicate that this chunk was apparently + * bad, and it should be check to see if it empty or not. If + * the chunk (with ECC bytes) is not declared empty, the calling + * function must increment the failure count. + */ + return -EBADMSG; + } + + /* Check correctable error flag */ + if (ndsr & NDSR_CORERR) { + writel_relaxed(ndsr, nfc->regs + NDSR); + + if (chip->ecc.algo == NAND_ECC_ALGO_BCH) + bf = NDSR_ERRCNT(ndsr); + else + bf = 1; + } + + /* Update the stats and max_bitflips */ + mtd->ecc_stats.corrected += bf; + *max_bitflips = max_t(unsigned int, *max_bitflips, bf); + + return 0; +} + +/* Hamming read helpers */ +static int marvell_nfc_hw_ecc_hmg_do_read_page(struct nand_chip *chip, + u8 *data_buf, u8 *oob_buf, + bool raw, int page) +{ + struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip); + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout; + struct marvell_nfc_op nfc_op = { + .ndcb[0] = NDCB0_CMD_TYPE(TYPE_READ) | + NDCB0_ADDR_CYC(marvell_nand->addr_cyc) | + NDCB0_DBC | + NDCB0_CMD1(NAND_CMD_READ0) | + NDCB0_CMD2(NAND_CMD_READSTART), + .ndcb[1] = NDCB1_ADDRS_PAGE(page), + .ndcb[2] = NDCB2_ADDR5_PAGE(page), + }; + unsigned int oob_bytes = lt->spare_bytes + (raw ? lt->ecc_bytes : 0); + int ret; + + /* NFCv2 needs more information about the operation being executed */ + if (nfc->caps->is_nfcv2) + nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW); + + ret = marvell_nfc_prepare_cmd(chip); + if (ret) + return ret; + + marvell_nfc_send_cmd(chip, &nfc_op); + ret = marvell_nfc_end_cmd(chip, NDSR_RDDREQ, + "RDDREQ while draining FIFO (data/oob)"); + if (ret) + return ret; + + /* + * Read the page then the OOB area. Unlike what is shown in current + * documentation, spare bytes are protected by the ECC engine, and must + * be at the beginning of the OOB area or running this driver on legacy + * systems will prevent the discovery of the BBM/BBT. + */ + if (nfc->use_dma) { + marvell_nfc_xfer_data_dma(nfc, DMA_FROM_DEVICE, + lt->data_bytes + oob_bytes); + memcpy(data_buf, nfc->dma_buf, lt->data_bytes); + memcpy(oob_buf, nfc->dma_buf + lt->data_bytes, oob_bytes); + } else { + marvell_nfc_xfer_data_in_pio(nfc, data_buf, lt->data_bytes); + marvell_nfc_xfer_data_in_pio(nfc, oob_buf, oob_bytes); + } + + ret = marvell_nfc_wait_cmdd(chip); + return ret; +} + +static int marvell_nfc_hw_ecc_hmg_read_page_raw(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + marvell_nfc_select_target(chip, chip->cur_cs); + return marvell_nfc_hw_ecc_hmg_do_read_page(chip, buf, chip->oob_poi, + true, page); +} + +static int marvell_nfc_hw_ecc_hmg_read_page(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout; + unsigned int full_sz = lt->data_bytes + lt->spare_bytes + lt->ecc_bytes; + int max_bitflips = 0, ret; + u8 *raw_buf; + + marvell_nfc_select_target(chip, chip->cur_cs); + marvell_nfc_enable_hw_ecc(chip); + marvell_nfc_hw_ecc_hmg_do_read_page(chip, buf, chip->oob_poi, false, + page); + ret = marvell_nfc_hw_ecc_check_bitflips(chip, &max_bitflips); + marvell_nfc_disable_hw_ecc(chip); + + if (!ret) + return max_bitflips; + + /* + * When ECC failures are detected, check if the full page has been + * written or not. Ignore the failure if it is actually empty. + */ + raw_buf = kmalloc(full_sz, GFP_KERNEL); + if (!raw_buf) + return -ENOMEM; + + marvell_nfc_hw_ecc_hmg_do_read_page(chip, raw_buf, raw_buf + + lt->data_bytes, true, page); + marvell_nfc_check_empty_chunk(chip, raw_buf, full_sz, NULL, 0, NULL, 0, + &max_bitflips); + kfree(raw_buf); + + return max_bitflips; +} + +/* + * Spare area in Hamming layouts is not protected by the ECC engine (even if + * it appears before the ECC bytes when reading), the ->read_oob_raw() function + * also stands for ->read_oob(). + */ +static int marvell_nfc_hw_ecc_hmg_read_oob_raw(struct nand_chip *chip, int page) +{ + u8 *buf = nand_get_data_buf(chip); + + marvell_nfc_select_target(chip, chip->cur_cs); + return marvell_nfc_hw_ecc_hmg_do_read_page(chip, buf, chip->oob_poi, + true, page); +} + +/* Hamming write helpers */ +static int marvell_nfc_hw_ecc_hmg_do_write_page(struct nand_chip *chip, + const u8 *data_buf, + const u8 *oob_buf, bool raw, + int page) +{ + const struct nand_sdr_timings *sdr = + nand_get_sdr_timings(nand_get_interface_config(chip)); + struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip); + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout; + struct marvell_nfc_op nfc_op = { + .ndcb[0] = NDCB0_CMD_TYPE(TYPE_WRITE) | + NDCB0_ADDR_CYC(marvell_nand->addr_cyc) | + NDCB0_CMD1(NAND_CMD_SEQIN) | + NDCB0_CMD2(NAND_CMD_PAGEPROG) | + NDCB0_DBC, + .ndcb[1] = NDCB1_ADDRS_PAGE(page), + .ndcb[2] = NDCB2_ADDR5_PAGE(page), + }; + unsigned int oob_bytes = lt->spare_bytes + (raw ? lt->ecc_bytes : 0); + u8 status; + int ret; + + /* NFCv2 needs more information about the operation being executed */ + if (nfc->caps->is_nfcv2) + nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW); + + ret = marvell_nfc_prepare_cmd(chip); + if (ret) + return ret; + + marvell_nfc_send_cmd(chip, &nfc_op); + ret = marvell_nfc_end_cmd(chip, NDSR_WRDREQ, + "WRDREQ while loading FIFO (data)"); + if (ret) + return ret; + + /* Write the page then the OOB area */ + if (nfc->use_dma) { + memcpy(nfc->dma_buf, data_buf, lt->data_bytes); + memcpy(nfc->dma_buf + lt->data_bytes, oob_buf, oob_bytes); + marvell_nfc_xfer_data_dma(nfc, DMA_TO_DEVICE, lt->data_bytes + + lt->ecc_bytes + lt->spare_bytes); + } else { + marvell_nfc_xfer_data_out_pio(nfc, data_buf, lt->data_bytes); + marvell_nfc_xfer_data_out_pio(nfc, oob_buf, oob_bytes); + } + + ret = marvell_nfc_wait_cmdd(chip); + if (ret) + return ret; + + ret = marvell_nfc_wait_op(chip, + PSEC_TO_MSEC(sdr->tPROG_max)); + if (ret) + return ret; + + /* Check write status on the chip side */ + ret = nand_status_op(chip, &status); + if (ret) + return ret; + + if (status & NAND_STATUS_FAIL) + return -EIO; + + return 0; +} + +static int marvell_nfc_hw_ecc_hmg_write_page_raw(struct nand_chip *chip, + const u8 *buf, + int oob_required, int page) +{ + marvell_nfc_select_target(chip, chip->cur_cs); + return marvell_nfc_hw_ecc_hmg_do_write_page(chip, buf, chip->oob_poi, + true, page); +} + +static int marvell_nfc_hw_ecc_hmg_write_page(struct nand_chip *chip, + const u8 *buf, + int oob_required, int page) +{ + int ret; + + marvell_nfc_select_target(chip, chip->cur_cs); + marvell_nfc_enable_hw_ecc(chip); + ret = marvell_nfc_hw_ecc_hmg_do_write_page(chip, buf, chip->oob_poi, + false, page); + marvell_nfc_disable_hw_ecc(chip); + + return ret; +} + +/* + * Spare area in Hamming layouts is not protected by the ECC engine (even if + * it appears before the ECC bytes when reading), the ->write_oob_raw() function + * also stands for ->write_oob(). + */ +static int marvell_nfc_hw_ecc_hmg_write_oob_raw(struct nand_chip *chip, + int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + u8 *buf = nand_get_data_buf(chip); + + memset(buf, 0xFF, mtd->writesize); + + marvell_nfc_select_target(chip, chip->cur_cs); + return marvell_nfc_hw_ecc_hmg_do_write_page(chip, buf, chip->oob_poi, + true, page); +} + +/* BCH read helpers */ +static int marvell_nfc_hw_ecc_bch_read_page_raw(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout; + u8 *oob = chip->oob_poi; + int chunk_size = lt->data_bytes + lt->spare_bytes + lt->ecc_bytes; + int ecc_offset = (lt->full_chunk_cnt * lt->spare_bytes) + + lt->last_spare_bytes; + int data_len = lt->data_bytes; + int spare_len = lt->spare_bytes; + int ecc_len = lt->ecc_bytes; + int chunk; + + marvell_nfc_select_target(chip, chip->cur_cs); + + if (oob_required) + memset(chip->oob_poi, 0xFF, mtd->oobsize); + + nand_read_page_op(chip, page, 0, NULL, 0); + + for (chunk = 0; chunk < lt->nchunks; chunk++) { + /* Update last chunk length */ + if (chunk >= lt->full_chunk_cnt) { + data_len = lt->last_data_bytes; + spare_len = lt->last_spare_bytes; + ecc_len = lt->last_ecc_bytes; + } + + /* Read data bytes*/ + nand_change_read_column_op(chip, chunk * chunk_size, + buf + (lt->data_bytes * chunk), + data_len, false); + + /* Read spare bytes */ + nand_read_data_op(chip, oob + (lt->spare_bytes * chunk), + spare_len, false, false); + + /* Read ECC bytes */ + nand_read_data_op(chip, oob + ecc_offset + + (ALIGN(lt->ecc_bytes, 32) * chunk), + ecc_len, false, false); + } + + return 0; +} + +static void marvell_nfc_hw_ecc_bch_read_chunk(struct nand_chip *chip, int chunk, + u8 *data, unsigned int data_len, + u8 *spare, unsigned int spare_len, + int page) +{ + struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip); + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout; + int i, ret; + struct marvell_nfc_op nfc_op = { + .ndcb[0] = NDCB0_CMD_TYPE(TYPE_READ) | + NDCB0_ADDR_CYC(marvell_nand->addr_cyc) | + NDCB0_LEN_OVRD, + .ndcb[1] = NDCB1_ADDRS_PAGE(page), + .ndcb[2] = NDCB2_ADDR5_PAGE(page), + .ndcb[3] = data_len + spare_len, + }; + + ret = marvell_nfc_prepare_cmd(chip); + if (ret) + return; + + if (chunk == 0) + nfc_op.ndcb[0] |= NDCB0_DBC | + NDCB0_CMD1(NAND_CMD_READ0) | + NDCB0_CMD2(NAND_CMD_READSTART); + + /* + * Trigger the monolithic read on the first chunk, then naked read on + * intermediate chunks and finally a last naked read on the last chunk. + */ + if (chunk == 0) + nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW); + else if (chunk < lt->nchunks - 1) + nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_NAKED_RW); + else + nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW); + + marvell_nfc_send_cmd(chip, &nfc_op); + + /* + * According to the datasheet, when reading from NDDB + * with BCH enabled, after each 32 bytes reads, we + * have to make sure that the NDSR.RDDREQ bit is set. + * + * Drain the FIFO, 8 32-bit reads at a time, and skip + * the polling on the last read. + * + * Length is a multiple of 32 bytes, hence it is a multiple of 8 too. + */ + for (i = 0; i < data_len; i += FIFO_DEPTH * BCH_SEQ_READS) { + marvell_nfc_end_cmd(chip, NDSR_RDDREQ, + "RDDREQ while draining FIFO (data)"); + marvell_nfc_xfer_data_in_pio(nfc, data, + FIFO_DEPTH * BCH_SEQ_READS); + data += FIFO_DEPTH * BCH_SEQ_READS; + } + + for (i = 0; i < spare_len; i += FIFO_DEPTH * BCH_SEQ_READS) { + marvell_nfc_end_cmd(chip, NDSR_RDDREQ, + "RDDREQ while draining FIFO (OOB)"); + marvell_nfc_xfer_data_in_pio(nfc, spare, + FIFO_DEPTH * BCH_SEQ_READS); + spare += FIFO_DEPTH * BCH_SEQ_READS; + } +} + +static int marvell_nfc_hw_ecc_bch_read_page(struct nand_chip *chip, + u8 *buf, int oob_required, + int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout; + int data_len = lt->data_bytes, spare_len = lt->spare_bytes; + u8 *data = buf, *spare = chip->oob_poi; + int max_bitflips = 0; + u32 failure_mask = 0; + int chunk, ret; + + marvell_nfc_select_target(chip, chip->cur_cs); + + /* + * With BCH, OOB is not fully used (and thus not read entirely), not + * expected bytes could show up at the end of the OOB buffer if not + * explicitly erased. + */ + if (oob_required) + memset(chip->oob_poi, 0xFF, mtd->oobsize); + + marvell_nfc_enable_hw_ecc(chip); + + for (chunk = 0; chunk < lt->nchunks; chunk++) { + /* Update length for the last chunk */ + if (chunk >= lt->full_chunk_cnt) { + data_len = lt->last_data_bytes; + spare_len = lt->last_spare_bytes; + } + + /* Read the chunk and detect number of bitflips */ + marvell_nfc_hw_ecc_bch_read_chunk(chip, chunk, data, data_len, + spare, spare_len, page); + ret = marvell_nfc_hw_ecc_check_bitflips(chip, &max_bitflips); + if (ret) + failure_mask |= BIT(chunk); + + data += data_len; + spare += spare_len; + } + + marvell_nfc_disable_hw_ecc(chip); + + if (!failure_mask) + return max_bitflips; + + /* + * Please note that dumping the ECC bytes during a normal read with OOB + * area would add a significant overhead as ECC bytes are "consumed" by + * the controller in normal mode and must be re-read in raw mode. To + * avoid dropping the performances, we prefer not to include them. The + * user should re-read the page in raw mode if ECC bytes are required. + */ + + /* + * In case there is any subpage read error, we usually re-read only ECC + * bytes in raw mode and check if the whole page is empty. In this case, + * it is normal that the ECC check failed and we just ignore the error. + * + * However, it has been empirically observed that for some layouts (e.g + * 2k page, 8b strength per 512B chunk), the controller tries to correct + * bits and may create itself bitflips in the erased area. To overcome + * this strange behavior, the whole page is re-read in raw mode, not + * only the ECC bytes. + */ + for (chunk = 0; chunk < lt->nchunks; chunk++) { + int data_off_in_page, spare_off_in_page, ecc_off_in_page; + int data_off, spare_off, ecc_off; + int data_len, spare_len, ecc_len; + + /* No failure reported for this chunk, move to the next one */ + if (!(failure_mask & BIT(chunk))) + continue; + + data_off_in_page = chunk * (lt->data_bytes + lt->spare_bytes + + lt->ecc_bytes); + spare_off_in_page = data_off_in_page + + (chunk < lt->full_chunk_cnt ? lt->data_bytes : + lt->last_data_bytes); + ecc_off_in_page = spare_off_in_page + + (chunk < lt->full_chunk_cnt ? lt->spare_bytes : + lt->last_spare_bytes); + + data_off = chunk * lt->data_bytes; + spare_off = chunk * lt->spare_bytes; + ecc_off = (lt->full_chunk_cnt * lt->spare_bytes) + + lt->last_spare_bytes + + (chunk * (lt->ecc_bytes + 2)); + + data_len = chunk < lt->full_chunk_cnt ? lt->data_bytes : + lt->last_data_bytes; + spare_len = chunk < lt->full_chunk_cnt ? lt->spare_bytes : + lt->last_spare_bytes; + ecc_len = chunk < lt->full_chunk_cnt ? lt->ecc_bytes : + lt->last_ecc_bytes; + + /* + * Only re-read the ECC bytes, unless we are using the 2k/8b + * layout which is buggy in the sense that the ECC engine will + * try to correct data bytes anyway, creating bitflips. In this + * case, re-read the entire page. + */ + if (lt->writesize == 2048 && lt->strength == 8) { + nand_change_read_column_op(chip, data_off_in_page, + buf + data_off, data_len, + false); + nand_change_read_column_op(chip, spare_off_in_page, + chip->oob_poi + spare_off, spare_len, + false); + } + + nand_change_read_column_op(chip, ecc_off_in_page, + chip->oob_poi + ecc_off, ecc_len, + false); + + /* Check the entire chunk (data + spare + ecc) for emptyness */ + marvell_nfc_check_empty_chunk(chip, buf + data_off, data_len, + chip->oob_poi + spare_off, spare_len, + chip->oob_poi + ecc_off, ecc_len, + &max_bitflips); + } + + return max_bitflips; +} + +static int marvell_nfc_hw_ecc_bch_read_oob_raw(struct nand_chip *chip, int page) +{ + u8 *buf = nand_get_data_buf(chip); + + return chip->ecc.read_page_raw(chip, buf, true, page); +} + +static int marvell_nfc_hw_ecc_bch_read_oob(struct nand_chip *chip, int page) +{ + u8 *buf = nand_get_data_buf(chip); + + return chip->ecc.read_page(chip, buf, true, page); +} + +/* BCH write helpers */ +static int marvell_nfc_hw_ecc_bch_write_page_raw(struct nand_chip *chip, + const u8 *buf, + int oob_required, int page) +{ + const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout; + int full_chunk_size = lt->data_bytes + lt->spare_bytes + lt->ecc_bytes; + int data_len = lt->data_bytes; + int spare_len = lt->spare_bytes; + int ecc_len = lt->ecc_bytes; + int spare_offset = 0; + int ecc_offset = (lt->full_chunk_cnt * lt->spare_bytes) + + lt->last_spare_bytes; + int chunk; + + marvell_nfc_select_target(chip, chip->cur_cs); + + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + + for (chunk = 0; chunk < lt->nchunks; chunk++) { + if (chunk >= lt->full_chunk_cnt) { + data_len = lt->last_data_bytes; + spare_len = lt->last_spare_bytes; + ecc_len = lt->last_ecc_bytes; + } + + /* Point to the column of the next chunk */ + nand_change_write_column_op(chip, chunk * full_chunk_size, + NULL, 0, false); + + /* Write the data */ + nand_write_data_op(chip, buf + (chunk * lt->data_bytes), + data_len, false); + + if (!oob_required) + continue; + + /* Write the spare bytes */ + if (spare_len) + nand_write_data_op(chip, chip->oob_poi + spare_offset, + spare_len, false); + + /* Write the ECC bytes */ + if (ecc_len) + nand_write_data_op(chip, chip->oob_poi + ecc_offset, + ecc_len, false); + + spare_offset += spare_len; + ecc_offset += ALIGN(ecc_len, 32); + } + + return nand_prog_page_end_op(chip); +} + +static int +marvell_nfc_hw_ecc_bch_write_chunk(struct nand_chip *chip, int chunk, + const u8 *data, unsigned int data_len, + const u8 *spare, unsigned int spare_len, + int page) +{ + struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip); + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout; + u32 xtype; + int ret; + struct marvell_nfc_op nfc_op = { + .ndcb[0] = NDCB0_CMD_TYPE(TYPE_WRITE) | NDCB0_LEN_OVRD, + .ndcb[3] = data_len + spare_len, + }; + + /* + * First operation dispatches the CMD_SEQIN command, issue the address + * cycles and asks for the first chunk of data. + * All operations in the middle (if any) will issue a naked write and + * also ask for data. + * Last operation (if any) asks for the last chunk of data through a + * last naked write. + */ + if (chunk == 0) { + if (lt->nchunks == 1) + xtype = XTYPE_MONOLITHIC_RW; + else + xtype = XTYPE_WRITE_DISPATCH; + + nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(xtype) | + NDCB0_ADDR_CYC(marvell_nand->addr_cyc) | + NDCB0_CMD1(NAND_CMD_SEQIN); + nfc_op.ndcb[1] |= NDCB1_ADDRS_PAGE(page); + nfc_op.ndcb[2] |= NDCB2_ADDR5_PAGE(page); + } else if (chunk < lt->nchunks - 1) { + nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_NAKED_RW); + } else { + nfc_op.ndcb[0] |= NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW); + } + + /* Always dispatch the PAGEPROG command on the last chunk */ + if (chunk == lt->nchunks - 1) + nfc_op.ndcb[0] |= NDCB0_CMD2(NAND_CMD_PAGEPROG) | NDCB0_DBC; + + ret = marvell_nfc_prepare_cmd(chip); + if (ret) + return ret; + + marvell_nfc_send_cmd(chip, &nfc_op); + ret = marvell_nfc_end_cmd(chip, NDSR_WRDREQ, + "WRDREQ while loading FIFO (data)"); + if (ret) + return ret; + + /* Transfer the contents */ + iowrite32_rep(nfc->regs + NDDB, data, FIFO_REP(data_len)); + iowrite32_rep(nfc->regs + NDDB, spare, FIFO_REP(spare_len)); + + return 0; +} + +static int marvell_nfc_hw_ecc_bch_write_page(struct nand_chip *chip, + const u8 *buf, + int oob_required, int page) +{ + const struct nand_sdr_timings *sdr = + nand_get_sdr_timings(nand_get_interface_config(chip)); + struct mtd_info *mtd = nand_to_mtd(chip); + const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout; + const u8 *data = buf; + const u8 *spare = chip->oob_poi; + int data_len = lt->data_bytes; + int spare_len = lt->spare_bytes; + int chunk, ret; + u8 status; + + marvell_nfc_select_target(chip, chip->cur_cs); + + /* Spare data will be written anyway, so clear it to avoid garbage */ + if (!oob_required) + memset(chip->oob_poi, 0xFF, mtd->oobsize); + + marvell_nfc_enable_hw_ecc(chip); + + for (chunk = 0; chunk < lt->nchunks; chunk++) { + if (chunk >= lt->full_chunk_cnt) { + data_len = lt->last_data_bytes; + spare_len = lt->last_spare_bytes; + } + + marvell_nfc_hw_ecc_bch_write_chunk(chip, chunk, data, data_len, + spare, spare_len, page); + data += data_len; + spare += spare_len; + + /* + * Waiting only for CMDD or PAGED is not enough, ECC are + * partially written. No flag is set once the operation is + * really finished but the ND_RUN bit is cleared, so wait for it + * before stepping into the next command. + */ + marvell_nfc_wait_ndrun(chip); + } + + ret = marvell_nfc_wait_op(chip, PSEC_TO_MSEC(sdr->tPROG_max)); + + marvell_nfc_disable_hw_ecc(chip); + + if (ret) + return ret; + + /* Check write status on the chip side */ + ret = nand_status_op(chip, &status); + if (ret) + return ret; + + if (status & NAND_STATUS_FAIL) + return -EIO; + + return 0; +} + +static int marvell_nfc_hw_ecc_bch_write_oob_raw(struct nand_chip *chip, + int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + u8 *buf = nand_get_data_buf(chip); + + memset(buf, 0xFF, mtd->writesize); + + return chip->ecc.write_page_raw(chip, buf, true, page); +} + +static int marvell_nfc_hw_ecc_bch_write_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + u8 *buf = nand_get_data_buf(chip); + + memset(buf, 0xFF, mtd->writesize); + + return chip->ecc.write_page(chip, buf, true, page); +} + +/* NAND framework ->exec_op() hooks and related helpers */ +static void marvell_nfc_parse_instructions(struct nand_chip *chip, + const struct nand_subop *subop, + struct marvell_nfc_op *nfc_op) +{ + const struct nand_op_instr *instr = NULL; + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + bool first_cmd = true; + unsigned int op_id; + int i; + + /* Reset the input structure as most of its fields will be OR'ed */ + memset(nfc_op, 0, sizeof(struct marvell_nfc_op)); + + for (op_id = 0; op_id < subop->ninstrs; op_id++) { + unsigned int offset, naddrs; + const u8 *addrs; + int len; + + instr = &subop->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + if (first_cmd) + nfc_op->ndcb[0] |= + NDCB0_CMD1(instr->ctx.cmd.opcode); + else + nfc_op->ndcb[0] |= + NDCB0_CMD2(instr->ctx.cmd.opcode) | + NDCB0_DBC; + + nfc_op->cle_ale_delay_ns = instr->delay_ns; + first_cmd = false; + break; + + case NAND_OP_ADDR_INSTR: + offset = nand_subop_get_addr_start_off(subop, op_id); + naddrs = nand_subop_get_num_addr_cyc(subop, op_id); + addrs = &instr->ctx.addr.addrs[offset]; + + nfc_op->ndcb[0] |= NDCB0_ADDR_CYC(naddrs); + + for (i = 0; i < min_t(unsigned int, 4, naddrs); i++) + nfc_op->ndcb[1] |= addrs[i] << (8 * i); + + if (naddrs >= 5) + nfc_op->ndcb[2] |= NDCB2_ADDR5_CYC(addrs[4]); + if (naddrs >= 6) + nfc_op->ndcb[3] |= NDCB3_ADDR6_CYC(addrs[5]); + if (naddrs == 7) + nfc_op->ndcb[3] |= NDCB3_ADDR7_CYC(addrs[6]); + + nfc_op->cle_ale_delay_ns = instr->delay_ns; + break; + + case NAND_OP_DATA_IN_INSTR: + nfc_op->data_instr = instr; + nfc_op->data_instr_idx = op_id; + nfc_op->ndcb[0] |= NDCB0_CMD_TYPE(TYPE_READ); + if (nfc->caps->is_nfcv2) { + nfc_op->ndcb[0] |= + NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW) | + NDCB0_LEN_OVRD; + len = nand_subop_get_data_len(subop, op_id); + nfc_op->ndcb[3] |= round_up(len, FIFO_DEPTH); + } + nfc_op->data_delay_ns = instr->delay_ns; + break; + + case NAND_OP_DATA_OUT_INSTR: + nfc_op->data_instr = instr; + nfc_op->data_instr_idx = op_id; + nfc_op->ndcb[0] |= NDCB0_CMD_TYPE(TYPE_WRITE); + if (nfc->caps->is_nfcv2) { + nfc_op->ndcb[0] |= + NDCB0_CMD_XTYPE(XTYPE_MONOLITHIC_RW) | + NDCB0_LEN_OVRD; + len = nand_subop_get_data_len(subop, op_id); + nfc_op->ndcb[3] |= round_up(len, FIFO_DEPTH); + } + nfc_op->data_delay_ns = instr->delay_ns; + break; + + case NAND_OP_WAITRDY_INSTR: + nfc_op->rdy_timeout_ms = instr->ctx.waitrdy.timeout_ms; + nfc_op->rdy_delay_ns = instr->delay_ns; + break; + } + } +} + +static int marvell_nfc_xfer_data_pio(struct nand_chip *chip, + const struct nand_subop *subop, + struct marvell_nfc_op *nfc_op) +{ + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + const struct nand_op_instr *instr = nfc_op->data_instr; + unsigned int op_id = nfc_op->data_instr_idx; + unsigned int len = nand_subop_get_data_len(subop, op_id); + unsigned int offset = nand_subop_get_data_start_off(subop, op_id); + bool reading = (instr->type == NAND_OP_DATA_IN_INSTR); + int ret; + + if (instr->ctx.data.force_8bit) + marvell_nfc_force_byte_access(chip, true); + + if (reading) { + u8 *in = instr->ctx.data.buf.in + offset; + + ret = marvell_nfc_xfer_data_in_pio(nfc, in, len); + } else { + const u8 *out = instr->ctx.data.buf.out + offset; + + ret = marvell_nfc_xfer_data_out_pio(nfc, out, len); + } + + if (instr->ctx.data.force_8bit) + marvell_nfc_force_byte_access(chip, false); + + return ret; +} + +static int marvell_nfc_monolithic_access_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct marvell_nfc_op nfc_op; + bool reading; + int ret; + + marvell_nfc_parse_instructions(chip, subop, &nfc_op); + reading = (nfc_op.data_instr->type == NAND_OP_DATA_IN_INSTR); + + ret = marvell_nfc_prepare_cmd(chip); + if (ret) + return ret; + + marvell_nfc_send_cmd(chip, &nfc_op); + ret = marvell_nfc_end_cmd(chip, NDSR_RDDREQ | NDSR_WRDREQ, + "RDDREQ/WRDREQ while draining raw data"); + if (ret) + return ret; + + cond_delay(nfc_op.cle_ale_delay_ns); + + if (reading) { + if (nfc_op.rdy_timeout_ms) { + ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); + if (ret) + return ret; + } + + cond_delay(nfc_op.rdy_delay_ns); + } + + marvell_nfc_xfer_data_pio(chip, subop, &nfc_op); + ret = marvell_nfc_wait_cmdd(chip); + if (ret) + return ret; + + cond_delay(nfc_op.data_delay_ns); + + if (!reading) { + if (nfc_op.rdy_timeout_ms) { + ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); + if (ret) + return ret; + } + + cond_delay(nfc_op.rdy_delay_ns); + } + + /* + * NDCR ND_RUN bit should be cleared automatically at the end of each + * operation but experience shows that the behavior is buggy when it + * comes to writes (with LEN_OVRD). Clear it by hand in this case. + */ + if (!reading) { + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + + writel_relaxed(readl(nfc->regs + NDCR) & ~NDCR_ND_RUN, + nfc->regs + NDCR); + } + + return 0; +} + +static int marvell_nfc_naked_access_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct marvell_nfc_op nfc_op; + int ret; + + marvell_nfc_parse_instructions(chip, subop, &nfc_op); + + /* + * Naked access are different in that they need to be flagged as naked + * by the controller. Reset the controller registers fields that inform + * on the type and refill them according to the ongoing operation. + */ + nfc_op.ndcb[0] &= ~(NDCB0_CMD_TYPE(TYPE_MASK) | + NDCB0_CMD_XTYPE(XTYPE_MASK)); + switch (subop->instrs[0].type) { + case NAND_OP_CMD_INSTR: + nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_NAKED_CMD); + break; + case NAND_OP_ADDR_INSTR: + nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_NAKED_ADDR); + break; + case NAND_OP_DATA_IN_INSTR: + nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_READ) | + NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW); + break; + case NAND_OP_DATA_OUT_INSTR: + nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_WRITE) | + NDCB0_CMD_XTYPE(XTYPE_LAST_NAKED_RW); + break; + default: + /* This should never happen */ + break; + } + + ret = marvell_nfc_prepare_cmd(chip); + if (ret) + return ret; + + marvell_nfc_send_cmd(chip, &nfc_op); + + if (!nfc_op.data_instr) { + ret = marvell_nfc_wait_cmdd(chip); + cond_delay(nfc_op.cle_ale_delay_ns); + return ret; + } + + ret = marvell_nfc_end_cmd(chip, NDSR_RDDREQ | NDSR_WRDREQ, + "RDDREQ/WRDREQ while draining raw data"); + if (ret) + return ret; + + marvell_nfc_xfer_data_pio(chip, subop, &nfc_op); + ret = marvell_nfc_wait_cmdd(chip); + if (ret) + return ret; + + /* + * NDCR ND_RUN bit should be cleared automatically at the end of each + * operation but experience shows that the behavior is buggy when it + * comes to writes (with LEN_OVRD). Clear it by hand in this case. + */ + if (subop->instrs[0].type == NAND_OP_DATA_OUT_INSTR) { + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + + writel_relaxed(readl(nfc->regs + NDCR) & ~NDCR_ND_RUN, + nfc->regs + NDCR); + } + + return 0; +} + +static int marvell_nfc_naked_waitrdy_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct marvell_nfc_op nfc_op; + int ret; + + marvell_nfc_parse_instructions(chip, subop, &nfc_op); + + ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); + cond_delay(nfc_op.rdy_delay_ns); + + return ret; +} + +static int marvell_nfc_read_id_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct marvell_nfc_op nfc_op; + int ret; + + marvell_nfc_parse_instructions(chip, subop, &nfc_op); + nfc_op.ndcb[0] &= ~NDCB0_CMD_TYPE(TYPE_READ); + nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_READ_ID); + + ret = marvell_nfc_prepare_cmd(chip); + if (ret) + return ret; + + marvell_nfc_send_cmd(chip, &nfc_op); + ret = marvell_nfc_end_cmd(chip, NDSR_RDDREQ, + "RDDREQ while reading ID"); + if (ret) + return ret; + + cond_delay(nfc_op.cle_ale_delay_ns); + + if (nfc_op.rdy_timeout_ms) { + ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); + if (ret) + return ret; + } + + cond_delay(nfc_op.rdy_delay_ns); + + marvell_nfc_xfer_data_pio(chip, subop, &nfc_op); + ret = marvell_nfc_wait_cmdd(chip); + if (ret) + return ret; + + cond_delay(nfc_op.data_delay_ns); + + return 0; +} + +static int marvell_nfc_read_status_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct marvell_nfc_op nfc_op; + int ret; + + marvell_nfc_parse_instructions(chip, subop, &nfc_op); + nfc_op.ndcb[0] &= ~NDCB0_CMD_TYPE(TYPE_READ); + nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_STATUS); + + ret = marvell_nfc_prepare_cmd(chip); + if (ret) + return ret; + + marvell_nfc_send_cmd(chip, &nfc_op); + ret = marvell_nfc_end_cmd(chip, NDSR_RDDREQ, + "RDDREQ while reading status"); + if (ret) + return ret; + + cond_delay(nfc_op.cle_ale_delay_ns); + + if (nfc_op.rdy_timeout_ms) { + ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); + if (ret) + return ret; + } + + cond_delay(nfc_op.rdy_delay_ns); + + marvell_nfc_xfer_data_pio(chip, subop, &nfc_op); + ret = marvell_nfc_wait_cmdd(chip); + if (ret) + return ret; + + cond_delay(nfc_op.data_delay_ns); + + return 0; +} + +static int marvell_nfc_reset_cmd_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct marvell_nfc_op nfc_op; + int ret; + + marvell_nfc_parse_instructions(chip, subop, &nfc_op); + nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_RESET); + + ret = marvell_nfc_prepare_cmd(chip); + if (ret) + return ret; + + marvell_nfc_send_cmd(chip, &nfc_op); + ret = marvell_nfc_wait_cmdd(chip); + if (ret) + return ret; + + cond_delay(nfc_op.cle_ale_delay_ns); + + ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); + if (ret) + return ret; + + cond_delay(nfc_op.rdy_delay_ns); + + return 0; +} + +static int marvell_nfc_erase_cmd_type_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct marvell_nfc_op nfc_op; + int ret; + + marvell_nfc_parse_instructions(chip, subop, &nfc_op); + nfc_op.ndcb[0] |= NDCB0_CMD_TYPE(TYPE_ERASE); + + ret = marvell_nfc_prepare_cmd(chip); + if (ret) + return ret; + + marvell_nfc_send_cmd(chip, &nfc_op); + ret = marvell_nfc_wait_cmdd(chip); + if (ret) + return ret; + + cond_delay(nfc_op.cle_ale_delay_ns); + + ret = marvell_nfc_wait_op(chip, nfc_op.rdy_timeout_ms); + if (ret) + return ret; + + cond_delay(nfc_op.rdy_delay_ns); + + return 0; +} + +static const struct nand_op_parser marvell_nfcv2_op_parser = NAND_OP_PARSER( + /* Monolithic reads/writes */ + NAND_OP_PARSER_PATTERN( + marvell_nfc_monolithic_access_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, MAX_ADDRESS_CYC_NFCV2), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, MAX_CHUNK_SIZE)), + NAND_OP_PARSER_PATTERN( + marvell_nfc_monolithic_access_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYC_NFCV2), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, MAX_CHUNK_SIZE), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), + /* Naked commands */ + NAND_OP_PARSER_PATTERN( + marvell_nfc_naked_access_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false)), + NAND_OP_PARSER_PATTERN( + marvell_nfc_naked_access_exec, + NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYC_NFCV2)), + NAND_OP_PARSER_PATTERN( + marvell_nfc_naked_access_exec, + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, MAX_CHUNK_SIZE)), + NAND_OP_PARSER_PATTERN( + marvell_nfc_naked_access_exec, + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, MAX_CHUNK_SIZE)), + NAND_OP_PARSER_PATTERN( + marvell_nfc_naked_waitrdy_exec, + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + ); + +static const struct nand_op_parser marvell_nfcv1_op_parser = NAND_OP_PARSER( + /* Naked commands not supported, use a function for each pattern */ + NAND_OP_PARSER_PATTERN( + marvell_nfc_read_id_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYC_NFCV1), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 8)), + NAND_OP_PARSER_PATTERN( + marvell_nfc_erase_cmd_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYC_NFCV1), + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + NAND_OP_PARSER_PATTERN( + marvell_nfc_read_status_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 1)), + NAND_OP_PARSER_PATTERN( + marvell_nfc_reset_cmd_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + NAND_OP_PARSER_PATTERN( + marvell_nfc_naked_waitrdy_exec, + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + ); + +static int marvell_nfc_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + + if (!check_only) + marvell_nfc_select_target(chip, op->cs); + + if (nfc->caps->is_nfcv2) + return nand_op_parser_exec_op(chip, &marvell_nfcv2_op_parser, + op, check_only); + else + return nand_op_parser_exec_op(chip, &marvell_nfcv1_op_parser, + op, check_only); +} + +/* + * Layouts were broken in old pxa3xx_nand driver, these are supposed to be + * usable. + */ +static int marvell_nand_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout; + + if (section) + return -ERANGE; + + oobregion->length = (lt->full_chunk_cnt * lt->ecc_bytes) + + lt->last_ecc_bytes; + oobregion->offset = mtd->oobsize - oobregion->length; + + return 0; +} + +static int marvell_nand_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + const struct marvell_hw_ecc_layout *lt = to_marvell_nand(chip)->layout; + + if (section) + return -ERANGE; + + /* + * Bootrom looks in bytes 0 & 5 for bad blocks for the + * 4KB page / 4bit BCH combination. + */ + if (mtd->writesize == SZ_4K && lt->data_bytes == SZ_2K) + oobregion->offset = 6; + else + oobregion->offset = 2; + + oobregion->length = (lt->full_chunk_cnt * lt->spare_bytes) + + lt->last_spare_bytes - oobregion->offset; + + return 0; +} + +static const struct mtd_ooblayout_ops marvell_nand_ooblayout_ops = { + .ecc = marvell_nand_ooblayout_ecc, + .free = marvell_nand_ooblayout_free, +}; + +static int marvell_nand_hw_ecc_controller_init(struct mtd_info *mtd, + struct nand_ecc_ctrl *ecc) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + const struct marvell_hw_ecc_layout *l; + int i; + + if (!nfc->caps->is_nfcv2 && + (mtd->writesize + mtd->oobsize > MAX_CHUNK_SIZE)) { + dev_err(nfc->dev, + "NFCv1: writesize (%d) cannot be bigger than a chunk (%d)\n", + mtd->writesize, MAX_CHUNK_SIZE - mtd->oobsize); + return -ENOTSUPP; + } + + to_marvell_nand(chip)->layout = NULL; + for (i = 0; i < ARRAY_SIZE(marvell_nfc_layouts); i++) { + l = &marvell_nfc_layouts[i]; + if (mtd->writesize == l->writesize && + ecc->size == l->chunk && ecc->strength == l->strength) { + to_marvell_nand(chip)->layout = l; + break; + } + } + + if (!to_marvell_nand(chip)->layout || + (!nfc->caps->is_nfcv2 && ecc->strength > 1)) { + dev_err(nfc->dev, + "ECC strength %d at page size %d is not supported\n", + ecc->strength, mtd->writesize); + return -ENOTSUPP; + } + + /* Special care for the layout 2k/8-bit/512B */ + if (l->writesize == 2048 && l->strength == 8) { + if (mtd->oobsize < 128) { + dev_err(nfc->dev, "Requested layout needs at least 128 OOB bytes\n"); + return -ENOTSUPP; + } else { + chip->bbt_options |= NAND_BBT_NO_OOB_BBM; + } + } + + mtd_set_ooblayout(mtd, &marvell_nand_ooblayout_ops); + ecc->steps = l->nchunks; + ecc->size = l->data_bytes; + + if (ecc->strength == 1) { + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + ecc->read_page_raw = marvell_nfc_hw_ecc_hmg_read_page_raw; + ecc->read_page = marvell_nfc_hw_ecc_hmg_read_page; + ecc->read_oob_raw = marvell_nfc_hw_ecc_hmg_read_oob_raw; + ecc->read_oob = ecc->read_oob_raw; + ecc->write_page_raw = marvell_nfc_hw_ecc_hmg_write_page_raw; + ecc->write_page = marvell_nfc_hw_ecc_hmg_write_page; + ecc->write_oob_raw = marvell_nfc_hw_ecc_hmg_write_oob_raw; + ecc->write_oob = ecc->write_oob_raw; + } else { + chip->ecc.algo = NAND_ECC_ALGO_BCH; + ecc->strength = 16; + ecc->read_page_raw = marvell_nfc_hw_ecc_bch_read_page_raw; + ecc->read_page = marvell_nfc_hw_ecc_bch_read_page; + ecc->read_oob_raw = marvell_nfc_hw_ecc_bch_read_oob_raw; + ecc->read_oob = marvell_nfc_hw_ecc_bch_read_oob; + ecc->write_page_raw = marvell_nfc_hw_ecc_bch_write_page_raw; + ecc->write_page = marvell_nfc_hw_ecc_bch_write_page; + ecc->write_oob_raw = marvell_nfc_hw_ecc_bch_write_oob_raw; + ecc->write_oob = marvell_nfc_hw_ecc_bch_write_oob; + } + + return 0; +} + +static int marvell_nand_ecc_init(struct mtd_info *mtd, + struct nand_ecc_ctrl *ecc) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&chip->base); + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + int ret; + + if (ecc->engine_type != NAND_ECC_ENGINE_TYPE_NONE && + (!ecc->size || !ecc->strength)) { + if (requirements->step_size && requirements->strength) { + ecc->size = requirements->step_size; + ecc->strength = requirements->strength; + } else { + dev_info(nfc->dev, + "No minimum ECC strength, using 1b/512B\n"); + ecc->size = 512; + ecc->strength = 1; + } + } + + switch (ecc->engine_type) { + case NAND_ECC_ENGINE_TYPE_ON_HOST: + ret = marvell_nand_hw_ecc_controller_init(mtd, ecc); + if (ret) + return ret; + break; + case NAND_ECC_ENGINE_TYPE_NONE: + case NAND_ECC_ENGINE_TYPE_SOFT: + case NAND_ECC_ENGINE_TYPE_ON_DIE: + if (!nfc->caps->is_nfcv2 && mtd->writesize != SZ_512 && + mtd->writesize != SZ_2K) { + dev_err(nfc->dev, "NFCv1 cannot write %d bytes pages\n", + mtd->writesize); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static u8 bbt_pattern[] = {'M', 'V', 'B', 'b', 't', '0' }; +static u8 bbt_mirror_pattern[] = {'1', 't', 'b', 'B', 'V', 'M' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | + NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 8, + .len = 6, + .veroffs = 14, + .maxblocks = 8, /* Last 8 blocks in each chip */ + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | + NAND_BBT_2BIT | NAND_BBT_VERSION, + .offs = 8, + .len = 6, + .veroffs = 14, + .maxblocks = 8, /* Last 8 blocks in each chip */ + .pattern = bbt_mirror_pattern +}; + +static int marvell_nfc_setup_interface(struct nand_chip *chip, int chipnr, + const struct nand_interface_config *conf) +{ + struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip); + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + unsigned int period_ns = 1000000000 / clk_get_rate(nfc->core_clk) * 2; + const struct nand_sdr_timings *sdr; + struct marvell_nfc_timings nfc_tmg; + int read_delay; + + sdr = nand_get_sdr_timings(conf); + if (IS_ERR(sdr)) + return PTR_ERR(sdr); + + if (nfc->caps->max_mode_number && nfc->caps->max_mode_number < conf->timings.mode) + return -EOPNOTSUPP; + + /* + * SDR timings are given in pico-seconds while NFC timings must be + * expressed in NAND controller clock cycles, which is half of the + * frequency of the accessible ECC clock retrieved by clk_get_rate(). + * This is not written anywhere in the datasheet but was observed + * with an oscilloscope. + * + * NFC datasheet gives equations from which thoses calculations + * are derived, they tend to be slightly more restrictives than the + * given core timings and may improve the overall speed. + */ + nfc_tmg.tRP = TO_CYCLES(DIV_ROUND_UP(sdr->tRC_min, 2), period_ns) - 1; + nfc_tmg.tRH = nfc_tmg.tRP; + nfc_tmg.tWP = TO_CYCLES(DIV_ROUND_UP(sdr->tWC_min, 2), period_ns) - 1; + nfc_tmg.tWH = nfc_tmg.tWP; + nfc_tmg.tCS = TO_CYCLES(sdr->tCS_min, period_ns); + nfc_tmg.tCH = TO_CYCLES(sdr->tCH_min, period_ns) - 1; + nfc_tmg.tADL = TO_CYCLES(sdr->tADL_min, period_ns); + /* + * Read delay is the time of propagation from SoC pins to NFC internal + * logic. With non-EDO timings, this is MIN_RD_DEL_CNT clock cycles. In + * EDO mode, an additional delay of tRH must be taken into account so + * the data is sampled on the falling edge instead of the rising edge. + */ + read_delay = sdr->tRC_min >= 30000 ? + MIN_RD_DEL_CNT : MIN_RD_DEL_CNT + nfc_tmg.tRH; + + nfc_tmg.tAR = TO_CYCLES(sdr->tAR_min, period_ns); + /* + * tWHR and tRHW are supposed to be read to write delays (and vice + * versa) but in some cases, ie. when doing a change column, they must + * be greater than that to be sure tCCS delay is respected. + */ + nfc_tmg.tWHR = TO_CYCLES(max_t(int, sdr->tWHR_min, sdr->tCCS_min), + period_ns) - 2; + nfc_tmg.tRHW = TO_CYCLES(max_t(int, sdr->tRHW_min, sdr->tCCS_min), + period_ns); + + /* + * NFCv2: Use WAIT_MODE (wait for RB line), do not rely only on delays. + * NFCv1: No WAIT_MODE, tR must be maximal. + */ + if (nfc->caps->is_nfcv2) { + nfc_tmg.tR = TO_CYCLES(sdr->tWB_max, period_ns); + } else { + nfc_tmg.tR = TO_CYCLES64(sdr->tWB_max + sdr->tR_max, + period_ns); + if (nfc_tmg.tR + 3 > nfc_tmg.tCH) + nfc_tmg.tR = nfc_tmg.tCH - 3; + else + nfc_tmg.tR = 0; + } + + if (chipnr < 0) + return 0; + + marvell_nand->ndtr0 = + NDTR0_TRP(nfc_tmg.tRP) | + NDTR0_TRH(nfc_tmg.tRH) | + NDTR0_ETRP(nfc_tmg.tRP) | + NDTR0_TWP(nfc_tmg.tWP) | + NDTR0_TWH(nfc_tmg.tWH) | + NDTR0_TCS(nfc_tmg.tCS) | + NDTR0_TCH(nfc_tmg.tCH); + + marvell_nand->ndtr1 = + NDTR1_TAR(nfc_tmg.tAR) | + NDTR1_TWHR(nfc_tmg.tWHR) | + NDTR1_TR(nfc_tmg.tR); + + if (nfc->caps->is_nfcv2) { + marvell_nand->ndtr0 |= + NDTR0_RD_CNT_DEL(read_delay) | + NDTR0_SELCNTR | + NDTR0_TADL(nfc_tmg.tADL); + + marvell_nand->ndtr1 |= + NDTR1_TRHW(nfc_tmg.tRHW) | + NDTR1_WAIT_MODE; + } + + /* + * Reset nfc->selected_chip so the next command will cause the timing + * registers to be updated in marvell_nfc_select_target(). + */ + nfc->selected_chip = NULL; + + return 0; +} + +static int marvell_nand_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct marvell_nand_chip *marvell_nand = to_marvell_nand(chip); + struct marvell_nfc *nfc = to_marvell_nfc(chip->controller); + struct pxa3xx_nand_platform_data *pdata = dev_get_platdata(nfc->dev); + int ret; + + if (pdata && pdata->flash_bbt) + chip->bbt_options |= NAND_BBT_USE_FLASH; + + if (chip->bbt_options & NAND_BBT_USE_FLASH) { + /* + * We'll use a bad block table stored in-flash and don't + * allow writing the bad block marker to the flash. + */ + chip->bbt_options |= NAND_BBT_NO_OOB_BBM; + chip->bbt_td = &bbt_main_descr; + chip->bbt_md = &bbt_mirror_descr; + } + + /* Save the chip-specific fields of NDCR */ + marvell_nand->ndcr = NDCR_PAGE_SZ(mtd->writesize); + if (chip->options & NAND_BUSWIDTH_16) + marvell_nand->ndcr |= NDCR_DWIDTH_M | NDCR_DWIDTH_C; + + /* + * On small page NANDs, only one cycle is needed to pass the + * column address. + */ + if (mtd->writesize <= 512) { + marvell_nand->addr_cyc = 1; + } else { + marvell_nand->addr_cyc = 2; + marvell_nand->ndcr |= NDCR_RA_START; + } + + /* + * Now add the number of cycles needed to pass the row + * address. + * + * Addressing a chip using CS 2 or 3 should also need the third row + * cycle but due to inconsistance in the documentation and lack of + * hardware to test this situation, this case is not supported. + */ + if (chip->options & NAND_ROW_ADDR_3) + marvell_nand->addr_cyc += 3; + else + marvell_nand->addr_cyc += 2; + + if (pdata) { + chip->ecc.size = pdata->ecc_step_size; + chip->ecc.strength = pdata->ecc_strength; + } + + ret = marvell_nand_ecc_init(mtd, &chip->ecc); + if (ret) { + dev_err(nfc->dev, "ECC init failed: %d\n", ret); + return ret; + } + + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) { + /* + * Subpage write not available with hardware ECC, prohibit also + * subpage read as in userspace subpage access would still be + * allowed and subpage write, if used, would lead to numerous + * uncorrectable ECC errors. + */ + chip->options |= NAND_NO_SUBPAGE_WRITE; + } + + if (pdata || nfc->caps->legacy_of_bindings) { + /* + * We keep the MTD name unchanged to avoid breaking platforms + * where the MTD cmdline parser is used and the bootloader + * has not been updated to use the new naming scheme. + */ + mtd->name = "pxa3xx_nand-0"; + } else if (!mtd->name) { + /* + * If the new bindings are used and the bootloader has not been + * updated to pass a new mtdparts parameter on the cmdline, you + * should define the following property in your NAND node, ie: + * + * label = "main-storage"; + * + * This way, mtd->name will be set by the core when + * nand_set_flash_node() is called. + */ + mtd->name = devm_kasprintf(nfc->dev, GFP_KERNEL, + "%s:nand.%d", dev_name(nfc->dev), + marvell_nand->sels[0].cs); + if (!mtd->name) { + dev_err(nfc->dev, "Failed to allocate mtd->name\n"); + return -ENOMEM; + } + } + + return 0; +} + +static const struct nand_controller_ops marvell_nand_controller_ops = { + .attach_chip = marvell_nand_attach_chip, + .exec_op = marvell_nfc_exec_op, + .setup_interface = marvell_nfc_setup_interface, +}; + +static int marvell_nand_chip_init(struct device *dev, struct marvell_nfc *nfc, + struct device_node *np) +{ + struct pxa3xx_nand_platform_data *pdata = dev_get_platdata(dev); + struct marvell_nand_chip *marvell_nand; + struct mtd_info *mtd; + struct nand_chip *chip; + int nsels, ret, i; + u32 cs, rb; + + /* + * The legacy "num-cs" property indicates the number of CS on the only + * chip connected to the controller (legacy bindings does not support + * more than one chip). The CS and RB pins are always the #0. + * + * When not using legacy bindings, a couple of "reg" and "nand-rb" + * properties must be filled. For each chip, expressed as a subnode, + * "reg" points to the CS lines and "nand-rb" to the RB line. + */ + if (pdata || nfc->caps->legacy_of_bindings) { + nsels = 1; + } else { + nsels = of_property_count_elems_of_size(np, "reg", sizeof(u32)); + if (nsels <= 0) { + dev_err(dev, "missing/invalid reg property\n"); + return -EINVAL; + } + } + + /* Alloc the nand chip structure */ + marvell_nand = devm_kzalloc(dev, + struct_size(marvell_nand, sels, nsels), + GFP_KERNEL); + if (!marvell_nand) { + dev_err(dev, "could not allocate chip structure\n"); + return -ENOMEM; + } + + marvell_nand->nsels = nsels; + marvell_nand->selected_die = -1; + + for (i = 0; i < nsels; i++) { + if (pdata || nfc->caps->legacy_of_bindings) { + /* + * Legacy bindings use the CS lines in natural + * order (0, 1, ...) + */ + cs = i; + } else { + /* Retrieve CS id */ + ret = of_property_read_u32_index(np, "reg", i, &cs); + if (ret) { + dev_err(dev, "could not retrieve reg property: %d\n", + ret); + return ret; + } + } + + if (cs >= nfc->caps->max_cs_nb) { + dev_err(dev, "invalid reg value: %u (max CS = %d)\n", + cs, nfc->caps->max_cs_nb); + return -EINVAL; + } + + if (test_and_set_bit(cs, &nfc->assigned_cs)) { + dev_err(dev, "CS %d already assigned\n", cs); + return -EINVAL; + } + + /* + * The cs variable represents the chip select id, which must be + * converted in bit fields for NDCB0 and NDCB2 to select the + * right chip. Unfortunately, due to a lack of information on + * the subject and incoherent documentation, the user should not + * use CS1 and CS3 at all as asserting them is not supported in + * a reliable way (due to multiplexing inside ADDR5 field). + */ + marvell_nand->sels[i].cs = cs; + switch (cs) { + case 0: + case 2: + marvell_nand->sels[i].ndcb0_csel = 0; + break; + case 1: + case 3: + marvell_nand->sels[i].ndcb0_csel = NDCB0_CSEL; + break; + default: + return -EINVAL; + } + + /* Retrieve RB id */ + if (pdata || nfc->caps->legacy_of_bindings) { + /* Legacy bindings always use RB #0 */ + rb = 0; + } else { + ret = of_property_read_u32_index(np, "nand-rb", i, + &rb); + if (ret) { + dev_err(dev, + "could not retrieve RB property: %d\n", + ret); + return ret; + } + } + + if (rb >= nfc->caps->max_rb_nb) { + dev_err(dev, "invalid reg value: %u (max RB = %d)\n", + rb, nfc->caps->max_rb_nb); + return -EINVAL; + } + + marvell_nand->sels[i].rb = rb; + } + + chip = &marvell_nand->chip; + chip->controller = &nfc->controller; + nand_set_flash_node(chip, np); + + if (of_property_read_bool(np, "marvell,nand-keep-config")) + chip->options |= NAND_KEEP_TIMINGS; + + mtd = nand_to_mtd(chip); + mtd->dev.parent = dev; + + /* + * Save a reference value for timing registers before + * ->setup_interface() is called. + */ + marvell_nand->ndtr0 = readl_relaxed(nfc->regs + NDTR0); + marvell_nand->ndtr1 = readl_relaxed(nfc->regs + NDTR1); + + chip->options |= NAND_BUSWIDTH_AUTO; + + ret = nand_scan(chip, marvell_nand->nsels); + if (ret) { + dev_err(dev, "could not scan the nand chip\n"); + return ret; + } + + if (pdata) + /* Legacy bindings support only one chip */ + ret = mtd_device_register(mtd, pdata->parts, pdata->nr_parts); + else + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(dev, "failed to register mtd device: %d\n", ret); + nand_cleanup(chip); + return ret; + } + + list_add_tail(&marvell_nand->node, &nfc->chips); + + return 0; +} + +static void marvell_nand_chips_cleanup(struct marvell_nfc *nfc) +{ + struct marvell_nand_chip *entry, *temp; + struct nand_chip *chip; + int ret; + + list_for_each_entry_safe(entry, temp, &nfc->chips, node) { + chip = &entry->chip; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + list_del(&entry->node); + } +} + +static int marvell_nand_chips_init(struct device *dev, struct marvell_nfc *nfc) +{ + struct device_node *np = dev->of_node; + int max_cs = nfc->caps->max_cs_nb; + int nchips; + int ret; + + if (!np) + nchips = 1; + else + nchips = of_get_child_count(np); + + if (nchips > max_cs) { + dev_err(dev, "too many NAND chips: %d (max = %d CS)\n", nchips, + max_cs); + return -EINVAL; + } + + /* + * Legacy bindings do not use child nodes to exhibit NAND chip + * properties and layout. Instead, NAND properties are mixed with the + * controller ones, and partitions are defined as direct subnodes of the + * NAND controller node. + */ + if (nfc->caps->legacy_of_bindings) { + ret = marvell_nand_chip_init(dev, nfc, np); + return ret; + } + + for_each_child_of_node_scoped(np, nand_np) { + ret = marvell_nand_chip_init(dev, nfc, nand_np); + if (ret) { + marvell_nand_chips_cleanup(nfc); + return ret; + } + } + + return 0; +} + +static int marvell_nfc_init_dma(struct marvell_nfc *nfc) +{ + struct platform_device *pdev = container_of(nfc->dev, + struct platform_device, + dev); + struct dma_slave_config config = {}; + struct resource *r; + int ret; + + if (!IS_ENABLED(CONFIG_PXA_DMA)) { + dev_warn(nfc->dev, + "DMA not enabled in configuration\n"); + return -ENOTSUPP; + } + + ret = dma_set_mask_and_coherent(nfc->dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + nfc->dma_chan = dma_request_chan(nfc->dev, "data"); + if (IS_ERR(nfc->dma_chan)) { + ret = PTR_ERR(nfc->dma_chan); + nfc->dma_chan = NULL; + return dev_err_probe(nfc->dev, ret, "DMA channel request failed\n"); + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + ret = -ENXIO; + goto release_channel; + } + + config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + config.src_addr = r->start + NDDB; + config.dst_addr = r->start + NDDB; + config.src_maxburst = 32; + config.dst_maxburst = 32; + ret = dmaengine_slave_config(nfc->dma_chan, &config); + if (ret < 0) { + dev_err(nfc->dev, "Failed to configure DMA channel\n"); + goto release_channel; + } + + /* + * DMA must act on length multiple of 32 and this length may be + * bigger than the destination buffer. Use this buffer instead + * for DMA transfers and then copy the desired amount of data to + * the provided buffer. + */ + nfc->dma_buf = kmalloc(MAX_CHUNK_SIZE, GFP_KERNEL | GFP_DMA); + if (!nfc->dma_buf) { + ret = -ENOMEM; + goto release_channel; + } + + nfc->use_dma = true; + + return 0; + +release_channel: + dma_release_channel(nfc->dma_chan); + nfc->dma_chan = NULL; + + return ret; +} + +static void marvell_nfc_reset(struct marvell_nfc *nfc) +{ + /* + * ECC operations and interruptions are only enabled when specifically + * needed. ECC shall not be activated in the early stages (fails probe). + * Arbiter flag, even if marked as "reserved", must be set (empirical). + * SPARE_EN bit must always be set or ECC bytes will not be at the same + * offset in the read page and this will fail the protection. + */ + writel_relaxed(NDCR_ALL_INT | NDCR_ND_ARB_EN | NDCR_SPARE_EN | + NDCR_RD_ID_CNT(NFCV1_READID_LEN), nfc->regs + NDCR); + writel_relaxed(0xFFFFFFFF, nfc->regs + NDSR); + writel_relaxed(0, nfc->regs + NDECCCTRL); +} + +static int marvell_nfc_init(struct marvell_nfc *nfc) +{ + struct device_node *np = nfc->dev->of_node; + + /* + * Some SoCs like A7k/A8k need to enable manually the NAND + * controller, gated clocks and reset bits to avoid being bootloader + * dependent. This is done through the use of the System Functions + * registers. + */ + if (nfc->caps->need_system_controller) { + struct regmap *sysctrl_base = + syscon_regmap_lookup_by_phandle(np, + "marvell,system-controller"); + + if (IS_ERR(sysctrl_base)) + return PTR_ERR(sysctrl_base); + + regmap_write(sysctrl_base, GENCONF_SOC_DEVICE_MUX, + GENCONF_SOC_DEVICE_MUX_NFC_EN | + GENCONF_SOC_DEVICE_MUX_ECC_CLK_RST | + GENCONF_SOC_DEVICE_MUX_ECC_CORE_RST | + GENCONF_SOC_DEVICE_MUX_NFC_INT_EN | + GENCONF_SOC_DEVICE_MUX_NFC_DEVBUS_ARB_EN); + + regmap_update_bits(sysctrl_base, GENCONF_CLK_GATING_CTRL, + GENCONF_CLK_GATING_CTRL_ND_GATE, + GENCONF_CLK_GATING_CTRL_ND_GATE); + } + + /* Configure the DMA if appropriate */ + if (!nfc->caps->is_nfcv2) + marvell_nfc_init_dma(nfc); + + marvell_nfc_reset(nfc); + + return 0; +} + +static int marvell_nfc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct marvell_nfc *nfc; + int ret; + int irq; + + nfc = devm_kzalloc(&pdev->dev, sizeof(struct marvell_nfc), + GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->dev = dev; + nand_controller_init(&nfc->controller); + nfc->controller.ops = &marvell_nand_controller_ops; + INIT_LIST_HEAD(&nfc->chips); + + nfc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(nfc->regs)) + return PTR_ERR(nfc->regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + nfc->core_clk = devm_clk_get(&pdev->dev, "core"); + + /* Managed the legacy case (when the first clock was not named) */ + if (nfc->core_clk == ERR_PTR(-ENOENT)) + nfc->core_clk = devm_clk_get(&pdev->dev, NULL); + + if (IS_ERR(nfc->core_clk)) + return PTR_ERR(nfc->core_clk); + + ret = clk_prepare_enable(nfc->core_clk); + if (ret) + return ret; + + nfc->reg_clk = devm_clk_get(&pdev->dev, "reg"); + if (IS_ERR(nfc->reg_clk)) { + if (PTR_ERR(nfc->reg_clk) != -ENOENT) { + ret = PTR_ERR(nfc->reg_clk); + goto unprepare_core_clk; + } + + nfc->reg_clk = NULL; + } + + ret = clk_prepare_enable(nfc->reg_clk); + if (ret) + goto unprepare_core_clk; + + marvell_nfc_disable_int(nfc, NDCR_ALL_INT); + marvell_nfc_clear_int(nfc, NDCR_ALL_INT); + ret = devm_request_irq(dev, irq, marvell_nfc_isr, + 0, "marvell-nfc", nfc); + if (ret) + goto unprepare_reg_clk; + + /* Get NAND controller capabilities */ + if (pdev->id_entry) + nfc->caps = (void *)pdev->id_entry->driver_data; + else + nfc->caps = of_device_get_match_data(&pdev->dev); + + if (!nfc->caps) { + dev_err(dev, "Could not retrieve NFC caps\n"); + ret = -EINVAL; + goto unprepare_reg_clk; + } + + /* Init the controller and then probe the chips */ + ret = marvell_nfc_init(nfc); + if (ret) + goto unprepare_reg_clk; + + platform_set_drvdata(pdev, nfc); + + ret = marvell_nand_chips_init(dev, nfc); + if (ret) + goto release_dma; + + return 0; + +release_dma: + if (nfc->use_dma) + dma_release_channel(nfc->dma_chan); +unprepare_reg_clk: + clk_disable_unprepare(nfc->reg_clk); +unprepare_core_clk: + clk_disable_unprepare(nfc->core_clk); + + return ret; +} + +static void marvell_nfc_remove(struct platform_device *pdev) +{ + struct marvell_nfc *nfc = platform_get_drvdata(pdev); + + marvell_nand_chips_cleanup(nfc); + + if (nfc->use_dma) { + dmaengine_terminate_all(nfc->dma_chan); + dma_release_channel(nfc->dma_chan); + } + + clk_disable_unprepare(nfc->reg_clk); + clk_disable_unprepare(nfc->core_clk); +} + +static int __maybe_unused marvell_nfc_suspend(struct device *dev) +{ + struct marvell_nfc *nfc = dev_get_drvdata(dev); + struct marvell_nand_chip *chip; + + list_for_each_entry(chip, &nfc->chips, node) + marvell_nfc_wait_ndrun(&chip->chip); + + clk_disable_unprepare(nfc->reg_clk); + clk_disable_unprepare(nfc->core_clk); + + return 0; +} + +static int __maybe_unused marvell_nfc_resume(struct device *dev) +{ + struct marvell_nfc *nfc = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(nfc->core_clk); + if (ret < 0) + return ret; + + ret = clk_prepare_enable(nfc->reg_clk); + if (ret < 0) { + clk_disable_unprepare(nfc->core_clk); + return ret; + } + + /* + * Reset nfc->selected_chip so the next command will cause the timing + * registers to be restored in marvell_nfc_select_target(). + */ + nfc->selected_chip = NULL; + + /* Reset registers that have lost their contents */ + marvell_nfc_reset(nfc); + + return 0; +} + +static const struct dev_pm_ops marvell_nfc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(marvell_nfc_suspend, marvell_nfc_resume) +}; + +static const struct marvell_nfc_caps marvell_armada_8k_nfc_caps = { + .max_cs_nb = 4, + .max_rb_nb = 2, + .need_system_controller = true, + .is_nfcv2 = true, +}; + +static const struct marvell_nfc_caps marvell_ac5_caps = { + .max_cs_nb = 2, + .max_rb_nb = 1, + .is_nfcv2 = true, + .max_mode_number = 3, +}; + +static const struct marvell_nfc_caps marvell_armada370_nfc_caps = { + .max_cs_nb = 4, + .max_rb_nb = 2, + .is_nfcv2 = true, +}; + +static const struct marvell_nfc_caps marvell_pxa3xx_nfc_caps = { + .max_cs_nb = 2, + .max_rb_nb = 1, + .use_dma = true, +}; + +static const struct marvell_nfc_caps marvell_armada_8k_nfc_legacy_caps = { + .max_cs_nb = 4, + .max_rb_nb = 2, + .need_system_controller = true, + .legacy_of_bindings = true, + .is_nfcv2 = true, +}; + +static const struct marvell_nfc_caps marvell_armada370_nfc_legacy_caps = { + .max_cs_nb = 4, + .max_rb_nb = 2, + .legacy_of_bindings = true, + .is_nfcv2 = true, +}; + +static const struct marvell_nfc_caps marvell_pxa3xx_nfc_legacy_caps = { + .max_cs_nb = 2, + .max_rb_nb = 1, + .legacy_of_bindings = true, + .use_dma = true, +}; + +static const struct platform_device_id marvell_nfc_platform_ids[] = { + { + .name = "pxa3xx-nand", + .driver_data = (kernel_ulong_t)&marvell_pxa3xx_nfc_legacy_caps, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, marvell_nfc_platform_ids); + +static const struct of_device_id marvell_nfc_of_ids[] = { + { + .compatible = "marvell,armada-8k-nand-controller", + .data = &marvell_armada_8k_nfc_caps, + }, + { + .compatible = "marvell,ac5-nand-controller", + .data = &marvell_ac5_caps, + }, + { + .compatible = "marvell,armada370-nand-controller", + .data = &marvell_armada370_nfc_caps, + }, + { + .compatible = "marvell,pxa3xx-nand-controller", + .data = &marvell_pxa3xx_nfc_caps, + }, + /* Support for old/deprecated bindings: */ + { + .compatible = "marvell,armada-8k-nand", + .data = &marvell_armada_8k_nfc_legacy_caps, + }, + { + .compatible = "marvell,armada370-nand", + .data = &marvell_armada370_nfc_legacy_caps, + }, + { + .compatible = "marvell,pxa3xx-nand", + .data = &marvell_pxa3xx_nfc_legacy_caps, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, marvell_nfc_of_ids); + +static struct platform_driver marvell_nfc_driver = { + .driver = { + .name = "marvell-nfc", + .of_match_table = marvell_nfc_of_ids, + .pm = &marvell_nfc_pm_ops, + }, + .id_table = marvell_nfc_platform_ids, + .probe = marvell_nfc_probe, + .remove = marvell_nfc_remove, +}; +module_platform_driver(marvell_nfc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Marvell NAND controller driver"); diff --git a/drivers/mtd/nand/raw/meson_nand.c b/drivers/mtd/nand/raw/meson_nand.c new file mode 100644 index 000000000..b8834aa96 --- /dev/null +++ b/drivers/mtd/nand/raw/meson_nand.c @@ -0,0 +1,1634 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Amlogic Meson Nand Flash Controller Driver + * + * Copyright (c) 2018 Amlogic, inc. + * Author: Liang Yang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NFC_REG_CMD 0x00 +#define NFC_CMD_IDLE (0xc << 14) +#define NFC_CMD_CLE (0x5 << 14) +#define NFC_CMD_ALE (0x6 << 14) +#define NFC_CMD_ADL ((0 << 16) | (3 << 20)) +#define NFC_CMD_ADH ((1 << 16) | (3 << 20)) +#define NFC_CMD_AIL ((2 << 16) | (3 << 20)) +#define NFC_CMD_AIH ((3 << 16) | (3 << 20)) +#define NFC_CMD_SEED ((8 << 16) | (3 << 20)) +#define NFC_CMD_M2N ((0 << 17) | (2 << 20)) +#define NFC_CMD_N2M ((1 << 17) | (2 << 20)) +#define NFC_CMD_RB BIT(20) +#define NFC_CMD_SCRAMBLER_ENABLE BIT(19) +#define NFC_CMD_SCRAMBLER_DISABLE 0 +#define NFC_CMD_SHORTMODE_ENABLE 1 +#define NFC_CMD_SHORTMODE_DISABLE 0 +#define NFC_CMD_RB_INT BIT(14) +#define NFC_CMD_RB_INT_NO_PIN ((0xb << 10) | BIT(18) | BIT(16)) + +#define NFC_CMD_GET_SIZE(x) (((x) >> 22) & GENMASK(4, 0)) + +#define NFC_REG_CFG 0x04 +#define NFC_REG_DADR 0x08 +#define NFC_REG_IADR 0x0c +#define NFC_REG_BUF 0x10 +#define NFC_REG_INFO 0x14 +#define NFC_REG_DC 0x18 +#define NFC_REG_ADR 0x1c +#define NFC_REG_DL 0x20 +#define NFC_REG_DH 0x24 +#define NFC_REG_CADR 0x28 +#define NFC_REG_SADR 0x2c +#define NFC_REG_PINS 0x30 +#define NFC_REG_VER 0x38 + +#define NFC_RB_IRQ_EN BIT(21) + +#define CLK_DIV_SHIFT 0 +#define CLK_DIV_WIDTH 6 + +#define CMDRWGEN(cmd_dir, ran, bch, short_mode, page_size, pages) \ + ( \ + (cmd_dir) | \ + (ran) | \ + ((bch) << 14) | \ + ((short_mode) << 13) | \ + (((page_size) & 0x7f) << 6) | \ + ((pages) & 0x3f) \ + ) + +#define GENCMDDADDRL(adl, addr) ((adl) | ((addr) & 0xffff)) +#define GENCMDDADDRH(adh, addr) ((adh) | (((addr) >> 16) & 0xffff)) +#define GENCMDIADDRL(ail, addr) ((ail) | ((addr) & 0xffff)) +#define GENCMDIADDRH(aih, addr) ((aih) | (((addr) >> 16) & 0xffff)) + +#define DMA_DIR(dir) ((dir) ? NFC_CMD_N2M : NFC_CMD_M2N) +#define DMA_ADDR_ALIGN 8 + +#define NFC_SHORT_MODE_ECC_SZ 384 + +#define ECC_CHECK_RETURN_FF (-1) + +#define NAND_CE0 (0xe << 10) +#define NAND_CE1 (0xd << 10) + +#define DMA_BUSY_TIMEOUT 0x100000 +#define CMD_FIFO_EMPTY_TIMEOUT 1000 + +#define MAX_CE_NUM 2 + +/* eMMC clock register, misc control */ +#define CLK_SELECT_NAND BIT(31) +#define CLK_ALWAYS_ON_NAND BIT(24) +#define CLK_SELECT_FIX_PLL2 BIT(6) + +#define NFC_CLK_CYCLE 6 + +/* nand flash controller delay 3 ns */ +#define NFC_DEFAULT_DELAY 3000 + +#define ROW_ADDER(page, index) (((page) >> (8 * (index))) & 0xff) +#define MAX_CYCLE_ADDRS 5 +#define DIRREAD 1 +#define DIRWRITE 0 + +#define ECC_PARITY_BCH8_512B 14 +#define ECC_COMPLETE BIT(31) +#define ECC_ERR_CNT(x) (((x) >> 24) & GENMASK(5, 0)) +#define ECC_ZERO_CNT(x) (((x) >> 16) & GENMASK(5, 0)) +#define ECC_UNCORRECTABLE 0x3f + +#define PER_INFO_BYTE 8 + +#define NFC_CMD_RAW_LEN GENMASK(13, 0) + +#define NFC_COLUMN_ADDR_0 0 +#define NFC_COLUMN_ADDR_1 0 + +struct meson_nfc_nand_chip { + struct list_head node; + struct nand_chip nand; + unsigned long clk_rate; + unsigned long level1_divider; + u32 bus_timing; + u32 twb; + u32 tadl; + u32 tbers_max; + u32 boot_pages; + u32 boot_page_step; + + u32 bch_mode; + u8 *data_buf; + __le64 *info_buf; + u32 nsels; + u8 sels[] __counted_by(nsels); +}; + +struct meson_nand_ecc { + u32 bch; + u32 strength; + u32 size; +}; + +struct meson_nfc_data { + const struct nand_ecc_caps *ecc_caps; +}; + +struct meson_nfc_param { + u32 chip_select; + u32 rb_select; +}; + +struct nand_rw_cmd { + u32 cmd0; + u32 addrs[MAX_CYCLE_ADDRS]; + u32 cmd1; +}; + +struct nand_timing { + u32 twb; + u32 tadl; + u32 tbers_max; +}; + +struct meson_nfc { + struct nand_controller controller; + struct clk *core_clk; + struct clk *device_clk; + struct clk *nand_clk; + struct clk_divider nand_divider; + + unsigned long clk_rate; + u32 bus_timing; + + struct device *dev; + void __iomem *reg_base; + void __iomem *reg_clk; + struct completion completion; + struct list_head chips; + const struct meson_nfc_data *data; + struct meson_nfc_param param; + struct nand_timing timing; + union { + int cmd[32]; + struct nand_rw_cmd rw; + } cmdfifo; + + dma_addr_t daddr; + dma_addr_t iaddr; + u32 info_bytes; + + unsigned long assigned_cs; + bool no_rb_pin; +}; + +enum { + NFC_ECC_BCH8_512 = 1, + NFC_ECC_BCH8_1K, + NFC_ECC_BCH24_1K, + NFC_ECC_BCH30_1K, + NFC_ECC_BCH40_1K, + NFC_ECC_BCH50_1K, + NFC_ECC_BCH60_1K, +}; + +#define MESON_ECC_DATA(b, s, sz) { .bch = (b), .strength = (s), .size = (sz) } + +static struct meson_nand_ecc meson_ecc[] = { + MESON_ECC_DATA(NFC_ECC_BCH8_512, 8, 512), + MESON_ECC_DATA(NFC_ECC_BCH8_1K, 8, 1024), + MESON_ECC_DATA(NFC_ECC_BCH24_1K, 24, 1024), + MESON_ECC_DATA(NFC_ECC_BCH30_1K, 30, 1024), + MESON_ECC_DATA(NFC_ECC_BCH40_1K, 40, 1024), + MESON_ECC_DATA(NFC_ECC_BCH50_1K, 50, 1024), + MESON_ECC_DATA(NFC_ECC_BCH60_1K, 60, 1024), +}; + +static int meson_nand_calc_ecc_bytes(int step_size, int strength) +{ + int ecc_bytes; + + if (step_size == 512 && strength == 8) + return ECC_PARITY_BCH8_512B; + + ecc_bytes = DIV_ROUND_UP(strength * fls(step_size * 8), 8); + ecc_bytes = ALIGN(ecc_bytes, 2); + + return ecc_bytes; +} + +NAND_ECC_CAPS_SINGLE(meson_gxl_ecc_caps, + meson_nand_calc_ecc_bytes, 1024, 8, 24, 30, 40, 50, 60); + +static const int axg_stepinfo_strengths[] = { 8 }; + +static const struct nand_ecc_step_info axg_stepinfo[] = { + { + .stepsize = 1024, + .strengths = axg_stepinfo_strengths, + .nstrengths = ARRAY_SIZE(axg_stepinfo_strengths) + }, + { + .stepsize = 512, + .strengths = axg_stepinfo_strengths, + .nstrengths = ARRAY_SIZE(axg_stepinfo_strengths) + }, +}; + +static const struct nand_ecc_caps meson_axg_ecc_caps = { + .stepinfos = axg_stepinfo, + .nstepinfos = ARRAY_SIZE(axg_stepinfo), + .calc_ecc_bytes = meson_nand_calc_ecc_bytes, +}; + +static struct meson_nfc_nand_chip *to_meson_nand(struct nand_chip *nand) +{ + return container_of(nand, struct meson_nfc_nand_chip, nand); +} + +static void meson_nfc_select_chip(struct nand_chip *nand, int chip) +{ + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + struct meson_nfc *nfc = nand_get_controller_data(nand); + int ret, value; + + if (chip < 0 || WARN_ON_ONCE(chip >= meson_chip->nsels)) + return; + + nfc->param.chip_select = meson_chip->sels[chip] ? NAND_CE1 : NAND_CE0; + nfc->param.rb_select = nfc->param.chip_select; + nfc->timing.twb = meson_chip->twb; + nfc->timing.tadl = meson_chip->tadl; + nfc->timing.tbers_max = meson_chip->tbers_max; + + if (nfc->clk_rate != meson_chip->clk_rate) { + ret = clk_set_rate(nfc->nand_clk, meson_chip->clk_rate); + if (ret) { + dev_err(nfc->dev, "failed to set clock rate\n"); + return; + } + nfc->clk_rate = meson_chip->clk_rate; + } + if (nfc->bus_timing != meson_chip->bus_timing) { + value = (NFC_CLK_CYCLE - 1) | (meson_chip->bus_timing << 5); + writel(value, nfc->reg_base + NFC_REG_CFG); + writel((1 << 31), nfc->reg_base + NFC_REG_CMD); + nfc->bus_timing = meson_chip->bus_timing; + } +} + +static void meson_nfc_cmd_idle(struct meson_nfc *nfc, u32 time) +{ + writel(nfc->param.chip_select | NFC_CMD_IDLE | (time & 0x3ff), + nfc->reg_base + NFC_REG_CMD); +} + +static void meson_nfc_cmd_seed(struct meson_nfc *nfc, u32 seed) +{ + writel(NFC_CMD_SEED | (0xc2 + (seed & 0x7fff)), + nfc->reg_base + NFC_REG_CMD); +} + +static int meson_nfc_is_boot_page(struct nand_chip *nand, int page) +{ + const struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + + return (nand->options & NAND_IS_BOOT_MEDIUM) && + !(page % meson_chip->boot_page_step) && + (page < meson_chip->boot_pages); +} + +static void meson_nfc_cmd_access(struct nand_chip *nand, int raw, bool dir, int page) +{ + const struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + struct mtd_info *mtd = nand_to_mtd(nand); + struct meson_nfc *nfc = nand_get_controller_data(mtd_to_nand(mtd)); + int len = mtd->writesize, pagesize, pages; + int scrambler; + u32 cmd; + + if (nand->options & NAND_NEED_SCRAMBLING) + scrambler = NFC_CMD_SCRAMBLER_ENABLE; + else + scrambler = NFC_CMD_SCRAMBLER_DISABLE; + + if (raw) { + len = mtd->writesize + mtd->oobsize; + cmd = len | scrambler | DMA_DIR(dir); + } else if (meson_nfc_is_boot_page(nand, page)) { + pagesize = NFC_SHORT_MODE_ECC_SZ >> 3; + pages = mtd->writesize / 512; + + scrambler = NFC_CMD_SCRAMBLER_ENABLE; + cmd = CMDRWGEN(DMA_DIR(dir), scrambler, NFC_ECC_BCH8_1K, + NFC_CMD_SHORTMODE_ENABLE, pagesize, pages); + } else { + pagesize = nand->ecc.size >> 3; + pages = len / nand->ecc.size; + + cmd = CMDRWGEN(DMA_DIR(dir), scrambler, meson_chip->bch_mode, + NFC_CMD_SHORTMODE_DISABLE, pagesize, pages); + } + + if (scrambler == NFC_CMD_SCRAMBLER_ENABLE) + meson_nfc_cmd_seed(nfc, page); + + writel(cmd, nfc->reg_base + NFC_REG_CMD); +} + +static void meson_nfc_drain_cmd(struct meson_nfc *nfc) +{ + /* + * Insert two commands to make sure all valid commands are finished. + * + * The Nand flash controller is designed as two stages pipleline - + * a) fetch and b) excute. + * There might be cases when the driver see command queue is empty, + * but the Nand flash controller still has two commands buffered, + * one is fetched into NFC request queue (ready to run), and another + * is actively executing. So pushing 2 "IDLE" commands guarantees that + * the pipeline is emptied. + */ + meson_nfc_cmd_idle(nfc, 0); + meson_nfc_cmd_idle(nfc, 0); +} + +static int meson_nfc_wait_cmd_finish(struct meson_nfc *nfc, + unsigned int timeout_ms) +{ + u32 cmd_size = 0; + int ret; + + /* wait cmd fifo is empty */ + ret = readl_relaxed_poll_timeout(nfc->reg_base + NFC_REG_CMD, cmd_size, + !NFC_CMD_GET_SIZE(cmd_size), + 10, timeout_ms * 1000); + if (ret) + dev_err(nfc->dev, "wait for empty CMD FIFO time out\n"); + + return ret; +} + +static int meson_nfc_wait_dma_finish(struct meson_nfc *nfc) +{ + meson_nfc_drain_cmd(nfc); + + return meson_nfc_wait_cmd_finish(nfc, DMA_BUSY_TIMEOUT); +} + +static u8 *meson_nfc_oob_ptr(struct nand_chip *nand, int i) +{ + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + int len; + + len = nand->ecc.size * (i + 1) + (nand->ecc.bytes + 2) * i; + + return meson_chip->data_buf + len; +} + +static u8 *meson_nfc_data_ptr(struct nand_chip *nand, int i) +{ + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + int len, temp; + + temp = nand->ecc.size + nand->ecc.bytes; + len = (temp + 2) * i; + + return meson_chip->data_buf + len; +} + +static void meson_nfc_get_data_oob(struct nand_chip *nand, + u8 *buf, u8 *oobbuf) +{ + int i, oob_len = 0; + u8 *dsrc, *osrc; + + oob_len = nand->ecc.bytes + 2; + for (i = 0; i < nand->ecc.steps; i++) { + if (buf) { + dsrc = meson_nfc_data_ptr(nand, i); + memcpy(buf, dsrc, nand->ecc.size); + buf += nand->ecc.size; + } + osrc = meson_nfc_oob_ptr(nand, i); + memcpy(oobbuf, osrc, oob_len); + oobbuf += oob_len; + } +} + +static void meson_nfc_set_data_oob(struct nand_chip *nand, + const u8 *buf, u8 *oobbuf) +{ + int i, oob_len = 0; + u8 *dsrc, *osrc; + + oob_len = nand->ecc.bytes + 2; + for (i = 0; i < nand->ecc.steps; i++) { + if (buf) { + dsrc = meson_nfc_data_ptr(nand, i); + memcpy(dsrc, buf, nand->ecc.size); + buf += nand->ecc.size; + } + osrc = meson_nfc_oob_ptr(nand, i); + memcpy(osrc, oobbuf, oob_len); + oobbuf += oob_len; + } +} + +static int meson_nfc_wait_no_rb_pin(struct nand_chip *nand, int timeout_ms, + bool need_cmd_read0) +{ + struct meson_nfc *nfc = nand_get_controller_data(nand); + u32 cmd, cfg; + + meson_nfc_cmd_idle(nfc, nfc->timing.twb); + meson_nfc_drain_cmd(nfc); + meson_nfc_wait_cmd_finish(nfc, CMD_FIFO_EMPTY_TIMEOUT); + + cfg = readl(nfc->reg_base + NFC_REG_CFG); + cfg |= NFC_RB_IRQ_EN; + writel(cfg, nfc->reg_base + NFC_REG_CFG); + + reinit_completion(&nfc->completion); + nand_status_op(nand, NULL); + + /* use the max erase time as the maximum clock for waiting R/B */ + cmd = NFC_CMD_RB | NFC_CMD_RB_INT_NO_PIN | nfc->timing.tbers_max; + writel(cmd, nfc->reg_base + NFC_REG_CMD); + + if (!wait_for_completion_timeout(&nfc->completion, + msecs_to_jiffies(timeout_ms))) + return -ETIMEDOUT; + + if (need_cmd_read0) + nand_exit_status_op(nand); + + return 0; +} + +static int meson_nfc_wait_rb_pin(struct meson_nfc *nfc, int timeout_ms) +{ + u32 cmd, cfg; + int ret = 0; + + meson_nfc_cmd_idle(nfc, nfc->timing.twb); + meson_nfc_drain_cmd(nfc); + meson_nfc_wait_cmd_finish(nfc, CMD_FIFO_EMPTY_TIMEOUT); + + cfg = readl(nfc->reg_base + NFC_REG_CFG); + cfg |= NFC_RB_IRQ_EN; + writel(cfg, nfc->reg_base + NFC_REG_CFG); + + reinit_completion(&nfc->completion); + + /* use the max erase time as the maximum clock for waiting R/B */ + cmd = NFC_CMD_RB | NFC_CMD_RB_INT + | nfc->param.chip_select | nfc->timing.tbers_max; + writel(cmd, nfc->reg_base + NFC_REG_CMD); + + ret = wait_for_completion_timeout(&nfc->completion, + msecs_to_jiffies(timeout_ms)); + if (ret == 0) + ret = -1; + + return ret; +} + +static int meson_nfc_queue_rb(struct nand_chip *nand, int timeout_ms, + bool need_cmd_read0) +{ + struct meson_nfc *nfc = nand_get_controller_data(nand); + + if (nfc->no_rb_pin) { + /* This mode is used when there is no wired R/B pin. + * It works like 'nand_soft_waitrdy()', but instead of + * polling NAND_CMD_STATUS bit in the software loop, + * it will wait for interrupt - controllers checks IO + * bus and when it detects NAND_CMD_STATUS on it, it + * raises interrupt. After interrupt, NAND_CMD_READ0 is + * sent as terminator of the ready waiting procedure if + * needed (for all cases except page programming - this + * is reason of 'need_cmd_read0' flag). + */ + return meson_nfc_wait_no_rb_pin(nand, timeout_ms, + need_cmd_read0); + } else { + return meson_nfc_wait_rb_pin(nfc, timeout_ms); + } +} + +static void meson_nfc_set_user_byte(struct nand_chip *nand, u8 *oob_buf) +{ + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + __le64 *info; + int i, count; + + for (i = 0, count = 0; i < nand->ecc.steps; i++, count += (2 + nand->ecc.bytes)) { + info = &meson_chip->info_buf[i]; + *info |= oob_buf[count]; + *info |= oob_buf[count + 1] << 8; + } +} + +static void meson_nfc_get_user_byte(struct nand_chip *nand, u8 *oob_buf) +{ + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + __le64 *info; + int i, count; + + for (i = 0, count = 0; i < nand->ecc.steps; i++, count += (2 + nand->ecc.bytes)) { + info = &meson_chip->info_buf[i]; + oob_buf[count] = *info; + oob_buf[count + 1] = *info >> 8; + } +} + +static int meson_nfc_ecc_correct(struct nand_chip *nand, u32 *bitflips, + u64 *correct_bitmap) +{ + struct mtd_info *mtd = nand_to_mtd(nand); + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + __le64 *info; + int ret = 0, i; + + for (i = 0; i < nand->ecc.steps; i++) { + info = &meson_chip->info_buf[i]; + if (ECC_ERR_CNT(*info) != ECC_UNCORRECTABLE) { + mtd->ecc_stats.corrected += ECC_ERR_CNT(*info); + *bitflips = max_t(u32, *bitflips, ECC_ERR_CNT(*info)); + *correct_bitmap |= BIT_ULL(i); + continue; + } + if ((nand->options & NAND_NEED_SCRAMBLING) && + ECC_ZERO_CNT(*info) < nand->ecc.strength) { + mtd->ecc_stats.corrected += ECC_ZERO_CNT(*info); + *bitflips = max_t(u32, *bitflips, + ECC_ZERO_CNT(*info)); + ret = ECC_CHECK_RETURN_FF; + } else { + ret = -EBADMSG; + } + } + return ret; +} + +static int meson_nfc_dma_buffer_setup(struct nand_chip *nand, void *databuf, + int datalen, void *infobuf, int infolen, + enum dma_data_direction dir) +{ + struct meson_nfc *nfc = nand_get_controller_data(nand); + u32 cmd; + int ret = 0; + + nfc->daddr = dma_map_single(nfc->dev, databuf, datalen, dir); + ret = dma_mapping_error(nfc->dev, nfc->daddr); + if (ret) { + dev_err(nfc->dev, "DMA mapping error\n"); + return ret; + } + cmd = GENCMDDADDRL(NFC_CMD_ADL, nfc->daddr); + writel(cmd, nfc->reg_base + NFC_REG_CMD); + + cmd = GENCMDDADDRH(NFC_CMD_ADH, nfc->daddr); + writel(cmd, nfc->reg_base + NFC_REG_CMD); + + if (infobuf) { + nfc->iaddr = dma_map_single(nfc->dev, infobuf, infolen, dir); + ret = dma_mapping_error(nfc->dev, nfc->iaddr); + if (ret) { + dev_err(nfc->dev, "DMA mapping error\n"); + dma_unmap_single(nfc->dev, + nfc->daddr, datalen, dir); + return ret; + } + nfc->info_bytes = infolen; + cmd = GENCMDIADDRL(NFC_CMD_AIL, nfc->iaddr); + writel(cmd, nfc->reg_base + NFC_REG_CMD); + + cmd = GENCMDIADDRH(NFC_CMD_AIH, nfc->iaddr); + writel(cmd, nfc->reg_base + NFC_REG_CMD); + } + + return ret; +} + +static void meson_nfc_dma_buffer_release(struct nand_chip *nand, + int datalen, int infolen, + enum dma_data_direction dir) +{ + struct meson_nfc *nfc = nand_get_controller_data(nand); + + dma_unmap_single(nfc->dev, nfc->daddr, datalen, dir); + if (infolen) { + dma_unmap_single(nfc->dev, nfc->iaddr, infolen, dir); + nfc->info_bytes = 0; + } +} + +static int meson_nfc_read_buf(struct nand_chip *nand, u8 *buf, int len) +{ + struct meson_nfc *nfc = nand_get_controller_data(nand); + int ret = 0; + u32 cmd; + u8 *info; + + info = kzalloc(PER_INFO_BYTE, GFP_KERNEL); + if (!info) + return -ENOMEM; + + ret = meson_nfc_dma_buffer_setup(nand, buf, len, info, + PER_INFO_BYTE, DMA_FROM_DEVICE); + if (ret) + goto out; + + cmd = NFC_CMD_N2M | len; + writel(cmd, nfc->reg_base + NFC_REG_CMD); + + meson_nfc_drain_cmd(nfc); + meson_nfc_wait_cmd_finish(nfc, 1000); + meson_nfc_dma_buffer_release(nand, len, PER_INFO_BYTE, DMA_FROM_DEVICE); + +out: + kfree(info); + + return ret; +} + +static int meson_nfc_write_buf(struct nand_chip *nand, u8 *buf, int len) +{ + struct meson_nfc *nfc = nand_get_controller_data(nand); + int ret = 0; + u32 cmd; + + ret = meson_nfc_dma_buffer_setup(nand, buf, len, NULL, + 0, DMA_TO_DEVICE); + if (ret) + return ret; + + cmd = NFC_CMD_M2N | len; + writel(cmd, nfc->reg_base + NFC_REG_CMD); + + meson_nfc_drain_cmd(nfc); + meson_nfc_wait_cmd_finish(nfc, 1000); + meson_nfc_dma_buffer_release(nand, len, 0, DMA_TO_DEVICE); + + return ret; +} + +static int meson_nfc_rw_cmd_prepare_and_execute(struct nand_chip *nand, + int page, bool in) +{ + const struct nand_sdr_timings *sdr = + nand_get_sdr_timings(nand_get_interface_config(nand)); + struct mtd_info *mtd = nand_to_mtd(nand); + struct meson_nfc *nfc = nand_get_controller_data(nand); + u32 *addrs = nfc->cmdfifo.rw.addrs; + u32 cs = nfc->param.chip_select; + u32 cmd0, cmd_num, row_start; + int i; + + cmd_num = sizeof(struct nand_rw_cmd) / sizeof(int); + + cmd0 = in ? NAND_CMD_READ0 : NAND_CMD_SEQIN; + nfc->cmdfifo.rw.cmd0 = cs | NFC_CMD_CLE | cmd0; + + addrs[0] = cs | NFC_CMD_ALE | NFC_COLUMN_ADDR_0; + if (mtd->writesize <= 512) { + cmd_num--; + row_start = 1; + } else { + addrs[1] = cs | NFC_CMD_ALE | NFC_COLUMN_ADDR_1; + row_start = 2; + } + + addrs[row_start] = cs | NFC_CMD_ALE | ROW_ADDER(page, 0); + addrs[row_start + 1] = cs | NFC_CMD_ALE | ROW_ADDER(page, 1); + + if (nand->options & NAND_ROW_ADDR_3) + addrs[row_start + 2] = + cs | NFC_CMD_ALE | ROW_ADDER(page, 2); + else + cmd_num--; + + /* subtract cmd1 */ + cmd_num--; + + for (i = 0; i < cmd_num; i++) + writel_relaxed(nfc->cmdfifo.cmd[i], + nfc->reg_base + NFC_REG_CMD); + + if (in) { + nfc->cmdfifo.rw.cmd1 = cs | NFC_CMD_CLE | NAND_CMD_READSTART; + writel(nfc->cmdfifo.rw.cmd1, nfc->reg_base + NFC_REG_CMD); + meson_nfc_queue_rb(nand, PSEC_TO_MSEC(sdr->tR_max), true); + } else { + meson_nfc_cmd_idle(nfc, nfc->timing.tadl); + } + + return 0; +} + +static int meson_nfc_write_page_sub(struct nand_chip *nand, + int page, int raw) +{ + const struct nand_sdr_timings *sdr = + nand_get_sdr_timings(nand_get_interface_config(nand)); + struct mtd_info *mtd = nand_to_mtd(nand); + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + struct meson_nfc *nfc = nand_get_controller_data(nand); + int data_len, info_len; + u32 cmd; + int ret; + + meson_nfc_select_chip(nand, nand->cur_cs); + + data_len = mtd->writesize + mtd->oobsize; + info_len = nand->ecc.steps * PER_INFO_BYTE; + + ret = meson_nfc_rw_cmd_prepare_and_execute(nand, page, DIRWRITE); + if (ret) + return ret; + + ret = meson_nfc_dma_buffer_setup(nand, meson_chip->data_buf, + data_len, meson_chip->info_buf, + info_len, DMA_TO_DEVICE); + if (ret) + return ret; + + meson_nfc_cmd_access(nand, raw, DIRWRITE, page); + + cmd = nfc->param.chip_select | NFC_CMD_CLE | NAND_CMD_PAGEPROG; + writel(cmd, nfc->reg_base + NFC_REG_CMD); + meson_nfc_queue_rb(nand, PSEC_TO_MSEC(sdr->tPROG_max), false); + + meson_nfc_dma_buffer_release(nand, data_len, info_len, DMA_TO_DEVICE); + + return ret; +} + +static int meson_nfc_write_page_raw(struct nand_chip *nand, const u8 *buf, + int oob_required, int page) +{ + u8 *oob_buf = nand->oob_poi; + + meson_nfc_set_data_oob(nand, buf, oob_buf); + + return meson_nfc_write_page_sub(nand, page, 1); +} + +static int meson_nfc_write_page_hwecc(struct nand_chip *nand, + const u8 *buf, int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(nand); + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + u8 *oob_buf = nand->oob_poi; + + memcpy(meson_chip->data_buf, buf, mtd->writesize); + memset(meson_chip->info_buf, 0, nand->ecc.steps * PER_INFO_BYTE); + meson_nfc_set_user_byte(nand, oob_buf); + + return meson_nfc_write_page_sub(nand, page, 0); +} + +static void meson_nfc_check_ecc_pages_valid(struct meson_nfc *nfc, + struct nand_chip *nand, int raw) +{ + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + __le64 *info; + u32 neccpages; + int ret; + + neccpages = raw ? 1 : nand->ecc.steps; + info = &meson_chip->info_buf[neccpages - 1]; + do { + usleep_range(10, 15); + /* info is updated by nfc dma engine*/ + smp_rmb(); + dma_sync_single_for_cpu(nfc->dev, nfc->iaddr, nfc->info_bytes, + DMA_FROM_DEVICE); + ret = *info & ECC_COMPLETE; + } while (!ret); +} + +static int meson_nfc_read_page_sub(struct nand_chip *nand, + int page, int raw) +{ + struct mtd_info *mtd = nand_to_mtd(nand); + struct meson_nfc *nfc = nand_get_controller_data(nand); + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + int data_len, info_len; + int ret; + + meson_nfc_select_chip(nand, nand->cur_cs); + + data_len = mtd->writesize + mtd->oobsize; + info_len = nand->ecc.steps * PER_INFO_BYTE; + + ret = meson_nfc_rw_cmd_prepare_and_execute(nand, page, DIRREAD); + if (ret) + return ret; + + ret = meson_nfc_dma_buffer_setup(nand, meson_chip->data_buf, + data_len, meson_chip->info_buf, + info_len, DMA_FROM_DEVICE); + if (ret) + return ret; + + meson_nfc_cmd_access(nand, raw, DIRREAD, page); + + ret = meson_nfc_wait_dma_finish(nfc); + meson_nfc_check_ecc_pages_valid(nfc, nand, raw); + + meson_nfc_dma_buffer_release(nand, data_len, info_len, DMA_FROM_DEVICE); + + return ret; +} + +static int meson_nfc_read_page_raw(struct nand_chip *nand, u8 *buf, + int oob_required, int page) +{ + u8 *oob_buf = nand->oob_poi; + int ret; + + ret = meson_nfc_read_page_sub(nand, page, 1); + if (ret) + return ret; + + meson_nfc_get_data_oob(nand, buf, oob_buf); + + return 0; +} + +static int meson_nfc_read_page_hwecc(struct nand_chip *nand, u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(nand); + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + struct nand_ecc_ctrl *ecc = &nand->ecc; + u64 correct_bitmap = 0; + u32 bitflips = 0; + u8 *oob_buf = nand->oob_poi; + int ret, i; + + ret = meson_nfc_read_page_sub(nand, page, 0); + if (ret) + return ret; + + meson_nfc_get_user_byte(nand, oob_buf); + ret = meson_nfc_ecc_correct(nand, &bitflips, &correct_bitmap); + if (ret == ECC_CHECK_RETURN_FF) { + if (buf) + memset(buf, 0xff, mtd->writesize); + memset(oob_buf, 0xff, mtd->oobsize); + } else if (ret < 0) { + if ((nand->options & NAND_NEED_SCRAMBLING) || !buf) { + mtd->ecc_stats.failed++; + return bitflips; + } + ret = meson_nfc_read_page_raw(nand, buf, 0, page); + if (ret) + return ret; + + for (i = 0; i < nand->ecc.steps ; i++) { + u8 *data = buf + i * ecc->size; + u8 *oob = nand->oob_poi + i * (ecc->bytes + 2); + + if (correct_bitmap & BIT_ULL(i)) + continue; + ret = nand_check_erased_ecc_chunk(data, ecc->size, + oob, ecc->bytes + 2, + NULL, 0, + ecc->strength); + if (ret < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += ret; + bitflips = max_t(u32, bitflips, ret); + } + } + } else if (buf && buf != meson_chip->data_buf) { + memcpy(buf, meson_chip->data_buf, mtd->writesize); + } + + return bitflips; +} + +static int meson_nfc_read_oob_raw(struct nand_chip *nand, int page) +{ + return meson_nfc_read_page_raw(nand, NULL, 1, page); +} + +static int meson_nfc_read_oob(struct nand_chip *nand, int page) +{ + return meson_nfc_read_page_hwecc(nand, NULL, 1, page); +} + +static bool meson_nfc_is_buffer_dma_safe(const void *buffer) +{ + if ((uintptr_t)buffer % DMA_ADDR_ALIGN) + return false; + + if (virt_addr_valid(buffer) && (!object_is_on_stack(buffer))) + return true; + return false; +} + +static void * +meson_nand_op_get_dma_safe_input_buf(const struct nand_op_instr *instr) +{ + if (WARN_ON(instr->type != NAND_OP_DATA_IN_INSTR)) + return NULL; + + if (meson_nfc_is_buffer_dma_safe(instr->ctx.data.buf.in)) + return instr->ctx.data.buf.in; + + return kzalloc(instr->ctx.data.len, GFP_KERNEL); +} + +static void +meson_nand_op_put_dma_safe_input_buf(const struct nand_op_instr *instr, + void *buf) +{ + if (WARN_ON(instr->type != NAND_OP_DATA_IN_INSTR) || + WARN_ON(!buf)) + return; + + if (buf == instr->ctx.data.buf.in) + return; + + memcpy(instr->ctx.data.buf.in, buf, instr->ctx.data.len); + kfree(buf); +} + +static void * +meson_nand_op_get_dma_safe_output_buf(const struct nand_op_instr *instr) +{ + if (WARN_ON(instr->type != NAND_OP_DATA_OUT_INSTR)) + return NULL; + + if (meson_nfc_is_buffer_dma_safe(instr->ctx.data.buf.out)) + return (void *)instr->ctx.data.buf.out; + + return kmemdup(instr->ctx.data.buf.out, + instr->ctx.data.len, GFP_KERNEL); +} + +static void +meson_nand_op_put_dma_safe_output_buf(const struct nand_op_instr *instr, + const void *buf) +{ + if (WARN_ON(instr->type != NAND_OP_DATA_OUT_INSTR) || + WARN_ON(!buf)) + return; + + if (buf != instr->ctx.data.buf.out) + kfree(buf); +} + +static int meson_nfc_check_op(struct nand_chip *chip, + const struct nand_operation *op) +{ + int op_id; + + for (op_id = 0; op_id < op->ninstrs; op_id++) { + const struct nand_op_instr *instr; + + instr = &op->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_DATA_IN_INSTR: + case NAND_OP_DATA_OUT_INSTR: + if (instr->ctx.data.len > NFC_CMD_RAW_LEN) + return -ENOTSUPP; + + break; + default: + break; + } + } + + return 0; +} + +static int meson_nfc_exec_op(struct nand_chip *nand, + const struct nand_operation *op, bool check_only) +{ + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + struct meson_nfc *nfc = nand_get_controller_data(nand); + const struct nand_op_instr *instr = NULL; + void *buf; + u32 op_id, delay_idle, cmd; + int err; + int i; + + err = meson_nfc_check_op(nand, op); + if (err) + return err; + + if (check_only) + return 0; + + meson_nfc_select_chip(nand, op->cs); + for (op_id = 0; op_id < op->ninstrs; op_id++) { + instr = &op->instrs[op_id]; + delay_idle = DIV_ROUND_UP(PSEC_TO_NSEC(instr->delay_ns), + meson_chip->level1_divider * + NFC_CLK_CYCLE); + switch (instr->type) { + case NAND_OP_CMD_INSTR: + cmd = nfc->param.chip_select | NFC_CMD_CLE; + cmd |= instr->ctx.cmd.opcode & 0xff; + writel(cmd, nfc->reg_base + NFC_REG_CMD); + meson_nfc_cmd_idle(nfc, delay_idle); + break; + + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) { + cmd = nfc->param.chip_select | NFC_CMD_ALE; + cmd |= instr->ctx.addr.addrs[i] & 0xff; + writel(cmd, nfc->reg_base + NFC_REG_CMD); + } + meson_nfc_cmd_idle(nfc, delay_idle); + break; + + case NAND_OP_DATA_IN_INSTR: + buf = meson_nand_op_get_dma_safe_input_buf(instr); + if (!buf) + return -ENOMEM; + meson_nfc_read_buf(nand, buf, instr->ctx.data.len); + meson_nand_op_put_dma_safe_input_buf(instr, buf); + break; + + case NAND_OP_DATA_OUT_INSTR: + buf = meson_nand_op_get_dma_safe_output_buf(instr); + if (!buf) + return -ENOMEM; + meson_nfc_write_buf(nand, buf, instr->ctx.data.len); + meson_nand_op_put_dma_safe_output_buf(instr, buf); + break; + + case NAND_OP_WAITRDY_INSTR: + meson_nfc_queue_rb(nand, instr->ctx.waitrdy.timeout_ms, + true); + if (instr->delay_ns) + meson_nfc_cmd_idle(nfc, delay_idle); + break; + } + } + meson_nfc_wait_cmd_finish(nfc, 1000); + return 0; +} + +static int meson_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *nand = mtd_to_nand(mtd); + + if (section >= nand->ecc.steps) + return -ERANGE; + + oobregion->offset = 2 + (section * (2 + nand->ecc.bytes)); + oobregion->length = nand->ecc.bytes; + + return 0; +} + +static int meson_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *nand = mtd_to_nand(mtd); + + if (section >= nand->ecc.steps) + return -ERANGE; + + oobregion->offset = section * (2 + nand->ecc.bytes); + oobregion->length = 2; + + return 0; +} + +static const struct mtd_ooblayout_ops meson_ooblayout_ops = { + .ecc = meson_ooblayout_ecc, + .free = meson_ooblayout_free, +}; + +static int meson_nfc_clk_init(struct meson_nfc *nfc) +{ + struct clk_parent_data nfc_divider_parent_data[1] = {0}; + struct clk_init_data init = {0}; + int ret; + + /* request core clock */ + nfc->core_clk = devm_clk_get(nfc->dev, "core"); + if (IS_ERR(nfc->core_clk)) { + dev_err(nfc->dev, "failed to get core clock\n"); + return PTR_ERR(nfc->core_clk); + } + + nfc->device_clk = devm_clk_get(nfc->dev, "device"); + if (IS_ERR(nfc->device_clk)) { + dev_err(nfc->dev, "failed to get device clock\n"); + return PTR_ERR(nfc->device_clk); + } + + init.name = devm_kasprintf(nfc->dev, + GFP_KERNEL, "%s#div", + dev_name(nfc->dev)); + if (!init.name) + return -ENOMEM; + + init.ops = &clk_divider_ops; + nfc_divider_parent_data[0].fw_name = "device"; + init.parent_data = nfc_divider_parent_data; + init.num_parents = 1; + nfc->nand_divider.reg = nfc->reg_clk; + nfc->nand_divider.shift = CLK_DIV_SHIFT; + nfc->nand_divider.width = CLK_DIV_WIDTH; + nfc->nand_divider.hw.init = &init; + nfc->nand_divider.flags = CLK_DIVIDER_ONE_BASED | + CLK_DIVIDER_ROUND_CLOSEST | + CLK_DIVIDER_ALLOW_ZERO; + + nfc->nand_clk = devm_clk_register(nfc->dev, &nfc->nand_divider.hw); + if (IS_ERR(nfc->nand_clk)) + return PTR_ERR(nfc->nand_clk); + + /* init SD_EMMC_CLOCK to sane defaults w/min clock rate */ + writel(CLK_ALWAYS_ON_NAND | CLK_SELECT_NAND | CLK_SELECT_FIX_PLL2, + nfc->reg_clk); + + ret = clk_prepare_enable(nfc->core_clk); + if (ret) { + dev_err(nfc->dev, "failed to enable core clock\n"); + return ret; + } + + ret = clk_prepare_enable(nfc->device_clk); + if (ret) { + dev_err(nfc->dev, "failed to enable device clock\n"); + goto err_device_clk; + } + + ret = clk_prepare_enable(nfc->nand_clk); + if (ret) { + dev_err(nfc->dev, "pre enable NFC divider fail\n"); + goto err_nand_clk; + } + + ret = clk_set_rate(nfc->nand_clk, 24000000); + if (ret) + goto err_disable_clk; + + return 0; + +err_disable_clk: + clk_disable_unprepare(nfc->nand_clk); +err_nand_clk: + clk_disable_unprepare(nfc->device_clk); +err_device_clk: + clk_disable_unprepare(nfc->core_clk); + return ret; +} + +static void meson_nfc_disable_clk(struct meson_nfc *nfc) +{ + clk_disable_unprepare(nfc->nand_clk); + clk_disable_unprepare(nfc->device_clk); + clk_disable_unprepare(nfc->core_clk); +} + +static void meson_nfc_free_buffer(struct nand_chip *nand) +{ + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + + kfree(meson_chip->info_buf); + kfree(meson_chip->data_buf); +} + +static int meson_chip_buffer_init(struct nand_chip *nand) +{ + struct mtd_info *mtd = nand_to_mtd(nand); + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + u32 page_bytes, info_bytes, nsectors; + + nsectors = mtd->writesize / nand->ecc.size; + + page_bytes = mtd->writesize + mtd->oobsize; + info_bytes = nsectors * PER_INFO_BYTE; + + meson_chip->data_buf = kmalloc(page_bytes, GFP_KERNEL); + if (!meson_chip->data_buf) + return -ENOMEM; + + meson_chip->info_buf = kmalloc(info_bytes, GFP_KERNEL); + if (!meson_chip->info_buf) { + kfree(meson_chip->data_buf); + return -ENOMEM; + } + + return 0; +} + +static +int meson_nfc_setup_interface(struct nand_chip *nand, int csline, + const struct nand_interface_config *conf) +{ + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + const struct nand_sdr_timings *timings; + u32 div, bt_min, bt_max, tbers_clocks; + + timings = nand_get_sdr_timings(conf); + if (IS_ERR(timings)) + return -ENOTSUPP; + + if (csline == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + div = DIV_ROUND_UP((timings->tRC_min / 1000), NFC_CLK_CYCLE); + bt_min = (timings->tREA_max + NFC_DEFAULT_DELAY) / div; + bt_max = (NFC_DEFAULT_DELAY + timings->tRHOH_min + + timings->tRC_min / 2) / div; + + meson_chip->twb = DIV_ROUND_UP(PSEC_TO_NSEC(timings->tWB_max), + div * NFC_CLK_CYCLE); + meson_chip->tadl = DIV_ROUND_UP(PSEC_TO_NSEC(timings->tADL_min), + div * NFC_CLK_CYCLE); + tbers_clocks = DIV_ROUND_UP_ULL(PSEC_TO_NSEC(timings->tBERS_max), + div * NFC_CLK_CYCLE); + meson_chip->tbers_max = ilog2(tbers_clocks); + if (!is_power_of_2(tbers_clocks)) + meson_chip->tbers_max++; + + bt_min = DIV_ROUND_UP(bt_min, 1000); + bt_max = DIV_ROUND_UP(bt_max, 1000); + + if (bt_max < bt_min) + return -EINVAL; + + meson_chip->level1_divider = div; + meson_chip->clk_rate = 1000000000 / meson_chip->level1_divider; + meson_chip->bus_timing = (bt_min + bt_max) / 2 + 1; + + return 0; +} + +static int meson_nand_bch_mode(struct nand_chip *nand) +{ + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + int i; + + if (nand->ecc.strength > 60 || nand->ecc.strength < 8) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(meson_ecc); i++) { + if (meson_ecc[i].strength == nand->ecc.strength && + meson_ecc[i].size == nand->ecc.size) { + meson_chip->bch_mode = meson_ecc[i].bch; + return 0; + } + } + + return -EINVAL; +} + +static void meson_nand_detach_chip(struct nand_chip *nand) +{ + meson_nfc_free_buffer(nand); +} + +static int meson_nand_attach_chip(struct nand_chip *nand) +{ + struct meson_nfc *nfc = nand_get_controller_data(nand); + struct meson_nfc_nand_chip *meson_chip = to_meson_nand(nand); + struct mtd_info *mtd = nand_to_mtd(nand); + int raw_writesize; + int ret; + + if (!mtd->name) { + mtd->name = devm_kasprintf(nfc->dev, GFP_KERNEL, + "%s:nand%d", + dev_name(nfc->dev), + meson_chip->sels[0]); + if (!mtd->name) + return -ENOMEM; + } + + raw_writesize = mtd->writesize + mtd->oobsize; + if (raw_writesize > NFC_CMD_RAW_LEN) { + dev_err(nfc->dev, "too big write size in raw mode: %d > %ld\n", + raw_writesize, NFC_CMD_RAW_LEN); + return -EINVAL; + } + + if (nand->bbt_options & NAND_BBT_USE_FLASH) + nand->bbt_options |= NAND_BBT_NO_OOB; + + nand->options |= NAND_NO_SUBPAGE_WRITE; + + ret = nand_ecc_choose_conf(nand, nfc->data->ecc_caps, + mtd->oobsize - 2); + if (ret) { + dev_err(nfc->dev, "failed to ECC init\n"); + return -EINVAL; + } + + mtd_set_ooblayout(mtd, &meson_ooblayout_ops); + + ret = meson_nand_bch_mode(nand); + if (ret) + return -EINVAL; + + nand->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + nand->ecc.write_page_raw = meson_nfc_write_page_raw; + nand->ecc.write_page = meson_nfc_write_page_hwecc; + nand->ecc.write_oob_raw = nand_write_oob_std; + nand->ecc.write_oob = nand_write_oob_std; + + nand->ecc.read_page_raw = meson_nfc_read_page_raw; + nand->ecc.read_page = meson_nfc_read_page_hwecc; + nand->ecc.read_oob_raw = meson_nfc_read_oob_raw; + nand->ecc.read_oob = meson_nfc_read_oob; + + if (nand->options & NAND_BUSWIDTH_16) { + dev_err(nfc->dev, "16bits bus width not supported"); + return -EINVAL; + } + ret = meson_chip_buffer_init(nand); + if (ret) + return -ENOMEM; + + return ret; +} + +static const struct nand_controller_ops meson_nand_controller_ops = { + .attach_chip = meson_nand_attach_chip, + .detach_chip = meson_nand_detach_chip, + .setup_interface = meson_nfc_setup_interface, + .exec_op = meson_nfc_exec_op, +}; + +static int +meson_nfc_nand_chip_init(struct device *dev, + struct meson_nfc *nfc, struct device_node *np) +{ + struct meson_nfc_nand_chip *meson_chip; + struct nand_chip *nand; + struct mtd_info *mtd; + int ret, i; + u32 tmp, nsels; + u32 nand_rb_val = 0; + + nsels = of_property_count_elems_of_size(np, "reg", sizeof(u32)); + if (!nsels || nsels > MAX_CE_NUM) { + dev_err(dev, "invalid register property size\n"); + return -EINVAL; + } + + meson_chip = devm_kzalloc(dev, struct_size(meson_chip, sels, nsels), + GFP_KERNEL); + if (!meson_chip) + return -ENOMEM; + + meson_chip->nsels = nsels; + + for (i = 0; i < nsels; i++) { + ret = of_property_read_u32_index(np, "reg", i, &tmp); + if (ret) { + dev_err(dev, "could not retrieve register property: %d\n", + ret); + return ret; + } + + if (test_and_set_bit(tmp, &nfc->assigned_cs)) { + dev_err(dev, "CS %d already assigned\n", tmp); + return -EINVAL; + } + } + + nand = &meson_chip->nand; + nand->controller = &nfc->controller; + nand->controller->ops = &meson_nand_controller_ops; + nand_set_flash_node(nand, np); + nand_set_controller_data(nand, nfc); + + nand->options |= NAND_USES_DMA; + mtd = nand_to_mtd(nand); + mtd->owner = THIS_MODULE; + mtd->dev.parent = dev; + + ret = of_property_read_u32(np, "nand-rb", &nand_rb_val); + if (ret == -EINVAL) + nfc->no_rb_pin = true; + else if (ret) + return ret; + + if (nand_rb_val) + return -EINVAL; + + ret = nand_scan(nand, nsels); + if (ret) + return ret; + + if (nand->options & NAND_IS_BOOT_MEDIUM) { + ret = of_property_read_u32(np, "amlogic,boot-pages", + &meson_chip->boot_pages); + if (ret) { + dev_err(dev, "could not retrieve 'amlogic,boot-pages' property: %d", + ret); + nand_cleanup(nand); + return ret; + } + + ret = of_property_read_u32(np, "amlogic,boot-page-step", + &meson_chip->boot_page_step); + if (ret) { + dev_err(dev, "could not retrieve 'amlogic,boot-page-step' property: %d", + ret); + nand_cleanup(nand); + return ret; + } + } + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(dev, "failed to register MTD device: %d\n", ret); + nand_cleanup(nand); + return ret; + } + + list_add_tail(&meson_chip->node, &nfc->chips); + + return 0; +} + +static void meson_nfc_nand_chips_cleanup(struct meson_nfc *nfc) +{ + struct meson_nfc_nand_chip *meson_chip; + struct mtd_info *mtd; + + while (!list_empty(&nfc->chips)) { + meson_chip = list_first_entry(&nfc->chips, + struct meson_nfc_nand_chip, node); + mtd = nand_to_mtd(&meson_chip->nand); + WARN_ON(mtd_device_unregister(mtd)); + + nand_cleanup(&meson_chip->nand); + list_del(&meson_chip->node); + } +} + +static int meson_nfc_nand_chips_init(struct device *dev, + struct meson_nfc *nfc) +{ + struct device_node *np = dev->of_node; + int ret; + + for_each_child_of_node_scoped(np, nand_np) { + ret = meson_nfc_nand_chip_init(dev, nfc, nand_np); + if (ret) { + meson_nfc_nand_chips_cleanup(nfc); + return ret; + } + } + + return 0; +} + +static irqreturn_t meson_nfc_irq(int irq, void *id) +{ + struct meson_nfc *nfc = id; + u32 cfg; + + cfg = readl(nfc->reg_base + NFC_REG_CFG); + if (!(cfg & NFC_RB_IRQ_EN)) + return IRQ_NONE; + + cfg &= ~(NFC_RB_IRQ_EN); + writel(cfg, nfc->reg_base + NFC_REG_CFG); + + complete(&nfc->completion); + return IRQ_HANDLED; +} + +static const struct meson_nfc_data meson_gxl_data = { + .ecc_caps = &meson_gxl_ecc_caps, +}; + +static const struct meson_nfc_data meson_axg_data = { + .ecc_caps = &meson_axg_ecc_caps, +}; + +static const struct of_device_id meson_nfc_id_table[] = { + { + .compatible = "amlogic,meson-gxl-nfc", + .data = &meson_gxl_data, + }, { + .compatible = "amlogic,meson-axg-nfc", + .data = &meson_axg_data, + }, + {} +}; +MODULE_DEVICE_TABLE(of, meson_nfc_id_table); + +static int meson_nfc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct meson_nfc *nfc; + int ret, irq; + + nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->data = of_device_get_match_data(&pdev->dev); + if (!nfc->data) + return -ENODEV; + + nand_controller_init(&nfc->controller); + INIT_LIST_HEAD(&nfc->chips); + init_completion(&nfc->completion); + + nfc->dev = dev; + + nfc->reg_base = devm_platform_ioremap_resource_byname(pdev, "nfc"); + if (IS_ERR(nfc->reg_base)) + return PTR_ERR(nfc->reg_base); + + nfc->reg_clk = devm_platform_ioremap_resource_byname(pdev, "emmc"); + if (IS_ERR(nfc->reg_clk)) + return PTR_ERR(nfc->reg_clk); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return -EINVAL; + + ret = meson_nfc_clk_init(nfc); + if (ret) { + dev_err(dev, "failed to initialize NAND clock\n"); + return ret; + } + + writel(0, nfc->reg_base + NFC_REG_CFG); + ret = devm_request_irq(dev, irq, meson_nfc_irq, 0, dev_name(dev), nfc); + if (ret) { + dev_err(dev, "failed to request NFC IRQ\n"); + ret = -EINVAL; + goto err_clk; + } + + ret = dma_set_mask(dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(dev, "failed to set DMA mask\n"); + goto err_clk; + } + + platform_set_drvdata(pdev, nfc); + + ret = meson_nfc_nand_chips_init(dev, nfc); + if (ret) { + dev_err(dev, "failed to init NAND chips\n"); + goto err_clk; + } + + return 0; +err_clk: + meson_nfc_disable_clk(nfc); + return ret; +} + +static void meson_nfc_remove(struct platform_device *pdev) +{ + struct meson_nfc *nfc = platform_get_drvdata(pdev); + + meson_nfc_nand_chips_cleanup(nfc); + + meson_nfc_disable_clk(nfc); +} + +static struct platform_driver meson_nfc_driver = { + .probe = meson_nfc_probe, + .remove = meson_nfc_remove, + .driver = { + .name = "meson-nand", + .of_match_table = meson_nfc_id_table, + }, +}; +module_platform_driver(meson_nfc_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_AUTHOR("Liang Yang "); +MODULE_DESCRIPTION("Amlogic's Meson NAND Flash Controller driver"); diff --git a/drivers/mtd/nand/raw/mpc5121_nfc.c b/drivers/mtd/nand/raw/mpc5121_nfc.c new file mode 100644 index 000000000..e6594548b --- /dev/null +++ b/drivers/mtd/nand/raw/mpc5121_nfc.c @@ -0,0 +1,834 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright 2004-2008 Freescale Semiconductor, Inc. + * Copyright 2009 Semihalf. + * + * Approved as OSADL project by a majority of OSADL members and funded + * by OSADL membership fees in 2009; for details see www.osadl.org. + * + * Based on original driver from Freescale Semiconductor + * written by John Rigby on basis of mxc_nand.c. + * Reworked and extended by Piotr Ziecik . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Addresses for NFC MAIN RAM BUFFER areas */ +#define NFC_MAIN_AREA(n) ((n) * 0x200) + +/* Addresses for NFC SPARE BUFFER areas */ +#define NFC_SPARE_BUFFERS 8 +#define NFC_SPARE_LEN 0x40 +#define NFC_SPARE_AREA(n) (0x1000 + ((n) * NFC_SPARE_LEN)) + +/* MPC5121 NFC registers */ +#define NFC_BUF_ADDR 0x1E04 +#define NFC_FLASH_ADDR 0x1E06 +#define NFC_FLASH_CMD 0x1E08 +#define NFC_CONFIG 0x1E0A +#define NFC_ECC_STATUS1 0x1E0C +#define NFC_ECC_STATUS2 0x1E0E +#define NFC_SPAS 0x1E10 +#define NFC_WRPROT 0x1E12 +#define NFC_NF_WRPRST 0x1E18 +#define NFC_CONFIG1 0x1E1A +#define NFC_CONFIG2 0x1E1C +#define NFC_UNLOCKSTART_BLK0 0x1E20 +#define NFC_UNLOCKEND_BLK0 0x1E22 +#define NFC_UNLOCKSTART_BLK1 0x1E24 +#define NFC_UNLOCKEND_BLK1 0x1E26 +#define NFC_UNLOCKSTART_BLK2 0x1E28 +#define NFC_UNLOCKEND_BLK2 0x1E2A +#define NFC_UNLOCKSTART_BLK3 0x1E2C +#define NFC_UNLOCKEND_BLK3 0x1E2E + +/* Bit Definitions: NFC_BUF_ADDR */ +#define NFC_RBA_MASK (7 << 0) +#define NFC_ACTIVE_CS_SHIFT 5 +#define NFC_ACTIVE_CS_MASK (3 << NFC_ACTIVE_CS_SHIFT) + +/* Bit Definitions: NFC_CONFIG */ +#define NFC_BLS_UNLOCKED (1 << 1) + +/* Bit Definitions: NFC_CONFIG1 */ +#define NFC_ECC_4BIT (1 << 0) +#define NFC_FULL_PAGE_DMA (1 << 1) +#define NFC_SPARE_ONLY (1 << 2) +#define NFC_ECC_ENABLE (1 << 3) +#define NFC_INT_MASK (1 << 4) +#define NFC_BIG_ENDIAN (1 << 5) +#define NFC_RESET (1 << 6) +#define NFC_CE (1 << 7) +#define NFC_ONE_CYCLE (1 << 8) +#define NFC_PPB_32 (0 << 9) +#define NFC_PPB_64 (1 << 9) +#define NFC_PPB_128 (2 << 9) +#define NFC_PPB_256 (3 << 9) +#define NFC_PPB_MASK (3 << 9) +#define NFC_FULL_PAGE_INT (1 << 11) + +/* Bit Definitions: NFC_CONFIG2 */ +#define NFC_COMMAND (1 << 0) +#define NFC_ADDRESS (1 << 1) +#define NFC_INPUT (1 << 2) +#define NFC_OUTPUT (1 << 3) +#define NFC_ID (1 << 4) +#define NFC_STATUS (1 << 5) +#define NFC_CMD_FAIL (1 << 15) +#define NFC_INT (1 << 15) + +/* Bit Definitions: NFC_WRPROT */ +#define NFC_WPC_LOCK_TIGHT (1 << 0) +#define NFC_WPC_LOCK (1 << 1) +#define NFC_WPC_UNLOCK (1 << 2) + +#define DRV_NAME "mpc5121_nfc" + +/* Timeouts */ +#define NFC_RESET_TIMEOUT 1000 /* 1 ms */ +#define NFC_TIMEOUT (HZ / 10) /* 1/10 s */ + +struct mpc5121_nfc_prv { + struct nand_controller controller; + struct nand_chip chip; + int irq; + void __iomem *regs; + struct clk *clk; + wait_queue_head_t irq_waitq; + uint column; + int spareonly; + void __iomem *csreg; + struct device *dev; +}; + +static void mpc5121_nfc_done(struct mtd_info *mtd); + +/* Read NFC register */ +static inline u16 nfc_read(struct mtd_info *mtd, uint reg) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mpc5121_nfc_prv *prv = nand_get_controller_data(chip); + + return in_be16(prv->regs + reg); +} + +/* Write NFC register */ +static inline void nfc_write(struct mtd_info *mtd, uint reg, u16 val) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mpc5121_nfc_prv *prv = nand_get_controller_data(chip); + + out_be16(prv->regs + reg, val); +} + +/* Set bits in NFC register */ +static inline void nfc_set(struct mtd_info *mtd, uint reg, u16 bits) +{ + nfc_write(mtd, reg, nfc_read(mtd, reg) | bits); +} + +/* Clear bits in NFC register */ +static inline void nfc_clear(struct mtd_info *mtd, uint reg, u16 bits) +{ + nfc_write(mtd, reg, nfc_read(mtd, reg) & ~bits); +} + +/* Invoke address cycle */ +static inline void mpc5121_nfc_send_addr(struct mtd_info *mtd, u16 addr) +{ + nfc_write(mtd, NFC_FLASH_ADDR, addr); + nfc_write(mtd, NFC_CONFIG2, NFC_ADDRESS); + mpc5121_nfc_done(mtd); +} + +/* Invoke command cycle */ +static inline void mpc5121_nfc_send_cmd(struct mtd_info *mtd, u16 cmd) +{ + nfc_write(mtd, NFC_FLASH_CMD, cmd); + nfc_write(mtd, NFC_CONFIG2, NFC_COMMAND); + mpc5121_nfc_done(mtd); +} + +/* Send data from NFC buffers to NAND flash */ +static inline void mpc5121_nfc_send_prog_page(struct mtd_info *mtd) +{ + nfc_clear(mtd, NFC_BUF_ADDR, NFC_RBA_MASK); + nfc_write(mtd, NFC_CONFIG2, NFC_INPUT); + mpc5121_nfc_done(mtd); +} + +/* Receive data from NAND flash */ +static inline void mpc5121_nfc_send_read_page(struct mtd_info *mtd) +{ + nfc_clear(mtd, NFC_BUF_ADDR, NFC_RBA_MASK); + nfc_write(mtd, NFC_CONFIG2, NFC_OUTPUT); + mpc5121_nfc_done(mtd); +} + +/* Receive ID from NAND flash */ +static inline void mpc5121_nfc_send_read_id(struct mtd_info *mtd) +{ + nfc_clear(mtd, NFC_BUF_ADDR, NFC_RBA_MASK); + nfc_write(mtd, NFC_CONFIG2, NFC_ID); + mpc5121_nfc_done(mtd); +} + +/* Receive status from NAND flash */ +static inline void mpc5121_nfc_send_read_status(struct mtd_info *mtd) +{ + nfc_clear(mtd, NFC_BUF_ADDR, NFC_RBA_MASK); + nfc_write(mtd, NFC_CONFIG2, NFC_STATUS); + mpc5121_nfc_done(mtd); +} + +/* NFC interrupt handler */ +static irqreturn_t mpc5121_nfc_irq(int irq, void *data) +{ + struct mtd_info *mtd = data; + struct nand_chip *chip = mtd_to_nand(mtd); + struct mpc5121_nfc_prv *prv = nand_get_controller_data(chip); + + nfc_set(mtd, NFC_CONFIG1, NFC_INT_MASK); + wake_up(&prv->irq_waitq); + + return IRQ_HANDLED; +} + +/* Wait for operation complete */ +static void mpc5121_nfc_done(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mpc5121_nfc_prv *prv = nand_get_controller_data(chip); + int rv; + + if ((nfc_read(mtd, NFC_CONFIG2) & NFC_INT) == 0) { + nfc_clear(mtd, NFC_CONFIG1, NFC_INT_MASK); + rv = wait_event_timeout(prv->irq_waitq, + (nfc_read(mtd, NFC_CONFIG2) & NFC_INT), NFC_TIMEOUT); + + if (!rv) + dev_warn(prv->dev, + "Timeout while waiting for interrupt.\n"); + } + + nfc_clear(mtd, NFC_CONFIG2, NFC_INT); +} + +/* Do address cycle(s) */ +static void mpc5121_nfc_addr_cycle(struct mtd_info *mtd, int column, int page) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + u32 pagemask = chip->pagemask; + + if (column != -1) { + mpc5121_nfc_send_addr(mtd, column); + if (mtd->writesize > 512) + mpc5121_nfc_send_addr(mtd, column >> 8); + } + + if (page != -1) { + do { + mpc5121_nfc_send_addr(mtd, page & 0xFF); + page >>= 8; + pagemask >>= 8; + } while (pagemask); + } +} + +/* Control chip select signals */ +static void mpc5121_nfc_select_chip(struct nand_chip *nand, int chip) +{ + struct mtd_info *mtd = nand_to_mtd(nand); + + if (chip < 0) { + nfc_clear(mtd, NFC_CONFIG1, NFC_CE); + return; + } + + nfc_clear(mtd, NFC_BUF_ADDR, NFC_ACTIVE_CS_MASK); + nfc_set(mtd, NFC_BUF_ADDR, (chip << NFC_ACTIVE_CS_SHIFT) & + NFC_ACTIVE_CS_MASK); + nfc_set(mtd, NFC_CONFIG1, NFC_CE); +} + +/* Init external chip select logic on ADS5121 board */ +static int ads5121_chipselect_init(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mpc5121_nfc_prv *prv = nand_get_controller_data(chip); + struct device_node *dn; + + dn = of_find_compatible_node(NULL, NULL, "fsl,mpc5121ads-cpld"); + if (dn) { + prv->csreg = of_iomap(dn, 0); + of_node_put(dn); + if (!prv->csreg) + return -ENOMEM; + + /* CPLD Register 9 controls NAND /CE Lines */ + prv->csreg += 9; + return 0; + } + + return -EINVAL; +} + +/* Control chips select signal on ADS5121 board */ +static void ads5121_select_chip(struct nand_chip *nand, int chip) +{ + struct mpc5121_nfc_prv *prv = nand_get_controller_data(nand); + u8 v; + + v = in_8(prv->csreg); + v |= 0x0F; + + if (chip >= 0) { + mpc5121_nfc_select_chip(nand, 0); + v &= ~(1 << chip); + } else + mpc5121_nfc_select_chip(nand, -1); + + out_8(prv->csreg, v); +} + +/* Read NAND Ready/Busy signal */ +static int mpc5121_nfc_dev_ready(struct nand_chip *nand) +{ + /* + * NFC handles ready/busy signal internally. Therefore, this function + * always returns status as ready. + */ + return 1; +} + +/* Write command to NAND flash */ +static void mpc5121_nfc_command(struct nand_chip *chip, unsigned command, + int column, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mpc5121_nfc_prv *prv = nand_get_controller_data(chip); + + prv->column = (column >= 0) ? column : 0; + prv->spareonly = 0; + + switch (command) { + case NAND_CMD_PAGEPROG: + mpc5121_nfc_send_prog_page(mtd); + break; + /* + * NFC does not support sub-page reads and writes, + * so emulate them using full page transfers. + */ + case NAND_CMD_READ0: + column = 0; + break; + + case NAND_CMD_READ1: + prv->column += 256; + command = NAND_CMD_READ0; + column = 0; + break; + + case NAND_CMD_READOOB: + prv->spareonly = 1; + command = NAND_CMD_READ0; + column = 0; + break; + + case NAND_CMD_SEQIN: + mpc5121_nfc_command(chip, NAND_CMD_READ0, column, page); + column = 0; + break; + + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + case NAND_CMD_READID: + case NAND_CMD_STATUS: + break; + + default: + return; + } + + mpc5121_nfc_send_cmd(mtd, command); + mpc5121_nfc_addr_cycle(mtd, column, page); + + switch (command) { + case NAND_CMD_READ0: + if (mtd->writesize > 512) + mpc5121_nfc_send_cmd(mtd, NAND_CMD_READSTART); + mpc5121_nfc_send_read_page(mtd); + break; + + case NAND_CMD_READID: + mpc5121_nfc_send_read_id(mtd); + break; + + case NAND_CMD_STATUS: + mpc5121_nfc_send_read_status(mtd); + if (chip->options & NAND_BUSWIDTH_16) + prv->column = 1; + else + prv->column = 0; + break; + } +} + +/* Copy data from/to NFC spare buffers. */ +static void mpc5121_nfc_copy_spare(struct mtd_info *mtd, uint offset, + u8 *buffer, uint size, int wr) +{ + struct nand_chip *nand = mtd_to_nand(mtd); + struct mpc5121_nfc_prv *prv = nand_get_controller_data(nand); + uint o, s, sbsize, blksize; + + /* + * NAND spare area is available through NFC spare buffers. + * The NFC divides spare area into (page_size / 512) chunks. + * Each chunk is placed into separate spare memory area, using + * first (spare_size / num_of_chunks) bytes of the buffer. + * + * For NAND device in which the spare area is not divided fully + * by the number of chunks, number of used bytes in each spare + * buffer is rounded down to the nearest even number of bytes, + * and all remaining bytes are added to the last used spare area. + * + * For more information read section 26.6.10 of MPC5121e + * Microcontroller Reference Manual, Rev. 3. + */ + + /* Calculate number of valid bytes in each spare buffer */ + sbsize = (mtd->oobsize / (mtd->writesize / 512)) & ~1; + + while (size) { + /* Calculate spare buffer number */ + s = offset / sbsize; + if (s > NFC_SPARE_BUFFERS - 1) + s = NFC_SPARE_BUFFERS - 1; + + /* + * Calculate offset to requested data block in selected spare + * buffer and its size. + */ + o = offset - (s * sbsize); + blksize = min(sbsize - o, size); + + if (wr) + memcpy_toio(prv->regs + NFC_SPARE_AREA(s) + o, + buffer, blksize); + else + memcpy_fromio(buffer, + prv->regs + NFC_SPARE_AREA(s) + o, blksize); + + buffer += blksize; + offset += blksize; + size -= blksize; + } +} + +/* Copy data from/to NFC main and spare buffers */ +static void mpc5121_nfc_buf_copy(struct mtd_info *mtd, u_char *buf, int len, + int wr) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mpc5121_nfc_prv *prv = nand_get_controller_data(chip); + uint c = prv->column; + uint l; + + /* Handle spare area access */ + if (prv->spareonly || c >= mtd->writesize) { + /* Calculate offset from beginning of spare area */ + if (c >= mtd->writesize) + c -= mtd->writesize; + + prv->column += len; + mpc5121_nfc_copy_spare(mtd, c, buf, len, wr); + return; + } + + /* + * Handle main area access - limit copy length to prevent + * crossing main/spare boundary. + */ + l = min((uint)len, mtd->writesize - c); + prv->column += l; + + if (wr) + memcpy_toio(prv->regs + NFC_MAIN_AREA(0) + c, buf, l); + else + memcpy_fromio(buf, prv->regs + NFC_MAIN_AREA(0) + c, l); + + /* Handle crossing main/spare boundary */ + if (l != len) { + buf += l; + len -= l; + mpc5121_nfc_buf_copy(mtd, buf, len, wr); + } +} + +/* Read data from NFC buffers */ +static void mpc5121_nfc_read_buf(struct nand_chip *chip, u_char *buf, int len) +{ + mpc5121_nfc_buf_copy(nand_to_mtd(chip), buf, len, 0); +} + +/* Write data to NFC buffers */ +static void mpc5121_nfc_write_buf(struct nand_chip *chip, const u_char *buf, + int len) +{ + mpc5121_nfc_buf_copy(nand_to_mtd(chip), (u_char *)buf, len, 1); +} + +/* Read byte from NFC buffers */ +static u8 mpc5121_nfc_read_byte(struct nand_chip *chip) +{ + u8 tmp; + + mpc5121_nfc_read_buf(chip, &tmp, sizeof(tmp)); + + return tmp; +} + +/* + * Read NFC configuration from Reset Config Word + * + * NFC is configured during reset in basis of information stored + * in Reset Config Word. There is no other way to set NAND block + * size, spare size and bus width. + */ +static int mpc5121_nfc_read_hw_config(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mpc5121_nfc_prv *prv = nand_get_controller_data(chip); + struct mpc512x_reset_module *rm; + struct device_node *rmnode; + uint rcw_pagesize = 0; + uint rcw_sparesize = 0; + uint rcw_width; + uint rcwh; + uint romloc, ps; + int ret = 0; + + rmnode = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-reset"); + if (!rmnode) { + dev_err(prv->dev, "Missing 'fsl,mpc5121-reset' " + "node in device tree!\n"); + return -ENODEV; + } + + rm = of_iomap(rmnode, 0); + if (!rm) { + dev_err(prv->dev, "Error mapping reset module node!\n"); + ret = -EBUSY; + goto out; + } + + rcwh = in_be32(&rm->rcwhr); + + /* Bit 6: NFC bus width */ + rcw_width = ((rcwh >> 6) & 0x1) ? 2 : 1; + + /* Bit 7: NFC Page/Spare size */ + ps = (rcwh >> 7) & 0x1; + + /* Bits [22:21]: ROM Location */ + romloc = (rcwh >> 21) & 0x3; + + /* Decode RCW bits */ + switch ((ps << 2) | romloc) { + case 0x00: + case 0x01: + rcw_pagesize = 512; + rcw_sparesize = 16; + break; + case 0x02: + case 0x03: + rcw_pagesize = 4096; + rcw_sparesize = 128; + break; + case 0x04: + case 0x05: + rcw_pagesize = 2048; + rcw_sparesize = 64; + break; + case 0x06: + case 0x07: + rcw_pagesize = 4096; + rcw_sparesize = 218; + break; + } + + mtd->writesize = rcw_pagesize; + mtd->oobsize = rcw_sparesize; + if (rcw_width == 2) + chip->options |= NAND_BUSWIDTH_16; + + dev_notice(prv->dev, "Configured for " + "%u-bit NAND, page size %u " + "with %u spare.\n", + rcw_width * 8, rcw_pagesize, + rcw_sparesize); + iounmap(rm); +out: + of_node_put(rmnode); + return ret; +} + +/* Free driver resources */ +static void mpc5121_nfc_free(struct device *dev, struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mpc5121_nfc_prv *prv = nand_get_controller_data(chip); + + if (prv->csreg) + iounmap(prv->csreg); +} + +static int mpc5121_nfc_attach_chip(struct nand_chip *chip) +{ + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT && + chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + + return 0; +} + +static const struct nand_controller_ops mpc5121_nfc_ops = { + .attach_chip = mpc5121_nfc_attach_chip, +}; + +static int mpc5121_nfc_probe(struct platform_device *op) +{ + struct device_node *dn = op->dev.of_node; + struct clk *clk; + struct device *dev = &op->dev; + struct mpc5121_nfc_prv *prv; + struct mtd_info *mtd; + struct nand_chip *chip; + const __be32 *chips_no; + void __iomem *regs; + int resettime = 0; + int retval = 0; + int rev, len; + int irq; + + /* + * Check SoC revision. This driver supports only NFC + * in MPC5121 revision 2 and MPC5123 revision 3. + */ + rev = (mfspr(SPRN_SVR) >> 4) & 0xF; + if ((rev != 2) && (rev != 3)) { + dev_err(dev, "SoC revision %u is not supported!\n", rev); + return -ENXIO; + } + + regs = devm_platform_ioremap_resource(op, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + irq = platform_get_irq(op, 0); + if (irq < 0) + return irq; + + prv = devm_kzalloc(dev, sizeof(*prv), GFP_KERNEL); + if (!prv) + return -ENOMEM; + + chip = &prv->chip; + mtd = nand_to_mtd(chip); + + nand_controller_init(&prv->controller); + prv->controller.ops = &mpc5121_nfc_ops; + chip->controller = &prv->controller; + + mtd->dev.parent = dev; + nand_set_controller_data(chip, prv); + nand_set_flash_node(chip, dn); + prv->dev = dev; + + /* Read NFC configuration from Reset Config Word */ + retval = mpc5121_nfc_read_hw_config(mtd); + if (retval) { + dev_err(dev, "Unable to read NFC config!\n"); + return retval; + } + + prv->irq = irq; + + chips_no = of_get_property(dn, "chips", &len); + if (!chips_no || len != sizeof(*chips_no)) { + dev_err(dev, "Invalid/missing 'chips' property!\n"); + return -EINVAL; + } + + prv->regs = regs; + + mtd->name = "MPC5121 NAND"; + chip->legacy.dev_ready = mpc5121_nfc_dev_ready; + chip->legacy.cmdfunc = mpc5121_nfc_command; + chip->legacy.read_byte = mpc5121_nfc_read_byte; + chip->legacy.read_buf = mpc5121_nfc_read_buf; + chip->legacy.write_buf = mpc5121_nfc_write_buf; + chip->legacy.select_chip = mpc5121_nfc_select_chip; + chip->legacy.set_features = nand_get_set_features_notsupp; + chip->legacy.get_features = nand_get_set_features_notsupp; + chip->bbt_options = NAND_BBT_USE_FLASH; + + /* Support external chip-select logic on ADS5121 board */ + if (of_machine_is_compatible("fsl,mpc5121ads")) { + retval = ads5121_chipselect_init(mtd); + if (retval) { + dev_err(dev, "Chipselect init error!\n"); + return retval; + } + + chip->legacy.select_chip = ads5121_select_chip; + } + + /* Enable NFC clock */ + clk = devm_clk_get_enabled(dev, "ipg"); + if (IS_ERR(clk)) { + dev_err(dev, "Unable to acquire and enable NFC clock!\n"); + retval = PTR_ERR(clk); + goto error; + } + prv->clk = clk; + + /* Reset NAND Flash controller */ + nfc_set(mtd, NFC_CONFIG1, NFC_RESET); + while (nfc_read(mtd, NFC_CONFIG1) & NFC_RESET) { + if (resettime++ >= NFC_RESET_TIMEOUT) { + dev_err(dev, "Timeout while resetting NFC!\n"); + retval = -EINVAL; + goto error; + } + + udelay(1); + } + + /* Enable write to NFC memory */ + nfc_write(mtd, NFC_CONFIG, NFC_BLS_UNLOCKED); + + /* Enable write to all NAND pages */ + nfc_write(mtd, NFC_UNLOCKSTART_BLK0, 0x0000); + nfc_write(mtd, NFC_UNLOCKEND_BLK0, 0xFFFF); + nfc_write(mtd, NFC_WRPROT, NFC_WPC_UNLOCK); + + /* + * Setup NFC: + * - Big Endian transfers, + * - Interrupt after full page read/write. + */ + nfc_write(mtd, NFC_CONFIG1, NFC_BIG_ENDIAN | NFC_INT_MASK | + NFC_FULL_PAGE_INT); + + /* Set spare area size */ + nfc_write(mtd, NFC_SPAS, mtd->oobsize >> 1); + + init_waitqueue_head(&prv->irq_waitq); + retval = devm_request_irq(dev, prv->irq, &mpc5121_nfc_irq, 0, DRV_NAME, + mtd); + if (retval) { + dev_err(dev, "Error requesting IRQ!\n"); + goto error; + } + + /* + * This driver assumes that the default ECC engine should be TYPE_SOFT. + * Set ->engine_type before registering the NAND devices in order to + * provide a driver specific default value. + */ + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + + /* Detect NAND chips */ + retval = nand_scan(chip, be32_to_cpup(chips_no)); + if (retval) { + dev_err(dev, "NAND Flash not found !\n"); + goto error; + } + + /* Set erase block size */ + switch (mtd->erasesize / mtd->writesize) { + case 32: + nfc_set(mtd, NFC_CONFIG1, NFC_PPB_32); + break; + + case 64: + nfc_set(mtd, NFC_CONFIG1, NFC_PPB_64); + break; + + case 128: + nfc_set(mtd, NFC_CONFIG1, NFC_PPB_128); + break; + + case 256: + nfc_set(mtd, NFC_CONFIG1, NFC_PPB_256); + break; + + default: + dev_err(dev, "Unsupported NAND flash!\n"); + retval = -ENXIO; + goto error; + } + + dev_set_drvdata(dev, mtd); + + /* Register device in MTD */ + retval = mtd_device_register(mtd, NULL, 0); + if (retval) { + dev_err(dev, "Error adding MTD device!\n"); + goto error; + } + + return 0; +error: + mpc5121_nfc_free(dev, mtd); + return retval; +} + +static void mpc5121_nfc_remove(struct platform_device *op) +{ + struct device *dev = &op->dev; + struct mtd_info *mtd = dev_get_drvdata(dev); + int ret; + + ret = mtd_device_unregister(mtd); + WARN_ON(ret); + nand_cleanup(mtd_to_nand(mtd)); + mpc5121_nfc_free(dev, mtd); +} + +static const struct of_device_id mpc5121_nfc_match[] = { + { .compatible = "fsl,mpc5121-nfc", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mpc5121_nfc_match); + +static struct platform_driver mpc5121_nfc_driver = { + .probe = mpc5121_nfc_probe, + .remove = mpc5121_nfc_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = mpc5121_nfc_match, + }, +}; + +module_platform_driver(mpc5121_nfc_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MPC5121 NAND MTD driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/raw/mtk_nand.c b/drivers/mtd/nand/raw/mtk_nand.c new file mode 100644 index 000000000..21c7e1102 --- /dev/null +++ b/drivers/mtd/nand/raw/mtk_nand.c @@ -0,0 +1,1657 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * MTK NAND Flash controller driver. + * Copyright (C) 2016 MediaTek Inc. + * Authors: Xiaolei Li + * Jorge Ramirez-Ortiz + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* NAND controller register definition */ +#define NFI_CNFG (0x00) +#define CNFG_AHB BIT(0) +#define CNFG_READ_EN BIT(1) +#define CNFG_DMA_BURST_EN BIT(2) +#define CNFG_BYTE_RW BIT(6) +#define CNFG_HW_ECC_EN BIT(8) +#define CNFG_AUTO_FMT_EN BIT(9) +#define CNFG_OP_CUST (6 << 12) +#define NFI_PAGEFMT (0x04) +#define PAGEFMT_FDM_ECC_SHIFT (12) +#define PAGEFMT_FDM_SHIFT (8) +#define PAGEFMT_SEC_SEL_512 BIT(2) +#define PAGEFMT_512_2K (0) +#define PAGEFMT_2K_4K (1) +#define PAGEFMT_4K_8K (2) +#define PAGEFMT_8K_16K (3) +/* NFI control */ +#define NFI_CON (0x08) +#define CON_FIFO_FLUSH BIT(0) +#define CON_NFI_RST BIT(1) +#define CON_BRD BIT(8) /* burst read */ +#define CON_BWR BIT(9) /* burst write */ +#define CON_SEC_SHIFT (12) +/* Timming control register */ +#define NFI_ACCCON (0x0C) +#define NFI_INTR_EN (0x10) +#define INTR_AHB_DONE_EN BIT(6) +#define NFI_INTR_STA (0x14) +#define NFI_CMD (0x20) +#define NFI_ADDRNOB (0x30) +#define NFI_COLADDR (0x34) +#define NFI_ROWADDR (0x38) +#define NFI_STRDATA (0x40) +#define STAR_EN (1) +#define STAR_DE (0) +#define NFI_CNRNB (0x44) +#define NFI_DATAW (0x50) +#define NFI_DATAR (0x54) +#define NFI_PIO_DIRDY (0x58) +#define PIO_DI_RDY (0x01) +#define NFI_STA (0x60) +#define STA_CMD BIT(0) +#define STA_ADDR BIT(1) +#define STA_BUSY BIT(8) +#define STA_EMP_PAGE BIT(12) +#define NFI_FSM_CUSTDATA (0xe << 16) +#define NFI_FSM_MASK (0xf << 16) +#define NFI_ADDRCNTR (0x70) +#define CNTR_MASK GENMASK(16, 12) +#define ADDRCNTR_SEC_SHIFT (12) +#define ADDRCNTR_SEC(val) \ + (((val) & CNTR_MASK) >> ADDRCNTR_SEC_SHIFT) +#define NFI_STRADDR (0x80) +#define NFI_BYTELEN (0x84) +#define NFI_CSEL (0x90) +#define NFI_FDML(x) (0xA0 + (x) * sizeof(u32) * 2) +#define NFI_FDMM(x) (0xA4 + (x) * sizeof(u32) * 2) +#define NFI_FDM_MAX_SIZE (8) +#define NFI_FDM_MIN_SIZE (1) +#define NFI_DEBUG_CON1 (0x220) +#define STROBE_MASK GENMASK(4, 3) +#define STROBE_SHIFT (3) +#define MAX_STROBE_DLY (3) +#define NFI_MASTER_STA (0x224) +#define MASTER_STA_MASK (0x0FFF) +#define NFI_EMPTY_THRESH (0x23C) + +#define MTK_NAME "mtk-nand" +#define KB(x) ((x) * 1024UL) +#define MB(x) (KB(x) * 1024UL) + +#define MTK_TIMEOUT (500000) +#define MTK_RESET_TIMEOUT (1000000) +#define MTK_NAND_MAX_NSELS (2) +#define MTK_NFC_MIN_SPARE (16) +#define ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt) \ + ((tpoecs) << 28 | (tprecs) << 22 | (tc2r) << 16 | \ + (tw2r) << 12 | (twh) << 8 | (twst) << 4 | (trlt)) + +struct mtk_nfc_caps { + const u8 *spare_size; + u8 num_spare_size; + u8 pageformat_spare_shift; + u8 nfi_clk_div; + u8 max_sector; + u32 max_sector_size; +}; + +struct mtk_nfc_bad_mark_ctl { + void (*bm_swap)(struct mtd_info *, u8 *buf, int raw); + u32 sec; + u32 pos; +}; + +/* + * FDM: region used to store free OOB data + */ +struct mtk_nfc_fdm { + u32 reg_size; + u32 ecc_size; +}; + +struct mtk_nfc_nand_chip { + struct list_head node; + struct nand_chip nand; + + struct mtk_nfc_bad_mark_ctl bad_mark; + struct mtk_nfc_fdm fdm; + u32 spare_per_sector; + + int nsels; + u8 sels[] __counted_by(nsels); + /* nothing after this field */ +}; + +struct mtk_nfc_clk { + struct clk *nfi_clk; + struct clk *pad_clk; +}; + +struct mtk_nfc { + struct nand_controller controller; + struct mtk_ecc_config ecc_cfg; + struct mtk_nfc_clk clk; + struct mtk_ecc *ecc; + + struct device *dev; + const struct mtk_nfc_caps *caps; + void __iomem *regs; + + struct completion done; + struct list_head chips; + + u8 *buffer; + + unsigned long assigned_cs; +}; + +/* + * supported spare size of each IP. + * order should be the same with the spare size bitfiled defination of + * register NFI_PAGEFMT. + */ +static const u8 spare_size_mt2701[] = { + 16, 26, 27, 28, 32, 36, 40, 44, 48, 49, 50, 51, 52, 62, 63, 64 +}; + +static const u8 spare_size_mt2712[] = { + 16, 26, 27, 28, 32, 36, 40, 44, 48, 49, 50, 51, 52, 62, 61, 63, 64, 67, + 74 +}; + +static const u8 spare_size_mt7622[] = { + 16, 26, 27, 28 +}; + +static inline struct mtk_nfc_nand_chip *to_mtk_nand(struct nand_chip *nand) +{ + return container_of(nand, struct mtk_nfc_nand_chip, nand); +} + +static inline u8 *data_ptr(struct nand_chip *chip, const u8 *p, int i) +{ + return (u8 *)p + i * chip->ecc.size; +} + +static inline u8 *oob_ptr(struct nand_chip *chip, int i) +{ + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + u8 *poi; + + /* map the sector's FDM data to free oob: + * the beginning of the oob area stores the FDM data of bad mark sectors + */ + + if (i < mtk_nand->bad_mark.sec) + poi = chip->oob_poi + (i + 1) * mtk_nand->fdm.reg_size; + else if (i == mtk_nand->bad_mark.sec) + poi = chip->oob_poi; + else + poi = chip->oob_poi + i * mtk_nand->fdm.reg_size; + + return poi; +} + +static inline int mtk_data_len(struct nand_chip *chip) +{ + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + + return chip->ecc.size + mtk_nand->spare_per_sector; +} + +static inline u8 *mtk_data_ptr(struct nand_chip *chip, int i) +{ + struct mtk_nfc *nfc = nand_get_controller_data(chip); + + return nfc->buffer + i * mtk_data_len(chip); +} + +static inline u8 *mtk_oob_ptr(struct nand_chip *chip, int i) +{ + struct mtk_nfc *nfc = nand_get_controller_data(chip); + + return nfc->buffer + i * mtk_data_len(chip) + chip->ecc.size; +} + +static inline void nfi_writel(struct mtk_nfc *nfc, u32 val, u32 reg) +{ + writel(val, nfc->regs + reg); +} + +static inline void nfi_writew(struct mtk_nfc *nfc, u16 val, u32 reg) +{ + writew(val, nfc->regs + reg); +} + +static inline void nfi_writeb(struct mtk_nfc *nfc, u8 val, u32 reg) +{ + writeb(val, nfc->regs + reg); +} + +static inline u32 nfi_readl(struct mtk_nfc *nfc, u32 reg) +{ + return readl_relaxed(nfc->regs + reg); +} + +static inline u16 nfi_readw(struct mtk_nfc *nfc, u32 reg) +{ + return readw_relaxed(nfc->regs + reg); +} + +static inline u8 nfi_readb(struct mtk_nfc *nfc, u32 reg) +{ + return readb_relaxed(nfc->regs + reg); +} + +static void mtk_nfc_hw_reset(struct mtk_nfc *nfc) +{ + struct device *dev = nfc->dev; + u32 val; + int ret; + + /* reset all registers and force the NFI master to terminate */ + nfi_writel(nfc, CON_FIFO_FLUSH | CON_NFI_RST, NFI_CON); + + /* wait for the master to finish the last transaction */ + ret = readl_poll_timeout(nfc->regs + NFI_MASTER_STA, val, + !(val & MASTER_STA_MASK), 50, + MTK_RESET_TIMEOUT); + if (ret) + dev_warn(dev, "master active in reset [0x%x] = 0x%x\n", + NFI_MASTER_STA, val); + + /* ensure any status register affected by the NFI master is reset */ + nfi_writel(nfc, CON_FIFO_FLUSH | CON_NFI_RST, NFI_CON); + nfi_writew(nfc, STAR_DE, NFI_STRDATA); +} + +static int mtk_nfc_send_command(struct mtk_nfc *nfc, u8 command) +{ + struct device *dev = nfc->dev; + u32 val; + int ret; + + nfi_writel(nfc, command, NFI_CMD); + + ret = readl_poll_timeout_atomic(nfc->regs + NFI_STA, val, + !(val & STA_CMD), 10, MTK_TIMEOUT); + if (ret) { + dev_warn(dev, "nfi core timed out entering command mode\n"); + return -EIO; + } + + return 0; +} + +static int mtk_nfc_send_address(struct mtk_nfc *nfc, int addr) +{ + struct device *dev = nfc->dev; + u32 val; + int ret; + + nfi_writel(nfc, addr, NFI_COLADDR); + nfi_writel(nfc, 0, NFI_ROWADDR); + nfi_writew(nfc, 1, NFI_ADDRNOB); + + ret = readl_poll_timeout_atomic(nfc->regs + NFI_STA, val, + !(val & STA_ADDR), 10, MTK_TIMEOUT); + if (ret) { + dev_warn(dev, "nfi core timed out entering address mode\n"); + return -EIO; + } + + return 0; +} + +static int mtk_nfc_hw_runtime_config(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + struct mtk_nfc *nfc = nand_get_controller_data(chip); + u32 fmt, spare, i; + + if (!mtd->writesize) + return 0; + + spare = mtk_nand->spare_per_sector; + + switch (mtd->writesize) { + case 512: + fmt = PAGEFMT_512_2K | PAGEFMT_SEC_SEL_512; + break; + case KB(2): + if (chip->ecc.size == 512) + fmt = PAGEFMT_2K_4K | PAGEFMT_SEC_SEL_512; + else + fmt = PAGEFMT_512_2K; + break; + case KB(4): + if (chip->ecc.size == 512) + fmt = PAGEFMT_4K_8K | PAGEFMT_SEC_SEL_512; + else + fmt = PAGEFMT_2K_4K; + break; + case KB(8): + if (chip->ecc.size == 512) + fmt = PAGEFMT_8K_16K | PAGEFMT_SEC_SEL_512; + else + fmt = PAGEFMT_4K_8K; + break; + case KB(16): + fmt = PAGEFMT_8K_16K; + break; + default: + dev_err(nfc->dev, "invalid page len: %d\n", mtd->writesize); + return -EINVAL; + } + + /* + * the hardware will double the value for this eccsize, so we need to + * halve it + */ + if (chip->ecc.size == 1024) + spare >>= 1; + + for (i = 0; i < nfc->caps->num_spare_size; i++) { + if (nfc->caps->spare_size[i] == spare) + break; + } + + if (i == nfc->caps->num_spare_size) { + dev_err(nfc->dev, "invalid spare size %d\n", spare); + return -EINVAL; + } + + fmt |= i << nfc->caps->pageformat_spare_shift; + + fmt |= mtk_nand->fdm.reg_size << PAGEFMT_FDM_SHIFT; + fmt |= mtk_nand->fdm.ecc_size << PAGEFMT_FDM_ECC_SHIFT; + nfi_writel(nfc, fmt, NFI_PAGEFMT); + + nfc->ecc_cfg.strength = chip->ecc.strength; + nfc->ecc_cfg.len = chip->ecc.size + mtk_nand->fdm.ecc_size; + + return 0; +} + +static inline void mtk_nfc_wait_ioready(struct mtk_nfc *nfc) +{ + int rc; + u8 val; + + rc = readb_poll_timeout_atomic(nfc->regs + NFI_PIO_DIRDY, val, + val & PIO_DI_RDY, 10, MTK_TIMEOUT); + if (rc < 0) + dev_err(nfc->dev, "data not ready\n"); +} + +static inline u8 mtk_nfc_read_byte(struct nand_chip *chip) +{ + struct mtk_nfc *nfc = nand_get_controller_data(chip); + u32 reg; + + /* after each byte read, the NFI_STA reg is reset by the hardware */ + reg = nfi_readl(nfc, NFI_STA) & NFI_FSM_MASK; + if (reg != NFI_FSM_CUSTDATA) { + reg = nfi_readw(nfc, NFI_CNFG); + reg |= CNFG_BYTE_RW | CNFG_READ_EN; + nfi_writew(nfc, reg, NFI_CNFG); + + /* + * set to max sector to allow the HW to continue reading over + * unaligned accesses + */ + reg = (nfc->caps->max_sector << CON_SEC_SHIFT) | CON_BRD; + nfi_writel(nfc, reg, NFI_CON); + + /* trigger to fetch data */ + nfi_writew(nfc, STAR_EN, NFI_STRDATA); + } + + mtk_nfc_wait_ioready(nfc); + + return nfi_readb(nfc, NFI_DATAR); +} + +static void mtk_nfc_read_buf(struct nand_chip *chip, u8 *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) + buf[i] = mtk_nfc_read_byte(chip); +} + +static void mtk_nfc_write_byte(struct nand_chip *chip, u8 byte) +{ + struct mtk_nfc *nfc = nand_get_controller_data(chip); + u32 reg; + + reg = nfi_readl(nfc, NFI_STA) & NFI_FSM_MASK; + + if (reg != NFI_FSM_CUSTDATA) { + reg = nfi_readw(nfc, NFI_CNFG) | CNFG_BYTE_RW; + nfi_writew(nfc, reg, NFI_CNFG); + + reg = nfc->caps->max_sector << CON_SEC_SHIFT | CON_BWR; + nfi_writel(nfc, reg, NFI_CON); + + nfi_writew(nfc, STAR_EN, NFI_STRDATA); + } + + mtk_nfc_wait_ioready(nfc); + nfi_writeb(nfc, byte, NFI_DATAW); +} + +static void mtk_nfc_write_buf(struct nand_chip *chip, const u8 *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) + mtk_nfc_write_byte(chip, buf[i]); +} + +static int mtk_nfc_exec_instr(struct nand_chip *chip, + const struct nand_op_instr *instr) +{ + struct mtk_nfc *nfc = nand_get_controller_data(chip); + unsigned int i; + u32 status; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + mtk_nfc_send_command(nfc, instr->ctx.cmd.opcode); + return 0; + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) + mtk_nfc_send_address(nfc, instr->ctx.addr.addrs[i]); + return 0; + case NAND_OP_DATA_IN_INSTR: + mtk_nfc_read_buf(chip, instr->ctx.data.buf.in, + instr->ctx.data.len); + return 0; + case NAND_OP_DATA_OUT_INSTR: + mtk_nfc_write_buf(chip, instr->ctx.data.buf.out, + instr->ctx.data.len); + return 0; + case NAND_OP_WAITRDY_INSTR: + return readl_poll_timeout(nfc->regs + NFI_STA, status, + !(status & STA_BUSY), 20, + instr->ctx.waitrdy.timeout_ms * 1000); + default: + break; + } + + return -EINVAL; +} + +static void mtk_nfc_select_target(struct nand_chip *nand, unsigned int cs) +{ + struct mtk_nfc *nfc = nand_get_controller_data(nand); + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(nand); + + mtk_nfc_hw_runtime_config(nand_to_mtd(nand)); + + nfi_writel(nfc, mtk_nand->sels[cs], NFI_CSEL); +} + +static int mtk_nfc_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + struct mtk_nfc *nfc = nand_get_controller_data(chip); + unsigned int i; + int ret = 0; + + if (check_only) + return 0; + + mtk_nfc_hw_reset(nfc); + nfi_writew(nfc, CNFG_OP_CUST, NFI_CNFG); + mtk_nfc_select_target(chip, op->cs); + + for (i = 0; i < op->ninstrs; i++) { + ret = mtk_nfc_exec_instr(chip, &op->instrs[i]); + if (ret) + break; + } + + return ret; +} + +static int mtk_nfc_setup_interface(struct nand_chip *chip, int csline, + const struct nand_interface_config *conf) +{ + struct mtk_nfc *nfc = nand_get_controller_data(chip); + const struct nand_sdr_timings *timings; + u32 rate, tpoecs, tprecs, tc2r, tw2r, twh, twst = 0, trlt = 0; + u32 temp, tsel = 0; + + timings = nand_get_sdr_timings(conf); + if (IS_ERR(timings)) + return -ENOTSUPP; + + if (csline == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + rate = clk_get_rate(nfc->clk.nfi_clk); + /* There is a frequency divider in some IPs */ + rate /= nfc->caps->nfi_clk_div; + + /* turn clock rate into KHZ */ + rate /= 1000; + + tpoecs = max(timings->tALH_min, timings->tCLH_min) / 1000; + tpoecs = DIV_ROUND_UP(tpoecs * rate, 1000000); + tpoecs &= 0xf; + + tprecs = max(timings->tCLS_min, timings->tALS_min) / 1000; + tprecs = DIV_ROUND_UP(tprecs * rate, 1000000); + tprecs &= 0x3f; + + /* sdr interface has no tCR which means CE# low to RE# low */ + tc2r = 0; + + tw2r = timings->tWHR_min / 1000; + tw2r = DIV_ROUND_UP(tw2r * rate, 1000000); + tw2r = DIV_ROUND_UP(tw2r - 1, 2); + tw2r &= 0xf; + + twh = max(timings->tREH_min, timings->tWH_min) / 1000; + twh = DIV_ROUND_UP(twh * rate, 1000000) - 1; + twh &= 0xf; + + /* Calculate real WE#/RE# hold time in nanosecond */ + temp = (twh + 1) * 1000000 / rate; + /* nanosecond to picosecond */ + temp *= 1000; + + /* + * WE# low level time should be expaned to meet WE# pulse time + * and WE# cycle time at the same time. + */ + if (temp < timings->tWC_min) + twst = timings->tWC_min - temp; + twst = max(timings->tWP_min, twst) / 1000; + twst = DIV_ROUND_UP(twst * rate, 1000000) - 1; + twst &= 0xf; + + /* + * RE# low level time should be expaned to meet RE# pulse time + * and RE# cycle time at the same time. + */ + if (temp < timings->tRC_min) + trlt = timings->tRC_min - temp; + trlt = max(trlt, timings->tRP_min) / 1000; + trlt = DIV_ROUND_UP(trlt * rate, 1000000) - 1; + trlt &= 0xf; + + /* Calculate RE# pulse time in nanosecond. */ + temp = (trlt + 1) * 1000000 / rate; + /* nanosecond to picosecond */ + temp *= 1000; + /* + * If RE# access time is bigger than RE# pulse time, + * delay sampling data timing. + */ + if (temp < timings->tREA_max) { + tsel = timings->tREA_max / 1000; + tsel = DIV_ROUND_UP(tsel * rate, 1000000); + tsel -= (trlt + 1); + if (tsel > MAX_STROBE_DLY) { + trlt += tsel - MAX_STROBE_DLY; + tsel = MAX_STROBE_DLY; + } + } + temp = nfi_readl(nfc, NFI_DEBUG_CON1); + temp &= ~STROBE_MASK; + temp |= tsel << STROBE_SHIFT; + nfi_writel(nfc, temp, NFI_DEBUG_CON1); + + /* + * ACCON: access timing control register + * ------------------------------------- + * 31:28: tpoecs, minimum required time for CS post pulling down after + * accessing the device + * 27:22: tprecs, minimum required time for CS pre pulling down before + * accessing the device + * 21:16: tc2r, minimum required time from NCEB low to NREB low + * 15:12: tw2r, minimum required time from NWEB high to NREB low. + * 11:08: twh, write enable hold time + * 07:04: twst, write wait states + * 03:00: trlt, read wait states + */ + trlt = ACCTIMING(tpoecs, tprecs, tc2r, tw2r, twh, twst, trlt); + nfi_writel(nfc, trlt, NFI_ACCCON); + + return 0; +} + +static int mtk_nfc_sector_encode(struct nand_chip *chip, u8 *data) +{ + struct mtk_nfc *nfc = nand_get_controller_data(chip); + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + int size = chip->ecc.size + mtk_nand->fdm.reg_size; + + nfc->ecc_cfg.mode = ECC_DMA_MODE; + nfc->ecc_cfg.op = ECC_ENCODE; + + return mtk_ecc_encode(nfc->ecc, &nfc->ecc_cfg, data, size); +} + +static void mtk_nfc_no_bad_mark_swap(struct mtd_info *a, u8 *b, int c) +{ + /* nop */ +} + +static void mtk_nfc_bad_mark_swap(struct mtd_info *mtd, u8 *buf, int raw) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mtk_nfc_nand_chip *nand = to_mtk_nand(chip); + u32 bad_pos = nand->bad_mark.pos; + + if (raw) + bad_pos += nand->bad_mark.sec * mtk_data_len(chip); + else + bad_pos += nand->bad_mark.sec * chip->ecc.size; + + swap(chip->oob_poi[0], buf[bad_pos]); +} + +static int mtk_nfc_format_subpage(struct mtd_info *mtd, u32 offset, + u32 len, const u8 *buf) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + struct mtk_nfc *nfc = nand_get_controller_data(chip); + struct mtk_nfc_fdm *fdm = &mtk_nand->fdm; + u32 start, end; + int i, ret; + + start = offset / chip->ecc.size; + end = DIV_ROUND_UP(offset + len, chip->ecc.size); + + memset(nfc->buffer, 0xff, mtd->writesize + mtd->oobsize); + for (i = 0; i < chip->ecc.steps; i++) { + memcpy(mtk_data_ptr(chip, i), data_ptr(chip, buf, i), + chip->ecc.size); + + if (start > i || i >= end) + continue; + + if (i == mtk_nand->bad_mark.sec) + mtk_nand->bad_mark.bm_swap(mtd, nfc->buffer, 1); + + memcpy(mtk_oob_ptr(chip, i), oob_ptr(chip, i), fdm->reg_size); + + /* program the CRC back to the OOB */ + ret = mtk_nfc_sector_encode(chip, mtk_data_ptr(chip, i)); + if (ret < 0) + return ret; + } + + return 0; +} + +static void mtk_nfc_format_page(struct mtd_info *mtd, const u8 *buf) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + struct mtk_nfc *nfc = nand_get_controller_data(chip); + struct mtk_nfc_fdm *fdm = &mtk_nand->fdm; + u32 i; + + memset(nfc->buffer, 0xff, mtd->writesize + mtd->oobsize); + for (i = 0; i < chip->ecc.steps; i++) { + if (buf) + memcpy(mtk_data_ptr(chip, i), data_ptr(chip, buf, i), + chip->ecc.size); + + if (i == mtk_nand->bad_mark.sec) + mtk_nand->bad_mark.bm_swap(mtd, nfc->buffer, 1); + + memcpy(mtk_oob_ptr(chip, i), oob_ptr(chip, i), fdm->reg_size); + } +} + +static inline void mtk_nfc_read_fdm(struct nand_chip *chip, u32 start, + u32 sectors) +{ + struct mtk_nfc *nfc = nand_get_controller_data(chip); + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + struct mtk_nfc_fdm *fdm = &mtk_nand->fdm; + u32 vall, valm; + u8 *oobptr; + int i, j; + + for (i = 0; i < sectors; i++) { + oobptr = oob_ptr(chip, start + i); + vall = nfi_readl(nfc, NFI_FDML(i)); + valm = nfi_readl(nfc, NFI_FDMM(i)); + + for (j = 0; j < fdm->reg_size; j++) + oobptr[j] = (j >= 4 ? valm : vall) >> ((j % 4) * 8); + } +} + +static inline void mtk_nfc_write_fdm(struct nand_chip *chip) +{ + struct mtk_nfc *nfc = nand_get_controller_data(chip); + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + struct mtk_nfc_fdm *fdm = &mtk_nand->fdm; + u32 vall, valm; + u8 *oobptr; + int i, j; + + for (i = 0; i < chip->ecc.steps; i++) { + oobptr = oob_ptr(chip, i); + vall = 0; + valm = 0; + for (j = 0; j < 8; j++) { + if (j < 4) + vall |= (j < fdm->reg_size ? oobptr[j] : 0xff) + << (j * 8); + else + valm |= (j < fdm->reg_size ? oobptr[j] : 0xff) + << ((j - 4) * 8); + } + nfi_writel(nfc, vall, NFI_FDML(i)); + nfi_writel(nfc, valm, NFI_FDMM(i)); + } +} + +static int mtk_nfc_do_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const u8 *buf, int page, int len) +{ + struct mtk_nfc *nfc = nand_get_controller_data(chip); + struct device *dev = nfc->dev; + dma_addr_t addr; + u32 reg; + int ret; + + addr = dma_map_single(dev, (void *)buf, len, DMA_TO_DEVICE); + ret = dma_mapping_error(nfc->dev, addr); + if (ret) { + dev_err(nfc->dev, "dma mapping error\n"); + return -EINVAL; + } + + reg = nfi_readw(nfc, NFI_CNFG) | CNFG_AHB | CNFG_DMA_BURST_EN; + nfi_writew(nfc, reg, NFI_CNFG); + + nfi_writel(nfc, chip->ecc.steps << CON_SEC_SHIFT, NFI_CON); + nfi_writel(nfc, lower_32_bits(addr), NFI_STRADDR); + nfi_writew(nfc, INTR_AHB_DONE_EN, NFI_INTR_EN); + + init_completion(&nfc->done); + + reg = nfi_readl(nfc, NFI_CON) | CON_BWR; + nfi_writel(nfc, reg, NFI_CON); + nfi_writew(nfc, STAR_EN, NFI_STRDATA); + + ret = wait_for_completion_timeout(&nfc->done, msecs_to_jiffies(500)); + if (!ret) { + dev_err(dev, "program ahb done timeout\n"); + nfi_writew(nfc, 0, NFI_INTR_EN); + ret = -ETIMEDOUT; + goto timeout; + } + + ret = readl_poll_timeout_atomic(nfc->regs + NFI_ADDRCNTR, reg, + ADDRCNTR_SEC(reg) >= chip->ecc.steps, + 10, MTK_TIMEOUT); + if (ret) + dev_err(dev, "hwecc write timeout\n"); + +timeout: + + dma_unmap_single(nfc->dev, addr, len, DMA_TO_DEVICE); + nfi_writel(nfc, 0, NFI_CON); + + return ret; +} + +static int mtk_nfc_write_page(struct mtd_info *mtd, struct nand_chip *chip, + const u8 *buf, int page, int raw) +{ + struct mtk_nfc *nfc = nand_get_controller_data(chip); + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + size_t len; + const u8 *bufpoi; + u32 reg; + int ret; + + mtk_nfc_select_target(chip, chip->cur_cs); + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + + if (!raw) { + /* OOB => FDM: from register, ECC: from HW */ + reg = nfi_readw(nfc, NFI_CNFG) | CNFG_AUTO_FMT_EN; + nfi_writew(nfc, reg | CNFG_HW_ECC_EN, NFI_CNFG); + + nfc->ecc_cfg.op = ECC_ENCODE; + nfc->ecc_cfg.mode = ECC_NFI_MODE; + ret = mtk_ecc_enable(nfc->ecc, &nfc->ecc_cfg); + if (ret) { + /* clear NFI config */ + reg = nfi_readw(nfc, NFI_CNFG); + reg &= ~(CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN); + nfi_writew(nfc, reg, NFI_CNFG); + + return ret; + } + + memcpy(nfc->buffer, buf, mtd->writesize); + mtk_nand->bad_mark.bm_swap(mtd, nfc->buffer, raw); + bufpoi = nfc->buffer; + + /* write OOB into the FDM registers (OOB area in MTK NAND) */ + mtk_nfc_write_fdm(chip); + } else { + bufpoi = buf; + } + + len = mtd->writesize + (raw ? mtd->oobsize : 0); + ret = mtk_nfc_do_write_page(mtd, chip, bufpoi, page, len); + + if (!raw) + mtk_ecc_disable(nfc->ecc); + + if (ret) + return ret; + + return nand_prog_page_end_op(chip); +} + +static int mtk_nfc_write_page_hwecc(struct nand_chip *chip, const u8 *buf, + int oob_on, int page) +{ + return mtk_nfc_write_page(nand_to_mtd(chip), chip, buf, page, 0); +} + +static int mtk_nfc_write_page_raw(struct nand_chip *chip, const u8 *buf, + int oob_on, int pg) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mtk_nfc *nfc = nand_get_controller_data(chip); + + mtk_nfc_format_page(mtd, buf); + return mtk_nfc_write_page(mtd, chip, nfc->buffer, pg, 1); +} + +static int mtk_nfc_write_subpage_hwecc(struct nand_chip *chip, u32 offset, + u32 data_len, const u8 *buf, + int oob_on, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mtk_nfc *nfc = nand_get_controller_data(chip); + int ret; + + ret = mtk_nfc_format_subpage(mtd, offset, data_len, buf); + if (ret < 0) + return ret; + + /* use the data in the private buffer (now with FDM and CRC) */ + return mtk_nfc_write_page(mtd, chip, nfc->buffer, page, 1); +} + +static int mtk_nfc_write_oob_std(struct nand_chip *chip, int page) +{ + return mtk_nfc_write_page_raw(chip, NULL, 1, page); +} + +static int mtk_nfc_update_ecc_stats(struct mtd_info *mtd, u8 *buf, u32 start, + u32 sectors) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mtk_nfc *nfc = nand_get_controller_data(chip); + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + struct mtk_ecc_stats stats; + u32 reg_size = mtk_nand->fdm.reg_size; + int rc, i; + + rc = nfi_readl(nfc, NFI_STA) & STA_EMP_PAGE; + if (rc) { + memset(buf, 0xff, sectors * chip->ecc.size); + for (i = 0; i < sectors; i++) + memset(oob_ptr(chip, start + i), 0xff, reg_size); + return 0; + } + + mtk_ecc_get_stats(nfc->ecc, &stats, sectors); + mtd->ecc_stats.corrected += stats.corrected; + mtd->ecc_stats.failed += stats.failed; + + return stats.bitflips; +} + +static int mtk_nfc_read_subpage(struct mtd_info *mtd, struct nand_chip *chip, + u32 data_offs, u32 readlen, + u8 *bufpoi, int page, int raw) +{ + struct mtk_nfc *nfc = nand_get_controller_data(chip); + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + u32 spare = mtk_nand->spare_per_sector; + u32 column, sectors, start, end, reg; + dma_addr_t addr; + int bitflips = 0; + size_t len; + u8 *buf; + int rc; + + mtk_nfc_select_target(chip, chip->cur_cs); + start = data_offs / chip->ecc.size; + end = DIV_ROUND_UP(data_offs + readlen, chip->ecc.size); + + sectors = end - start; + column = start * (chip->ecc.size + spare); + + len = sectors * chip->ecc.size + (raw ? sectors * spare : 0); + buf = bufpoi + start * chip->ecc.size; + + nand_read_page_op(chip, page, column, NULL, 0); + + addr = dma_map_single(nfc->dev, buf, len, DMA_FROM_DEVICE); + rc = dma_mapping_error(nfc->dev, addr); + if (rc) { + dev_err(nfc->dev, "dma mapping error\n"); + + return -EINVAL; + } + + reg = nfi_readw(nfc, NFI_CNFG); + reg |= CNFG_READ_EN | CNFG_DMA_BURST_EN | CNFG_AHB; + if (!raw) { + reg |= CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN; + nfi_writew(nfc, reg, NFI_CNFG); + + nfc->ecc_cfg.mode = ECC_NFI_MODE; + nfc->ecc_cfg.sectors = sectors; + nfc->ecc_cfg.op = ECC_DECODE; + rc = mtk_ecc_enable(nfc->ecc, &nfc->ecc_cfg); + if (rc) { + dev_err(nfc->dev, "ecc enable\n"); + /* clear NFI_CNFG */ + reg &= ~(CNFG_DMA_BURST_EN | CNFG_AHB | CNFG_READ_EN | + CNFG_AUTO_FMT_EN | CNFG_HW_ECC_EN); + nfi_writew(nfc, reg, NFI_CNFG); + dma_unmap_single(nfc->dev, addr, len, DMA_FROM_DEVICE); + + return rc; + } + } else { + nfi_writew(nfc, reg, NFI_CNFG); + } + + nfi_writel(nfc, sectors << CON_SEC_SHIFT, NFI_CON); + nfi_writew(nfc, INTR_AHB_DONE_EN, NFI_INTR_EN); + nfi_writel(nfc, lower_32_bits(addr), NFI_STRADDR); + + init_completion(&nfc->done); + reg = nfi_readl(nfc, NFI_CON) | CON_BRD; + nfi_writel(nfc, reg, NFI_CON); + nfi_writew(nfc, STAR_EN, NFI_STRDATA); + + rc = wait_for_completion_timeout(&nfc->done, msecs_to_jiffies(500)); + if (!rc) + dev_warn(nfc->dev, "read ahb/dma done timeout\n"); + + rc = readl_poll_timeout_atomic(nfc->regs + NFI_BYTELEN, reg, + ADDRCNTR_SEC(reg) >= sectors, 10, + MTK_TIMEOUT); + if (rc < 0) { + dev_err(nfc->dev, "subpage done timeout\n"); + bitflips = -EIO; + } else if (!raw) { + rc = mtk_ecc_wait_done(nfc->ecc, ECC_DECODE); + bitflips = rc < 0 ? -ETIMEDOUT : + mtk_nfc_update_ecc_stats(mtd, buf, start, sectors); + mtk_nfc_read_fdm(chip, start, sectors); + } + + dma_unmap_single(nfc->dev, addr, len, DMA_FROM_DEVICE); + + if (raw) + goto done; + + mtk_ecc_disable(nfc->ecc); + + if (clamp(mtk_nand->bad_mark.sec, start, end) == mtk_nand->bad_mark.sec) + mtk_nand->bad_mark.bm_swap(mtd, bufpoi, raw); +done: + nfi_writel(nfc, 0, NFI_CON); + + return bitflips; +} + +static int mtk_nfc_read_subpage_hwecc(struct nand_chip *chip, u32 off, + u32 len, u8 *p, int pg) +{ + return mtk_nfc_read_subpage(nand_to_mtd(chip), chip, off, len, p, pg, + 0); +} + +static int mtk_nfc_read_page_hwecc(struct nand_chip *chip, u8 *p, int oob_on, + int pg) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + return mtk_nfc_read_subpage(mtd, chip, 0, mtd->writesize, p, pg, 0); +} + +static int mtk_nfc_read_page_raw(struct nand_chip *chip, u8 *buf, int oob_on, + int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + struct mtk_nfc *nfc = nand_get_controller_data(chip); + struct mtk_nfc_fdm *fdm = &mtk_nand->fdm; + int i, ret; + + memset(nfc->buffer, 0xff, mtd->writesize + mtd->oobsize); + ret = mtk_nfc_read_subpage(mtd, chip, 0, mtd->writesize, nfc->buffer, + page, 1); + if (ret < 0) + return ret; + + for (i = 0; i < chip->ecc.steps; i++) { + memcpy(oob_ptr(chip, i), mtk_oob_ptr(chip, i), fdm->reg_size); + + if (i == mtk_nand->bad_mark.sec) + mtk_nand->bad_mark.bm_swap(mtd, nfc->buffer, 1); + + if (buf) + memcpy(data_ptr(chip, buf, i), mtk_data_ptr(chip, i), + chip->ecc.size); + } + + return ret; +} + +static int mtk_nfc_read_oob_std(struct nand_chip *chip, int page) +{ + return mtk_nfc_read_page_raw(chip, NULL, 1, page); +} + +static inline void mtk_nfc_hw_init(struct mtk_nfc *nfc) +{ + /* + * CNRNB: nand ready/busy register + * ------------------------------- + * 7:4: timeout register for polling the NAND busy/ready signal + * 0 : poll the status of the busy/ready signal after [7:4]*16 cycles. + */ + nfi_writew(nfc, 0xf1, NFI_CNRNB); + nfi_writel(nfc, PAGEFMT_8K_16K, NFI_PAGEFMT); + + mtk_nfc_hw_reset(nfc); + + nfi_readl(nfc, NFI_INTR_STA); + nfi_writel(nfc, 0, NFI_INTR_EN); +} + +static irqreturn_t mtk_nfc_irq(int irq, void *id) +{ + struct mtk_nfc *nfc = id; + u16 sta, ien; + + sta = nfi_readw(nfc, NFI_INTR_STA); + ien = nfi_readw(nfc, NFI_INTR_EN); + + if (!(sta & ien)) + return IRQ_NONE; + + nfi_writew(nfc, ~sta & ien, NFI_INTR_EN); + complete(&nfc->done); + + return IRQ_HANDLED; +} + +static int mtk_nfc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oob_region) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + struct mtk_nfc_fdm *fdm = &mtk_nand->fdm; + u32 eccsteps; + + eccsteps = mtd->writesize / chip->ecc.size; + + if (section >= eccsteps) + return -ERANGE; + + oob_region->length = fdm->reg_size - fdm->ecc_size; + oob_region->offset = section * fdm->reg_size + fdm->ecc_size; + + return 0; +} + +static int mtk_nfc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oob_region) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + u32 eccsteps; + + if (section) + return -ERANGE; + + eccsteps = mtd->writesize / chip->ecc.size; + oob_region->offset = mtk_nand->fdm.reg_size * eccsteps; + oob_region->length = mtd->oobsize - oob_region->offset; + + return 0; +} + +static const struct mtd_ooblayout_ops mtk_nfc_ooblayout_ops = { + .free = mtk_nfc_ooblayout_free, + .ecc = mtk_nfc_ooblayout_ecc, +}; + +static void mtk_nfc_set_fdm(struct mtk_nfc_fdm *fdm, struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd_to_nand(mtd); + struct mtk_nfc_nand_chip *chip = to_mtk_nand(nand); + struct mtk_nfc *nfc = nand_get_controller_data(nand); + u32 ecc_bytes; + + ecc_bytes = DIV_ROUND_UP(nand->ecc.strength * + mtk_ecc_get_parity_bits(nfc->ecc), 8); + + fdm->reg_size = chip->spare_per_sector - ecc_bytes; + if (fdm->reg_size > NFI_FDM_MAX_SIZE) + fdm->reg_size = NFI_FDM_MAX_SIZE; + + /* bad block mark storage */ + fdm->ecc_size = 1; +} + +static void mtk_nfc_set_bad_mark_ctl(struct mtk_nfc_bad_mark_ctl *bm_ctl, + struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd_to_nand(mtd); + + if (mtd->writesize == 512) { + bm_ctl->bm_swap = mtk_nfc_no_bad_mark_swap; + } else { + bm_ctl->bm_swap = mtk_nfc_bad_mark_swap; + bm_ctl->sec = mtd->writesize / mtk_data_len(nand); + bm_ctl->pos = mtd->writesize % mtk_data_len(nand); + } +} + +static int mtk_nfc_set_spare_per_sector(u32 *sps, struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd_to_nand(mtd); + struct mtk_nfc *nfc = nand_get_controller_data(nand); + const u8 *spare = nfc->caps->spare_size; + u32 eccsteps, i, closest_spare = 0; + + eccsteps = mtd->writesize / nand->ecc.size; + *sps = mtd->oobsize / eccsteps; + + if (nand->ecc.size == 1024) + *sps >>= 1; + + if (*sps < MTK_NFC_MIN_SPARE) + return -EINVAL; + + for (i = 0; i < nfc->caps->num_spare_size; i++) { + if (*sps >= spare[i] && spare[i] >= spare[closest_spare]) { + closest_spare = i; + if (*sps == spare[i]) + break; + } + } + + *sps = spare[closest_spare]; + + if (nand->ecc.size == 1024) + *sps <<= 1; + + return 0; +} + +static int mtk_nfc_ecc_init(struct device *dev, struct mtd_info *mtd) +{ + struct nand_chip *nand = mtd_to_nand(mtd); + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&nand->base); + struct mtk_nfc *nfc = nand_get_controller_data(nand); + u32 spare; + int free, ret; + + /* support only ecc hw mode */ + if (nand->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) { + dev_err(dev, "ecc.engine_type not supported\n"); + return -EINVAL; + } + + /* if optional dt settings not present */ + if (!nand->ecc.size || !nand->ecc.strength) { + /* use datasheet requirements */ + nand->ecc.strength = requirements->strength; + nand->ecc.size = requirements->step_size; + + /* + * align eccstrength and eccsize + * this controller only supports 512 and 1024 sizes + */ + if (nand->ecc.size < 1024) { + if (mtd->writesize > 512 && + nfc->caps->max_sector_size > 512) { + nand->ecc.size = 1024; + nand->ecc.strength <<= 1; + } else { + nand->ecc.size = 512; + } + } else { + nand->ecc.size = 1024; + } + + ret = mtk_nfc_set_spare_per_sector(&spare, mtd); + if (ret) + return ret; + + /* calculate oob bytes except ecc parity data */ + free = (nand->ecc.strength * mtk_ecc_get_parity_bits(nfc->ecc) + + 7) >> 3; + free = spare - free; + + /* + * enhance ecc strength if oob left is bigger than max FDM size + * or reduce ecc strength if oob size is not enough for ecc + * parity data. + */ + if (free > NFI_FDM_MAX_SIZE) { + spare -= NFI_FDM_MAX_SIZE; + nand->ecc.strength = (spare << 3) / + mtk_ecc_get_parity_bits(nfc->ecc); + } else if (free < 0) { + spare -= NFI_FDM_MIN_SIZE; + nand->ecc.strength = (spare << 3) / + mtk_ecc_get_parity_bits(nfc->ecc); + } + } + + mtk_ecc_adjust_strength(nfc->ecc, &nand->ecc.strength); + + dev_info(dev, "eccsize %d eccstrength %d\n", + nand->ecc.size, nand->ecc.strength); + + return 0; +} + +static int mtk_nfc_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct device *dev = mtd->dev.parent; + struct mtk_nfc *nfc = nand_get_controller_data(chip); + struct mtk_nfc_nand_chip *mtk_nand = to_mtk_nand(chip); + int len; + int ret; + + if (chip->options & NAND_BUSWIDTH_16) { + dev_err(dev, "16bits buswidth not supported"); + return -EINVAL; + } + + /* store bbt magic in page, cause OOB is not protected */ + if (chip->bbt_options & NAND_BBT_USE_FLASH) + chip->bbt_options |= NAND_BBT_NO_OOB; + + ret = mtk_nfc_ecc_init(dev, mtd); + if (ret) + return ret; + + ret = mtk_nfc_set_spare_per_sector(&mtk_nand->spare_per_sector, mtd); + if (ret) + return ret; + + mtk_nfc_set_fdm(&mtk_nand->fdm, mtd); + mtk_nfc_set_bad_mark_ctl(&mtk_nand->bad_mark, mtd); + + len = mtd->writesize + mtd->oobsize; + nfc->buffer = devm_kzalloc(dev, len, GFP_KERNEL); + if (!nfc->buffer) + return -ENOMEM; + + return 0; +} + +static const struct nand_controller_ops mtk_nfc_controller_ops = { + .attach_chip = mtk_nfc_attach_chip, + .setup_interface = mtk_nfc_setup_interface, + .exec_op = mtk_nfc_exec_op, +}; + +static int mtk_nfc_nand_chip_init(struct device *dev, struct mtk_nfc *nfc, + struct device_node *np) +{ + struct mtk_nfc_nand_chip *chip; + struct nand_chip *nand; + struct mtd_info *mtd; + int nsels; + u32 tmp; + int ret; + int i; + + if (!of_get_property(np, "reg", &nsels)) + return -ENODEV; + + nsels /= sizeof(u32); + if (!nsels || nsels > MTK_NAND_MAX_NSELS) { + dev_err(dev, "invalid reg property size %d\n", nsels); + return -EINVAL; + } + + chip = devm_kzalloc(dev, struct_size(chip, sels, nsels), + GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->nsels = nsels; + for (i = 0; i < nsels; i++) { + ret = of_property_read_u32_index(np, "reg", i, &tmp); + if (ret) { + dev_err(dev, "reg property failure : %d\n", ret); + return ret; + } + + if (tmp >= MTK_NAND_MAX_NSELS) { + dev_err(dev, "invalid CS: %u\n", tmp); + return -EINVAL; + } + + if (test_and_set_bit(tmp, &nfc->assigned_cs)) { + dev_err(dev, "CS %u already assigned\n", tmp); + return -EINVAL; + } + + chip->sels[i] = tmp; + } + + nand = &chip->nand; + nand->controller = &nfc->controller; + + nand_set_flash_node(nand, np); + nand_set_controller_data(nand, nfc); + + nand->options |= NAND_USES_DMA | NAND_SUBPAGE_READ; + + /* set default mode in case dt entry is missing */ + nand->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + + nand->ecc.write_subpage = mtk_nfc_write_subpage_hwecc; + nand->ecc.write_page_raw = mtk_nfc_write_page_raw; + nand->ecc.write_page = mtk_nfc_write_page_hwecc; + nand->ecc.write_oob_raw = mtk_nfc_write_oob_std; + nand->ecc.write_oob = mtk_nfc_write_oob_std; + + nand->ecc.read_subpage = mtk_nfc_read_subpage_hwecc; + nand->ecc.read_page_raw = mtk_nfc_read_page_raw; + nand->ecc.read_page = mtk_nfc_read_page_hwecc; + nand->ecc.read_oob_raw = mtk_nfc_read_oob_std; + nand->ecc.read_oob = mtk_nfc_read_oob_std; + + mtd = nand_to_mtd(nand); + mtd->owner = THIS_MODULE; + mtd->dev.parent = dev; + mtd->name = MTK_NAME; + mtd_set_ooblayout(mtd, &mtk_nfc_ooblayout_ops); + + mtk_nfc_hw_init(nfc); + + ret = nand_scan(nand, nsels); + if (ret) + return ret; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(dev, "mtd parse partition error\n"); + nand_cleanup(nand); + return ret; + } + + list_add_tail(&chip->node, &nfc->chips); + + return 0; +} + +static void mtk_nfc_nand_chips_cleanup(struct mtk_nfc *nfc) +{ + struct mtk_nfc_nand_chip *mtk_chip; + struct nand_chip *chip; + int ret; + + while (!list_empty(&nfc->chips)) { + mtk_chip = list_first_entry(&nfc->chips, + struct mtk_nfc_nand_chip, node); + chip = &mtk_chip->nand; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + list_del(&mtk_chip->node); + } +} + +static int mtk_nfc_nand_chips_init(struct device *dev, struct mtk_nfc *nfc) +{ + struct device_node *np = dev->of_node; + int ret; + + for_each_child_of_node_scoped(np, nand_np) { + ret = mtk_nfc_nand_chip_init(dev, nfc, nand_np); + if (ret) { + mtk_nfc_nand_chips_cleanup(nfc); + return ret; + } + } + + return 0; +} + +static const struct mtk_nfc_caps mtk_nfc_caps_mt2701 = { + .spare_size = spare_size_mt2701, + .num_spare_size = 16, + .pageformat_spare_shift = 4, + .nfi_clk_div = 1, + .max_sector = 16, + .max_sector_size = 1024, +}; + +static const struct mtk_nfc_caps mtk_nfc_caps_mt2712 = { + .spare_size = spare_size_mt2712, + .num_spare_size = 19, + .pageformat_spare_shift = 16, + .nfi_clk_div = 2, + .max_sector = 16, + .max_sector_size = 1024, +}; + +static const struct mtk_nfc_caps mtk_nfc_caps_mt7622 = { + .spare_size = spare_size_mt7622, + .num_spare_size = 4, + .pageformat_spare_shift = 4, + .nfi_clk_div = 1, + .max_sector = 8, + .max_sector_size = 512, +}; + +static const struct of_device_id mtk_nfc_id_table[] = { + { + .compatible = "mediatek,mt2701-nfc", + .data = &mtk_nfc_caps_mt2701, + }, { + .compatible = "mediatek,mt2712-nfc", + .data = &mtk_nfc_caps_mt2712, + }, { + .compatible = "mediatek,mt7622-nfc", + .data = &mtk_nfc_caps_mt7622, + }, + {} +}; +MODULE_DEVICE_TABLE(of, mtk_nfc_id_table); + +static int mtk_nfc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct mtk_nfc *nfc; + int ret, irq; + + nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nand_controller_init(&nfc->controller); + INIT_LIST_HEAD(&nfc->chips); + nfc->controller.ops = &mtk_nfc_controller_ops; + + /* probe defer if not ready */ + nfc->ecc = of_mtk_ecc_get(np); + if (IS_ERR(nfc->ecc)) + return PTR_ERR(nfc->ecc); + else if (!nfc->ecc) + return -ENODEV; + + nfc->caps = of_device_get_match_data(dev); + nfc->dev = dev; + + nfc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(nfc->regs)) { + ret = PTR_ERR(nfc->regs); + goto release_ecc; + } + + nfc->clk.nfi_clk = devm_clk_get_enabled(dev, "nfi_clk"); + if (IS_ERR(nfc->clk.nfi_clk)) { + dev_err(dev, "no clk\n"); + ret = PTR_ERR(nfc->clk.nfi_clk); + goto release_ecc; + } + + nfc->clk.pad_clk = devm_clk_get_enabled(dev, "pad_clk"); + if (IS_ERR(nfc->clk.pad_clk)) { + dev_err(dev, "no pad clk\n"); + ret = PTR_ERR(nfc->clk.pad_clk); + goto release_ecc; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = -EINVAL; + goto release_ecc; + } + + ret = devm_request_irq(dev, irq, mtk_nfc_irq, 0x0, "mtk-nand", nfc); + if (ret) { + dev_err(dev, "failed to request nfi irq\n"); + goto release_ecc; + } + + ret = dma_set_mask(dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(dev, "failed to set dma mask\n"); + goto release_ecc; + } + + platform_set_drvdata(pdev, nfc); + + ret = mtk_nfc_nand_chips_init(dev, nfc); + if (ret) { + dev_err(dev, "failed to init nand chips\n"); + goto release_ecc; + } + + return 0; + +release_ecc: + mtk_ecc_release(nfc->ecc); + + return ret; +} + +static void mtk_nfc_remove(struct platform_device *pdev) +{ + struct mtk_nfc *nfc = platform_get_drvdata(pdev); + + mtk_nfc_nand_chips_cleanup(nfc); + mtk_ecc_release(nfc->ecc); +} + +#ifdef CONFIG_PM_SLEEP +static int mtk_nfc_suspend(struct device *dev) +{ + struct mtk_nfc *nfc = dev_get_drvdata(dev); + + clk_disable_unprepare(nfc->clk.nfi_clk); + clk_disable_unprepare(nfc->clk.pad_clk); + + return 0; +} + +static int mtk_nfc_resume(struct device *dev) +{ + struct mtk_nfc *nfc = dev_get_drvdata(dev); + struct mtk_nfc_nand_chip *chip; + struct nand_chip *nand; + int ret; + u32 i; + + udelay(200); + + ret = clk_prepare_enable(nfc->clk.nfi_clk); + if (ret) { + dev_err(dev, "failed to enable nfi clk\n"); + return ret; + } + + ret = clk_prepare_enable(nfc->clk.pad_clk); + if (ret) { + dev_err(dev, "failed to enable pad clk\n"); + clk_disable_unprepare(nfc->clk.nfi_clk); + return ret; + } + + /* reset NAND chip if VCC was powered off */ + list_for_each_entry(chip, &nfc->chips, node) { + nand = &chip->nand; + for (i = 0; i < chip->nsels; i++) + nand_reset(nand, i); + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(mtk_nfc_pm_ops, mtk_nfc_suspend, mtk_nfc_resume); +#endif + +static struct platform_driver mtk_nfc_driver = { + .probe = mtk_nfc_probe, + .remove = mtk_nfc_remove, + .driver = { + .name = MTK_NAME, + .of_match_table = mtk_nfc_id_table, +#ifdef CONFIG_PM_SLEEP + .pm = &mtk_nfc_pm_ops, +#endif + }, +}; + +module_platform_driver(mtk_nfc_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_AUTHOR("Xiaolei Li "); +MODULE_DESCRIPTION("MTK Nand Flash Controller Driver"); diff --git a/drivers/mtd/nand/raw/mxc_nand.c b/drivers/mtd/nand/raw/mxc_nand.c new file mode 100644 index 000000000..4d8b92e7e --- /dev/null +++ b/drivers/mtd/nand/raw/mxc_nand.c @@ -0,0 +1,1841 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008 Sascha Hauer, kernel@pengutronix.de + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "mxc_nand" + +/* Addresses for NFC registers */ +#define NFC_V1_V2_BUF_SIZE (host->regs + 0x00) +#define NFC_V1_V2_BUF_ADDR (host->regs + 0x04) +#define NFC_V1_V2_FLASH_ADDR (host->regs + 0x06) +#define NFC_V1_V2_FLASH_CMD (host->regs + 0x08) +#define NFC_V1_V2_CONFIG (host->regs + 0x0a) +#define NFC_V1_V2_ECC_STATUS_RESULT (host->regs + 0x0c) +#define NFC_V1_V2_RSLTMAIN_AREA (host->regs + 0x0e) +#define NFC_V21_RSLTSPARE_AREA (host->regs + 0x10) +#define NFC_V1_V2_WRPROT (host->regs + 0x12) +#define NFC_V1_UNLOCKSTART_BLKADDR (host->regs + 0x14) +#define NFC_V1_UNLOCKEND_BLKADDR (host->regs + 0x16) +#define NFC_V21_UNLOCKSTART_BLKADDR0 (host->regs + 0x20) +#define NFC_V21_UNLOCKSTART_BLKADDR1 (host->regs + 0x24) +#define NFC_V21_UNLOCKSTART_BLKADDR2 (host->regs + 0x28) +#define NFC_V21_UNLOCKSTART_BLKADDR3 (host->regs + 0x2c) +#define NFC_V21_UNLOCKEND_BLKADDR0 (host->regs + 0x22) +#define NFC_V21_UNLOCKEND_BLKADDR1 (host->regs + 0x26) +#define NFC_V21_UNLOCKEND_BLKADDR2 (host->regs + 0x2a) +#define NFC_V21_UNLOCKEND_BLKADDR3 (host->regs + 0x2e) +#define NFC_V1_V2_NF_WRPRST (host->regs + 0x18) +#define NFC_V1_V2_CONFIG1 (host->regs + 0x1a) +#define NFC_V1_V2_CONFIG2 (host->regs + 0x1c) + +#define NFC_V1_V2_ECC_STATUS_RESULT_ERM GENMASK(3, 2) + +#define NFC_V2_CONFIG1_ECC_MODE_4 (1 << 0) +#define NFC_V1_V2_CONFIG1_SP_EN (1 << 2) +#define NFC_V1_V2_CONFIG1_ECC_EN (1 << 3) +#define NFC_V1_V2_CONFIG1_INT_MSK (1 << 4) +#define NFC_V1_V2_CONFIG1_BIG (1 << 5) +#define NFC_V1_V2_CONFIG1_RST (1 << 6) +#define NFC_V1_V2_CONFIG1_CE (1 << 7) +#define NFC_V2_CONFIG1_ONE_CYCLE (1 << 8) +#define NFC_V2_CONFIG1_PPB(x) (((x) & 0x3) << 9) +#define NFC_V2_CONFIG1_FP_INT (1 << 11) + +#define NFC_V1_V2_CONFIG2_INT (1 << 15) + +/* + * Operation modes for the NFC. Valid for v1, v2 and v3 + * type controllers. + */ +#define NFC_CMD (1 << 0) +#define NFC_ADDR (1 << 1) +#define NFC_INPUT (1 << 2) +#define NFC_OUTPUT (1 << 3) +#define NFC_ID (1 << 4) +#define NFC_STATUS (1 << 5) + +#define NFC_V3_FLASH_CMD (host->regs_axi + 0x00) +#define NFC_V3_FLASH_ADDR0 (host->regs_axi + 0x04) + +#define NFC_V3_CONFIG1 (host->regs_axi + 0x34) +#define NFC_V3_CONFIG1_SP_EN (1 << 0) +#define NFC_V3_CONFIG1_RBA(x) (((x) & 0x7 ) << 4) + +#define NFC_V3_ECC_STATUS_RESULT (host->regs_axi + 0x38) + +#define NFC_V3_LAUNCH (host->regs_axi + 0x40) + +#define NFC_V3_WRPROT (host->regs_ip + 0x0) +#define NFC_V3_WRPROT_LOCK_TIGHT (1 << 0) +#define NFC_V3_WRPROT_LOCK (1 << 1) +#define NFC_V3_WRPROT_UNLOCK (1 << 2) +#define NFC_V3_WRPROT_BLS_UNLOCK (2 << 6) + +#define NFC_V3_WRPROT_UNLOCK_BLK_ADD0 (host->regs_ip + 0x04) + +#define NFC_V3_CONFIG2 (host->regs_ip + 0x24) +#define NFC_V3_CONFIG2_PS_512 (0 << 0) +#define NFC_V3_CONFIG2_PS_2048 (1 << 0) +#define NFC_V3_CONFIG2_PS_4096 (2 << 0) +#define NFC_V3_CONFIG2_ONE_CYCLE (1 << 2) +#define NFC_V3_CONFIG2_ECC_EN (1 << 3) +#define NFC_V3_CONFIG2_2CMD_PHASES (1 << 4) +#define NFC_V3_CONFIG2_NUM_ADDR_PHASE0 (1 << 5) +#define NFC_V3_CONFIG2_ECC_MODE_8 (1 << 6) +#define NFC_V3_CONFIG2_PPB(x, shift) (((x) & 0x3) << shift) +#define NFC_V3_CONFIG2_NUM_ADDR_PHASE1(x) (((x) & 0x3) << 12) +#define NFC_V3_CONFIG2_INT_MSK (1 << 15) +#define NFC_V3_CONFIG2_ST_CMD(x) (((x) & 0xff) << 24) +#define NFC_V3_CONFIG2_SPAS(x) (((x) & 0xff) << 16) + +#define NFC_V3_CONFIG3 (host->regs_ip + 0x28) +#define NFC_V3_CONFIG3_ADD_OP(x) (((x) & 0x3) << 0) +#define NFC_V3_CONFIG3_FW8 (1 << 3) +#define NFC_V3_CONFIG3_SBB(x) (((x) & 0x7) << 8) +#define NFC_V3_CONFIG3_NUM_OF_DEVICES(x) (((x) & 0x7) << 12) +#define NFC_V3_CONFIG3_RBB_MODE (1 << 15) +#define NFC_V3_CONFIG3_NO_SDMA (1 << 20) + +#define NFC_V3_IPC (host->regs_ip + 0x2C) +#define NFC_V3_IPC_CREQ (1 << 0) +#define NFC_V3_IPC_INT (1 << 31) + +#define NFC_V3_DELAY_LINE (host->regs_ip + 0x34) + +struct mxc_nand_host; + +struct mxc_nand_devtype_data { + void (*preset)(struct mtd_info *); + int (*read_page)(struct nand_chip *chip); + void (*send_cmd)(struct mxc_nand_host *, uint16_t, int); + void (*send_addr)(struct mxc_nand_host *, uint16_t, int); + void (*send_page)(struct mtd_info *, unsigned int); + void (*send_read_id)(struct mxc_nand_host *); + uint16_t (*get_dev_status)(struct mxc_nand_host *); + int (*check_int)(struct mxc_nand_host *); + void (*irq_control)(struct mxc_nand_host *, int); + u32 (*get_ecc_status)(struct nand_chip *); + const struct mtd_ooblayout_ops *ooblayout; + void (*select_chip)(struct nand_chip *chip, int cs); + int (*setup_interface)(struct nand_chip *chip, int csline, + const struct nand_interface_config *conf); + void (*enable_hwecc)(struct nand_chip *chip, bool enable); + + /* + * On i.MX21 the CONFIG2:INT bit cannot be read if interrupts are masked + * (CONFIG1:INT_MSK is set). To handle this the driver uses + * enable_irq/disable_irq_nosync instead of CONFIG1:INT_MSK + */ + int irqpending_quirk; + int needs_ip; + + size_t regs_offset; + size_t spare0_offset; + size_t axi_offset; + + int spare_len; + int eccbytes; + int eccsize; + int ppb_shift; +}; + +struct mxc_nand_host { + struct nand_chip nand; + struct device *dev; + + void __iomem *spare0; + void __iomem *main_area0; + + void __iomem *base; + void __iomem *regs; + void __iomem *regs_axi; + void __iomem *regs_ip; + int status_request; + struct clk *clk; + int clk_act; + int irq; + int eccsize; + int used_oobsize; + int active_cs; + unsigned int ecc_stats_v1; + + struct completion op_completion; + + void *data_buf; + + const struct mxc_nand_devtype_data *devtype_data; +}; + +static const char * const part_probes[] = { + "cmdlinepart", "RedBoot", "ofpart", NULL }; + +static void memcpy32_fromio(void *trg, const void __iomem *src, size_t size) +{ + int i; + u32 *t = trg; + const __iomem u32 *s = src; + + for (i = 0; i < (size >> 2); i++) + *t++ = __raw_readl(s++); +} + +static void memcpy16_fromio(void *trg, const void __iomem *src, size_t size) +{ + int i; + u16 *t = trg; + const __iomem u16 *s = src; + + /* We assume that src (IO) is always 32bit aligned */ + if (PTR_ALIGN(trg, 4) == trg && IS_ALIGNED(size, 4)) { + memcpy32_fromio(trg, src, size); + return; + } + + for (i = 0; i < (size >> 1); i++) + *t++ = __raw_readw(s++); +} + +static inline void memcpy32_toio(void __iomem *trg, const void *src, int size) +{ + /* __iowrite32_copy use 32bit size values so divide by 4 */ + __iowrite32_copy(trg, src, size / 4); +} + +static void memcpy16_toio(void __iomem *trg, const void *src, int size) +{ + int i; + __iomem u16 *t = trg; + const u16 *s = src; + + /* We assume that trg (IO) is always 32bit aligned */ + if (PTR_ALIGN(src, 4) == src && IS_ALIGNED(size, 4)) { + memcpy32_toio(trg, src, size); + return; + } + + for (i = 0; i < (size >> 1); i++) + __raw_writew(*s++, t++); +} + +/* + * The controller splits a page into data chunks of 512 bytes + partial oob. + * There are writesize / 512 such chunks, the size of the partial oob parts is + * oobsize / #chunks rounded down to a multiple of 2. The last oob chunk then + * contains additionally the byte lost by rounding (if any). + * This function handles the needed shuffling between host->data_buf (which + * holds a page in natural order, i.e. writesize bytes data + oobsize bytes + * spare) and the NFC buffer. + */ +static void copy_spare(struct mtd_info *mtd, bool bfrom, void *buf) +{ + struct nand_chip *this = mtd_to_nand(mtd); + struct mxc_nand_host *host = nand_get_controller_data(this); + u16 i, oob_chunk_size; + u16 num_chunks = mtd->writesize / 512; + + u8 *d = buf; + u8 __iomem *s = host->spare0; + u16 sparebuf_size = host->devtype_data->spare_len; + + /* size of oob chunk for all but possibly the last one */ + oob_chunk_size = (host->used_oobsize / num_chunks) & ~1; + + if (bfrom) { + for (i = 0; i < num_chunks - 1; i++) + memcpy16_fromio(d + i * oob_chunk_size, + s + i * sparebuf_size, + oob_chunk_size); + + /* the last chunk */ + memcpy16_fromio(d + i * oob_chunk_size, + s + i * sparebuf_size, + host->used_oobsize - i * oob_chunk_size); + } else { + for (i = 0; i < num_chunks - 1; i++) + memcpy16_toio(&s[i * sparebuf_size], + &d[i * oob_chunk_size], + oob_chunk_size); + + /* the last chunk */ + memcpy16_toio(&s[i * sparebuf_size], + &d[i * oob_chunk_size], + host->used_oobsize - i * oob_chunk_size); + } +} + +static int check_int_v3(struct mxc_nand_host *host) +{ + uint32_t tmp; + + tmp = readl(NFC_V3_IPC); + if (!(tmp & NFC_V3_IPC_INT)) + return 0; + + tmp &= ~NFC_V3_IPC_INT; + writel(tmp, NFC_V3_IPC); + + return 1; +} + +static int check_int_v1_v2(struct mxc_nand_host *host) +{ + uint32_t tmp; + + tmp = readw(NFC_V1_V2_CONFIG2); + if (!(tmp & NFC_V1_V2_CONFIG2_INT)) + return 0; + + if (!host->devtype_data->irqpending_quirk) + writew(tmp & ~NFC_V1_V2_CONFIG2_INT, NFC_V1_V2_CONFIG2); + + return 1; +} + +static void irq_control_v1_v2(struct mxc_nand_host *host, int activate) +{ + uint16_t tmp; + + tmp = readw(NFC_V1_V2_CONFIG1); + + if (activate) + tmp &= ~NFC_V1_V2_CONFIG1_INT_MSK; + else + tmp |= NFC_V1_V2_CONFIG1_INT_MSK; + + writew(tmp, NFC_V1_V2_CONFIG1); +} + +static void irq_control_v3(struct mxc_nand_host *host, int activate) +{ + uint32_t tmp; + + tmp = readl(NFC_V3_CONFIG2); + + if (activate) + tmp &= ~NFC_V3_CONFIG2_INT_MSK; + else + tmp |= NFC_V3_CONFIG2_INT_MSK; + + writel(tmp, NFC_V3_CONFIG2); +} + +static void irq_control(struct mxc_nand_host *host, int activate) +{ + if (host->devtype_data->irqpending_quirk) { + if (activate) + enable_irq(host->irq); + else + disable_irq_nosync(host->irq); + } else { + host->devtype_data->irq_control(host, activate); + } +} + +static u32 get_ecc_status_v1(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mxc_nand_host *host = nand_get_controller_data(chip); + unsigned int ecc_stats, max_bitflips = 0; + int no_subpages, i; + + no_subpages = mtd->writesize >> 9; + + ecc_stats = host->ecc_stats_v1; + + for (i = 0; i < no_subpages; i++) { + switch (ecc_stats & 0x3) { + case 0: + default: + break; + case 1: + mtd->ecc_stats.corrected++; + max_bitflips = 1; + break; + case 2: + mtd->ecc_stats.failed++; + break; + } + + ecc_stats >>= 2; + } + + return max_bitflips; +} + +static u32 get_ecc_status_v2_v3(struct nand_chip *chip, unsigned int ecc_stat) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mxc_nand_host *host = nand_get_controller_data(chip); + u8 ecc_bit_mask, err_limit; + unsigned int max_bitflips = 0; + int no_subpages, err; + + ecc_bit_mask = (host->eccsize == 4) ? 0x7 : 0xf; + err_limit = (host->eccsize == 4) ? 0x4 : 0x8; + + no_subpages = mtd->writesize >> 9; + + do { + err = ecc_stat & ecc_bit_mask; + if (err > err_limit) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += err; + max_bitflips = max_t(unsigned int, max_bitflips, err); + } + + ecc_stat >>= 4; + } while (--no_subpages); + + return max_bitflips; +} + +static u32 get_ecc_status_v2(struct nand_chip *chip) +{ + struct mxc_nand_host *host = nand_get_controller_data(chip); + + u32 ecc_stat = readl(NFC_V1_V2_ECC_STATUS_RESULT); + + return get_ecc_status_v2_v3(chip, ecc_stat); +} + +static u32 get_ecc_status_v3(struct nand_chip *chip) +{ + struct mxc_nand_host *host = nand_get_controller_data(chip); + + u32 ecc_stat = readl(NFC_V3_ECC_STATUS_RESULT); + + return get_ecc_status_v2_v3(chip, ecc_stat); +} + +static irqreturn_t mxc_nfc_irq(int irq, void *dev_id) +{ + struct mxc_nand_host *host = dev_id; + + if (!host->devtype_data->check_int(host)) + return IRQ_NONE; + + irq_control(host, 0); + + complete(&host->op_completion); + + return IRQ_HANDLED; +} + +/* This function polls the NANDFC to wait for the basic operation to + * complete by checking the INT bit of config2 register. + */ +static int wait_op_done(struct mxc_nand_host *host, int useirq) +{ + int ret = 0; + + /* + * If operation is already complete, don't bother to setup an irq or a + * loop. + */ + if (host->devtype_data->check_int(host)) + return 0; + + if (useirq) { + unsigned long time_left; + + reinit_completion(&host->op_completion); + + irq_control(host, 1); + + time_left = wait_for_completion_timeout(&host->op_completion, HZ); + if (!time_left && !host->devtype_data->check_int(host)) { + dev_dbg(host->dev, "timeout waiting for irq\n"); + ret = -ETIMEDOUT; + } + } else { + int max_retries = 8000; + int done; + + do { + udelay(1); + + done = host->devtype_data->check_int(host); + if (done) + break; + + } while (--max_retries); + + if (!done) { + dev_dbg(host->dev, "timeout polling for completion\n"); + ret = -ETIMEDOUT; + } + } + + WARN_ONCE(ret < 0, "timeout! useirq=%d\n", useirq); + + return ret; +} + +static void send_cmd_v3(struct mxc_nand_host *host, uint16_t cmd, int useirq) +{ + /* fill command */ + writel(cmd, NFC_V3_FLASH_CMD); + + /* send out command */ + writel(NFC_CMD, NFC_V3_LAUNCH); + + /* Wait for operation to complete */ + wait_op_done(host, useirq); +} + +/* This function issues the specified command to the NAND device and + * waits for completion. */ +static void send_cmd_v1_v2(struct mxc_nand_host *host, uint16_t cmd, int useirq) +{ + dev_dbg(host->dev, "send_cmd(host, 0x%x, %d)\n", cmd, useirq); + + writew(cmd, NFC_V1_V2_FLASH_CMD); + writew(NFC_CMD, NFC_V1_V2_CONFIG2); + + if (host->devtype_data->irqpending_quirk && (cmd == NAND_CMD_RESET)) { + int max_retries = 100; + /* Reset completion is indicated by NFC_CONFIG2 */ + /* being set to 0 */ + while (max_retries-- > 0) { + if (readw(NFC_V1_V2_CONFIG2) == 0) { + break; + } + udelay(1); + } + if (max_retries < 0) + dev_dbg(host->dev, "%s: RESET failed\n", __func__); + } else { + /* Wait for operation to complete */ + wait_op_done(host, useirq); + } +} + +static void send_addr_v3(struct mxc_nand_host *host, uint16_t addr, int islast) +{ + /* fill address */ + writel(addr, NFC_V3_FLASH_ADDR0); + + /* send out address */ + writel(NFC_ADDR, NFC_V3_LAUNCH); + + wait_op_done(host, 0); +} + +/* This function sends an address (or partial address) to the + * NAND device. The address is used to select the source/destination for + * a NAND command. */ +static void send_addr_v1_v2(struct mxc_nand_host *host, uint16_t addr, int islast) +{ + dev_dbg(host->dev, "send_addr(host, 0x%x %d)\n", addr, islast); + + writew(addr, NFC_V1_V2_FLASH_ADDR); + writew(NFC_ADDR, NFC_V1_V2_CONFIG2); + + /* Wait for operation to complete */ + wait_op_done(host, islast); +} + +static void send_page_v3(struct mtd_info *mtd, unsigned int ops) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + struct mxc_nand_host *host = nand_get_controller_data(nand_chip); + uint32_t tmp; + + tmp = readl(NFC_V3_CONFIG1); + tmp &= ~(7 << 4); + writel(tmp, NFC_V3_CONFIG1); + + /* transfer data from NFC ram to nand */ + writel(ops, NFC_V3_LAUNCH); + + wait_op_done(host, false); +} + +static void send_page_v2(struct mtd_info *mtd, unsigned int ops) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + struct mxc_nand_host *host = nand_get_controller_data(nand_chip); + + /* NANDFC buffer 0 is used for page read/write */ + writew(host->active_cs << 4, NFC_V1_V2_BUF_ADDR); + + writew(ops, NFC_V1_V2_CONFIG2); + + /* Wait for operation to complete */ + wait_op_done(host, true); +} + +static void send_page_v1(struct mtd_info *mtd, unsigned int ops) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + struct mxc_nand_host *host = nand_get_controller_data(nand_chip); + int bufs, i; + + if (mtd->writesize > 512) + bufs = 4; + else + bufs = 1; + + for (i = 0; i < bufs; i++) { + + /* NANDFC buffer 0 is used for page read/write */ + writew((host->active_cs << 4) | i, NFC_V1_V2_BUF_ADDR); + + writew(ops, NFC_V1_V2_CONFIG2); + + /* Wait for operation to complete */ + wait_op_done(host, true); + } +} + +static void send_read_id_v3(struct mxc_nand_host *host) +{ + /* Read ID into main buffer */ + writel(NFC_ID, NFC_V3_LAUNCH); + + wait_op_done(host, true); + + memcpy32_fromio(host->data_buf, host->main_area0, 16); +} + +/* Request the NANDFC to perform a read of the NAND device ID. */ +static void send_read_id_v1_v2(struct mxc_nand_host *host) +{ + /* NANDFC buffer 0 is used for device ID output */ + writew(host->active_cs << 4, NFC_V1_V2_BUF_ADDR); + + writew(NFC_ID, NFC_V1_V2_CONFIG2); + + /* Wait for operation to complete */ + wait_op_done(host, true); + + memcpy32_fromio(host->data_buf, host->main_area0, 16); +} + +static uint16_t get_dev_status_v3(struct mxc_nand_host *host) +{ + writew(NFC_STATUS, NFC_V3_LAUNCH); + wait_op_done(host, true); + + return readl(NFC_V3_CONFIG1) >> 16; +} + +/* This function requests the NANDFC to perform a read of the + * NAND device status and returns the current status. */ +static uint16_t get_dev_status_v1_v2(struct mxc_nand_host *host) +{ + void __iomem *main_buf = host->main_area0; + uint32_t store; + uint16_t ret; + + writew(host->active_cs << 4, NFC_V1_V2_BUF_ADDR); + + /* + * The device status is stored in main_area0. To + * prevent corruption of the buffer save the value + * and restore it afterwards. + */ + store = readl(main_buf); + + writew(NFC_STATUS, NFC_V1_V2_CONFIG2); + wait_op_done(host, true); + + ret = readw(main_buf); + + writel(store, main_buf); + + return ret; +} + +static void mxc_nand_enable_hwecc_v1_v2(struct nand_chip *chip, bool enable) +{ + struct mxc_nand_host *host = nand_get_controller_data(chip); + uint16_t config1; + + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return; + + config1 = readw(NFC_V1_V2_CONFIG1); + + if (enable) + config1 |= NFC_V1_V2_CONFIG1_ECC_EN; + else + config1 &= ~NFC_V1_V2_CONFIG1_ECC_EN; + + writew(config1, NFC_V1_V2_CONFIG1); +} + +static void mxc_nand_enable_hwecc_v3(struct nand_chip *chip, bool enable) +{ + struct mxc_nand_host *host = nand_get_controller_data(chip); + uint32_t config2; + + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return; + + config2 = readl(NFC_V3_CONFIG2); + + if (enable) + config2 |= NFC_V3_CONFIG2_ECC_EN; + else + config2 &= ~NFC_V3_CONFIG2_ECC_EN; + + writel(config2, NFC_V3_CONFIG2); +} + +static int mxc_nand_read_page_v1(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mxc_nand_host *host = nand_get_controller_data(chip); + int no_subpages; + int i; + unsigned int ecc_stats = 0; + + if (mtd->writesize) + no_subpages = mtd->writesize >> 9; + else + /* READ PARAMETER PAGE is called when mtd->writesize is not yet set */ + no_subpages = 1; + + for (i = 0; i < no_subpages; i++) { + /* NANDFC buffer 0 is used for page read/write */ + writew((host->active_cs << 4) | i, NFC_V1_V2_BUF_ADDR); + + writew(NFC_OUTPUT, NFC_V1_V2_CONFIG2); + + /* Wait for operation to complete */ + wait_op_done(host, true); + + ecc_stats |= FIELD_GET(NFC_V1_V2_ECC_STATUS_RESULT_ERM, + readw(NFC_V1_V2_ECC_STATUS_RESULT)) << i * 2; + } + + host->ecc_stats_v1 = ecc_stats; + + return 0; +} + +static int mxc_nand_read_page_v2_v3(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mxc_nand_host *host = nand_get_controller_data(chip); + + host->devtype_data->send_page(mtd, NFC_OUTPUT); + + return 0; +} + +static int mxc_nand_read_page(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mxc_nand_host *host = nand_get_controller_data(chip); + int ret; + + host->devtype_data->enable_hwecc(chip, true); + + ret = nand_read_page_op(chip, page, 0, buf, mtd->writesize); + + host->devtype_data->enable_hwecc(chip, false); + + if (ret) + return ret; + + if (oob_required) + copy_spare(mtd, true, chip->oob_poi); + + return host->devtype_data->get_ecc_status(chip); +} + +static int mxc_nand_read_page_raw(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + ret = nand_read_page_op(chip, page, 0, buf, mtd->writesize); + if (ret) + return ret; + + if (oob_required) + copy_spare(mtd, true, chip->oob_poi); + + return 0; +} + +static int mxc_nand_read_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mxc_nand_host *host = nand_get_controller_data(chip); + int ret; + + ret = nand_read_page_op(chip, page, 0, host->data_buf, mtd->writesize); + if (ret) + return ret; + + copy_spare(mtd, true, chip->oob_poi); + + return 0; +} + +static int mxc_nand_write_page_ecc(struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mxc_nand_host *host = nand_get_controller_data(chip); + int ret; + + copy_spare(mtd, false, chip->oob_poi); + + host->devtype_data->enable_hwecc(chip, true); + + ret = nand_prog_page_op(chip, page, 0, buf, mtd->writesize); + + host->devtype_data->enable_hwecc(chip, false); + + return ret; +} + +static int mxc_nand_write_page_raw(struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + copy_spare(mtd, false, chip->oob_poi); + + return nand_prog_page_op(chip, page, 0, buf, mtd->writesize); +} + +static int mxc_nand_write_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mxc_nand_host *host = nand_get_controller_data(chip); + + memset(host->data_buf, 0xff, mtd->writesize); + copy_spare(mtd, false, chip->oob_poi); + + return nand_prog_page_op(chip, page, 0, host->data_buf, mtd->writesize); +} + +/* This function is used by upper layer for select and + * deselect of the NAND chip */ +static void mxc_nand_select_chip_v1_v3(struct nand_chip *nand_chip, int chip) +{ + struct mxc_nand_host *host = nand_get_controller_data(nand_chip); + + if (chip == -1) { + /* Disable the NFC clock */ + if (host->clk_act) { + clk_disable_unprepare(host->clk); + host->clk_act = 0; + } + return; + } + + if (!host->clk_act) { + /* Enable the NFC clock */ + clk_prepare_enable(host->clk); + host->clk_act = 1; + } +} + +static void mxc_nand_select_chip_v2(struct nand_chip *nand_chip, int chip) +{ + struct mxc_nand_host *host = nand_get_controller_data(nand_chip); + + if (chip == -1) { + /* Disable the NFC clock */ + if (host->clk_act) { + clk_disable_unprepare(host->clk); + host->clk_act = 0; + } + return; + } + + if (!host->clk_act) { + /* Enable the NFC clock */ + clk_prepare_enable(host->clk); + host->clk_act = 1; + } + + host->active_cs = chip; + writew(host->active_cs << 4, NFC_V1_V2_BUF_ADDR); +} + +#define MXC_V1_ECCBYTES 5 + +static int mxc_v1_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + + if (section >= nand_chip->ecc.steps) + return -ERANGE; + + oobregion->offset = (section * 16) + 6; + oobregion->length = MXC_V1_ECCBYTES; + + return 0; +} + +static int mxc_v1_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + + if (section > nand_chip->ecc.steps) + return -ERANGE; + + if (!section) { + if (mtd->writesize <= 512) { + oobregion->offset = 0; + oobregion->length = 5; + } else { + oobregion->offset = 2; + oobregion->length = 4; + } + } else { + oobregion->offset = ((section - 1) * 16) + MXC_V1_ECCBYTES + 6; + if (section < nand_chip->ecc.steps) + oobregion->length = (section * 16) + 6 - + oobregion->offset; + else + oobregion->length = mtd->oobsize - oobregion->offset; + } + + return 0; +} + +static const struct mtd_ooblayout_ops mxc_v1_ooblayout_ops = { + .ecc = mxc_v1_ooblayout_ecc, + .free = mxc_v1_ooblayout_free, +}; + +static int mxc_v2_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + int stepsize = nand_chip->ecc.bytes == 9 ? 16 : 26; + + if (section >= nand_chip->ecc.steps) + return -ERANGE; + + oobregion->offset = (section * stepsize) + 7; + oobregion->length = nand_chip->ecc.bytes; + + return 0; +} + +static int mxc_v2_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + int stepsize = nand_chip->ecc.bytes == 9 ? 16 : 26; + + if (section >= nand_chip->ecc.steps) + return -ERANGE; + + if (!section) { + if (mtd->writesize <= 512) { + oobregion->offset = 0; + oobregion->length = 5; + } else { + oobregion->offset = 2; + oobregion->length = 4; + } + } else { + oobregion->offset = section * stepsize; + oobregion->length = 7; + } + + return 0; +} + +static const struct mtd_ooblayout_ops mxc_v2_ooblayout_ops = { + .ecc = mxc_v2_ooblayout_ecc, + .free = mxc_v2_ooblayout_free, +}; + +/* + * v2 and v3 type controllers can do 4bit or 8bit ecc depending + * on how much oob the nand chip has. For 8bit ecc we need at least + * 26 bytes of oob data per 512 byte block. + */ +static int get_eccsize(struct mtd_info *mtd) +{ + int oobbytes_per_512 = 0; + + oobbytes_per_512 = mtd->oobsize * 512 / mtd->writesize; + + if (oobbytes_per_512 < 26) + return 4; + else + return 8; +} + +static void preset_v1(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + struct mxc_nand_host *host = nand_get_controller_data(nand_chip); + uint16_t config1 = 0; + + if (nand_chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST && + mtd->writesize) + config1 |= NFC_V1_V2_CONFIG1_ECC_EN; + + if (!host->devtype_data->irqpending_quirk) + config1 |= NFC_V1_V2_CONFIG1_INT_MSK; + + host->eccsize = 1; + + writew(config1, NFC_V1_V2_CONFIG1); + /* preset operation */ + + /* Unlock the internal RAM Buffer */ + writew(0x2, NFC_V1_V2_CONFIG); + + /* Blocks to be unlocked */ + writew(0x0, NFC_V1_UNLOCKSTART_BLKADDR); + writew(0xffff, NFC_V1_UNLOCKEND_BLKADDR); + + /* Unlock Block Command for given address range */ + writew(0x4, NFC_V1_V2_WRPROT); +} + +static int mxc_nand_v2_setup_interface(struct nand_chip *chip, int csline, + const struct nand_interface_config *conf) +{ + struct mxc_nand_host *host = nand_get_controller_data(chip); + int tRC_min_ns, tRC_ps, ret; + unsigned long rate, rate_round; + const struct nand_sdr_timings *timings; + u16 config1; + + timings = nand_get_sdr_timings(conf); + if (IS_ERR(timings)) + return -ENOTSUPP; + + config1 = readw(NFC_V1_V2_CONFIG1); + + tRC_min_ns = timings->tRC_min / 1000; + rate = 1000000000 / tRC_min_ns; + + /* + * For tRC < 30ns we have to use EDO mode. In this case the controller + * does one access per clock cycle. Otherwise the controller does one + * access in two clock cycles, thus we have to double the rate to the + * controller. + */ + if (tRC_min_ns < 30) { + rate_round = clk_round_rate(host->clk, rate); + config1 |= NFC_V2_CONFIG1_ONE_CYCLE; + tRC_ps = 1000000000 / (rate_round / 1000); + } else { + rate *= 2; + rate_round = clk_round_rate(host->clk, rate); + config1 &= ~NFC_V2_CONFIG1_ONE_CYCLE; + tRC_ps = 1000000000 / (rate_round / 1000 / 2); + } + + /* + * The timing values compared against are from the i.MX25 Automotive + * datasheet, Table 50. NFC Timing Parameters + */ + if (timings->tCLS_min > tRC_ps - 1000 || + timings->tCLH_min > tRC_ps - 2000 || + timings->tCS_min > tRC_ps - 1000 || + timings->tCH_min > tRC_ps - 2000 || + timings->tWP_min > tRC_ps - 1500 || + timings->tALS_min > tRC_ps || + timings->tALH_min > tRC_ps - 3000 || + timings->tDS_min > tRC_ps || + timings->tDH_min > tRC_ps - 5000 || + timings->tWC_min > 2 * tRC_ps || + timings->tWH_min > tRC_ps - 2500 || + timings->tRR_min > 6 * tRC_ps || + timings->tRP_min > 3 * tRC_ps / 2 || + timings->tRC_min > 2 * tRC_ps || + timings->tREH_min > (tRC_ps / 2) - 2500) { + dev_dbg(host->dev, "Timing out of bounds\n"); + return -EINVAL; + } + + if (csline == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + ret = clk_set_rate(host->clk, rate); + if (ret) + return ret; + + writew(config1, NFC_V1_V2_CONFIG1); + + dev_dbg(host->dev, "Setting rate to %ldHz, %s mode\n", rate_round, + config1 & NFC_V2_CONFIG1_ONE_CYCLE ? "One cycle (EDO)" : + "normal"); + + return 0; +} + +static void preset_v2(struct mtd_info *mtd) +{ + struct nand_chip *nand_chip = mtd_to_nand(mtd); + struct mxc_nand_host *host = nand_get_controller_data(nand_chip); + uint16_t config1 = 0; + + config1 |= NFC_V2_CONFIG1_FP_INT; + + if (!host->devtype_data->irqpending_quirk) + config1 |= NFC_V1_V2_CONFIG1_INT_MSK; + + if (mtd->writesize) { + uint16_t pages_per_block = mtd->erasesize / mtd->writesize; + + if (nand_chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) + config1 |= NFC_V1_V2_CONFIG1_ECC_EN; + + host->eccsize = get_eccsize(mtd); + if (host->eccsize == 4) + config1 |= NFC_V2_CONFIG1_ECC_MODE_4; + + config1 |= NFC_V2_CONFIG1_PPB(ffs(pages_per_block) - 6); + } else { + host->eccsize = 1; + } + + writew(config1, NFC_V1_V2_CONFIG1); + /* preset operation */ + + /* spare area size in 16-bit half-words */ + writew(mtd->oobsize / 2, NFC_V21_RSLTSPARE_AREA); + + /* Unlock the internal RAM Buffer */ + writew(0x2, NFC_V1_V2_CONFIG); + + /* Blocks to be unlocked */ + writew(0x0, NFC_V21_UNLOCKSTART_BLKADDR0); + writew(0x0, NFC_V21_UNLOCKSTART_BLKADDR1); + writew(0x0, NFC_V21_UNLOCKSTART_BLKADDR2); + writew(0x0, NFC_V21_UNLOCKSTART_BLKADDR3); + writew(0xffff, NFC_V21_UNLOCKEND_BLKADDR0); + writew(0xffff, NFC_V21_UNLOCKEND_BLKADDR1); + writew(0xffff, NFC_V21_UNLOCKEND_BLKADDR2); + writew(0xffff, NFC_V21_UNLOCKEND_BLKADDR3); + + /* Unlock Block Command for given address range */ + writew(0x4, NFC_V1_V2_WRPROT); +} + +static void preset_v3(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mxc_nand_host *host = nand_get_controller_data(chip); + uint32_t config2, config3; + int i, addr_phases; + + writel(NFC_V3_CONFIG1_RBA(0), NFC_V3_CONFIG1); + writel(NFC_V3_IPC_CREQ, NFC_V3_IPC); + + /* Unlock the internal RAM Buffer */ + writel(NFC_V3_WRPROT_BLS_UNLOCK | NFC_V3_WRPROT_UNLOCK, + NFC_V3_WRPROT); + + /* Blocks to be unlocked */ + for (i = 0; i < NAND_MAX_CHIPS; i++) + writel(0xffff << 16, NFC_V3_WRPROT_UNLOCK_BLK_ADD0 + (i << 2)); + + writel(0, NFC_V3_IPC); + + config2 = NFC_V3_CONFIG2_ONE_CYCLE | + NFC_V3_CONFIG2_2CMD_PHASES | + NFC_V3_CONFIG2_SPAS(mtd->oobsize >> 1) | + NFC_V3_CONFIG2_ST_CMD(0x70) | + NFC_V3_CONFIG2_INT_MSK | + NFC_V3_CONFIG2_NUM_ADDR_PHASE0; + + addr_phases = fls(chip->pagemask) >> 3; + + if (mtd->writesize == 2048) { + config2 |= NFC_V3_CONFIG2_PS_2048; + config2 |= NFC_V3_CONFIG2_NUM_ADDR_PHASE1(addr_phases); + } else if (mtd->writesize == 4096) { + config2 |= NFC_V3_CONFIG2_PS_4096; + config2 |= NFC_V3_CONFIG2_NUM_ADDR_PHASE1(addr_phases); + } else { + config2 |= NFC_V3_CONFIG2_PS_512; + config2 |= NFC_V3_CONFIG2_NUM_ADDR_PHASE1(addr_phases - 1); + } + + if (mtd->writesize) { + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) + config2 |= NFC_V3_CONFIG2_ECC_EN; + + config2 |= NFC_V3_CONFIG2_PPB( + ffs(mtd->erasesize / mtd->writesize) - 6, + host->devtype_data->ppb_shift); + host->eccsize = get_eccsize(mtd); + if (host->eccsize == 8) + config2 |= NFC_V3_CONFIG2_ECC_MODE_8; + } + + writel(config2, NFC_V3_CONFIG2); + + config3 = NFC_V3_CONFIG3_NUM_OF_DEVICES(0) | + NFC_V3_CONFIG3_NO_SDMA | + NFC_V3_CONFIG3_RBB_MODE | + NFC_V3_CONFIG3_SBB(6) | /* Reset default */ + NFC_V3_CONFIG3_ADD_OP(0); + + if (!(chip->options & NAND_BUSWIDTH_16)) + config3 |= NFC_V3_CONFIG3_FW8; + + writel(config3, NFC_V3_CONFIG3); + + writel(0, NFC_V3_DELAY_LINE); +} + +/* + * The generic flash bbt descriptors overlap with our ecc + * hardware, so define some i.MX specific ones. + */ +static uint8_t bbt_pattern[] = { 'B', 'b', 't', '0' }; +static uint8_t mirror_pattern[] = { '1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = bbt_pattern, +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 0, + .len = 4, + .veroffs = 4, + .maxblocks = 4, + .pattern = mirror_pattern, +}; + +/* v1 + irqpending_quirk: i.MX21 */ +static const struct mxc_nand_devtype_data imx21_nand_devtype_data = { + .preset = preset_v1, + .read_page = mxc_nand_read_page_v1, + .send_cmd = send_cmd_v1_v2, + .send_addr = send_addr_v1_v2, + .send_page = send_page_v1, + .send_read_id = send_read_id_v1_v2, + .get_dev_status = get_dev_status_v1_v2, + .check_int = check_int_v1_v2, + .irq_control = irq_control_v1_v2, + .get_ecc_status = get_ecc_status_v1, + .ooblayout = &mxc_v1_ooblayout_ops, + .select_chip = mxc_nand_select_chip_v1_v3, + .enable_hwecc = mxc_nand_enable_hwecc_v1_v2, + .irqpending_quirk = 1, + .needs_ip = 0, + .regs_offset = 0xe00, + .spare0_offset = 0x800, + .spare_len = 16, + .eccbytes = 3, + .eccsize = 1, +}; + +/* v1 + !irqpending_quirk: i.MX27, i.MX31 */ +static const struct mxc_nand_devtype_data imx27_nand_devtype_data = { + .preset = preset_v1, + .read_page = mxc_nand_read_page_v1, + .send_cmd = send_cmd_v1_v2, + .send_addr = send_addr_v1_v2, + .send_page = send_page_v1, + .send_read_id = send_read_id_v1_v2, + .get_dev_status = get_dev_status_v1_v2, + .check_int = check_int_v1_v2, + .irq_control = irq_control_v1_v2, + .get_ecc_status = get_ecc_status_v1, + .ooblayout = &mxc_v1_ooblayout_ops, + .select_chip = mxc_nand_select_chip_v1_v3, + .enable_hwecc = mxc_nand_enable_hwecc_v1_v2, + .irqpending_quirk = 0, + .needs_ip = 0, + .regs_offset = 0xe00, + .spare0_offset = 0x800, + .axi_offset = 0, + .spare_len = 16, + .eccbytes = 3, + .eccsize = 1, +}; + +/* v21: i.MX25, i.MX35 */ +static const struct mxc_nand_devtype_data imx25_nand_devtype_data = { + .preset = preset_v2, + .read_page = mxc_nand_read_page_v2_v3, + .send_cmd = send_cmd_v1_v2, + .send_addr = send_addr_v1_v2, + .send_page = send_page_v2, + .send_read_id = send_read_id_v1_v2, + .get_dev_status = get_dev_status_v1_v2, + .check_int = check_int_v1_v2, + .irq_control = irq_control_v1_v2, + .get_ecc_status = get_ecc_status_v2, + .ooblayout = &mxc_v2_ooblayout_ops, + .select_chip = mxc_nand_select_chip_v2, + .setup_interface = mxc_nand_v2_setup_interface, + .enable_hwecc = mxc_nand_enable_hwecc_v1_v2, + .irqpending_quirk = 0, + .needs_ip = 0, + .regs_offset = 0x1e00, + .spare0_offset = 0x1000, + .axi_offset = 0, + .spare_len = 64, + .eccbytes = 9, + .eccsize = 0, +}; + +/* v3.2a: i.MX51 */ +static const struct mxc_nand_devtype_data imx51_nand_devtype_data = { + .preset = preset_v3, + .read_page = mxc_nand_read_page_v2_v3, + .send_cmd = send_cmd_v3, + .send_addr = send_addr_v3, + .send_page = send_page_v3, + .send_read_id = send_read_id_v3, + .get_dev_status = get_dev_status_v3, + .check_int = check_int_v3, + .irq_control = irq_control_v3, + .get_ecc_status = get_ecc_status_v3, + .ooblayout = &mxc_v2_ooblayout_ops, + .select_chip = mxc_nand_select_chip_v1_v3, + .enable_hwecc = mxc_nand_enable_hwecc_v3, + .irqpending_quirk = 0, + .needs_ip = 1, + .regs_offset = 0, + .spare0_offset = 0x1000, + .axi_offset = 0x1e00, + .spare_len = 64, + .eccbytes = 0, + .eccsize = 0, + .ppb_shift = 7, +}; + +/* v3.2b: i.MX53 */ +static const struct mxc_nand_devtype_data imx53_nand_devtype_data = { + .preset = preset_v3, + .read_page = mxc_nand_read_page_v2_v3, + .send_cmd = send_cmd_v3, + .send_addr = send_addr_v3, + .send_page = send_page_v3, + .send_read_id = send_read_id_v3, + .get_dev_status = get_dev_status_v3, + .check_int = check_int_v3, + .irq_control = irq_control_v3, + .get_ecc_status = get_ecc_status_v3, + .ooblayout = &mxc_v2_ooblayout_ops, + .select_chip = mxc_nand_select_chip_v1_v3, + .enable_hwecc = mxc_nand_enable_hwecc_v3, + .irqpending_quirk = 0, + .needs_ip = 1, + .regs_offset = 0, + .spare0_offset = 0x1000, + .axi_offset = 0x1e00, + .spare_len = 64, + .eccbytes = 0, + .eccsize = 0, + .ppb_shift = 8, +}; + +static inline int is_imx21_nfc(struct mxc_nand_host *host) +{ + return host->devtype_data == &imx21_nand_devtype_data; +} + +static inline int is_imx27_nfc(struct mxc_nand_host *host) +{ + return host->devtype_data == &imx27_nand_devtype_data; +} + +static inline int is_imx25_nfc(struct mxc_nand_host *host) +{ + return host->devtype_data == &imx25_nand_devtype_data; +} + +static const struct of_device_id mxcnd_dt_ids[] = { + { .compatible = "fsl,imx21-nand", .data = &imx21_nand_devtype_data, }, + { .compatible = "fsl,imx27-nand", .data = &imx27_nand_devtype_data, }, + { .compatible = "fsl,imx25-nand", .data = &imx25_nand_devtype_data, }, + { .compatible = "fsl,imx51-nand", .data = &imx51_nand_devtype_data, }, + { .compatible = "fsl,imx53-nand", .data = &imx53_nand_devtype_data, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mxcnd_dt_ids); + +static int mxcnd_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mxc_nand_host *host = nand_get_controller_data(chip); + struct device *dev = mtd->dev.parent; + + chip->ecc.bytes = host->devtype_data->eccbytes; + host->eccsize = host->devtype_data->eccsize; + chip->ecc.size = 512; + + switch (chip->ecc.engine_type) { + case NAND_ECC_ENGINE_TYPE_ON_HOST: + mtd_set_ooblayout(mtd, host->devtype_data->ooblayout); + chip->ecc.read_page = mxc_nand_read_page; + chip->ecc.read_page_raw = mxc_nand_read_page_raw; + chip->ecc.read_oob = mxc_nand_read_oob; + chip->ecc.write_page = mxc_nand_write_page_ecc; + chip->ecc.write_page_raw = mxc_nand_write_page_raw; + chip->ecc.write_oob = mxc_nand_write_oob; + break; + + case NAND_ECC_ENGINE_TYPE_SOFT: + chip->ecc.write_page_raw = nand_monolithic_write_page_raw; + chip->ecc.read_page_raw = nand_monolithic_read_page_raw; + break; + + default: + return -EINVAL; + } + + if (chip->bbt_options & NAND_BBT_USE_FLASH) { + chip->bbt_td = &bbt_main_descr; + chip->bbt_md = &bbt_mirror_descr; + } + + /* Allocate the right size buffer now */ + devm_kfree(dev, (void *)host->data_buf); + host->data_buf = devm_kzalloc(dev, mtd->writesize + mtd->oobsize, + GFP_KERNEL); + if (!host->data_buf) + return -ENOMEM; + + /* Call preset again, with correct writesize chip time */ + host->devtype_data->preset(mtd); + + if (!chip->ecc.bytes) { + if (host->eccsize == 8) + chip->ecc.bytes = 18; + else if (host->eccsize == 4) + chip->ecc.bytes = 9; + } + + /* + * Experimentation shows that i.MX NFC can only handle up to 218 oob + * bytes. Limit used_oobsize to 218 so as to not confuse copy_spare() + * into copying invalid data to/from the spare IO buffer, as this + * might cause ECC data corruption when doing sub-page write to a + * partially written page. + */ + host->used_oobsize = min(mtd->oobsize, 218U); + + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) { + if (is_imx21_nfc(host) || is_imx27_nfc(host)) + chip->ecc.strength = 1; + else + chip->ecc.strength = (host->eccsize == 4) ? 4 : 8; + } + + return 0; +} + +static int mxcnd_setup_interface(struct nand_chip *chip, int chipnr, + const struct nand_interface_config *conf) +{ + struct mxc_nand_host *host = nand_get_controller_data(chip); + + return host->devtype_data->setup_interface(chip, chipnr, conf); +} + +static void memff16_toio(void *buf, int n) +{ + __iomem u16 *t = buf; + int i; + + for (i = 0; i < (n >> 1); i++) + __raw_writew(0xffff, t++); +} + +static void copy_page_to_sram(struct mtd_info *mtd, const void *buf, int buf_len) +{ + struct nand_chip *this = mtd_to_nand(mtd); + struct mxc_nand_host *host = nand_get_controller_data(this); + unsigned int no_subpages = mtd->writesize / 512; + int oob_per_subpage, i; + + oob_per_subpage = (mtd->oobsize / no_subpages) & ~1; + + /* + * During a page write the i.MX NAND controller will read 512b from + * main_area0 SRAM, then oob_per_subpage bytes from spare0 SRAM, then + * 512b from main_area1 SRAM and so on until the full page is written. + * For software ECC we want to have a 1:1 mapping between the raw page + * data on the NAND chip and the view of the NAND core. This is + * necessary to make the NAND_CMD_RNDOUT read the data it expects. + * To accomplish this we have to write the data in the order the controller + * reads it. This is reversed in copy_page_from_sram() below. + * + * buf_len can either be the full page including the OOB or user data only. + * When it's user data only make sure that we fill up the rest of the + * SRAM with 0xff. + */ + for (i = 0; i < no_subpages; i++) { + int now = min(buf_len, 512); + + if (now) + memcpy16_toio(host->main_area0 + i * 512, buf, now); + + if (now < 512) + memff16_toio(host->main_area0 + i * 512 + now, 512 - now); + + buf += 512; + buf_len -= now; + + now = min(buf_len, oob_per_subpage); + if (now) + memcpy16_toio(host->spare0 + i * host->devtype_data->spare_len, + buf, now); + + if (now < oob_per_subpage) + memff16_toio(host->spare0 + i * host->devtype_data->spare_len + now, + oob_per_subpage - now); + + buf += oob_per_subpage; + buf_len -= now; + } +} + +static void copy_page_from_sram(struct mtd_info *mtd) +{ + struct nand_chip *this = mtd_to_nand(mtd); + struct mxc_nand_host *host = nand_get_controller_data(this); + void *buf = host->data_buf; + unsigned int no_subpages = mtd->writesize / 512; + int oob_per_subpage, i; + + /* mtd->writesize is not set during ident scanning */ + if (!no_subpages) + no_subpages = 1; + + oob_per_subpage = (mtd->oobsize / no_subpages) & ~1; + + for (i = 0; i < no_subpages; i++) { + memcpy16_fromio(buf, host->main_area0 + i * 512, 512); + buf += 512; + + memcpy16_fromio(buf, host->spare0 + i * host->devtype_data->spare_len, + oob_per_subpage); + buf += oob_per_subpage; + } +} + +static int mxcnd_do_exec_op(struct nand_chip *chip, + const struct nand_subop *op) +{ + struct mxc_nand_host *host = nand_get_controller_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int i, j, buf_len; + void *buf_read = NULL; + const void *buf_write = NULL; + const struct nand_op_instr *instr; + bool readid = false; + bool statusreq = false; + + for (i = 0; i < op->ninstrs; i++) { + instr = &op->instrs[i]; + + switch (instr->type) { + case NAND_OP_WAITRDY_INSTR: + /* NFC handles R/B internally, nothing to do here */ + break; + case NAND_OP_CMD_INSTR: + host->devtype_data->send_cmd(host, instr->ctx.cmd.opcode, true); + + if (instr->ctx.cmd.opcode == NAND_CMD_READID) + readid = true; + if (instr->ctx.cmd.opcode == NAND_CMD_STATUS) + statusreq = true; + + break; + case NAND_OP_ADDR_INSTR: + for (j = 0; j < instr->ctx.addr.naddrs; j++) { + bool islast = j == instr->ctx.addr.naddrs - 1; + host->devtype_data->send_addr(host, instr->ctx.addr.addrs[j], islast); + } + break; + case NAND_OP_DATA_OUT_INSTR: + buf_write = instr->ctx.data.buf.out; + buf_len = instr->ctx.data.len; + + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) + memcpy32_toio(host->main_area0, buf_write, buf_len); + else + copy_page_to_sram(mtd, buf_write, buf_len); + + host->devtype_data->send_page(mtd, NFC_INPUT); + + break; + case NAND_OP_DATA_IN_INSTR: + + buf_read = instr->ctx.data.buf.in; + buf_len = instr->ctx.data.len; + + if (readid) { + host->devtype_data->send_read_id(host); + readid = false; + + memcpy32_fromio(host->data_buf, host->main_area0, buf_len * 2); + + if (chip->options & NAND_BUSWIDTH_16) { + u8 *bufr = buf_read; + u16 *bufw = host->data_buf; + for (j = 0; j < buf_len; j++) + bufr[j] = bufw[j]; + } else { + memcpy(buf_read, host->data_buf, buf_len); + } + break; + } + + if (statusreq) { + *(u8*)buf_read = host->devtype_data->get_dev_status(host); + statusreq = false; + break; + } + + host->devtype_data->read_page(chip); + + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) { + if (IS_ALIGNED(buf_len, 4)) { + memcpy32_fromio(buf_read, host->main_area0, buf_len); + } else { + memcpy32_fromio(host->data_buf, host->main_area0, mtd->writesize); + memcpy(buf_read, host->data_buf, buf_len); + } + } else { + copy_page_from_sram(mtd); + memcpy(buf_read, host->data_buf, buf_len); + } + + break; + } + } + + return 0; +} + +#define MAX_DATA_SIZE (4096 + 512) + +static const struct nand_op_parser mxcnd_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN(mxcnd_do_exec_op, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 7), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, MAX_DATA_SIZE)), + NAND_OP_PARSER_PATTERN(mxcnd_do_exec_op, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, 7), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, MAX_DATA_SIZE), + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), + NAND_OP_PARSER_PATTERN(mxcnd_do_exec_op, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, 7), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, MAX_DATA_SIZE), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), + ); + +static int mxcnd_exec_op(struct nand_chip *chip, + const struct nand_operation *op, bool check_only) +{ + return nand_op_parser_exec_op(chip, &mxcnd_op_parser, + op, check_only); +} + +static const struct nand_controller_ops mxcnd_controller_ops = { + .attach_chip = mxcnd_attach_chip, + .setup_interface = mxcnd_setup_interface, + .exec_op = mxcnd_exec_op, +}; + +static int mxcnd_probe(struct platform_device *pdev) +{ + struct nand_chip *this; + struct mtd_info *mtd; + struct mxc_nand_host *host; + int err = 0; + + /* Allocate memory for MTD device structure and private data */ + host = devm_kzalloc(&pdev->dev, sizeof(struct mxc_nand_host), + GFP_KERNEL); + if (!host) + return -ENOMEM; + + /* allocate a temporary buffer for the nand_scan_ident() */ + host->data_buf = devm_kzalloc(&pdev->dev, PAGE_SIZE, GFP_KERNEL); + if (!host->data_buf) + return -ENOMEM; + + host->dev = &pdev->dev; + /* structures must be linked */ + this = &host->nand; + mtd = nand_to_mtd(this); + mtd->dev.parent = &pdev->dev; + mtd->name = DRIVER_NAME; + + /* 50 us command delay time */ + this->legacy.chip_delay = 5; + + nand_set_controller_data(this, host); + + 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)) + return PTR_ERR(host->clk); + + host->devtype_data = device_get_match_data(&pdev->dev); + + if (!host->devtype_data->setup_interface) + this->options |= NAND_KEEP_TIMINGS; + + if (host->devtype_data->needs_ip) { + host->regs_ip = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(host->regs_ip)) + return PTR_ERR(host->regs_ip); + + host->base = devm_platform_ioremap_resource(pdev, 1); + } else { + host->base = devm_platform_ioremap_resource(pdev, 0); + } + + if (IS_ERR(host->base)) + return PTR_ERR(host->base); + + host->main_area0 = host->base; + + if (host->devtype_data->regs_offset) + host->regs = host->base + host->devtype_data->regs_offset; + host->spare0 = host->base + host->devtype_data->spare0_offset; + if (host->devtype_data->axi_offset) + host->regs_axi = host->base + host->devtype_data->axi_offset; + + this->legacy.select_chip = host->devtype_data->select_chip; + + init_completion(&host->op_completion); + + host->irq = platform_get_irq(pdev, 0); + if (host->irq < 0) + return host->irq; + + /* + * Use host->devtype_data->irq_control() here instead of irq_control() + * because we must not disable_irq_nosync without having requested the + * irq. + */ + host->devtype_data->irq_control(host, 0); + + err = devm_request_irq(&pdev->dev, host->irq, mxc_nfc_irq, + 0, DRIVER_NAME, host); + if (err) + return err; + + err = clk_prepare_enable(host->clk); + if (err) + return err; + host->clk_act = 1; + + /* + * Now that we "own" the interrupt make sure the interrupt mask bit is + * cleared on i.MX21. Otherwise we can't read the interrupt status bit + * on this machine. + */ + if (host->devtype_data->irqpending_quirk) { + disable_irq_nosync(host->irq); + host->devtype_data->irq_control(host, 1); + } + + /* Scan the NAND device */ + this->legacy.dummy_controller.ops = &mxcnd_controller_ops; + err = nand_scan(this, is_imx25_nfc(host) ? 4 : 1); + if (err) + goto escan; + + /* Register the partitions */ + err = mtd_device_parse_register(mtd, part_probes, NULL, NULL, 0); + if (err) + goto cleanup_nand; + + platform_set_drvdata(pdev, host); + + return 0; + +cleanup_nand: + nand_cleanup(this); +escan: + if (host->clk_act) + clk_disable_unprepare(host->clk); + + return err; +} + +static void mxcnd_remove(struct platform_device *pdev) +{ + struct mxc_nand_host *host = platform_get_drvdata(pdev); + struct nand_chip *chip = &host->nand; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + if (host->clk_act) + clk_disable_unprepare(host->clk); +} + +static struct platform_driver mxcnd_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = mxcnd_dt_ids, + }, + .probe = mxcnd_probe, + .remove = mxcnd_remove, +}; +module_platform_driver(mxcnd_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MXC NAND MTD driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/raw/mxic_nand.c b/drivers/mtd/nand/raw/mxic_nand.c new file mode 100644 index 000000000..92de26697 --- /dev/null +++ b/drivers/mtd/nand/raw/mxic_nand.c @@ -0,0 +1,587 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Macronix International Co., Ltd. + * + * Author: + * Mason Yang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internals.h" + +#define HC_CFG 0x0 +#define HC_CFG_IF_CFG(x) ((x) << 27) +#define HC_CFG_DUAL_SLAVE BIT(31) +#define HC_CFG_INDIVIDUAL BIT(30) +#define HC_CFG_NIO(x) (((x) / 4) << 27) +#define HC_CFG_TYPE(s, t) ((t) << (23 + ((s) * 2))) +#define HC_CFG_TYPE_SPI_NOR 0 +#define HC_CFG_TYPE_SPI_NAND 1 +#define HC_CFG_TYPE_SPI_RAM 2 +#define HC_CFG_TYPE_RAW_NAND 3 +#define HC_CFG_SLV_ACT(x) ((x) << 21) +#define HC_CFG_CLK_PH_EN BIT(20) +#define HC_CFG_CLK_POL_INV BIT(19) +#define HC_CFG_BIG_ENDIAN BIT(18) +#define HC_CFG_DATA_PASS BIT(17) +#define HC_CFG_IDLE_SIO_LVL(x) ((x) << 16) +#define HC_CFG_MAN_START_EN BIT(3) +#define HC_CFG_MAN_START BIT(2) +#define HC_CFG_MAN_CS_EN BIT(1) +#define HC_CFG_MAN_CS_ASSERT BIT(0) + +#define INT_STS 0x4 +#define INT_STS_EN 0x8 +#define INT_SIG_EN 0xc +#define INT_STS_ALL GENMASK(31, 0) +#define INT_RDY_PIN BIT(26) +#define INT_RDY_SR BIT(25) +#define INT_LNR_SUSP BIT(24) +#define INT_ECC_ERR BIT(17) +#define INT_CRC_ERR BIT(16) +#define INT_LWR_DIS BIT(12) +#define INT_LRD_DIS BIT(11) +#define INT_SDMA_INT BIT(10) +#define INT_DMA_FINISH BIT(9) +#define INT_RX_NOT_FULL BIT(3) +#define INT_RX_NOT_EMPTY BIT(2) +#define INT_TX_NOT_FULL BIT(1) +#define INT_TX_EMPTY BIT(0) + +#define HC_EN 0x10 +#define HC_EN_BIT BIT(0) + +#define TXD(x) (0x14 + ((x) * 4)) +#define RXD 0x24 + +#define SS_CTRL(s) (0x30 + ((s) * 4)) +#define LRD_CFG 0x44 +#define LWR_CFG 0x80 +#define RWW_CFG 0x70 +#define OP_READ BIT(23) +#define OP_DUMMY_CYC(x) ((x) << 17) +#define OP_ADDR_BYTES(x) ((x) << 14) +#define OP_CMD_BYTES(x) (((x) - 1) << 13) +#define OP_OCTA_CRC_EN BIT(12) +#define OP_DQS_EN BIT(11) +#define OP_ENHC_EN BIT(10) +#define OP_PREAMBLE_EN BIT(9) +#define OP_DATA_DDR BIT(8) +#define OP_DATA_BUSW(x) ((x) << 6) +#define OP_ADDR_DDR BIT(5) +#define OP_ADDR_BUSW(x) ((x) << 3) +#define OP_CMD_DDR BIT(2) +#define OP_CMD_BUSW(x) (x) +#define OP_BUSW_1 0 +#define OP_BUSW_2 1 +#define OP_BUSW_4 2 +#define OP_BUSW_8 3 + +#define OCTA_CRC 0x38 +#define OCTA_CRC_IN_EN(s) BIT(3 + ((s) * 16)) +#define OCTA_CRC_CHUNK(s, x) ((fls((x) / 32)) << (1 + ((s) * 16))) +#define OCTA_CRC_OUT_EN(s) BIT(0 + ((s) * 16)) + +#define ONFI_DIN_CNT(s) (0x3c + (s)) + +#define LRD_CTRL 0x48 +#define RWW_CTRL 0x74 +#define LWR_CTRL 0x84 +#define LMODE_EN BIT(31) +#define LMODE_SLV_ACT(x) ((x) << 21) +#define LMODE_CMD1(x) ((x) << 8) +#define LMODE_CMD0(x) (x) + +#define LRD_ADDR 0x4c +#define LWR_ADDR 0x88 +#define LRD_RANGE 0x50 +#define LWR_RANGE 0x8c + +#define AXI_SLV_ADDR 0x54 + +#define DMAC_RD_CFG 0x58 +#define DMAC_WR_CFG 0x94 +#define DMAC_CFG_PERIPH_EN BIT(31) +#define DMAC_CFG_ALLFLUSH_EN BIT(30) +#define DMAC_CFG_LASTFLUSH_EN BIT(29) +#define DMAC_CFG_QE(x) (((x) + 1) << 16) +#define DMAC_CFG_BURST_LEN(x) (((x) + 1) << 12) +#define DMAC_CFG_BURST_SZ(x) ((x) << 8) +#define DMAC_CFG_DIR_READ BIT(1) +#define DMAC_CFG_START BIT(0) + +#define DMAC_RD_CNT 0x5c +#define DMAC_WR_CNT 0x98 + +#define SDMA_ADDR 0x60 + +#define DMAM_CFG 0x64 +#define DMAM_CFG_START BIT(31) +#define DMAM_CFG_CONT BIT(30) +#define DMAM_CFG_SDMA_GAP(x) (fls((x) / 8192) << 2) +#define DMAM_CFG_DIR_READ BIT(1) +#define DMAM_CFG_EN BIT(0) + +#define DMAM_CNT 0x68 + +#define LNR_TIMER_TH 0x6c + +#define RDM_CFG0 0x78 +#define RDM_CFG0_POLY(x) (x) + +#define RDM_CFG1 0x7c +#define RDM_CFG1_RDM_EN BIT(31) +#define RDM_CFG1_SEED(x) (x) + +#define LWR_SUSP_CTRL 0x90 +#define LWR_SUSP_CTRL_EN BIT(31) + +#define DMAS_CTRL 0x9c +#define DMAS_CTRL_EN BIT(31) +#define DMAS_CTRL_DIR_READ BIT(30) + +#define DATA_STROB 0xa0 +#define DATA_STROB_EDO_EN BIT(2) +#define DATA_STROB_INV_POL BIT(1) +#define DATA_STROB_DELAY_2CYC BIT(0) + +#define IDLY_CODE(x) (0xa4 + ((x) * 4)) +#define IDLY_CODE_VAL(x, v) ((v) << (((x) % 4) * 8)) + +#define GPIO 0xc4 +#define GPIO_PT(x) BIT(3 + ((x) * 16)) +#define GPIO_RESET(x) BIT(2 + ((x) * 16)) +#define GPIO_HOLDB(x) BIT(1 + ((x) * 16)) +#define GPIO_WPB(x) BIT((x) * 16) + +#define HC_VER 0xd0 + +#define HW_TEST(x) (0xe0 + ((x) * 4)) + +#define MXIC_NFC_MAX_CLK_HZ 50000000 +#define IRQ_TIMEOUT 1000 + +struct mxic_nand_ctlr { + struct clk *ps_clk; + struct clk *send_clk; + struct clk *send_dly_clk; + struct completion complete; + void __iomem *regs; + struct nand_controller controller; + struct device *dev; + struct nand_chip chip; +}; + +static int mxic_nfc_clk_enable(struct mxic_nand_ctlr *nfc) +{ + int ret; + + ret = clk_prepare_enable(nfc->ps_clk); + if (ret) + return ret; + + ret = clk_prepare_enable(nfc->send_clk); + if (ret) + goto err_ps_clk; + + ret = clk_prepare_enable(nfc->send_dly_clk); + if (ret) + goto err_send_dly_clk; + + return ret; + +err_send_dly_clk: + clk_disable_unprepare(nfc->send_clk); +err_ps_clk: + clk_disable_unprepare(nfc->ps_clk); + + return ret; +} + +static void mxic_nfc_clk_disable(struct mxic_nand_ctlr *nfc) +{ + clk_disable_unprepare(nfc->send_clk); + clk_disable_unprepare(nfc->send_dly_clk); + clk_disable_unprepare(nfc->ps_clk); +} + +static void mxic_nfc_set_input_delay(struct mxic_nand_ctlr *nfc, u8 idly_code) +{ + writel(IDLY_CODE_VAL(0, idly_code) | + IDLY_CODE_VAL(1, idly_code) | + IDLY_CODE_VAL(2, idly_code) | + IDLY_CODE_VAL(3, idly_code), + nfc->regs + IDLY_CODE(0)); + writel(IDLY_CODE_VAL(4, idly_code) | + IDLY_CODE_VAL(5, idly_code) | + IDLY_CODE_VAL(6, idly_code) | + IDLY_CODE_VAL(7, idly_code), + nfc->regs + IDLY_CODE(1)); +} + +static int mxic_nfc_clk_setup(struct mxic_nand_ctlr *nfc, unsigned long freq) +{ + int ret; + + ret = clk_set_rate(nfc->send_clk, freq); + if (ret) + return ret; + + ret = clk_set_rate(nfc->send_dly_clk, freq); + if (ret) + return ret; + + /* + * A constant delay range from 0x0 ~ 0x1F for input delay, + * the unit is 78 ps, the max input delay is 2.418 ns. + */ + mxic_nfc_set_input_delay(nfc, 0xf); + + /* + * Phase degree = 360 * freq * output-delay + * where output-delay is a constant value 1 ns in FPGA. + * + * Get Phase degree = 360 * freq * 1 ns + * = 360 * freq * 1 sec / 1000000000 + * = 9 * freq / 25000000 + */ + ret = clk_set_phase(nfc->send_dly_clk, 9 * freq / 25000000); + if (ret) + return ret; + + return 0; +} + +static int mxic_nfc_set_freq(struct mxic_nand_ctlr *nfc, unsigned long freq) +{ + int ret; + + if (freq > MXIC_NFC_MAX_CLK_HZ) + freq = MXIC_NFC_MAX_CLK_HZ; + + mxic_nfc_clk_disable(nfc); + ret = mxic_nfc_clk_setup(nfc, freq); + if (ret) + return ret; + + ret = mxic_nfc_clk_enable(nfc); + if (ret) + return ret; + + return 0; +} + +static irqreturn_t mxic_nfc_isr(int irq, void *dev_id) +{ + struct mxic_nand_ctlr *nfc = dev_id; + u32 sts; + + sts = readl(nfc->regs + INT_STS); + if (sts & INT_RDY_PIN) + complete(&nfc->complete); + else + return IRQ_NONE; + + return IRQ_HANDLED; +} + +static void mxic_nfc_hw_init(struct mxic_nand_ctlr *nfc) +{ + writel(HC_CFG_NIO(8) | HC_CFG_TYPE(1, HC_CFG_TYPE_RAW_NAND) | + HC_CFG_SLV_ACT(0) | HC_CFG_MAN_CS_EN | + HC_CFG_IDLE_SIO_LVL(1), nfc->regs + HC_CFG); + writel(INT_STS_ALL, nfc->regs + INT_STS_EN); + writel(INT_RDY_PIN, nfc->regs + INT_SIG_EN); + writel(0x0, nfc->regs + ONFI_DIN_CNT(0)); + writel(0, nfc->regs + LRD_CFG); + writel(0, nfc->regs + LRD_CTRL); + writel(0x0, nfc->regs + HC_EN); +} + +static void mxic_nfc_cs_enable(struct mxic_nand_ctlr *nfc) +{ + writel(readl(nfc->regs + HC_CFG) | HC_CFG_MAN_CS_EN, + nfc->regs + HC_CFG); + writel(HC_CFG_MAN_CS_ASSERT | readl(nfc->regs + HC_CFG), + nfc->regs + HC_CFG); +} + +static void mxic_nfc_cs_disable(struct mxic_nand_ctlr *nfc) +{ + writel(~HC_CFG_MAN_CS_ASSERT & readl(nfc->regs + HC_CFG), + nfc->regs + HC_CFG); +} + +static int mxic_nfc_wait_ready(struct nand_chip *chip) +{ + struct mxic_nand_ctlr *nfc = nand_get_controller_data(chip); + int ret; + + ret = wait_for_completion_timeout(&nfc->complete, + msecs_to_jiffies(IRQ_TIMEOUT)); + if (!ret) { + dev_err(nfc->dev, "nand device timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int mxic_nfc_data_xfer(struct mxic_nand_ctlr *nfc, const void *txbuf, + void *rxbuf, unsigned int len) +{ + unsigned int pos = 0; + + while (pos < len) { + unsigned int nbytes = len - pos; + u32 data = 0xffffffff; + u32 sts; + int ret; + + if (nbytes > 4) + nbytes = 4; + + if (txbuf) + memcpy(&data, txbuf + pos, nbytes); + + ret = readl_poll_timeout(nfc->regs + INT_STS, sts, + sts & INT_TX_EMPTY, 0, USEC_PER_SEC); + if (ret) + return ret; + + writel(data, nfc->regs + TXD(nbytes % 4)); + + ret = readl_poll_timeout(nfc->regs + INT_STS, sts, + sts & INT_TX_EMPTY, 0, USEC_PER_SEC); + if (ret) + return ret; + + ret = readl_poll_timeout(nfc->regs + INT_STS, sts, + sts & INT_RX_NOT_EMPTY, 0, + USEC_PER_SEC); + if (ret) + return ret; + + data = readl(nfc->regs + RXD); + if (rxbuf) { + data >>= (8 * (4 - nbytes)); + memcpy(rxbuf + pos, &data, nbytes); + } + if (readl(nfc->regs + INT_STS) & INT_RX_NOT_EMPTY) + dev_warn(nfc->dev, "RX FIFO not empty\n"); + + pos += nbytes; + } + + return 0; +} + +static int mxic_nfc_exec_op(struct nand_chip *chip, + const struct nand_operation *op, bool check_only) +{ + struct mxic_nand_ctlr *nfc = nand_get_controller_data(chip); + const struct nand_op_instr *instr = NULL; + int ret = 0; + unsigned int op_id; + + if (check_only) + return 0; + + mxic_nfc_cs_enable(nfc); + init_completion(&nfc->complete); + for (op_id = 0; op_id < op->ninstrs; op_id++) { + instr = &op->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + writel(0, nfc->regs + HC_EN); + writel(HC_EN_BIT, nfc->regs + HC_EN); + writel(OP_CMD_BUSW(OP_BUSW_8) | OP_DUMMY_CYC(0x3F) | + OP_CMD_BYTES(0), nfc->regs + SS_CTRL(0)); + + ret = mxic_nfc_data_xfer(nfc, + &instr->ctx.cmd.opcode, + NULL, 1); + break; + + case NAND_OP_ADDR_INSTR: + writel(OP_ADDR_BUSW(OP_BUSW_8) | OP_DUMMY_CYC(0x3F) | + OP_ADDR_BYTES(instr->ctx.addr.naddrs), + nfc->regs + SS_CTRL(0)); + ret = mxic_nfc_data_xfer(nfc, + instr->ctx.addr.addrs, NULL, + instr->ctx.addr.naddrs); + break; + + case NAND_OP_DATA_IN_INSTR: + writel(0x0, nfc->regs + ONFI_DIN_CNT(0)); + writel(OP_DATA_BUSW(OP_BUSW_8) | OP_DUMMY_CYC(0x3F) | + OP_READ, nfc->regs + SS_CTRL(0)); + ret = mxic_nfc_data_xfer(nfc, NULL, + instr->ctx.data.buf.in, + instr->ctx.data.len); + break; + + case NAND_OP_DATA_OUT_INSTR: + writel(instr->ctx.data.len, + nfc->regs + ONFI_DIN_CNT(0)); + writel(OP_DATA_BUSW(OP_BUSW_8) | OP_DUMMY_CYC(0x3F), + nfc->regs + SS_CTRL(0)); + ret = mxic_nfc_data_xfer(nfc, + instr->ctx.data.buf.out, NULL, + instr->ctx.data.len); + break; + + case NAND_OP_WAITRDY_INSTR: + ret = mxic_nfc_wait_ready(chip); + break; + } + } + mxic_nfc_cs_disable(nfc); + + return ret; +} + +static int mxic_nfc_setup_interface(struct nand_chip *chip, int chipnr, + const struct nand_interface_config *conf) +{ + struct mxic_nand_ctlr *nfc = nand_get_controller_data(chip); + const struct nand_sdr_timings *sdr; + unsigned long freq; + int ret; + + sdr = nand_get_sdr_timings(conf); + if (IS_ERR(sdr)) + return PTR_ERR(sdr); + + if (chipnr == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + freq = NSEC_PER_SEC / (sdr->tRC_min / 1000); + + ret = mxic_nfc_set_freq(nfc, freq); + if (ret) + dev_err(nfc->dev, "set freq:%ld failed\n", freq); + + if (sdr->tRC_min < 30000) + writel(DATA_STROB_EDO_EN, nfc->regs + DATA_STROB); + + return 0; +} + +static const struct nand_controller_ops mxic_nand_controller_ops = { + .exec_op = mxic_nfc_exec_op, + .setup_interface = mxic_nfc_setup_interface, +}; + +static int mxic_nfc_probe(struct platform_device *pdev) +{ + struct device_node *nand_np, *np = pdev->dev.of_node; + struct mtd_info *mtd; + struct mxic_nand_ctlr *nfc; + struct nand_chip *nand_chip; + int err; + int irq; + + nfc = devm_kzalloc(&pdev->dev, sizeof(struct mxic_nand_ctlr), + GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->ps_clk = devm_clk_get(&pdev->dev, "ps"); + if (IS_ERR(nfc->ps_clk)) + return PTR_ERR(nfc->ps_clk); + + nfc->send_clk = devm_clk_get(&pdev->dev, "send"); + if (IS_ERR(nfc->send_clk)) + return PTR_ERR(nfc->send_clk); + + nfc->send_dly_clk = devm_clk_get(&pdev->dev, "send_dly"); + if (IS_ERR(nfc->send_dly_clk)) + return PTR_ERR(nfc->send_dly_clk); + + nfc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(nfc->regs)) + return PTR_ERR(nfc->regs); + + nand_chip = &nfc->chip; + mtd = nand_to_mtd(nand_chip); + mtd->dev.parent = &pdev->dev; + + for_each_child_of_node(np, nand_np) + nand_set_flash_node(nand_chip, nand_np); + + nand_chip->priv = nfc; + nfc->dev = &pdev->dev; + nfc->controller.ops = &mxic_nand_controller_ops; + nand_controller_init(&nfc->controller); + nand_chip->controller = &nfc->controller; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + mxic_nfc_hw_init(nfc); + + err = devm_request_irq(&pdev->dev, irq, mxic_nfc_isr, + 0, "mxic-nfc", nfc); + if (err) + goto fail; + + err = nand_scan(nand_chip, 1); + if (err) + goto fail; + + err = mtd_device_register(mtd, NULL, 0); + if (err) + goto fail; + + platform_set_drvdata(pdev, nfc); + return 0; + +fail: + mxic_nfc_clk_disable(nfc); + return err; +} + +static void mxic_nfc_remove(struct platform_device *pdev) +{ + struct mxic_nand_ctlr *nfc = platform_get_drvdata(pdev); + struct nand_chip *chip = &nfc->chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + + mxic_nfc_clk_disable(nfc); +} + +static const struct of_device_id mxic_nfc_of_ids[] = { + { .compatible = "mxic,multi-itfc-v009-nand-controller", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mxic_nfc_of_ids); + +static struct platform_driver mxic_nfc_driver = { + .probe = mxic_nfc_probe, + .remove = mxic_nfc_remove, + .driver = { + .name = "mxic-nfc", + .of_match_table = mxic_nfc_of_ids, + }, +}; +module_platform_driver(mxic_nfc_driver); + +MODULE_AUTHOR("Mason Yang "); +MODULE_DESCRIPTION("Macronix raw NAND controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/raw/nand_amd.c b/drivers/mtd/nand/raw/nand_amd.c new file mode 100644 index 000000000..c3d4dae3c --- /dev/null +++ b/drivers/mtd/nand/raw/nand_amd.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2017 Free Electrons + * Copyright (C) 2017 NextThing Co + * + * Author: Boris Brezillon + */ + +#include "internals.h" + +static void amd_nand_decode_id(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_memory_organization *memorg; + + memorg = nanddev_get_memorg(&chip->base); + + nand_decode_ext_id(chip); + + /* + * Check for Spansion/AMD ID + repeating 5th, 6th byte since + * some Spansion chips have erasesize that conflicts with size + * listed in nand_ids table. + * Data sheet (5 byte ID): Spansion S30ML-P ORNAND (p.39) + */ + if (chip->id.data[4] != 0x00 && chip->id.data[5] == 0x00 && + chip->id.data[6] == 0x00 && chip->id.data[7] == 0x00 && + memorg->pagesize == 512) { + memorg->pages_per_eraseblock = 256; + memorg->pages_per_eraseblock <<= ((chip->id.data[3] & 0x03) << 1); + mtd->erasesize = memorg->pages_per_eraseblock * + memorg->pagesize; + } +} + +static int amd_nand_init(struct nand_chip *chip) +{ + if (nand_is_slc(chip)) + /* + * According to the datasheet of some Cypress SLC NANDs, + * the bad block markers can be in the first, second or last + * page of a block. So let's check all three locations. + */ + chip->options |= NAND_BBM_FIRSTPAGE | NAND_BBM_SECONDPAGE | + NAND_BBM_LASTPAGE; + + return 0; +} + +const struct nand_manufacturer_ops amd_nand_manuf_ops = { + .detect = amd_nand_decode_id, + .init = amd_nand_init, +}; diff --git a/drivers/mtd/nand/raw/nand_base.c b/drivers/mtd/nand/raw/nand_base.c new file mode 100644 index 000000000..a5b278ab9 --- /dev/null +++ b/drivers/mtd/nand/raw/nand_base.c @@ -0,0 +1,6609 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Overview: + * This is the generic MTD driver for NAND flash devices. It should be + * capable of working with almost all NAND chips currently available. + * + * Additional technical information is available on + * http://www.linux-mtd.infradead.org/doc/nand.html + * + * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) + * 2002-2006 Thomas Gleixner (tglx@kernel.org) + * + * Credits: + * David Woodhouse for adding multichip support + * + * Aleph One Ltd. and Toby Churchill Ltd. for supporting the + * rework for 2K page size chips + * + * TODO: + * Enable cached programming for 2k page size chips + * Check, if mtd->ecctype should be set to MTD_ECC_HW + * if we have HW ECC support. + * BBT table is not serialized, has to be fixed + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internals.h" + +static int nand_pairing_dist3_get_info(struct mtd_info *mtd, int page, + struct mtd_pairing_info *info) +{ + int lastpage = (mtd->erasesize / mtd->writesize) - 1; + int dist = 3; + + if (page == lastpage) + dist = 2; + + if (!page || (page & 1)) { + info->group = 0; + info->pair = (page + 1) / 2; + } else { + info->group = 1; + info->pair = (page + 1 - dist) / 2; + } + + return 0; +} + +static int nand_pairing_dist3_get_wunit(struct mtd_info *mtd, + const struct mtd_pairing_info *info) +{ + int lastpair = ((mtd->erasesize / mtd->writesize) - 1) / 2; + int page = info->pair * 2; + int dist = 3; + + if (!info->group && !info->pair) + return 0; + + if (info->pair == lastpair && info->group) + dist = 2; + + if (!info->group) + page--; + else if (info->pair) + page += dist - 1; + + if (page >= mtd->erasesize / mtd->writesize) + return -EINVAL; + + return page; +} + +const struct mtd_pairing_scheme dist3_pairing_scheme = { + .ngroups = 2, + .get_info = nand_pairing_dist3_get_info, + .get_wunit = nand_pairing_dist3_get_wunit, +}; + +static int check_offs_len(struct nand_chip *chip, loff_t ofs, uint64_t len) +{ + int ret = 0; + + /* Start address must align on block boundary */ + if (ofs & ((1ULL << chip->phys_erase_shift) - 1)) { + pr_debug("%s: unaligned address\n", __func__); + ret = -EINVAL; + } + + /* Length must align on block boundary */ + if (len & ((1ULL << chip->phys_erase_shift) - 1)) { + pr_debug("%s: length not block aligned\n", __func__); + ret = -EINVAL; + } + + return ret; +} + +/** + * nand_extract_bits - Copy unaligned bits from one buffer to another one + * @dst: destination buffer + * @dst_off: bit offset at which the writing starts + * @src: source buffer + * @src_off: bit offset at which the reading starts + * @nbits: number of bits to copy from @src to @dst + * + * Copy bits from one memory region to another (overlap authorized). + */ +void nand_extract_bits(u8 *dst, unsigned int dst_off, const u8 *src, + unsigned int src_off, unsigned int nbits) +{ + unsigned int tmp, n; + + dst += dst_off / 8; + dst_off %= 8; + src += src_off / 8; + src_off %= 8; + + while (nbits) { + n = min3(8 - dst_off, 8 - src_off, nbits); + + tmp = (*src >> src_off) & GENMASK(n - 1, 0); + *dst &= ~GENMASK(n - 1 + dst_off, dst_off); + *dst |= tmp << dst_off; + + dst_off += n; + if (dst_off >= 8) { + dst++; + dst_off -= 8; + } + + src_off += n; + if (src_off >= 8) { + src++; + src_off -= 8; + } + + nbits -= n; + } +} +EXPORT_SYMBOL_GPL(nand_extract_bits); + +/** + * nand_select_target() - Select a NAND target (A.K.A. die) + * @chip: NAND chip object + * @cs: the CS line to select. Note that this CS id is always from the chip + * PoV, not the controller one + * + * Select a NAND target so that further operations executed on @chip go to the + * selected NAND target. + */ +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))) + return; + + chip->cur_cs = cs; + + if (chip->legacy.select_chip) + chip->legacy.select_chip(chip, cs); +} +EXPORT_SYMBOL_GPL(nand_select_target); + +/** + * nand_deselect_target() - Deselect the currently selected target + * @chip: NAND chip object + * + * Deselect the currently selected NAND target. The result of operations + * executed on @chip after the target has been deselected is undefined. + */ +void nand_deselect_target(struct nand_chip *chip) +{ + if (chip->legacy.select_chip) + chip->legacy.select_chip(chip, -1); + + chip->cur_cs = -1; +} +EXPORT_SYMBOL_GPL(nand_deselect_target); + +/** + * nand_release_device - [GENERIC] release chip + * @chip: NAND chip object + * + * Release chip lock and wake up anyone waiting on the device. + */ +static void nand_release_device(struct nand_chip *chip) +{ + /* Release the controller and the chip */ + mutex_unlock(&chip->controller->lock); + mutex_unlock(&chip->lock); +} + +/** + * nand_bbm_get_next_page - Get the next page for bad block markers + * @chip: NAND chip object + * @page: First page to start checking for bad block marker usage + * + * Returns an integer that corresponds to the page offset within a block, for + * a page that is used to store bad block markers. If no more pages are + * available, -EINVAL is returned. + */ +int nand_bbm_get_next_page(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int last_page = ((mtd->erasesize - mtd->writesize) >> + chip->page_shift) & chip->pagemask; + unsigned int bbm_flags = NAND_BBM_FIRSTPAGE | NAND_BBM_SECONDPAGE + | NAND_BBM_LASTPAGE; + + if (page == 0 && !(chip->options & bbm_flags)) + return 0; + if (page == 0 && chip->options & NAND_BBM_FIRSTPAGE) + return 0; + if (page <= 1 && chip->options & NAND_BBM_SECONDPAGE) + return 1; + if (page <= last_page && chip->options & NAND_BBM_LASTPAGE) + return last_page; + + return -EINVAL; +} + +/** + * nand_block_bad - [DEFAULT] Read bad block marker from the chip + * @chip: NAND chip object + * @ofs: offset from device start + * + * Check, if the block is bad. + */ +static int nand_block_bad(struct nand_chip *chip, loff_t ofs) +{ + int first_page, page_offset; + int res; + u8 bad; + + first_page = (int)(ofs >> chip->page_shift) & chip->pagemask; + page_offset = nand_bbm_get_next_page(chip, 0); + + while (page_offset >= 0) { + res = chip->ecc.read_oob(chip, first_page + page_offset); + if (res < 0) + return res; + + bad = chip->oob_poi[chip->badblockpos]; + + if (likely(chip->badblockbits == 8)) + res = bad != 0xFF; + else + res = hweight8(bad) < chip->badblockbits; + if (res) + return res; + + page_offset = nand_bbm_get_next_page(chip, page_offset + 1); + } + + return 0; +} + +/** + * nand_region_is_secured() - Check if the region is secured + * @chip: NAND chip object + * @offset: Offset of the region to check + * @size: Size of the region to check + * + * Checks if the region is secured by comparing the offset and size with the + * list of secure regions obtained from DT. Returns true if the region is + * secured else false. + */ +static bool nand_region_is_secured(struct nand_chip *chip, loff_t offset, u64 size) +{ + int i; + + /* Skip touching the secure regions if present */ + for (i = 0; i < chip->nr_secure_regions; i++) { + const struct nand_secure_region *region = &chip->secure_regions[i]; + + if (offset + size <= region->offset || + offset >= region->offset + region->size) + continue; + + pr_debug("%s: Region 0x%llx - 0x%llx is secured!", + __func__, offset, offset + size); + + return true; + } + + return false; +} + +static int nand_isbad_bbm(struct nand_chip *chip, loff_t ofs) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (chip->options & NAND_NO_BBM_QUIRK) + return 0; + + /* Check if the region is secured */ + if (nand_region_is_secured(chip, ofs, mtd->erasesize)) + return -EIO; + + if (mtd_check_expert_analysis_mode()) + return 0; + + if (chip->legacy.block_bad) + return chip->legacy.block_bad(chip, ofs); + + return nand_block_bad(chip, ofs); +} + +/** + * nand_get_device - [GENERIC] Get chip for selected access + * @chip: NAND chip structure + * + * Lock the device and its controller for exclusive access + */ +static void nand_get_device(struct nand_chip *chip) +{ + /* Wait until the device is resumed. */ + while (1) { + mutex_lock(&chip->lock); + if (!chip->suspended) { + mutex_lock(&chip->controller->lock); + return; + } + mutex_unlock(&chip->lock); + + wait_event(chip->resume_wq, !chip->suspended); + } +} + +/** + * nand_check_wp - [GENERIC] check if the chip is write protected + * @chip: NAND chip object + * + * Check, if the device is write protected. The function expects, that the + * device is already selected. + */ +static int nand_check_wp(struct nand_chip *chip) +{ + u8 status; + int ret; + + /* Broken xD cards report WP despite being writable */ + if (chip->options & NAND_BROKEN_XD) + return 0; + + /* controller responsible for NAND write protect */ + if (chip->controller->controller_wp) + return 0; + + /* Check the WP bit */ + ret = nand_status_op(chip, &status); + if (ret) + return ret; + + return status & NAND_STATUS_WP ? 0 : 1; +} + +/** + * nand_fill_oob - [INTERN] Transfer client buffer to oob + * @chip: NAND chip object + * @oob: oob data buffer + * @len: oob data write length + * @ops: oob ops structure + */ +static uint8_t *nand_fill_oob(struct nand_chip *chip, uint8_t *oob, size_t len, + struct mtd_oob_ops *ops) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + /* + * Initialise to all 0xFF, to avoid the possibility of left over OOB + * data from a previous OOB read. + */ + memset(chip->oob_poi, 0xff, mtd->oobsize); + + switch (ops->mode) { + + case MTD_OPS_PLACE_OOB: + case MTD_OPS_RAW: + memcpy(chip->oob_poi + ops->ooboffs, oob, len); + return oob + len; + + case MTD_OPS_AUTO_OOB: + ret = mtd_ooblayout_set_databytes(mtd, oob, chip->oob_poi, + ops->ooboffs, len); + BUG_ON(ret); + return oob + len; + + default: + BUG(); + } + return NULL; +} + +/** + * nand_do_write_oob - [MTD Interface] NAND write out-of-band + * @chip: NAND chip object + * @to: offset to write to + * @ops: oob operation description structure + * + * NAND write out-of-band. + */ +static int nand_do_write_oob(struct nand_chip *chip, loff_t to, + struct mtd_oob_ops *ops) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int chipnr, page, status, len, ret; + + pr_debug("%s: to = 0x%08x, len = %i\n", + __func__, (unsigned int)to, (int)ops->ooblen); + + len = mtd_oobavail(mtd, ops); + + /* Do not allow write past end of page */ + if ((ops->ooboffs + ops->ooblen) > len) { + pr_debug("%s: attempt to write past end of page\n", + __func__); + return -EINVAL; + } + + /* Check if the region is secured */ + if (nand_region_is_secured(chip, to, ops->ooblen)) + return -EIO; + + chipnr = (int)(to >> chip->chip_shift); + + /* + * Reset the chip. Some chips (like the Toshiba TC5832DC found in one + * of my DiskOnChip 2000 test units) will clear the whole data page too + * if we don't do this. I have no clue why, but I seem to have 'fixed' + * it in the doc2000 driver in August 1999. dwmw2. + */ + ret = nand_reset(chip, chipnr); + if (ret) + return ret; + + nand_select_target(chip, chipnr); + + /* Shift to get page */ + page = (int)(to >> chip->page_shift); + + /* Check, if it is write protected */ + if (nand_check_wp(chip)) { + nand_deselect_target(chip); + return -EROFS; + } + + /* Invalidate the page cache, if we write to the cached page */ + if (page == chip->pagecache.page) + chip->pagecache.page = -1; + + nand_fill_oob(chip, ops->oobbuf, ops->ooblen, ops); + + if (ops->mode == MTD_OPS_RAW) + status = chip->ecc.write_oob_raw(chip, page & chip->pagemask); + else + status = chip->ecc.write_oob(chip, page & chip->pagemask); + + nand_deselect_target(chip); + + if (status) + return status; + + ops->oobretlen = ops->ooblen; + + return 0; +} + +/** + * nand_default_block_markbad - [DEFAULT] mark a block bad via bad block marker + * @chip: NAND chip object + * @ofs: offset from device start + * + * This is the default implementation, which can be overridden by a hardware + * specific driver. It provides the details for writing a bad block marker to a + * block. + */ +static int nand_default_block_markbad(struct nand_chip *chip, loff_t ofs) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mtd_oob_ops ops; + uint8_t buf[2] = { 0, 0 }; + int ret = 0, res, page_offset; + + memset(&ops, 0, sizeof(ops)); + ops.oobbuf = buf; + ops.ooboffs = chip->badblockpos; + if (chip->options & NAND_BUSWIDTH_16) { + ops.ooboffs &= ~0x01; + ops.len = ops.ooblen = 2; + } else { + ops.len = ops.ooblen = 1; + } + ops.mode = MTD_OPS_PLACE_OOB; + + page_offset = nand_bbm_get_next_page(chip, 0); + + while (page_offset >= 0) { + res = nand_do_write_oob(chip, + ofs + (page_offset * mtd->writesize), + &ops); + + if (!ret) + ret = res; + + page_offset = nand_bbm_get_next_page(chip, page_offset + 1); + } + + return ret; +} + +/** + * nand_markbad_bbm - mark a block by updating the BBM + * @chip: NAND chip object + * @ofs: offset of the block to mark bad + */ +int nand_markbad_bbm(struct nand_chip *chip, loff_t ofs) +{ + if (chip->legacy.block_markbad) + return chip->legacy.block_markbad(chip, ofs); + + return nand_default_block_markbad(chip, ofs); +} + +/** + * nand_block_markbad_lowlevel - mark a block bad + * @chip: NAND chip object + * @ofs: offset from device start + * + * This function performs the generic NAND bad block marking steps (i.e., bad + * block table(s) and/or marker(s)). We only allow the hardware driver to + * specify how to write bad block markers to OOB (chip->legacy.block_markbad). + * + * We try operations in the following order: + * + * (1) erase the affected block, to allow OOB marker to be written cleanly + * (2) write bad block marker to OOB area of affected block (unless flag + * NAND_BBT_NO_OOB_BBM is present) + * (3) update the BBT + * + * Note that we retain the first error encountered in (2) or (3), finish the + * procedures, and dump the error in the end. +*/ +static int nand_block_markbad_lowlevel(struct nand_chip *chip, loff_t ofs) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int res, ret = 0; + + if (!(chip->bbt_options & NAND_BBT_NO_OOB_BBM)) { + struct erase_info einfo; + + /* Attempt erase before marking OOB */ + memset(&einfo, 0, sizeof(einfo)); + einfo.addr = ofs; + einfo.len = 1ULL << chip->phys_erase_shift; + nand_erase_nand(chip, &einfo, 0); + + /* Write bad block marker to OOB */ + nand_get_device(chip); + + ret = nand_markbad_bbm(chip, ofs); + nand_release_device(chip); + } + + /* Mark block bad in BBT */ + if (chip->bbt) { + res = nand_markbad_bbt(chip, ofs); + if (!ret) + ret = res; + } + + if (!ret) + mtd->ecc_stats.badblocks++; + + return ret; +} + +/** + * nand_block_isreserved - [GENERIC] Check if a block is marked reserved. + * @mtd: MTD device structure + * @ofs: offset from device start + * + * Check if the block is marked as reserved. + */ +static int nand_block_isreserved(struct mtd_info *mtd, loff_t ofs) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (!chip->bbt) + return 0; + /* Return info from the table */ + return nand_isreserved_bbt(chip, ofs); +} + +/** + * nand_block_checkbad - [GENERIC] Check if a block is marked bad + * @chip: NAND chip object + * @ofs: offset from device start + * @allowbbt: 1, if its allowed to access the bbt area + * + * Check, if the block is bad. Either by reading the bad block table or + * calling of the scan function. + */ +static int nand_block_checkbad(struct nand_chip *chip, loff_t ofs, int allowbbt) +{ + /* Return info from the table */ + if (chip->bbt) + return nand_isbad_bbt(chip, ofs, allowbbt); + + return nand_isbad_bbm(chip, ofs); +} + +/** + * nand_soft_waitrdy - Poll STATUS reg until RDY bit is set to 1 + * @chip: NAND chip structure + * @timeout_ms: Timeout in ms + * + * Poll the STATUS register using ->exec_op() until the RDY bit becomes 1. + * If that does not happen whitin the specified timeout, -ETIMEDOUT is + * returned. + * + * This helper is intended to be used when the controller does not have access + * to the NAND R/B pin. + * + * Be aware that calling this helper from an ->exec_op() implementation means + * ->exec_op() must be re-entrant. + * + * Return 0 if the NAND chip is ready, a negative error otherwise. + */ +int nand_soft_waitrdy(struct nand_chip *chip, unsigned long timeout_ms) +{ + const struct nand_interface_config *conf; + u8 status = 0; + int ret; + + if (!nand_has_exec_op(chip)) + return -ENOTSUPP; + + /* Wait tWB before polling the STATUS reg. */ + conf = nand_get_interface_config(chip); + ndelay(NAND_COMMON_TIMING_NS(conf, tWB_max)); + + ret = nand_status_op(chip, NULL); + if (ret) + return ret; + + /* + * +1 below is necessary because if we are now in the last fraction + * of jiffy and msecs_to_jiffies is 1 then we will wait only that + * small jiffy fraction - possibly leading to false timeout + */ + timeout_ms = jiffies + msecs_to_jiffies(timeout_ms) + 1; + do { + ret = nand_read_data_op(chip, &status, sizeof(status), true, + false); + if (ret) + break; + + if (status & NAND_STATUS_READY) + break; + + /* + * Typical lowest execution time for a tR on most NANDs is 10us, + * use this as polling delay before doing something smarter (ie. + * deriving a delay from the timeout value, timeout_ms/ratio). + */ + udelay(10); + } while (time_before(jiffies, timeout_ms)); + + /* + * We have to exit READ_STATUS mode in order to read real data on the + * bus in case the WAITRDY instruction is preceding a DATA_IN + * instruction. + */ + nand_exit_status_op(chip); + + if (ret) + return ret; + + return status & NAND_STATUS_READY ? 0 : -ETIMEDOUT; +}; +EXPORT_SYMBOL_GPL(nand_soft_waitrdy); + +/** + * nand_gpio_waitrdy - Poll R/B GPIO pin until ready + * @chip: NAND chip structure + * @gpiod: GPIO descriptor of R/B pin + * @timeout_ms: Timeout in ms + * + * Poll the R/B GPIO pin until it becomes ready. If that does not happen + * whitin the specified timeout, -ETIMEDOUT is returned. + * + * This helper is intended to be used when the controller has access to the + * NAND R/B pin over GPIO. + * + * Return 0 if the R/B pin indicates chip is ready, a negative error otherwise. + */ +int nand_gpio_waitrdy(struct nand_chip *chip, struct gpio_desc *gpiod, + unsigned long timeout_ms) +{ + + /* + * Wait until R/B pin indicates chip is ready or timeout occurs. + * +1 below is necessary because if we are now in the last fraction + * of jiffy and msecs_to_jiffies is 1 then we will wait only that + * small jiffy fraction - possibly leading to false timeout. + */ + timeout_ms = jiffies + msecs_to_jiffies(timeout_ms) + 1; + do { + if (gpiod_get_value_cansleep(gpiod)) + return 0; + + cond_resched(); + } while (time_before(jiffies, timeout_ms)); + + return gpiod_get_value_cansleep(gpiod) ? 0 : -ETIMEDOUT; +}; +EXPORT_SYMBOL_GPL(nand_gpio_waitrdy); + +/** + * panic_nand_wait - [GENERIC] wait until the command is done + * @chip: NAND chip structure + * @timeo: timeout + * + * Wait for command done. This is a helper function for nand_wait used when + * we are in interrupt context. May happen when in panic and trying to write + * an oops through mtdoops. + */ +void panic_nand_wait(struct nand_chip *chip, unsigned long timeo) +{ + int i; + for (i = 0; i < timeo; i++) { + if (chip->legacy.dev_ready) { + if (chip->legacy.dev_ready(chip)) + break; + } else { + int ret; + u8 status; + + ret = nand_read_data_op(chip, &status, sizeof(status), + true, false); + if (ret) + return; + + if (status & NAND_STATUS_READY) + break; + } + mdelay(1); + } +} + +static bool nand_supports_get_features(struct nand_chip *chip, int addr) +{ + return (chip->parameters.supports_set_get_features && + test_bit(addr, chip->parameters.get_feature_list)); +} + +static bool nand_supports_set_features(struct nand_chip *chip, int addr) +{ + return (chip->parameters.supports_set_get_features && + test_bit(addr, chip->parameters.set_feature_list)); +} + +/** + * nand_reset_interface - Reset data interface and timings + * @chip: The NAND chip + * @chipnr: Internal die id + * + * Reset the Data interface and timings to ONFI mode 0. + * + * Returns 0 for success or negative error code otherwise. + */ +static int nand_reset_interface(struct nand_chip *chip, int chipnr) +{ + const struct nand_controller_ops *ops = chip->controller->ops; + int ret; + + if (!nand_controller_can_setup_interface(chip)) + return 0; + + /* + * The ONFI specification says: + * " + * To transition from NV-DDR or NV-DDR2 to the SDR data + * interface, the host shall use the Reset (FFh) command + * using SDR timing mode 0. A device in any timing mode is + * required to recognize Reset (FFh) command issued in SDR + * timing mode 0. + * " + * + * Configure the data interface in SDR mode and set the + * timings to timing mode 0. + */ + + chip->current_interface_config = nand_get_reset_interface_config(); + ret = ops->setup_interface(chip, chipnr, + chip->current_interface_config); + if (ret) + pr_err("Failed to configure data interface to SDR timing mode 0\n"); + + return ret; +} + +/** + * nand_setup_interface - Setup the best data interface and timings + * @chip: The NAND chip + * @chipnr: Internal die id + * + * Configure what has been reported to be the best data interface and NAND + * timings supported by the chip and the driver. + * + * Returns 0 for success or negative error code otherwise. + */ +static int nand_setup_interface(struct nand_chip *chip, int chipnr) +{ + const struct nand_controller_ops *ops = chip->controller->ops; + u8 tmode_param[ONFI_SUBFEATURE_PARAM_LEN] = { }, request; + int ret; + + if (!nand_controller_can_setup_interface(chip)) + return 0; + + /* + * A nand_reset_interface() put both the NAND chip and the NAND + * controller in timings mode 0. If the default mode for this chip is + * also 0, no need to proceed to the change again. Plus, at probe time, + * nand_setup_interface() uses ->set/get_features() which would + * fail anyway as the parameter page is not available yet. + */ + if (!chip->best_interface_config) + return 0; + + request = chip->best_interface_config->timings.mode; + if (nand_interface_is_sdr(chip->best_interface_config)) + request |= ONFI_DATA_INTERFACE_SDR; + else + request |= ONFI_DATA_INTERFACE_NVDDR; + tmode_param[0] = request; + + /* Change the mode on the chip side (if supported by the NAND chip) */ + if (nand_supports_set_features(chip, ONFI_FEATURE_ADDR_TIMING_MODE)) { + nand_select_target(chip, chipnr); + ret = nand_set_features(chip, ONFI_FEATURE_ADDR_TIMING_MODE, + tmode_param); + nand_deselect_target(chip); + if (ret) + return ret; + } + + /* Change the mode on the controller side */ + ret = ops->setup_interface(chip, chipnr, chip->best_interface_config); + if (ret) + return ret; + + /* Check the mode has been accepted by the chip, if supported */ + if (!nand_supports_get_features(chip, ONFI_FEATURE_ADDR_TIMING_MODE)) + goto update_interface_config; + + memset(tmode_param, 0, ONFI_SUBFEATURE_PARAM_LEN); + nand_select_target(chip, chipnr); + ret = nand_get_features(chip, ONFI_FEATURE_ADDR_TIMING_MODE, + tmode_param); + nand_deselect_target(chip); + if (ret) + goto err_reset_chip; + + if (request != tmode_param[0]) { + pr_warn("%s timing mode %d not acknowledged by the NAND chip\n", + nand_interface_is_nvddr(chip->best_interface_config) ? "NV-DDR" : "SDR", + chip->best_interface_config->timings.mode); + pr_debug("NAND chip would work in %s timing mode %d\n", + tmode_param[0] & ONFI_DATA_INTERFACE_NVDDR ? "NV-DDR" : "SDR", + (unsigned int)ONFI_TIMING_MODE_PARAM(tmode_param[0])); + goto err_reset_chip; + } + +update_interface_config: + chip->current_interface_config = chip->best_interface_config; + + return 0; + +err_reset_chip: + /* + * Fallback to mode 0 if the chip explicitly did not ack the chosen + * timing mode. + */ + nand_reset_interface(chip, chipnr); + nand_select_target(chip, chipnr); + nand_reset_op(chip); + nand_deselect_target(chip); + + return ret; +} + +/** + * nand_choose_best_sdr_timings - Pick up the best SDR timings that both the + * NAND controller and the NAND chip support + * @chip: the NAND chip + * @iface: the interface configuration (can eventually be updated) + * @spec_timings: specific timings, when not fitting the ONFI specification + * + * If specific timings are provided, use them. Otherwise, retrieve supported + * timing modes from ONFI information. + */ +int nand_choose_best_sdr_timings(struct nand_chip *chip, + struct nand_interface_config *iface, + struct nand_sdr_timings *spec_timings) +{ + const struct nand_controller_ops *ops = chip->controller->ops; + int best_mode = 0, mode, ret = -EOPNOTSUPP; + + iface->type = NAND_SDR_IFACE; + + if (spec_timings) { + iface->timings.sdr = *spec_timings; + iface->timings.mode = onfi_find_closest_sdr_mode(spec_timings); + + /* Verify the controller supports the requested interface */ + ret = ops->setup_interface(chip, NAND_DATA_IFACE_CHECK_ONLY, + iface); + if (!ret) { + chip->best_interface_config = iface; + return ret; + } + + /* Fallback to slower modes */ + best_mode = iface->timings.mode; + } else if (chip->parameters.onfi) { + best_mode = fls(chip->parameters.onfi->sdr_timing_modes) - 1; + } + + for (mode = best_mode; mode >= 0; mode--) { + onfi_fill_interface_config(chip, iface, NAND_SDR_IFACE, mode); + + ret = ops->setup_interface(chip, NAND_DATA_IFACE_CHECK_ONLY, + iface); + if (!ret) { + chip->best_interface_config = iface; + break; + } + } + + return ret; +} + +/** + * nand_choose_best_nvddr_timings - Pick up the best NVDDR timings that both the + * NAND controller and the NAND chip support + * @chip: the NAND chip + * @iface: the interface configuration (can eventually be updated) + * @spec_timings: specific timings, when not fitting the ONFI specification + * + * If specific timings are provided, use them. Otherwise, retrieve supported + * timing modes from ONFI information. + */ +int nand_choose_best_nvddr_timings(struct nand_chip *chip, + struct nand_interface_config *iface, + struct nand_nvddr_timings *spec_timings) +{ + const struct nand_controller_ops *ops = chip->controller->ops; + int best_mode = 0, mode, ret = -EOPNOTSUPP; + + iface->type = NAND_NVDDR_IFACE; + + if (spec_timings) { + iface->timings.nvddr = *spec_timings; + iface->timings.mode = onfi_find_closest_nvddr_mode(spec_timings); + + /* Verify the controller supports the requested interface */ + ret = ops->setup_interface(chip, NAND_DATA_IFACE_CHECK_ONLY, + iface); + if (!ret) { + chip->best_interface_config = iface; + return ret; + } + + /* Fallback to slower modes */ + best_mode = iface->timings.mode; + } else if (chip->parameters.onfi) { + best_mode = fls(chip->parameters.onfi->nvddr_timing_modes) - 1; + } + + for (mode = best_mode; mode >= 0; mode--) { + onfi_fill_interface_config(chip, iface, NAND_NVDDR_IFACE, mode); + + ret = ops->setup_interface(chip, NAND_DATA_IFACE_CHECK_ONLY, + iface); + if (!ret) { + chip->best_interface_config = iface; + break; + } + } + + return ret; +} + +/** + * nand_choose_best_timings - Pick up the best NVDDR or SDR timings that both + * NAND controller and the NAND chip support + * @chip: the NAND chip + * @iface: the interface configuration (can eventually be updated) + * + * If specific timings are provided, use them. Otherwise, retrieve supported + * timing modes from ONFI information. + */ +static int nand_choose_best_timings(struct nand_chip *chip, + struct nand_interface_config *iface) +{ + int ret; + + /* Try the fastest timings: NV-DDR */ + ret = nand_choose_best_nvddr_timings(chip, iface, NULL); + if (!ret) + return 0; + + /* Fallback to SDR timings otherwise */ + return nand_choose_best_sdr_timings(chip, iface, NULL); +} + +/** + * nand_choose_interface_config - find the best data interface and timings + * @chip: The NAND chip + * + * Find the best data interface and NAND timings supported by the chip + * and the driver. Eventually let the NAND manufacturer driver propose his own + * set of timings. + * + * After this function nand_chip->interface_config is initialized with the best + * timing mode available. + * + * Returns 0 for success or negative error code otherwise. + */ +static int nand_choose_interface_config(struct nand_chip *chip) +{ + struct nand_interface_config *iface; + int ret; + + if (!nand_controller_can_setup_interface(chip)) + return 0; + + iface = kzalloc_obj(*iface); + if (!iface) + return -ENOMEM; + + if (chip->ops.choose_interface_config) + ret = chip->ops.choose_interface_config(chip, iface); + else + ret = nand_choose_best_timings(chip, iface); + + if (ret) + kfree(iface); + + return ret; +} + +/** + * nand_fill_column_cycles - fill the column cycles of an address + * @chip: The NAND chip + * @addrs: Array of address cycles to fill + * @offset_in_page: The offset in the page + * + * Fills the first or the first two bytes of the @addrs field depending + * on the NAND bus width and the page size. + * + * Returns the number of cycles needed to encode the column, or a negative + * error code in case one of the arguments is invalid. + */ +static int nand_fill_column_cycles(struct nand_chip *chip, u8 *addrs, + unsigned int offset_in_page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + bool ident_stage = !mtd->writesize; + + /* Bypass all checks during NAND identification */ + if (likely(!ident_stage)) { + /* Make sure the offset is less than the actual page size. */ + if (offset_in_page > mtd->writesize + mtd->oobsize) + return -EINVAL; + + /* + * On small page NANDs, there's a dedicated command to access the OOB + * area, and the column address is relative to the start of the OOB + * area, not the start of the page. Asjust the address accordingly. + */ + if (mtd->writesize <= 512 && offset_in_page >= mtd->writesize) + offset_in_page -= mtd->writesize; + + /* + * The offset in page is expressed in bytes, if the NAND bus is 16-bit + * wide, then it must be divided by 2. + */ + if (chip->options & NAND_BUSWIDTH_16) { + if (WARN_ON(offset_in_page % 2)) + return -EINVAL; + + offset_in_page /= 2; + } + } + + addrs[0] = offset_in_page; + + /* + * Small page NANDs use 1 cycle for the columns, while large page NANDs + * need 2 + */ + if (!ident_stage && mtd->writesize <= 512) + return 1; + + addrs[1] = offset_in_page >> 8; + + return 2; +} + +static int nand_sp_exec_read_page_op(struct nand_chip *chip, unsigned int page, + unsigned int offset_in_page, void *buf, + unsigned int len) +{ + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + u8 addrs[4]; + struct nand_op_instr instrs[] = { + NAND_OP_CMD(NAND_CMD_READ0, 0), + NAND_OP_ADDR(3, addrs, NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tR_max), + NAND_COMMON_TIMING_NS(conf, tRR_min)), + NAND_OP_DATA_IN(len, buf, 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + int ret; + + /* Drop the DATA_IN instruction if len is set to 0. */ + if (!len) + op.ninstrs--; + + if (offset_in_page >= mtd->writesize) + instrs[0].ctx.cmd.opcode = NAND_CMD_READOOB; + else if (offset_in_page >= 256 && + !(chip->options & NAND_BUSWIDTH_16)) + instrs[0].ctx.cmd.opcode = NAND_CMD_READ1; + + ret = nand_fill_column_cycles(chip, addrs, offset_in_page); + if (ret < 0) + return ret; + + addrs[1] = page; + addrs[2] = page >> 8; + + if (chip->options & NAND_ROW_ADDR_3) { + addrs[3] = page >> 16; + instrs[1].ctx.addr.naddrs++; + } + + return nand_exec_op(chip, &op); +} + +static int nand_lp_exec_read_page_op(struct nand_chip *chip, unsigned int page, + unsigned int offset_in_page, void *buf, + unsigned int len) +{ + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + u8 addrs[5]; + struct nand_op_instr instrs[] = { + NAND_OP_CMD(NAND_CMD_READ0, 0), + NAND_OP_ADDR(4, addrs, 0), + NAND_OP_CMD(NAND_CMD_READSTART, NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tR_max), + NAND_COMMON_TIMING_NS(conf, tRR_min)), + NAND_OP_DATA_IN(len, buf, 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + int ret; + + /* Drop the DATA_IN instruction if len is set to 0. */ + if (!len) + op.ninstrs--; + + ret = nand_fill_column_cycles(chip, addrs, offset_in_page); + if (ret < 0) + return ret; + + addrs[2] = page; + addrs[3] = page >> 8; + + if (chip->options & NAND_ROW_ADDR_3) { + addrs[4] = page >> 16; + instrs[1].ctx.addr.naddrs++; + } + + return nand_exec_op(chip, &op); +} + +static unsigned int rawnand_last_page_of_block(unsigned int ppb, unsigned int block) +{ + /* 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 ppb, first_block, last_block; + + memorg = nanddev_get_memorg(&chip->base); + 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 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_block(ppb, first_block + 1)); + } + + if (chip->cont_read.first_page >= chip->cont_read.last_page) + chip->cont_read.ongoing = false; +} + +static int nand_lp_exec_cont_read_page_op(struct nand_chip *chip, unsigned int page, + unsigned int offset_in_page, void *buf, + unsigned int len, bool check_only) +{ + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + u8 addrs[5]; + struct nand_op_instr start_instrs[] = { + NAND_OP_CMD(NAND_CMD_READ0, 0), + NAND_OP_ADDR(4, addrs, 0), + NAND_OP_CMD(NAND_CMD_READSTART, NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tR_max), 0), + NAND_OP_CMD(NAND_CMD_READCACHESEQ, NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tR_max), + NAND_COMMON_TIMING_NS(conf, tRR_min)), + NAND_OP_DATA_IN(len, buf, 0), + }; + struct nand_op_instr cont_instrs[] = { + NAND_OP_CMD(page == chip->cont_read.pause_page ? + NAND_CMD_READCACHEEND : NAND_CMD_READCACHESEQ, + NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tR_max), + NAND_COMMON_TIMING_NS(conf, tRR_min)), + NAND_OP_DATA_IN(len, buf, 0), + }; + struct nand_operation start_op = NAND_OPERATION(chip->cur_cs, start_instrs); + struct nand_operation cont_op = NAND_OPERATION(chip->cur_cs, cont_instrs); + int ret; + + if (!len) { + start_op.ninstrs--; + cont_op.ninstrs--; + } + + ret = nand_fill_column_cycles(chip, addrs, offset_in_page); + if (ret < 0) + return ret; + + addrs[2] = page; + addrs[3] = page >> 8; + + if (chip->options & NAND_ROW_ADDR_3) { + addrs[4] = page >> 16; + start_instrs[1].ctx.addr.naddrs++; + } + + /* Check if cache reads are supported */ + if (check_only) { + if (nand_check_op(chip, &start_op) || nand_check_op(chip, &cont_op)) + return -EOPNOTSUPP; + + return 0; + } + + if (page == chip->cont_read.first_page) + ret = nand_exec_op(chip, &start_op); + else + ret = nand_exec_op(chip, &cont_op); + if (ret) + return ret; + + if (!chip->cont_read.ongoing) + return 0; + + if (page == chip->cont_read.last_page) { + chip->cont_read.ongoing = false; + } else if (page == chip->cont_read.pause_page) { + chip->cont_read.first_page++; + rawnand_cap_cont_reads(chip); + } + + return 0; +} + +static bool rawnand_cont_read_ongoing(struct nand_chip *chip, unsigned int page) +{ + return chip->cont_read.ongoing && page >= chip->cont_read.first_page; +} + +/** + * nand_read_page_op - Do a READ PAGE operation + * @chip: The NAND chip + * @page: page to read + * @offset_in_page: offset within the page + * @buf: buffer used to store the data + * @len: length of the buffer + * + * This function issues a READ PAGE operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_read_page_op(struct nand_chip *chip, unsigned int page, + unsigned int offset_in_page, void *buf, unsigned int len) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (len && !buf) + return -EINVAL; + + if (offset_in_page + len > mtd->writesize + mtd->oobsize) + return -EINVAL; + + if (nand_has_exec_op(chip)) { + if (mtd->writesize > 512) { + if (rawnand_cont_read_ongoing(chip, page)) + return nand_lp_exec_cont_read_page_op(chip, page, + offset_in_page, + buf, len, false); + else + return nand_lp_exec_read_page_op(chip, page, + offset_in_page, buf, + len); + } + + return nand_sp_exec_read_page_op(chip, page, offset_in_page, + buf, len); + } + + chip->legacy.cmdfunc(chip, NAND_CMD_READ0, offset_in_page, page); + if (len) + chip->legacy.read_buf(chip, buf, len); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_read_page_op); + +/** + * nand_read_param_page_op - Do a READ PARAMETER PAGE operation + * @chip: The NAND chip + * @page: parameter page to read + * @buf: buffer used to store the data + * @len: length of the buffer + * + * This function issues a READ PARAMETER PAGE operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_read_param_page_op(struct nand_chip *chip, u8 page, void *buf, + unsigned int len) +{ + unsigned int i; + u8 *p = buf; + + if (len && !buf) + return -EINVAL; + + if (nand_has_exec_op(chip)) { + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + struct nand_op_instr instrs[] = { + NAND_OP_CMD(NAND_CMD_PARAM, 0), + NAND_OP_ADDR(1, &page, + NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tR_max), + NAND_COMMON_TIMING_NS(conf, tRR_min)), + NAND_OP_8BIT_DATA_IN(len, buf, 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + + /* Drop the DATA_IN instruction if len is set to 0. */ + if (!len) + op.ninstrs--; + + return nand_exec_op(chip, &op); + } + + chip->legacy.cmdfunc(chip, NAND_CMD_PARAM, page, -1); + for (i = 0; i < len; i++) + p[i] = chip->legacy.read_byte(chip); + + return 0; +} + +/** + * nand_change_read_column_op - Do a CHANGE READ COLUMN operation + * @chip: The NAND chip + * @offset_in_page: offset within the page + * @buf: buffer used to store the data + * @len: length of the buffer + * @force_8bit: force 8-bit bus access + * + * This function issues a CHANGE READ COLUMN operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_change_read_column_op(struct nand_chip *chip, + unsigned int offset_in_page, void *buf, + unsigned int len, bool force_8bit) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + bool ident_stage = !mtd->writesize; + + if (len && !buf) + return -EINVAL; + + if (!ident_stage) { + if (offset_in_page + len > mtd->writesize + mtd->oobsize) + return -EINVAL; + + /* Small page NANDs do not support column change. */ + if (mtd->writesize <= 512) + return -ENOTSUPP; + } + + if (nand_has_exec_op(chip)) { + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + u8 addrs[2] = {}; + struct nand_op_instr instrs[] = { + NAND_OP_CMD(NAND_CMD_RNDOUT, 0), + NAND_OP_ADDR(2, addrs, 0), + NAND_OP_CMD(NAND_CMD_RNDOUTSTART, + NAND_COMMON_TIMING_NS(conf, tCCS_min)), + NAND_OP_DATA_IN(len, buf, 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + int ret; + + ret = nand_fill_column_cycles(chip, addrs, offset_in_page); + if (ret < 0) + return ret; + + /* Drop the DATA_IN instruction if len is set to 0. */ + if (!len) + op.ninstrs--; + + instrs[3].ctx.data.force_8bit = force_8bit; + + return nand_exec_op(chip, &op); + } + + chip->legacy.cmdfunc(chip, NAND_CMD_RNDOUT, offset_in_page, -1); + if (len) + chip->legacy.read_buf(chip, buf, len); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_change_read_column_op); + +/** + * nand_read_oob_op - Do a READ OOB operation + * @chip: The NAND chip + * @page: page to read + * @offset_in_oob: offset within the OOB area + * @buf: buffer used to store the data + * @len: length of the buffer + * + * This function issues a READ OOB operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_read_oob_op(struct nand_chip *chip, unsigned int page, + unsigned int offset_in_oob, void *buf, unsigned int len) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (len && !buf) + return -EINVAL; + + if (offset_in_oob + len > mtd->oobsize) + return -EINVAL; + + if (nand_has_exec_op(chip)) + return nand_read_page_op(chip, page, + mtd->writesize + offset_in_oob, + buf, len); + + chip->legacy.cmdfunc(chip, NAND_CMD_READOOB, offset_in_oob, page); + if (len) + chip->legacy.read_buf(chip, buf, len); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_read_oob_op); + +static int nand_exec_prog_page_op(struct nand_chip *chip, unsigned int page, + unsigned int offset_in_page, const void *buf, + unsigned int len, bool prog) +{ + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + u8 addrs[5] = {}; + struct nand_op_instr instrs[] = { + /* + * The first instruction will be dropped if we're dealing + * with a large page NAND and adjusted if we're dealing + * with a small page NAND and the page offset is > 255. + */ + NAND_OP_CMD(NAND_CMD_READ0, 0), + NAND_OP_CMD(NAND_CMD_SEQIN, 0), + NAND_OP_ADDR(0, addrs, NAND_COMMON_TIMING_NS(conf, tADL_min)), + NAND_OP_DATA_OUT(len, buf, 0), + NAND_OP_CMD(NAND_CMD_PAGEPROG, + NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tPROG_max), 0), + }; + struct nand_operation op = NAND_DESTRUCTIVE_OPERATION(chip->cur_cs, + instrs); + int naddrs = nand_fill_column_cycles(chip, addrs, offset_in_page); + + if (naddrs < 0) + return naddrs; + + addrs[naddrs++] = page; + addrs[naddrs++] = page >> 8; + if (chip->options & NAND_ROW_ADDR_3) + addrs[naddrs++] = page >> 16; + + instrs[2].ctx.addr.naddrs = naddrs; + + /* Drop the last two instructions if we're not programming the page. */ + if (!prog) { + op.ninstrs -= 2; + /* Also drop the DATA_OUT instruction if empty. */ + if (!len) + op.ninstrs--; + } + + if (mtd->writesize <= 512) { + /* + * Small pages need some more tweaking: we have to adjust the + * first instruction depending on the page offset we're trying + * to access. + */ + if (offset_in_page >= mtd->writesize) + instrs[0].ctx.cmd.opcode = NAND_CMD_READOOB; + else if (offset_in_page >= 256 && + !(chip->options & NAND_BUSWIDTH_16)) + instrs[0].ctx.cmd.opcode = NAND_CMD_READ1; + } else { + /* + * Drop the first command if we're dealing with a large page + * NAND. + */ + op.instrs++; + op.ninstrs--; + } + + return nand_exec_op(chip, &op); +} + +/** + * nand_prog_page_begin_op - starts a PROG PAGE operation + * @chip: The NAND chip + * @page: page to write + * @offset_in_page: offset within the page + * @buf: buffer containing the data to write to the page + * @len: length of the buffer + * + * This function issues the first half of a PROG PAGE operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_prog_page_begin_op(struct nand_chip *chip, unsigned int page, + unsigned int offset_in_page, const void *buf, + unsigned int len) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (len && !buf) + return -EINVAL; + + if (offset_in_page + len > mtd->writesize + mtd->oobsize) + return -EINVAL; + + if (nand_has_exec_op(chip)) + return nand_exec_prog_page_op(chip, page, offset_in_page, buf, + len, false); + + chip->legacy.cmdfunc(chip, NAND_CMD_SEQIN, offset_in_page, page); + + if (buf) + chip->legacy.write_buf(chip, buf, len); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_prog_page_begin_op); + +/** + * nand_prog_page_end_op - ends a PROG PAGE operation + * @chip: The NAND chip + * + * This function issues the second half of a PROG PAGE operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_prog_page_end_op(struct nand_chip *chip) +{ + int ret; + u8 status; + + if (nand_has_exec_op(chip)) { + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + struct nand_op_instr instrs[] = { + NAND_OP_CMD(NAND_CMD_PAGEPROG, + NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tPROG_max), + 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + + ret = nand_exec_op(chip, &op); + if (ret) + return ret; + + ret = nand_status_op(chip, &status); + if (ret) + return ret; + } else { + chip->legacy.cmdfunc(chip, NAND_CMD_PAGEPROG, -1, -1); + ret = chip->legacy.waitfunc(chip); + if (ret < 0) + return ret; + + status = ret; + } + + if (status & NAND_STATUS_FAIL) + return -EIO; + + return 0; +} +EXPORT_SYMBOL_GPL(nand_prog_page_end_op); + +/** + * nand_prog_page_op - Do a full PROG PAGE operation + * @chip: The NAND chip + * @page: page to write + * @offset_in_page: offset within the page + * @buf: buffer containing the data to write to the page + * @len: length of the buffer + * + * This function issues a full PROG PAGE operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_prog_page_op(struct nand_chip *chip, unsigned int page, + unsigned int offset_in_page, const void *buf, + unsigned int len) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + u8 status; + int ret; + + if (!len || !buf) + return -EINVAL; + + if (offset_in_page + len > mtd->writesize + mtd->oobsize) + return -EINVAL; + + if (nand_has_exec_op(chip)) { + ret = nand_exec_prog_page_op(chip, page, offset_in_page, buf, + len, true); + if (ret) + return ret; + + ret = nand_status_op(chip, &status); + if (ret) + return ret; + } else { + chip->legacy.cmdfunc(chip, NAND_CMD_SEQIN, offset_in_page, + page); + chip->legacy.write_buf(chip, buf, len); + chip->legacy.cmdfunc(chip, NAND_CMD_PAGEPROG, -1, -1); + ret = chip->legacy.waitfunc(chip); + if (ret < 0) + return ret; + + status = ret; + } + + if (status & NAND_STATUS_FAIL) + return -EIO; + + return 0; +} +EXPORT_SYMBOL_GPL(nand_prog_page_op); + +/** + * nand_change_write_column_op - Do a CHANGE WRITE COLUMN operation + * @chip: The NAND chip + * @offset_in_page: offset within the page + * @buf: buffer containing the data to send to the NAND + * @len: length of the buffer + * @force_8bit: force 8-bit bus access + * + * This function issues a CHANGE WRITE COLUMN operation. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_change_write_column_op(struct nand_chip *chip, + unsigned int offset_in_page, + const void *buf, unsigned int len, + bool force_8bit) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (len && !buf) + return -EINVAL; + + if (offset_in_page + len > mtd->writesize + mtd->oobsize) + return -EINVAL; + + /* Small page NANDs do not support column change. */ + if (mtd->writesize <= 512) + return -ENOTSUPP; + + if (nand_has_exec_op(chip)) { + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + u8 addrs[2]; + struct nand_op_instr instrs[] = { + NAND_OP_CMD(NAND_CMD_RNDIN, 0), + NAND_OP_ADDR(2, addrs, NAND_COMMON_TIMING_NS(conf, tCCS_min)), + NAND_OP_DATA_OUT(len, buf, 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + int ret; + + ret = nand_fill_column_cycles(chip, addrs, offset_in_page); + if (ret < 0) + return ret; + + instrs[2].ctx.data.force_8bit = force_8bit; + + /* Drop the DATA_OUT instruction if len is set to 0. */ + if (!len) + op.ninstrs--; + + return nand_exec_op(chip, &op); + } + + chip->legacy.cmdfunc(chip, NAND_CMD_RNDIN, offset_in_page, -1); + if (len) + chip->legacy.write_buf(chip, buf, len); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_change_write_column_op); + +/** + * nand_readid_op - Do a READID operation + * @chip: The NAND chip + * @addr: address cycle to pass after the READID command + * @buf: buffer used to store the ID + * @len: length of the buffer + * + * This function sends a READID command and reads back the ID returned by the + * NAND. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_readid_op(struct nand_chip *chip, u8 addr, void *buf, + unsigned int len) +{ + unsigned int i; + u8 *id = buf, *ddrbuf = NULL; + + if (len && !buf) + return -EINVAL; + + if (nand_has_exec_op(chip)) { + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + struct nand_op_instr instrs[] = { + NAND_OP_CMD(NAND_CMD_READID, 0), + NAND_OP_ADDR(1, &addr, + NAND_COMMON_TIMING_NS(conf, tADL_min)), + NAND_OP_8BIT_DATA_IN(len, buf, 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + int ret; + + /* READ_ID data bytes are received twice in NV-DDR mode */ + if (len && nand_interface_is_nvddr(conf)) { + ddrbuf = kcalloc(2, len, GFP_KERNEL); + if (!ddrbuf) + return -ENOMEM; + + instrs[2].ctx.data.len *= 2; + instrs[2].ctx.data.buf.in = ddrbuf; + } + + /* Drop the DATA_IN instruction if len is set to 0. */ + if (!len) + op.ninstrs--; + + ret = nand_exec_op(chip, &op); + if (!ret && len && nand_interface_is_nvddr(conf)) { + for (i = 0; i < len; i++) + id[i] = ddrbuf[i * 2]; + } + + kfree(ddrbuf); + + return ret; + } + + chip->legacy.cmdfunc(chip, NAND_CMD_READID, addr, -1); + + for (i = 0; i < len; i++) + id[i] = chip->legacy.read_byte(chip); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_readid_op); + +/** + * nand_status_op - Do a STATUS operation + * @chip: The NAND chip + * @status: out variable to store the NAND status + * + * This function sends a STATUS command and reads back the status returned by + * the NAND. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_status_op(struct nand_chip *chip, u8 *status) +{ + if (nand_has_exec_op(chip)) { + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + u8 ddrstatus[2]; + struct nand_op_instr instrs[] = { + NAND_OP_CMD(NAND_CMD_STATUS, + NAND_COMMON_TIMING_NS(conf, tADL_min)), + NAND_OP_8BIT_DATA_IN(1, status, 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + int ret; + + /* The status data byte will be received twice in NV-DDR mode */ + if (status && nand_interface_is_nvddr(conf)) { + instrs[1].ctx.data.len *= 2; + instrs[1].ctx.data.buf.in = ddrstatus; + } + + if (!status) + op.ninstrs--; + + ret = nand_exec_op(chip, &op); + if (!ret && status && nand_interface_is_nvddr(conf)) + *status = ddrstatus[0]; + + return ret; + } + + chip->legacy.cmdfunc(chip, NAND_CMD_STATUS, -1, -1); + if (status) + *status = chip->legacy.read_byte(chip); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_status_op); + +/** + * nand_exit_status_op - Exit a STATUS operation + * @chip: The NAND chip + * + * This function sends a READ0 command to cancel the effect of the STATUS + * command to avoid reading only the status until a new read command is sent. + * + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_exit_status_op(struct nand_chip *chip) +{ + if (nand_has_exec_op(chip)) { + struct nand_op_instr instrs[] = { + NAND_OP_CMD(NAND_CMD_READ0, 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + + return nand_exec_op(chip, &op); + } + + chip->legacy.cmdfunc(chip, NAND_CMD_READ0, -1, -1); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_exit_status_op); + +/** + * nand_erase_op - Do an erase operation + * @chip: The NAND chip + * @eraseblock: block to erase + * + * This function sends an ERASE command and waits for the NAND to be ready + * before returning. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_erase_op(struct nand_chip *chip, unsigned int eraseblock) +{ + unsigned int page = eraseblock << + (chip->phys_erase_shift - chip->page_shift); + int ret; + u8 status; + + if (nand_has_exec_op(chip)) { + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + u8 addrs[3] = { page, page >> 8, page >> 16 }; + struct nand_op_instr instrs[] = { + NAND_OP_CMD(NAND_CMD_ERASE1, 0), + NAND_OP_ADDR(2, addrs, 0), + NAND_OP_CMD(NAND_CMD_ERASE2, + NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tBERS_max), + 0), + }; + struct nand_operation op = NAND_DESTRUCTIVE_OPERATION(chip->cur_cs, + instrs); + + if (chip->options & NAND_ROW_ADDR_3) + instrs[1].ctx.addr.naddrs++; + + ret = nand_exec_op(chip, &op); + if (ret) + return ret; + + ret = nand_status_op(chip, &status); + if (ret) + return ret; + } else { + chip->legacy.cmdfunc(chip, NAND_CMD_ERASE1, -1, page); + chip->legacy.cmdfunc(chip, NAND_CMD_ERASE2, -1, -1); + + ret = chip->legacy.waitfunc(chip); + if (ret < 0) + return ret; + + status = ret; + } + + if (status & NAND_STATUS_FAIL) + return -EIO; + + return 0; +} +EXPORT_SYMBOL_GPL(nand_erase_op); + +/** + * nand_set_features_op - Do a SET FEATURES operation + * @chip: The NAND chip + * @feature: feature id + * @data: 4 bytes of data + * + * This function sends a SET FEATURES command and waits for the NAND to be + * ready before returning. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +static int nand_set_features_op(struct nand_chip *chip, u8 feature, + const void *data) +{ + const u8 *params = data; + int i, ret; + + if (nand_has_exec_op(chip)) { + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + struct nand_op_instr instrs[] = { + NAND_OP_CMD(NAND_CMD_SET_FEATURES, 0), + NAND_OP_ADDR(1, &feature, NAND_COMMON_TIMING_NS(conf, + tADL_min)), + NAND_OP_8BIT_DATA_OUT(ONFI_SUBFEATURE_PARAM_LEN, data, + NAND_COMMON_TIMING_NS(conf, + tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tFEAT_max), + 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + + return nand_exec_op(chip, &op); + } + + chip->legacy.cmdfunc(chip, NAND_CMD_SET_FEATURES, feature, -1); + for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i) + chip->legacy.write_byte(chip, params[i]); + + ret = chip->legacy.waitfunc(chip); + if (ret < 0) + return ret; + + if (ret & NAND_STATUS_FAIL) + return -EIO; + + return 0; +} + +/** + * nand_get_features_op - Do a GET FEATURES operation + * @chip: The NAND chip + * @feature: feature id + * @data: 4 bytes of data + * + * This function sends a GET FEATURES command and waits for the NAND to be + * ready before returning. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +static int nand_get_features_op(struct nand_chip *chip, u8 feature, + void *data) +{ + u8 *params = data, ddrbuf[ONFI_SUBFEATURE_PARAM_LEN * 2]; + int i; + + if (nand_has_exec_op(chip)) { + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + struct nand_op_instr instrs[] = { + NAND_OP_CMD(NAND_CMD_GET_FEATURES, 0), + NAND_OP_ADDR(1, &feature, + NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tFEAT_max), + NAND_COMMON_TIMING_NS(conf, tRR_min)), + NAND_OP_8BIT_DATA_IN(ONFI_SUBFEATURE_PARAM_LEN, + data, 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + int ret; + + /* GET_FEATURE data bytes are received twice in NV-DDR mode */ + if (nand_interface_is_nvddr(conf)) { + instrs[3].ctx.data.len *= 2; + instrs[3].ctx.data.buf.in = ddrbuf; + } + + ret = nand_exec_op(chip, &op); + if (nand_interface_is_nvddr(conf)) { + for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; i++) + params[i] = ddrbuf[i * 2]; + } + + return ret; + } + + chip->legacy.cmdfunc(chip, NAND_CMD_GET_FEATURES, feature, -1); + for (i = 0; i < ONFI_SUBFEATURE_PARAM_LEN; ++i) + params[i] = chip->legacy.read_byte(chip); + + return 0; +} + +static int nand_wait_rdy_op(struct nand_chip *chip, unsigned int timeout_ms, + unsigned int delay_ns) +{ + if (nand_has_exec_op(chip)) { + struct nand_op_instr instrs[] = { + NAND_OP_WAIT_RDY(PSEC_TO_MSEC(timeout_ms), + PSEC_TO_NSEC(delay_ns)), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + + return nand_exec_op(chip, &op); + } + + /* Apply delay or wait for ready/busy pin */ + if (!chip->legacy.dev_ready) + udelay(chip->legacy.chip_delay); + else + nand_wait_ready(chip); + + return 0; +} + +/** + * nand_reset_op - Do a reset operation + * @chip: The NAND chip + * + * This function sends a RESET command and waits for the NAND to be ready + * before returning. + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_reset_op(struct nand_chip *chip) +{ + if (nand_has_exec_op(chip)) { + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + struct nand_op_instr instrs[] = { + NAND_OP_CMD(NAND_CMD_RESET, + NAND_COMMON_TIMING_NS(conf, tWB_max)), + NAND_OP_WAIT_RDY(NAND_COMMON_TIMING_MS(conf, tRST_max), + 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + + return nand_exec_op(chip, &op); + } + + chip->legacy.cmdfunc(chip, NAND_CMD_RESET, -1, -1); + + return 0; +} +EXPORT_SYMBOL_GPL(nand_reset_op); + +/** + * nand_read_data_op - Read data from the NAND + * @chip: The NAND chip + * @buf: buffer used to store the data + * @len: length of the buffer + * @force_8bit: force 8-bit bus access + * @check_only: do not actually run the command, only checks if the + * controller driver supports it + * + * This function does a raw data read on the bus. Usually used after launching + * another NAND operation like nand_read_page_op(). + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_read_data_op(struct nand_chip *chip, void *buf, unsigned int len, + bool force_8bit, bool check_only) +{ + if (!len || (!check_only && !buf)) + return -EINVAL; + + if (nand_has_exec_op(chip)) { + const struct nand_interface_config *conf = + nand_get_interface_config(chip); + struct nand_op_instr instrs[] = { + NAND_OP_DATA_IN(len, buf, 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + u8 *ddrbuf = NULL; + int ret, i; + + instrs[0].ctx.data.force_8bit = force_8bit; + + /* + * Parameter payloads (ID, status, features, etc) do not go + * through the same pipeline as regular data, hence the + * force_8bit flag must be set and this also indicates that in + * case NV-DDR timings are being used the data will be received + * twice. + */ + if (force_8bit && nand_interface_is_nvddr(conf)) { + ddrbuf = kcalloc(2, len, GFP_KERNEL); + if (!ddrbuf) + return -ENOMEM; + + instrs[0].ctx.data.len *= 2; + instrs[0].ctx.data.buf.in = ddrbuf; + } + + if (check_only) { + ret = nand_check_op(chip, &op); + kfree(ddrbuf); + return ret; + } + + ret = nand_exec_op(chip, &op); + if (!ret && force_8bit && nand_interface_is_nvddr(conf)) { + u8 *dst = buf; + + for (i = 0; i < len; i++) + dst[i] = ddrbuf[i * 2]; + } + + kfree(ddrbuf); + + return ret; + } + + if (check_only) + return 0; + + if (force_8bit) { + u8 *p = buf; + unsigned int i; + + for (i = 0; i < len; i++) + p[i] = chip->legacy.read_byte(chip); + } else { + chip->legacy.read_buf(chip, buf, len); + } + + return 0; +} +EXPORT_SYMBOL_GPL(nand_read_data_op); + +/** + * nand_write_data_op - Write data from the NAND + * @chip: The NAND chip + * @buf: buffer containing the data to send on the bus + * @len: length of the buffer + * @force_8bit: force 8-bit bus access + * + * This function does a raw data write on the bus. Usually used after launching + * another NAND operation like nand_write_page_begin_op(). + * This function does not select/unselect the CS line. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_write_data_op(struct nand_chip *chip, const void *buf, + unsigned int len, bool force_8bit) +{ + if (!len || !buf) + return -EINVAL; + + if (nand_has_exec_op(chip)) { + struct nand_op_instr instrs[] = { + NAND_OP_DATA_OUT(len, buf, 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + + instrs[0].ctx.data.force_8bit = force_8bit; + + return nand_exec_op(chip, &op); + } + + if (force_8bit) { + const u8 *p = buf; + unsigned int i; + + for (i = 0; i < len; i++) + chip->legacy.write_byte(chip, p[i]); + } else { + chip->legacy.write_buf(chip, buf, len); + } + + return 0; +} +EXPORT_SYMBOL_GPL(nand_write_data_op); + +/** + * struct nand_op_parser_ctx - Context used by the parser + * @instrs: array of all the instructions that must be addressed + * @ninstrs: length of the @instrs array + * @subop: Sub-operation to be passed to the NAND controller + * + * This structure is used by the core to split NAND operations into + * sub-operations that can be handled by the NAND controller. + */ +struct nand_op_parser_ctx { + const struct nand_op_instr *instrs; + unsigned int ninstrs; + struct nand_subop subop; +}; + +/** + * nand_op_parser_must_split_instr - Checks if an instruction must be split + * @pat: the parser pattern element that matches @instr + * @instr: pointer to the instruction to check + * @start_offset: this is an in/out parameter. If @instr has already been + * split, then @start_offset is the offset from which to start + * (either an address cycle or an offset in the data buffer). + * Conversely, if the function returns true (ie. instr must be + * split), this parameter is updated to point to the first + * data/address cycle that has not been taken care of. + * + * Some NAND controllers are limited and cannot send X address cycles with a + * unique operation, or cannot read/write more than Y bytes at the same time. + * In this case, split the instruction that does not fit in a single + * controller-operation into two or more chunks. + * + * Returns true if the instruction must be split, false otherwise. + * The @start_offset parameter is also updated to the offset at which the next + * bundle of instruction must start (if an address or a data instruction). + */ +static bool +nand_op_parser_must_split_instr(const struct nand_op_parser_pattern_elem *pat, + const struct nand_op_instr *instr, + unsigned int *start_offset) +{ + switch (pat->type) { + case NAND_OP_ADDR_INSTR: + if (!pat->ctx.addr.maxcycles) + break; + + if (instr->ctx.addr.naddrs - *start_offset > + pat->ctx.addr.maxcycles) { + *start_offset += pat->ctx.addr.maxcycles; + return true; + } + break; + + case NAND_OP_DATA_IN_INSTR: + case NAND_OP_DATA_OUT_INSTR: + if (!pat->ctx.data.maxlen) + break; + + if (instr->ctx.data.len - *start_offset > + pat->ctx.data.maxlen) { + *start_offset += pat->ctx.data.maxlen; + return true; + } + break; + + default: + break; + } + + return false; +} + +/** + * nand_op_parser_match_pat - Checks if a pattern matches the instructions + * remaining in the parser context + * @pat: the pattern to test + * @ctx: the parser context structure to match with the pattern @pat + * + * Check if @pat matches the set or a sub-set of instructions remaining in @ctx. + * Returns true if this is the case, false ortherwise. When true is returned, + * @ctx->subop is updated with the set of instructions to be passed to the + * controller driver. + */ +static bool +nand_op_parser_match_pat(const struct nand_op_parser_pattern *pat, + struct nand_op_parser_ctx *ctx) +{ + unsigned int instr_offset = ctx->subop.first_instr_start_off; + const struct nand_op_instr *end = ctx->instrs + ctx->ninstrs; + const struct nand_op_instr *instr = ctx->subop.instrs; + unsigned int i, ninstrs; + + for (i = 0, ninstrs = 0; i < pat->nelems && instr < end; i++) { + /* + * The pattern instruction does not match the operation + * instruction. If the instruction is marked optional in the + * pattern definition, we skip the pattern element and continue + * to the next one. If the element is mandatory, there's no + * match and we can return false directly. + */ + if (instr->type != pat->elems[i].type) { + if (!pat->elems[i].optional) + return false; + + continue; + } + + /* + * Now check the pattern element constraints. If the pattern is + * not able to handle the whole instruction in a single step, + * we have to split it. + * The last_instr_end_off value comes back updated to point to + * the position where we have to split the instruction (the + * start of the next subop chunk). + */ + if (nand_op_parser_must_split_instr(&pat->elems[i], instr, + &instr_offset)) { + ninstrs++; + i++; + break; + } + + instr++; + ninstrs++; + instr_offset = 0; + } + + /* + * This can happen if all instructions of a pattern are optional. + * Still, if there's not at least one instruction handled by this + * pattern, this is not a match, and we should try the next one (if + * any). + */ + if (!ninstrs) + return false; + + /* + * We had a match on the pattern head, but the pattern may be longer + * than the instructions we're asked to execute. We need to make sure + * there's no mandatory elements in the pattern tail. + */ + for (; i < pat->nelems; i++) { + if (!pat->elems[i].optional) + return false; + } + + /* + * We have a match: update the subop structure accordingly and return + * true. + */ + ctx->subop.ninstrs = ninstrs; + ctx->subop.last_instr_end_off = instr_offset; + + return true; +} + +#if IS_ENABLED(CONFIG_DYNAMIC_DEBUG) || defined(DEBUG) +static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx) +{ + const struct nand_op_instr *instr; + char *prefix = " "; + unsigned int i; + + pr_debug("executing subop (CS%d):\n", ctx->subop.cs); + + for (i = 0; i < ctx->ninstrs; i++) { + instr = &ctx->instrs[i]; + + if (instr == &ctx->subop.instrs[0]) + prefix = " ->"; + + nand_op_trace(prefix, instr); + + if (instr == &ctx->subop.instrs[ctx->subop.ninstrs - 1]) + prefix = " "; + } +} +#else +static void nand_op_parser_trace(const struct nand_op_parser_ctx *ctx) +{ + /* NOP */ +} +#endif + +static int nand_op_parser_cmp_ctx(const struct nand_op_parser_ctx *a, + const struct nand_op_parser_ctx *b) +{ + if (a->subop.ninstrs < b->subop.ninstrs) + return -1; + else if (a->subop.ninstrs > b->subop.ninstrs) + return 1; + + if (a->subop.last_instr_end_off < b->subop.last_instr_end_off) + return -1; + else if (a->subop.last_instr_end_off > b->subop.last_instr_end_off) + return 1; + + return 0; +} + +/** + * nand_op_parser_exec_op - exec_op parser + * @chip: the NAND chip + * @parser: patterns description provided by the controller driver + * @op: the NAND operation to address + * @check_only: when true, the function only checks if @op can be handled but + * does not execute the operation + * + * Helper function designed to ease integration of NAND controller drivers that + * only support a limited set of instruction sequences. The supported sequences + * are described in @parser, and the framework takes care of splitting @op into + * multiple sub-operations (if required) and pass them back to the ->exec() + * callback of the matching pattern if @check_only is set to false. + * + * NAND controller drivers should call this function from their own ->exec_op() + * implementation. + * + * Returns 0 on success, a negative error code otherwise. A failure can be + * caused by an unsupported operation (none of the supported patterns is able + * to handle the requested operation), or an error returned by one of the + * matching pattern->exec() hook. + */ +int nand_op_parser_exec_op(struct nand_chip *chip, + const struct nand_op_parser *parser, + const struct nand_operation *op, bool check_only) +{ + struct nand_op_parser_ctx ctx = { + .subop.cs = op->cs, + .subop.instrs = op->instrs, + .instrs = op->instrs, + .ninstrs = op->ninstrs, + }; + unsigned int i; + + while (ctx.subop.instrs < op->instrs + op->ninstrs) { + const struct nand_op_parser_pattern *pattern; + struct nand_op_parser_ctx best_ctx; + int ret, best_pattern = -1; + + for (i = 0; i < parser->npatterns; i++) { + struct nand_op_parser_ctx test_ctx = ctx; + + pattern = &parser->patterns[i]; + if (!nand_op_parser_match_pat(pattern, &test_ctx)) + continue; + + if (best_pattern >= 0 && + nand_op_parser_cmp_ctx(&test_ctx, &best_ctx) <= 0) + continue; + + best_pattern = i; + best_ctx = test_ctx; + } + + if (best_pattern < 0) { + pr_debug("->exec_op() parser: pattern not found!\n"); + return -ENOTSUPP; + } + + ctx = best_ctx; + nand_op_parser_trace(&ctx); + + if (!check_only) { + pattern = &parser->patterns[best_pattern]; + ret = pattern->exec(chip, &ctx.subop); + if (ret) + return ret; + } + + /* + * Update the context structure by pointing to the start of the + * next subop. + */ + ctx.subop.instrs = ctx.subop.instrs + ctx.subop.ninstrs; + if (ctx.subop.last_instr_end_off) + ctx.subop.instrs -= 1; + + ctx.subop.first_instr_start_off = ctx.subop.last_instr_end_off; + } + + return 0; +} +EXPORT_SYMBOL_GPL(nand_op_parser_exec_op); + +static bool nand_instr_is_data(const struct nand_op_instr *instr) +{ + return instr && (instr->type == NAND_OP_DATA_IN_INSTR || + instr->type == NAND_OP_DATA_OUT_INSTR); +} + +static bool nand_subop_instr_is_valid(const struct nand_subop *subop, + unsigned int instr_idx) +{ + return subop && instr_idx < subop->ninstrs; +} + +static unsigned int nand_subop_get_start_off(const struct nand_subop *subop, + unsigned int instr_idx) +{ + if (instr_idx) + return 0; + + return subop->first_instr_start_off; +} + +/** + * nand_subop_get_addr_start_off - Get the start offset in an address array + * @subop: The entire sub-operation + * @instr_idx: Index of the instruction inside the sub-operation + * + * During driver development, one could be tempted to directly use the + * ->addr.addrs field of address instructions. This is wrong as address + * instructions might be split. + * + * Given an address instruction, returns the offset of the first cycle to issue. + */ +unsigned int nand_subop_get_addr_start_off(const struct nand_subop *subop, + unsigned int instr_idx) +{ + if (WARN_ON(!nand_subop_instr_is_valid(subop, instr_idx) || + subop->instrs[instr_idx].type != NAND_OP_ADDR_INSTR)) + return 0; + + return nand_subop_get_start_off(subop, instr_idx); +} +EXPORT_SYMBOL_GPL(nand_subop_get_addr_start_off); + +/** + * nand_subop_get_num_addr_cyc - Get the remaining address cycles to assert + * @subop: The entire sub-operation + * @instr_idx: Index of the instruction inside the sub-operation + * + * During driver development, one could be tempted to directly use the + * ->addr->naddrs field of a data instruction. This is wrong as instructions + * might be split. + * + * Given an address instruction, returns the number of address cycle to issue. + */ +unsigned int nand_subop_get_num_addr_cyc(const struct nand_subop *subop, + unsigned int instr_idx) +{ + int start_off, end_off; + + if (WARN_ON(!nand_subop_instr_is_valid(subop, instr_idx) || + subop->instrs[instr_idx].type != NAND_OP_ADDR_INSTR)) + return 0; + + start_off = nand_subop_get_addr_start_off(subop, instr_idx); + + if (instr_idx == subop->ninstrs - 1 && + subop->last_instr_end_off) + end_off = subop->last_instr_end_off; + else + end_off = subop->instrs[instr_idx].ctx.addr.naddrs; + + return end_off - start_off; +} +EXPORT_SYMBOL_GPL(nand_subop_get_num_addr_cyc); + +/** + * nand_subop_get_data_start_off - Get the start offset in a data array + * @subop: The entire sub-operation + * @instr_idx: Index of the instruction inside the sub-operation + * + * During driver development, one could be tempted to directly use the + * ->data->buf.{in,out} field of data instructions. This is wrong as data + * instructions might be split. + * + * Given a data instruction, returns the offset to start from. + */ +unsigned int nand_subop_get_data_start_off(const struct nand_subop *subop, + unsigned int instr_idx) +{ + if (WARN_ON(!nand_subop_instr_is_valid(subop, instr_idx) || + !nand_instr_is_data(&subop->instrs[instr_idx]))) + return 0; + + return nand_subop_get_start_off(subop, instr_idx); +} +EXPORT_SYMBOL_GPL(nand_subop_get_data_start_off); + +/** + * nand_subop_get_data_len - Get the number of bytes to retrieve + * @subop: The entire sub-operation + * @instr_idx: Index of the instruction inside the sub-operation + * + * During driver development, one could be tempted to directly use the + * ->data->len field of a data instruction. This is wrong as data instructions + * might be split. + * + * Returns the length of the chunk of data to send/receive. + */ +unsigned int nand_subop_get_data_len(const struct nand_subop *subop, + unsigned int instr_idx) +{ + int start_off = 0, end_off; + + if (WARN_ON(!nand_subop_instr_is_valid(subop, instr_idx) || + !nand_instr_is_data(&subop->instrs[instr_idx]))) + return 0; + + start_off = nand_subop_get_data_start_off(subop, instr_idx); + + if (instr_idx == subop->ninstrs - 1 && + subop->last_instr_end_off) + end_off = subop->last_instr_end_off; + else + end_off = subop->instrs[instr_idx].ctx.data.len; + + return end_off - start_off; +} +EXPORT_SYMBOL_GPL(nand_subop_get_data_len); + +/** + * nand_reset - Reset and initialize a NAND device + * @chip: The NAND chip + * @chipnr: Internal die id + * + * Save the timings data structure, then apply SDR timings mode 0 (see + * nand_reset_interface for details), do the reset operation, and apply + * back the previous timings. + * + * Returns 0 on success, a negative error code otherwise. + */ +int nand_reset(struct nand_chip *chip, int chipnr) +{ + int ret; + + ret = nand_reset_interface(chip, chipnr); + if (ret) + return ret; + + /* + * The CS line has to be released before we can apply the new NAND + * interface settings, hence this weird nand_select_target() + * nand_deselect_target() dance. + */ + nand_select_target(chip, chipnr); + ret = nand_reset_op(chip); + nand_deselect_target(chip); + if (ret) + return ret; + + ret = nand_setup_interface(chip, chipnr); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(nand_reset); + +/** + * nand_get_features - wrapper to perform a GET_FEATURE + * @chip: NAND chip info structure + * @addr: feature address + * @subfeature_param: the subfeature parameters, a four bytes array + * + * Returns 0 for success, a negative error otherwise. Returns -ENOTSUPP if the + * operation cannot be handled. + */ +int nand_get_features(struct nand_chip *chip, int addr, + u8 *subfeature_param) +{ + if (!nand_supports_get_features(chip, addr)) + return -ENOTSUPP; + + if (chip->legacy.get_features) + return chip->legacy.get_features(chip, addr, subfeature_param); + + return nand_get_features_op(chip, addr, subfeature_param); +} + +/** + * nand_set_features - wrapper to perform a SET_FEATURE + * @chip: NAND chip info structure + * @addr: feature address + * @subfeature_param: the subfeature parameters, a four bytes array + * + * Returns 0 for success, a negative error otherwise. Returns -ENOTSUPP if the + * operation cannot be handled. + */ +int nand_set_features(struct nand_chip *chip, int addr, + u8 *subfeature_param) +{ + if (!nand_supports_set_features(chip, addr)) + return -ENOTSUPP; + + if (chip->legacy.set_features) + return chip->legacy.set_features(chip, addr, subfeature_param); + + return nand_set_features_op(chip, addr, subfeature_param); +} + +/** + * nand_read_page_raw_notsupp - dummy read raw page function + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read + * + * Returns -ENOTSUPP unconditionally. + */ +int nand_read_page_raw_notsupp(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + return -ENOTSUPP; +} + +/** + * nand_read_page_raw - [INTERN] read raw page data without ecc + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read + * + * Not for syndrome calculating ECC controllers, which use a special oob layout. + */ +int nand_read_page_raw(struct nand_chip *chip, uint8_t *buf, int oob_required, + int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + ret = nand_read_page_op(chip, page, 0, buf, mtd->writesize); + if (ret) + return ret; + + if (oob_required) { + ret = nand_read_data_op(chip, chip->oob_poi, mtd->oobsize, + false, false); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL(nand_read_page_raw); + +/** + * nand_monolithic_read_page_raw - Monolithic page read in raw mode + * @chip: NAND chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read + * + * This is a raw page read, ie. without any error detection/correction. + * Monolithic means we are requesting all the relevant data (main plus + * eventually OOB) to be loaded in the NAND cache and sent over the + * bus (from the NAND chip to the NAND controller) in a single + * operation. This is an alternative to nand_read_page_raw(), which + * first reads the main data, and if the OOB data is requested too, + * then reads more data on the bus. + */ +int nand_monolithic_read_page_raw(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int size = mtd->writesize; + u8 *read_buf = buf; + int ret; + + if (oob_required) { + size += mtd->oobsize; + + if (buf != chip->data_buf) + read_buf = nand_get_data_buf(chip); + } + + ret = nand_read_page_op(chip, page, 0, read_buf, size); + if (ret) + return ret; + + if (buf != chip->data_buf) + memcpy(buf, read_buf, mtd->writesize); + + return 0; +} +EXPORT_SYMBOL(nand_monolithic_read_page_raw); + +/** + * nand_read_page_raw_syndrome - [INTERN] read raw page data without ecc + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read + * + * We need a special oob layout and handling even when OOB isn't used. + */ +static int nand_read_page_raw_syndrome(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + uint8_t *oob = chip->oob_poi; + int steps, size, ret; + + ret = nand_read_page_op(chip, page, 0, NULL, 0); + if (ret) + return ret; + + for (steps = chip->ecc.steps; steps > 0; steps--) { + ret = nand_read_data_op(chip, buf, eccsize, false, false); + if (ret) + return ret; + + buf += eccsize; + + if (chip->ecc.prepad) { + ret = nand_read_data_op(chip, oob, chip->ecc.prepad, + false, false); + if (ret) + return ret; + + oob += chip->ecc.prepad; + } + + ret = nand_read_data_op(chip, oob, eccbytes, false, false); + if (ret) + return ret; + + oob += eccbytes; + + if (chip->ecc.postpad) { + ret = nand_read_data_op(chip, oob, chip->ecc.postpad, + false, false); + if (ret) + return ret; + + oob += chip->ecc.postpad; + } + } + + size = mtd->oobsize - (oob - chip->oob_poi); + if (size) { + ret = nand_read_data_op(chip, oob, size, false, false); + if (ret) + return ret; + } + + return 0; +} + +/** + * nand_read_page_swecc - [REPLACEABLE] software ECC based page read function + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read + */ +static int nand_read_page_swecc(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int i, eccsize = chip->ecc.size, ret; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + uint8_t *p = buf; + uint8_t *ecc_calc = chip->ecc.calc_buf; + uint8_t *ecc_code = chip->ecc.code_buf; + unsigned int max_bitflips = 0; + + chip->ecc.read_page_raw(chip, buf, 1, page); + + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) + chip->ecc.calculate(chip, p, &ecc_calc[i]); + + ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, chip->oob_poi, 0, + chip->ecc.total); + if (ret) + return ret; + + eccsteps = chip->ecc.steps; + p = buf; + + for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { + int stat; + + stat = chip->ecc.correct(chip, p, &ecc_code[i], &ecc_calc[i]); + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + return max_bitflips; +} + +/** + * nand_read_subpage - [REPLACEABLE] ECC based sub-page read function + * @chip: nand chip info structure + * @data_offs: offset of requested data within the page + * @readlen: data length + * @bufpoi: buffer to store read data + * @page: page number to read + */ +static int nand_read_subpage(struct nand_chip *chip, uint32_t data_offs, + uint32_t readlen, uint8_t *bufpoi, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int start_step, end_step, num_steps, ret; + uint8_t *p; + int data_col_addr, i, gaps = 0; + int datafrag_len, eccfrag_len, aligned_len, aligned_pos; + int busw = (chip->options & NAND_BUSWIDTH_16) ? 2 : 1; + int index, section = 0; + unsigned int max_bitflips = 0; + struct mtd_oob_region oobregion = { }; + + /* Column address within the page aligned to ECC size (256bytes) */ + start_step = data_offs / chip->ecc.size; + end_step = (data_offs + readlen - 1) / chip->ecc.size; + num_steps = end_step - start_step + 1; + index = start_step * chip->ecc.bytes; + + /* Data size aligned to ECC ecc.size */ + datafrag_len = num_steps * chip->ecc.size; + eccfrag_len = num_steps * chip->ecc.bytes; + + data_col_addr = start_step * chip->ecc.size; + /* If we read not a page aligned data */ + p = bufpoi + data_col_addr; + ret = nand_read_page_op(chip, page, data_col_addr, p, datafrag_len); + if (ret) + return ret; + + /* Calculate ECC */ + for (i = 0; i < eccfrag_len ; i += chip->ecc.bytes, p += chip->ecc.size) + chip->ecc.calculate(chip, p, &chip->ecc.calc_buf[i]); + + /* + * The performance is faster if we position offsets according to + * ecc.pos. Let's make sure that there are no gaps in ECC positions. + */ + ret = mtd_ooblayout_find_eccregion(mtd, index, §ion, &oobregion); + if (ret) + return ret; + + if (oobregion.length < eccfrag_len) + gaps = 1; + + if (gaps) { + ret = nand_change_read_column_op(chip, mtd->writesize, + chip->oob_poi, mtd->oobsize, + false); + if (ret) + return ret; + } else { + /* + * Send the command to read the particular ECC bytes take care + * about buswidth alignment in read_buf. + */ + aligned_pos = oobregion.offset & ~(busw - 1); + aligned_len = eccfrag_len; + if (oobregion.offset & (busw - 1)) + aligned_len++; + if ((oobregion.offset + (num_steps * chip->ecc.bytes)) & + (busw - 1)) + aligned_len++; + + ret = nand_change_read_column_op(chip, + mtd->writesize + aligned_pos, + &chip->oob_poi[aligned_pos], + aligned_len, false); + if (ret) + return ret; + } + + ret = mtd_ooblayout_get_eccbytes(mtd, chip->ecc.code_buf, + chip->oob_poi, index, eccfrag_len); + if (ret) + return ret; + + p = bufpoi + data_col_addr; + for (i = 0; i < eccfrag_len ; i += chip->ecc.bytes, p += chip->ecc.size) { + int stat; + + stat = chip->ecc.correct(chip, p, &chip->ecc.code_buf[i], + &chip->ecc.calc_buf[i]); + if (stat == -EBADMSG && + (chip->ecc.options & NAND_ECC_GENERIC_ERASED_CHECK)) { + /* check for empty pages with bitflips */ + stat = nand_check_erased_ecc_chunk(p, chip->ecc.size, + &chip->ecc.code_buf[i], + chip->ecc.bytes, + NULL, 0, + chip->ecc.strength); + } + + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + return max_bitflips; +} + +/** + * nand_read_page_hwecc - [REPLACEABLE] hardware ECC based page read function + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read + * + * Not for syndrome calculating ECC controllers which need a special oob layout. + */ +static int nand_read_page_hwecc(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int i, eccsize = chip->ecc.size, ret; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + uint8_t *p = buf; + uint8_t *ecc_calc = chip->ecc.calc_buf; + uint8_t *ecc_code = chip->ecc.code_buf; + unsigned int max_bitflips = 0; + + ret = nand_read_page_op(chip, page, 0, NULL, 0); + if (ret) + return ret; + + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { + chip->ecc.hwctl(chip, NAND_ECC_READ); + + ret = nand_read_data_op(chip, p, eccsize, false, false); + if (ret) + return ret; + + chip->ecc.calculate(chip, p, &ecc_calc[i]); + } + + ret = nand_read_data_op(chip, chip->oob_poi, mtd->oobsize, false, + false); + if (ret) + return ret; + + ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, chip->oob_poi, 0, + chip->ecc.total); + if (ret) + return ret; + + eccsteps = chip->ecc.steps; + p = buf; + + for (i = 0 ; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { + int stat; + + stat = chip->ecc.correct(chip, p, &ecc_code[i], &ecc_calc[i]); + if (stat == -EBADMSG && + (chip->ecc.options & NAND_ECC_GENERIC_ERASED_CHECK)) { + /* check for empty pages with bitflips */ + stat = nand_check_erased_ecc_chunk(p, eccsize, + &ecc_code[i], eccbytes, + NULL, 0, + chip->ecc.strength); + } + + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + return max_bitflips; +} + +/** + * nand_read_page_hwecc_oob_first - Hardware ECC page read with ECC + * data read from OOB area + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read + * + * Hardware ECC for large page chips, which requires the ECC data to be + * extracted from the OOB before the actual data is read. + */ +int nand_read_page_hwecc_oob_first(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int i, eccsize = chip->ecc.size, ret; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + uint8_t *p = buf; + uint8_t *ecc_code = chip->ecc.code_buf; + unsigned int max_bitflips = 0; + + /* Read the OOB area first */ + ret = nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize); + if (ret) + return ret; + + /* Move read cursor to start of page */ + ret = nand_change_read_column_op(chip, 0, NULL, 0, false); + if (ret) + return ret; + + ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, chip->oob_poi, 0, + chip->ecc.total); + if (ret) + return ret; + + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { + int stat; + + chip->ecc.hwctl(chip, NAND_ECC_READ); + + ret = nand_read_data_op(chip, p, eccsize, false, false); + if (ret) + return ret; + + stat = chip->ecc.correct(chip, p, &ecc_code[i], NULL); + if (stat == -EBADMSG && + (chip->ecc.options & NAND_ECC_GENERIC_ERASED_CHECK)) { + /* check for empty pages with bitflips */ + stat = nand_check_erased_ecc_chunk(p, eccsize, + &ecc_code[i], + eccbytes, NULL, 0, + chip->ecc.strength); + } + + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + return max_bitflips; +} +EXPORT_SYMBOL_GPL(nand_read_page_hwecc_oob_first); + +/** + * nand_read_page_syndrome - [REPLACEABLE] hardware ECC syndrome based page read + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read + * + * The hw generator calculates the error syndrome automatically. Therefore we + * need a special oob layout and handling. + */ +static int nand_read_page_syndrome(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret, i, eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + int eccpadbytes = eccbytes + chip->ecc.prepad + chip->ecc.postpad; + uint8_t *p = buf; + uint8_t *oob = chip->oob_poi; + unsigned int max_bitflips = 0; + + ret = nand_read_page_op(chip, page, 0, NULL, 0); + if (ret) + return ret; + + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { + int stat; + + chip->ecc.hwctl(chip, NAND_ECC_READ); + + ret = nand_read_data_op(chip, p, eccsize, false, false); + if (ret) + return ret; + + if (chip->ecc.prepad) { + ret = nand_read_data_op(chip, oob, chip->ecc.prepad, + false, false); + if (ret) + return ret; + + oob += chip->ecc.prepad; + } + + chip->ecc.hwctl(chip, NAND_ECC_READSYN); + + ret = nand_read_data_op(chip, oob, eccbytes, false, false); + if (ret) + return ret; + + stat = chip->ecc.correct(chip, p, oob, NULL); + + oob += eccbytes; + + if (chip->ecc.postpad) { + ret = nand_read_data_op(chip, oob, chip->ecc.postpad, + false, false); + if (ret) + return ret; + + oob += chip->ecc.postpad; + } + + if (stat == -EBADMSG && + (chip->ecc.options & NAND_ECC_GENERIC_ERASED_CHECK)) { + /* check for empty pages with bitflips */ + stat = nand_check_erased_ecc_chunk(p, chip->ecc.size, + oob - eccpadbytes, + eccpadbytes, + NULL, 0, + chip->ecc.strength); + } + + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + + /* Calculate remaining oob bytes */ + i = mtd->oobsize - (oob - chip->oob_poi); + if (i) { + ret = nand_read_data_op(chip, oob, i, false, false); + if (ret) + return ret; + } + + return max_bitflips; +} + +/** + * nand_transfer_oob - [INTERN] Transfer oob to client buffer + * @chip: NAND chip object + * @oob: oob destination address + * @ops: oob ops structure + * @len: size of oob to transfer + */ +static uint8_t *nand_transfer_oob(struct nand_chip *chip, uint8_t *oob, + struct mtd_oob_ops *ops, size_t len) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + switch (ops->mode) { + + case MTD_OPS_PLACE_OOB: + case MTD_OPS_RAW: + memcpy(oob, chip->oob_poi + ops->ooboffs, len); + return oob + len; + + case MTD_OPS_AUTO_OOB: + ret = mtd_ooblayout_get_databytes(mtd, oob, chip->oob_poi, + ops->ooboffs, len); + BUG_ON(ret); + return oob + len; + + default: + BUG(); + } + return NULL; +} + +static void rawnand_enable_cont_reads(struct nand_chip *chip, unsigned int page, + u32 readlen, int col) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int first_page, last_page; + + chip->cont_read.ongoing = false; + + if (!chip->controller->supported_op.cont_read) + return; + + /* + * Don't bother making any calculations if the length is too small. + * Side effect: avoids possible integer underflows below. + */ + if (readlen < (2 * mtd->writesize)) + return; + + /* Derive the page where continuous read should start (the first full page read) */ + first_page = page; + if (col) + first_page++; + + /* Derive the page where continuous read should stop (the last full page read) */ + last_page = page + ((col + readlen) / mtd->writesize) - 1; + + /* Configure and enable continuous read when suitable */ + if (first_page < last_page) { + chip->cont_read.first_page = first_page; + chip->cont_read.last_page = last_page; + chip->cont_read.ongoing = true; + /* May reset the ongoing flag */ + rawnand_cap_cont_reads(chip); + } +} + +static void rawnand_cont_read_skip_first_page(struct nand_chip *chip, unsigned int page) +{ + if (!chip->cont_read.ongoing || page != chip->cont_read.first_page) + return; + + chip->cont_read.first_page++; + rawnand_cap_cont_reads(chip); +} + +/** + * nand_setup_read_retry - [INTERN] Set the READ RETRY mode + * @chip: NAND chip object + * @retry_mode: the retry mode to use + * + * Some vendors supply a special command to shift the Vt threshold, to be used + * when there are too many bitflips in a page (i.e., ECC error). After setting + * a new threshold, the host should retry reading the page. + */ +static int nand_setup_read_retry(struct nand_chip *chip, int retry_mode) +{ + pr_debug("setting READ RETRY mode %d\n", retry_mode); + + if (retry_mode >= chip->read_retries) + return -EINVAL; + + if (!chip->ops.setup_read_retry) + return -EOPNOTSUPP; + + return chip->ops.setup_read_retry(chip, retry_mode); +} + +static void nand_wait_readrdy(struct nand_chip *chip) +{ + const struct nand_interface_config *conf; + + if (!(chip->options & NAND_NEED_READRDY)) + return; + + conf = nand_get_interface_config(chip); + WARN_ON(nand_wait_rdy_op(chip, NAND_COMMON_TIMING_MS(conf, tR_max), 0)); +} + +/** + * nand_do_read_ops - [INTERN] Read data with ECC + * @chip: NAND chip object + * @from: offset to read from + * @ops: oob ops structure + * + * Internal function. Called with chip held. + */ +static int nand_do_read_ops(struct nand_chip *chip, loff_t from, + struct mtd_oob_ops *ops) +{ + int chipnr, page, realpage, col, bytes, aligned, oob_required; + struct mtd_info *mtd = nand_to_mtd(chip); + int ret = 0; + uint32_t readlen = ops->len; + uint32_t oobreadlen = ops->ooblen; + uint32_t max_oobsize = mtd_oobavail(mtd, ops); + + uint8_t *bufpoi, *oob, *buf; + int use_bounce_buf; + unsigned int max_bitflips = 0; + int retry_mode = 0; + bool ecc_fail = false; + + /* Check if the region is secured */ + if (nand_region_is_secured(chip, from, readlen)) + return -EIO; + + chipnr = (int)(from >> chip->chip_shift); + nand_select_target(chip, chipnr); + + realpage = (int)(from >> chip->page_shift); + page = realpage & chip->pagemask; + + col = (int)(from & (mtd->writesize - 1)); + + buf = ops->datbuf; + oob = ops->oobbuf; + oob_required = oob ? 1 : 0; + + if (likely(ops->mode != MTD_OPS_RAW)) + rawnand_enable_cont_reads(chip, page, readlen, col); + + while (1) { + struct mtd_ecc_stats ecc_stats = mtd->ecc_stats; + + bytes = min(mtd->writesize - col, readlen); + aligned = (bytes == mtd->writesize); + + if (!aligned) + use_bounce_buf = 1; + else if (chip->options & NAND_USES_DMA) + use_bounce_buf = !virt_addr_valid(buf) || + !IS_ALIGNED((unsigned long)buf, + chip->buf_align); + else + use_bounce_buf = 0; + + /* Is the current page in the buffer? */ + if (realpage != chip->pagecache.page || oob) { + bufpoi = use_bounce_buf ? chip->data_buf : buf; + + if (use_bounce_buf && aligned) + pr_debug("%s: using read bounce buffer for buf@%p\n", + __func__, buf); + +read_retry: + /* + * Now read the page into the buffer. Absent an error, + * the read methods return max bitflips per ecc step. + */ + if (unlikely(ops->mode == MTD_OPS_RAW)) + ret = chip->ecc.read_page_raw(chip, bufpoi, + oob_required, + page); + else if (!aligned && NAND_HAS_SUBPAGE_READ(chip) && + !oob) + ret = chip->ecc.read_subpage(chip, col, bytes, + bufpoi, page); + else + ret = chip->ecc.read_page(chip, bufpoi, + oob_required, page); + if (ret < 0) { + if (use_bounce_buf) + /* Invalidate page cache */ + chip->pagecache.page = -1; + break; + } + + /* + * Copy back the data in the initial buffer when reading + * partial pages or when a bounce buffer is required. + */ + if (use_bounce_buf) { + if (!NAND_HAS_SUBPAGE_READ(chip) && !oob && + !(mtd->ecc_stats.failed - ecc_stats.failed) && + (ops->mode != MTD_OPS_RAW)) { + chip->pagecache.page = realpage; + chip->pagecache.bitflips = ret; + } else { + /* Invalidate page cache */ + chip->pagecache.page = -1; + } + memcpy(buf, bufpoi + col, bytes); + } + + if (unlikely(oob)) { + int toread = min(oobreadlen, max_oobsize); + + if (toread) { + oob = nand_transfer_oob(chip, oob, ops, + toread); + oobreadlen -= toread; + } + } + + nand_wait_readrdy(chip); + + if (mtd->ecc_stats.failed - ecc_stats.failed) { + if (retry_mode + 1 < chip->read_retries) { + retry_mode++; + ret = nand_setup_read_retry(chip, + retry_mode); + if (ret < 0) + break; + + /* Reset ecc_stats; retry */ + mtd->ecc_stats = ecc_stats; + goto read_retry; + } else { + /* No more retry modes; real failure */ + ecc_fail = true; + } + } + + buf += bytes; + max_bitflips = max_t(unsigned int, max_bitflips, ret); + } else { + memcpy(buf, chip->data_buf + col, bytes); + buf += bytes; + max_bitflips = max_t(unsigned int, max_bitflips, + chip->pagecache.bitflips); + + rawnand_cont_read_skip_first_page(chip, page); + } + + readlen -= bytes; + + /* Reset to retry mode 0 */ + if (retry_mode) { + ret = nand_setup_read_retry(chip, 0); + if (ret < 0) + break; + retry_mode = 0; + } + + if (!readlen) + break; + + /* For subsequent reads align to page boundary */ + col = 0; + /* Increment page address */ + realpage++; + + page = realpage & chip->pagemask; + /* Check, if we cross a chip boundary */ + if (!page) { + chipnr++; + nand_deselect_target(chip); + nand_select_target(chip, chipnr); + } + } + nand_deselect_target(chip); + + if (WARN_ON_ONCE(chip->cont_read.ongoing)) + chip->cont_read.ongoing = false; + + ops->retlen = ops->len - (size_t) readlen; + if (oob) + ops->oobretlen = ops->ooblen - oobreadlen; + + if (ret < 0) + return ret; + + if (ecc_fail) + return -EBADMSG; + + return max_bitflips; +} + +/** + * nand_read_oob_std - [REPLACEABLE] the most common OOB data read function + * @chip: nand chip info structure + * @page: page number to read + */ +int nand_read_oob_std(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize); +} +EXPORT_SYMBOL(nand_read_oob_std); + +/** + * nand_read_oob_syndrome - [REPLACEABLE] OOB data read function for HW ECC + * with syndromes + * @chip: nand chip info structure + * @page: page number to read + */ +static int nand_read_oob_syndrome(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int length = mtd->oobsize; + int chunk = chip->ecc.bytes + chip->ecc.prepad + chip->ecc.postpad; + int eccsize = chip->ecc.size; + uint8_t *bufpoi = chip->oob_poi; + int i, toread, sndrnd = 0, pos, ret; + + ret = nand_read_page_op(chip, page, chip->ecc.size, NULL, 0); + if (ret) + return ret; + + for (i = 0; i < chip->ecc.steps; i++) { + if (sndrnd) { + int ret; + + pos = eccsize + i * (eccsize + chunk); + if (mtd->writesize > 512) + ret = nand_change_read_column_op(chip, pos, + NULL, 0, + false); + else + ret = nand_read_page_op(chip, page, pos, NULL, + 0); + + if (ret) + return ret; + } else + sndrnd = 1; + toread = min_t(int, length, chunk); + + ret = nand_read_data_op(chip, bufpoi, toread, false, false); + if (ret) + return ret; + + bufpoi += toread; + length -= toread; + } + if (length > 0) { + ret = nand_read_data_op(chip, bufpoi, length, false, false); + if (ret) + return ret; + } + + return 0; +} + +/** + * nand_write_oob_std - [REPLACEABLE] the most common OOB data write function + * @chip: nand chip info structure + * @page: page number to write + */ +int nand_write_oob_std(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + return nand_prog_page_op(chip, page, mtd->writesize, chip->oob_poi, + mtd->oobsize); +} +EXPORT_SYMBOL(nand_write_oob_std); + +/** + * nand_write_oob_syndrome - [REPLACEABLE] OOB data write function for HW ECC + * with syndrome - only for large page flash + * @chip: nand chip info structure + * @page: page number to write + */ +static int nand_write_oob_syndrome(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int chunk = chip->ecc.bytes + chip->ecc.prepad + chip->ecc.postpad; + int eccsize = chip->ecc.size, length = mtd->oobsize; + int ret, i, len, pos, sndcmd = 0, steps = chip->ecc.steps; + const uint8_t *bufpoi = chip->oob_poi; + + /* + * data-ecc-data-ecc ... ecc-oob + * or + * data-pad-ecc-pad-data-pad .... ecc-pad-oob + */ + if (!chip->ecc.prepad && !chip->ecc.postpad) { + pos = steps * (eccsize + chunk); + steps = 0; + } else + pos = eccsize; + + ret = nand_prog_page_begin_op(chip, page, pos, NULL, 0); + if (ret) + return ret; + + for (i = 0; i < steps; i++) { + if (sndcmd) { + if (mtd->writesize <= 512) { + uint32_t fill = 0xFFFFFFFF; + + len = eccsize; + while (len > 0) { + int num = min_t(int, len, 4); + + ret = nand_write_data_op(chip, &fill, + num, false); + if (ret) + return ret; + + len -= num; + } + } else { + pos = eccsize + i * (eccsize + chunk); + ret = nand_change_write_column_op(chip, pos, + NULL, 0, + false); + if (ret) + return ret; + } + } else + sndcmd = 1; + len = min_t(int, length, chunk); + + ret = nand_write_data_op(chip, bufpoi, len, false); + if (ret) + return ret; + + bufpoi += len; + length -= len; + } + if (length > 0) { + ret = nand_write_data_op(chip, bufpoi, length, false); + if (ret) + return ret; + } + + return nand_prog_page_end_op(chip); +} + +/** + * nand_do_read_oob - [INTERN] NAND read out-of-band + * @chip: NAND chip object + * @from: offset to read from + * @ops: oob operations description structure + * + * NAND read out-of-band data from the spare area. + */ +static int nand_do_read_oob(struct nand_chip *chip, loff_t from, + struct mtd_oob_ops *ops) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int max_bitflips = 0; + int page, realpage, chipnr; + struct mtd_ecc_stats stats; + int readlen = ops->ooblen; + int len; + uint8_t *buf = ops->oobbuf; + int ret = 0; + + pr_debug("%s: from = 0x%08Lx, len = %i\n", + __func__, (unsigned long long)from, readlen); + + /* Check if the region is secured */ + if (nand_region_is_secured(chip, from, readlen)) + return -EIO; + + stats = mtd->ecc_stats; + + len = mtd_oobavail(mtd, ops); + + chipnr = (int)(from >> chip->chip_shift); + nand_select_target(chip, chipnr); + + /* Shift to get page */ + realpage = (int)(from >> chip->page_shift); + page = realpage & chip->pagemask; + + while (1) { + if (ops->mode == MTD_OPS_RAW) + ret = chip->ecc.read_oob_raw(chip, page); + else + ret = chip->ecc.read_oob(chip, page); + + if (ret < 0) + break; + + len = min(len, readlen); + buf = nand_transfer_oob(chip, buf, ops, len); + + nand_wait_readrdy(chip); + + max_bitflips = max_t(unsigned int, max_bitflips, ret); + + readlen -= len; + if (!readlen) + break; + + /* Increment page address */ + realpage++; + + page = realpage & chip->pagemask; + /* Check, if we cross a chip boundary */ + if (!page) { + chipnr++; + nand_deselect_target(chip); + nand_select_target(chip, chipnr); + } + } + nand_deselect_target(chip); + + ops->oobretlen = ops->ooblen - readlen; + + if (ret < 0) + return ret; + + if (mtd->ecc_stats.failed - stats.failed) + return -EBADMSG; + + return max_bitflips; +} + +/** + * nand_read_oob - [MTD Interface] NAND read data and/or out-of-band + * @mtd: MTD device structure + * @from: offset to read from + * @ops: oob operation description structure + * + * NAND read data and/or out-of-band data. + */ +static int nand_read_oob(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct mtd_ecc_stats old_stats; + int ret; + + ops->retlen = 0; + + if (ops->mode != MTD_OPS_PLACE_OOB && + ops->mode != MTD_OPS_AUTO_OOB && + ops->mode != MTD_OPS_RAW) + return -ENOTSUPP; + + nand_get_device(chip); + + old_stats = mtd->ecc_stats; + + if (!ops->datbuf) + ret = nand_do_read_oob(chip, from, ops); + else + ret = nand_do_read_ops(chip, from, ops); + + if (ops->stats) { + ops->stats->uncorrectable_errors += + mtd->ecc_stats.failed - old_stats.failed; + ops->stats->corrected_bitflips += + mtd->ecc_stats.corrected - old_stats.corrected; + } + + nand_release_device(chip); + return ret; +} + +/** + * nand_write_page_raw_notsupp - dummy raw page write function + * @chip: nand chip info structure + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB + * @page: page number to write + * + * Returns -ENOTSUPP unconditionally. + */ +int nand_write_page_raw_notsupp(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + return -ENOTSUPP; +} + +/** + * nand_write_page_raw - [INTERN] raw page write function + * @chip: nand chip info structure + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB + * @page: page number to write + * + * Not for syndrome calculating ECC controllers, which use a special oob layout. + */ +int nand_write_page_raw(struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + ret = nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize); + if (ret) + return ret; + + if (oob_required) { + ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, + false); + if (ret) + return ret; + } + + return nand_prog_page_end_op(chip); +} +EXPORT_SYMBOL(nand_write_page_raw); + +/** + * nand_monolithic_write_page_raw - Monolithic page write in raw mode + * @chip: NAND chip info structure + * @buf: data buffer to write + * @oob_required: must write chip->oob_poi to OOB + * @page: page number to write + * + * This is a raw page write, ie. without any error detection/correction. + * Monolithic means we are requesting all the relevant data (main plus + * eventually OOB) to be sent over the bus and effectively programmed + * into the NAND chip arrays in a single operation. This is an + * alternative to nand_write_page_raw(), which first sends the main + * data, then eventually send the OOB data by latching more data + * cycles on the NAND bus, and finally sends the program command to + * synchronyze the NAND chip cache. + */ +int nand_monolithic_write_page_raw(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int size = mtd->writesize; + u8 *write_buf = (u8 *)buf; + + if (oob_required) { + size += mtd->oobsize; + + if (buf != chip->data_buf) { + write_buf = nand_get_data_buf(chip); + memcpy(write_buf, buf, mtd->writesize); + } + } + + return nand_prog_page_op(chip, page, 0, write_buf, size); +} +EXPORT_SYMBOL(nand_monolithic_write_page_raw); + +/** + * nand_write_page_raw_syndrome - [INTERN] raw page write function + * @chip: nand chip info structure + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB + * @page: page number to write + * + * We need a special oob layout and handling even when ECC isn't checked. + */ +static int nand_write_page_raw_syndrome(struct nand_chip *chip, + const uint8_t *buf, int oob_required, + int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + uint8_t *oob = chip->oob_poi; + int steps, size, ret; + + ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0); + if (ret) + return ret; + + for (steps = chip->ecc.steps; steps > 0; steps--) { + ret = nand_write_data_op(chip, buf, eccsize, false); + if (ret) + return ret; + + buf += eccsize; + + if (chip->ecc.prepad) { + ret = nand_write_data_op(chip, oob, chip->ecc.prepad, + false); + if (ret) + return ret; + + oob += chip->ecc.prepad; + } + + ret = nand_write_data_op(chip, oob, eccbytes, false); + if (ret) + return ret; + + oob += eccbytes; + + if (chip->ecc.postpad) { + ret = nand_write_data_op(chip, oob, chip->ecc.postpad, + false); + if (ret) + return ret; + + oob += chip->ecc.postpad; + } + } + + size = mtd->oobsize - (oob - chip->oob_poi); + if (size) { + ret = nand_write_data_op(chip, oob, size, false); + if (ret) + return ret; + } + + return nand_prog_page_end_op(chip); +} +/** + * nand_write_page_swecc - [REPLACEABLE] software ECC based page write function + * @chip: nand chip info structure + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB + * @page: page number to write + */ +static int nand_write_page_swecc(struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int i, eccsize = chip->ecc.size, ret; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + uint8_t *ecc_calc = chip->ecc.calc_buf; + const uint8_t *p = buf; + + /* Software ECC calculation */ + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) + chip->ecc.calculate(chip, p, &ecc_calc[i]); + + ret = mtd_ooblayout_set_eccbytes(mtd, ecc_calc, chip->oob_poi, 0, + chip->ecc.total); + if (ret) + return ret; + + return chip->ecc.write_page_raw(chip, buf, 1, page); +} + +/** + * nand_write_page_hwecc - [REPLACEABLE] hardware ECC based page write function + * @chip: nand chip info structure + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB + * @page: page number to write + */ +static int nand_write_page_hwecc(struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int i, eccsize = chip->ecc.size, ret; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + uint8_t *ecc_calc = chip->ecc.calc_buf; + const uint8_t *p = buf; + + ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0); + if (ret) + return ret; + + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { + chip->ecc.hwctl(chip, NAND_ECC_WRITE); + + ret = nand_write_data_op(chip, p, eccsize, false); + if (ret) + return ret; + + chip->ecc.calculate(chip, p, &ecc_calc[i]); + } + + ret = mtd_ooblayout_set_eccbytes(mtd, ecc_calc, chip->oob_poi, 0, + chip->ecc.total); + if (ret) + return ret; + + ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false); + if (ret) + return ret; + + return nand_prog_page_end_op(chip); +} + + +/** + * nand_write_subpage_hwecc - [REPLACEABLE] hardware ECC based subpage write + * @chip: nand chip info structure + * @offset: column address of subpage within the page + * @data_len: data length + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB + * @page: page number to write + */ +static int nand_write_subpage_hwecc(struct nand_chip *chip, uint32_t offset, + uint32_t data_len, const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + uint8_t *oob_buf = chip->oob_poi; + uint8_t *ecc_calc = chip->ecc.calc_buf; + int ecc_size = chip->ecc.size; + int ecc_bytes = chip->ecc.bytes; + int ecc_steps = chip->ecc.steps; + uint32_t start_step = offset / ecc_size; + uint32_t end_step = (offset + data_len - 1) / ecc_size; + int oob_bytes = mtd->oobsize / ecc_steps; + int step, ret; + + ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0); + if (ret) + return ret; + + for (step = 0; step < ecc_steps; step++) { + /* configure controller for WRITE access */ + chip->ecc.hwctl(chip, NAND_ECC_WRITE); + + /* write data (untouched subpages already masked by 0xFF) */ + ret = nand_write_data_op(chip, buf, ecc_size, false); + if (ret) + return ret; + + /* mask ECC of un-touched subpages by padding 0xFF */ + if ((step < start_step) || (step > end_step)) + memset(ecc_calc, 0xff, ecc_bytes); + else + chip->ecc.calculate(chip, buf, ecc_calc); + + /* mask OOB of un-touched subpages by padding 0xFF */ + /* if oob_required, preserve OOB metadata of written subpage */ + if (!oob_required || (step < start_step) || (step > end_step)) + memset(oob_buf, 0xff, oob_bytes); + + buf += ecc_size; + ecc_calc += ecc_bytes; + oob_buf += oob_bytes; + } + + /* copy calculated ECC for whole page to chip->buffer->oob */ + /* this include masked-value(0xFF) for unwritten subpages */ + ecc_calc = chip->ecc.calc_buf; + ret = mtd_ooblayout_set_eccbytes(mtd, ecc_calc, chip->oob_poi, 0, + chip->ecc.total); + if (ret) + return ret; + + /* write OOB buffer to NAND device */ + ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false); + if (ret) + return ret; + + return nand_prog_page_end_op(chip); +} + + +/** + * nand_write_page_syndrome - [REPLACEABLE] hardware ECC syndrome based page write + * @chip: nand chip info structure + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB + * @page: page number to write + * + * The hw generator calculates the error syndrome automatically. Therefore we + * need a special oob layout and handling. + */ +static int nand_write_page_syndrome(struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int i, eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + const uint8_t *p = buf; + uint8_t *oob = chip->oob_poi; + int ret; + + ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0); + if (ret) + return ret; + + for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) { + chip->ecc.hwctl(chip, NAND_ECC_WRITE); + + ret = nand_write_data_op(chip, p, eccsize, false); + if (ret) + return ret; + + if (chip->ecc.prepad) { + ret = nand_write_data_op(chip, oob, chip->ecc.prepad, + false); + if (ret) + return ret; + + oob += chip->ecc.prepad; + } + + chip->ecc.calculate(chip, p, oob); + + ret = nand_write_data_op(chip, oob, eccbytes, false); + if (ret) + return ret; + + oob += eccbytes; + + if (chip->ecc.postpad) { + ret = nand_write_data_op(chip, oob, chip->ecc.postpad, + false); + if (ret) + return ret; + + oob += chip->ecc.postpad; + } + } + + /* Calculate remaining oob bytes */ + i = mtd->oobsize - (oob - chip->oob_poi); + if (i) { + ret = nand_write_data_op(chip, oob, i, false); + if (ret) + return ret; + } + + return nand_prog_page_end_op(chip); +} + +/** + * nand_write_page - write one page + * @chip: NAND chip descriptor + * @offset: address offset within the page + * @data_len: length of actual data to be written + * @buf: the data to write + * @oob_required: must write chip->oob_poi to OOB + * @page: page number to write + * @raw: use _raw version of write_page + */ +static int nand_write_page(struct nand_chip *chip, uint32_t offset, + int data_len, const uint8_t *buf, int oob_required, + int page, int raw) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int status, subpage; + + if (!(chip->options & NAND_NO_SUBPAGE_WRITE) && + chip->ecc.write_subpage) + subpage = offset || (data_len < mtd->writesize); + else + subpage = 0; + + if (unlikely(raw)) + status = chip->ecc.write_page_raw(chip, buf, oob_required, + page); + else if (subpage) + status = chip->ecc.write_subpage(chip, offset, data_len, buf, + oob_required, page); + else + status = chip->ecc.write_page(chip, buf, oob_required, page); + + if (status < 0) + return status; + + return 0; +} + +#define NOTALIGNED(x) ((x & (chip->subpagesize - 1)) != 0) + +/** + * nand_do_write_ops - [INTERN] NAND write with ECC + * @chip: NAND chip object + * @to: offset to write to + * @ops: oob operations description structure + * + * NAND write with ECC. + */ +static int nand_do_write_ops(struct nand_chip *chip, loff_t to, + struct mtd_oob_ops *ops) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int chipnr, realpage, page, column; + uint32_t writelen = ops->len; + + uint32_t oobwritelen = ops->ooblen; + uint32_t oobmaxlen = mtd_oobavail(mtd, ops); + + uint8_t *oob = ops->oobbuf; + uint8_t *buf = ops->datbuf; + int ret; + int oob_required = oob ? 1 : 0; + + ops->retlen = 0; + if (!writelen) + return 0; + + /* Reject writes, which are not page aligned */ + if (NOTALIGNED(to) || NOTALIGNED(ops->len)) { + pr_notice("%s: attempt to write non page aligned data\n", + __func__); + return -EINVAL; + } + + /* Check if the region is secured */ + if (nand_region_is_secured(chip, to, writelen)) + return -EIO; + + column = to & (mtd->writesize - 1); + + chipnr = (int)(to >> chip->chip_shift); + nand_select_target(chip, chipnr); + + /* Check, if it is write protected */ + if (nand_check_wp(chip)) { + ret = -EIO; + goto err_out; + } + + realpage = (int)(to >> chip->page_shift); + page = realpage & chip->pagemask; + + /* Invalidate the page cache, when we write to the cached page */ + if (to <= ((loff_t)chip->pagecache.page << chip->page_shift) && + ((loff_t)chip->pagecache.page << chip->page_shift) < (to + ops->len)) + chip->pagecache.page = -1; + + /* Don't allow multipage oob writes with offset */ + if (oob && ops->ooboffs && (ops->ooboffs + ops->ooblen > oobmaxlen)) { + ret = -EINVAL; + goto err_out; + } + + while (1) { + int bytes = mtd->writesize; + uint8_t *wbuf = buf; + int use_bounce_buf; + int part_pagewr = (column || writelen < mtd->writesize); + + if (part_pagewr) + use_bounce_buf = 1; + else if (chip->options & NAND_USES_DMA) + use_bounce_buf = !virt_addr_valid(buf) || + !IS_ALIGNED((unsigned long)buf, + chip->buf_align); + else + use_bounce_buf = 0; + + /* + * Copy the data from the initial buffer when doing partial page + * writes or when a bounce buffer is required. + */ + if (use_bounce_buf) { + pr_debug("%s: using write bounce buffer for buf@%p\n", + __func__, buf); + if (part_pagewr) + bytes = min_t(int, bytes - column, writelen); + wbuf = nand_get_data_buf(chip); + memset(wbuf, 0xff, mtd->writesize); + memcpy(&wbuf[column], buf, bytes); + } + + if (unlikely(oob)) { + size_t len = min(oobwritelen, oobmaxlen); + oob = nand_fill_oob(chip, oob, len, ops); + oobwritelen -= len; + } else { + /* We still need to erase leftover OOB data */ + memset(chip->oob_poi, 0xff, mtd->oobsize); + } + + ret = nand_write_page(chip, column, bytes, wbuf, + oob_required, page, + (ops->mode == MTD_OPS_RAW)); + if (ret) + break; + + writelen -= bytes; + if (!writelen) + break; + + column = 0; + buf += bytes; + realpage++; + + page = realpage & chip->pagemask; + /* Check, if we cross a chip boundary */ + if (!page) { + chipnr++; + nand_deselect_target(chip); + nand_select_target(chip, chipnr); + } + } + + ops->retlen = ops->len - writelen; + if (unlikely(oob)) + ops->oobretlen = ops->ooblen; + +err_out: + nand_deselect_target(chip); + return ret; +} + +/** + * panic_nand_write - [MTD Interface] NAND write with ECC + * @mtd: MTD device structure + * @to: offset to write to + * @len: number of bytes to write + * @retlen: pointer to variable to store the number of written bytes + * @buf: the data to write + * + * NAND write with ECC. Used when performing writes in interrupt context, this + * may for example be called by mtdoops when writing an oops while in panic. + */ +static int panic_nand_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const uint8_t *buf) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + int chipnr = (int)(to >> chip->chip_shift); + struct mtd_oob_ops ops; + int ret; + + nand_select_target(chip, chipnr); + + /* Wait for the device to get ready */ + panic_nand_wait(chip, 400); + + memset(&ops, 0, sizeof(ops)); + ops.len = len; + ops.datbuf = (uint8_t *)buf; + ops.mode = MTD_OPS_PLACE_OOB; + + ret = nand_do_write_ops(chip, to, &ops); + + *retlen = ops.retlen; + return ret; +} + +/** + * nand_write_oob - [MTD Interface] NAND write data and/or out-of-band + * @mtd: MTD device structure + * @to: offset to write to + * @ops: oob operation description structure + */ +static int nand_write_oob(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + int ret = 0; + + ops->retlen = 0; + + nand_get_device(chip); + + switch (ops->mode) { + case MTD_OPS_PLACE_OOB: + case MTD_OPS_AUTO_OOB: + case MTD_OPS_RAW: + break; + + default: + goto out; + } + + if (!ops->datbuf) + ret = nand_do_write_oob(chip, to, ops); + else + ret = nand_do_write_ops(chip, to, ops); + +out: + nand_release_device(chip); + return ret; +} + +/** + * nand_erase - [MTD Interface] erase block(s) + * @mtd: MTD device structure + * @instr: erase instruction + * + * Erase one ore more blocks. + */ +static int nand_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + return nand_erase_nand(mtd_to_nand(mtd), instr, 0); +} + +/** + * nand_erase_nand - [INTERN] erase block(s) + * @chip: NAND chip object + * @instr: erase instruction + * @allowbbt: allow erasing the bbt area + * + * Erase one ore more blocks. + */ +int nand_erase_nand(struct nand_chip *chip, struct erase_info *instr, + int allowbbt) +{ + int page, pages_per_block, ret, chipnr; + loff_t len; + + pr_debug("%s: start = 0x%012llx, len = %llu\n", + __func__, (unsigned long long)instr->addr, + (unsigned long long)instr->len); + + if (check_offs_len(chip, instr->addr, instr->len)) + return -EINVAL; + + /* Check if the region is secured */ + if (nand_region_is_secured(chip, instr->addr, instr->len)) + return -EIO; + + /* Grab the lock and see if the device is available */ + nand_get_device(chip); + + /* Shift to get first page */ + page = (int)(instr->addr >> chip->page_shift); + chipnr = (int)(instr->addr >> chip->chip_shift); + + /* Calculate pages in each block */ + pages_per_block = 1 << (chip->phys_erase_shift - chip->page_shift); + + /* Select the NAND device */ + nand_select_target(chip, chipnr); + + /* Check, if it is write protected */ + if (nand_check_wp(chip)) { + pr_debug("%s: device is write protected!\n", + __func__); + ret = -EIO; + goto erase_exit; + } + + /* Loop through the pages */ + len = instr->len; + + while (len) { + loff_t ofs = (loff_t)page << chip->page_shift; + + /* Check if we have a bad block, we do not erase bad blocks! */ + if (nand_block_checkbad(chip, ((loff_t) page) << + chip->page_shift, allowbbt)) { + pr_warn("%s: attempt to erase a bad block at 0x%08llx\n", + __func__, (unsigned long long)ofs); + ret = -EIO; + goto erase_exit; + } + + /* + * Invalidate the page cache, if we erase the block which + * contains the current cached page. + */ + if (page <= chip->pagecache.page && chip->pagecache.page < + (page + pages_per_block)) + chip->pagecache.page = -1; + + ret = nand_erase_op(chip, (page & chip->pagemask) >> + (chip->phys_erase_shift - chip->page_shift)); + if (ret) { + pr_debug("%s: failed erase, page 0x%08x\n", + __func__, page); + instr->fail_addr = ofs; + goto erase_exit; + } + + /* Increment page address and decrement length */ + len -= (1ULL << chip->phys_erase_shift); + page += pages_per_block; + + /* Check, if we cross a chip boundary */ + if (len && !(page & chip->pagemask)) { + chipnr++; + nand_deselect_target(chip); + nand_select_target(chip, chipnr); + } + } + + ret = 0; +erase_exit: + + /* Deselect and wake up anyone waiting on the device */ + nand_deselect_target(chip); + nand_release_device(chip); + + /* Return more or less happy */ + return ret; +} + +/** + * nand_sync - [MTD Interface] sync + * @mtd: MTD device structure + * + * Sync is actually a wait for chip ready function. + */ +static void nand_sync(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + pr_debug("%s: called\n", __func__); + + /* Grab the lock and see if the device is available */ + nand_get_device(chip); + /* Release it and go back */ + nand_release_device(chip); +} + +/** + * nand_block_isbad - [MTD Interface] Check if block at offset is bad + * @mtd: MTD device structure + * @offs: offset relative to mtd start + */ +static int nand_block_isbad(struct mtd_info *mtd, loff_t offs) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + int chipnr = (int)(offs >> chip->chip_shift); + int ret; + + /* Select the NAND device */ + nand_get_device(chip); + + nand_select_target(chip, chipnr); + + ret = nand_block_checkbad(chip, offs, 0); + + nand_deselect_target(chip); + nand_release_device(chip); + + return ret; +} + +/** + * nand_block_markbad - [MTD Interface] Mark block at the given offset as bad + * @mtd: MTD device structure + * @ofs: offset relative to mtd start + */ +static int nand_block_markbad(struct mtd_info *mtd, loff_t ofs) +{ + int ret; + + ret = nand_block_isbad(mtd, ofs); + if (ret) { + /* If it was bad already, return success and do nothing */ + if (ret > 0) + return 0; + return ret; + } + + return nand_block_markbad_lowlevel(mtd_to_nand(mtd), ofs); +} + +/** + * nand_suspend - [MTD Interface] Suspend the NAND flash + * @mtd: MTD device structure + * + * Returns 0 for success or negative error code otherwise. + */ +static int nand_suspend(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + int ret = 0; + + mutex_lock(&chip->lock); + if (chip->ops.suspend) + ret = chip->ops.suspend(chip); + if (!ret) + chip->suspended = 1; + mutex_unlock(&chip->lock); + + return ret; +} + +/** + * nand_resume - [MTD Interface] Resume the NAND flash + * @mtd: MTD device structure + */ +static void nand_resume(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + 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__); + } + } + + wake_up_all(&chip->resume_wq); +} + +/** + * nand_shutdown - [MTD Interface] Finish the current NAND operation and + * prevent further operations + * @mtd: MTD device structure + */ +static void nand_shutdown(struct mtd_info *mtd) +{ + nand_suspend(mtd); +} + +/** + * nand_lock - [MTD Interface] Lock the NAND flash + * @mtd: MTD device structure + * @ofs: offset byte address + * @len: number of bytes to lock (must be a multiple of block/page size) + */ +static int nand_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + int ret; + + if (!chip->ops.lock_area) + return -ENOTSUPP; + + nand_get_device(chip); + ret = chip->ops.lock_area(chip, ofs, len); + nand_release_device(chip); + + return ret; +} + +/** + * nand_unlock - [MTD Interface] Unlock the NAND flash + * @mtd: MTD device structure + * @ofs: offset byte address + * @len: number of bytes to unlock (must be a multiple of block/page size) + */ +static int nand_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + int ret; + + if (!chip->ops.unlock_area) + return -ENOTSUPP; + + nand_get_device(chip); + ret = chip->ops.unlock_area(chip, ofs, len); + nand_release_device(chip); + + return ret; +} + +/* Set default functions */ +static void nand_set_defaults(struct nand_chip *chip) +{ + /* If no controller is provided, use the dummy, legacy one. */ + if (!chip->controller) { + chip->controller = &chip->legacy.dummy_controller; + nand_controller_init(chip->controller); + } + + nand_legacy_set_defaults(chip); + + if (!chip->buf_align) + chip->buf_align = 1; +} + +/* Sanitize ONFI strings so we can safely print them */ +void sanitize_string(uint8_t *s, size_t len) +{ + ssize_t i; + + /* Null terminate */ + s[len - 1] = 0; + + /* Remove non printable chars */ + for (i = 0; i < len - 1; i++) { + if (s[i] < ' ' || s[i] > 127) + s[i] = '?'; + } + + /* Remove trailing spaces */ + strim(s); +} + +/* + * nand_id_has_period - Check if an ID string has a given wraparound period + * @id_data: the ID string + * @arrlen: the length of the @id_data array + * @period: the period of repitition + * + * Check if an ID string is repeated within a given sequence of bytes at + * specific repetition interval period (e.g., {0x20,0x01,0x7F,0x20} has a + * period of 3). This is a helper function for nand_id_len(). Returns non-zero + * if the repetition has a period of @period; otherwise, returns zero. + */ +static int nand_id_has_period(u8 *id_data, int arrlen, int period) +{ + int i, j; + for (i = 0; i < period; i++) + for (j = i + period; j < arrlen; j += period) + if (id_data[i] != id_data[j]) + return 0; + return 1; +} + +/* + * nand_id_len - Get the length of an ID string returned by CMD_READID + * @id_data: the ID string + * @arrlen: the length of the @id_data array + + * Returns the length of the ID string, according to known wraparound/trailing + * zero patterns. If no pattern exists, returns the length of the array. + */ +static int nand_id_len(u8 *id_data, int arrlen) +{ + int last_nonzero, period; + + /* Find last non-zero byte */ + for (last_nonzero = arrlen - 1; last_nonzero >= 0; last_nonzero--) + if (id_data[last_nonzero]) + break; + + /* All zeros */ + if (last_nonzero < 0) + return 0; + + /* Calculate wraparound period */ + for (period = 1; period < arrlen; period++) + if (nand_id_has_period(id_data, arrlen, period)) + break; + + /* There's a repeated pattern */ + if (period < arrlen) + return period; + + /* There are trailing zeros */ + if (last_nonzero < arrlen - 1) + return last_nonzero + 1; + + /* No pattern detected */ + return arrlen; +} + +/* Extract the bits of per cell from the 3rd byte of the extended ID */ +static int nand_get_bits_per_cell(u8 cellinfo) +{ + int bits; + + bits = cellinfo & NAND_CI_CELLTYPE_MSK; + bits >>= NAND_CI_CELLTYPE_SHIFT; + return bits + 1; +} + +/* + * Many new NAND share similar device ID codes, which represent the size of the + * chip. The rest of the parameters must be decoded according to generic or + * manufacturer-specific "extended ID" decoding patterns. + */ +void nand_decode_ext_id(struct nand_chip *chip) +{ + struct nand_memory_organization *memorg; + struct mtd_info *mtd = nand_to_mtd(chip); + int extid; + u8 *id_data = chip->id.data; + + memorg = nanddev_get_memorg(&chip->base); + + /* The 3rd id byte holds MLC / multichip data */ + memorg->bits_per_cell = nand_get_bits_per_cell(id_data[2]); + /* The 4th id byte is the important one */ + extid = id_data[3]; + + /* Calc pagesize */ + memorg->pagesize = 1024 << (extid & 0x03); + mtd->writesize = memorg->pagesize; + extid >>= 2; + /* Calc oobsize */ + memorg->oobsize = (8 << (extid & 0x01)) * (mtd->writesize >> 9); + mtd->oobsize = memorg->oobsize; + extid >>= 2; + /* Calc blocksize. Blocksize is multiples of 64KiB */ + memorg->pages_per_eraseblock = ((64 * 1024) << (extid & 0x03)) / + memorg->pagesize; + mtd->erasesize = (64 * 1024) << (extid & 0x03); + extid >>= 2; + /* Get buswidth information */ + if (extid & 0x1) + chip->options |= NAND_BUSWIDTH_16; +} +EXPORT_SYMBOL_GPL(nand_decode_ext_id); + +/* + * Old devices have chip data hardcoded in the device ID table. nand_decode_id + * decodes a matching ID table entry and assigns the MTD size parameters for + * the chip. + */ +static void nand_decode_id(struct nand_chip *chip, struct nand_flash_dev *type) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_memory_organization *memorg; + + memorg = nanddev_get_memorg(&chip->base); + + memorg->pages_per_eraseblock = type->erasesize / type->pagesize; + mtd->erasesize = type->erasesize; + memorg->pagesize = type->pagesize; + mtd->writesize = memorg->pagesize; + memorg->oobsize = memorg->pagesize / 32; + mtd->oobsize = memorg->oobsize; + + /* All legacy ID NAND are small-page, SLC */ + memorg->bits_per_cell = 1; +} + +/* + * Set the bad block marker/indicator (BBM/BBI) patterns according to some + * heuristic patterns using various detected parameters (e.g., manufacturer, + * page size, cell-type information). + */ +static void nand_decode_bbm_options(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + /* Set the bad block position */ + if (mtd->writesize > 512 || (chip->options & NAND_BUSWIDTH_16)) + chip->badblockpos = NAND_BBM_POS_LARGE; + else + chip->badblockpos = NAND_BBM_POS_SMALL; +} + +static inline bool is_full_id_nand(struct nand_flash_dev *type) +{ + return type->id_len; +} + +static bool find_full_id_nand(struct nand_chip *chip, + struct nand_flash_dev *type) +{ + struct nand_device *base = &chip->base; + struct nand_ecc_props requirements; + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_memory_organization *memorg; + u8 *id_data = chip->id.data; + + memorg = nanddev_get_memorg(&chip->base); + + if (!strncmp(type->id, id_data, type->id_len)) { + memorg->pagesize = type->pagesize; + mtd->writesize = memorg->pagesize; + memorg->pages_per_eraseblock = type->erasesize / + type->pagesize; + mtd->erasesize = type->erasesize; + memorg->oobsize = type->oobsize; + mtd->oobsize = memorg->oobsize; + + memorg->bits_per_cell = nand_get_bits_per_cell(id_data[2]); + memorg->eraseblocks_per_lun = + DIV_ROUND_DOWN_ULL((u64)type->chipsize << 20, + memorg->pagesize * + memorg->pages_per_eraseblock); + chip->options |= type->options; + requirements.strength = NAND_ECC_STRENGTH(type); + requirements.step_size = NAND_ECC_STEP(type); + nanddev_set_ecc_requirements(base, &requirements); + + chip->parameters.model = kstrdup(type->name, GFP_KERNEL); + if (!chip->parameters.model) + return false; + + return true; + } + return false; +} + +/* + * Manufacturer detection. Only used when the NAND is not ONFI or JEDEC + * compliant and does not have a full-id or legacy-id entry in the nand_ids + * table. + */ +static void nand_manufacturer_detect(struct nand_chip *chip) +{ + /* + * Try manufacturer detection if available and use + * nand_decode_ext_id() otherwise. + */ + if (chip->manufacturer.desc && chip->manufacturer.desc->ops && + chip->manufacturer.desc->ops->detect) { + struct nand_memory_organization *memorg; + + memorg = nanddev_get_memorg(&chip->base); + + /* The 3rd id byte holds MLC / multichip data */ + memorg->bits_per_cell = nand_get_bits_per_cell(chip->id.data[2]); + chip->manufacturer.desc->ops->detect(chip); + } else { + nand_decode_ext_id(chip); + } +} + +/* + * Manufacturer initialization. This function is called for all NANDs including + * ONFI and JEDEC compliant ones. + * Manufacturer drivers should put all their specific initialization code in + * their ->init() hook. + */ +static int nand_manufacturer_init(struct nand_chip *chip) +{ + if (!chip->manufacturer.desc || !chip->manufacturer.desc->ops || + !chip->manufacturer.desc->ops->init) + return 0; + + return chip->manufacturer.desc->ops->init(chip); +} + +/* + * Manufacturer cleanup. This function is called for all NANDs including + * ONFI and JEDEC compliant ones. + * Manufacturer drivers should put all their specific cleanup code in their + * ->cleanup() hook. + */ +static void nand_manufacturer_cleanup(struct nand_chip *chip) +{ + /* Release manufacturer private data */ + if (chip->manufacturer.desc && chip->manufacturer.desc->ops && + chip->manufacturer.desc->ops->cleanup) + chip->manufacturer.desc->ops->cleanup(chip); +} + +static const char * +nand_manufacturer_name(const struct nand_manufacturer_desc *manufacturer_desc) +{ + return manufacturer_desc ? manufacturer_desc->name : "Unknown"; +} + +static void rawnand_check_data_only_read_support(struct nand_chip *chip) +{ + /* Use an arbitrary size for the check */ + if (!nand_read_data_op(chip, NULL, SZ_512, true, true)) + chip->controller->supported_op.data_only_read = 1; +} + +static void rawnand_early_check_supported_ops(struct nand_chip *chip) +{ + /* The supported_op fields should not be set by individual drivers */ + WARN_ON_ONCE(chip->controller->supported_op.data_only_read); + + if (!nand_has_exec_op(chip)) + return; + + rawnand_check_data_only_read_support(chip); +} + +static void rawnand_check_cont_read_support(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (!chip->parameters.supports_read_cache) + return; + + if (chip->read_retries) + return; + + if (!nand_lp_exec_cont_read_page_op(chip, 0, 0, NULL, + mtd->writesize, true)) + chip->controller->supported_op.cont_read = 1; +} + +static void rawnand_late_check_supported_ops(struct nand_chip *chip) +{ + /* The supported_op fields should not be set by individual drivers */ + WARN_ON_ONCE(chip->controller->supported_op.cont_read); + + /* + * Too many devices do not support sequential cached reads with on-die + * ECC correction enabled, so in this case refuse to perform the + * automation. + */ + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_DIE) + return; + + if (!nand_has_exec_op(chip)) + return; + + /* + * For now, continuous reads can only be used with the core page helpers. + * This can be extended later. + */ + if (!(chip->ecc.read_page == nand_read_page_hwecc || + chip->ecc.read_page == nand_read_page_syndrome || + chip->ecc.read_page == nand_read_page_swecc)) + return; + + rawnand_check_cont_read_support(chip); +} + +/* + * Get the flash and manufacturer id and lookup if the type is supported. + */ +static int nand_detect(struct nand_chip *chip, struct nand_flash_dev *type) +{ + const struct nand_manufacturer_desc *manufacturer_desc; + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_memory_organization *memorg; + int busw, ret; + u8 *id_data = chip->id.data; + u8 maf_id, dev_id; + u64 targetsize; + + /* + * Let's start by initializing memorg fields that might be left + * unassigned by the ID-based detection logic. + */ + memorg = nanddev_get_memorg(&chip->base); + memorg->planes_per_lun = 1; + memorg->luns_per_target = 1; + + /* + * Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx) + * after power-up. + */ + ret = nand_reset(chip, 0); + if (ret) + return ret; + + /* Select the device */ + nand_select_target(chip, 0); + + rawnand_early_check_supported_ops(chip); + + /* Send the command for reading device ID */ + ret = nand_readid_op(chip, 0, id_data, 2); + if (ret) + return ret; + + /* Read manufacturer and device IDs */ + maf_id = id_data[0]; + dev_id = id_data[1]; + + /* + * Try again to make sure, as some systems the bus-hold or other + * interface concerns can cause random data which looks like a + * possibly credible NAND flash to appear. If the two results do + * not match, ignore the device completely. + */ + + /* Read entire ID string */ + ret = nand_readid_op(chip, 0, id_data, sizeof(chip->id.data)); + if (ret) + return ret; + + if (id_data[0] != maf_id || id_data[1] != dev_id) { + pr_info("second ID read did not match %02x,%02x against %02x,%02x\n", + maf_id, dev_id, id_data[0], id_data[1]); + return -ENODEV; + } + + chip->id.len = nand_id_len(id_data, ARRAY_SIZE(chip->id.data)); + + /* Try to identify manufacturer */ + manufacturer_desc = nand_get_manufacturer_desc(maf_id); + chip->manufacturer.desc = manufacturer_desc; + + if (!type) + type = nand_flash_ids; + + /* + * Save the NAND_BUSWIDTH_16 flag before letting auto-detection logic + * override it. + * This is required to make sure initial NAND bus width set by the + * NAND controller driver is coherent with the real NAND bus width + * (extracted by auto-detection code). + */ + busw = chip->options & NAND_BUSWIDTH_16; + + /* + * The flag is only set (never cleared), reset it to its default value + * before starting auto-detection. + */ + chip->options &= ~NAND_BUSWIDTH_16; + + for (; type->name != NULL; type++) { + if (is_full_id_nand(type)) { + if (find_full_id_nand(chip, type)) + goto ident_done; + } else if (dev_id == type->dev_id) { + break; + } + } + + if (!type->name || !type->pagesize) { + /* Check if the chip is ONFI compliant */ + ret = nand_onfi_detect(chip); + if (ret < 0) + return ret; + else if (ret) + goto ident_done; + + /* Check if the chip is JEDEC compliant */ + ret = nand_jedec_detect(chip); + if (ret < 0) + return ret; + else if (ret) + goto ident_done; + } + + if (!type->name) + return -ENODEV; + + chip->parameters.model = kstrdup(type->name, GFP_KERNEL); + if (!chip->parameters.model) + return -ENOMEM; + + if (!type->pagesize) + nand_manufacturer_detect(chip); + else + nand_decode_id(chip, type); + + /* Get chip options */ + chip->options |= type->options; + + memorg->eraseblocks_per_lun = + DIV_ROUND_DOWN_ULL((u64)type->chipsize << 20, + memorg->pagesize * + memorg->pages_per_eraseblock); + +ident_done: + if (!mtd->name) + mtd->name = chip->parameters.model; + + if (chip->options & NAND_BUSWIDTH_AUTO) { + WARN_ON(busw & NAND_BUSWIDTH_16); + nand_set_defaults(chip); + } else if (busw != (chip->options & NAND_BUSWIDTH_16)) { + /* + * Check, if buswidth is correct. Hardware drivers should set + * chip correct! + */ + pr_info("device found, Manufacturer ID: 0x%02x, Chip ID: 0x%02x\n", + maf_id, dev_id); + pr_info("%s %s\n", nand_manufacturer_name(manufacturer_desc), + mtd->name); + pr_warn("bus width %d instead of %d bits\n", busw ? 16 : 8, + (chip->options & NAND_BUSWIDTH_16) ? 16 : 8); + ret = -EINVAL; + + goto free_detect_allocation; + } + + nand_decode_bbm_options(chip); + + /* Calculate the address shift from the page size */ + chip->page_shift = ffs(mtd->writesize) - 1; + /* Convert chipsize to number of pages per chip -1 */ + targetsize = nanddev_target_size(&chip->base); + chip->pagemask = (targetsize >> chip->page_shift) - 1; + + chip->bbt_erase_shift = chip->phys_erase_shift = + ffs(mtd->erasesize) - 1; + if (targetsize & 0xffffffff) + chip->chip_shift = ffs((unsigned)targetsize) - 1; + else { + chip->chip_shift = ffs((unsigned)(targetsize >> 32)); + chip->chip_shift += 32 - 1; + } + + if (chip->chip_shift - chip->page_shift > 16) + chip->options |= NAND_ROW_ADDR_3; + + chip->badblockbits = 8; + + nand_legacy_adjust_cmdfunc(chip); + + pr_info("device found, Manufacturer ID: 0x%02x, Chip ID: 0x%02x\n", + maf_id, dev_id); + pr_info("%s %s\n", nand_manufacturer_name(manufacturer_desc), + chip->parameters.model); + pr_info("%d MiB, %s, erase size: %d KiB, page size: %d, OOB size: %d\n", + (int)(targetsize >> 20), nand_is_slc(chip) ? "SLC" : "MLC", + mtd->erasesize >> 10, mtd->writesize, mtd->oobsize); + return 0; + +free_detect_allocation: + kfree(chip->parameters.model); + + return ret; +} + +static enum nand_ecc_engine_type +of_get_rawnand_ecc_engine_type_legacy(struct device_node *np) +{ + enum nand_ecc_legacy_mode { + NAND_ECC_INVALID, + NAND_ECC_NONE, + NAND_ECC_SOFT, + NAND_ECC_SOFT_BCH, + NAND_ECC_HW, + NAND_ECC_HW_SYNDROME, + NAND_ECC_ON_DIE, + }; + const char * const nand_ecc_legacy_modes[] = { + [NAND_ECC_NONE] = "none", + [NAND_ECC_SOFT] = "soft", + [NAND_ECC_SOFT_BCH] = "soft_bch", + [NAND_ECC_HW] = "hw", + [NAND_ECC_HW_SYNDROME] = "hw_syndrome", + [NAND_ECC_ON_DIE] = "on-die", + }; + enum nand_ecc_legacy_mode eng_type; + const char *pm; + int err; + + err = of_property_read_string(np, "nand-ecc-mode", &pm); + if (err) + return NAND_ECC_ENGINE_TYPE_INVALID; + + for (eng_type = NAND_ECC_NONE; + eng_type < ARRAY_SIZE(nand_ecc_legacy_modes); eng_type++) { + if (!strcasecmp(pm, nand_ecc_legacy_modes[eng_type])) { + switch (eng_type) { + case NAND_ECC_NONE: + return NAND_ECC_ENGINE_TYPE_NONE; + case NAND_ECC_SOFT: + case NAND_ECC_SOFT_BCH: + return NAND_ECC_ENGINE_TYPE_SOFT; + case NAND_ECC_HW: + case NAND_ECC_HW_SYNDROME: + return NAND_ECC_ENGINE_TYPE_ON_HOST; + case NAND_ECC_ON_DIE: + return NAND_ECC_ENGINE_TYPE_ON_DIE; + default: + break; + } + } + } + + return NAND_ECC_ENGINE_TYPE_INVALID; +} + +static enum nand_ecc_placement +of_get_rawnand_ecc_placement_legacy(struct device_node *np) +{ + const char *pm; + int err; + + err = of_property_read_string(np, "nand-ecc-mode", &pm); + if (!err) { + if (!strcasecmp(pm, "hw_syndrome")) + return NAND_ECC_PLACEMENT_INTERLEAVED; + } + + return NAND_ECC_PLACEMENT_UNKNOWN; +} + +static enum nand_ecc_algo of_get_rawnand_ecc_algo_legacy(struct device_node *np) +{ + const char *pm; + int err; + + err = of_property_read_string(np, "nand-ecc-mode", &pm); + if (!err) { + if (!strcasecmp(pm, "soft")) + return NAND_ECC_ALGO_HAMMING; + else if (!strcasecmp(pm, "soft_bch")) + return NAND_ECC_ALGO_BCH; + } + + return NAND_ECC_ALGO_UNKNOWN; +} + +static void of_get_nand_ecc_legacy_user_config(struct nand_chip *chip) +{ + struct device_node *dn = nand_get_flash_node(chip); + struct nand_ecc_props *user_conf = &chip->base.ecc.user_conf; + + if (user_conf->engine_type == NAND_ECC_ENGINE_TYPE_INVALID) + user_conf->engine_type = of_get_rawnand_ecc_engine_type_legacy(dn); + + if (user_conf->algo == NAND_ECC_ALGO_UNKNOWN) + user_conf->algo = of_get_rawnand_ecc_algo_legacy(dn); + + if (user_conf->placement == NAND_ECC_PLACEMENT_UNKNOWN) + user_conf->placement = of_get_rawnand_ecc_placement_legacy(dn); +} + +static int of_get_nand_bus_width(struct nand_chip *chip) +{ + struct device_node *dn = nand_get_flash_node(chip); + u32 val; + int ret; + + ret = of_property_read_u32(dn, "nand-bus-width", &val); + if (ret == -EINVAL) + /* Buswidth defaults to 8 if the property does not exist .*/ + return 0; + else if (ret) + return ret; + + if (val == 16) + chip->options |= NAND_BUSWIDTH_16; + else if (val != 8) + return -EINVAL; + return 0; +} + +static int of_get_nand_secure_regions(struct nand_chip *chip) +{ + struct device_node *dn = nand_get_flash_node(chip); + struct property *prop; + int nr_elem, i, j; + + /* Only proceed if the "secure-regions" property is present in DT */ + prop = of_find_property(dn, "secure-regions", NULL); + if (!prop) + return 0; + + nr_elem = of_property_count_elems_of_size(dn, "secure-regions", sizeof(u64)); + if (nr_elem <= 0) + return nr_elem; + + chip->nr_secure_regions = nr_elem / 2; + chip->secure_regions = kzalloc_objs(*chip->secure_regions, + chip->nr_secure_regions); + if (!chip->secure_regions) + return -ENOMEM; + + for (i = 0, j = 0; i < chip->nr_secure_regions; i++, j += 2) { + of_property_read_u64_index(dn, "secure-regions", j, + &chip->secure_regions[i].offset); + of_property_read_u64_index(dn, "secure-regions", j + 1, + &chip->secure_regions[i].size); + } + + return 0; +} + +/** + * rawnand_dt_parse_gpio_cs - Parse the gpio-cs property of a controller + * @dev: Device that will be parsed. Also used for managed allocations. + * @cs_array: Array of GPIO desc pointers allocated on success + * @ncs_array: Number of entries in @cs_array updated on success. + * @return 0 on success, an error otherwise. + */ +int rawnand_dt_parse_gpio_cs(struct device *dev, struct gpio_desc ***cs_array, + unsigned int *ncs_array) +{ + struct gpio_desc **descs; + int ndescs, i; + + ndescs = gpiod_count(dev, "cs"); + if (ndescs < 0) { + dev_dbg(dev, "No valid cs-gpios property\n"); + return 0; + } + + descs = devm_kcalloc(dev, ndescs, sizeof(*descs), GFP_KERNEL); + if (!descs) + return -ENOMEM; + + for (i = 0; i < ndescs; i++) { + descs[i] = gpiod_get_index_optional(dev, "cs", i, + GPIOD_OUT_HIGH); + if (IS_ERR(descs[i])) + return PTR_ERR(descs[i]); + } + + *ncs_array = ndescs; + *cs_array = descs; + + return 0; +} +EXPORT_SYMBOL(rawnand_dt_parse_gpio_cs); + +static int rawnand_dt_init(struct nand_chip *chip) +{ + struct nand_device *nand = mtd_to_nanddev(nand_to_mtd(chip)); + struct device_node *dn = nand_get_flash_node(chip); + int ret; + + if (!dn) + return 0; + + ret = of_get_nand_bus_width(chip); + if (ret) + return ret; + + if (of_property_read_bool(dn, "nand-is-boot-medium")) + chip->options |= NAND_IS_BOOT_MEDIUM; + + if (of_property_read_bool(dn, "nand-on-flash-bbt")) + chip->bbt_options |= NAND_BBT_USE_FLASH; + + of_get_nand_ecc_user_config(nand); + of_get_nand_ecc_legacy_user_config(chip); + + /* + * If neither the user nor the NAND controller have requested a specific + * ECC engine type, we will default to NAND_ECC_ENGINE_TYPE_ON_HOST. + */ + nand->ecc.defaults.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + + /* + * Use the user requested engine type, unless there is none, in this + * case default to the NAND controller choice, otherwise fallback to + * the raw NAND default one. + */ + if (nand->ecc.user_conf.engine_type != NAND_ECC_ENGINE_TYPE_INVALID) + chip->ecc.engine_type = nand->ecc.user_conf.engine_type; + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_INVALID) + chip->ecc.engine_type = nand->ecc.defaults.engine_type; + + chip->ecc.placement = nand->ecc.user_conf.placement; + chip->ecc.algo = nand->ecc.user_conf.algo; + chip->ecc.strength = nand->ecc.user_conf.strength; + chip->ecc.size = nand->ecc.user_conf.step_size; + + return 0; +} + +/** + * nand_scan_ident - Scan for the NAND device + * @chip: NAND chip object + * @maxchips: number of chips to scan for + * @table: alternative NAND ID table + * + * This is the first phase of the normal nand_scan() function. It reads the + * flash ID and sets up MTD fields accordingly. + * + * This helper used to be called directly from controller drivers that needed + * to tweak some ECC-related parameters before nand_scan_tail(). This separation + * prevented dynamic allocations during this phase which was unconvenient and + * as been banned for the benefit of the ->init_ecc()/cleanup_ecc() hooks. + */ +static int nand_scan_ident(struct nand_chip *chip, unsigned int maxchips, + struct nand_flash_dev *table) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_memory_organization *memorg; + int nand_maf_id, nand_dev_id; + unsigned int i; + int ret; + + memorg = nanddev_get_memorg(&chip->base); + + /* Assume all dies are deselected when we enter nand_scan_ident(). */ + chip->cur_cs = -1; + + mutex_init(&chip->lock); + init_waitqueue_head(&chip->resume_wq); + + /* Enforce the right timings for reset/detection */ + chip->current_interface_config = nand_get_reset_interface_config(); + + ret = rawnand_dt_init(chip); + if (ret) + return ret; + + if (!mtd->name && mtd->dev.parent) + mtd->name = dev_name(mtd->dev.parent); + + /* Set the default functions */ + nand_set_defaults(chip); + + ret = nand_legacy_check_hooks(chip); + if (ret) + return ret; + + memorg->ntargets = maxchips; + + /* Read the flash type */ + ret = nand_detect(chip, table); + if (ret) { + if (!(chip->options & NAND_SCAN_SILENT_NODEV)) + pr_warn("No NAND device found\n"); + nand_deselect_target(chip); + return ret; + } + + nand_maf_id = chip->id.data[0]; + nand_dev_id = chip->id.data[1]; + + nand_deselect_target(chip); + + /* Check for a chip array */ + for (i = 1; i < maxchips; i++) { + u8 id[2]; + + /* See comment in nand_get_flash_type for reset */ + ret = nand_reset(chip, i); + if (ret) + break; + + nand_select_target(chip, i); + /* Send the command for reading device ID */ + ret = nand_readid_op(chip, 0, id, sizeof(id)); + if (ret) + break; + /* Read manufacturer and device IDs */ + if (nand_maf_id != id[0] || nand_dev_id != id[1]) { + nand_deselect_target(chip); + break; + } + nand_deselect_target(chip); + } + if (i > 1) + pr_info("%d chips detected\n", i); + + /* Store the number of chips and calc total size for mtd */ + memorg->ntargets = i; + mtd->size = i * nanddev_target_size(&chip->base); + + return 0; +} + +static void nand_scan_ident_cleanup(struct nand_chip *chip) +{ + kfree(chip->parameters.model); + kfree(chip->parameters.onfi); +} + +int rawnand_sw_hamming_init(struct nand_chip *chip) +{ + struct nand_ecc_sw_hamming_conf *engine_conf; + struct nand_device *base = &chip->base; + int ret; + + base->ecc.user_conf.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + base->ecc.user_conf.algo = NAND_ECC_ALGO_HAMMING; + base->ecc.user_conf.strength = chip->ecc.strength; + base->ecc.user_conf.step_size = chip->ecc.size; + + ret = nand_ecc_sw_hamming_init_ctx(base); + if (ret) + return ret; + + engine_conf = base->ecc.ctx.priv; + + if (chip->ecc.options & NAND_ECC_SOFT_HAMMING_SM_ORDER) + engine_conf->sm_order = true; + + chip->ecc.size = base->ecc.ctx.conf.step_size; + chip->ecc.strength = base->ecc.ctx.conf.strength; + chip->ecc.total = base->ecc.ctx.total; + chip->ecc.steps = nanddev_get_ecc_nsteps(base); + chip->ecc.bytes = base->ecc.ctx.total / nanddev_get_ecc_nsteps(base); + + return 0; +} +EXPORT_SYMBOL(rawnand_sw_hamming_init); + +int rawnand_sw_hamming_calculate(struct nand_chip *chip, + const unsigned char *buf, + unsigned char *code) +{ + struct nand_device *base = &chip->base; + + return nand_ecc_sw_hamming_calculate(base, buf, code); +} +EXPORT_SYMBOL(rawnand_sw_hamming_calculate); + +int rawnand_sw_hamming_correct(struct nand_chip *chip, + unsigned char *buf, + unsigned char *read_ecc, + unsigned char *calc_ecc) +{ + struct nand_device *base = &chip->base; + + return nand_ecc_sw_hamming_correct(base, buf, read_ecc, calc_ecc); +} +EXPORT_SYMBOL(rawnand_sw_hamming_correct); + +void rawnand_sw_hamming_cleanup(struct nand_chip *chip) +{ + struct nand_device *base = &chip->base; + + nand_ecc_sw_hamming_cleanup_ctx(base); +} +EXPORT_SYMBOL(rawnand_sw_hamming_cleanup); + +int rawnand_sw_bch_init(struct nand_chip *chip) +{ + struct nand_device *base = &chip->base; + const struct nand_ecc_props *ecc_conf = nanddev_get_ecc_conf(base); + int ret; + + base->ecc.user_conf.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + base->ecc.user_conf.algo = NAND_ECC_ALGO_BCH; + base->ecc.user_conf.step_size = chip->ecc.size; + base->ecc.user_conf.strength = chip->ecc.strength; + + ret = nand_ecc_sw_bch_init_ctx(base); + if (ret) + return ret; + + chip->ecc.size = ecc_conf->step_size; + chip->ecc.strength = ecc_conf->strength; + chip->ecc.total = base->ecc.ctx.total; + chip->ecc.steps = nanddev_get_ecc_nsteps(base); + chip->ecc.bytes = base->ecc.ctx.total / nanddev_get_ecc_nsteps(base); + + return 0; +} +EXPORT_SYMBOL(rawnand_sw_bch_init); + +static int rawnand_sw_bch_calculate(struct nand_chip *chip, + const unsigned char *buf, + unsigned char *code) +{ + struct nand_device *base = &chip->base; + + return nand_ecc_sw_bch_calculate(base, buf, code); +} + +int rawnand_sw_bch_correct(struct nand_chip *chip, unsigned char *buf, + unsigned char *read_ecc, unsigned char *calc_ecc) +{ + struct nand_device *base = &chip->base; + + return nand_ecc_sw_bch_correct(base, buf, read_ecc, calc_ecc); +} +EXPORT_SYMBOL(rawnand_sw_bch_correct); + +void rawnand_sw_bch_cleanup(struct nand_chip *chip) +{ + struct nand_device *base = &chip->base; + + nand_ecc_sw_bch_cleanup_ctx(base); +} +EXPORT_SYMBOL(rawnand_sw_bch_cleanup); + +static int nand_set_ecc_on_host_ops(struct nand_chip *chip) +{ + struct nand_ecc_ctrl *ecc = &chip->ecc; + + switch (ecc->placement) { + case NAND_ECC_PLACEMENT_UNKNOWN: + case NAND_ECC_PLACEMENT_OOB: + /* Use standard hwecc read page function? */ + if (!ecc->read_page) + ecc->read_page = nand_read_page_hwecc; + if (!ecc->write_page) + ecc->write_page = nand_write_page_hwecc; + if (!ecc->read_page_raw) + ecc->read_page_raw = nand_read_page_raw; + if (!ecc->write_page_raw) + ecc->write_page_raw = nand_write_page_raw; + if (!ecc->read_oob) + ecc->read_oob = nand_read_oob_std; + if (!ecc->write_oob) + ecc->write_oob = nand_write_oob_std; + if (!ecc->read_subpage) + ecc->read_subpage = nand_read_subpage; + if (!ecc->write_subpage && ecc->hwctl && ecc->calculate) + ecc->write_subpage = nand_write_subpage_hwecc; + fallthrough; + + case NAND_ECC_PLACEMENT_INTERLEAVED: + if ((!ecc->calculate || !ecc->correct || !ecc->hwctl) && + (!ecc->read_page || + ecc->read_page == nand_read_page_hwecc || + !ecc->write_page || + ecc->write_page == nand_write_page_hwecc)) { + WARN(1, "No ECC functions supplied; hardware ECC not possible\n"); + return -EINVAL; + } + /* Use standard syndrome read/write page function? */ + if (!ecc->read_page) + ecc->read_page = nand_read_page_syndrome; + if (!ecc->write_page) + ecc->write_page = nand_write_page_syndrome; + if (!ecc->read_page_raw) + ecc->read_page_raw = nand_read_page_raw_syndrome; + if (!ecc->write_page_raw) + ecc->write_page_raw = nand_write_page_raw_syndrome; + if (!ecc->read_oob) + ecc->read_oob = nand_read_oob_syndrome; + if (!ecc->write_oob) + ecc->write_oob = nand_write_oob_syndrome; + break; + + default: + pr_warn("Invalid NAND_ECC_PLACEMENT %d\n", + ecc->placement); + return -EINVAL; + } + + return 0; +} + +static int nand_set_ecc_soft_ops(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_device *nanddev = mtd_to_nanddev(mtd); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int ret; + + if (WARN_ON(ecc->engine_type != NAND_ECC_ENGINE_TYPE_SOFT)) + return -EINVAL; + + switch (ecc->algo) { + case NAND_ECC_ALGO_HAMMING: + ecc->calculate = rawnand_sw_hamming_calculate; + ecc->correct = rawnand_sw_hamming_correct; + ecc->read_page = nand_read_page_swecc; + ecc->read_subpage = nand_read_subpage; + ecc->write_page = nand_write_page_swecc; + if (!ecc->read_page_raw) + ecc->read_page_raw = nand_read_page_raw; + if (!ecc->write_page_raw) + ecc->write_page_raw = nand_write_page_raw; + ecc->read_oob = nand_read_oob_std; + ecc->write_oob = nand_write_oob_std; + if (!ecc->size) + ecc->size = 256; + ecc->bytes = 3; + ecc->strength = 1; + + if (IS_ENABLED(CONFIG_MTD_NAND_ECC_SW_HAMMING_SMC)) + ecc->options |= NAND_ECC_SOFT_HAMMING_SM_ORDER; + + ret = rawnand_sw_hamming_init(chip); + if (ret) { + WARN(1, "Hamming ECC initialization failed!\n"); + return ret; + } + + return 0; + case NAND_ECC_ALGO_BCH: + if (!IS_ENABLED(CONFIG_MTD_NAND_ECC_SW_BCH)) { + WARN(1, "CONFIG_MTD_NAND_ECC_SW_BCH not enabled\n"); + return -EINVAL; + } + ecc->calculate = rawnand_sw_bch_calculate; + ecc->correct = rawnand_sw_bch_correct; + ecc->read_page = nand_read_page_swecc; + ecc->read_subpage = nand_read_subpage; + ecc->write_page = nand_write_page_swecc; + if (!ecc->read_page_raw) + ecc->read_page_raw = nand_read_page_raw; + if (!ecc->write_page_raw) + ecc->write_page_raw = nand_write_page_raw; + ecc->read_oob = nand_read_oob_std; + ecc->write_oob = nand_write_oob_std; + + /* + * We can only maximize ECC config when the default layout is + * used, otherwise we don't know how many bytes can really be + * used. + */ + if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH && + mtd->ooblayout != nand_get_large_page_ooblayout()) + nanddev->ecc.user_conf.flags &= ~NAND_ECC_MAXIMIZE_STRENGTH; + + ret = rawnand_sw_bch_init(chip); + if (ret) { + WARN(1, "BCH ECC initialization failed!\n"); + return ret; + } + + return 0; + default: + WARN(1, "Unsupported ECC algorithm!\n"); + return -EINVAL; + } +} + +/** + * nand_check_ecc_caps - check the sanity of preset ECC settings + * @chip: nand chip info structure + * @caps: ECC caps info structure + * @oobavail: OOB size that the ECC engine can use + * + * When ECC step size and strength are already set, check if they are supported + * by the controller and the calculated ECC bytes fit within the chip's OOB. + * On success, the calculated ECC bytes is set. + */ +static int +nand_check_ecc_caps(struct nand_chip *chip, + const struct nand_ecc_caps *caps, int oobavail) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + const struct nand_ecc_step_info *stepinfo; + int preset_step = chip->ecc.size; + int preset_strength = chip->ecc.strength; + int ecc_bytes, nsteps = mtd->writesize / preset_step; + int i, j; + + for (i = 0; i < caps->nstepinfos; i++) { + stepinfo = &caps->stepinfos[i]; + + if (stepinfo->stepsize != preset_step) + continue; + + for (j = 0; j < stepinfo->nstrengths; j++) { + if (stepinfo->strengths[j] != preset_strength) + continue; + + ecc_bytes = caps->calc_ecc_bytes(preset_step, + preset_strength); + if (WARN_ON_ONCE(ecc_bytes < 0)) + return ecc_bytes; + + if (ecc_bytes * nsteps > oobavail) { + pr_err("ECC (step, strength) = (%d, %d) does not fit in OOB", + preset_step, preset_strength); + return -ENOSPC; + } + + chip->ecc.bytes = ecc_bytes; + + return 0; + } + } + + pr_err("ECC (step, strength) = (%d, %d) not supported on this controller", + preset_step, preset_strength); + + return -ENOTSUPP; +} + +/** + * nand_match_ecc_req - meet the chip's requirement with least ECC bytes + * @chip: nand chip info structure + * @caps: ECC engine caps info structure + * @oobavail: OOB size that the ECC engine can use + * + * If a chip's ECC requirement is provided, try to meet it with the least + * number of ECC bytes (i.e. with the largest number of OOB-free bytes). + * On success, the chosen ECC settings are set. + */ +static int +nand_match_ecc_req(struct nand_chip *chip, + const struct nand_ecc_caps *caps, int oobavail) +{ + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&chip->base); + struct mtd_info *mtd = nand_to_mtd(chip); + const struct nand_ecc_step_info *stepinfo; + int req_step = requirements->step_size; + int req_strength = requirements->strength; + int req_corr, step_size, strength, nsteps, ecc_bytes, ecc_bytes_total; + int best_step = 0, best_strength = 0, best_ecc_bytes = 0; + int best_ecc_bytes_total = INT_MAX; + int i, j; + + /* No information provided by the NAND chip */ + if (!req_step || !req_strength) + return -ENOTSUPP; + + /* number of correctable bits the chip requires in a page */ + req_corr = mtd->writesize / req_step * req_strength; + + for (i = 0; i < caps->nstepinfos; i++) { + stepinfo = &caps->stepinfos[i]; + step_size = stepinfo->stepsize; + + for (j = 0; j < stepinfo->nstrengths; j++) { + strength = stepinfo->strengths[j]; + + /* + * If both step size and strength are smaller than the + * chip's requirement, it is not easy to compare the + * resulted reliability. + */ + if (step_size < req_step && strength < req_strength) + continue; + + if (mtd->writesize % step_size) + continue; + + nsteps = mtd->writesize / step_size; + + ecc_bytes = caps->calc_ecc_bytes(step_size, strength); + if (WARN_ON_ONCE(ecc_bytes < 0)) + continue; + ecc_bytes_total = ecc_bytes * nsteps; + + if (ecc_bytes_total > oobavail || + strength * nsteps < req_corr) + continue; + + /* + * We assume the best is to meet the chip's requrement + * with the least number of ECC bytes. + */ + if (ecc_bytes_total < best_ecc_bytes_total) { + best_ecc_bytes_total = ecc_bytes_total; + best_step = step_size; + best_strength = strength; + best_ecc_bytes = ecc_bytes; + } + } + } + + if (best_ecc_bytes_total == INT_MAX) + return -ENOTSUPP; + + chip->ecc.size = best_step; + chip->ecc.strength = best_strength; + chip->ecc.bytes = best_ecc_bytes; + + return 0; +} + +/** + * nand_maximize_ecc - choose the max ECC strength available + * @chip: nand chip info structure + * @caps: ECC engine caps info structure + * @oobavail: OOB size that the ECC engine can use + * + * Choose the max ECC strength that is supported on the controller, and can fit + * within the chip's OOB. On success, the chosen ECC settings are set. + */ +static int +nand_maximize_ecc(struct nand_chip *chip, + const struct nand_ecc_caps *caps, int oobavail) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + const struct nand_ecc_step_info *stepinfo; + int step_size, strength, nsteps, ecc_bytes, corr; + int best_corr = 0; + int best_step = 0; + int best_strength = 0, best_ecc_bytes = 0; + int i, j; + + for (i = 0; i < caps->nstepinfos; i++) { + stepinfo = &caps->stepinfos[i]; + step_size = stepinfo->stepsize; + + /* If chip->ecc.size is already set, respect it */ + if (chip->ecc.size && step_size != chip->ecc.size) + continue; + + for (j = 0; j < stepinfo->nstrengths; j++) { + strength = stepinfo->strengths[j]; + + if (mtd->writesize % step_size) + continue; + + nsteps = mtd->writesize / step_size; + + ecc_bytes = caps->calc_ecc_bytes(step_size, strength); + if (WARN_ON_ONCE(ecc_bytes < 0)) + continue; + + if (ecc_bytes * nsteps > oobavail) + continue; + + corr = strength * nsteps; + + /* + * If the number of correctable bits is the same, + * bigger step_size has more reliability. + */ + if (corr > best_corr || + (corr == best_corr && step_size > best_step)) { + best_corr = corr; + best_step = step_size; + best_strength = strength; + best_ecc_bytes = ecc_bytes; + } + } + } + + if (!best_corr) + return -ENOTSUPP; + + chip->ecc.size = best_step; + chip->ecc.strength = best_strength; + chip->ecc.bytes = best_ecc_bytes; + + return 0; +} + +/** + * nand_ecc_choose_conf - Set the ECC strength and ECC step size + * @chip: nand chip info structure + * @caps: ECC engine caps info structure + * @oobavail: OOB size that the ECC engine can use + * + * Choose the ECC configuration according to following logic. + * + * 1. If both ECC step size and ECC strength are already set (usually by DT) + * then check if it is supported by this controller. + * 2. If the user provided the nand-ecc-maximize property, then select maximum + * ECC strength. + * 3. Otherwise, try to match the ECC step size and ECC strength closest + * to the chip's requirement. If available OOB size can't fit the chip + * requirement then fallback to the maximum ECC step size and ECC strength. + * + * On success, the chosen ECC settings are set. + */ +int nand_ecc_choose_conf(struct nand_chip *chip, + const struct nand_ecc_caps *caps, int oobavail) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_device *nanddev = mtd_to_nanddev(mtd); + + if (WARN_ON(oobavail < 0 || oobavail > mtd->oobsize)) + return -EINVAL; + + if (chip->ecc.size && chip->ecc.strength) + return nand_check_ecc_caps(chip, caps, oobavail); + + if (nanddev->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH) + return nand_maximize_ecc(chip, caps, oobavail); + + if (!nand_match_ecc_req(chip, caps, oobavail)) + return 0; + + return nand_maximize_ecc(chip, caps, oobavail); +} +EXPORT_SYMBOL_GPL(nand_ecc_choose_conf); + +static int rawnand_erase(struct nand_device *nand, const struct nand_pos *pos) +{ + struct nand_chip *chip = container_of(nand, struct nand_chip, + base); + unsigned int eb = nanddev_pos_to_row(nand, pos); + int ret; + + eb >>= nand->rowconv.eraseblock_addr_shift; + + nand_select_target(chip, pos->target); + ret = nand_erase_op(chip, eb); + nand_deselect_target(chip); + + return ret; +} + +static int rawnand_markbad(struct nand_device *nand, + const struct nand_pos *pos) +{ + struct nand_chip *chip = container_of(nand, struct nand_chip, + base); + + return nand_markbad_bbm(chip, nanddev_pos_to_offs(nand, pos)); +} + +static bool rawnand_isbad(struct nand_device *nand, const struct nand_pos *pos) +{ + struct nand_chip *chip = container_of(nand, struct nand_chip, + base); + int ret; + + nand_select_target(chip, pos->target); + ret = nand_isbad_bbm(chip, nanddev_pos_to_offs(nand, pos)); + nand_deselect_target(chip); + + return ret; +} + +static const struct nand_ops rawnand_ops = { + .erase = rawnand_erase, + .markbad = rawnand_markbad, + .isbad = rawnand_isbad, +}; + +/** + * nand_scan_tail - Scan for the NAND device + * @chip: NAND chip object + * + * This is the second phase of the normal nand_scan() function. It fills out + * all the uninitialized function pointers with the defaults and scans for a + * bad block table if appropriate. + */ +static int nand_scan_tail(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_device *base = &chip->base; + struct nand_ecc_ctrl *ecc = &chip->ecc; + int ret, i; + + /* New bad blocks should be marked in OOB, flash-based BBT, or both */ + if (WARN_ON((chip->bbt_options & NAND_BBT_NO_OOB_BBM) && + !(chip->bbt_options & NAND_BBT_USE_FLASH))) { + return -EINVAL; + } + + chip->data_buf = kmalloc(mtd->writesize + mtd->oobsize, GFP_KERNEL); + if (!chip->data_buf) + return -ENOMEM; + + /* + * FIXME: some NAND manufacturer drivers expect the first die to be + * selected when manufacturer->init() is called. They should be fixed + * to explictly select the relevant die when interacting with the NAND + * chip. + */ + nand_select_target(chip, 0); + ret = nand_manufacturer_init(chip); + nand_deselect_target(chip); + if (ret) + goto err_free_buf; + + /* Set the internal oob buffer location, just after the page data */ + chip->oob_poi = chip->data_buf + mtd->writesize; + + /* + * If no default placement scheme is given, select an appropriate one. + */ + if (!mtd->ooblayout && + !(ecc->engine_type == NAND_ECC_ENGINE_TYPE_SOFT && + ecc->algo == NAND_ECC_ALGO_BCH) && + !(ecc->engine_type == NAND_ECC_ENGINE_TYPE_SOFT && + ecc->algo == NAND_ECC_ALGO_HAMMING)) { + switch (mtd->oobsize) { + case 8: + case 16: + mtd_set_ooblayout(mtd, nand_get_small_page_ooblayout()); + break; + case 64: + case 128: + mtd_set_ooblayout(mtd, + nand_get_large_page_hamming_ooblayout()); + break; + default: + /* + * Expose the whole OOB area to users if ECC_NONE + * is passed. We could do that for all kind of + * ->oobsize, but we must keep the old large/small + * page with ECC layout when ->oobsize <= 128 for + * compatibility reasons. + */ + if (ecc->engine_type == NAND_ECC_ENGINE_TYPE_NONE) { + mtd_set_ooblayout(mtd, + nand_get_large_page_ooblayout()); + break; + } + + WARN(1, "No oob scheme defined for oobsize %d\n", + mtd->oobsize); + ret = -EINVAL; + goto err_nand_manuf_cleanup; + } + } + + /* + * Check ECC mode, default to software if 3byte/512byte hardware ECC is + * selected and we have 256 byte pagesize fallback to software ECC + */ + + switch (ecc->engine_type) { + case NAND_ECC_ENGINE_TYPE_ON_HOST: + ret = nand_set_ecc_on_host_ops(chip); + if (ret) + goto err_nand_manuf_cleanup; + + if (mtd->writesize >= ecc->size) { + if (!ecc->strength) { + WARN(1, "Driver must set ecc.strength when using hardware ECC\n"); + ret = -EINVAL; + goto err_nand_manuf_cleanup; + } + break; + } + pr_warn("%d byte HW ECC not possible on %d byte page size, fallback to SW ECC\n", + ecc->size, mtd->writesize); + ecc->engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + ecc->algo = NAND_ECC_ALGO_HAMMING; + fallthrough; + + case NAND_ECC_ENGINE_TYPE_SOFT: + ret = nand_set_ecc_soft_ops(chip); + if (ret) + goto err_nand_manuf_cleanup; + break; + + case NAND_ECC_ENGINE_TYPE_ON_DIE: + if (!ecc->read_page || !ecc->write_page) { + WARN(1, "No ECC functions supplied; on-die ECC not possible\n"); + ret = -EINVAL; + goto err_nand_manuf_cleanup; + } + if (!ecc->read_oob) + ecc->read_oob = nand_read_oob_std; + if (!ecc->write_oob) + ecc->write_oob = nand_write_oob_std; + break; + + case NAND_ECC_ENGINE_TYPE_NONE: + pr_warn("NAND_ECC_ENGINE_TYPE_NONE selected by board driver. This is not recommended!\n"); + ecc->read_page = nand_read_page_raw; + ecc->write_page = nand_write_page_raw; + ecc->read_oob = nand_read_oob_std; + ecc->read_page_raw = nand_read_page_raw; + ecc->write_page_raw = nand_write_page_raw; + ecc->write_oob = nand_write_oob_std; + ecc->size = mtd->writesize; + ecc->bytes = 0; + ecc->strength = 0; + break; + + default: + WARN(1, "Invalid NAND_ECC_MODE %d\n", ecc->engine_type); + ret = -EINVAL; + goto err_nand_manuf_cleanup; + } + + if (ecc->correct || ecc->calculate) { + ecc->calc_buf = kmalloc(mtd->oobsize, GFP_KERNEL); + ecc->code_buf = kmalloc(mtd->oobsize, GFP_KERNEL); + if (!ecc->calc_buf || !ecc->code_buf) { + ret = -ENOMEM; + goto err_nand_manuf_cleanup; + } + } + + /* For many systems, the standard OOB write also works for raw */ + if (!ecc->read_oob_raw) + ecc->read_oob_raw = ecc->read_oob; + if (!ecc->write_oob_raw) + ecc->write_oob_raw = ecc->write_oob; + + /* Propagate ECC info to the generic NAND and MTD layers */ + mtd->ecc_strength = ecc->strength; + if (!base->ecc.ctx.conf.strength) + base->ecc.ctx.conf.strength = ecc->strength; + mtd->ecc_step_size = ecc->size; + if (!base->ecc.ctx.conf.step_size) + base->ecc.ctx.conf.step_size = ecc->size; + + /* + * Set the number of read / write steps for one page depending on ECC + * mode. + */ + if (!ecc->steps) + ecc->steps = mtd->writesize / ecc->size; + if (!base->ecc.ctx.nsteps) + base->ecc.ctx.nsteps = ecc->steps; + + /* + * 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; + chip->base.ecc.ctx.total = ecc->total; + } + + if (ecc->total > mtd->oobsize) { + WARN(1, "Total number of ECC bytes exceeded oobsize\n"); + ret = -EINVAL; + goto err_nand_manuf_cleanup; + } + + /* + * The number of bytes available for a client to place data into + * the out of band area. + */ + ret = mtd_ooblayout_count_freebytes(mtd); + if (ret < 0) + ret = 0; + + mtd->oobavail = ret; + + /* ECC sanity check: warn if it's too weak */ + if (!nand_ecc_is_strong_enough(&chip->base)) + pr_warn("WARNING: %s: the ECC used on your system (%db/%dB) is too weak compared to the one required by the NAND chip (%db/%dB)\n", + mtd->name, chip->ecc.strength, chip->ecc.size, + nanddev_get_ecc_requirements(&chip->base)->strength, + nanddev_get_ecc_requirements(&chip->base)->step_size); + + /* Allow subpage writes up to ecc.steps. Not possible for MLC flash */ + if (!(chip->options & NAND_NO_SUBPAGE_WRITE) && nand_is_slc(chip)) { + switch (ecc->steps) { + case 2: + mtd->subpage_sft = 1; + break; + case 4: + case 8: + case 16: + mtd->subpage_sft = 2; + break; + } + } + chip->subpagesize = mtd->writesize >> mtd->subpage_sft; + + /* Invalidate the pagebuffer reference */ + chip->pagecache.page = -1; + + /* Large page NAND with SOFT_ECC should support subpage reads */ + switch (ecc->engine_type) { + case NAND_ECC_ENGINE_TYPE_SOFT: + if (chip->page_shift > 9) + chip->options |= NAND_SUBPAGE_READ; + break; + + default: + break; + } + + ret = nanddev_init(&chip->base, &rawnand_ops, mtd->owner); + if (ret) + goto err_nand_manuf_cleanup; + + /* Adjust the MTD_CAP_ flags when NAND_ROM is set. */ + if (chip->options & NAND_ROM) + mtd->flags = MTD_CAP_ROM; + + /* Fill in remaining MTD driver data */ + mtd->_erase = nand_erase; + mtd->_point = NULL; + mtd->_unpoint = NULL; + mtd->_panic_write = panic_nand_write; + mtd->_read_oob = nand_read_oob; + mtd->_write_oob = nand_write_oob; + mtd->_sync = nand_sync; + mtd->_lock = nand_lock; + mtd->_unlock = nand_unlock; + mtd->_suspend = nand_suspend; + mtd->_resume = nand_resume; + mtd->_reboot = nand_shutdown; + mtd->_block_isreserved = nand_block_isreserved; + mtd->_block_isbad = nand_block_isbad; + mtd->_block_markbad = nand_block_markbad; + mtd->_max_bad_blocks = nanddev_mtd_max_bad_blocks; + + /* + * Initialize bitflip_threshold to its default prior scan_bbt() call. + * scan_bbt() might invoke mtd_read(), thus bitflip_threshold must be + * properly set. + */ + if (!mtd->bitflip_threshold) + mtd->bitflip_threshold = DIV_ROUND_UP(mtd->ecc_strength * 3, 4); + + /* Find the fastest data interface for this chip */ + ret = nand_choose_interface_config(chip); + if (ret) + goto err_nanddev_cleanup; + + /* Enter fastest possible mode on all dies. */ + for (i = 0; i < nanddev_ntargets(&chip->base); i++) { + ret = nand_setup_interface(chip, i); + if (ret) + goto err_free_interface_config; + } + + rawnand_late_check_supported_ops(chip); + + /* + * Look for secure regions in the NAND chip. These regions are supposed + * to be protected by a secure element like Trustzone. So the read/write + * accesses to these regions will be blocked in the runtime by this + * driver. + */ + ret = of_get_nand_secure_regions(chip); + if (ret) + goto err_free_interface_config; + + /* Check, if we should skip the bad block table scan */ + if (chip->options & NAND_SKIP_BBTSCAN) + return 0; + + /* Build bad block table */ + ret = nand_create_bbt(chip); + if (ret) + goto err_free_secure_regions; + + return 0; + +err_free_secure_regions: + kfree(chip->secure_regions); + +err_free_interface_config: + kfree(chip->best_interface_config); + +err_nanddev_cleanup: + nanddev_cleanup(&chip->base); + +err_nand_manuf_cleanup: + nand_manufacturer_cleanup(chip); + +err_free_buf: + kfree(chip->data_buf); + kfree(ecc->code_buf); + kfree(ecc->calc_buf); + + return ret; +} + +static int nand_attach(struct nand_chip *chip) +{ + if (chip->controller->ops && chip->controller->ops->attach_chip) + return chip->controller->ops->attach_chip(chip); + + return 0; +} + +static void nand_detach(struct nand_chip *chip) +{ + if (chip->controller->ops && chip->controller->ops->detach_chip) + chip->controller->ops->detach_chip(chip); +} + +/** + * nand_scan_with_ids - [NAND Interface] Scan for the NAND device + * @chip: NAND chip object + * @maxchips: number of chips to scan for. + * @ids: optional flash IDs table + * + * This fills out all the uninitialized function pointers with the defaults. + * The flash ID is read and the mtd/chip structures are filled with the + * appropriate values. + */ +int nand_scan_with_ids(struct nand_chip *chip, unsigned int maxchips, + struct nand_flash_dev *ids) +{ + int ret; + + if (!maxchips) + return -EINVAL; + + ret = nand_scan_ident(chip, maxchips, ids); + if (ret) + return ret; + + ret = nand_attach(chip); + if (ret) + goto cleanup_ident; + + ret = nand_scan_tail(chip); + if (ret) + goto detach_chip; + + return 0; + +detach_chip: + nand_detach(chip); +cleanup_ident: + nand_scan_ident_cleanup(chip); + + return ret; +} +EXPORT_SYMBOL(nand_scan_with_ids); + +/** + * nand_cleanup - [NAND Interface] Free resources held by the NAND device + * @chip: NAND chip object + */ +void nand_cleanup(struct nand_chip *chip) +{ + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT) { + if (chip->ecc.algo == NAND_ECC_ALGO_HAMMING) + rawnand_sw_hamming_cleanup(chip); + else if (chip->ecc.algo == NAND_ECC_ALGO_BCH) + rawnand_sw_bch_cleanup(chip); + } + + nanddev_cleanup(&chip->base); + + /* Free secure regions data */ + kfree(chip->secure_regions); + + /* Free bad block table memory */ + kfree(chip->bbt); + kfree(chip->data_buf); + kfree(chip->ecc.code_buf); + kfree(chip->ecc.calc_buf); + + /* Free bad block descriptor memory */ + if (chip->badblock_pattern && chip->badblock_pattern->options + & NAND_BBT_DYNAMICSTRUCT) + kfree(chip->badblock_pattern); + + /* Free the data interface */ + kfree(chip->best_interface_config); + + /* Free manufacturer priv data. */ + nand_manufacturer_cleanup(chip); + + /* Free controller specific allocations after chip identification */ + nand_detach(chip); + + /* Free identification phase allocations */ + nand_scan_ident_cleanup(chip); +} + +EXPORT_SYMBOL_GPL(nand_cleanup); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Steven J. Hill "); +MODULE_AUTHOR("Thomas Gleixner "); +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 new file mode 100644 index 000000000..b0e7592a2 --- /dev/null +++ b/drivers/mtd/nand/raw/nand_bbt.c @@ -0,0 +1,1490 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Overview: + * Bad block table support for the NAND driver + * + * Copyright © 2004 Thomas Gleixner (tglx@kernel.org) + * + * Description: + * + * When nand_scan_bbt is called, then it tries to find the bad block table + * depending on the options in the BBT descriptor(s). If no flash based BBT + * (NAND_BBT_USE_FLASH) is specified then the device is scanned for factory + * marked good / bad blocks. This information is used to create a memory BBT. + * Once a new bad block is discovered then the "factory" information is updated + * on the device. + * If a flash based BBT is specified then the function first tries to find the + * BBT on flash. If a BBT is found then the contents are read and the memory + * based BBT is created. If a mirrored BBT is selected then the mirror is + * searched too and the versions are compared. If the mirror has a greater + * version number, then the mirror BBT is used to build the memory based BBT. + * If the tables are not versioned, then we "or" the bad block information. + * If one of the BBTs is out of date or does not exist it is (re)created. + * If no BBT exists at all then the device is scanned for factory marked + * good / bad blocks and the bad block tables are created. + * + * For manufacturer created BBTs like the one found on M-SYS DOC devices + * the BBT is searched and read but never created + * + * The auto generated bad block table is located in the last good blocks + * of the device. The table is mirrored, so it can be updated eventually. + * The table is marked in the OOB area with an ident pattern and a version + * number which indicates which of both tables is more up to date. If the NAND + * controller needs the complete OOB area for the ECC information then the + * option NAND_BBT_NO_OOB should be used (along with NAND_BBT_USE_FLASH, of + * course): it moves the ident pattern and the version byte into the data area + * and the OOB area will remain untouched. + * + * The table uses 2 bits per block + * 11b: block is good + * 00b: block is factory marked bad + * 01b, 10b: block is marked bad due to wear + * + * The memory bad block table uses the following scheme: + * 00b: block is good + * 01b: block is marked bad due to wear + * 10b: block is reserved (to protect the bbt area) + * 11b: block is factory marked bad + * + * Multichip devices like DOC store the bad block info per floor. + * + * Following assumptions are made: + * - bbts start at a page boundary, if autolocated on a block boundary + * - the space necessary for a bbt in FLASH does not exceed a block boundary + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "internals.h" + +#define BBT_BLOCK_GOOD 0x00 +#define BBT_BLOCK_WORN 0x01 +#define BBT_BLOCK_RESERVED 0x02 +#define BBT_BLOCK_FACTORY_BAD 0x03 + +#define BBT_ENTRY_MASK 0x03 +#define BBT_ENTRY_SHIFT 2 + +static inline uint8_t bbt_get_entry(struct nand_chip *chip, int block) +{ + uint8_t entry = chip->bbt[block >> BBT_ENTRY_SHIFT]; + entry >>= (block & BBT_ENTRY_MASK) * 2; + return entry & BBT_ENTRY_MASK; +} + +static inline void bbt_mark_entry(struct nand_chip *chip, int block, + uint8_t mark) +{ + uint8_t msk = (mark & BBT_ENTRY_MASK) << ((block & BBT_ENTRY_MASK) * 2); + chip->bbt[block >> BBT_ENTRY_SHIFT] |= msk; +} + +static int check_pattern_no_oob(uint8_t *buf, struct nand_bbt_descr *td) +{ + if (memcmp(buf, td->pattern, td->len)) + return -1; + return 0; +} + +/** + * check_pattern - [GENERIC] check if a pattern is in the buffer + * @buf: the buffer to search + * @len: the length of buffer to search + * @paglen: the pagelength + * @td: search pattern descriptor + * + * Check for a pattern at the given place. Used to search bad block tables and + * good / bad block identifiers. + */ +static int check_pattern(uint8_t *buf, int len, int paglen, struct nand_bbt_descr *td) +{ + if (td->options & NAND_BBT_NO_OOB) + return check_pattern_no_oob(buf, td); + + /* Compare the pattern */ + if (memcmp(buf + paglen + td->offs, td->pattern, td->len)) + return -1; + + return 0; +} + +/** + * check_short_pattern - [GENERIC] check if a pattern is in the buffer + * @buf: the buffer to search + * @td: search pattern descriptor + * + * Check for a pattern at the given place. Used to search bad block tables and + * good / bad block identifiers. Same as check_pattern, but no optional empty + * check. + */ +static int check_short_pattern(uint8_t *buf, struct nand_bbt_descr *td) +{ + /* Compare the pattern */ + if (memcmp(buf + td->offs, td->pattern, td->len)) + return -1; + return 0; +} + +/** + * add_marker_len - compute the length of the marker in data area + * @td: BBT descriptor used for computation + * + * The length will be 0 if the marker is located in OOB area. + */ +static u32 add_marker_len(struct nand_bbt_descr *td) +{ + u32 len; + + if (!(td->options & NAND_BBT_NO_OOB)) + return 0; + + len = td->len; + if (td->options & NAND_BBT_VERSION) + len++; + return len; +} + +/** + * read_bbt - [GENERIC] Read the bad block table starting from page + * @this: NAND chip object + * @buf: temporary buffer + * @page: the starting page + * @num: the number of bbt descriptors to read + * @td: the bbt describtion table + * @offs: block number offset in the table + * + * Read the bad block table starting from page. + */ +static int read_bbt(struct nand_chip *this, uint8_t *buf, int page, int num, + struct nand_bbt_descr *td, int offs) +{ + struct mtd_info *mtd = nand_to_mtd(this); + int res, ret = 0, i, j, act = 0; + size_t retlen, len, totlen; + loff_t from; + int bits = td->options & NAND_BBT_NRBITS_MSK; + uint8_t msk = (uint8_t)((1 << bits) - 1); + u32 marker_len; + int reserved_block_code = td->reserved_block_code; + + totlen = (num * bits) >> 3; + marker_len = add_marker_len(td); + from = ((loff_t)page) << this->page_shift; + + while (totlen) { + len = min(totlen, (size_t)(1 << this->bbt_erase_shift)); + if (marker_len) { + /* + * In case the BBT marker is not in the OOB area it + * will be just in the first page. + */ + len -= marker_len; + from += marker_len; + marker_len = 0; + } + res = mtd_read(mtd, from, len, &retlen, buf); + if (res < 0) { + if (mtd_is_eccerr(res)) { + pr_info("nand_bbt: ECC error in BBT at 0x%012llx\n", + from & ~mtd->writesize); + return res; + } else if (mtd_is_bitflip(res)) { + pr_info("nand_bbt: corrected error in BBT at 0x%012llx\n", + from & ~mtd->writesize); + ret = res; + } else { + pr_info("nand_bbt: error reading BBT\n"); + return res; + } + } + + /* Analyse data */ + for (i = 0; i < len; i++) { + uint8_t dat = buf[i]; + for (j = 0; j < 8; j += bits, act++) { + uint8_t tmp = (dat >> j) & msk; + if (tmp == msk) + continue; + if (reserved_block_code && (tmp == reserved_block_code)) { + pr_info("nand_read_bbt: reserved block at 0x%012llx\n", + (loff_t)(offs + act) << + this->bbt_erase_shift); + bbt_mark_entry(this, offs + act, + BBT_BLOCK_RESERVED); + mtd->ecc_stats.bbtblocks++; + continue; + } + /* + * Leave it for now, if it's matured we can + * move this message to pr_debug. + */ + pr_info("nand_read_bbt: bad block at 0x%012llx\n", + (loff_t)(offs + act) << + this->bbt_erase_shift); + /* Factory marked bad or worn out? */ + if (tmp == 0) + bbt_mark_entry(this, offs + act, + BBT_BLOCK_FACTORY_BAD); + else + bbt_mark_entry(this, offs + act, + BBT_BLOCK_WORN); + mtd->ecc_stats.badblocks++; + } + } + totlen -= len; + from += len; + } + return ret; +} + +/** + * read_abs_bbt - [GENERIC] Read the bad block table starting at a given page + * @this: NAND chip object + * @buf: temporary buffer + * @td: descriptor for the bad block table + * @chip: read the table for a specific chip, -1 read all chips; applies only if + * NAND_BBT_PERCHIP option is set + * + * Read the bad block table for all chips starting at a given page. We assume + * that the bbt bits are in consecutive order. + */ +static int read_abs_bbt(struct nand_chip *this, uint8_t *buf, + struct nand_bbt_descr *td, int chip) +{ + struct mtd_info *mtd = nand_to_mtd(this); + u64 targetsize = nanddev_target_size(&this->base); + int res = 0, i; + + if (td->options & NAND_BBT_PERCHIP) { + int offs = 0; + for (i = 0; i < nanddev_ntargets(&this->base); i++) { + if (chip == -1 || chip == i) + res = read_bbt(this, buf, td->pages[i], + targetsize >> this->bbt_erase_shift, + td, offs); + if (res) + return res; + offs += targetsize >> this->bbt_erase_shift; + } + } else { + res = read_bbt(this, buf, td->pages[0], + mtd->size >> this->bbt_erase_shift, td, 0); + if (res) + return res; + } + return 0; +} + +/* BBT marker is in the first page, no OOB */ +static int scan_read_data(struct nand_chip *this, uint8_t *buf, loff_t offs, + struct nand_bbt_descr *td) +{ + struct mtd_info *mtd = nand_to_mtd(this); + size_t retlen; + size_t len; + + len = td->len; + if (td->options & NAND_BBT_VERSION) + len++; + + return mtd_read(mtd, offs, len, &retlen, buf); +} + +/** + * scan_read_oob - [GENERIC] Scan data+OOB region to buffer + * @this: NAND chip object + * @buf: temporary buffer + * @offs: offset at which to scan + * @len: length of data region to read + * + * Scan read data from data+OOB. May traverse multiple pages, interleaving + * page,OOB,page,OOB,... in buf. Completes transfer and returns the "strongest" + * ECC condition (error or bitflip). May quit on the first (non-ECC) error. + */ +static int scan_read_oob(struct nand_chip *this, uint8_t *buf, loff_t offs, + size_t len) +{ + struct mtd_info *mtd = nand_to_mtd(this); + struct mtd_oob_ops ops = { }; + int res, ret = 0; + + ops.mode = MTD_OPS_PLACE_OOB; + ops.ooboffs = 0; + ops.ooblen = mtd->oobsize; + + while (len > 0) { + ops.datbuf = buf; + ops.len = min(len, (size_t)mtd->writesize); + ops.oobbuf = buf + ops.len; + + res = mtd_read_oob(mtd, offs, &ops); + if (res) { + if (!mtd_is_bitflip_or_eccerr(res)) + return res; + else if (mtd_is_eccerr(res) || !ret) + ret = res; + } + + buf += mtd->oobsize + mtd->writesize; + len -= mtd->writesize; + offs += mtd->writesize; + } + return ret; +} + +static int scan_read(struct nand_chip *this, uint8_t *buf, loff_t offs, + size_t len, struct nand_bbt_descr *td) +{ + if (td->options & NAND_BBT_NO_OOB) + return scan_read_data(this, buf, offs, td); + else + return scan_read_oob(this, buf, offs, len); +} + +/* Scan write data with oob to flash */ +static int scan_write_bbt(struct nand_chip *this, loff_t offs, size_t len, + uint8_t *buf, uint8_t *oob) +{ + struct mtd_info *mtd = nand_to_mtd(this); + struct mtd_oob_ops ops = { }; + + ops.mode = MTD_OPS_PLACE_OOB; + ops.ooboffs = 0; + ops.ooblen = mtd->oobsize; + ops.datbuf = buf; + ops.oobbuf = oob; + ops.len = len; + + return mtd_write_oob(mtd, offs, &ops); +} + +static u32 bbt_get_ver_offs(struct nand_chip *this, struct nand_bbt_descr *td) +{ + struct mtd_info *mtd = nand_to_mtd(this); + u32 ver_offs = td->veroffs; + + if (!(td->options & NAND_BBT_NO_OOB)) + ver_offs += mtd->writesize; + return ver_offs; +} + +/** + * read_abs_bbts - [GENERIC] Read the bad block table(s) for all chips starting at a given page + * @this: NAND chip object + * @buf: temporary buffer + * @td: descriptor for the bad block table + * @md: descriptor for the bad block table mirror + * + * Read the bad block table(s) for all chips starting at a given page. We + * assume that the bbt bits are in consecutive order. + */ +static void read_abs_bbts(struct nand_chip *this, uint8_t *buf, + struct nand_bbt_descr *td, struct nand_bbt_descr *md) +{ + struct mtd_info *mtd = nand_to_mtd(this); + + /* Read the primary version, if available */ + if (td->options & NAND_BBT_VERSION) { + scan_read(this, buf, (loff_t)td->pages[0] << this->page_shift, + mtd->writesize, td); + td->version[0] = buf[bbt_get_ver_offs(this, td)]; + pr_info("Bad block table at page %d, version 0x%02X\n", + td->pages[0], td->version[0]); + } + + /* Read the mirror version, if available */ + if (md && (md->options & NAND_BBT_VERSION)) { + scan_read(this, buf, (loff_t)md->pages[0] << this->page_shift, + mtd->writesize, md); + md->version[0] = buf[bbt_get_ver_offs(this, md)]; + pr_info("Bad block table at page %d, version 0x%02X\n", + md->pages[0], md->version[0]); + } +} + +/* Scan a given block partially */ +static int scan_block_fast(struct nand_chip *this, struct nand_bbt_descr *bd, + loff_t offs, uint8_t *buf) +{ + struct mtd_info *mtd = nand_to_mtd(this); + + struct mtd_oob_ops ops = { }; + int ret, page_offset; + + ops.ooblen = mtd->oobsize; + ops.oobbuf = buf; + ops.ooboffs = 0; + ops.datbuf = NULL; + ops.mode = MTD_OPS_PLACE_OOB; + + page_offset = nand_bbm_get_next_page(this, 0); + + while (page_offset >= 0) { + /* + * Read the full oob until read_oob is fixed to handle single + * byte reads for 16 bit buswidth. + */ + ret = mtd_read_oob(mtd, offs + (page_offset * mtd->writesize), + &ops); + /* Ignore ECC errors when checking for BBM */ + if (ret && !mtd_is_bitflip_or_eccerr(ret)) + return ret; + + if (check_short_pattern(buf, bd)) + return 1; + + page_offset = nand_bbm_get_next_page(this, page_offset + 1); + } + + return 0; +} + +/* Check if a potential BBT block is marked as bad */ +static int bbt_block_checkbad(struct nand_chip *this, struct nand_bbt_descr *td, + loff_t offs, uint8_t *buf) +{ + struct nand_bbt_descr *bd = this->badblock_pattern; + + /* + * No need to check for a bad BBT block if the BBM area overlaps with + * the bad block table marker area in OOB since writing a BBM here + * invalidates the bad block table marker anyway. + */ + if (!(td->options & NAND_BBT_NO_OOB) && + td->offs >= bd->offs && td->offs < bd->offs + bd->len) + return 0; + + /* + * There is no point in checking for a bad block marker if writing + * such marker is not supported + */ + if (this->bbt_options & NAND_BBT_NO_OOB_BBM || + this->options & NAND_NO_BBM_QUIRK) + return 0; + + if (scan_block_fast(this, bd, offs, buf) > 0) + return 1; + + return 0; +} + +/** + * create_bbt - [GENERIC] Create a bad block table by scanning the device + * @this: NAND chip object + * @buf: temporary buffer + * @bd: descriptor for the good/bad block search pattern + * @chip: create the table for a specific chip, -1 read all chips; applies only + * if NAND_BBT_PERCHIP option is set + * + * Create a bad block table by scanning the device for the given good/bad block + * identify pattern. + */ +static int create_bbt(struct nand_chip *this, uint8_t *buf, + struct nand_bbt_descr *bd, int chip) +{ + u64 targetsize = nanddev_target_size(&this->base); + struct mtd_info *mtd = nand_to_mtd(this); + int i, numblocks, startblock; + loff_t from; + + pr_info("Scanning device for bad blocks\n"); + + if (chip == -1) { + numblocks = mtd->size >> this->bbt_erase_shift; + startblock = 0; + from = 0; + } else { + if (chip >= nanddev_ntargets(&this->base)) { + pr_warn("create_bbt(): chipnr (%d) > available chips (%d)\n", + chip + 1, nanddev_ntargets(&this->base)); + return -EINVAL; + } + numblocks = targetsize >> this->bbt_erase_shift; + startblock = chip * numblocks; + numblocks += startblock; + from = (loff_t)startblock << this->bbt_erase_shift; + } + + for (i = startblock; i < numblocks; i++) { + int ret; + + BUG_ON(bd->options & NAND_BBT_NO_OOB); + + ret = scan_block_fast(this, bd, from, buf); + if (ret < 0) + return ret; + + if (ret) { + bbt_mark_entry(this, i, BBT_BLOCK_FACTORY_BAD); + pr_warn("Bad eraseblock %d at 0x%012llx\n", + i, (unsigned long long)from); + mtd->ecc_stats.badblocks++; + } + + from += (1 << this->bbt_erase_shift); + } + return 0; +} + +/** + * search_bbt - [GENERIC] scan the device for a specific bad block table + * @this: NAND chip object + * @buf: temporary buffer + * @td: descriptor for the bad block table + * + * Read the bad block table by searching for a given ident pattern. Search is + * preformed either from the beginning up or from the end of the device + * downwards. The search starts always at the start of a block. If the option + * NAND_BBT_PERCHIP is given, each chip is searched for a bbt, which contains + * the bad block information of this chip. This is necessary to provide support + * for certain DOC devices. + * + * The bbt ident pattern resides in the oob area of the first page in a block. + */ +static int search_bbt(struct nand_chip *this, uint8_t *buf, + struct nand_bbt_descr *td) +{ + u64 targetsize = nanddev_target_size(&this->base); + struct mtd_info *mtd = nand_to_mtd(this); + int i, chips; + int startblock, block, dir; + int scanlen = mtd->writesize + mtd->oobsize; + int bbtblocks; + int blocktopage = this->bbt_erase_shift - this->page_shift; + + /* Search direction top -> down? */ + if (td->options & NAND_BBT_LASTBLOCK) { + startblock = (mtd->size >> this->bbt_erase_shift) - 1; + dir = -1; + } else { + startblock = 0; + dir = 1; + } + + /* Do we have a bbt per chip? */ + if (td->options & NAND_BBT_PERCHIP) { + chips = nanddev_ntargets(&this->base); + bbtblocks = targetsize >> this->bbt_erase_shift; + startblock &= bbtblocks - 1; + } else { + chips = 1; + } + + for (i = 0; i < chips; i++) { + /* Reset version information */ + td->version[i] = 0; + td->pages[i] = -1; + /* Scan the maximum number of blocks */ + for (block = 0; block < td->maxblocks; block++) { + + int actblock = startblock + dir * block; + loff_t offs = (loff_t)actblock << this->bbt_erase_shift; + + /* Check if block is marked bad */ + if (bbt_block_checkbad(this, td, offs, buf)) + continue; + + /* Read first page */ + scan_read(this, buf, offs, mtd->writesize, td); + if (!check_pattern(buf, scanlen, mtd->writesize, td)) { + td->pages[i] = actblock << blocktopage; + if (td->options & NAND_BBT_VERSION) { + offs = bbt_get_ver_offs(this, td); + td->version[i] = buf[offs]; + } + break; + } + } + startblock += targetsize >> this->bbt_erase_shift; + } + /* Check, if we found a bbt for each requested chip */ + for (i = 0; i < chips; i++) { + if (td->pages[i] == -1) + pr_warn("Bad block table not found for chip %d\n", i); + else + pr_info("Bad block table found at page %d, version 0x%02X\n", + td->pages[i], td->version[i]); + } + return 0; +} + +/** + * search_read_bbts - [GENERIC] scan the device for bad block table(s) + * @this: NAND chip object + * @buf: temporary buffer + * @td: descriptor for the bad block table + * @md: descriptor for the bad block table mirror + * + * Search and read the bad block table(s). + */ +static void search_read_bbts(struct nand_chip *this, uint8_t *buf, + struct nand_bbt_descr *td, + struct nand_bbt_descr *md) +{ + /* Search the primary table */ + search_bbt(this, buf, td); + + /* Search the mirror table */ + if (md) + search_bbt(this, buf, md); +} + +/** + * get_bbt_block - Get the first valid eraseblock suitable to store a BBT + * @this: the NAND device + * @td: the BBT description + * @md: the mirror BBT descriptor + * @chip: the CHIP selector + * + * This functions returns a positive block number pointing a valid eraseblock + * suitable to store a BBT (i.e. in the range reserved for BBT), or -ENOSPC if + * all blocks are already used of marked bad. If td->pages[chip] was already + * pointing to a valid block we re-use it, otherwise we search for the next + * valid one. + */ +static int get_bbt_block(struct nand_chip *this, struct nand_bbt_descr *td, + struct nand_bbt_descr *md, int chip) +{ + u64 targetsize = nanddev_target_size(&this->base); + int startblock, dir, page, numblocks, i; + + /* + * There was already a version of the table, reuse the page. This + * applies for absolute placement too, as we have the page number in + * td->pages. + */ + if (td->pages[chip] != -1) + return td->pages[chip] >> + (this->bbt_erase_shift - this->page_shift); + + numblocks = (int)(targetsize >> this->bbt_erase_shift); + if (!(td->options & NAND_BBT_PERCHIP)) + numblocks *= nanddev_ntargets(&this->base); + + /* + * Automatic placement of the bad block table. Search direction + * top -> down? + */ + if (td->options & NAND_BBT_LASTBLOCK) { + startblock = numblocks * (chip + 1) - 1; + dir = -1; + } else { + startblock = chip * numblocks; + dir = 1; + } + + for (i = 0; i < td->maxblocks; i++) { + int block = startblock + dir * i; + + /* Check, if the block is bad */ + switch (bbt_get_entry(this, block)) { + case BBT_BLOCK_WORN: + case BBT_BLOCK_FACTORY_BAD: + continue; + } + + page = block << (this->bbt_erase_shift - this->page_shift); + + /* Check, if the block is used by the mirror table */ + if (!md || md->pages[chip] != page) + return block; + } + + return -ENOSPC; +} + +/** + * mark_bbt_block_bad - Mark one of the block reserved for BBT bad + * @this: the NAND device + * @td: the BBT description + * @chip: the CHIP selector + * @block: the BBT block to mark + * + * Blocks reserved for BBT can become bad. This functions is an helper to mark + * such blocks as bad. It takes care of updating the in-memory BBT, marking the + * block as bad using a bad block marker and invalidating the associated + * td->pages[] entry. + */ +static void mark_bbt_block_bad(struct nand_chip *this, + struct nand_bbt_descr *td, + int chip, int block) +{ + loff_t to; + int res; + + bbt_mark_entry(this, block, BBT_BLOCK_WORN); + + to = (loff_t)block << this->bbt_erase_shift; + res = nand_markbad_bbm(this, to); + if (res) + pr_warn("nand_bbt: error %d while marking block %d bad\n", + res, block); + + td->pages[chip] = -1; +} + +/** + * write_bbt - [GENERIC] (Re)write the bad block table + * @this: NAND chip object + * @buf: temporary buffer + * @td: descriptor for the bad block table + * @md: descriptor for the bad block table mirror + * @chipsel: selector for a specific chip, -1 for all + * + * (Re)write the bad block table. + */ +static int write_bbt(struct nand_chip *this, uint8_t *buf, + struct nand_bbt_descr *td, struct nand_bbt_descr *md, + int chipsel) +{ + u64 targetsize = nanddev_target_size(&this->base); + struct mtd_info *mtd = nand_to_mtd(this); + struct erase_info einfo; + int i, res, chip = 0; + int bits, page, offs, numblocks, sft, sftmsk; + int nrchips, pageoffs, ooboffs; + uint8_t msk[4]; + uint8_t rcode = td->reserved_block_code; + size_t retlen, len = 0; + loff_t to; + struct mtd_oob_ops ops = { }; + + ops.ooblen = mtd->oobsize; + ops.ooboffs = 0; + ops.datbuf = NULL; + ops.mode = MTD_OPS_PLACE_OOB; + + if (!rcode) + rcode = 0xff; + /* Write bad block table per chip rather than per device? */ + if (td->options & NAND_BBT_PERCHIP) { + numblocks = (int)(targetsize >> this->bbt_erase_shift); + /* Full device write or specific chip? */ + if (chipsel == -1) { + nrchips = nanddev_ntargets(&this->base); + } else { + nrchips = chipsel + 1; + chip = chipsel; + } + } else { + numblocks = (int)(mtd->size >> this->bbt_erase_shift); + nrchips = 1; + } + + /* Loop through the chips */ + while (chip < nrchips) { + int block; + + block = get_bbt_block(this, td, md, chip); + if (block < 0) { + pr_err("No space left to write bad block table\n"); + res = block; + goto outerr; + } + + /* + * get_bbt_block() returns a block number, shift the value to + * get a page number. + */ + page = block << (this->bbt_erase_shift - this->page_shift); + + /* Set up shift count and masks for the flash table */ + bits = td->options & NAND_BBT_NRBITS_MSK; + msk[2] = ~rcode; + switch (bits) { + case 1: sft = 3; sftmsk = 0x07; msk[0] = 0x00; msk[1] = 0x01; + msk[3] = 0x01; + break; + case 2: sft = 2; sftmsk = 0x06; msk[0] = 0x00; msk[1] = 0x01; + msk[3] = 0x03; + break; + case 4: sft = 1; sftmsk = 0x04; msk[0] = 0x00; msk[1] = 0x0C; + msk[3] = 0x0f; + break; + case 8: sft = 0; sftmsk = 0x00; msk[0] = 0x00; msk[1] = 0x0F; + msk[3] = 0xff; + break; + default: return -EINVAL; + } + + to = ((loff_t)page) << this->page_shift; + + /* Must we save the block contents? */ + if (td->options & NAND_BBT_SAVECONTENT) { + /* Make it block aligned */ + to &= ~(((loff_t)1 << this->bbt_erase_shift) - 1); + len = 1 << this->bbt_erase_shift; + res = mtd_read(mtd, to, len, &retlen, buf); + if (res < 0) { + if (retlen != len) { + pr_info("nand_bbt: error reading block for writing the bad block table\n"); + return res; + } + pr_warn("nand_bbt: ECC error while reading block for writing bad block table\n"); + } + /* Read oob data */ + ops.ooblen = (len >> this->page_shift) * mtd->oobsize; + ops.oobbuf = &buf[len]; + res = mtd_read_oob(mtd, to + mtd->writesize, &ops); + if (res < 0 || ops.oobretlen != ops.ooblen) + goto outerr; + + /* Calc the byte offset in the buffer */ + pageoffs = page - (int)(to >> this->page_shift); + offs = pageoffs << this->page_shift; + /* Preset the bbt area with 0xff */ + memset(&buf[offs], 0xff, (size_t)(numblocks >> sft)); + ooboffs = len + (pageoffs * mtd->oobsize); + + } else if (td->options & NAND_BBT_NO_OOB) { + ooboffs = 0; + offs = td->len; + /* The version byte */ + if (td->options & NAND_BBT_VERSION) + offs++; + /* Calc length */ + len = (size_t)(numblocks >> sft); + len += offs; + /* Make it page aligned! */ + len = ALIGN(len, mtd->writesize); + /* Preset the buffer with 0xff */ + memset(buf, 0xff, len); + /* Pattern is located at the begin of first page */ + memcpy(buf, td->pattern, td->len); + } else { + /* Calc length */ + len = (size_t)(numblocks >> sft); + /* Make it page aligned! */ + len = ALIGN(len, mtd->writesize); + /* Preset the buffer with 0xff */ + memset(buf, 0xff, len + + (len >> this->page_shift)* mtd->oobsize); + offs = 0; + ooboffs = len; + /* Pattern is located in oob area of first page */ + memcpy(&buf[ooboffs + td->offs], td->pattern, td->len); + } + + if (td->options & NAND_BBT_VERSION) + buf[ooboffs + td->veroffs] = td->version[chip]; + + /* Walk through the memory table */ + for (i = 0; i < numblocks; i++) { + uint8_t dat; + int sftcnt = (i << (3 - sft)) & sftmsk; + dat = bbt_get_entry(this, chip * numblocks + i); + /* Do not store the reserved bbt blocks! */ + buf[offs + (i >> sft)] &= ~(msk[dat] << sftcnt); + } + + memset(&einfo, 0, sizeof(einfo)); + einfo.addr = to; + einfo.len = 1 << this->bbt_erase_shift; + res = nand_erase_nand(this, &einfo, 1); + if (res < 0) { + pr_warn("nand_bbt: error while erasing BBT block %d\n", + res); + mark_bbt_block_bad(this, td, chip, block); + continue; + } + + res = scan_write_bbt(this, to, len, buf, + td->options & NAND_BBT_NO_OOB ? + NULL : &buf[len]); + if (res < 0) { + pr_warn("nand_bbt: error while writing BBT block %d\n", + res); + mark_bbt_block_bad(this, td, chip, block); + continue; + } + + pr_info("Bad block table written to 0x%012llx, version 0x%02X\n", + (unsigned long long)to, td->version[chip]); + + /* Mark it as used */ + td->pages[chip++] = page; + } + return 0; + + outerr: + pr_warn("nand_bbt: error while writing bad block table %d\n", res); + return res; +} + +/** + * nand_memory_bbt - [GENERIC] create a memory based bad block table + * @this: NAND chip object + * @bd: descriptor for the good/bad block search pattern + * + * The function creates a memory based bbt by scanning the device for + * manufacturer / software marked good / bad blocks. + */ +static inline int nand_memory_bbt(struct nand_chip *this, + struct nand_bbt_descr *bd) +{ + u8 *pagebuf = nand_get_data_buf(this); + + return create_bbt(this, pagebuf, bd, -1); +} + +/** + * check_create - [GENERIC] create and write bbt(s) if necessary + * @this: the NAND device + * @buf: temporary buffer + * @bd: descriptor for the good/bad block search pattern + * + * The function checks the results of the previous call to read_bbt and creates + * / updates the bbt(s) if necessary. Creation is necessary if no bbt was found + * for the chip/device. Update is necessary if one of the tables is missing or + * the version nr. of one table is less than the other. + */ +static int check_create(struct nand_chip *this, uint8_t *buf, + struct nand_bbt_descr *bd) +{ + int i, chips, writeops, create, chipsel, res, res2; + struct nand_bbt_descr *td = this->bbt_td; + struct nand_bbt_descr *md = this->bbt_md; + struct nand_bbt_descr *rd, *rd2; + + /* Do we have a bbt per chip? */ + if (td->options & NAND_BBT_PERCHIP) + chips = nanddev_ntargets(&this->base); + else + chips = 1; + + for (i = 0; i < chips; i++) { + writeops = 0; + create = 0; + rd = NULL; + rd2 = NULL; + res = res2 = 0; + /* Per chip or per device? */ + chipsel = (td->options & NAND_BBT_PERCHIP) ? i : -1; + /* Mirrored table available? */ + if (md) { + if (td->pages[i] == -1 && md->pages[i] == -1) { + create = 1; + writeops = 0x03; + } else if (td->pages[i] == -1) { + rd = md; + writeops = 0x01; + } else if (md->pages[i] == -1) { + rd = td; + writeops = 0x02; + } else if (td->version[i] == md->version[i]) { + rd = td; + if (!(td->options & NAND_BBT_VERSION)) + rd2 = md; + } else if (((int8_t)(td->version[i] - md->version[i])) > 0) { + rd = td; + writeops = 0x02; + } else { + rd = md; + writeops = 0x01; + } + } else { + if (td->pages[i] == -1) { + create = 1; + writeops = 0x01; + } else { + rd = td; + } + } + + if (create) { + /* Create the bad block table by scanning the device? */ + if (!(td->options & NAND_BBT_CREATE)) + continue; + + /* Create the table in memory by scanning the chip(s) */ + if (!(this->bbt_options & NAND_BBT_CREATE_EMPTY)) + create_bbt(this, buf, bd, chipsel); + + td->version[i] = 1; + if (md) + md->version[i] = 1; + } + + /* Read back first? */ + if (rd) { + res = read_abs_bbt(this, buf, rd, chipsel); + if (mtd_is_eccerr(res)) { + /* Mark table as invalid */ + rd->pages[i] = -1; + rd->version[i] = 0; + i--; + continue; + } + } + /* If they weren't versioned, read both */ + if (rd2) { + res2 = read_abs_bbt(this, buf, rd2, chipsel); + if (mtd_is_eccerr(res2)) { + /* Mark table as invalid */ + rd2->pages[i] = -1; + rd2->version[i] = 0; + i--; + continue; + } + } + + /* Scrub the flash table(s)? */ + if (mtd_is_bitflip(res) || mtd_is_bitflip(res2)) + writeops = 0x03; + + /* Update version numbers before writing */ + if (md) { + td->version[i] = max(td->version[i], md->version[i]); + md->version[i] = td->version[i]; + } + + /* Write the bad block table to the device? */ + if ((writeops & 0x01) && (td->options & NAND_BBT_WRITE)) { + res = write_bbt(this, buf, td, md, chipsel); + if (res < 0) + return res; + } + + /* Write the mirror bad block table to the device? */ + if ((writeops & 0x02) && md && (md->options & NAND_BBT_WRITE)) { + res = write_bbt(this, buf, md, td, chipsel); + if (res < 0) + return res; + } + } + return 0; +} + +/** + * nand_update_bbt - update bad block table(s) + * @this: the NAND device + * @offs: the offset of the newly marked block + * + * The function updates the bad block table(s). + */ +static int nand_update_bbt(struct nand_chip *this, loff_t offs) +{ + struct mtd_info *mtd = nand_to_mtd(this); + int len, res = 0; + int chip, chipsel; + uint8_t *buf; + struct nand_bbt_descr *td = this->bbt_td; + struct nand_bbt_descr *md = this->bbt_md; + + if (!this->bbt || !td) + return -EINVAL; + + /* Allocate a temporary buffer for one eraseblock incl. oob */ + len = (1 << this->bbt_erase_shift); + len += (len >> this->page_shift) * mtd->oobsize; + buf = kmalloc(len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* Do we have a bbt per chip? */ + if (td->options & NAND_BBT_PERCHIP) { + chip = (int)(offs >> this->chip_shift); + chipsel = chip; + } else { + chip = 0; + chipsel = -1; + } + + td->version[chip]++; + if (md) + md->version[chip]++; + + /* Write the bad block table to the device? */ + if (td->options & NAND_BBT_WRITE) { + res = write_bbt(this, buf, td, md, chipsel); + if (res < 0) + goto out; + } + /* Write the mirror bad block table to the device? */ + if (md && (md->options & NAND_BBT_WRITE)) { + res = write_bbt(this, buf, md, td, chipsel); + } + + out: + kfree(buf); + return res; +} + +/** + * mark_bbt_region - [GENERIC] mark the bad block table regions + * @this: the NAND device + * @td: bad block table descriptor + * + * The bad block table regions are marked as "bad" to prevent accidental + * erasures / writes. The regions are identified by the mark 0x02. + */ +static void mark_bbt_region(struct nand_chip *this, struct nand_bbt_descr *td) +{ + u64 targetsize = nanddev_target_size(&this->base); + struct mtd_info *mtd = nand_to_mtd(this); + int i, j, chips, block, nrblocks, update; + uint8_t oldval; + + /* Do we have a bbt per chip? */ + if (td->options & NAND_BBT_PERCHIP) { + chips = nanddev_ntargets(&this->base); + nrblocks = (int)(targetsize >> this->bbt_erase_shift); + } else { + chips = 1; + nrblocks = (int)(mtd->size >> this->bbt_erase_shift); + } + + for (i = 0; i < chips; i++) { + if ((td->options & NAND_BBT_ABSPAGE) || + !(td->options & NAND_BBT_WRITE)) { + if (td->pages[i] == -1) + continue; + block = td->pages[i] >> (this->bbt_erase_shift - this->page_shift); + oldval = bbt_get_entry(this, block); + bbt_mark_entry(this, block, BBT_BLOCK_RESERVED); + if ((oldval != BBT_BLOCK_RESERVED) && + td->reserved_block_code) + nand_update_bbt(this, (loff_t)block << + this->bbt_erase_shift); + continue; + } + update = 0; + if (td->options & NAND_BBT_LASTBLOCK) + block = ((i + 1) * nrblocks) - td->maxblocks; + else + block = i * nrblocks; + for (j = 0; j < td->maxblocks; j++) { + oldval = bbt_get_entry(this, block); + bbt_mark_entry(this, block, BBT_BLOCK_RESERVED); + if (oldval != BBT_BLOCK_RESERVED) + update = 1; + block++; + } + /* + * If we want reserved blocks to be recorded to flash, and some + * new ones have been marked, then we need to update the stored + * bbts. This should only happen once. + */ + if (update && td->reserved_block_code) + nand_update_bbt(this, (loff_t)(block - 1) << + this->bbt_erase_shift); + } +} + +/** + * verify_bbt_descr - verify the bad block description + * @this: the NAND device + * @bd: the table to verify + * + * This functions performs a few sanity checks on the bad block description + * table. + */ +static void verify_bbt_descr(struct nand_chip *this, struct nand_bbt_descr *bd) +{ + u64 targetsize = nanddev_target_size(&this->base); + struct mtd_info *mtd = nand_to_mtd(this); + u32 pattern_len; + u32 bits; + u32 table_size; + + if (!bd) + return; + + pattern_len = bd->len; + bits = bd->options & NAND_BBT_NRBITS_MSK; + + BUG_ON((this->bbt_options & NAND_BBT_NO_OOB) && + !(this->bbt_options & NAND_BBT_USE_FLASH)); + BUG_ON(!bits); + + if (bd->options & NAND_BBT_VERSION) + pattern_len++; + + if (bd->options & NAND_BBT_NO_OOB) { + BUG_ON(!(this->bbt_options & NAND_BBT_USE_FLASH)); + BUG_ON(!(this->bbt_options & NAND_BBT_NO_OOB)); + BUG_ON(bd->offs); + if (bd->options & NAND_BBT_VERSION) + BUG_ON(bd->veroffs != bd->len); + BUG_ON(bd->options & NAND_BBT_SAVECONTENT); + } + + if (bd->options & NAND_BBT_PERCHIP) + table_size = targetsize >> this->bbt_erase_shift; + else + table_size = mtd->size >> this->bbt_erase_shift; + table_size >>= 3; + table_size *= bits; + if (bd->options & NAND_BBT_NO_OOB) + table_size += pattern_len; + BUG_ON(table_size > (1 << this->bbt_erase_shift)); +} + +/** + * nand_scan_bbt - [NAND Interface] scan, find, read and maybe create bad block table(s) + * @this: the NAND device + * @bd: descriptor for the good/bad block search pattern + * + * The function checks, if a bad block table(s) is/are already available. If + * not it scans the device for manufacturer marked good / bad blocks and writes + * the bad block table(s) to the selected place. + * + * The bad block table memory is allocated here. It must be freed by calling + * the nand_free_bbt function. + */ +static int nand_scan_bbt(struct nand_chip *this, struct nand_bbt_descr *bd) +{ + struct mtd_info *mtd = nand_to_mtd(this); + int len, res; + uint8_t *buf; + struct nand_bbt_descr *td = this->bbt_td; + struct nand_bbt_descr *md = this->bbt_md; + + len = (mtd->size >> (this->bbt_erase_shift + 2)) ? : 1; + /* + * Allocate memory (2bit per block) and clear the memory bad block + * table. + */ + this->bbt = kzalloc(len, GFP_KERNEL); + if (!this->bbt) + return -ENOMEM; + + /* + * If no primary table descriptor is given, scan the device to build a + * memory based bad block table. + */ + if (!td) { + if ((res = nand_memory_bbt(this, bd))) { + pr_err("nand_bbt: can't scan flash and build the RAM-based BBT\n"); + goto err_free_bbt; + } + return 0; + } + verify_bbt_descr(this, td); + verify_bbt_descr(this, md); + + /* Allocate a temporary buffer for one eraseblock incl. oob */ + len = (1 << this->bbt_erase_shift); + len += (len >> this->page_shift) * mtd->oobsize; + buf = vmalloc(len); + if (!buf) { + res = -ENOMEM; + goto err_free_bbt; + } + + /* Is the bbt at a given page? */ + if (td->options & NAND_BBT_ABSPAGE) { + read_abs_bbts(this, buf, td, md); + } else { + /* Search the bad block table using a pattern in oob */ + search_read_bbts(this, buf, td, md); + } + + res = check_create(this, buf, bd); + if (res) + goto err_free_buf; + + /* Prevent the bbt regions from erasing / writing */ + mark_bbt_region(this, td); + if (md) + mark_bbt_region(this, md); + + vfree(buf); + return 0; + +err_free_buf: + vfree(buf); +err_free_bbt: + kfree(this->bbt); + this->bbt = NULL; + return res; +} + +/* + * Define some generic bad / good block scan pattern which are used + * while scanning a device for factory marked good / bad blocks. + */ +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; + +/* Generic flash bbt descriptors */ +static uint8_t bbt_pattern[] = {'B', 'b', 't', '0' }; +static uint8_t mirror_pattern[] = {'1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 8, + .len = 4, + .veroffs = 12, + .maxblocks = NAND_BBT_SCAN_MAXBLOCKS, + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 8, + .len = 4, + .veroffs = 12, + .maxblocks = NAND_BBT_SCAN_MAXBLOCKS, + .pattern = mirror_pattern +}; + +static struct nand_bbt_descr bbt_main_no_oob_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP + | NAND_BBT_NO_OOB, + .len = 4, + .veroffs = 4, + .maxblocks = NAND_BBT_SCAN_MAXBLOCKS, + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_mirror_no_oob_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP + | NAND_BBT_NO_OOB, + .len = 4, + .veroffs = 4, + .maxblocks = NAND_BBT_SCAN_MAXBLOCKS, + .pattern = mirror_pattern +}; + +#define BADBLOCK_SCAN_MASK (~NAND_BBT_NO_OOB) +/** + * nand_create_badblock_pattern - [INTERN] Creates a BBT descriptor structure + * @this: NAND chip to create descriptor for + * + * This function allocates and initializes a nand_bbt_descr for BBM detection + * based on the properties of @this. The new descriptor is stored in + * this->badblock_pattern. Thus, this->badblock_pattern should be NULL when + * passed to this function. + */ +static int nand_create_badblock_pattern(struct nand_chip *this) +{ + struct nand_bbt_descr *bd; + if (this->badblock_pattern) { + pr_warn("Bad block pattern already allocated; not replacing\n"); + return -EINVAL; + } + bd = kzalloc_obj(*bd); + if (!bd) + return -ENOMEM; + bd->options = this->bbt_options & BADBLOCK_SCAN_MASK; + bd->offs = this->badblockpos; + bd->len = (this->options & NAND_BUSWIDTH_16) ? 2 : 1; + bd->pattern = scan_ff_pattern; + bd->options |= NAND_BBT_DYNAMICSTRUCT; + this->badblock_pattern = bd; + return 0; +} + +/** + * nand_create_bbt - [NAND Interface] Select a default bad block table for the device + * @this: NAND chip object + * + * This function selects the default bad block table support for the device and + * calls the nand_scan_bbt function. + */ +int nand_create_bbt(struct nand_chip *this) +{ + int ret; + + /* Is a flash based bad block table requested? */ + if (this->bbt_options & NAND_BBT_USE_FLASH) { + /* Use the default pattern descriptors */ + if (!this->bbt_td) { + if (this->bbt_options & NAND_BBT_NO_OOB) { + this->bbt_td = &bbt_main_no_oob_descr; + this->bbt_md = &bbt_mirror_no_oob_descr; + } else { + this->bbt_td = &bbt_main_descr; + this->bbt_md = &bbt_mirror_descr; + } + } + } else { + this->bbt_td = NULL; + this->bbt_md = NULL; + } + + if (!this->badblock_pattern) { + ret = nand_create_badblock_pattern(this); + if (ret) + return ret; + } + + return nand_scan_bbt(this, this->badblock_pattern); +} +EXPORT_SYMBOL(nand_create_bbt); + +/** + * nand_isreserved_bbt - [NAND Interface] Check if a block is reserved + * @this: NAND chip object + * @offs: offset in the device + */ +int nand_isreserved_bbt(struct nand_chip *this, loff_t offs) +{ + int block; + + block = (int)(offs >> this->bbt_erase_shift); + return bbt_get_entry(this, block) == BBT_BLOCK_RESERVED; +} + +/** + * nand_isbad_bbt - [NAND Interface] Check if a block is bad + * @this: NAND chip object + * @offs: offset in the device + * @allowbbt: allow access to bad block table region + */ +int nand_isbad_bbt(struct nand_chip *this, loff_t offs, int allowbbt) +{ + int block, res; + + block = (int)(offs >> this->bbt_erase_shift); + res = bbt_get_entry(this, block); + + pr_debug("nand_isbad_bbt(): bbt info for offs 0x%08x: (block %d) 0x%02x\n", + (unsigned int)offs, block, res); + + if (mtd_check_expert_analysis_mode()) + return 0; + + switch (res) { + case BBT_BLOCK_GOOD: + return 0; + case BBT_BLOCK_WORN: + return 1; + case BBT_BLOCK_RESERVED: + return allowbbt ? 0 : 1; + } + return 1; +} + +/** + * nand_markbad_bbt - [NAND Interface] Mark a block bad in the BBT + * @this: NAND chip object + * @offs: offset of the bad block + */ +int nand_markbad_bbt(struct nand_chip *this, loff_t offs) +{ + int block, ret = 0; + + block = (int)(offs >> this->bbt_erase_shift); + + /* Mark bad block in memory */ + bbt_mark_entry(this, block, BBT_BLOCK_WORN); + + /* Update flash-based bad block table */ + if (this->bbt_options & NAND_BBT_USE_FLASH) + ret = nand_update_bbt(this, offs); + + return ret; +} diff --git a/drivers/mtd/nand/raw/nand_esmt.c b/drivers/mtd/nand/raw/nand_esmt.c new file mode 100644 index 000000000..4412c407a --- /dev/null +++ b/drivers/mtd/nand/raw/nand_esmt.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Toradex AG + * + * Author: Marcel Ziswiler + */ + +#include +#include "internals.h" + +static void esmt_nand_decode_id(struct nand_chip *chip) +{ + struct nand_device *base = &chip->base; + struct nand_ecc_props requirements = {}; + + nand_decode_ext_id(chip); + + /* Extract ECC requirements from 5th id byte. */ + if (chip->id.len >= 5 && nand_is_slc(chip)) { + requirements.step_size = 512; + switch (chip->id.data[4] & 0x3) { + case 0x0: + requirements.strength = 4; + break; + case 0x1: + requirements.strength = 2; + break; + case 0x2: + requirements.strength = 1; + break; + default: + WARN(1, "Could not get ECC info"); + requirements.step_size = 0; + break; + } + } + + nanddev_set_ecc_requirements(base, &requirements); +} + +static int esmt_nand_init(struct nand_chip *chip) +{ + if (nand_is_slc(chip)) + /* + * It is known that some ESMT SLC NANDs have been shipped + * with the factory bad block markers in the first or last page + * of the block, instead of the first or second page. To be on + * the safe side, let's check all three locations. + */ + chip->options |= NAND_BBM_FIRSTPAGE | NAND_BBM_SECONDPAGE | + NAND_BBM_LASTPAGE; + + return 0; +} + +const struct nand_manufacturer_ops esmt_nand_manuf_ops = { + .detect = esmt_nand_decode_id, + .init = esmt_nand_init, +}; diff --git a/drivers/mtd/nand/raw/nand_hynix.c b/drivers/mtd/nand/raw/nand_hynix.c new file mode 100644 index 000000000..0510ceff5 --- /dev/null +++ b/drivers/mtd/nand/raw/nand_hynix.c @@ -0,0 +1,747 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2017 Free Electrons + * Copyright (C) 2017 NextThing Co + * + * Author: Boris Brezillon + */ + +#include +#include + +#include "internals.h" + +#define NAND_HYNIX_CMD_SET_PARAMS 0x36 +#define NAND_HYNIX_CMD_APPLY_PARAMS 0x16 + +#define NAND_HYNIX_1XNM_RR_REPEAT 8 + +/** + * struct hynix_read_retry - read-retry data + * @nregs: number of register to set when applying a new read-retry mode + * @regs: register offsets (NAND chip dependent) + * @values: array of values to set in registers. The array size is equal to + * (nregs * nmodes) + */ +struct hynix_read_retry { + int nregs; + const u8 *regs; + u8 values[]; +}; + +/** + * struct hynix_nand - private Hynix NAND struct + * @read_retry: read-retry information + */ +struct hynix_nand { + const struct hynix_read_retry *read_retry; +}; + +/** + * struct hynix_read_retry_otp - structure describing how the read-retry OTP + * area + * @nregs: number of hynix private registers to set before reading the reading + * the OTP area + * @regs: registers that should be configured + * @values: values that should be set in regs + * @page: the address to pass to the READ_PAGE command. Depends on the NAND + * chip + * @size: size of the read-retry OTP section + */ +struct hynix_read_retry_otp { + int nregs; + const u8 *regs; + const u8 *values; + int page; + int size; +}; + +static bool hynix_nand_has_valid_jedecid(struct nand_chip *chip) +{ + u8 jedecid[5] = { }; + int ret; + + ret = nand_readid_op(chip, 0x40, jedecid, sizeof(jedecid)); + if (ret) + return false; + + return !strncmp("JEDEC", jedecid, sizeof(jedecid)); +} + +static int hynix_nand_cmd_op(struct nand_chip *chip, u8 cmd) +{ + if (nand_has_exec_op(chip)) { + struct nand_op_instr instrs[] = { + NAND_OP_CMD(cmd, 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + + return nand_exec_op(chip, &op); + } + + chip->legacy.cmdfunc(chip, cmd, -1, -1); + + return 0; +} + +static int hynix_nand_reg_write_op(struct nand_chip *chip, u8 addr, u8 val) +{ + u16 column = ((u16)addr << 8) | addr; + + if (nand_has_exec_op(chip)) { + struct nand_op_instr instrs[] = { + NAND_OP_ADDR(1, &addr, 0), + NAND_OP_8BIT_DATA_OUT(1, &val, 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + + return nand_exec_op(chip, &op); + } + + chip->legacy.cmdfunc(chip, NAND_CMD_NONE, column, -1); + chip->legacy.write_byte(chip, val); + + return 0; +} + +static int hynix_nand_setup_read_retry(struct nand_chip *chip, int retry_mode) +{ + struct hynix_nand *hynix = nand_get_manufacturer_data(chip); + const u8 *values; + int i, ret; + + values = hynix->read_retry->values + + (retry_mode * hynix->read_retry->nregs); + + /* Enter 'Set Hynix Parameters' mode */ + ret = hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS); + if (ret) + return ret; + + /* + * Configure the NAND in the requested read-retry mode. + * This is done by setting pre-defined values in internal NAND + * registers. + * + * The set of registers is NAND specific, and the values are either + * predefined or extracted from an OTP area on the NAND (values are + * probably tweaked at production in this case). + */ + for (i = 0; i < hynix->read_retry->nregs; i++) { + ret = hynix_nand_reg_write_op(chip, hynix->read_retry->regs[i], + values[i]); + if (ret) + return ret; + } + + /* Apply the new settings. */ + return hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS); +} + +/** + * hynix_get_majority - get the value that is occurring the most in a given + * set of values + * @in: the array of values to test + * @repeat: the size of the in array + * @out: pointer used to store the output value + * + * This function implements the 'majority check' logic that is supposed to + * overcome the unreliability of MLC NANDs when reading the OTP area storing + * the read-retry parameters. + * + * It's based on a pretty simple assumption: if we repeat the same value + * several times and then take the one that is occurring the most, we should + * find the correct value. + * Let's hope this dummy algorithm prevents us from losing the read-retry + * parameters. + */ +static int hynix_get_majority(const u8 *in, int repeat, u8 *out) +{ + int i, j, half = repeat / 2; + + /* + * We only test the first half of the in array because we must ensure + * that the value is at least occurring repeat / 2 times. + * + * This loop is suboptimal since we may count the occurrences of the + * same value several time, but we are doing that on small sets, which + * makes it acceptable. + */ + for (i = 0; i < half; i++) { + int cnt = 0; + u8 val = in[i]; + + /* Count all values that are matching the one at index i. */ + for (j = i + 1; j < repeat; j++) { + if (in[j] == val) + cnt++; + } + + /* We found a value occurring more than repeat / 2. */ + if (cnt > half) { + *out = val; + return 0; + } + } + + return -EIO; +} + +static int hynix_read_rr_otp(struct nand_chip *chip, + const struct hynix_read_retry_otp *info, + void *buf) +{ + int i, ret; + + ret = nand_reset_op(chip); + if (ret) + return ret; + + ret = hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS); + if (ret) + return ret; + + for (i = 0; i < info->nregs; i++) { + ret = hynix_nand_reg_write_op(chip, info->regs[i], + info->values[i]); + if (ret) + return ret; + } + + ret = hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS); + if (ret) + return ret; + + /* Sequence to enter OTP mode? */ + ret = hynix_nand_cmd_op(chip, 0x17); + if (ret) + return ret; + + ret = hynix_nand_cmd_op(chip, 0x4); + if (ret) + return ret; + + ret = hynix_nand_cmd_op(chip, 0x19); + if (ret) + return ret; + + /* Now read the page */ + ret = nand_read_page_op(chip, info->page, 0, buf, info->size); + if (ret) + return ret; + + /* Put everything back to normal */ + ret = nand_reset_op(chip); + if (ret) + return ret; + + ret = hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_SET_PARAMS); + if (ret) + return ret; + + ret = hynix_nand_reg_write_op(chip, 0x38, 0); + if (ret) + return ret; + + ret = hynix_nand_cmd_op(chip, NAND_HYNIX_CMD_APPLY_PARAMS); + if (ret) + return ret; + + return nand_read_page_op(chip, 0, 0, NULL, 0); +} + +#define NAND_HYNIX_1XNM_RR_COUNT_OFFS 0 +#define NAND_HYNIX_1XNM_RR_REG_COUNT_OFFS 8 +#define NAND_HYNIX_1XNM_RR_SET_OFFS(x, setsize, inv) \ + (16 + ((((x) * 2) + ((inv) ? 1 : 0)) * (setsize))) + +static int hynix_mlc_1xnm_rr_value(const u8 *buf, int nmodes, int nregs, + int mode, int reg, bool inv, u8 *val) +{ + u8 tmp[NAND_HYNIX_1XNM_RR_REPEAT]; + int val_offs = (mode * nregs) + reg; + int set_size = nmodes * nregs; + int i, ret; + + for (i = 0; i < NAND_HYNIX_1XNM_RR_REPEAT; i++) { + int set_offs = NAND_HYNIX_1XNM_RR_SET_OFFS(i, set_size, inv); + + tmp[i] = buf[val_offs + set_offs]; + } + + ret = hynix_get_majority(tmp, NAND_HYNIX_1XNM_RR_REPEAT, val); + if (ret) + return ret; + + if (inv) + *val = ~*val; + + return 0; +} + +static u8 hynix_1xnm_mlc_read_retry_regs[] = { + 0xcc, 0xbf, 0xaa, 0xab, 0xcd, 0xad, 0xae, 0xaf +}; + +static int hynix_mlc_1xnm_rr_init(struct nand_chip *chip, + const struct hynix_read_retry_otp *info) +{ + struct hynix_nand *hynix = nand_get_manufacturer_data(chip); + struct hynix_read_retry *rr = NULL; + int ret, i, j; + u8 nregs, nmodes; + u8 *buf; + + buf = kmalloc(info->size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = hynix_read_rr_otp(chip, info, buf); + if (ret) + goto out; + + ret = hynix_get_majority(buf, NAND_HYNIX_1XNM_RR_REPEAT, + &nmodes); + if (ret) + goto out; + + ret = hynix_get_majority(buf + NAND_HYNIX_1XNM_RR_REPEAT, + NAND_HYNIX_1XNM_RR_REPEAT, + &nregs); + if (ret) + goto out; + + rr = kzalloc(sizeof(*rr) + (nregs * nmodes), GFP_KERNEL); + if (!rr) { + ret = -ENOMEM; + goto out; + } + + for (i = 0; i < nmodes; i++) { + for (j = 0; j < nregs; j++) { + u8 *val = rr->values + (i * nregs); + + ret = hynix_mlc_1xnm_rr_value(buf, nmodes, nregs, i, j, + false, val); + if (!ret) + continue; + + ret = hynix_mlc_1xnm_rr_value(buf, nmodes, nregs, i, j, + true, val); + if (ret) + goto out; + } + } + + rr->nregs = nregs; + rr->regs = hynix_1xnm_mlc_read_retry_regs; + hynix->read_retry = rr; + chip->ops.setup_read_retry = hynix_nand_setup_read_retry; + chip->read_retries = nmodes; + +out: + kfree(buf); + + if (ret) + kfree(rr); + + return ret; +} + +static const u8 hynix_mlc_1xnm_rr_otp_regs[] = { 0x38 }; +static const u8 hynix_mlc_1xnm_rr_otp_values[] = { 0x52 }; + +static const struct hynix_read_retry_otp hynix_mlc_1xnm_rr_otps[] = { + { + .nregs = ARRAY_SIZE(hynix_mlc_1xnm_rr_otp_regs), + .regs = hynix_mlc_1xnm_rr_otp_regs, + .values = hynix_mlc_1xnm_rr_otp_values, + .page = 0x21f, + .size = 784 + }, + { + .nregs = ARRAY_SIZE(hynix_mlc_1xnm_rr_otp_regs), + .regs = hynix_mlc_1xnm_rr_otp_regs, + .values = hynix_mlc_1xnm_rr_otp_values, + .page = 0x200, + .size = 528, + }, +}; + +static int hynix_nand_rr_init(struct nand_chip *chip) +{ + int i, ret = 0; + bool valid_jedecid; + + valid_jedecid = hynix_nand_has_valid_jedecid(chip); + + /* + * We only support read-retry for 1xnm NANDs, and those NANDs all + * expose a valid JEDEC ID. SLC NANDs don't require read-retry. + */ + if (valid_jedecid && nanddev_bits_per_cell(&chip->base) > 1) { + u8 nand_tech = chip->id.data[5] >> 4; + + /* 1xnm technology */ + if (nand_tech == 4) { + for (i = 0; i < ARRAY_SIZE(hynix_mlc_1xnm_rr_otps); + i++) { + /* + * FIXME: Hynix recommend to copy the + * read-retry OTP area into a normal page. + */ + ret = hynix_mlc_1xnm_rr_init(chip, + hynix_mlc_1xnm_rr_otps); + if (!ret) + break; + } + } + } + + if (ret) + pr_warn("failed to initialize read-retry infrastructure"); + + return ret; +} + +static void hynix_nand_extract_oobsize(struct nand_chip *chip, + bool valid_jedecid) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_memory_organization *memorg; + u8 oobsize; + + memorg = nanddev_get_memorg(&chip->base); + + oobsize = ((chip->id.data[3] >> 2) & 0x3) | + ((chip->id.data[3] >> 4) & 0x4); + + if (valid_jedecid) { + switch (oobsize) { + case 0: + memorg->oobsize = 2048; + break; + case 1: + memorg->oobsize = 1664; + break; + case 2: + memorg->oobsize = 1024; + break; + case 3: + memorg->oobsize = 640; + break; + default: + /* + * We should never reach this case, but if that + * happens, this probably means Hynix decided to use + * a different extended ID format, and we should find + * a way to support it. + */ + WARN(1, "Invalid OOB size"); + break; + } + } else { + switch (oobsize) { + case 0: + memorg->oobsize = 128; + break; + case 1: + memorg->oobsize = 224; + break; + case 2: + memorg->oobsize = 448; + break; + case 3: + memorg->oobsize = 64; + break; + case 4: + memorg->oobsize = 32; + break; + case 5: + memorg->oobsize = 16; + break; + case 6: + memorg->oobsize = 640; + break; + default: + /* + * We should never reach this case, but if that + * happens, this probably means Hynix decided to use + * a different extended ID format, and we should find + * a way to support it. + */ + WARN(1, "Invalid OOB size"); + break; + } + + /* + * The datasheet of H27UCG8T2BTR mentions that the "Redundant + * Area Size" is encoded "per 8KB" (page size). This chip uses + * a page size of 16KiB. The datasheet mentions an OOB size of + * 1.280 bytes, but the OOB size encoded in the ID bytes (using + * the existing logic above) is 640 bytes. + * Update the OOB size for this chip by taking the value + * determined above and scaling it to the actual page size (so + * the actual OOB size for this chip is: 640 * 16k / 8k). + */ + if (chip->id.data[1] == 0xde) + memorg->oobsize *= memorg->pagesize / SZ_8K; + } + + mtd->oobsize = memorg->oobsize; +} + +static void hynix_nand_extract_ecc_requirements(struct nand_chip *chip, + bool valid_jedecid) +{ + struct nand_device *base = &chip->base; + struct nand_ecc_props requirements = {}; + u8 ecc_level = (chip->id.data[4] >> 4) & 0x7; + + if (valid_jedecid) { + /* Reference: H27UCG8T2E datasheet */ + requirements.step_size = 1024; + + switch (ecc_level) { + case 0: + requirements.step_size = 0; + requirements.strength = 0; + break; + case 1: + requirements.strength = 4; + break; + case 2: + requirements.strength = 24; + break; + case 3: + requirements.strength = 32; + break; + case 4: + requirements.strength = 40; + break; + case 5: + requirements.strength = 50; + break; + case 6: + requirements.strength = 60; + break; + default: + /* + * We should never reach this case, but if that + * happens, this probably means Hynix decided to use + * a different extended ID format, and we should find + * a way to support it. + */ + WARN(1, "Invalid ECC requirements"); + } + } else { + /* + * The ECC requirements field meaning depends on the + * NAND technology. + */ + u8 nand_tech = chip->id.data[5] & 0x7; + + if (nand_tech < 3) { + /* > 26nm, reference: H27UBG8T2A datasheet */ + if (ecc_level < 5) { + requirements.step_size = 512; + requirements.strength = 1 << ecc_level; + } else if (ecc_level < 7) { + if (ecc_level == 5) + requirements.step_size = 2048; + else + requirements.step_size = 1024; + requirements.strength = 24; + } else { + /* + * We should never reach this case, but if that + * happens, this probably means Hynix decided + * to use a different extended ID format, and + * we should find a way to support it. + */ + WARN(1, "Invalid ECC requirements"); + } + } else { + /* <= 26nm, reference: H27UBG8T2B datasheet */ + if (!ecc_level) { + requirements.step_size = 0; + requirements.strength = 0; + } else if (ecc_level < 5) { + requirements.step_size = 512; + requirements.strength = 1 << (ecc_level - 1); + } else { + requirements.step_size = 1024; + requirements.strength = 24 + + (8 * (ecc_level - 5)); + } + } + } + + nanddev_set_ecc_requirements(base, &requirements); +} + +static void hynix_nand_extract_scrambling_requirements(struct nand_chip *chip, + bool valid_jedecid) +{ + u8 nand_tech; + + /* We need scrambling on all TLC NANDs*/ + if (nanddev_bits_per_cell(&chip->base) > 2) + chip->options |= NAND_NEED_SCRAMBLING; + + /* And on MLC NANDs with sub-3xnm process */ + if (valid_jedecid) { + nand_tech = chip->id.data[5] >> 4; + + /* < 3xnm */ + if (nand_tech > 0) + chip->options |= NAND_NEED_SCRAMBLING; + } else { + nand_tech = chip->id.data[5] & 0x7; + + /* < 32nm */ + if (nand_tech > 2) + chip->options |= NAND_NEED_SCRAMBLING; + } +} + +static void hynix_nand_decode_id(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_memory_organization *memorg; + bool valid_jedecid; + u8 tmp; + + memorg = nanddev_get_memorg(&chip->base); + + /* + * Exclude all SLC NANDs from this advanced detection scheme. + * According to the ranges defined in several datasheets, it might + * appear that even SLC NANDs could fall in this extended ID scheme. + * If that the case rework the test to let SLC NANDs go through the + * detection process. + */ + if (chip->id.len < 6 || nand_is_slc(chip)) { + nand_decode_ext_id(chip); + return; + } + + /* Extract pagesize */ + memorg->pagesize = 2048 << (chip->id.data[3] & 0x03); + mtd->writesize = memorg->pagesize; + + tmp = (chip->id.data[3] >> 4) & 0x3; + /* + * When bit7 is set that means we start counting at 1MiB, otherwise + * we start counting at 128KiB and shift this value the content of + * ID[3][4:5]. + * The only exception is when ID[3][4:5] == 3 and ID[3][7] == 0, in + * this case the erasesize is set to 768KiB. + */ + if (chip->id.data[3] & 0x80) { + memorg->pages_per_eraseblock = (SZ_1M << tmp) / + memorg->pagesize; + mtd->erasesize = SZ_1M << tmp; + } else if (tmp == 3) { + memorg->pages_per_eraseblock = (SZ_512K + SZ_256K) / + memorg->pagesize; + mtd->erasesize = SZ_512K + SZ_256K; + } else { + memorg->pages_per_eraseblock = (SZ_128K << tmp) / + memorg->pagesize; + mtd->erasesize = SZ_128K << tmp; + } + + /* + * Modern Toggle DDR NANDs have a valid JEDECID even though they are + * not exposing a valid JEDEC parameter table. + * These NANDs use a different NAND ID scheme. + */ + valid_jedecid = hynix_nand_has_valid_jedecid(chip); + + hynix_nand_extract_oobsize(chip, valid_jedecid); + hynix_nand_extract_ecc_requirements(chip, valid_jedecid); + hynix_nand_extract_scrambling_requirements(chip, valid_jedecid); +} + +static void hynix_nand_cleanup(struct nand_chip *chip) +{ + struct hynix_nand *hynix = nand_get_manufacturer_data(chip); + + if (!hynix) + return; + + kfree(hynix->read_retry); + kfree(hynix); + nand_set_manufacturer_data(chip, NULL); +} + +static int +h27ucg8t2atrbc_choose_interface_config(struct nand_chip *chip, + struct nand_interface_config *iface) +{ + onfi_fill_interface_config(chip, iface, NAND_SDR_IFACE, 4); + + return nand_choose_best_sdr_timings(chip, iface, NULL); +} + +static int h27ucg8t2etrbc_init(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + chip->options |= NAND_NEED_SCRAMBLING; + mtd_set_pairing_scheme(mtd, &dist3_pairing_scheme); + + return 0; +} + +static int hynix_nand_init(struct nand_chip *chip) +{ + struct hynix_nand *hynix; + int ret; + + if (!nand_is_slc(chip)) + chip->options |= NAND_BBM_LASTPAGE; + else + chip->options |= NAND_BBM_FIRSTPAGE | NAND_BBM_SECONDPAGE; + + hynix = kzalloc_obj(*hynix); + if (!hynix) + return -ENOMEM; + + nand_set_manufacturer_data(chip, hynix); + + if (!strncmp("H27UCG8T2ATR-BC", chip->parameters.model, + sizeof("H27UCG8T2ATR-BC") - 1)) + chip->ops.choose_interface_config = + h27ucg8t2atrbc_choose_interface_config; + + if (!strncmp("H27UCG8T2ETR-BC", chip->parameters.model, + sizeof("H27UCG8T2ETR-BC") - 1)) + h27ucg8t2etrbc_init(chip); + + ret = hynix_nand_rr_init(chip); + if (ret) + hynix_nand_cleanup(chip); + + return ret; +} + +static void hynix_fixup_onfi_param_page(struct nand_chip *chip, + struct nand_onfi_params *p) +{ + /* + * Certain chips might report a 0 on sdr_timing_mode field + * (bytes 129-130). This has been seen on H27U4G8F2GDA-BI. + * According to ONFI specification, bit 0 of this field "shall be 1". + * Forcibly set this bit. + */ + p->sdr_timing_modes |= cpu_to_le16(BIT(0)); +} + +const struct nand_manufacturer_ops hynix_nand_manuf_ops = { + .detect = hynix_nand_decode_id, + .init = hynix_nand_init, + .cleanup = hynix_nand_cleanup, + .fixup_onfi_param_page = hynix_fixup_onfi_param_page, +}; diff --git a/drivers/mtd/nand/raw/nand_ids.c b/drivers/mtd/nand/raw/nand_ids.c new file mode 100644 index 000000000..08707ce9d --- /dev/null +++ b/drivers/mtd/nand/raw/nand_ids.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2002 Thomas Gleixner (tglx@kernel.org) + */ + +#include + +#include "internals.h" + +#define LP_OPTIONS 0 +#define LP_OPTIONS16 (LP_OPTIONS | NAND_BUSWIDTH_16) + +#define SP_OPTIONS NAND_NEED_READRDY +#define SP_OPTIONS16 (SP_OPTIONS | NAND_BUSWIDTH_16) + +/* + * The chip ID list: + * name, device ID, page size, chip size in MiB, eraseblock size, options + * + * If page size and eraseblock size are 0, the sizes are taken from the + * extended chip ID. + */ +struct nand_flash_dev nand_flash_ids[] = { + /* + * Some incompatible NAND chips share device ID's and so must be + * listed by full ID. We list them first so that we can easily identify + * the most specific match. + */ + {"TC58NVG0S3E 1G 3.3V 8-bit", + { .id = {0x98, 0xd1, 0x90, 0x15, 0x76, 0x14, 0x01, 0x00} }, + SZ_2K, SZ_128, SZ_128K, 0, 8, 64, NAND_ECC_INFO(1, SZ_512), }, + {"TC58NVG1S3H 2G 3.3V 8-bit", + { .id = {0x98, 0xda, 0x90, 0x15, 0x76} }, + SZ_2K, SZ_256, SZ_128K, 0, 5, 128, NAND_ECC_INFO(8, SZ_512) }, + {"TC58NVG2S0F 4G 3.3V 8-bit", + { .id = {0x98, 0xdc, 0x90, 0x26, 0x76, 0x15, 0x01, 0x08} }, + SZ_4K, SZ_512, SZ_256K, 0, 8, 224, NAND_ECC_INFO(4, SZ_512) }, + {"TC58NVG2S0H 4G 3.3V 8-bit", + { .id = {0x98, 0xdc, 0x90, 0x26, 0x76, 0x16, 0x08, 0x00} }, + SZ_4K, SZ_512, SZ_256K, 0, 8, 256, NAND_ECC_INFO(8, SZ_512) }, + {"TC58NVG3S0F 8G 3.3V 8-bit", + { .id = {0x98, 0xd3, 0x90, 0x26, 0x76, 0x15, 0x02, 0x08} }, + SZ_4K, SZ_1K, SZ_256K, 0, 8, 232, NAND_ECC_INFO(4, SZ_512) }, + {"TC58NVG5D2 32G 3.3V 8-bit", + { .id = {0x98, 0xd7, 0x94, 0x32, 0x76, 0x56, 0x09, 0x00} }, + SZ_8K, SZ_4K, SZ_1M, 0, 8, 640, NAND_ECC_INFO(40, SZ_1K) }, + {"TC58NVG6D2 64G 3.3V 8-bit", + { .id = {0x98, 0xde, 0x94, 0x82, 0x76, 0x56, 0x04, 0x20} }, + SZ_8K, SZ_8K, SZ_2M, 0, 8, 640, NAND_ECC_INFO(40, SZ_1K) }, + {"SDTNQGAMA 64G 3.3V 8-bit", + { .id = {0x45, 0xde, 0x94, 0x93, 0x76, 0x57} }, + SZ_16K, SZ_8K, SZ_4M, 0, 6, 1280, NAND_ECC_INFO(40, SZ_1K) }, + {"SDTNRGAMA 64G 3.3V 8-bit", + { .id = {0x45, 0xde, 0x94, 0x93, 0x76, 0x50} }, + SZ_16K, SZ_8K, SZ_4M, 0, 6, 1280, NAND_ECC_INFO(40, SZ_1K) }, + {"H27UCG8T2ATR-BC 64G 3.3V 8-bit", + { .id = {0xad, 0xde, 0x94, 0xda, 0x74, 0xc4} }, + SZ_8K, SZ_8K, SZ_2M, NAND_NEED_SCRAMBLING, 6, 640, + NAND_ECC_INFO(40, SZ_1K) }, + {"H27UCG8T2ETR-BC 64G 3.3V 8-bit", + { .id = {0xad, 0xde, 0x14, 0xa7, 0x42, 0x4a} }, + SZ_16K, SZ_8K, SZ_4M, NAND_NEED_SCRAMBLING, 6, 1664, + NAND_ECC_INFO(40, SZ_1K) }, + {"TH58NVG2S3HBAI4 4G 3.3V 8-bit", + { .id = {0x98, 0xdc, 0x91, 0x15, 0x76} }, + SZ_2K, SZ_512, SZ_128K, 0, 5, 128, NAND_ECC_INFO(8, SZ_512) }, + {"TH58NVG3S0HBAI4 8G 3.3V 8-bit", + { .id = {0x98, 0xd3, 0x91, 0x26, 0x76} }, + SZ_4K, SZ_1K, SZ_256K, 0, 5, 256, NAND_ECC_INFO(8, SZ_512)}, + + LEGACY_ID_NAND("NAND 4MiB 5V 8-bit", 0x6B, 4, SZ_8K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 4MiB 3,3V 8-bit", 0xE3, 4, SZ_8K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 4MiB 3,3V 8-bit", 0xE5, 4, SZ_8K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 8MiB 3,3V 8-bit", 0xD6, 8, SZ_8K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 8MiB 3,3V 8-bit", 0xE6, 8, SZ_8K, SP_OPTIONS), + + LEGACY_ID_NAND("NAND 16MiB 1,8V 8-bit", 0x33, 16, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 16MiB 3,3V 8-bit", 0x73, 16, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 16MiB 1,8V 16-bit", 0x43, 16, SZ_16K, SP_OPTIONS16), + LEGACY_ID_NAND("NAND 16MiB 3,3V 16-bit", 0x53, 16, SZ_16K, SP_OPTIONS16), + + LEGACY_ID_NAND("NAND 32MiB 1,8V 8-bit", 0x35, 32, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 32MiB 3,3V 8-bit", 0x75, 32, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 32MiB 1,8V 16-bit", 0x45, 32, SZ_16K, SP_OPTIONS16), + LEGACY_ID_NAND("NAND 32MiB 3,3V 16-bit", 0x55, 32, SZ_16K, SP_OPTIONS16), + + LEGACY_ID_NAND("NAND 64MiB 1,8V 8-bit", 0x36, 64, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 64MiB 3,3V 8-bit", 0x76, 64, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 64MiB 1,8V 16-bit", 0x46, 64, SZ_16K, SP_OPTIONS16), + LEGACY_ID_NAND("NAND 64MiB 3,3V 16-bit", 0x56, 64, SZ_16K, SP_OPTIONS16), + + LEGACY_ID_NAND("NAND 128MiB 1,8V 8-bit", 0x78, 128, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 128MiB 1,8V 8-bit", 0x39, 128, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 128MiB 3,3V 8-bit", 0x79, 128, SZ_16K, SP_OPTIONS), + LEGACY_ID_NAND("NAND 128MiB 1,8V 16-bit", 0x72, 128, SZ_16K, SP_OPTIONS16), + LEGACY_ID_NAND("NAND 128MiB 1,8V 16-bit", 0x49, 128, SZ_16K, SP_OPTIONS16), + LEGACY_ID_NAND("NAND 128MiB 3,3V 16-bit", 0x74, 128, SZ_16K, SP_OPTIONS16), + LEGACY_ID_NAND("NAND 128MiB 3,3V 16-bit", 0x59, 128, SZ_16K, SP_OPTIONS16), + + LEGACY_ID_NAND("NAND 256MiB 3,3V 8-bit", 0x71, 256, SZ_16K, SP_OPTIONS), + + /* + * These are the new chips with large page size. Their page size and + * eraseblock size are determined from the extended ID bytes. + */ + + /* 512 Megabit */ + EXTENDED_ID_NAND("NAND 64MiB 1,8V 8-bit", 0xA2, 64, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 64MiB 1,8V 8-bit", 0xA0, 64, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xF2, 64, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xD0, 64, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xF0, 64, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 64MiB 1,8V 16-bit", 0xB2, 64, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 64MiB 1,8V 16-bit", 0xB0, 64, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 64MiB 3,3V 16-bit", 0xC2, 64, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 64MiB 3,3V 16-bit", 0xC0, 64, LP_OPTIONS16), + + /* 1 Gigabit */ + EXTENDED_ID_NAND("NAND 128MiB 1,8V 8-bit", 0xA1, 128, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 128MiB 3,3V 8-bit", 0xF1, 128, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 128MiB 3,3V 8-bit", 0xD1, 128, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 128MiB 1,8V 16-bit", 0xB1, 128, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 128MiB 3,3V 16-bit", 0xC1, 128, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 128MiB 1,8V 16-bit", 0xAD, 128, LP_OPTIONS16), + + /* 2 Gigabit */ + EXTENDED_ID_NAND("NAND 256MiB 1,8V 8-bit", 0xAA, 256, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 256MiB 3,3V 8-bit", 0xDA, 256, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 256MiB 1,8V 16-bit", 0xBA, 256, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 256MiB 3,3V 16-bit", 0xCA, 256, LP_OPTIONS16), + + /* 4 Gigabit */ + EXTENDED_ID_NAND("NAND 512MiB 1,8V 8-bit", 0xAC, 512, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 512MiB 3,3V 8-bit", 0xDC, 512, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 512MiB 1,8V 16-bit", 0xBC, 512, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 512MiB 3,3V 16-bit", 0xCC, 512, LP_OPTIONS16), + + /* 8 Gigabit */ + EXTENDED_ID_NAND("NAND 1GiB 1,8V 8-bit", 0xA3, 1024, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 1GiB 3,3V 8-bit", 0xD3, 1024, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 1GiB 1,8V 16-bit", 0xB3, 1024, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 1GiB 3,3V 16-bit", 0xC3, 1024, LP_OPTIONS16), + + /* 16 Gigabit */ + EXTENDED_ID_NAND("NAND 2GiB 1,8V 8-bit", 0xA5, 2048, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 2GiB 3,3V 8-bit", 0xD5, 2048, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 2GiB 1,8V 16-bit", 0xB5, 2048, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 2GiB 3,3V 16-bit", 0xC5, 2048, LP_OPTIONS16), + + /* 32 Gigabit */ + EXTENDED_ID_NAND("NAND 4GiB 1,8V 8-bit", 0xA7, 4096, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 4GiB 3,3V 8-bit", 0xD7, 4096, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 4GiB 1,8V 16-bit", 0xB7, 4096, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 4GiB 3,3V 16-bit", 0xC7, 4096, LP_OPTIONS16), + + /* 64 Gigabit */ + EXTENDED_ID_NAND("NAND 8GiB 1,8V 8-bit", 0xAE, 8192, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 8GiB 3,3V 8-bit", 0xDE, 8192, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 8GiB 1,8V 16-bit", 0xBE, 8192, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 8GiB 3,3V 16-bit", 0xCE, 8192, LP_OPTIONS16), + + /* 128 Gigabit */ + EXTENDED_ID_NAND("NAND 16GiB 1,8V 8-bit", 0x1A, 16384, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 16GiB 3,3V 8-bit", 0x3A, 16384, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 16GiB 1,8V 16-bit", 0x2A, 16384, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 16GiB 3,3V 16-bit", 0x4A, 16384, LP_OPTIONS16), + + /* 256 Gigabit */ + EXTENDED_ID_NAND("NAND 32GiB 1,8V 8-bit", 0x1C, 32768, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 32GiB 3,3V 8-bit", 0x3C, 32768, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 32GiB 1,8V 16-bit", 0x2C, 32768, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 32GiB 3,3V 16-bit", 0x4C, 32768, LP_OPTIONS16), + + /* 512 Gigabit */ + EXTENDED_ID_NAND("NAND 64GiB 1,8V 8-bit", 0x1E, 65536, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 64GiB 3,3V 8-bit", 0x3E, 65536, LP_OPTIONS), + EXTENDED_ID_NAND("NAND 64GiB 1,8V 16-bit", 0x2E, 65536, LP_OPTIONS16), + EXTENDED_ID_NAND("NAND 64GiB 3,3V 16-bit", 0x4E, 65536, LP_OPTIONS16), + + {NULL} +}; + +/* Manufacturer IDs */ +static const struct nand_manufacturer_desc nand_manufacturer_descs[] = { + {NAND_MFR_AMD, "AMD/Spansion", &amd_nand_manuf_ops}, + {NAND_MFR_ATO, "ATO"}, + {NAND_MFR_EON, "Eon"}, + {NAND_MFR_ESMT, "ESMT", &esmt_nand_manuf_ops}, + {NAND_MFR_FUJITSU, "Fujitsu"}, + {NAND_MFR_HYNIX, "Hynix", &hynix_nand_manuf_ops}, + {NAND_MFR_INTEL, "Intel"}, + {NAND_MFR_MACRONIX, "Macronix", ¯onix_nand_manuf_ops}, + {NAND_MFR_MICRON, "Micron", µn_nand_manuf_ops}, + {NAND_MFR_NATIONAL, "National"}, + {NAND_MFR_RENESAS, "Renesas"}, + {NAND_MFR_SAMSUNG, "Samsung", &samsung_nand_manuf_ops}, + {NAND_MFR_SANDISK, "SanDisk", &sandisk_nand_manuf_ops}, + {NAND_MFR_STMICRO, "ST Micro"}, + {NAND_MFR_TOSHIBA, "Toshiba", &toshiba_nand_manuf_ops}, + {NAND_MFR_WINBOND, "Winbond"}, +}; + +/** + * nand_get_manufacturer_desc - Get manufacturer information from the + * manufacturer ID + * @id: manufacturer ID + * + * Returns a nand_manufacturer_desc object if the manufacturer is defined + * in the NAND manufacturers database, NULL otherwise. + */ +const struct nand_manufacturer_desc *nand_get_manufacturer_desc(u8 id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(nand_manufacturer_descs); i++) + if (nand_manufacturer_descs[i].id == id) + return &nand_manufacturer_descs[i]; + + return NULL; +} diff --git a/drivers/mtd/nand/raw/nand_jedec.c b/drivers/mtd/nand/raw/nand_jedec.c new file mode 100644 index 000000000..b523bf657 --- /dev/null +++ b/drivers/mtd/nand/raw/nand_jedec.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) + * 2002-2006 Thomas Gleixner (tglx@kernel.org) + * + * Credits: + * David Woodhouse for adding multichip support + * + * Aleph One Ltd. and Toby Churchill Ltd. for supporting the + * rework for 2K page size chips + * + * This file contains all ONFI helpers. + */ + +#include + +#include "internals.h" + +#define JEDEC_PARAM_PAGES 3 + +/* + * Check if the NAND chip is JEDEC compliant, returns 1 if it is, 0 otherwise. + */ +int nand_jedec_detect(struct nand_chip *chip) +{ + struct nand_device *base = &chip->base; + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_memory_organization *memorg; + struct nand_jedec_params *p; + struct jedec_ecc_info *ecc; + bool use_datain = false; + int jedec_version = 0; + char id[5]; + int i, val, ret; + u16 crc; + + memorg = nanddev_get_memorg(&chip->base); + + /* Try JEDEC for unknown chip or LP */ + ret = nand_readid_op(chip, 0x40, id, sizeof(id)); + if (ret || strncmp(id, "JEDEC", sizeof(id))) + return 0; + + /* JEDEC chip: allocate a buffer to hold its parameter page */ + p = kzalloc_obj(*p); + if (!p) + return -ENOMEM; + + if (!nand_has_exec_op(chip) || chip->controller->supported_op.data_only_read) + use_datain = true; + + for (i = 0; i < JEDEC_PARAM_PAGES; i++) { + if (!i) + ret = nand_read_param_page_op(chip, 0x40, p, + sizeof(*p)); + else if (use_datain) + ret = nand_read_data_op(chip, p, sizeof(*p), true, + false); + else + ret = nand_change_read_column_op(chip, sizeof(*p) * i, + p, sizeof(*p), true); + if (ret) { + ret = 0; + goto free_jedec_param_page; + } + + crc = onfi_crc16(ONFI_CRC_BASE, (u8 *)p, 510); + if (crc == le16_to_cpu(p->crc)) + break; + } + + if (i == JEDEC_PARAM_PAGES) { + pr_err("Could not find valid JEDEC parameter page; aborting\n"); + goto free_jedec_param_page; + } + + /* Check version */ + val = le16_to_cpu(p->revision); + if (val & (1 << 2)) + jedec_version = 10; + else if (val & (1 << 1)) + jedec_version = 1; /* vendor specific version */ + + if (!jedec_version) { + pr_info("unsupported JEDEC version: %d\n", val); + goto free_jedec_param_page; + } + + sanitize_string(p->manufacturer, sizeof(p->manufacturer)); + sanitize_string(p->model, sizeof(p->model)); + chip->parameters.model = kstrdup(p->model, GFP_KERNEL); + if (!chip->parameters.model) { + ret = -ENOMEM; + goto free_jedec_param_page; + } + + if (p->opt_cmd[0] & JEDEC_OPT_CMD_READ_CACHE) + chip->parameters.supports_read_cache = true; + + memorg->pagesize = le32_to_cpu(p->byte_per_page); + mtd->writesize = memorg->pagesize; + + /* Please reference to the comment for nand_flash_detect_onfi. */ + memorg->pages_per_eraseblock = + 1 << (fls(le32_to_cpu(p->pages_per_block)) - 1); + mtd->erasesize = memorg->pages_per_eraseblock * memorg->pagesize; + + memorg->oobsize = le16_to_cpu(p->spare_bytes_per_page); + mtd->oobsize = memorg->oobsize; + + memorg->luns_per_target = p->lun_count; + memorg->planes_per_lun = 1 << p->multi_plane_addr; + + /* Please reference to the comment for nand_flash_detect_onfi. */ + memorg->eraseblocks_per_lun = + 1 << (fls(le32_to_cpu(p->blocks_per_lun)) - 1); + memorg->bits_per_cell = p->bits_per_cell; + + if (le16_to_cpu(p->features) & JEDEC_FEATURE_16_BIT_BUS) + chip->options |= NAND_BUSWIDTH_16; + + /* ECC info */ + ecc = &p->ecc_info[0]; + + if (ecc->codeword_size >= 9) { + struct nand_ecc_props requirements = { + .strength = ecc->ecc_bits, + .step_size = 1 << ecc->codeword_size, + }; + + nanddev_set_ecc_requirements(base, &requirements); + } else { + pr_warn("Invalid codeword size\n"); + } + + ret = 1; + +free_jedec_param_page: + kfree(p); + return ret; +} diff --git a/drivers/mtd/nand/raw/nand_legacy.c b/drivers/mtd/nand/raw/nand_legacy.c new file mode 100644 index 000000000..97700f80d --- /dev/null +++ b/drivers/mtd/nand/raw/nand_legacy.c @@ -0,0 +1,644 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) + * 2002-2006 Thomas Gleixner (tglx@kernel.org) + * + * Credits: + * David Woodhouse for adding multichip support + * + * Aleph One Ltd. and Toby Churchill Ltd. for supporting the + * rework for 2K page size chips + * + * This file contains all legacy helpers/code that should be removed + * at some point. + */ + +#include +#include +#include + +#include "internals.h" + +/** + * nand_read_byte - [DEFAULT] read one byte from the chip + * @chip: NAND chip object + * + * Default read function for 8bit buswidth + */ +static uint8_t nand_read_byte(struct nand_chip *chip) +{ + return readb(chip->legacy.IO_ADDR_R); +} + +/** + * nand_read_byte16 - [DEFAULT] read one byte endianness aware from the chip + * @chip: NAND chip object + * + * Default read function for 16bit buswidth with endianness conversion. + * + */ +static uint8_t nand_read_byte16(struct nand_chip *chip) +{ + return (uint8_t) cpu_to_le16(readw(chip->legacy.IO_ADDR_R)); +} + +/** + * nand_select_chip - [DEFAULT] control CE line + * @chip: NAND chip object + * @chipnr: chipnumber to select, -1 for deselect + * + * Default select function for 1 chip devices. + */ +static void nand_select_chip(struct nand_chip *chip, int chipnr) +{ + switch (chipnr) { + case -1: + chip->legacy.cmd_ctrl(chip, NAND_CMD_NONE, + 0 | NAND_CTRL_CHANGE); + break; + case 0: + break; + + default: + BUG(); + } +} + +/** + * nand_write_byte - [DEFAULT] write single byte to chip + * @chip: NAND chip object + * @byte: value to write + * + * Default function to write a byte to I/O[7:0] + */ +static void nand_write_byte(struct nand_chip *chip, uint8_t byte) +{ + chip->legacy.write_buf(chip, &byte, 1); +} + +/** + * nand_write_byte16 - [DEFAULT] write single byte to a chip with width 16 + * @chip: NAND chip object + * @byte: value to write + * + * Default function to write a byte to I/O[7:0] on a 16-bit wide chip. + */ +static void nand_write_byte16(struct nand_chip *chip, uint8_t byte) +{ + uint16_t word = byte; + + /* + * It's not entirely clear what should happen to I/O[15:8] when writing + * a byte. The ONFi spec (Revision 3.1; 2012-09-19, Section 2.16) reads: + * + * When the host supports a 16-bit bus width, only data is + * transferred at the 16-bit width. All address and command line + * transfers shall use only the lower 8-bits of the data bus. During + * command transfers, the host may place any value on the upper + * 8-bits of the data bus. During address transfers, the host shall + * set the upper 8-bits of the data bus to 00h. + * + * One user of the write_byte callback is nand_set_features. The + * four parameters are specified to be written to I/O[7:0], but this is + * neither an address nor a command transfer. Let's assume a 0 on the + * upper I/O lines is OK. + */ + chip->legacy.write_buf(chip, (uint8_t *)&word, 2); +} + +/** + * nand_write_buf - [DEFAULT] write buffer to chip + * @chip: NAND chip object + * @buf: data buffer + * @len: number of bytes to write + * + * Default write function for 8bit buswidth. + */ +static void nand_write_buf(struct nand_chip *chip, const uint8_t *buf, int len) +{ + iowrite8_rep(chip->legacy.IO_ADDR_W, buf, len); +} + +/** + * nand_read_buf - [DEFAULT] read chip data into buffer + * @chip: NAND chip object + * @buf: buffer to store date + * @len: number of bytes to read + * + * Default read function for 8bit buswidth. + */ +static void nand_read_buf(struct nand_chip *chip, uint8_t *buf, int len) +{ + ioread8_rep(chip->legacy.IO_ADDR_R, buf, len); +} + +/** + * nand_write_buf16 - [DEFAULT] write buffer to chip + * @chip: NAND chip object + * @buf: data buffer + * @len: number of bytes to write + * + * Default write function for 16bit buswidth. + */ +static void nand_write_buf16(struct nand_chip *chip, const uint8_t *buf, + int len) +{ + u16 *p = (u16 *) buf; + + iowrite16_rep(chip->legacy.IO_ADDR_W, p, len >> 1); +} + +/** + * nand_read_buf16 - [DEFAULT] read chip data into buffer + * @chip: NAND chip object + * @buf: buffer to store date + * @len: number of bytes to read + * + * Default read function for 16bit buswidth. + */ +static void nand_read_buf16(struct nand_chip *chip, uint8_t *buf, int len) +{ + u16 *p = (u16 *) buf; + + ioread16_rep(chip->legacy.IO_ADDR_R, p, len >> 1); +} + +/** + * panic_nand_wait_ready - [GENERIC] Wait for the ready pin after commands. + * @chip: NAND chip object + * @timeo: Timeout + * + * Helper function for nand_wait_ready used when needing to wait in interrupt + * context. + */ +static void panic_nand_wait_ready(struct nand_chip *chip, unsigned long timeo) +{ + int i; + + /* Wait for the device to get ready */ + for (i = 0; i < timeo; i++) { + if (chip->legacy.dev_ready(chip)) + break; + touch_softlockup_watchdog(); + mdelay(1); + } +} + +/** + * nand_wait_ready - [GENERIC] Wait for the ready pin after commands. + * @chip: NAND chip object + * + * Wait for the ready pin after a command, and warn if a timeout occurs. + */ +void nand_wait_ready(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned long timeo = 400; + + if (mtd->oops_panic_write) + return panic_nand_wait_ready(chip, timeo); + + /* Wait until command is processed or timeout occurs */ + timeo = jiffies + msecs_to_jiffies(timeo); + do { + if (chip->legacy.dev_ready(chip)) + return; + cond_resched(); + } while (time_before(jiffies, timeo)); + + if (!chip->legacy.dev_ready(chip)) + pr_warn_ratelimited("timeout while waiting for chip to become ready\n"); +} +EXPORT_SYMBOL_GPL(nand_wait_ready); + +/** + * nand_wait_status_ready - [GENERIC] Wait for the ready status after commands. + * @chip: NAND chip object + * @timeo: Timeout in ms + * + * Wait for status ready (i.e. command done) or timeout. + */ +static void nand_wait_status_ready(struct nand_chip *chip, unsigned long timeo) +{ + int ret; + + timeo = jiffies + msecs_to_jiffies(timeo); + do { + u8 status; + + ret = nand_read_data_op(chip, &status, sizeof(status), true, + false); + if (ret) + return; + + if (status & NAND_STATUS_READY) + break; + touch_softlockup_watchdog(); + } while (time_before(jiffies, timeo)); +}; + +/** + * nand_command - [DEFAULT] Send command to NAND device + * @chip: NAND chip object + * @command: the command to be sent + * @column: the column address for this command, -1 if none + * @page_addr: the page address for this command, -1 if none + * + * Send command to NAND device. This function is used for small page devices + * (512 Bytes per page). + */ +static void nand_command(struct nand_chip *chip, unsigned int command, + int column, int page_addr) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ctrl = NAND_CTRL_CLE | NAND_CTRL_CHANGE; + + /* Write out the command to the device */ + if (command == NAND_CMD_SEQIN) { + int readcmd; + + if (column >= mtd->writesize) { + /* OOB area */ + column -= mtd->writesize; + readcmd = NAND_CMD_READOOB; + } else if (column < 256) { + /* First 256 bytes --> READ0 */ + readcmd = NAND_CMD_READ0; + } else { + column -= 256; + readcmd = NAND_CMD_READ1; + } + chip->legacy.cmd_ctrl(chip, readcmd, ctrl); + ctrl &= ~NAND_CTRL_CHANGE; + } + if (command != NAND_CMD_NONE) + chip->legacy.cmd_ctrl(chip, command, ctrl); + + /* Address cycle, when necessary */ + ctrl = NAND_CTRL_ALE | NAND_CTRL_CHANGE; + /* Serially input address */ + if (column != -1) { + /* Adjust columns for 16 bit buswidth */ + if (chip->options & NAND_BUSWIDTH_16 && + !nand_opcode_8bits(command)) + column >>= 1; + chip->legacy.cmd_ctrl(chip, column, ctrl); + ctrl &= ~NAND_CTRL_CHANGE; + } + if (page_addr != -1) { + chip->legacy.cmd_ctrl(chip, page_addr, ctrl); + ctrl &= ~NAND_CTRL_CHANGE; + chip->legacy.cmd_ctrl(chip, page_addr >> 8, ctrl); + if (chip->options & NAND_ROW_ADDR_3) + chip->legacy.cmd_ctrl(chip, page_addr >> 16, ctrl); + } + chip->legacy.cmd_ctrl(chip, NAND_CMD_NONE, + NAND_NCE | NAND_CTRL_CHANGE); + + /* + * Program and erase have their own busy handlers status and sequential + * in needs no delay + */ + switch (command) { + + case NAND_CMD_NONE: + case NAND_CMD_PAGEPROG: + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + case NAND_CMD_SEQIN: + case NAND_CMD_STATUS: + case NAND_CMD_READID: + case NAND_CMD_SET_FEATURES: + return; + + case NAND_CMD_RESET: + if (chip->legacy.dev_ready) + break; + udelay(chip->legacy.chip_delay); + chip->legacy.cmd_ctrl(chip, NAND_CMD_STATUS, + NAND_CTRL_CLE | NAND_CTRL_CHANGE); + chip->legacy.cmd_ctrl(chip, NAND_CMD_NONE, + NAND_NCE | NAND_CTRL_CHANGE); + /* EZ-NAND can take upto 250ms as per ONFi v4.0 */ + nand_wait_status_ready(chip, 250); + return; + + /* This applies to read commands */ + case NAND_CMD_READ0: + /* + * READ0 is sometimes used to exit GET STATUS mode. When this + * is the case no address cycles are requested, and we can use + * this information to detect that we should not wait for the + * device to be ready. + */ + if (column == -1 && page_addr == -1) + return; + fallthrough; + default: + /* + * If we don't have access to the busy pin, we apply the given + * command delay + */ + if (!chip->legacy.dev_ready) { + udelay(chip->legacy.chip_delay); + return; + } + } + /* + * Apply this short delay always to ensure that we do wait tWB in + * any case on any machine. + */ + ndelay(100); + + nand_wait_ready(chip); +} + +static void nand_ccs_delay(struct nand_chip *chip) +{ + const struct nand_sdr_timings *sdr = + nand_get_sdr_timings(nand_get_interface_config(chip)); + + /* + * The controller already takes care of waiting for tCCS when the RNDIN + * or RNDOUT command is sent, return directly. + */ + if (!(chip->options & NAND_WAIT_TCCS)) + return; + + /* + * Wait tCCS_min if it is correctly defined, otherwise wait 500ns + * (which should be safe for all NANDs). + */ + if (!IS_ERR(sdr) && nand_controller_can_setup_interface(chip)) + ndelay(sdr->tCCS_min / 1000); + else + ndelay(500); +} + +/** + * nand_command_lp - [DEFAULT] Send command to NAND large page device + * @chip: NAND chip object + * @command: the command to be sent + * @column: the column address for this command, -1 if none + * @page_addr: the page address for this command, -1 if none + * + * Send command to NAND device. This is the version for the new large page + * devices. We don't have the separate regions as we have in the small page + * devices. We must emulate NAND_CMD_READOOB to keep the code compatible. + */ +static void nand_command_lp(struct nand_chip *chip, unsigned int command, + int column, int page_addr) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + /* Emulate NAND_CMD_READOOB */ + if (command == NAND_CMD_READOOB) { + column += mtd->writesize; + command = NAND_CMD_READ0; + } + + /* Command latch cycle */ + if (command != NAND_CMD_NONE) + chip->legacy.cmd_ctrl(chip, command, + NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); + + if (column != -1 || page_addr != -1) { + int ctrl = NAND_CTRL_CHANGE | NAND_NCE | NAND_ALE; + + /* Serially input address */ + if (column != -1) { + /* Adjust columns for 16 bit buswidth */ + if (chip->options & NAND_BUSWIDTH_16 && + !nand_opcode_8bits(command)) + column >>= 1; + chip->legacy.cmd_ctrl(chip, column, ctrl); + ctrl &= ~NAND_CTRL_CHANGE; + + /* Only output a single addr cycle for 8bits opcodes. */ + if (!nand_opcode_8bits(command)) + chip->legacy.cmd_ctrl(chip, column >> 8, ctrl); + } + if (page_addr != -1) { + chip->legacy.cmd_ctrl(chip, page_addr, ctrl); + chip->legacy.cmd_ctrl(chip, page_addr >> 8, + NAND_NCE | NAND_ALE); + if (chip->options & NAND_ROW_ADDR_3) + chip->legacy.cmd_ctrl(chip, page_addr >> 16, + NAND_NCE | NAND_ALE); + } + } + chip->legacy.cmd_ctrl(chip, NAND_CMD_NONE, + NAND_NCE | NAND_CTRL_CHANGE); + + /* + * Program and erase have their own busy handlers status, sequential + * in and status need no delay. + */ + switch (command) { + + case NAND_CMD_NONE: + case NAND_CMD_CACHEDPROG: + case NAND_CMD_PAGEPROG: + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + case NAND_CMD_SEQIN: + case NAND_CMD_STATUS: + case NAND_CMD_READID: + case NAND_CMD_SET_FEATURES: + return; + + case NAND_CMD_RNDIN: + nand_ccs_delay(chip); + return; + + case NAND_CMD_RESET: + if (chip->legacy.dev_ready) + break; + udelay(chip->legacy.chip_delay); + chip->legacy.cmd_ctrl(chip, NAND_CMD_STATUS, + NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); + chip->legacy.cmd_ctrl(chip, NAND_CMD_NONE, + NAND_NCE | NAND_CTRL_CHANGE); + /* EZ-NAND can take upto 250ms as per ONFi v4.0 */ + nand_wait_status_ready(chip, 250); + return; + + case NAND_CMD_RNDOUT: + /* No ready / busy check necessary */ + chip->legacy.cmd_ctrl(chip, NAND_CMD_RNDOUTSTART, + NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); + chip->legacy.cmd_ctrl(chip, NAND_CMD_NONE, + NAND_NCE | NAND_CTRL_CHANGE); + + nand_ccs_delay(chip); + return; + + case NAND_CMD_READ0: + /* + * READ0 is sometimes used to exit GET STATUS mode. When this + * is the case no address cycles are requested, and we can use + * this information to detect that READSTART should not be + * issued. + */ + if (column == -1 && page_addr == -1) + return; + + chip->legacy.cmd_ctrl(chip, NAND_CMD_READSTART, + NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE); + chip->legacy.cmd_ctrl(chip, NAND_CMD_NONE, + NAND_NCE | NAND_CTRL_CHANGE); + fallthrough; /* This applies to read commands */ + default: + /* + * If we don't have access to the busy pin, we apply the given + * command delay. + */ + if (!chip->legacy.dev_ready) { + udelay(chip->legacy.chip_delay); + return; + } + } + + /* + * Apply this short delay always to ensure that we do wait tWB in + * any case on any machine. + */ + ndelay(100); + + nand_wait_ready(chip); +} + +/** + * nand_get_set_features_notsupp - set/get features stub returning -ENOTSUPP + * @chip: nand chip info structure + * @addr: feature address. + * @subfeature_param: the subfeature parameters, a four bytes array. + * + * Should be used by NAND controller drivers that do not support the SET/GET + * FEATURES operations. + */ +int nand_get_set_features_notsupp(struct nand_chip *chip, int addr, + u8 *subfeature_param) +{ + return -ENOTSUPP; +} +EXPORT_SYMBOL(nand_get_set_features_notsupp); + +/** + * nand_wait - [DEFAULT] wait until the command is done + * @chip: NAND chip structure + * + * Wait for command done. This applies to erase and program only. + */ +static int nand_wait(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned long timeo = 400; + u8 status; + int ret; + + /* + * Apply this short delay always to ensure that we do wait tWB in any + * case on any machine. + */ + ndelay(100); + + ret = nand_status_op(chip, NULL); + if (ret) + return ret; + + if (mtd->oops_panic_write) { + panic_nand_wait(chip, timeo); + } else { + timeo = jiffies + msecs_to_jiffies(timeo); + do { + if (chip->legacy.dev_ready) { + if (chip->legacy.dev_ready(chip)) + break; + } else { + ret = nand_read_data_op(chip, &status, + sizeof(status), true, + false); + if (ret) + return ret; + + if (status & NAND_STATUS_READY) + break; + } + cond_resched(); + } while (time_before(jiffies, timeo)); + } + + ret = nand_read_data_op(chip, &status, sizeof(status), true, false); + if (ret) + return ret; + + /* This can happen if in case of timeout or buggy dev_ready */ + WARN_ON(!(status & NAND_STATUS_READY)); + return status; +} + +void nand_legacy_set_defaults(struct nand_chip *chip) +{ + unsigned int busw = chip->options & NAND_BUSWIDTH_16; + + if (nand_has_exec_op(chip)) + return; + + /* check for proper chip_delay setup, set 20us if not */ + if (!chip->legacy.chip_delay) + chip->legacy.chip_delay = 20; + + /* check, if a user supplied command function given */ + if (!chip->legacy.cmdfunc) + chip->legacy.cmdfunc = nand_command; + + /* check, if a user supplied wait function given */ + if (chip->legacy.waitfunc == NULL) + chip->legacy.waitfunc = nand_wait; + + if (!chip->legacy.select_chip) + chip->legacy.select_chip = nand_select_chip; + + /* If called twice, pointers that depend on busw may need to be reset */ + if (!chip->legacy.read_byte || chip->legacy.read_byte == nand_read_byte) + chip->legacy.read_byte = busw ? nand_read_byte16 : nand_read_byte; + if (!chip->legacy.write_buf || chip->legacy.write_buf == nand_write_buf) + chip->legacy.write_buf = busw ? nand_write_buf16 : nand_write_buf; + if (!chip->legacy.write_byte || chip->legacy.write_byte == nand_write_byte) + chip->legacy.write_byte = busw ? nand_write_byte16 : nand_write_byte; + if (!chip->legacy.read_buf || chip->legacy.read_buf == nand_read_buf) + chip->legacy.read_buf = busw ? nand_read_buf16 : nand_read_buf; +} + +void nand_legacy_adjust_cmdfunc(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + /* Do not replace user supplied command function! */ + if (mtd->writesize > 512 && chip->legacy.cmdfunc == nand_command) + chip->legacy.cmdfunc = nand_command_lp; +} + +int nand_legacy_check_hooks(struct nand_chip *chip) +{ + /* + * ->legacy.cmdfunc() is legacy and will only be used if ->exec_op() is + * not populated. + */ + if (nand_has_exec_op(chip)) + return 0; + + /* + * Default functions assigned for ->legacy.cmdfunc() and + * ->legacy.select_chip() both expect ->legacy.cmd_ctrl() to be + * populated. + */ + if ((!chip->legacy.cmdfunc || !chip->legacy.select_chip) && + !chip->legacy.cmd_ctrl) { + pr_err("->legacy.cmd_ctrl() should be provided\n"); + return -EINVAL; + } + + return 0; +} diff --git a/drivers/mtd/nand/raw/nand_macronix.c b/drivers/mtd/nand/raw/nand_macronix.c new file mode 100644 index 000000000..032373108 --- /dev/null +++ b/drivers/mtd/nand/raw/nand_macronix.c @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2017 Free Electrons + * Copyright (C) 2017 NextThing Co + * + * Author: Boris Brezillon + */ + +#include +#include "linux/delay.h" +#include "internals.h" + +#define MACRONIX_READ_RETRY_BIT BIT(0) +#define MACRONIX_NUM_READ_RETRY_MODES 6 + +#define ONFI_FEATURE_ADDR_MXIC_PROTECTION 0xA0 +#define MXIC_BLOCK_PROTECTION_ALL_LOCK 0x38 +#define MXIC_BLOCK_PROTECTION_ALL_UNLOCK 0x0 + +#define ONFI_FEATURE_ADDR_MXIC_RANDOMIZER 0xB0 +#define MACRONIX_RANDOMIZER_BIT BIT(1) +#define MACRONIX_RANDOMIZER_ENPGM BIT(0) +#define MACRONIX_RANDOMIZER_RANDEN BIT(1) +#define MACRONIX_RANDOMIZER_RANDOPT BIT(2) +#define MACRONIX_RANDOMIZER_MODE_ENTER \ + (MACRONIX_RANDOMIZER_ENPGM | \ + MACRONIX_RANDOMIZER_RANDEN | \ + MACRONIX_RANDOMIZER_RANDOPT) +#define MACRONIX_RANDOMIZER_MODE_EXIT \ + (MACRONIX_RANDOMIZER_RANDEN | \ + MACRONIX_RANDOMIZER_RANDOPT) + +#define MXIC_CMD_POWER_DOWN 0xB9 + +#define ONFI_FEATURE_ADDR_30LFXG18AC_OTP 0x90 +#define MACRONIX_30LFXG18AC_OTP_START_PAGE 2 +#define MACRONIX_30LFXG18AC_OTP_PAGES 30 +#define MACRONIX_30LFXG18AC_OTP_PAGE_SIZE 2112 +#define MACRONIX_30LFXG18AC_OTP_SIZE_BYTES \ + (MACRONIX_30LFXG18AC_OTP_PAGES * \ + MACRONIX_30LFXG18AC_OTP_PAGE_SIZE) + +#define MACRONIX_30LFXG18AC_OTP_EN BIT(0) + +struct nand_onfi_vendor_macronix { + u8 reserved; + u8 reliability_func; +} __packed; + +static int macronix_nand_setup_read_retry(struct nand_chip *chip, int mode) +{ + u8 feature[ONFI_SUBFEATURE_PARAM_LEN]; + + if (!chip->parameters.supports_set_get_features || + !test_bit(ONFI_FEATURE_ADDR_READ_RETRY, + chip->parameters.set_feature_list)) + return -ENOTSUPP; + + feature[0] = mode; + return nand_set_features(chip, ONFI_FEATURE_ADDR_READ_RETRY, feature); +} + +static int macronix_nand_randomizer_check_enable(struct nand_chip *chip) +{ + u8 feature[ONFI_SUBFEATURE_PARAM_LEN]; + int ret; + + ret = nand_get_features(chip, ONFI_FEATURE_ADDR_MXIC_RANDOMIZER, + feature); + if (ret < 0) + return ret; + + if (feature[0]) + return feature[0]; + + feature[0] = MACRONIX_RANDOMIZER_MODE_ENTER; + ret = nand_set_features(chip, ONFI_FEATURE_ADDR_MXIC_RANDOMIZER, + feature); + if (ret < 0) + return ret; + + /* RANDEN and RANDOPT OTP bits are programmed */ + feature[0] = 0x0; + ret = nand_prog_page_op(chip, 0, 0, feature, 1); + if (ret < 0) + return ret; + + ret = nand_get_features(chip, ONFI_FEATURE_ADDR_MXIC_RANDOMIZER, + feature); + if (ret < 0) + return ret; + + feature[0] &= MACRONIX_RANDOMIZER_MODE_EXIT; + ret = nand_set_features(chip, ONFI_FEATURE_ADDR_MXIC_RANDOMIZER, + feature); + if (ret < 0) + return ret; + + return 0; +} + +static void macronix_nand_onfi_init(struct nand_chip *chip) +{ + struct nand_parameters *p = &chip->parameters; + struct nand_onfi_vendor_macronix *mxic; + struct device_node *dn = nand_get_flash_node(chip); + int rand_otp; + int ret; + + if (!p->onfi) + return; + + rand_otp = of_property_read_bool(dn, "mxic,enable-randomizer-otp"); + + mxic = (struct nand_onfi_vendor_macronix *)p->onfi->vendor; + /* Subpage write is prohibited in randomizer operation */ + if (rand_otp && chip->options & NAND_NO_SUBPAGE_WRITE && + mxic->reliability_func & MACRONIX_RANDOMIZER_BIT) { + if (p->supports_set_get_features) { + bitmap_set(p->set_feature_list, + ONFI_FEATURE_ADDR_MXIC_RANDOMIZER, 1); + bitmap_set(p->get_feature_list, + ONFI_FEATURE_ADDR_MXIC_RANDOMIZER, 1); + ret = macronix_nand_randomizer_check_enable(chip); + if (ret < 0) { + bitmap_clear(p->set_feature_list, + ONFI_FEATURE_ADDR_MXIC_RANDOMIZER, + 1); + bitmap_clear(p->get_feature_list, + ONFI_FEATURE_ADDR_MXIC_RANDOMIZER, + 1); + pr_info("Macronix NAND randomizer failed\n"); + } else { + pr_info("Macronix NAND randomizer enabled\n"); + } + } + } + + if ((mxic->reliability_func & MACRONIX_READ_RETRY_BIT) == 0) + return; + + chip->read_retries = MACRONIX_NUM_READ_RETRY_MODES; + chip->ops.setup_read_retry = macronix_nand_setup_read_retry; + + if (p->supports_set_get_features) { + bitmap_set(p->set_feature_list, + ONFI_FEATURE_ADDR_READ_RETRY, 1); + bitmap_set(p->get_feature_list, + ONFI_FEATURE_ADDR_READ_RETRY, 1); + } +} + +/* + * Macronix AC series does not support using SET/GET_FEATURES to change + * the timings unlike what is declared in the parameter page. Unflag + * this feature to avoid unnecessary downturns. + */ +static void macronix_nand_fix_broken_get_timings(struct nand_chip *chip) +{ + int i; + static const char * const broken_get_timings[] = { + "MX30LF1G18AC", + "MX30LF1G28AC", + "MX30LF2G18AC", + "MX30LF2G28AC", + "MX30LF4G18AC", + "MX30LF4G28AC", + "MX60LF8G18AC", + "MX30UF1G18AC", + "MX30UF1G16AC", + "MX30UF2G18AC", + "MX30UF2G16AC", + "MX30UF4G18AC", + "MX30UF4G16AC", + "MX30UF4G28AC", + }; + + if (!chip->parameters.supports_set_get_features) + return; + + i = match_string(broken_get_timings, ARRAY_SIZE(broken_get_timings), + chip->parameters.model); + if (i < 0) + return; + + bitmap_clear(chip->parameters.get_feature_list, + ONFI_FEATURE_ADDR_TIMING_MODE, 1); + bitmap_clear(chip->parameters.set_feature_list, + ONFI_FEATURE_ADDR_TIMING_MODE, 1); +} + +/* + * Macronix NAND supports Block Protection by Protectoin(PT) pin; + * active high at power-on which protects the entire chip even the #WP is + * disabled. Lock/unlock protection area can be partition according to + * protection bits, i.e. upper 1/2 locked, upper 1/4 locked and so on. + */ +static int mxic_nand_lock(struct nand_chip *chip, loff_t ofs, uint64_t len) +{ + u8 feature[ONFI_SUBFEATURE_PARAM_LEN]; + int ret; + + feature[0] = MXIC_BLOCK_PROTECTION_ALL_LOCK; + nand_select_target(chip, 0); + ret = nand_set_features(chip, ONFI_FEATURE_ADDR_MXIC_PROTECTION, + feature); + nand_deselect_target(chip); + if (ret) + pr_err("%s all blocks failed\n", __func__); + + return ret; +} + +static int mxic_nand_unlock(struct nand_chip *chip, loff_t ofs, uint64_t len) +{ + u8 feature[ONFI_SUBFEATURE_PARAM_LEN]; + int ret; + + feature[0] = MXIC_BLOCK_PROTECTION_ALL_UNLOCK; + nand_select_target(chip, 0); + ret = nand_set_features(chip, ONFI_FEATURE_ADDR_MXIC_PROTECTION, + feature); + nand_deselect_target(chip); + if (ret) + pr_err("%s all blocks failed\n", __func__); + + return ret; +} + +static void macronix_nand_block_protection_support(struct nand_chip *chip) +{ + u8 feature[ONFI_SUBFEATURE_PARAM_LEN]; + int ret; + + bitmap_set(chip->parameters.get_feature_list, + ONFI_FEATURE_ADDR_MXIC_PROTECTION, 1); + + feature[0] = MXIC_BLOCK_PROTECTION_ALL_UNLOCK; + nand_select_target(chip, 0); + ret = nand_get_features(chip, ONFI_FEATURE_ADDR_MXIC_PROTECTION, + feature); + nand_deselect_target(chip); + if (ret || feature[0] != MXIC_BLOCK_PROTECTION_ALL_LOCK) { + if (ret) + pr_err("Block protection check failed\n"); + + bitmap_clear(chip->parameters.get_feature_list, + ONFI_FEATURE_ADDR_MXIC_PROTECTION, 1); + return; + } + + bitmap_set(chip->parameters.set_feature_list, + ONFI_FEATURE_ADDR_MXIC_PROTECTION, 1); + + chip->ops.lock_area = mxic_nand_lock; + chip->ops.unlock_area = mxic_nand_unlock; +} + +static int nand_power_down_op(struct nand_chip *chip) +{ + int ret; + + if (nand_has_exec_op(chip)) { + struct nand_op_instr instrs[] = { + NAND_OP_CMD(MXIC_CMD_POWER_DOWN, 0), + }; + + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + + ret = nand_exec_op(chip, &op); + if (ret) + return ret; + + } else { + chip->legacy.cmdfunc(chip, MXIC_CMD_POWER_DOWN, -1, -1); + } + + return 0; +} + +static int mxic_nand_suspend(struct nand_chip *chip) +{ + int ret; + + nand_select_target(chip, 0); + ret = nand_power_down_op(chip); + if (ret < 0) + pr_err("Suspending MXIC NAND chip failed (%d)\n", ret); + nand_deselect_target(chip); + + return ret; +} + +static void mxic_nand_resume(struct nand_chip *chip) +{ + /* + * Toggle #CS pin to resume NAND device and don't care + * of the others CLE, #WE, #RE pins status. + * A NAND controller ensure it is able to assert/de-assert #CS + * by sending any byte over the NAND bus. + * i.e., + * NAND power down command or reset command w/o R/B# status checking. + */ + nand_select_target(chip, 0); + nand_power_down_op(chip); + /* The minimum of a recovery time tRDP is 35 us */ + usleep_range(35, 100); + nand_deselect_target(chip); +} + +static void macronix_nand_deep_power_down_support(struct nand_chip *chip) +{ + int i; + static const char * const deep_power_down_dev[] = { + "MX30UF1G28AD", + "MX30UF2G28AD", + "MX30UF4G28AD", + }; + + i = match_string(deep_power_down_dev, ARRAY_SIZE(deep_power_down_dev), + chip->parameters.model); + if (i < 0) + return; + + chip->ops.suspend = mxic_nand_suspend; + chip->ops.resume = mxic_nand_resume; +} + +static int macronix_30lfxg18ac_get_otp_info(struct mtd_info *mtd, size_t len, + size_t *retlen, + struct otp_info *buf) +{ + if (len < sizeof(*buf)) + return -EINVAL; + + /* Always report that OTP is unlocked. Reason is that this + * type of flash chip doesn't provide way to check that OTP + * is locked or not: subfeature parameter is implemented as + * volatile register. Technically OTP region could be locked + * and become readonly, but as there is no way to check it, + * don't allow to lock it ('_lock_user_prot_reg' callback + * always returns -EOPNOTSUPP) and thus we report that OTP + * is unlocked. + */ + buf->locked = 0; + buf->start = 0; + buf->length = MACRONIX_30LFXG18AC_OTP_SIZE_BYTES; + + *retlen = sizeof(*buf); + + return 0; +} + +static int macronix_30lfxg18ac_otp_enable(struct nand_chip *nand) +{ + u8 feature_buf[ONFI_SUBFEATURE_PARAM_LEN] = { 0 }; + + feature_buf[0] = MACRONIX_30LFXG18AC_OTP_EN; + return nand_set_features(nand, ONFI_FEATURE_ADDR_30LFXG18AC_OTP, + feature_buf); +} + +static int macronix_30lfxg18ac_otp_disable(struct nand_chip *nand) +{ + u8 feature_buf[ONFI_SUBFEATURE_PARAM_LEN] = { 0 }; + + return nand_set_features(nand, ONFI_FEATURE_ADDR_30LFXG18AC_OTP, + feature_buf); +} + +static int __macronix_30lfxg18ac_rw_otp(struct mtd_info *mtd, + loff_t offs_in_flash, + size_t len, size_t *retlen, + u_char *buf, bool write) +{ + struct nand_chip *nand; + size_t bytes_handled; + off_t offs_in_page; + u64 page; + int ret; + + nand = mtd_to_nand(mtd); + nand_select_target(nand, 0); + + ret = macronix_30lfxg18ac_otp_enable(nand); + if (ret) + goto out_otp; + + page = offs_in_flash; + /* 'page' will be result of division. */ + offs_in_page = do_div(page, MACRONIX_30LFXG18AC_OTP_PAGE_SIZE); + bytes_handled = 0; + + while (bytes_handled < len && + page < MACRONIX_30LFXG18AC_OTP_PAGES) { + size_t bytes_to_handle; + u64 phys_page = page + MACRONIX_30LFXG18AC_OTP_START_PAGE; + + bytes_to_handle = min_t(size_t, len - bytes_handled, + MACRONIX_30LFXG18AC_OTP_PAGE_SIZE - + offs_in_page); + + if (write) + ret = nand_prog_page_op(nand, phys_page, offs_in_page, + &buf[bytes_handled], bytes_to_handle); + else + ret = nand_read_page_op(nand, phys_page, offs_in_page, + &buf[bytes_handled], bytes_to_handle); + if (ret) + goto out_otp; + + bytes_handled += bytes_to_handle; + offs_in_page = 0; + page++; + } + + *retlen = bytes_handled; + +out_otp: + if (ret) + dev_err(&mtd->dev, "failed to perform OTP IO: %i\n", ret); + + ret = macronix_30lfxg18ac_otp_disable(nand); + if (ret) + dev_err(&mtd->dev, "failed to leave OTP mode after %s\n", + write ? "write" : "read"); + + nand_deselect_target(nand); + + return ret; +} + +static int macronix_30lfxg18ac_write_otp(struct mtd_info *mtd, loff_t to, + size_t len, size_t *rlen, + const u_char *buf) +{ + return __macronix_30lfxg18ac_rw_otp(mtd, to, len, rlen, (u_char *)buf, + true); +} + +static int macronix_30lfxg18ac_read_otp(struct mtd_info *mtd, loff_t from, + size_t len, size_t *rlen, + u_char *buf) +{ + return __macronix_30lfxg18ac_rw_otp(mtd, from, len, rlen, buf, false); +} + +static int macronix_30lfxg18ac_lock_otp(struct mtd_info *mtd, loff_t from, + size_t len) +{ + /* See comment in 'macronix_30lfxg18ac_get_otp_info()'. */ + return -EOPNOTSUPP; +} + +static void macronix_nand_setup_otp(struct nand_chip *chip) +{ + static const char * const supported_otp_models[] = { + "MX30LF1G18AC", + "MX30LF2G18AC", + "MX30LF4G18AC", + }; + struct mtd_info *mtd; + + if (match_string(supported_otp_models, + ARRAY_SIZE(supported_otp_models), + chip->parameters.model) < 0) + return; + + if (!chip->parameters.supports_set_get_features) + return; + + bitmap_set(chip->parameters.get_feature_list, + ONFI_FEATURE_ADDR_30LFXG18AC_OTP, 1); + bitmap_set(chip->parameters.set_feature_list, + ONFI_FEATURE_ADDR_30LFXG18AC_OTP, 1); + + mtd = nand_to_mtd(chip); + mtd->_get_user_prot_info = macronix_30lfxg18ac_get_otp_info; + mtd->_read_user_prot_reg = macronix_30lfxg18ac_read_otp; + mtd->_write_user_prot_reg = macronix_30lfxg18ac_write_otp; + mtd->_lock_user_prot_reg = macronix_30lfxg18ac_lock_otp; +} + +static int macronix_nand_init(struct nand_chip *chip) +{ + if (nand_is_slc(chip)) + chip->options |= NAND_BBM_FIRSTPAGE | NAND_BBM_SECONDPAGE; + + macronix_nand_fix_broken_get_timings(chip); + macronix_nand_onfi_init(chip); + macronix_nand_block_protection_support(chip); + macronix_nand_deep_power_down_support(chip); + macronix_nand_setup_otp(chip); + + return 0; +} + +const struct nand_manufacturer_ops macronix_nand_manuf_ops = { + .init = macronix_nand_init, +}; diff --git a/drivers/mtd/nand/raw/nand_micron.c b/drivers/mtd/nand/raw/nand_micron.c new file mode 100644 index 000000000..8807b8aad --- /dev/null +++ b/drivers/mtd/nand/raw/nand_micron.c @@ -0,0 +1,599 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2017 Free Electrons + * Copyright (C) 2017 NextThing Co + * + * Author: Boris Brezillon + */ + +#include + +#include "internals.h" + +/* + * Special Micron status bit 3 indicates that the block has been + * corrected by on-die ECC and should be rewritten. + */ +#define NAND_ECC_STATUS_WRITE_RECOMMENDED BIT(3) + +/* + * On chips with 8-bit ECC and additional bit can be used to distinguish + * cases where a errors were corrected without needing a rewrite + * + * Bit 4 Bit 3 Bit 0 Description + * ----- ----- ----- ----------- + * 0 0 0 No Errors + * 0 0 1 Multiple uncorrected errors + * 0 1 0 4 - 6 errors corrected, recommend rewrite + * 0 1 1 Reserved + * 1 0 0 1 - 3 errors corrected + * 1 0 1 Reserved + * 1 1 0 7 - 8 errors corrected, recommend rewrite + */ +#define NAND_ECC_STATUS_MASK (BIT(4) | BIT(3) | BIT(0)) +#define NAND_ECC_STATUS_UNCORRECTABLE BIT(0) +#define NAND_ECC_STATUS_4_6_CORRECTED BIT(3) +#define NAND_ECC_STATUS_1_3_CORRECTED BIT(4) +#define NAND_ECC_STATUS_7_8_CORRECTED (BIT(4) | BIT(3)) + +struct nand_onfi_vendor_micron { + u8 two_plane_read; + u8 read_cache; + u8 read_unique_id; + u8 dq_imped; + u8 dq_imped_num_settings; + u8 dq_imped_feat_addr; + u8 rb_pulldown_strength; + u8 rb_pulldown_strength_feat_addr; + u8 rb_pulldown_strength_num_settings; + u8 otp_mode; + u8 otp_page_start; + u8 otp_data_prot_addr; + u8 otp_num_pages; + u8 otp_feat_addr; + u8 read_retry_options; + u8 reserved[72]; + u8 param_revision; +} __packed; + +struct micron_on_die_ecc { + bool forced; + bool enabled; + void *rawbuf; +}; + +struct micron_nand { + struct micron_on_die_ecc ecc; +}; + +static int micron_nand_setup_read_retry(struct nand_chip *chip, int retry_mode) +{ + u8 feature[ONFI_SUBFEATURE_PARAM_LEN] = {retry_mode}; + + return nand_set_features(chip, ONFI_FEATURE_ADDR_READ_RETRY, feature); +} + +/* + * Configure chip properties from Micron vendor-specific ONFI table + */ +static int micron_nand_onfi_init(struct nand_chip *chip) +{ + struct nand_parameters *p = &chip->parameters; + + if (p->onfi) { + struct nand_onfi_vendor_micron *micron = (void *)p->onfi->vendor; + + chip->read_retries = micron->read_retry_options; + chip->ops.setup_read_retry = micron_nand_setup_read_retry; + } + + if (p->supports_set_get_features) { + set_bit(ONFI_FEATURE_ADDR_READ_RETRY, p->set_feature_list); + set_bit(ONFI_FEATURE_ON_DIE_ECC, p->set_feature_list); + set_bit(ONFI_FEATURE_ADDR_READ_RETRY, p->get_feature_list); + set_bit(ONFI_FEATURE_ON_DIE_ECC, p->get_feature_list); + } + + return 0; +} + +static int micron_nand_on_die_4_ooblayout_ecc(struct mtd_info *mtd, + int section, + struct mtd_oob_region *oobregion) +{ + if (section >= 4) + return -ERANGE; + + oobregion->offset = (section * 16) + 8; + oobregion->length = 8; + + return 0; +} + +static int micron_nand_on_die_4_ooblayout_free(struct mtd_info *mtd, + int section, + struct mtd_oob_region *oobregion) +{ + if (section >= 4) + return -ERANGE; + + oobregion->offset = (section * 16) + 2; + oobregion->length = 6; + + return 0; +} + +static const struct mtd_ooblayout_ops micron_nand_on_die_4_ooblayout_ops = { + .ecc = micron_nand_on_die_4_ooblayout_ecc, + .free = micron_nand_on_die_4_ooblayout_free, +}; + +static int micron_nand_on_die_8_ooblayout_ecc(struct mtd_info *mtd, + int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section) + return -ERANGE; + + oobregion->offset = mtd->oobsize - chip->ecc.total; + oobregion->length = chip->ecc.total; + + return 0; +} + +static int micron_nand_on_die_8_ooblayout_free(struct mtd_info *mtd, + int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section) + return -ERANGE; + + oobregion->offset = 2; + oobregion->length = mtd->oobsize - chip->ecc.total - 2; + + return 0; +} + +static const struct mtd_ooblayout_ops micron_nand_on_die_8_ooblayout_ops = { + .ecc = micron_nand_on_die_8_ooblayout_ecc, + .free = micron_nand_on_die_8_ooblayout_free, +}; + +static int micron_nand_on_die_ecc_setup(struct nand_chip *chip, bool enable) +{ + struct micron_nand *micron = nand_get_manufacturer_data(chip); + u8 feature[ONFI_SUBFEATURE_PARAM_LEN] = { 0, }; + int ret; + + if (micron->ecc.forced) + return 0; + + if (micron->ecc.enabled == enable) + return 0; + + if (enable) + feature[0] |= ONFI_FEATURE_ON_DIE_ECC_EN; + + ret = nand_set_features(chip, ONFI_FEATURE_ON_DIE_ECC, feature); + if (!ret) + micron->ecc.enabled = enable; + + return ret; +} + +static int micron_nand_on_die_ecc_status_4(struct nand_chip *chip, u8 status, + void *buf, int page, + int oob_required) +{ + struct micron_nand *micron = nand_get_manufacturer_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int step, max_bitflips = 0; + bool use_datain = false; + int ret; + + if (!(status & NAND_ECC_STATUS_WRITE_RECOMMENDED)) { + if (status & NAND_STATUS_FAIL) + mtd->ecc_stats.failed++; + + return 0; + } + + /* + * The internal ECC doesn't tell us the number of bitflips that have + * been corrected, but tells us if it recommends to rewrite the block. + * If it's the case, we need to read the page in raw mode and compare + * its content to the corrected version to extract the actual number of + * bitflips. + * But before we do that, we must make sure we have all OOB bytes read + * in non-raw mode, even if the user did not request those bytes. + */ + if (!oob_required) { + /* + * We first check which operation is supported by the controller + * before running it. This trick makes it possible to support + * all controllers, even the most constraints, without almost + * any performance hit. + * + * TODO: could be enhanced to avoid repeating the same check + * over and over in the fast path. + */ + if (!nand_has_exec_op(chip) || + !nand_read_data_op(chip, chip->oob_poi, mtd->oobsize, false, + true)) + use_datain = true; + + if (use_datain) + ret = nand_read_data_op(chip, chip->oob_poi, + mtd->oobsize, false, false); + else + ret = nand_change_read_column_op(chip, mtd->writesize, + chip->oob_poi, + mtd->oobsize, false); + if (ret) + return ret; + } + + micron_nand_on_die_ecc_setup(chip, false); + + ret = nand_read_page_op(chip, page, 0, micron->ecc.rawbuf, + mtd->writesize + mtd->oobsize); + if (ret) + return ret; + + for (step = 0; step < chip->ecc.steps; step++) { + unsigned int offs, i, nbitflips = 0; + u8 *rawbuf, *corrbuf; + + offs = step * chip->ecc.size; + rawbuf = micron->ecc.rawbuf + offs; + corrbuf = buf + offs; + + for (i = 0; i < chip->ecc.size; i++) + nbitflips += hweight8(corrbuf[i] ^ rawbuf[i]); + + offs = (step * 16) + 4; + rawbuf = micron->ecc.rawbuf + mtd->writesize + offs; + corrbuf = chip->oob_poi + offs; + + for (i = 0; i < chip->ecc.bytes + 4; i++) + nbitflips += hweight8(corrbuf[i] ^ rawbuf[i]); + + if (WARN_ON(nbitflips > chip->ecc.strength)) + return -EINVAL; + + max_bitflips = max(nbitflips, max_bitflips); + mtd->ecc_stats.corrected += nbitflips; + } + + return max_bitflips; +} + +static int micron_nand_on_die_ecc_status_8(struct nand_chip *chip, u8 status) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + /* + * With 8/512 we have more information but still don't know precisely + * how many bit-flips were seen. + */ + switch (status & NAND_ECC_STATUS_MASK) { + case NAND_ECC_STATUS_UNCORRECTABLE: + mtd->ecc_stats.failed++; + return 0; + case NAND_ECC_STATUS_1_3_CORRECTED: + mtd->ecc_stats.corrected += 3; + return 3; + case NAND_ECC_STATUS_4_6_CORRECTED: + mtd->ecc_stats.corrected += 6; + /* rewrite recommended */ + return 6; + case NAND_ECC_STATUS_7_8_CORRECTED: + mtd->ecc_stats.corrected += 8; + /* rewrite recommended */ + return 8; + default: + return 0; + } +} + +static int +micron_nand_read_page_on_die_ecc(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + bool use_datain = false; + u8 status; + int ret, max_bitflips = 0; + + ret = micron_nand_on_die_ecc_setup(chip, true); + if (ret) + return ret; + + ret = nand_read_page_op(chip, page, 0, NULL, 0); + if (ret) + goto out; + + ret = nand_status_op(chip, &status); + if (ret) + goto out; + + /* + * We first check which operation is supported by the controller before + * running it. This trick makes it possible to support all controllers, + * even the most constraints, without almost any performance hit. + * + * TODO: could be enhanced to avoid repeating the same check over and + * over in the fast path. + */ + if (!nand_has_exec_op(chip) || + !nand_read_data_op(chip, buf, mtd->writesize, false, true)) + use_datain = true; + + if (use_datain) { + ret = nand_exit_status_op(chip); + if (ret) + goto out; + + ret = nand_read_data_op(chip, buf, mtd->writesize, false, + false); + if (!ret && oob_required) + ret = nand_read_data_op(chip, chip->oob_poi, + mtd->oobsize, false, false); + } else { + ret = nand_change_read_column_op(chip, 0, buf, mtd->writesize, + false); + if (!ret && oob_required) + ret = nand_change_read_column_op(chip, mtd->writesize, + chip->oob_poi, + mtd->oobsize, false); + } + + if (chip->ecc.strength == 4) + max_bitflips = micron_nand_on_die_ecc_status_4(chip, status, + buf, page, + oob_required); + else + max_bitflips = micron_nand_on_die_ecc_status_8(chip, status); + +out: + micron_nand_on_die_ecc_setup(chip, false); + + return ret ? ret : max_bitflips; +} + +static int +micron_nand_write_page_on_die_ecc(struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + int ret; + + ret = micron_nand_on_die_ecc_setup(chip, true); + if (ret) + return ret; + + ret = nand_write_page_raw(chip, buf, oob_required, page); + micron_nand_on_die_ecc_setup(chip, false); + + return ret; +} + +enum { + /* The NAND flash doesn't support on-die ECC */ + MICRON_ON_DIE_UNSUPPORTED, + + /* + * The NAND flash supports on-die ECC and it can be + * enabled/disabled by a set features command. + */ + MICRON_ON_DIE_SUPPORTED, + + /* + * The NAND flash supports on-die ECC, and it cannot be + * disabled. + */ + MICRON_ON_DIE_MANDATORY, +}; + +#define MICRON_ID_INTERNAL_ECC_MASK GENMASK(1, 0) +#define MICRON_ID_ECC_ENABLED BIT(7) + +/* + * Try to detect if the NAND support on-die ECC. To do this, we enable + * the feature, and read back if it has been enabled as expected. We + * also check if it can be disabled, because some Micron NANDs do not + * allow disabling the on-die ECC and we don't support such NANDs for + * now. + * + * This function also has the side effect of disabling on-die ECC if + * it had been left enabled by the firmware/bootloader. + */ +static int micron_supports_on_die_ecc(struct nand_chip *chip) +{ + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&chip->base); + u8 id[5]; + int ret; + + if (!chip->parameters.onfi) + return MICRON_ON_DIE_UNSUPPORTED; + + if (nanddev_bits_per_cell(&chip->base) != 1) + return MICRON_ON_DIE_UNSUPPORTED; + + /* + * We only support on-die ECC of 4/512 or 8/512 + */ + if (requirements->strength != 4 && requirements->strength != 8) + return MICRON_ON_DIE_UNSUPPORTED; + + /* 0x2 means on-die ECC is available. */ + if (chip->id.len != 5 || + (chip->id.data[4] & MICRON_ID_INTERNAL_ECC_MASK) != 0x2) + return MICRON_ON_DIE_UNSUPPORTED; + + /* + * It seems that there are devices which do not support ECC officially. + * At least the MT29F2G08ABAGA / MT29F2G08ABBGA devices supports + * enabling the ECC feature but don't reflect that to the READ_ID table. + * So we have to guarantee that we disable the ECC feature directly + * after we did the READ_ID table command. Later we can evaluate the + * ECC_ENABLE support. + */ + ret = micron_nand_on_die_ecc_setup(chip, true); + if (ret) + return MICRON_ON_DIE_UNSUPPORTED; + + ret = nand_readid_op(chip, 0, id, sizeof(id)); + if (ret) + return MICRON_ON_DIE_UNSUPPORTED; + + ret = micron_nand_on_die_ecc_setup(chip, false); + if (ret) + return MICRON_ON_DIE_UNSUPPORTED; + + if (!(id[4] & MICRON_ID_ECC_ENABLED)) + return MICRON_ON_DIE_UNSUPPORTED; + + ret = nand_readid_op(chip, 0, id, sizeof(id)); + if (ret) + return MICRON_ON_DIE_UNSUPPORTED; + + if (id[4] & MICRON_ID_ECC_ENABLED) + return MICRON_ON_DIE_MANDATORY; + + /* + * We only support on-die ECC of 4/512 or 8/512 + */ + if (requirements->strength != 4 && requirements->strength != 8) + return MICRON_ON_DIE_UNSUPPORTED; + + return MICRON_ON_DIE_SUPPORTED; +} + +static int micron_nand_init(struct nand_chip *chip) +{ + struct nand_device *base = &chip->base; + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(base); + struct mtd_info *mtd = nand_to_mtd(chip); + struct micron_nand *micron; + int ondie; + int ret; + + micron = kzalloc_obj(*micron); + if (!micron) + return -ENOMEM; + + nand_set_manufacturer_data(chip, micron); + + ret = micron_nand_onfi_init(chip); + if (ret) + goto err_free_manuf_data; + + chip->options |= NAND_BBM_FIRSTPAGE; + + if (mtd->writesize == 2048) + chip->options |= NAND_BBM_SECONDPAGE; + + ondie = micron_supports_on_die_ecc(chip); + + if (ondie == MICRON_ON_DIE_MANDATORY && + chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_DIE) { + pr_err("On-die ECC forcefully enabled, not supported\n"); + ret = -EINVAL; + goto err_free_manuf_data; + } + + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_DIE) { + if (ondie == MICRON_ON_DIE_UNSUPPORTED) { + pr_err("On-die ECC selected but not supported\n"); + ret = -EINVAL; + goto err_free_manuf_data; + } + + if (ondie == MICRON_ON_DIE_MANDATORY) { + micron->ecc.forced = true; + micron->ecc.enabled = true; + } + + /* + * In case of 4bit on-die ECC, we need a buffer to store a + * page dumped in raw mode so that we can compare its content + * to the same page after ECC correction happened and extract + * the real number of bitflips from this comparison. + * That's not needed for 8-bit ECC, because the status expose + * a better approximation of the number of bitflips in a page. + */ + if (requirements->strength == 4) { + micron->ecc.rawbuf = kmalloc(mtd->writesize + + mtd->oobsize, + GFP_KERNEL); + if (!micron->ecc.rawbuf) { + ret = -ENOMEM; + goto err_free_manuf_data; + } + } + + if (requirements->strength == 4) + mtd_set_ooblayout(mtd, + µn_nand_on_die_4_ooblayout_ops); + else + mtd_set_ooblayout(mtd, + µn_nand_on_die_8_ooblayout_ops); + + chip->ecc.bytes = requirements->strength * 2; + chip->ecc.size = 512; + chip->ecc.strength = requirements->strength; + chip->ecc.algo = NAND_ECC_ALGO_BCH; + chip->ecc.read_page = micron_nand_read_page_on_die_ecc; + chip->ecc.write_page = micron_nand_write_page_on_die_ecc; + + if (ondie == MICRON_ON_DIE_MANDATORY) { + chip->ecc.read_page_raw = nand_read_page_raw_notsupp; + chip->ecc.write_page_raw = nand_write_page_raw_notsupp; + } else { + if (!chip->ecc.read_page_raw) + chip->ecc.read_page_raw = nand_read_page_raw; + if (!chip->ecc.write_page_raw) + chip->ecc.write_page_raw = nand_write_page_raw; + } + } + + return 0; + +err_free_manuf_data: + kfree(micron->ecc.rawbuf); + kfree(micron); + + return ret; +} + +static void micron_nand_cleanup(struct nand_chip *chip) +{ + struct micron_nand *micron = nand_get_manufacturer_data(chip); + + kfree(micron->ecc.rawbuf); + kfree(micron); +} + +static void micron_fixup_onfi_param_page(struct nand_chip *chip, + struct nand_onfi_params *p) +{ + /* + * MT29F1G08ABAFAWP-ITE:F and possibly others report 00 00 for the + * revision number field of the ONFI parameter page. Assume ONFI + * version 1.0 if the revision number is 00 00. + */ + if (le16_to_cpu(p->revision) == 0) + p->revision = cpu_to_le16(ONFI_VERSION_1_0); +} + +const struct nand_manufacturer_ops micron_nand_manuf_ops = { + .init = micron_nand_init, + .cleanup = micron_nand_cleanup, + .fixup_onfi_param_page = micron_fixup_onfi_param_page, +}; diff --git a/drivers/mtd/nand/raw/nand_onfi.c b/drivers/mtd/nand/raw/nand_onfi.c new file mode 100644 index 000000000..b5e304c35 --- /dev/null +++ b/drivers/mtd/nand/raw/nand_onfi.c @@ -0,0 +1,362 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2000 Steven J. Hill (sjhill@realitydiluted.com) + * 2002-2006 Thomas Gleixner (tglx@kernel.org) + * + * Credits: + * David Woodhouse for adding multichip support + * + * Aleph One Ltd. and Toby Churchill Ltd. for supporting the + * rework for 2K page size chips + * + * This file contains all ONFI helpers. + */ + +#include + +#include "internals.h" + +#define ONFI_PARAM_PAGES 3 + +u16 onfi_crc16(u16 crc, u8 const *p, size_t len) +{ + int i; + while (len--) { + crc ^= *p++ << 8; + for (i = 0; i < 8; i++) + crc = (crc << 1) ^ ((crc & 0x8000) ? 0x8005 : 0); + } + + return crc; +} + +/* Parse the Extended Parameter Page. */ +static int nand_flash_detect_ext_param_page(struct nand_chip *chip, + struct nand_onfi_params *p) +{ + struct nand_device *base = &chip->base; + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_ecc_props requirements; + struct onfi_ext_param_page *ep; + struct onfi_ext_section *s; + struct onfi_ext_ecc_info *ecc; + size_t remaining, section_len; + uint8_t *cursor; + int ret; + int len; + int i; + + len = le16_to_cpu(p->ext_param_page_length) * 16; + if (len < sizeof(*ep)) + return -EINVAL; + + ep = kmalloc(len, GFP_KERNEL); + if (!ep) + return -ENOMEM; + + /* + * Use the Change Read Column command to skip the ONFI param pages and + * ensure we read at the right location. + */ + ret = nand_change_read_column_op(chip, + sizeof(*p) * p->num_of_param_pages, + ep, len, true); + if (ret) + goto ext_out; + + ret = -EINVAL; + if ((onfi_crc16(ONFI_CRC_BASE, ((uint8_t *)ep) + 2, len - 2) + != le16_to_cpu(ep->crc))) { + pr_debug("fail in the CRC.\n"); + goto ext_out; + } + + /* + * Check the signature. + * Do not strictly follow the ONFI spec, maybe changed in future. + */ + if (strncmp(ep->sig, "EPPS", 4)) { + pr_debug("The signature is invalid.\n"); + goto ext_out; + } + + /* find the ECC section. */ + cursor = (uint8_t *)(ep + 1); + remaining = len - sizeof(*ep); + for (i = 0; i < ONFI_EXT_SECTION_MAX; i++) { + s = ep->sections + i; + section_len = s->length * 16; + if (section_len > remaining) { + dev_dbg(&mtd->dev, + "ONFI extended parameter section %d exceeds page\n", + i); + goto ext_out; + } + + if (s->type == ONFI_SECTION_TYPE_2) { + if (section_len < sizeof(*ecc)) { + dev_dbg(&mtd->dev, + "ONFI extended parameter ECC section %d is too short\n", + i); + goto ext_out; + } + break; + } + + cursor += section_len; + remaining -= section_len; + } + if (i == ONFI_EXT_SECTION_MAX) { + pr_debug("We can not find the ECC section.\n"); + goto ext_out; + } + + /* get the info we want. */ + ecc = (struct onfi_ext_ecc_info *)cursor; + + if (!ecc->codeword_size) { + pr_debug("Invalid codeword size\n"); + goto ext_out; + } + + requirements.strength = ecc->ecc_bits; + requirements.step_size = 1 << ecc->codeword_size; + nanddev_set_ecc_requirements(base, &requirements); + + ret = 0; + +ext_out: + kfree(ep); + return ret; +} + +/* + * Recover data with bit-wise majority + */ +static void nand_bit_wise_majority(const void **srcbufs, + unsigned int nsrcbufs, + void *dstbuf, + unsigned int bufsize) +{ + int i, j, k; + + for (i = 0; i < bufsize; i++) { + u8 val = 0; + + for (j = 0; j < 8; j++) { + unsigned int cnt = 0; + + for (k = 0; k < nsrcbufs; k++) { + const u8 *srcbuf = srcbufs[k]; + + if (srcbuf[i] & BIT(j)) + cnt++; + } + + if (cnt > nsrcbufs / 2) + val |= BIT(j); + } + + ((u8 *)dstbuf)[i] = val; + } +} + +/* + * Check if the NAND chip is ONFI compliant, returns 1 if it is, 0 otherwise. + */ +int nand_onfi_detect(struct nand_chip *chip) +{ + struct nand_device *base = &chip->base; + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_memory_organization *memorg; + struct nand_onfi_params *p = NULL, *pbuf; + struct onfi_params *onfi; + bool use_datain = false; + int onfi_version = 0; + char id[4]; + int i, ret, val; + u16 crc; + + memorg = nanddev_get_memorg(&chip->base); + + /* Try ONFI for unknown chip or LP */ + ret = nand_readid_op(chip, 0x20, id, sizeof(id)); + if (ret || strncmp(id, "ONFI", 4)) + return 0; + + /* ONFI chip: allocate a buffer to hold its parameter page */ + pbuf = kzalloc((sizeof(*pbuf) * ONFI_PARAM_PAGES), GFP_KERNEL); + if (!pbuf) + return -ENOMEM; + + if (!nand_has_exec_op(chip) || chip->controller->supported_op.data_only_read) + use_datain = true; + + for (i = 0; i < ONFI_PARAM_PAGES; i++) { + if (!i) + ret = nand_read_param_page_op(chip, 0, &pbuf[i], + sizeof(*pbuf)); + else if (use_datain) + ret = nand_read_data_op(chip, &pbuf[i], sizeof(*pbuf), + true, false); + else + ret = nand_change_read_column_op(chip, sizeof(*pbuf) * i, + &pbuf[i], sizeof(*pbuf), + true); + if (ret) { + ret = 0; + goto free_onfi_param_page; + } + + crc = onfi_crc16(ONFI_CRC_BASE, (u8 *)&pbuf[i], 254); + if (crc == le16_to_cpu(pbuf[i].crc)) { + p = &pbuf[i]; + break; + } + } + + if (i == ONFI_PARAM_PAGES) { + const void *srcbufs[ONFI_PARAM_PAGES]; + unsigned int j; + + for (j = 0; j < ONFI_PARAM_PAGES; j++) + srcbufs[j] = pbuf + j; + + pr_warn("Could not find a valid ONFI parameter page, trying bit-wise majority to recover it\n"); + nand_bit_wise_majority(srcbufs, ONFI_PARAM_PAGES, pbuf, + sizeof(*pbuf)); + + crc = onfi_crc16(ONFI_CRC_BASE, (u8 *)pbuf, 254); + if (crc != le16_to_cpu(pbuf->crc)) { + pr_err("ONFI parameter recovery failed, aborting\n"); + goto free_onfi_param_page; + } + p = pbuf; + } + + if (chip->manufacturer.desc && chip->manufacturer.desc->ops && + chip->manufacturer.desc->ops->fixup_onfi_param_page) + chip->manufacturer.desc->ops->fixup_onfi_param_page(chip, p); + + /* Check version */ + val = le16_to_cpu(p->revision); + if (val & ONFI_VERSION_2_3) + onfi_version = 23; + else if (val & ONFI_VERSION_2_2) + onfi_version = 22; + else if (val & ONFI_VERSION_2_1) + onfi_version = 21; + else if (val & ONFI_VERSION_2_0) + onfi_version = 20; + else if (val & ONFI_VERSION_1_0) + onfi_version = 10; + + if (!onfi_version) { + pr_info("unsupported ONFI version: %d\n", val); + goto free_onfi_param_page; + } + + sanitize_string(p->manufacturer, sizeof(p->manufacturer)); + sanitize_string(p->model, sizeof(p->model)); + chip->parameters.model = kstrdup(p->model, GFP_KERNEL); + if (!chip->parameters.model) { + ret = -ENOMEM; + goto free_onfi_param_page; + } + + memorg->pagesize = le32_to_cpu(p->byte_per_page); + mtd->writesize = memorg->pagesize; + + /* + * pages_per_block and blocks_per_lun may not be a power-of-2 size + * (don't ask me who thought of this...). MTD assumes that these + * dimensions will be power-of-2, so just truncate the remaining area. + */ + memorg->pages_per_eraseblock = + 1 << (fls(le32_to_cpu(p->pages_per_block)) - 1); + mtd->erasesize = memorg->pages_per_eraseblock * memorg->pagesize; + + memorg->oobsize = le16_to_cpu(p->spare_bytes_per_page); + mtd->oobsize = memorg->oobsize; + + memorg->luns_per_target = p->lun_count; + memorg->planes_per_lun = 1 << p->interleaved_bits; + + /* See erasesize comment */ + memorg->eraseblocks_per_lun = + 1 << (fls(le32_to_cpu(p->blocks_per_lun)) - 1); + memorg->max_bad_eraseblocks_per_lun = le32_to_cpu(p->blocks_per_lun); + memorg->bits_per_cell = p->bits_per_cell; + + if (le16_to_cpu(p->features) & ONFI_FEATURE_16_BIT_BUS) + chip->options |= NAND_BUSWIDTH_16; + + if (p->ecc_bits != 0xff) { + struct nand_ecc_props requirements = { + .strength = p->ecc_bits, + .step_size = 512, + }; + + nanddev_set_ecc_requirements(base, &requirements); + } else if (onfi_version >= 21 && + (le16_to_cpu(p->features) & ONFI_FEATURE_EXT_PARAM_PAGE)) { + + /* + * The nand_flash_detect_ext_param_page() uses the + * Change Read Column command which maybe not supported + * by the chip->legacy.cmdfunc. So try to update the + * chip->legacy.cmdfunc now. We do not replace user supplied + * command function. + */ + nand_legacy_adjust_cmdfunc(chip); + + /* The Extended Parameter Page is supported since ONFI 2.1. */ + if (nand_flash_detect_ext_param_page(chip, p)) + pr_warn("Failed to detect ONFI extended param page\n"); + } else { + pr_warn("Could not retrieve ONFI ECC requirements\n"); + } + + /* Save some parameters from the parameter page for future use */ + if (le16_to_cpu(p->opt_cmd) & ONFI_OPT_CMD_SET_GET_FEATURES) { + chip->parameters.supports_set_get_features = true; + bitmap_set(chip->parameters.get_feature_list, + ONFI_FEATURE_ADDR_TIMING_MODE, 1); + bitmap_set(chip->parameters.set_feature_list, + ONFI_FEATURE_ADDR_TIMING_MODE, 1); + } + + if (le16_to_cpu(p->opt_cmd) & ONFI_OPT_CMD_READ_CACHE) + chip->parameters.supports_read_cache = true; + + onfi = kzalloc_obj(*onfi); + if (!onfi) { + ret = -ENOMEM; + goto free_model; + } + + onfi->version = onfi_version; + onfi->tPROG = le16_to_cpu(p->t_prog); + onfi->tBERS = le16_to_cpu(p->t_bers); + onfi->tR = le16_to_cpu(p->t_r); + onfi->tCCS = le16_to_cpu(p->t_ccs); + onfi->fast_tCAD = le16_to_cpu(p->nvddr_nvddr2_features) & BIT(0); + onfi->sdr_timing_modes = le16_to_cpu(p->sdr_timing_modes); + if (le16_to_cpu(p->features) & ONFI_FEATURE_NV_DDR) + onfi->nvddr_timing_modes = le16_to_cpu(p->nvddr_timing_modes); + onfi->vendor_revision = le16_to_cpu(p->vendor_revision); + memcpy(onfi->vendor, p->vendor, sizeof(p->vendor)); + chip->parameters.onfi = onfi; + + /* Identification done, free the full ONFI parameter page and exit */ + kfree(pbuf); + + return 1; + +free_model: + kfree(chip->parameters.model); +free_onfi_param_page: + kfree(pbuf); + + return ret; +} diff --git a/drivers/mtd/nand/raw/nand_samsung.c b/drivers/mtd/nand/raw/nand_samsung.c new file mode 100644 index 000000000..0be6b7563 --- /dev/null +++ b/drivers/mtd/nand/raw/nand_samsung.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2017 Free Electrons + * Copyright (C) 2017 NextThing Co + * + * Author: Boris Brezillon + */ + +#include "internals.h" + +static void samsung_nand_decode_id(struct nand_chip *chip) +{ + struct nand_device *base = &chip->base; + struct nand_ecc_props requirements = {}; + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_memory_organization *memorg; + + memorg = nanddev_get_memorg(&chip->base); + + /* New Samsung (6 byte ID): Samsung K9GAG08U0F (p.44) */ + if (chip->id.len == 6 && !nand_is_slc(chip) && + chip->id.data[5] != 0x00) { + u8 extid = chip->id.data[3]; + + /* Get pagesize */ + memorg->pagesize = 2048 << (extid & 0x03); + mtd->writesize = memorg->pagesize; + + extid >>= 2; + + /* Get oobsize */ + switch (((extid >> 2) & 0x4) | (extid & 0x3)) { + case 1: + memorg->oobsize = 128; + break; + case 2: + memorg->oobsize = 218; + break; + case 3: + memorg->oobsize = 400; + break; + case 4: + memorg->oobsize = 436; + break; + case 5: + memorg->oobsize = 512; + break; + case 6: + memorg->oobsize = 640; + break; + default: + /* + * We should never reach this case, but if that + * happens, this probably means Samsung decided to use + * a different extended ID format, and we should find + * a way to support it. + */ + WARN(1, "Invalid OOB size value"); + break; + } + + mtd->oobsize = memorg->oobsize; + + /* Get blocksize */ + extid >>= 2; + memorg->pages_per_eraseblock = (128 * 1024) << + (((extid >> 1) & 0x04) | + (extid & 0x03)) / + memorg->pagesize; + mtd->erasesize = (128 * 1024) << + (((extid >> 1) & 0x04) | (extid & 0x03)); + + /* Extract ECC requirements from 5th id byte*/ + extid = (chip->id.data[4] >> 4) & 0x07; + if (extid < 5) { + requirements.step_size = 512; + requirements.strength = 1 << extid; + } else { + requirements.step_size = 1024; + switch (extid) { + case 5: + requirements.strength = 24; + break; + case 6: + requirements.strength = 40; + break; + case 7: + requirements.strength = 60; + break; + default: + WARN(1, "Could not decode ECC info"); + requirements.step_size = 0; + } + } + } else { + nand_decode_ext_id(chip); + + if (nand_is_slc(chip)) { + switch (chip->id.data[1]) { + /* K9F4G08U0D-S[I|C]B0(T00) */ + case 0xDC: + requirements.step_size = 512; + requirements.strength = 1; + break; + + /* K9F1G08U0E 21nm chips do not support subpage write */ + case 0xF1: + if (chip->id.len > 4 && + (chip->id.data[4] & GENMASK(1, 0)) == 0x1) + chip->options |= NAND_NO_SUBPAGE_WRITE; + break; + default: + break; + } + } + } + + nanddev_set_ecc_requirements(base, &requirements); +} + +static int samsung_nand_init(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (mtd->writesize > 512) + chip->options |= NAND_SAMSUNG_LP_OPTIONS; + + if (!nand_is_slc(chip)) + chip->options |= NAND_BBM_LASTPAGE; + else + chip->options |= NAND_BBM_FIRSTPAGE | NAND_BBM_SECONDPAGE; + + return 0; +} + +const struct nand_manufacturer_ops samsung_nand_manuf_ops = { + .detect = samsung_nand_decode_id, + .init = samsung_nand_init, +}; diff --git a/drivers/mtd/nand/raw/nand_sandisk.c b/drivers/mtd/nand/raw/nand_sandisk.c new file mode 100644 index 000000000..7c66e4187 --- /dev/null +++ b/drivers/mtd/nand/raw/nand_sandisk.c @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "internals.h" + +static int +sdtnqgama_choose_interface_config(struct nand_chip *chip, + struct nand_interface_config *iface) +{ + onfi_fill_interface_config(chip, iface, NAND_SDR_IFACE, 0); + + return nand_choose_best_sdr_timings(chip, iface, NULL); +} + +static int sandisk_nand_init(struct nand_chip *chip) +{ + if (!strncmp("SDTNQGAMA", chip->parameters.model, + sizeof("SDTNQGAMA") - 1)) + chip->ops.choose_interface_config = + &sdtnqgama_choose_interface_config; + + return 0; +} + +const struct nand_manufacturer_ops sandisk_nand_manuf_ops = { + .init = sandisk_nand_init, +}; diff --git a/drivers/mtd/nand/raw/nand_timings.c b/drivers/mtd/nand/raw/nand_timings.c new file mode 100644 index 000000000..7b41afc37 --- /dev/null +++ b/drivers/mtd/nand/raw/nand_timings.c @@ -0,0 +1,737 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014 Free Electrons + * + * Author: Boris BREZILLON + */ +#include +#include +#include + +#include "internals.h" + +#define ONFI_DYN_TIMING_MAX U16_MAX + +/* + * For non-ONFI chips we use the highest possible value for tPROG and tBERS. + * tR and tCCS will take the default values precised in the ONFI specification + * for timing mode 0, respectively 200us and 500ns. + * + * These four values are tweaked to be more accurate in the case of ONFI chips. + */ +static const struct nand_interface_config onfi_sdr_timings[] = { + /* Mode 0 */ + { + .type = NAND_SDR_IFACE, + .timings.mode = 0, + .timings.sdr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tADL_min = 400000, + .tALH_min = 20000, + .tALS_min = 50000, + .tAR_min = 25000, + .tCEA_max = 100000, + .tCEH_min = 20000, + .tCH_min = 20000, + .tCHZ_max = 100000, + .tCLH_min = 20000, + .tCLR_min = 20000, + .tCLS_min = 50000, + .tCOH_min = 0, + .tCS_min = 70000, + .tDH_min = 20000, + .tDS_min = 40000, + .tFEAT_max = 1000000, + .tIR_min = 10000, + .tITC_max = 1000000, + .tRC_min = 100000, + .tREA_max = 40000, + .tREH_min = 30000, + .tRHOH_min = 0, + .tRHW_min = 200000, + .tRHZ_max = 200000, + .tRLOH_min = 0, + .tRP_min = 50000, + .tRR_min = 40000, + .tRST_max = 250000000000ULL, + .tWB_max = 200000, + .tWC_min = 100000, + .tWH_min = 30000, + .tWHR_min = 120000, + .tWP_min = 50000, + .tWW_min = 100000, + }, + }, + /* Mode 1 */ + { + .type = NAND_SDR_IFACE, + .timings.mode = 1, + .timings.sdr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tADL_min = 400000, + .tALH_min = 10000, + .tALS_min = 25000, + .tAR_min = 10000, + .tCEA_max = 45000, + .tCEH_min = 20000, + .tCH_min = 10000, + .tCHZ_max = 50000, + .tCLH_min = 10000, + .tCLR_min = 10000, + .tCLS_min = 25000, + .tCOH_min = 15000, + .tCS_min = 35000, + .tDH_min = 10000, + .tDS_min = 20000, + .tFEAT_max = 1000000, + .tIR_min = 0, + .tITC_max = 1000000, + .tRC_min = 50000, + .tREA_max = 30000, + .tREH_min = 15000, + .tRHOH_min = 15000, + .tRHW_min = 100000, + .tRHZ_max = 100000, + .tRLOH_min = 0, + .tRP_min = 25000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWC_min = 45000, + .tWH_min = 15000, + .tWHR_min = 80000, + .tWP_min = 25000, + .tWW_min = 100000, + }, + }, + /* Mode 2 */ + { + .type = NAND_SDR_IFACE, + .timings.mode = 2, + .timings.sdr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tADL_min = 400000, + .tALH_min = 10000, + .tALS_min = 15000, + .tAR_min = 10000, + .tCEA_max = 30000, + .tCEH_min = 20000, + .tCH_min = 10000, + .tCHZ_max = 50000, + .tCLH_min = 10000, + .tCLR_min = 10000, + .tCLS_min = 15000, + .tCOH_min = 15000, + .tCS_min = 25000, + .tDH_min = 5000, + .tDS_min = 15000, + .tFEAT_max = 1000000, + .tIR_min = 0, + .tITC_max = 1000000, + .tRC_min = 35000, + .tREA_max = 25000, + .tREH_min = 15000, + .tRHOH_min = 15000, + .tRHW_min = 100000, + .tRHZ_max = 100000, + .tRLOH_min = 0, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tRP_min = 17000, + .tWC_min = 35000, + .tWH_min = 15000, + .tWHR_min = 80000, + .tWP_min = 17000, + .tWW_min = 100000, + }, + }, + /* Mode 3 */ + { + .type = NAND_SDR_IFACE, + .timings.mode = 3, + .timings.sdr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tADL_min = 400000, + .tALH_min = 5000, + .tALS_min = 10000, + .tAR_min = 10000, + .tCEA_max = 25000, + .tCEH_min = 20000, + .tCH_min = 5000, + .tCHZ_max = 50000, + .tCLH_min = 5000, + .tCLR_min = 10000, + .tCLS_min = 10000, + .tCOH_min = 15000, + .tCS_min = 25000, + .tDH_min = 5000, + .tDS_min = 10000, + .tFEAT_max = 1000000, + .tIR_min = 0, + .tITC_max = 1000000, + .tRC_min = 30000, + .tREA_max = 20000, + .tREH_min = 10000, + .tRHOH_min = 15000, + .tRHW_min = 100000, + .tRHZ_max = 100000, + .tRLOH_min = 0, + .tRP_min = 15000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWC_min = 30000, + .tWH_min = 10000, + .tWHR_min = 80000, + .tWP_min = 15000, + .tWW_min = 100000, + }, + }, + /* Mode 4 */ + { + .type = NAND_SDR_IFACE, + .timings.mode = 4, + .timings.sdr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tADL_min = 400000, + .tALH_min = 5000, + .tALS_min = 10000, + .tAR_min = 10000, + .tCEA_max = 25000, + .tCEH_min = 20000, + .tCH_min = 5000, + .tCHZ_max = 30000, + .tCLH_min = 5000, + .tCLR_min = 10000, + .tCLS_min = 10000, + .tCOH_min = 15000, + .tCS_min = 20000, + .tDH_min = 5000, + .tDS_min = 10000, + .tFEAT_max = 1000000, + .tIR_min = 0, + .tITC_max = 1000000, + .tRC_min = 25000, + .tREA_max = 20000, + .tREH_min = 10000, + .tRHOH_min = 15000, + .tRHW_min = 100000, + .tRHZ_max = 100000, + .tRLOH_min = 5000, + .tRP_min = 12000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWC_min = 25000, + .tWH_min = 10000, + .tWHR_min = 80000, + .tWP_min = 12000, + .tWW_min = 100000, + }, + }, + /* Mode 5 */ + { + .type = NAND_SDR_IFACE, + .timings.mode = 5, + .timings.sdr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tADL_min = 400000, + .tALH_min = 5000, + .tALS_min = 10000, + .tAR_min = 10000, + .tCEA_max = 25000, + .tCEH_min = 20000, + .tCH_min = 5000, + .tCHZ_max = 30000, + .tCLH_min = 5000, + .tCLR_min = 10000, + .tCLS_min = 10000, + .tCOH_min = 15000, + .tCS_min = 15000, + .tDH_min = 5000, + .tDS_min = 7000, + .tFEAT_max = 1000000, + .tIR_min = 0, + .tITC_max = 1000000, + .tRC_min = 20000, + .tREA_max = 16000, + .tREH_min = 7000, + .tRHOH_min = 15000, + .tRHW_min = 100000, + .tRHZ_max = 100000, + .tRLOH_min = 5000, + .tRP_min = 10000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWC_min = 20000, + .tWH_min = 7000, + .tWHR_min = 80000, + .tWP_min = 10000, + .tWW_min = 100000, + }, + }, +}; + +static const struct nand_interface_config onfi_nvddr_timings[] = { + /* Mode 0 */ + { + .type = NAND_NVDDR_IFACE, + .timings.mode = 0, + .timings.nvddr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tAC_min = 3000, + .tAC_max = 25000, + .tADL_min = 400000, + .tCAD_min = 45000, + .tCAH_min = 10000, + .tCALH_min = 10000, + .tCALS_min = 10000, + .tCAS_min = 10000, + .tCEH_min = 20000, + .tCH_min = 10000, + .tCK_min = 50000, + .tCS_min = 35000, + .tDH_min = 5000, + .tDQSCK_min = 3000, + .tDQSCK_max = 25000, + .tDQSD_min = 0, + .tDQSD_max = 18000, + .tDQSHZ_max = 20000, + .tDQSQ_max = 5000, + .tDS_min = 5000, + .tDSC_min = 50000, + .tFEAT_max = 1000000, + .tITC_max = 1000000, + .tQHS_max = 6000, + .tRHW_min = 100000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWHR_min = 80000, + .tWRCK_min = 20000, + .tWW_min = 100000, + }, + }, + /* Mode 1 */ + { + .type = NAND_NVDDR_IFACE, + .timings.mode = 1, + .timings.nvddr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tAC_min = 3000, + .tAC_max = 25000, + .tADL_min = 400000, + .tCAD_min = 45000, + .tCAH_min = 5000, + .tCALH_min = 5000, + .tCALS_min = 5000, + .tCAS_min = 5000, + .tCEH_min = 20000, + .tCH_min = 5000, + .tCK_min = 30000, + .tCS_min = 25000, + .tDH_min = 2500, + .tDQSCK_min = 3000, + .tDQSCK_max = 25000, + .tDQSD_min = 0, + .tDQSD_max = 18000, + .tDQSHZ_max = 20000, + .tDQSQ_max = 2500, + .tDS_min = 3000, + .tDSC_min = 30000, + .tFEAT_max = 1000000, + .tITC_max = 1000000, + .tQHS_max = 3000, + .tRHW_min = 100000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWHR_min = 80000, + .tWRCK_min = 20000, + .tWW_min = 100000, + }, + }, + /* Mode 2 */ + { + .type = NAND_NVDDR_IFACE, + .timings.mode = 2, + .timings.nvddr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tAC_min = 3000, + .tAC_max = 25000, + .tADL_min = 400000, + .tCAD_min = 45000, + .tCAH_min = 4000, + .tCALH_min = 4000, + .tCALS_min = 4000, + .tCAS_min = 4000, + .tCEH_min = 20000, + .tCH_min = 4000, + .tCK_min = 20000, + .tCS_min = 15000, + .tDH_min = 1700, + .tDQSCK_min = 3000, + .tDQSCK_max = 25000, + .tDQSD_min = 0, + .tDQSD_max = 18000, + .tDQSHZ_max = 20000, + .tDQSQ_max = 1700, + .tDS_min = 2000, + .tDSC_min = 20000, + .tFEAT_max = 1000000, + .tITC_max = 1000000, + .tQHS_max = 2000, + .tRHW_min = 100000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWHR_min = 80000, + .tWRCK_min = 20000, + .tWW_min = 100000, + }, + }, + /* Mode 3 */ + { + .type = NAND_NVDDR_IFACE, + .timings.mode = 3, + .timings.nvddr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tAC_min = 3000, + .tAC_max = 25000, + .tADL_min = 400000, + .tCAD_min = 45000, + .tCAH_min = 3000, + .tCALH_min = 3000, + .tCALS_min = 3000, + .tCAS_min = 3000, + .tCEH_min = 20000, + .tCH_min = 3000, + .tCK_min = 15000, + .tCS_min = 15000, + .tDH_min = 1300, + .tDQSCK_min = 3000, + .tDQSCK_max = 25000, + .tDQSD_min = 0, + .tDQSD_max = 18000, + .tDQSHZ_max = 20000, + .tDQSQ_max = 1300, + .tDS_min = 1500, + .tDSC_min = 15000, + .tFEAT_max = 1000000, + .tITC_max = 1000000, + .tQHS_max = 1500, + .tRHW_min = 100000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWHR_min = 80000, + .tWRCK_min = 20000, + .tWW_min = 100000, + }, + }, + /* Mode 4 */ + { + .type = NAND_NVDDR_IFACE, + .timings.mode = 4, + .timings.nvddr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tAC_min = 3000, + .tAC_max = 25000, + .tADL_min = 400000, + .tCAD_min = 45000, + .tCAH_min = 2500, + .tCALH_min = 2500, + .tCALS_min = 2500, + .tCAS_min = 2500, + .tCEH_min = 20000, + .tCH_min = 2500, + .tCK_min = 12000, + .tCS_min = 15000, + .tDH_min = 1100, + .tDQSCK_min = 3000, + .tDQSCK_max = 25000, + .tDQSD_min = 0, + .tDQSD_max = 18000, + .tDQSHZ_max = 20000, + .tDQSQ_max = 1000, + .tDS_min = 1100, + .tDSC_min = 12000, + .tFEAT_max = 1000000, + .tITC_max = 1000000, + .tQHS_max = 1200, + .tRHW_min = 100000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWHR_min = 80000, + .tWRCK_min = 20000, + .tWW_min = 100000, + }, + }, + /* Mode 5 */ + { + .type = NAND_NVDDR_IFACE, + .timings.mode = 5, + .timings.nvddr = { + .tCCS_min = 500000, + .tR_max = 200000000, + .tPROG_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tBERS_max = 1000000ULL * ONFI_DYN_TIMING_MAX, + .tAC_min = 3000, + .tAC_max = 25000, + .tADL_min = 400000, + .tCAD_min = 45000, + .tCAH_min = 2000, + .tCALH_min = 2000, + .tCALS_min = 2000, + .tCAS_min = 2000, + .tCEH_min = 20000, + .tCH_min = 2000, + .tCK_min = 10000, + .tCS_min = 15000, + .tDH_min = 900, + .tDQSCK_min = 3000, + .tDQSCK_max = 25000, + .tDQSD_min = 0, + .tDQSD_max = 18000, + .tDQSHZ_max = 20000, + .tDQSQ_max = 850, + .tDS_min = 900, + .tDSC_min = 10000, + .tFEAT_max = 1000000, + .tITC_max = 1000000, + .tQHS_max = 1000, + .tRHW_min = 100000, + .tRR_min = 20000, + .tRST_max = 500000000, + .tWB_max = 100000, + .tWHR_min = 80000, + .tWRCK_min = 20000, + .tWW_min = 100000, + }, + }, +}; + +/* All NAND chips share the same reset data interface: SDR mode 0 */ +const struct nand_interface_config *nand_get_reset_interface_config(void) +{ + return &onfi_sdr_timings[0]; +} + +/** + * onfi_find_closest_sdr_mode - Derive the closest ONFI SDR timing mode given a + * set of timings + * @spec_timings: the timings to challenge + */ +unsigned int +onfi_find_closest_sdr_mode(const struct nand_sdr_timings *spec_timings) +{ + const struct nand_sdr_timings *onfi_timings; + int mode; + + for (mode = ARRAY_SIZE(onfi_sdr_timings) - 1; mode > 0; mode--) { + onfi_timings = &onfi_sdr_timings[mode].timings.sdr; + + if (spec_timings->tCCS_min <= onfi_timings->tCCS_min && + spec_timings->tADL_min <= onfi_timings->tADL_min && + spec_timings->tALH_min <= onfi_timings->tALH_min && + spec_timings->tALS_min <= onfi_timings->tALS_min && + spec_timings->tAR_min <= onfi_timings->tAR_min && + spec_timings->tCEH_min <= onfi_timings->tCEH_min && + spec_timings->tCH_min <= onfi_timings->tCH_min && + spec_timings->tCLH_min <= onfi_timings->tCLH_min && + spec_timings->tCLR_min <= onfi_timings->tCLR_min && + spec_timings->tCLS_min <= onfi_timings->tCLS_min && + spec_timings->tCOH_min <= onfi_timings->tCOH_min && + spec_timings->tCS_min <= onfi_timings->tCS_min && + spec_timings->tDH_min <= onfi_timings->tDH_min && + spec_timings->tDS_min <= onfi_timings->tDS_min && + spec_timings->tIR_min <= onfi_timings->tIR_min && + spec_timings->tRC_min <= onfi_timings->tRC_min && + spec_timings->tREH_min <= onfi_timings->tREH_min && + spec_timings->tRHOH_min <= onfi_timings->tRHOH_min && + spec_timings->tRHW_min <= onfi_timings->tRHW_min && + spec_timings->tRLOH_min <= onfi_timings->tRLOH_min && + spec_timings->tRP_min <= onfi_timings->tRP_min && + spec_timings->tRR_min <= onfi_timings->tRR_min && + spec_timings->tWC_min <= onfi_timings->tWC_min && + spec_timings->tWH_min <= onfi_timings->tWH_min && + spec_timings->tWHR_min <= onfi_timings->tWHR_min && + spec_timings->tWP_min <= onfi_timings->tWP_min && + spec_timings->tWW_min <= onfi_timings->tWW_min) + return mode; + } + + return 0; +} + +/** + * onfi_find_closest_nvddr_mode - Derive the closest ONFI NVDDR timing mode + * given a set of timings + * @spec_timings: the timings to challenge + */ +unsigned int +onfi_find_closest_nvddr_mode(const struct nand_nvddr_timings *spec_timings) +{ + const struct nand_nvddr_timings *onfi_timings; + int mode; + + for (mode = ARRAY_SIZE(onfi_nvddr_timings) - 1; mode > 0; mode--) { + onfi_timings = &onfi_nvddr_timings[mode].timings.nvddr; + + if (spec_timings->tCCS_min <= onfi_timings->tCCS_min && + spec_timings->tAC_min <= onfi_timings->tAC_min && + spec_timings->tADL_min <= onfi_timings->tADL_min && + spec_timings->tCAD_min <= onfi_timings->tCAD_min && + spec_timings->tCAH_min <= onfi_timings->tCAH_min && + spec_timings->tCALH_min <= onfi_timings->tCALH_min && + spec_timings->tCALS_min <= onfi_timings->tCALS_min && + spec_timings->tCAS_min <= onfi_timings->tCAS_min && + spec_timings->tCEH_min <= onfi_timings->tCEH_min && + spec_timings->tCH_min <= onfi_timings->tCH_min && + spec_timings->tCK_min <= onfi_timings->tCK_min && + spec_timings->tCS_min <= onfi_timings->tCS_min && + spec_timings->tDH_min <= onfi_timings->tDH_min && + spec_timings->tDQSCK_min <= onfi_timings->tDQSCK_min && + spec_timings->tDQSD_min <= onfi_timings->tDQSD_min && + spec_timings->tDS_min <= onfi_timings->tDS_min && + spec_timings->tDSC_min <= onfi_timings->tDSC_min && + spec_timings->tRHW_min <= onfi_timings->tRHW_min && + spec_timings->tRR_min <= onfi_timings->tRR_min && + spec_timings->tWHR_min <= onfi_timings->tWHR_min && + spec_timings->tWRCK_min <= onfi_timings->tWRCK_min && + spec_timings->tWW_min <= onfi_timings->tWW_min) + return mode; + } + + return 0; +} + +/* + * onfi_fill_sdr_interface_config - Initialize a SDR interface config from a + * given ONFI mode + * @chip: The NAND chip + * @iface: The interface configuration to fill + * @timing_mode: The ONFI timing mode + */ +static void onfi_fill_sdr_interface_config(struct nand_chip *chip, + struct nand_interface_config *iface, + unsigned int timing_mode) +{ + struct onfi_params *onfi = chip->parameters.onfi; + + if (WARN_ON(timing_mode >= ARRAY_SIZE(onfi_sdr_timings))) + return; + + *iface = onfi_sdr_timings[timing_mode]; + + /* + * Initialize timings that cannot be deduced from timing mode: + * tPROG, tBERS, tR and tCCS. + * These information are part of the ONFI parameter page. + */ + if (onfi) { + struct nand_sdr_timings *timings = &iface->timings.sdr; + + /* microseconds -> picoseconds */ + timings->tPROG_max = 1000000ULL * onfi->tPROG; + timings->tBERS_max = 1000000ULL * onfi->tBERS; + timings->tR_max = 1000000ULL * onfi->tR; + + /* nanoseconds -> picoseconds */ + timings->tCCS_min = 1000UL * onfi->tCCS; + } +} + +/** + * onfi_fill_nvddr_interface_config - Initialize a NVDDR interface config from a + * given ONFI mode + * @chip: The NAND chip + * @iface: The interface configuration to fill + * @timing_mode: The ONFI timing mode + */ +static void onfi_fill_nvddr_interface_config(struct nand_chip *chip, + struct nand_interface_config *iface, + unsigned int timing_mode) +{ + struct onfi_params *onfi = chip->parameters.onfi; + + if (WARN_ON(timing_mode >= ARRAY_SIZE(onfi_nvddr_timings))) + return; + + *iface = onfi_nvddr_timings[timing_mode]; + + /* + * Initialize timings that cannot be deduced from timing mode: + * tPROG, tBERS, tR, tCCS and tCAD. + * These information are part of the ONFI parameter page. + */ + if (onfi) { + struct nand_nvddr_timings *timings = &iface->timings.nvddr; + + /* microseconds -> picoseconds */ + timings->tPROG_max = 1000000ULL * onfi->tPROG; + timings->tBERS_max = 1000000ULL * onfi->tBERS; + timings->tR_max = 1000000ULL * onfi->tR; + + /* nanoseconds -> picoseconds */ + timings->tCCS_min = 1000UL * onfi->tCCS; + + if (onfi->fast_tCAD) + timings->tCAD_min = 25000; + } +} + +/** + * onfi_fill_interface_config - Initialize an interface config from a given + * ONFI mode + * @chip: The NAND chip + * @iface: The interface configuration to fill + * @type: The interface type + * @timing_mode: The ONFI timing mode + */ +void onfi_fill_interface_config(struct nand_chip *chip, + struct nand_interface_config *iface, + enum nand_interface_type type, + unsigned int timing_mode) +{ + if (type == NAND_SDR_IFACE) + return onfi_fill_sdr_interface_config(chip, iface, timing_mode); + else + return onfi_fill_nvddr_interface_config(chip, iface, timing_mode); +} diff --git a/drivers/mtd/nand/raw/nand_toshiba.c b/drivers/mtd/nand/raw/nand_toshiba.c new file mode 100644 index 000000000..32062b26a --- /dev/null +++ b/drivers/mtd/nand/raw/nand_toshiba.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2017 Free Electrons + * Copyright (C) 2017 NextThing Co + * + * Author: Boris Brezillon + */ + +#include "internals.h" + +/* Bit for detecting BENAND */ +#define TOSHIBA_NAND_ID4_IS_BENAND BIT(7) + +/* Recommended to rewrite for BENAND */ +#define TOSHIBA_NAND_STATUS_REWRITE_RECOMMENDED BIT(3) + +/* ECC Status Read Command for BENAND */ +#define TOSHIBA_NAND_CMD_ECC_STATUS_READ 0x7A + +/* ECC Status Mask for BENAND */ +#define TOSHIBA_NAND_ECC_STATUS_MASK 0x0F + +/* Uncorrectable Error for BENAND */ +#define TOSHIBA_NAND_ECC_STATUS_UNCORR 0x0F + +/* Max ECC Steps for BENAND */ +#define TOSHIBA_NAND_MAX_ECC_STEPS 8 + +static int toshiba_nand_benand_read_eccstatus_op(struct nand_chip *chip, + u8 *buf) +{ + u8 *ecc_status = buf; + + if (nand_has_exec_op(chip)) { + const struct nand_sdr_timings *sdr = + nand_get_sdr_timings(nand_get_interface_config(chip)); + struct nand_op_instr instrs[] = { + NAND_OP_CMD(TOSHIBA_NAND_CMD_ECC_STATUS_READ, + PSEC_TO_NSEC(sdr->tADL_min)), + NAND_OP_8BIT_DATA_IN(chip->ecc.steps, ecc_status, 0), + }; + struct nand_operation op = NAND_OPERATION(chip->cur_cs, instrs); + + return nand_exec_op(chip, &op); + } + + return -ENOTSUPP; +} + +static int toshiba_nand_benand_eccstatus(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + unsigned int max_bitflips = 0; + u8 status, ecc_status[TOSHIBA_NAND_MAX_ECC_STEPS]; + + /* Check Status */ + ret = toshiba_nand_benand_read_eccstatus_op(chip, ecc_status); + if (!ret) { + unsigned int i, bitflips = 0; + + for (i = 0; i < chip->ecc.steps; i++) { + bitflips = ecc_status[i] & TOSHIBA_NAND_ECC_STATUS_MASK; + if (bitflips == TOSHIBA_NAND_ECC_STATUS_UNCORR) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += bitflips; + max_bitflips = max(max_bitflips, bitflips); + } + } + + return max_bitflips; + } + + /* + * Fallback to regular status check if + * toshiba_nand_benand_read_eccstatus_op() failed. + */ + ret = nand_status_op(chip, &status); + if (ret) + return ret; + + if (status & NAND_STATUS_FAIL) { + /* uncorrected */ + mtd->ecc_stats.failed++; + } else if (status & TOSHIBA_NAND_STATUS_REWRITE_RECOMMENDED) { + /* corrected */ + max_bitflips = mtd->bitflip_threshold; + mtd->ecc_stats.corrected += max_bitflips; + } + + return max_bitflips; +} + +static int +toshiba_nand_read_page_benand(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + int ret; + + ret = nand_read_page_raw(chip, buf, oob_required, page); + if (ret) + return ret; + + return toshiba_nand_benand_eccstatus(chip); +} + +static int +toshiba_nand_read_subpage_benand(struct nand_chip *chip, uint32_t data_offs, + uint32_t readlen, uint8_t *bufpoi, int page) +{ + int ret; + + ret = nand_read_page_op(chip, page, data_offs, + bufpoi + data_offs, readlen); + if (ret) + return ret; + + return toshiba_nand_benand_eccstatus(chip); +} + +static void toshiba_nand_benand_init(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + /* + * On BENAND, the entire OOB region can be used by the MTD user. + * The calculated ECC bytes are stored into other isolated + * area which is not accessible to users. + * This is why chip->ecc.bytes = 0. + */ + chip->ecc.bytes = 0; + chip->ecc.size = 512; + chip->ecc.strength = 8; + chip->ecc.read_page = toshiba_nand_read_page_benand; + chip->ecc.read_subpage = toshiba_nand_read_subpage_benand; + chip->ecc.write_page = nand_write_page_raw; + chip->ecc.read_page_raw = nand_read_page_raw_notsupp; + chip->ecc.write_page_raw = nand_write_page_raw_notsupp; + + chip->options |= NAND_SUBPAGE_READ; + + mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout()); +} + +static void toshiba_nand_decode_id(struct nand_chip *chip) +{ + struct nand_device *base = &chip->base; + struct nand_ecc_props requirements = {}; + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_memory_organization *memorg; + + memorg = nanddev_get_memorg(&chip->base); + + nand_decode_ext_id(chip); + + /* + * Toshiba 24nm raw SLC (i.e., not BENAND) have 32B OOB per + * 512B page. For Toshiba SLC, we decode the 5th/6th byte as + * follows: + * - ID byte 6, bits[2:0]: 100b -> 43nm, 101b -> 32nm, + * 110b -> 24nm + * - ID byte 5, bit[7]: 1 -> BENAND, 0 -> raw SLC + */ + if (chip->id.len >= 6 && nand_is_slc(chip) && + (chip->id.data[5] & 0x7) == 0x6 /* 24nm */ && + !(chip->id.data[4] & TOSHIBA_NAND_ID4_IS_BENAND) /* !BENAND */) { + memorg->oobsize = 32 * memorg->pagesize >> 9; + mtd->oobsize = memorg->oobsize; + } + + /* + * Extract ECC requirements from 6th id byte. + * For Toshiba SLC, ecc requrements are as follows: + * - 43nm: 1 bit ECC for each 512Byte is required. + * - 32nm: 4 bit ECC for each 512Byte is required. + * - 24nm: 8 bit ECC for each 512Byte is required. + */ + if (chip->id.len >= 6 && nand_is_slc(chip)) { + requirements.step_size = 512; + switch (chip->id.data[5] & 0x7) { + case 0x4: + requirements.strength = 1; + break; + case 0x5: + requirements.strength = 4; + break; + case 0x6: + requirements.strength = 8; + break; + default: + WARN(1, "Could not get ECC info"); + requirements.step_size = 0; + break; + } + } + + nanddev_set_ecc_requirements(base, &requirements); +} + +static int +tc58teg5dclta00_choose_interface_config(struct nand_chip *chip, + struct nand_interface_config *iface) +{ + onfi_fill_interface_config(chip, iface, NAND_SDR_IFACE, 5); + + return nand_choose_best_sdr_timings(chip, iface, NULL); +} + +static int +tc58nvg0s3e_choose_interface_config(struct nand_chip *chip, + struct nand_interface_config *iface) +{ + onfi_fill_interface_config(chip, iface, NAND_SDR_IFACE, 2); + + return nand_choose_best_sdr_timings(chip, iface, NULL); +} + +static int +th58nvg2s3hbai4_choose_interface_config(struct nand_chip *chip, + struct nand_interface_config *iface) +{ + struct nand_sdr_timings *sdr = &iface->timings.sdr; + + /* Start with timings from the closest timing mode, mode 4. */ + onfi_fill_interface_config(chip, iface, NAND_SDR_IFACE, 4); + + /* Patch timings that differ from mode 4. */ + sdr->tALS_min = 12000; + sdr->tCHZ_max = 20000; + sdr->tCLS_min = 12000; + sdr->tCOH_min = 0; + sdr->tDS_min = 12000; + sdr->tRHOH_min = 25000; + sdr->tRHW_min = 30000; + sdr->tRHZ_max = 60000; + sdr->tWHR_min = 60000; + + /* Patch timings not part of onfi timing mode. */ + sdr->tPROG_max = 700000000; + sdr->tBERS_max = 5000000000; + + return nand_choose_best_sdr_timings(chip, iface, sdr); +} + +static int tc58teg5dclta00_init(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + chip->ops.choose_interface_config = + &tc58teg5dclta00_choose_interface_config; + chip->options |= NAND_NEED_SCRAMBLING; + mtd_set_pairing_scheme(mtd, &dist3_pairing_scheme); + + return 0; +} + +static int tc58nvg0s3e_init(struct nand_chip *chip) +{ + chip->ops.choose_interface_config = + &tc58nvg0s3e_choose_interface_config; + + return 0; +} + +static int th58nvg2s3hbai4_init(struct nand_chip *chip) +{ + chip->ops.choose_interface_config = + &th58nvg2s3hbai4_choose_interface_config; + + return 0; +} + +static int toshiba_nand_init(struct nand_chip *chip) +{ + if (nand_is_slc(chip)) + chip->options |= NAND_BBM_FIRSTPAGE | NAND_BBM_SECONDPAGE; + + /* Check that chip is BENAND and ECC mode is on-die */ + if (nand_is_slc(chip) && + chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_DIE && + chip->id.data[4] & TOSHIBA_NAND_ID4_IS_BENAND) + toshiba_nand_benand_init(chip); + + if (!strcmp("TC58TEG5DCLTA00", chip->parameters.model)) + tc58teg5dclta00_init(chip); + if (!strncmp("TC58NVG0S3E", chip->parameters.model, + sizeof("TC58NVG0S3E") - 1)) + tc58nvg0s3e_init(chip); + if ((!strncmp("TC58NVG1S3H", chip->parameters.model, + sizeof("TC58NVG1S3H") - 1)) || + (!strncmp("TH58NVG2S3HBAI4", chip->parameters.model, + sizeof("TH58NVG2S3HBAI4") - 1)) || + (!strncmp("TH58NVG3S0HBAI4", chip->parameters.model, + sizeof("TH58NVG3S0HBAI4") - 1))) + th58nvg2s3hbai4_init(chip); + + return 0; +} + +const struct nand_manufacturer_ops toshiba_nand_manuf_ops = { + .detect = toshiba_nand_decode_id, + .init = toshiba_nand_init, +}; diff --git a/drivers/mtd/nand/raw/nandsim.c b/drivers/mtd/nand/raw/nandsim.c new file mode 100644 index 000000000..fe968037f --- /dev/null +++ b/drivers/mtd/nand/raw/nandsim.c @@ -0,0 +1,2459 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * NAND flash simulator. + * + * Author: Artem B. Bityuckiy , + * + * Copyright (C) 2004 Nokia Corporation + * + * Note: NS means "NAND Simulator". + * Note: Input means input TO flash chip, output means output FROM chip. + */ + +#define pr_fmt(fmt) "[nandsim]" fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Default simulator parameters values */ +#if !defined(CONFIG_NANDSIM_FIRST_ID_BYTE) || \ + !defined(CONFIG_NANDSIM_SECOND_ID_BYTE) || \ + !defined(CONFIG_NANDSIM_THIRD_ID_BYTE) || \ + !defined(CONFIG_NANDSIM_FOURTH_ID_BYTE) +#define CONFIG_NANDSIM_FIRST_ID_BYTE 0x98 +#define CONFIG_NANDSIM_SECOND_ID_BYTE 0x39 +#define CONFIG_NANDSIM_THIRD_ID_BYTE 0xFF /* No byte */ +#define CONFIG_NANDSIM_FOURTH_ID_BYTE 0xFF /* No byte */ +#endif + +#ifndef CONFIG_NANDSIM_ACCESS_DELAY +#define CONFIG_NANDSIM_ACCESS_DELAY 25 +#endif +#ifndef CONFIG_NANDSIM_PROGRAMM_DELAY +#define CONFIG_NANDSIM_PROGRAMM_DELAY 200 +#endif +#ifndef CONFIG_NANDSIM_ERASE_DELAY +#define CONFIG_NANDSIM_ERASE_DELAY 2 +#endif +#ifndef CONFIG_NANDSIM_OUTPUT_CYCLE +#define CONFIG_NANDSIM_OUTPUT_CYCLE 40 +#endif +#ifndef CONFIG_NANDSIM_INPUT_CYCLE +#define CONFIG_NANDSIM_INPUT_CYCLE 50 +#endif +#ifndef CONFIG_NANDSIM_BUS_WIDTH +#define CONFIG_NANDSIM_BUS_WIDTH 8 +#endif +#ifndef CONFIG_NANDSIM_DO_DELAYS +#define CONFIG_NANDSIM_DO_DELAYS 0 +#endif +#ifndef CONFIG_NANDSIM_LOG +#define CONFIG_NANDSIM_LOG 0 +#endif +#ifndef CONFIG_NANDSIM_DBG +#define CONFIG_NANDSIM_DBG 0 +#endif +#ifndef CONFIG_NANDSIM_MAX_PARTS +#define CONFIG_NANDSIM_MAX_PARTS 32 +#endif + +static uint access_delay = CONFIG_NANDSIM_ACCESS_DELAY; +static uint programm_delay = CONFIG_NANDSIM_PROGRAMM_DELAY; +static uint erase_delay = CONFIG_NANDSIM_ERASE_DELAY; +static uint output_cycle = CONFIG_NANDSIM_OUTPUT_CYCLE; +static uint input_cycle = CONFIG_NANDSIM_INPUT_CYCLE; +static uint bus_width = CONFIG_NANDSIM_BUS_WIDTH; +static uint do_delays = CONFIG_NANDSIM_DO_DELAYS; +static uint log = CONFIG_NANDSIM_LOG; +static uint dbg = CONFIG_NANDSIM_DBG; +static unsigned long parts[CONFIG_NANDSIM_MAX_PARTS]; +static unsigned int parts_num; +static char *badblocks = NULL; +static char *weakblocks = NULL; +static char *weakpages = NULL; +static unsigned int bitflips = 0; +static char *gravepages = NULL; +static unsigned int overridesize = 0; +static char *cache_file = NULL; +static unsigned int bbt; +static unsigned int bch; +static u_char id_bytes[8] = { + [0] = CONFIG_NANDSIM_FIRST_ID_BYTE, + [1] = CONFIG_NANDSIM_SECOND_ID_BYTE, + [2] = CONFIG_NANDSIM_THIRD_ID_BYTE, + [3] = CONFIG_NANDSIM_FOURTH_ID_BYTE, + [4 ... 7] = 0xFF, +}; + +module_param_array(id_bytes, byte, NULL, 0400); +module_param_named(first_id_byte, id_bytes[0], byte, 0400); +module_param_named(second_id_byte, id_bytes[1], byte, 0400); +module_param_named(third_id_byte, id_bytes[2], byte, 0400); +module_param_named(fourth_id_byte, id_bytes[3], byte, 0400); +module_param(access_delay, uint, 0400); +module_param(programm_delay, uint, 0400); +module_param(erase_delay, uint, 0400); +module_param(output_cycle, uint, 0400); +module_param(input_cycle, uint, 0400); +module_param(bus_width, uint, 0400); +module_param(do_delays, uint, 0400); +module_param(log, uint, 0400); +module_param(dbg, uint, 0400); +module_param_array(parts, ulong, &parts_num, 0400); +module_param(badblocks, charp, 0400); +module_param(weakblocks, charp, 0400); +module_param(weakpages, charp, 0400); +module_param(bitflips, uint, 0400); +module_param(gravepages, charp, 0400); +module_param(overridesize, uint, 0400); +module_param(cache_file, charp, 0400); +module_param(bbt, uint, 0400); +module_param(bch, uint, 0400); + +MODULE_PARM_DESC(id_bytes, "The ID bytes returned by NAND Flash 'read ID' command"); +MODULE_PARM_DESC(first_id_byte, "The first byte returned by NAND Flash 'read ID' command (manufacturer ID) (obsolete)"); +MODULE_PARM_DESC(second_id_byte, "The second byte returned by NAND Flash 'read ID' command (chip ID) (obsolete)"); +MODULE_PARM_DESC(third_id_byte, "The third byte returned by NAND Flash 'read ID' command (obsolete)"); +MODULE_PARM_DESC(fourth_id_byte, "The fourth byte returned by NAND Flash 'read ID' command (obsolete)"); +MODULE_PARM_DESC(access_delay, "Initial page access delay (microseconds)"); +MODULE_PARM_DESC(programm_delay, "Page programm delay (microseconds"); +MODULE_PARM_DESC(erase_delay, "Sector erase delay (milliseconds)"); +MODULE_PARM_DESC(output_cycle, "Word output (from flash) time (nanoseconds)"); +MODULE_PARM_DESC(input_cycle, "Word input (to flash) time (nanoseconds)"); +MODULE_PARM_DESC(bus_width, "Chip's bus width (8- or 16-bit)"); +MODULE_PARM_DESC(do_delays, "Simulate NAND delays using busy-waits if not zero"); +MODULE_PARM_DESC(log, "Perform logging if not zero"); +MODULE_PARM_DESC(dbg, "Output debug information if not zero"); +MODULE_PARM_DESC(parts, "Partition sizes (in erase blocks) separated by commas"); +/* Page and erase block positions for the following parameters are independent of any partitions */ +MODULE_PARM_DESC(badblocks, "Erase blocks that are initially marked bad, separated by commas"); +MODULE_PARM_DESC(weakblocks, "Weak erase blocks [: remaining erase cycles (defaults to 3)]" + " separated by commas e.g. 113:2 means eb 113" + " can be erased only twice before failing"); +MODULE_PARM_DESC(weakpages, "Weak pages [: maximum writes (defaults to 3)]" + " separated by commas e.g. 1401:2 means page 1401" + " can be written only twice before failing"); +MODULE_PARM_DESC(bitflips, "Maximum number of random bit flips per page (zero by default)"); +MODULE_PARM_DESC(gravepages, "Pages that lose data [: maximum reads (defaults to 3)]" + " separated by commas e.g. 1401:2 means page 1401" + " can be read only twice before failing"); +MODULE_PARM_DESC(overridesize, "Specifies the NAND Flash size overriding the ID bytes. " + "The size is specified in erase blocks and as the exponent of a power of two" + " e.g. 5 means a size of 32 erase blocks"); +MODULE_PARM_DESC(cache_file, "File to use to cache nand pages instead of memory"); +MODULE_PARM_DESC(bbt, "0 OOB, 1 BBT with marker in OOB, 2 BBT with marker in data area"); +MODULE_PARM_DESC(bch, "Enable BCH ecc and set how many bits should " + "be correctable in 512-byte blocks"); + +/* The largest possible page size */ +#define NS_LARGEST_PAGE_SIZE 4096 + +/* Simulator's output macros (logging, debugging, warning, error) */ +#define NS_LOG(args...) \ + do { if (log) pr_debug(" log: " args); } while(0) +#define NS_DBG(args...) \ + do { if (dbg) pr_debug(" debug: " args); } while(0) +#define NS_WARN(args...) \ + do { pr_warn(" warning: " args); } while(0) +#define NS_ERR(args...) \ + do { pr_err(" error: " args); } while(0) +#define NS_INFO(args...) \ + do { pr_info(" " args); } while(0) + +/* Busy-wait delay macros (microseconds, milliseconds) */ +#define NS_UDELAY(us) \ + do { if (do_delays) udelay(us); } while(0) +#define NS_MDELAY(us) \ + do { if (do_delays) mdelay(us); } while(0) + +/* Is the nandsim structure initialized ? */ +#define NS_IS_INITIALIZED(ns) ((ns)->geom.totsz != 0) + +/* Good operation completion status */ +#define NS_STATUS_OK(ns) (NAND_STATUS_READY | (NAND_STATUS_WP * ((ns)->lines.wp == 0))) + +/* Operation failed completion status */ +#define NS_STATUS_FAILED(ns) (NAND_STATUS_FAIL | NS_STATUS_OK(ns)) + +/* Calculate the page offset in flash RAM image by (row, column) address */ +#define NS_RAW_OFFSET(ns) \ + (((ns)->regs.row * (ns)->geom.pgszoob) + (ns)->regs.column) + +/* Calculate the OOB offset in flash RAM image by (row, column) address */ +#define NS_RAW_OFFSET_OOB(ns) (NS_RAW_OFFSET(ns) + ns->geom.pgsz) + +/* Calculate the byte shift in the next page to access */ +#define NS_PAGE_BYTE_SHIFT(ns) ((ns)->regs.column + (ns)->regs.off) + +/* After a command is input, the simulator goes to one of the following states */ +#define STATE_CMD_READ0 0x00000001 /* read data from the beginning of page */ +#define STATE_CMD_READ1 0x00000002 /* read data from the second half of page */ +#define STATE_CMD_READSTART 0x00000003 /* read data second command (large page devices) */ +#define STATE_CMD_PAGEPROG 0x00000004 /* start page program */ +#define STATE_CMD_READOOB 0x00000005 /* read OOB area */ +#define STATE_CMD_ERASE1 0x00000006 /* sector erase first command */ +#define STATE_CMD_STATUS 0x00000007 /* read status */ +#define STATE_CMD_SEQIN 0x00000009 /* sequential data input */ +#define STATE_CMD_READID 0x0000000A /* read ID */ +#define STATE_CMD_ERASE2 0x0000000B /* sector erase second command */ +#define STATE_CMD_RESET 0x0000000C /* reset */ +#define STATE_CMD_RNDOUT 0x0000000D /* random output command */ +#define STATE_CMD_RNDOUTSTART 0x0000000E /* random output start command */ +#define STATE_CMD_MASK 0x0000000F /* command states mask */ + +/* After an address is input, the simulator goes to one of these states */ +#define STATE_ADDR_PAGE 0x00000010 /* full (row, column) address is accepted */ +#define STATE_ADDR_SEC 0x00000020 /* sector address was accepted */ +#define STATE_ADDR_COLUMN 0x00000030 /* column address was accepted */ +#define STATE_ADDR_ZERO 0x00000040 /* one byte zero address was accepted */ +#define STATE_ADDR_MASK 0x00000070 /* address states mask */ + +/* During data input/output the simulator is in these states */ +#define STATE_DATAIN 0x00000100 /* waiting for data input */ +#define STATE_DATAIN_MASK 0x00000100 /* data input states mask */ + +#define STATE_DATAOUT 0x00001000 /* waiting for page data output */ +#define STATE_DATAOUT_ID 0x00002000 /* waiting for ID bytes output */ +#define STATE_DATAOUT_STATUS 0x00003000 /* waiting for status output */ +#define STATE_DATAOUT_MASK 0x00007000 /* data output states mask */ + +/* Previous operation is done, ready to accept new requests */ +#define STATE_READY 0x00000000 + +/* This state is used to mark that the next state isn't known yet */ +#define STATE_UNKNOWN 0x10000000 + +/* Simulator's actions bit masks */ +#define ACTION_CPY 0x00100000 /* copy page/OOB to the internal buffer */ +#define ACTION_PRGPAGE 0x00200000 /* program the internal buffer to flash */ +#define ACTION_SECERASE 0x00300000 /* erase sector */ +#define ACTION_ZEROOFF 0x00400000 /* don't add any offset to address */ +#define ACTION_HALFOFF 0x00500000 /* add to address half of page */ +#define ACTION_OOBOFF 0x00600000 /* add to address OOB offset */ +#define ACTION_MASK 0x00700000 /* action mask */ + +#define NS_OPER_NUM 13 /* Number of operations supported by the simulator */ +#define NS_OPER_STATES 6 /* Maximum number of states in operation */ + +#define OPT_ANY 0xFFFFFFFF /* any chip supports this operation */ +#define OPT_PAGE512 0x00000002 /* 512-byte page chips */ +#define OPT_PAGE2048 0x00000008 /* 2048-byte page chips */ +#define OPT_PAGE512_8BIT 0x00000040 /* 512-byte page chips with 8-bit bus width */ +#define OPT_PAGE4096 0x00000080 /* 4096-byte page chips */ +#define OPT_LARGEPAGE (OPT_PAGE2048 | OPT_PAGE4096) /* 2048 & 4096-byte page chips */ +#define OPT_SMALLPAGE (OPT_PAGE512) /* 512-byte page chips */ + +/* Remove action bits from state */ +#define NS_STATE(x) ((x) & ~ACTION_MASK) + +/* + * Maximum previous states which need to be saved. Currently saving is + * only needed for page program operation with preceded read command + * (which is only valid for 512-byte pages). + */ +#define NS_MAX_PREVSTATES 1 + +/* Maximum page cache pages needed to read or write a NAND page to the cache_file */ +#define NS_MAX_HELD_PAGES 16 + +/* + * A union to represent flash memory contents and flash buffer. + */ +union ns_mem { + u_char *byte; /* for byte access */ + uint16_t *word; /* for 16-bit word access */ +}; + +/* + * The structure which describes all the internal simulator data. + */ +struct nandsim { + struct nand_chip chip; + struct nand_controller base; + struct mtd_partition partitions[CONFIG_NANDSIM_MAX_PARTS]; + unsigned int nbparts; + + uint busw; /* flash chip bus width (8 or 16) */ + u_char ids[8]; /* chip's ID bytes */ + uint32_t options; /* chip's characteristic bits */ + uint32_t state; /* current chip state */ + uint32_t nxstate; /* next expected state */ + + uint32_t *op; /* current operation, NULL operations isn't known yet */ + uint32_t pstates[NS_MAX_PREVSTATES]; /* previous states */ + uint16_t npstates; /* number of previous states saved */ + uint16_t stateidx; /* current state index */ + + /* The simulated NAND flash pages array */ + union ns_mem *pages; + + /* Slab allocator for nand pages */ + struct kmem_cache *nand_pages_slab; + + /* Internal buffer of page + OOB size bytes */ + union ns_mem buf; + + /* NAND flash "geometry" */ + struct { + uint64_t totsz; /* total flash size, bytes */ + uint32_t secsz; /* flash sector (erase block) size, bytes */ + uint pgsz; /* NAND flash page size, bytes */ + uint oobsz; /* page OOB area size, bytes */ + uint64_t totszoob; /* total flash size including OOB, bytes */ + uint pgszoob; /* page size including OOB , bytes*/ + uint secszoob; /* sector size including OOB, bytes */ + uint pgnum; /* total number of pages */ + uint pgsec; /* number of pages per sector */ + uint secshift; /* bits number in sector size */ + uint pgshift; /* bits number in page size */ + uint pgaddrbytes; /* bytes per page address */ + uint secaddrbytes; /* bytes per sector address */ + uint idbytes; /* the number ID bytes that this chip outputs */ + } geom; + + /* NAND flash internal registers */ + struct { + unsigned command; /* the command register */ + u_char status; /* the status register */ + uint row; /* the page number */ + uint column; /* the offset within page */ + uint count; /* internal counter */ + uint num; /* number of bytes which must be processed */ + uint off; /* fixed page offset */ + } regs; + + /* NAND flash lines state */ + struct { + int ce; /* chip Enable */ + int cle; /* command Latch Enable */ + int ale; /* address Latch Enable */ + int wp; /* write Protect */ + } lines; + + /* Fields needed when using a cache file */ + struct file *cfile; /* Open file */ + unsigned long *pages_written; /* Which pages have been written */ + void *file_buf; + struct page *held_pages[NS_MAX_HELD_PAGES]; + int held_cnt; + + /* debugfs entry */ + struct dentry *dent; +}; + +/* + * Operations array. To perform any operation the simulator must pass + * through the correspondent states chain. + */ +static struct nandsim_operations { + uint32_t reqopts; /* options which are required to perform the operation */ + uint32_t states[NS_OPER_STATES]; /* operation's states */ +} ops[NS_OPER_NUM] = { + /* Read page + OOB from the beginning */ + {OPT_SMALLPAGE, {STATE_CMD_READ0 | ACTION_ZEROOFF, STATE_ADDR_PAGE | ACTION_CPY, + STATE_DATAOUT, STATE_READY}}, + /* Read page + OOB from the second half */ + {OPT_PAGE512_8BIT, {STATE_CMD_READ1 | ACTION_HALFOFF, STATE_ADDR_PAGE | ACTION_CPY, + STATE_DATAOUT, STATE_READY}}, + /* Read OOB */ + {OPT_SMALLPAGE, {STATE_CMD_READOOB | ACTION_OOBOFF, STATE_ADDR_PAGE | ACTION_CPY, + STATE_DATAOUT, STATE_READY}}, + /* Program page starting from the beginning */ + {OPT_ANY, {STATE_CMD_SEQIN, STATE_ADDR_PAGE, STATE_DATAIN, + STATE_CMD_PAGEPROG | ACTION_PRGPAGE, STATE_READY}}, + /* Program page starting from the beginning */ + {OPT_SMALLPAGE, {STATE_CMD_READ0, STATE_CMD_SEQIN | ACTION_ZEROOFF, STATE_ADDR_PAGE, + STATE_DATAIN, STATE_CMD_PAGEPROG | ACTION_PRGPAGE, STATE_READY}}, + /* Program page starting from the second half */ + {OPT_PAGE512, {STATE_CMD_READ1, STATE_CMD_SEQIN | ACTION_HALFOFF, STATE_ADDR_PAGE, + STATE_DATAIN, STATE_CMD_PAGEPROG | ACTION_PRGPAGE, STATE_READY}}, + /* Program OOB */ + {OPT_SMALLPAGE, {STATE_CMD_READOOB, STATE_CMD_SEQIN | ACTION_OOBOFF, STATE_ADDR_PAGE, + STATE_DATAIN, STATE_CMD_PAGEPROG | ACTION_PRGPAGE, STATE_READY}}, + /* Erase sector */ + {OPT_ANY, {STATE_CMD_ERASE1, STATE_ADDR_SEC, STATE_CMD_ERASE2 | ACTION_SECERASE, STATE_READY}}, + /* Read status */ + {OPT_ANY, {STATE_CMD_STATUS, STATE_DATAOUT_STATUS, STATE_READY}}, + /* Read ID */ + {OPT_ANY, {STATE_CMD_READID, STATE_ADDR_ZERO, STATE_DATAOUT_ID, STATE_READY}}, + /* Large page devices read page */ + {OPT_LARGEPAGE, {STATE_CMD_READ0, STATE_ADDR_PAGE, STATE_CMD_READSTART | ACTION_CPY, + STATE_DATAOUT, STATE_READY}}, + /* Large page devices random page read */ + {OPT_LARGEPAGE, {STATE_CMD_RNDOUT, STATE_ADDR_COLUMN, STATE_CMD_RNDOUTSTART | ACTION_CPY, + STATE_DATAOUT, STATE_READY}}, +}; + +struct weak_block { + struct list_head list; + unsigned int erase_block_no; + unsigned int max_erases; + unsigned int erases_done; +}; + +static LIST_HEAD(weak_blocks); + +struct weak_page { + struct list_head list; + unsigned int page_no; + unsigned int max_writes; + unsigned int writes_done; +}; + +static LIST_HEAD(weak_pages); + +struct grave_page { + struct list_head list; + unsigned int page_no; + unsigned int max_reads; + unsigned int reads_done; +}; + +static LIST_HEAD(grave_pages); + +static unsigned long *erase_block_wear = NULL; +static unsigned int wear_eb_count = 0; +static unsigned long total_wear = 0; + +/* MTD structure for NAND controller */ +static struct mtd_info *nsmtd; + +static int ns_show(struct seq_file *m, void *private) +{ + unsigned long wmin = -1, wmax = 0, avg; + unsigned long deciles[10], decile_max[10], tot = 0; + unsigned int i; + + /* Calc wear stats */ + for (i = 0; i < wear_eb_count; ++i) { + unsigned long wear = erase_block_wear[i]; + if (wear < wmin) + wmin = wear; + if (wear > wmax) + wmax = wear; + tot += wear; + } + + for (i = 0; i < 9; ++i) { + deciles[i] = 0; + decile_max[i] = (wmax * (i + 1) + 5) / 10; + } + deciles[9] = 0; + decile_max[9] = wmax; + for (i = 0; i < wear_eb_count; ++i) { + int d; + unsigned long wear = erase_block_wear[i]; + for (d = 0; d < 10; ++d) + if (wear <= decile_max[d]) { + deciles[d] += 1; + break; + } + } + avg = tot / wear_eb_count; + + /* Output wear report */ + seq_printf(m, "Total numbers of erases: %lu\n", tot); + seq_printf(m, "Number of erase blocks: %u\n", wear_eb_count); + seq_printf(m, "Average number of erases: %lu\n", avg); + seq_printf(m, "Maximum number of erases: %lu\n", wmax); + seq_printf(m, "Minimum number of erases: %lu\n", wmin); + for (i = 0; i < 10; ++i) { + unsigned long from = (i ? decile_max[i - 1] + 1 : 0); + if (from > decile_max[i]) + continue; + seq_printf(m, "Number of ebs with erase counts from %lu to %lu : %lu\n", + from, + decile_max[i], + deciles[i]); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ns); + +/** + * ns_debugfs_create - initialize debugfs + * @ns: nandsim device description object + * + * This function creates all debugfs files for UBI device @ubi. Returns zero in + * case of success and a negative error code in case of failure. + */ +static int ns_debugfs_create(struct nandsim *ns) +{ + struct dentry *root = nsmtd->dbg.dfs_dir; + + /* + * Just skip debugfs initialization when the debugfs directory is + * missing. + */ + if (IS_ERR_OR_NULL(root)) { + if (IS_ENABLED(CONFIG_DEBUG_FS) && + !IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER)) + NS_WARN("CONFIG_MTD_PARTITIONED_MASTER must be enabled to expose debugfs stuff\n"); + return 0; + } + + ns->dent = debugfs_create_file("nandsim_wear_report", 0400, root, ns, + &ns_fops); + if (IS_ERR_OR_NULL(ns->dent)) { + NS_ERR("cannot create \"nandsim_wear_report\" debugfs entry\n"); + return -1; + } + + return 0; +} + +static void ns_debugfs_remove(struct nandsim *ns) +{ + debugfs_remove_recursive(ns->dent); +} + +/* + * Allocate array of page pointers, create slab allocation for an array + * and initialize the array by NULL pointers. + * + * RETURNS: 0 if success, -ENOMEM if memory alloc fails. + */ +static int __init ns_alloc_device(struct nandsim *ns) +{ + struct file *cfile; + int i, err; + + if (cache_file) { + cfile = filp_open(cache_file, O_CREAT | O_RDWR | O_LARGEFILE, 0600); + if (IS_ERR(cfile)) + return PTR_ERR(cfile); + if (!(cfile->f_mode & FMODE_CAN_READ)) { + NS_ERR("alloc_device: cache file not readable\n"); + err = -EINVAL; + goto err_close_filp; + } + if (!(cfile->f_mode & FMODE_CAN_WRITE)) { + NS_ERR("alloc_device: cache file not writeable\n"); + err = -EINVAL; + goto err_close_filp; + } + 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; + goto err_close_filp; + } + ns->file_buf = kmalloc(ns->geom.pgszoob, GFP_KERNEL); + if (!ns->file_buf) { + NS_ERR("alloc_device: unable to allocate file buf\n"); + err = -ENOMEM; + goto err_free_pw; + } + ns->cfile = cfile; + + return 0; + +err_free_pw: + vfree(ns->pages_written); +err_close_filp: + filp_close(cfile, NULL); + + return err; + } + + 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; + } + for (i = 0; i < ns->geom.pgnum; i++) { + ns->pages[i].byte = NULL; + } + ns->nand_pages_slab = kmem_cache_create("nandsim", + ns->geom.pgszoob, 0, 0, NULL); + if (!ns->nand_pages_slab) { + NS_ERR("cache_create: unable to create kmem_cache\n"); + err = -ENOMEM; + goto err_free_pg; + } + + return 0; + +err_free_pg: + vfree(ns->pages); + + return err; +} + +/* + * Free any allocated pages, and free the array of page pointers. + */ +static void ns_free_device(struct nandsim *ns) +{ + int i; + + if (ns->cfile) { + kfree(ns->file_buf); + vfree(ns->pages_written); + filp_close(ns->cfile, NULL); + return; + } + + if (ns->pages) { + for (i = 0; i < ns->geom.pgnum; i++) { + if (ns->pages[i].byte) + kmem_cache_free(ns->nand_pages_slab, + ns->pages[i].byte); + } + kmem_cache_destroy(ns->nand_pages_slab); + vfree(ns->pages); + } +} + +static char __init *ns_get_partition_name(int i) +{ + return kasprintf(GFP_KERNEL, "NAND simulator partition %d", i); +} + +/* + * Initialize the nandsim structure. + * + * RETURNS: 0 if success, -ERRNO if failure. + */ +static int __init ns_init(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct nandsim *ns = nand_get_controller_data(chip); + int i, ret = 0; + uint64_t remains; + uint64_t next_offset; + + if (NS_IS_INITIALIZED(ns)) { + NS_ERR("init_nandsim: nandsim is already initialized\n"); + return -EIO; + } + + /* Initialize the NAND flash parameters */ + ns->busw = chip->options & NAND_BUSWIDTH_16 ? 16 : 8; + ns->geom.totsz = mtd->size; + ns->geom.pgsz = mtd->writesize; + ns->geom.oobsz = mtd->oobsize; + ns->geom.secsz = mtd->erasesize; + ns->geom.pgszoob = ns->geom.pgsz + ns->geom.oobsz; + ns->geom.pgnum = div_u64(ns->geom.totsz, ns->geom.pgsz); + ns->geom.totszoob = ns->geom.totsz + (uint64_t)ns->geom.pgnum * ns->geom.oobsz; + ns->geom.secshift = ffs(ns->geom.secsz) - 1; + ns->geom.pgshift = chip->page_shift; + ns->geom.pgsec = ns->geom.secsz / ns->geom.pgsz; + ns->geom.secszoob = ns->geom.secsz + ns->geom.oobsz * ns->geom.pgsec; + ns->options = 0; + + if (ns->geom.pgsz == 512) { + ns->options |= OPT_PAGE512; + if (ns->busw == 8) + ns->options |= OPT_PAGE512_8BIT; + } else if (ns->geom.pgsz == 2048) { + ns->options |= OPT_PAGE2048; + } else if (ns->geom.pgsz == 4096) { + ns->options |= OPT_PAGE4096; + } else { + NS_ERR("init_nandsim: unknown page size %u\n", ns->geom.pgsz); + return -EIO; + } + + if (ns->options & OPT_SMALLPAGE) { + if (ns->geom.totsz <= (32 << 20)) { + ns->geom.pgaddrbytes = 3; + ns->geom.secaddrbytes = 2; + } else { + ns->geom.pgaddrbytes = 4; + ns->geom.secaddrbytes = 3; + } + } else { + if (ns->geom.totsz <= (128 << 20)) { + ns->geom.pgaddrbytes = 4; + ns->geom.secaddrbytes = 2; + } else { + ns->geom.pgaddrbytes = 5; + ns->geom.secaddrbytes = 3; + } + } + + /* Fill the partition_info structure */ + if (parts_num > ARRAY_SIZE(ns->partitions)) { + NS_ERR("too many partitions.\n"); + return -EINVAL; + } + remains = ns->geom.totsz; + next_offset = 0; + for (i = 0; i < parts_num; ++i) { + uint64_t part_sz = (uint64_t)parts[i] * ns->geom.secsz; + + if (!part_sz || part_sz > remains) { + NS_ERR("bad partition size.\n"); + return -EINVAL; + } + ns->partitions[i].name = ns_get_partition_name(i); + if (!ns->partitions[i].name) { + NS_ERR("unable to allocate memory.\n"); + return -ENOMEM; + } + ns->partitions[i].offset = next_offset; + ns->partitions[i].size = part_sz; + next_offset += ns->partitions[i].size; + remains -= ns->partitions[i].size; + } + ns->nbparts = parts_num; + if (remains) { + if (parts_num + 1 > ARRAY_SIZE(ns->partitions)) { + NS_ERR("too many partitions.\n"); + ret = -EINVAL; + goto free_partition_names; + } + ns->partitions[i].name = ns_get_partition_name(i); + if (!ns->partitions[i].name) { + NS_ERR("unable to allocate memory.\n"); + ret = -ENOMEM; + goto free_partition_names; + } + ns->partitions[i].offset = next_offset; + ns->partitions[i].size = remains; + ns->nbparts += 1; + } + + if (ns->busw == 16) + NS_WARN("16-bit flashes support wasn't tested\n"); + + printk("flash size: %llu MiB\n", + (unsigned long long)ns->geom.totsz >> 20); + printk("page size: %u bytes\n", ns->geom.pgsz); + printk("OOB area size: %u bytes\n", ns->geom.oobsz); + printk("sector size: %u KiB\n", ns->geom.secsz >> 10); + printk("pages number: %u\n", ns->geom.pgnum); + printk("pages per sector: %u\n", ns->geom.pgsec); + printk("bus width: %u\n", ns->busw); + printk("bits in sector size: %u\n", ns->geom.secshift); + printk("bits in page size: %u\n", ns->geom.pgshift); + printk("bits in OOB size: %u\n", ffs(ns->geom.oobsz) - 1); + printk("flash size with OOB: %llu KiB\n", + (unsigned long long)ns->geom.totszoob >> 10); + printk("page address bytes: %u\n", ns->geom.pgaddrbytes); + printk("sector address bytes: %u\n", ns->geom.secaddrbytes); + printk("options: %#x\n", ns->options); + + ret = ns_alloc_device(ns); + if (ret) + goto free_partition_names; + + /* Allocate / initialize the internal buffer */ + ns->buf.byte = kmalloc(ns->geom.pgszoob, GFP_KERNEL); + if (!ns->buf.byte) { + NS_ERR("init_nandsim: unable to allocate %u bytes for the internal buffer\n", + ns->geom.pgszoob); + ret = -ENOMEM; + goto free_device; + } + memset(ns->buf.byte, 0xFF, ns->geom.pgszoob); + + return 0; + +free_device: + ns_free_device(ns); +free_partition_names: + for (i = 0; i < ARRAY_SIZE(ns->partitions); ++i) + kfree(ns->partitions[i].name); + + return ret; +} + +/* + * Free the nandsim structure. + */ +static void ns_free(struct nandsim *ns) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ns->partitions); ++i) + kfree(ns->partitions[i].name); + + kfree(ns->buf.byte); + ns_free_device(ns); + + return; +} + +static int ns_parse_badblocks(struct nandsim *ns, struct mtd_info *mtd) +{ + char *w; + int zero_ok; + unsigned int erase_block_no; + loff_t offset; + + if (!badblocks) + return 0; + w = badblocks; + do { + zero_ok = (*w == '0' ? 1 : 0); + erase_block_no = simple_strtoul(w, &w, 0); + if (!zero_ok && !erase_block_no) { + NS_ERR("invalid badblocks.\n"); + return -EINVAL; + } + offset = (loff_t)erase_block_no * ns->geom.secsz; + if (mtd_block_markbad(mtd, offset)) { + NS_ERR("invalid badblocks.\n"); + return -EINVAL; + } + if (*w == ',') + w += 1; + } while (*w); + return 0; +} + +static int ns_parse_weakblocks(void) +{ + char *w; + int zero_ok; + unsigned int erase_block_no; + unsigned int max_erases; + struct weak_block *wb; + + if (!weakblocks) + return 0; + w = weakblocks; + do { + zero_ok = (*w == '0' ? 1 : 0); + erase_block_no = simple_strtoul(w, &w, 0); + if (!zero_ok && !erase_block_no) { + NS_ERR("invalid weakblocks.\n"); + return -EINVAL; + } + max_erases = 3; + if (*w == ':') { + w += 1; + max_erases = simple_strtoul(w, &w, 0); + } + if (*w == ',') + w += 1; + wb = kzalloc_obj(*wb); + if (!wb) { + NS_ERR("unable to allocate memory.\n"); + return -ENOMEM; + } + wb->erase_block_no = erase_block_no; + wb->max_erases = max_erases; + list_add(&wb->list, &weak_blocks); + } while (*w); + return 0; +} + +static int ns_erase_error(unsigned int erase_block_no) +{ + struct weak_block *wb; + + list_for_each_entry(wb, &weak_blocks, list) + if (wb->erase_block_no == erase_block_no) { + if (wb->erases_done >= wb->max_erases) + return 1; + wb->erases_done += 1; + return 0; + } + return 0; +} + +static int ns_parse_weakpages(void) +{ + char *w; + int zero_ok; + unsigned int page_no; + unsigned int max_writes; + struct weak_page *wp; + + if (!weakpages) + return 0; + w = weakpages; + do { + zero_ok = (*w == '0' ? 1 : 0); + page_no = simple_strtoul(w, &w, 0); + if (!zero_ok && !page_no) { + NS_ERR("invalid weakpages.\n"); + return -EINVAL; + } + max_writes = 3; + if (*w == ':') { + w += 1; + max_writes = simple_strtoul(w, &w, 0); + } + if (*w == ',') + w += 1; + wp = kzalloc_obj(*wp); + if (!wp) { + NS_ERR("unable to allocate memory.\n"); + return -ENOMEM; + } + wp->page_no = page_no; + wp->max_writes = max_writes; + list_add(&wp->list, &weak_pages); + } while (*w); + return 0; +} + +static int ns_write_error(unsigned int page_no) +{ + struct weak_page *wp; + + list_for_each_entry(wp, &weak_pages, list) + if (wp->page_no == page_no) { + if (wp->writes_done >= wp->max_writes) + return 1; + wp->writes_done += 1; + return 0; + } + return 0; +} + +static int ns_parse_gravepages(void) +{ + char *g; + int zero_ok; + unsigned int page_no; + unsigned int max_reads; + struct grave_page *gp; + + if (!gravepages) + return 0; + g = gravepages; + do { + zero_ok = (*g == '0' ? 1 : 0); + page_no = simple_strtoul(g, &g, 0); + if (!zero_ok && !page_no) { + NS_ERR("invalid gravepagess.\n"); + return -EINVAL; + } + max_reads = 3; + if (*g == ':') { + g += 1; + max_reads = simple_strtoul(g, &g, 0); + } + if (*g == ',') + g += 1; + gp = kzalloc_obj(*gp); + if (!gp) { + NS_ERR("unable to allocate memory.\n"); + return -ENOMEM; + } + gp->page_no = page_no; + gp->max_reads = max_reads; + list_add(&gp->list, &grave_pages); + } while (*g); + return 0; +} + +static int ns_read_error(unsigned int page_no) +{ + struct grave_page *gp; + + list_for_each_entry(gp, &grave_pages, list) + if (gp->page_no == page_no) { + if (gp->reads_done >= gp->max_reads) + return 1; + gp->reads_done += 1; + return 0; + } + return 0; +} + +static int ns_setup_wear_reporting(struct mtd_info *mtd) +{ + wear_eb_count = div_u64(mtd->size, mtd->erasesize); + erase_block_wear = kcalloc(wear_eb_count, sizeof(unsigned long), GFP_KERNEL); + if (!erase_block_wear) { + NS_ERR("Too many erase blocks for wear reporting\n"); + return -ENOMEM; + } + return 0; +} + +static void ns_update_wear(unsigned int erase_block_no) +{ + if (!erase_block_wear) + return; + total_wear += 1; + /* + * TODO: Notify this through a debugfs entry, + * instead of showing an error message. + */ + if (total_wear == 0) + NS_ERR("Erase counter total overflow\n"); + erase_block_wear[erase_block_no] += 1; + if (erase_block_wear[erase_block_no] == 0) + NS_ERR("Erase counter overflow for erase block %u\n", erase_block_no); +} + +/* + * Returns the string representation of 'state' state. + */ +static char *ns_get_state_name(uint32_t state) +{ + switch (NS_STATE(state)) { + case STATE_CMD_READ0: + return "STATE_CMD_READ0"; + case STATE_CMD_READ1: + return "STATE_CMD_READ1"; + case STATE_CMD_PAGEPROG: + return "STATE_CMD_PAGEPROG"; + case STATE_CMD_READOOB: + return "STATE_CMD_READOOB"; + case STATE_CMD_READSTART: + return "STATE_CMD_READSTART"; + case STATE_CMD_ERASE1: + return "STATE_CMD_ERASE1"; + case STATE_CMD_STATUS: + return "STATE_CMD_STATUS"; + case STATE_CMD_SEQIN: + return "STATE_CMD_SEQIN"; + case STATE_CMD_READID: + return "STATE_CMD_READID"; + case STATE_CMD_ERASE2: + return "STATE_CMD_ERASE2"; + case STATE_CMD_RESET: + return "STATE_CMD_RESET"; + case STATE_CMD_RNDOUT: + return "STATE_CMD_RNDOUT"; + case STATE_CMD_RNDOUTSTART: + return "STATE_CMD_RNDOUTSTART"; + case STATE_ADDR_PAGE: + return "STATE_ADDR_PAGE"; + case STATE_ADDR_SEC: + return "STATE_ADDR_SEC"; + case STATE_ADDR_ZERO: + return "STATE_ADDR_ZERO"; + case STATE_ADDR_COLUMN: + return "STATE_ADDR_COLUMN"; + case STATE_DATAIN: + return "STATE_DATAIN"; + case STATE_DATAOUT: + return "STATE_DATAOUT"; + case STATE_DATAOUT_ID: + return "STATE_DATAOUT_ID"; + case STATE_DATAOUT_STATUS: + return "STATE_DATAOUT_STATUS"; + case STATE_READY: + return "STATE_READY"; + case STATE_UNKNOWN: + return "STATE_UNKNOWN"; + } + + NS_ERR("get_state_name: unknown state, BUG\n"); + return NULL; +} + +/* + * Check if command is valid. + * + * RETURNS: 1 if wrong command, 0 if right. + */ +static int ns_check_command(int cmd) +{ + switch (cmd) { + + case NAND_CMD_READ0: + case NAND_CMD_READ1: + case NAND_CMD_READSTART: + case NAND_CMD_PAGEPROG: + case NAND_CMD_READOOB: + case NAND_CMD_ERASE1: + case NAND_CMD_STATUS: + case NAND_CMD_SEQIN: + case NAND_CMD_READID: + case NAND_CMD_ERASE2: + case NAND_CMD_RESET: + case NAND_CMD_RNDOUT: + case NAND_CMD_RNDOUTSTART: + return 0; + + default: + return 1; + } +} + +/* + * Returns state after command is accepted by command number. + */ +static uint32_t ns_get_state_by_command(unsigned command) +{ + switch (command) { + case NAND_CMD_READ0: + return STATE_CMD_READ0; + case NAND_CMD_READ1: + return STATE_CMD_READ1; + case NAND_CMD_PAGEPROG: + return STATE_CMD_PAGEPROG; + case NAND_CMD_READSTART: + return STATE_CMD_READSTART; + case NAND_CMD_READOOB: + return STATE_CMD_READOOB; + case NAND_CMD_ERASE1: + return STATE_CMD_ERASE1; + case NAND_CMD_STATUS: + return STATE_CMD_STATUS; + case NAND_CMD_SEQIN: + return STATE_CMD_SEQIN; + case NAND_CMD_READID: + return STATE_CMD_READID; + case NAND_CMD_ERASE2: + return STATE_CMD_ERASE2; + case NAND_CMD_RESET: + return STATE_CMD_RESET; + case NAND_CMD_RNDOUT: + return STATE_CMD_RNDOUT; + case NAND_CMD_RNDOUTSTART: + return STATE_CMD_RNDOUTSTART; + } + + NS_ERR("get_state_by_command: unknown command, BUG\n"); + return 0; +} + +/* + * Move an address byte to the correspondent internal register. + */ +static inline void ns_accept_addr_byte(struct nandsim *ns, u_char bt) +{ + uint byte = (uint)bt; + + if (ns->regs.count < (ns->geom.pgaddrbytes - ns->geom.secaddrbytes)) + ns->regs.column |= (byte << 8 * ns->regs.count); + else { + ns->regs.row |= (byte << 8 * (ns->regs.count - + ns->geom.pgaddrbytes + + ns->geom.secaddrbytes)); + } + + return; +} + +/* + * Switch to STATE_READY state. + */ +static inline void ns_switch_to_ready_state(struct nandsim *ns, u_char status) +{ + NS_DBG("switch_to_ready_state: switch to %s state\n", + ns_get_state_name(STATE_READY)); + + ns->state = STATE_READY; + ns->nxstate = STATE_UNKNOWN; + ns->op = NULL; + ns->npstates = 0; + ns->stateidx = 0; + ns->regs.num = 0; + ns->regs.count = 0; + ns->regs.off = 0; + ns->regs.row = 0; + ns->regs.column = 0; + ns->regs.status = status; +} + +/* + * If the operation isn't known yet, try to find it in the global array + * of supported operations. + * + * Operation can be unknown because of the following. + * 1. New command was accepted and this is the first call to find the + * correspondent states chain. In this case ns->npstates = 0; + * 2. There are several operations which begin with the same command(s) + * (for example program from the second half and read from the + * second half operations both begin with the READ1 command). In this + * case the ns->pstates[] array contains previous states. + * + * Thus, the function tries to find operation containing the following + * states (if the 'flag' parameter is 0): + * ns->pstates[0], ... ns->pstates[ns->npstates], ns->state + * + * If (one and only one) matching operation is found, it is accepted ( + * ns->ops, ns->state, ns->nxstate are initialized, ns->npstate is + * zeroed). + * + * If there are several matches, the current state is pushed to the + * ns->pstates. + * + * The operation can be unknown only while commands are input to the chip. + * As soon as address command is accepted, the operation must be known. + * In such situation the function is called with 'flag' != 0, and the + * operation is searched using the following pattern: + * ns->pstates[0], ... ns->pstates[ns->npstates],
+ * + * It is supposed that this pattern must either match one operation or + * none. There can't be ambiguity in that case. + * + * If no matches found, the function does the following: + * 1. if there are saved states present, try to ignore them and search + * again only using the last command. If nothing was found, switch + * to the STATE_READY state. + * 2. if there are no saved states, switch to the STATE_READY state. + * + * RETURNS: -2 - no matched operations found. + * -1 - several matches. + * 0 - operation is found. + */ +static int ns_find_operation(struct nandsim *ns, uint32_t flag) +{ + int opsfound = 0; + int i, j, idx = 0; + + for (i = 0; i < NS_OPER_NUM; i++) { + + int found = 1; + + if (!(ns->options & ops[i].reqopts)) + /* Ignore operations we can't perform */ + continue; + + if (flag) { + if (!(ops[i].states[ns->npstates] & STATE_ADDR_MASK)) + continue; + } else { + if (NS_STATE(ns->state) != NS_STATE(ops[i].states[ns->npstates])) + continue; + } + + for (j = 0; j < ns->npstates; j++) + if (NS_STATE(ops[i].states[j]) != NS_STATE(ns->pstates[j]) + && (ns->options & ops[idx].reqopts)) { + found = 0; + break; + } + + if (found) { + idx = i; + opsfound += 1; + } + } + + if (opsfound == 1) { + /* Exact match */ + ns->op = &ops[idx].states[0]; + if (flag) { + /* + * In this case the find_operation function was + * called when address has just began input. But it isn't + * yet fully input and the current state must + * not be one of STATE_ADDR_*, but the STATE_ADDR_* + * state must be the next state (ns->nxstate). + */ + ns->stateidx = ns->npstates - 1; + } else { + ns->stateidx = ns->npstates; + } + ns->npstates = 0; + ns->state = ns->op[ns->stateidx]; + ns->nxstate = ns->op[ns->stateidx + 1]; + NS_DBG("find_operation: operation found, index: %d, state: %s, nxstate %s\n", + idx, ns_get_state_name(ns->state), + ns_get_state_name(ns->nxstate)); + return 0; + } + + if (opsfound == 0) { + /* Nothing was found. Try to ignore previous commands (if any) and search again */ + if (ns->npstates != 0) { + NS_DBG("find_operation: no operation found, try again with state %s\n", + ns_get_state_name(ns->state)); + ns->npstates = 0; + return ns_find_operation(ns, 0); + + } + NS_DBG("find_operation: no operations found\n"); + ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns)); + return -2; + } + + if (flag) { + /* This shouldn't happen */ + NS_DBG("find_operation: BUG, operation must be known if address is input\n"); + return -2; + } + + NS_DBG("find_operation: there is still ambiguity\n"); + + ns->pstates[ns->npstates++] = ns->state; + + return -1; +} + +static void ns_put_pages(struct nandsim *ns) +{ + int i; + + for (i = 0; i < ns->held_cnt; i++) + put_page(ns->held_pages[i]); +} + +/* Get page cache pages in advance to provide NOFS memory allocation */ +static int ns_get_pages(struct nandsim *ns, struct file *file, size_t count, + loff_t pos) +{ + pgoff_t index, start_index, end_index; + struct page *page; + struct address_space *mapping = file->f_mapping; + + start_index = pos >> PAGE_SHIFT; + end_index = (pos + count - 1) >> PAGE_SHIFT; + if (end_index - start_index + 1 > NS_MAX_HELD_PAGES) + return -EINVAL; + ns->held_cnt = 0; + for (index = start_index; index <= end_index; index++) { + page = find_get_page(mapping, index); + if (page == NULL) { + page = find_or_create_page(mapping, index, GFP_NOFS); + if (page == NULL) { + write_inode_now(mapping->host, 1); + page = find_or_create_page(mapping, index, GFP_NOFS); + } + if (page == NULL) { + ns_put_pages(ns); + return -ENOMEM; + } + unlock_page(page); + } + ns->held_pages[ns->held_cnt++] = page; + } + return 0; +} + +static ssize_t ns_read_file(struct nandsim *ns, struct file *file, void *buf, + size_t count, loff_t pos) +{ + ssize_t tx; + int err; + unsigned int noreclaim_flag; + + err = ns_get_pages(ns, file, count, pos); + if (err) + return err; + noreclaim_flag = memalloc_noreclaim_save(); + tx = kernel_read(file, buf, count, &pos); + memalloc_noreclaim_restore(noreclaim_flag); + ns_put_pages(ns); + return tx; +} + +static ssize_t ns_write_file(struct nandsim *ns, struct file *file, void *buf, + size_t count, loff_t pos) +{ + ssize_t tx; + int err; + unsigned int noreclaim_flag; + + err = ns_get_pages(ns, file, count, pos); + if (err) + return err; + noreclaim_flag = memalloc_noreclaim_save(); + tx = kernel_write(file, buf, count, &pos); + memalloc_noreclaim_restore(noreclaim_flag); + ns_put_pages(ns); + return tx; +} + +/* + * Returns a pointer to the current page. + */ +static inline union ns_mem *NS_GET_PAGE(struct nandsim *ns) +{ + return &(ns->pages[ns->regs.row]); +} + +/* + * Returns a pointer to the current byte, within the current page. + */ +static inline u_char *NS_PAGE_BYTE_OFF(struct nandsim *ns) +{ + return NS_GET_PAGE(ns)->byte + NS_PAGE_BYTE_SHIFT(ns); +} + +static int ns_do_read_error(struct nandsim *ns, int num) +{ + unsigned int page_no = ns->regs.row; + + if (ns_read_error(page_no)) { + get_random_bytes(ns->buf.byte, num); + NS_WARN("simulating read error in page %u\n", page_no); + return 1; + } + return 0; +} + +static void ns_do_bit_flips(struct nandsim *ns, int num) +{ + if (bitflips && get_random_u16() < (1 << 6)) { + int flips = 1; + if (bitflips > 1) + flips = get_random_u32_inclusive(1, bitflips); + while (flips--) { + int pos = get_random_u32_below(num * 8); + ns->buf.byte[pos / 8] ^= (1 << (pos % 8)); + NS_WARN("read_page: flipping bit %d in page %d " + "reading from %d ecc: corrected=%u failed=%u\n", + pos, ns->regs.row, NS_PAGE_BYTE_SHIFT(ns), + nsmtd->ecc_stats.corrected, nsmtd->ecc_stats.failed); + } + } +} + +/* + * Fill the NAND buffer with data read from the specified page. + */ +static void ns_read_page(struct nandsim *ns, int num) +{ + union ns_mem *mypage; + + if (ns->cfile) { + if (!test_bit(ns->regs.row, ns->pages_written)) { + NS_DBG("read_page: page %d not written\n", ns->regs.row); + memset(ns->buf.byte, 0xFF, num); + } else { + loff_t pos; + ssize_t tx; + + NS_DBG("read_page: page %d written, reading from %d\n", + ns->regs.row, NS_PAGE_BYTE_SHIFT(ns)); + if (ns_do_read_error(ns, num)) + return; + pos = (loff_t)NS_RAW_OFFSET(ns) + ns->regs.off; + tx = ns_read_file(ns, ns->cfile, ns->buf.byte, num, + pos); + if (tx != num) { + NS_ERR("read_page: read error for page %d ret %ld\n", ns->regs.row, (long)tx); + return; + } + ns_do_bit_flips(ns, num); + } + return; + } + + mypage = NS_GET_PAGE(ns); + if (mypage->byte == NULL) { + NS_DBG("read_page: page %d not allocated\n", ns->regs.row); + memset(ns->buf.byte, 0xFF, num); + } else { + NS_DBG("read_page: page %d allocated, reading from %d\n", + ns->regs.row, NS_PAGE_BYTE_SHIFT(ns)); + if (ns_do_read_error(ns, num)) + return; + memcpy(ns->buf.byte, NS_PAGE_BYTE_OFF(ns), num); + ns_do_bit_flips(ns, num); + } +} + +/* + * Erase all pages in the specified sector. + */ +static void ns_erase_sector(struct nandsim *ns) +{ + union ns_mem *mypage; + int i; + + if (ns->cfile) { + for (i = 0; i < ns->geom.pgsec; i++) + if (__test_and_clear_bit(ns->regs.row + i, + ns->pages_written)) { + NS_DBG("erase_sector: freeing page %d\n", ns->regs.row + i); + } + return; + } + + mypage = NS_GET_PAGE(ns); + for (i = 0; i < ns->geom.pgsec; i++) { + if (mypage->byte != NULL) { + NS_DBG("erase_sector: freeing page %d\n", ns->regs.row+i); + kmem_cache_free(ns->nand_pages_slab, mypage->byte); + mypage->byte = NULL; + } + mypage++; + } +} + +/* + * Program the specified page with the contents from the NAND buffer. + */ +static int ns_prog_page(struct nandsim *ns, int num) +{ + int i; + union ns_mem *mypage; + u_char *pg_off; + + if (ns->cfile) { + loff_t off; + ssize_t tx; + int all; + + NS_DBG("prog_page: writing page %d\n", ns->regs.row); + pg_off = ns->file_buf + NS_PAGE_BYTE_SHIFT(ns); + off = (loff_t)NS_RAW_OFFSET(ns) + ns->regs.off; + if (!test_bit(ns->regs.row, ns->pages_written)) { + all = 1; + memset(ns->file_buf, 0xff, ns->geom.pgszoob); + } else { + all = 0; + tx = ns_read_file(ns, ns->cfile, pg_off, num, off); + if (tx != num) { + NS_ERR("prog_page: read error for page %d ret %ld\n", ns->regs.row, (long)tx); + return -1; + } + } + for (i = 0; i < num; i++) + pg_off[i] &= ns->buf.byte[i]; + if (all) { + loff_t pos = (loff_t)ns->regs.row * ns->geom.pgszoob; + tx = ns_write_file(ns, ns->cfile, ns->file_buf, + ns->geom.pgszoob, pos); + if (tx != ns->geom.pgszoob) { + NS_ERR("prog_page: write error for page %d ret %ld\n", ns->regs.row, (long)tx); + return -1; + } + __set_bit(ns->regs.row, ns->pages_written); + } else { + tx = ns_write_file(ns, ns->cfile, pg_off, num, off); + if (tx != num) { + NS_ERR("prog_page: write error for page %d ret %ld\n", ns->regs.row, (long)tx); + return -1; + } + } + return 0; + } + + mypage = NS_GET_PAGE(ns); + if (mypage->byte == NULL) { + NS_DBG("prog_page: allocating page %d\n", ns->regs.row); + /* + * We allocate memory with GFP_NOFS because a flash FS may + * utilize this. If it is holding an FS lock, then gets here, + * then kernel memory alloc runs writeback which goes to the FS + * again and deadlocks. This was seen in practice. + */ + mypage->byte = kmem_cache_alloc(ns->nand_pages_slab, GFP_NOFS); + if (mypage->byte == NULL) { + NS_ERR("prog_page: error allocating memory for page %d\n", ns->regs.row); + return -1; + } + memset(mypage->byte, 0xFF, ns->geom.pgszoob); + } + + pg_off = NS_PAGE_BYTE_OFF(ns); + for (i = 0; i < num; i++) + pg_off[i] &= ns->buf.byte[i]; + + return 0; +} + +/* + * If state has any action bit, perform this action. + * + * RETURNS: 0 if success, -1 if error. + */ +static int ns_do_state_action(struct nandsim *ns, uint32_t action) +{ + int num; + int busdiv = ns->busw == 8 ? 1 : 2; + unsigned int erase_block_no, page_no; + + action &= ACTION_MASK; + + /* Check that page address input is correct */ + if (action != ACTION_SECERASE && ns->regs.row >= ns->geom.pgnum) { + NS_WARN("do_state_action: wrong page number (%#x)\n", ns->regs.row); + return -1; + } + + switch (action) { + + case ACTION_CPY: + /* + * Copy page data to the internal buffer. + */ + + /* Column shouldn't be very large */ + if (ns->regs.column >= (ns->geom.pgszoob - ns->regs.off)) { + NS_ERR("do_state_action: column number is too large\n"); + break; + } + num = ns->geom.pgszoob - NS_PAGE_BYTE_SHIFT(ns); + ns_read_page(ns, num); + + NS_DBG("do_state_action: (ACTION_CPY:) copy %d bytes to int buf, raw offset %d\n", + num, NS_RAW_OFFSET(ns) + ns->regs.off); + + if (ns->regs.off == 0) + NS_LOG("read page %d\n", ns->regs.row); + else if (ns->regs.off < ns->geom.pgsz) + NS_LOG("read page %d (second half)\n", ns->regs.row); + else + NS_LOG("read OOB of page %d\n", ns->regs.row); + + NS_UDELAY(access_delay); + NS_UDELAY(input_cycle * ns->geom.pgsz / 1000 / busdiv); + + break; + + case ACTION_SECERASE: + /* + * Erase sector. + */ + + if (ns->lines.wp) { + NS_ERR("do_state_action: device is write-protected, ignore sector erase\n"); + return -1; + } + + if (ns->regs.row >= ns->geom.pgnum - ns->geom.pgsec + || (ns->regs.row & ~(ns->geom.secsz - 1))) { + NS_ERR("do_state_action: wrong sector address (%#x)\n", ns->regs.row); + return -1; + } + + ns->regs.row = (ns->regs.row << + 8 * (ns->geom.pgaddrbytes - ns->geom.secaddrbytes)) | ns->regs.column; + ns->regs.column = 0; + + erase_block_no = ns->regs.row >> (ns->geom.secshift - ns->geom.pgshift); + + NS_DBG("do_state_action: erase sector at address %#x, off = %d\n", + ns->regs.row, NS_RAW_OFFSET(ns)); + NS_LOG("erase sector %u\n", erase_block_no); + + ns_erase_sector(ns); + + NS_MDELAY(erase_delay); + + if (erase_block_wear) + ns_update_wear(erase_block_no); + + if (ns_erase_error(erase_block_no)) { + NS_WARN("simulating erase failure in erase block %u\n", erase_block_no); + return -1; + } + + break; + + case ACTION_PRGPAGE: + /* + * Program page - move internal buffer data to the page. + */ + + if (ns->lines.wp) { + NS_WARN("do_state_action: device is write-protected, programm\n"); + return -1; + } + + num = ns->geom.pgszoob - NS_PAGE_BYTE_SHIFT(ns); + if (num != ns->regs.count) { + NS_ERR("do_state_action: too few bytes were input (%d instead of %d)\n", + ns->regs.count, num); + return -1; + } + + if (ns_prog_page(ns, num) == -1) + return -1; + + page_no = ns->regs.row; + + NS_DBG("do_state_action: copy %d bytes from int buf to (%#x, %#x), raw off = %d\n", + num, ns->regs.row, ns->regs.column, NS_RAW_OFFSET(ns) + ns->regs.off); + NS_LOG("programm page %d\n", ns->regs.row); + + NS_UDELAY(programm_delay); + NS_UDELAY(output_cycle * ns->geom.pgsz / 1000 / busdiv); + + if (ns_write_error(page_no)) { + NS_WARN("simulating write failure in page %u\n", page_no); + return -1; + } + + break; + + case ACTION_ZEROOFF: + NS_DBG("do_state_action: set internal offset to 0\n"); + ns->regs.off = 0; + break; + + case ACTION_HALFOFF: + if (!(ns->options & OPT_PAGE512_8BIT)) { + NS_ERR("do_state_action: BUG! can't skip half of page for non-512" + "byte page size 8x chips\n"); + return -1; + } + NS_DBG("do_state_action: set internal offset to %d\n", ns->geom.pgsz/2); + ns->regs.off = ns->geom.pgsz/2; + break; + + case ACTION_OOBOFF: + NS_DBG("do_state_action: set internal offset to %d\n", ns->geom.pgsz); + ns->regs.off = ns->geom.pgsz; + break; + + default: + NS_DBG("do_state_action: BUG! unknown action\n"); + } + + return 0; +} + +/* + * Switch simulator's state. + */ +static void ns_switch_state(struct nandsim *ns) +{ + if (ns->op) { + /* + * The current operation have already been identified. + * Just follow the states chain. + */ + + ns->stateidx += 1; + ns->state = ns->nxstate; + ns->nxstate = ns->op[ns->stateidx + 1]; + + NS_DBG("switch_state: operation is known, switch to the next state, " + "state: %s, nxstate: %s\n", + ns_get_state_name(ns->state), + ns_get_state_name(ns->nxstate)); + } else { + /* + * We don't yet know which operation we perform. + * Try to identify it. + */ + + /* + * The only event causing the switch_state function to + * be called with yet unknown operation is new command. + */ + ns->state = ns_get_state_by_command(ns->regs.command); + + NS_DBG("switch_state: operation is unknown, try to find it\n"); + + if (ns_find_operation(ns, 0)) + return; + } + + /* See, whether we need to do some action */ + if ((ns->state & ACTION_MASK) && + ns_do_state_action(ns, ns->state) < 0) { + ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns)); + return; + } + + /* For 16x devices column means the page offset in words */ + if ((ns->nxstate & STATE_ADDR_MASK) && ns->busw == 16) { + NS_DBG("switch_state: double the column number for 16x device\n"); + ns->regs.column <<= 1; + } + + if (NS_STATE(ns->nxstate) == STATE_READY) { + /* + * The current state is the last. Return to STATE_READY + */ + + u_char status = NS_STATUS_OK(ns); + + /* In case of data states, see if all bytes were input/output */ + if ((ns->state & (STATE_DATAIN_MASK | STATE_DATAOUT_MASK)) + && ns->regs.count != ns->regs.num) { + NS_WARN("switch_state: not all bytes were processed, %d left\n", + ns->regs.num - ns->regs.count); + status = NS_STATUS_FAILED(ns); + } + + NS_DBG("switch_state: operation complete, switch to STATE_READY state\n"); + + ns_switch_to_ready_state(ns, status); + + return; + } else if (ns->nxstate & (STATE_DATAIN_MASK | STATE_DATAOUT_MASK)) { + /* + * If the next state is data input/output, switch to it now + */ + + ns->state = ns->nxstate; + ns->nxstate = ns->op[++ns->stateidx + 1]; + ns->regs.num = ns->regs.count = 0; + + NS_DBG("switch_state: the next state is data I/O, switch, " + "state: %s, nxstate: %s\n", + ns_get_state_name(ns->state), + ns_get_state_name(ns->nxstate)); + + /* + * Set the internal register to the count of bytes which + * are expected to be input or output + */ + switch (NS_STATE(ns->state)) { + case STATE_DATAIN: + case STATE_DATAOUT: + ns->regs.num = ns->geom.pgszoob - NS_PAGE_BYTE_SHIFT(ns); + break; + + case STATE_DATAOUT_ID: + ns->regs.num = ns->geom.idbytes; + break; + + case STATE_DATAOUT_STATUS: + ns->regs.count = ns->regs.num = 0; + break; + + default: + NS_ERR("switch_state: BUG! unknown data state\n"); + } + + } else if (ns->nxstate & STATE_ADDR_MASK) { + /* + * If the next state is address input, set the internal + * register to the number of expected address bytes + */ + + ns->regs.count = 0; + + switch (NS_STATE(ns->nxstate)) { + case STATE_ADDR_PAGE: + ns->regs.num = ns->geom.pgaddrbytes; + + break; + case STATE_ADDR_SEC: + ns->regs.num = ns->geom.secaddrbytes; + break; + + case STATE_ADDR_ZERO: + ns->regs.num = 1; + break; + + case STATE_ADDR_COLUMN: + /* Column address is always 2 bytes */ + ns->regs.num = ns->geom.pgaddrbytes - ns->geom.secaddrbytes; + break; + + default: + NS_ERR("switch_state: BUG! unknown address state\n"); + } + } else { + /* + * Just reset internal counters. + */ + + ns->regs.num = 0; + ns->regs.count = 0; + } +} + +static u_char ns_nand_read_byte(struct nand_chip *chip) +{ + struct nandsim *ns = nand_get_controller_data(chip); + u_char outb = 0x00; + + /* Sanity and correctness checks */ + if (!ns->lines.ce) { + NS_ERR("read_byte: chip is disabled, return %#x\n", (uint)outb); + return outb; + } + if (ns->lines.ale || ns->lines.cle) { + NS_ERR("read_byte: ALE or CLE pin is high, return %#x\n", (uint)outb); + return outb; + } + if (!(ns->state & STATE_DATAOUT_MASK)) { + NS_WARN("read_byte: unexpected data output cycle, state is %s return %#x\n", + ns_get_state_name(ns->state), (uint)outb); + return outb; + } + + /* Status register may be read as many times as it is wanted */ + if (NS_STATE(ns->state) == STATE_DATAOUT_STATUS) { + NS_DBG("read_byte: return %#x status\n", ns->regs.status); + return ns->regs.status; + } + + /* Check if there is any data in the internal buffer which may be read */ + if (ns->regs.count == ns->regs.num) { + NS_WARN("read_byte: no more data to output, return %#x\n", (uint)outb); + return outb; + } + + switch (NS_STATE(ns->state)) { + case STATE_DATAOUT: + if (ns->busw == 8) { + outb = ns->buf.byte[ns->regs.count]; + ns->regs.count += 1; + } else { + outb = (u_char)cpu_to_le16(ns->buf.word[ns->regs.count >> 1]); + ns->regs.count += 2; + } + break; + case STATE_DATAOUT_ID: + NS_DBG("read_byte: read ID byte %d, total = %d\n", ns->regs.count, ns->regs.num); + outb = ns->ids[ns->regs.count]; + ns->regs.count += 1; + break; + default: + BUG(); + } + + if (ns->regs.count == ns->regs.num) { + NS_DBG("read_byte: all bytes were read\n"); + + if (NS_STATE(ns->nxstate) == STATE_READY) + ns_switch_state(ns); + } + + return outb; +} + +static void ns_nand_write_byte(struct nand_chip *chip, u_char byte) +{ + struct nandsim *ns = nand_get_controller_data(chip); + + /* Sanity and correctness checks */ + if (!ns->lines.ce) { + NS_ERR("write_byte: chip is disabled, ignore write\n"); + return; + } + if (ns->lines.ale && ns->lines.cle) { + NS_ERR("write_byte: ALE and CLE pins are high simultaneously, ignore write\n"); + return; + } + + if (ns->lines.cle == 1) { + /* + * The byte written is a command. + */ + + if (byte == NAND_CMD_RESET) { + NS_LOG("reset chip\n"); + ns_switch_to_ready_state(ns, NS_STATUS_OK(ns)); + return; + } + + /* Check that the command byte is correct */ + if (ns_check_command(byte)) { + NS_ERR("write_byte: unknown command %#x\n", (uint)byte); + return; + } + + if (NS_STATE(ns->state) == STATE_DATAOUT_STATUS + || NS_STATE(ns->state) == STATE_DATAOUT) { + int row = ns->regs.row; + + ns_switch_state(ns); + if (byte == NAND_CMD_RNDOUT) + ns->regs.row = row; + } + + /* Check if chip is expecting command */ + if (NS_STATE(ns->nxstate) != STATE_UNKNOWN && !(ns->nxstate & STATE_CMD_MASK)) { + /* Do not warn if only 2 id bytes are read */ + if (!(ns->regs.command == NAND_CMD_READID && + NS_STATE(ns->state) == STATE_DATAOUT_ID && ns->regs.count == 2)) { + /* + * We are in situation when something else (not command) + * was expected but command was input. In this case ignore + * previous command(s)/state(s) and accept the last one. + */ + NS_WARN("write_byte: command (%#x) wasn't expected, expected state is %s, ignore previous states\n", + (uint)byte, + ns_get_state_name(ns->nxstate)); + } + ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns)); + } + + NS_DBG("command byte corresponding to %s state accepted\n", + ns_get_state_name(ns_get_state_by_command(byte))); + ns->regs.command = byte; + ns_switch_state(ns); + + } else if (ns->lines.ale == 1) { + /* + * The byte written is an address. + */ + + if (NS_STATE(ns->nxstate) == STATE_UNKNOWN) { + + NS_DBG("write_byte: operation isn't known yet, identify it\n"); + + if (ns_find_operation(ns, 1) < 0) + return; + + if ((ns->state & ACTION_MASK) && + ns_do_state_action(ns, ns->state) < 0) { + ns_switch_to_ready_state(ns, + NS_STATUS_FAILED(ns)); + return; + } + + ns->regs.count = 0; + switch (NS_STATE(ns->nxstate)) { + case STATE_ADDR_PAGE: + ns->regs.num = ns->geom.pgaddrbytes; + break; + case STATE_ADDR_SEC: + ns->regs.num = ns->geom.secaddrbytes; + break; + case STATE_ADDR_ZERO: + ns->regs.num = 1; + break; + default: + BUG(); + } + } + + /* Check that chip is expecting address */ + if (!(ns->nxstate & STATE_ADDR_MASK)) { + NS_ERR("write_byte: address (%#x) isn't expected, expected state is %s, switch to STATE_READY\n", + (uint)byte, ns_get_state_name(ns->nxstate)); + ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns)); + return; + } + + /* Check if this is expected byte */ + if (ns->regs.count == ns->regs.num) { + NS_ERR("write_byte: no more address bytes expected\n"); + ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns)); + return; + } + + ns_accept_addr_byte(ns, byte); + + ns->regs.count += 1; + + NS_DBG("write_byte: address byte %#x was accepted (%d bytes input, %d expected)\n", + (uint)byte, ns->regs.count, ns->regs.num); + + if (ns->regs.count == ns->regs.num) { + NS_DBG("address (%#x, %#x) is accepted\n", ns->regs.row, ns->regs.column); + ns_switch_state(ns); + } + + } else { + /* + * The byte written is an input data. + */ + + /* Check that chip is expecting data input */ + if (!(ns->state & STATE_DATAIN_MASK)) { + NS_ERR("write_byte: data input (%#x) isn't expected, state is %s, switch to %s\n", + (uint)byte, ns_get_state_name(ns->state), + ns_get_state_name(STATE_READY)); + ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns)); + return; + } + + /* Check if this is expected byte */ + if (ns->regs.count == ns->regs.num) { + NS_WARN("write_byte: %u input bytes has already been accepted, ignore write\n", + ns->regs.num); + return; + } + + if (ns->busw == 8) { + ns->buf.byte[ns->regs.count] = byte; + ns->regs.count += 1; + } else { + ns->buf.word[ns->regs.count >> 1] = cpu_to_le16((uint16_t)byte); + ns->regs.count += 2; + } + } + + return; +} + +static void ns_nand_write_buf(struct nand_chip *chip, const u_char *buf, + int len) +{ + struct nandsim *ns = nand_get_controller_data(chip); + + /* Check that chip is expecting data input */ + if (!(ns->state & STATE_DATAIN_MASK)) { + NS_ERR("write_buf: data input isn't expected, state is %s, switch to STATE_READY\n", + ns_get_state_name(ns->state)); + ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns)); + return; + } + + /* Check if these are expected bytes */ + if (ns->regs.count + len > ns->regs.num) { + NS_ERR("write_buf: too many input bytes\n"); + ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns)); + return; + } + + memcpy(ns->buf.byte + ns->regs.count, buf, len); + ns->regs.count += len; + + if (ns->regs.count == ns->regs.num) { + NS_DBG("write_buf: %d bytes were written\n", ns->regs.count); + } +} + +static void ns_nand_read_buf(struct nand_chip *chip, u_char *buf, int len) +{ + struct nandsim *ns = nand_get_controller_data(chip); + + /* Sanity and correctness checks */ + if (!ns->lines.ce) { + NS_ERR("read_buf: chip is disabled\n"); + return; + } + if (ns->lines.ale || ns->lines.cle) { + NS_ERR("read_buf: ALE or CLE pin is high\n"); + return; + } + if (!(ns->state & STATE_DATAOUT_MASK)) { + NS_WARN("read_buf: unexpected data output cycle, current state is %s\n", + ns_get_state_name(ns->state)); + return; + } + + if (NS_STATE(ns->state) != STATE_DATAOUT) { + int i; + + for (i = 0; i < len; i++) + buf[i] = ns_nand_read_byte(chip); + + return; + } + + /* Check if these are expected bytes */ + if (ns->regs.count + len > ns->regs.num) { + NS_ERR("read_buf: too many bytes to read\n"); + ns_switch_to_ready_state(ns, NS_STATUS_FAILED(ns)); + return; + } + + memcpy(buf, ns->buf.byte + ns->regs.count, len); + ns->regs.count += len; + + if (ns->regs.count == ns->regs.num) { + if (NS_STATE(ns->nxstate) == STATE_READY) + ns_switch_state(ns); + } + + return; +} + +static int ns_exec_op(struct nand_chip *chip, const struct nand_operation *op, + bool check_only) +{ + int i; + unsigned int op_id; + const struct nand_op_instr *instr = NULL; + struct nandsim *ns = nand_get_controller_data(chip); + + if (check_only) { + /* The current implementation of nandsim needs to know the + * ongoing operation when performing the address cycles. This + * means it cannot make the difference between a regular read + * and a continuous read. Hence, this hack to manually refuse + * supporting sequential cached operations. + */ + for (op_id = 0; op_id < op->ninstrs; op_id++) { + instr = &op->instrs[op_id]; + if (instr->type == NAND_OP_CMD_INSTR && + (instr->ctx.cmd.opcode == NAND_CMD_READCACHEEND || + instr->ctx.cmd.opcode == NAND_CMD_READCACHESEQ)) + return -EOPNOTSUPP; + } + + return 0; + } + + ns->lines.ce = 1; + + for (op_id = 0; op_id < op->ninstrs; op_id++) { + instr = &op->instrs[op_id]; + ns->lines.cle = 0; + ns->lines.ale = 0; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + ns->lines.cle = 1; + ns_nand_write_byte(chip, instr->ctx.cmd.opcode); + break; + case NAND_OP_ADDR_INSTR: + ns->lines.ale = 1; + for (i = 0; i < instr->ctx.addr.naddrs; i++) + ns_nand_write_byte(chip, instr->ctx.addr.addrs[i]); + break; + case NAND_OP_DATA_IN_INSTR: + ns_nand_read_buf(chip, instr->ctx.data.buf.in, instr->ctx.data.len); + break; + case NAND_OP_DATA_OUT_INSTR: + ns_nand_write_buf(chip, instr->ctx.data.buf.out, instr->ctx.data.len); + break; + case NAND_OP_WAITRDY_INSTR: + /* we are always ready */ + break; + } + } + + return 0; +} + +static int ns_attach_chip(struct nand_chip *chip) +{ + unsigned int eccsteps, eccbytes; + + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + chip->ecc.algo = bch ? NAND_ECC_ALGO_BCH : NAND_ECC_ALGO_HAMMING; + + if (!bch) + return 0; + + if (!IS_ENABLED(CONFIG_MTD_NAND_ECC_SW_BCH)) { + NS_ERR("BCH ECC support is disabled\n"); + return -EINVAL; + } + + /* Use 512-byte ecc blocks */ + eccsteps = nsmtd->writesize / 512; + eccbytes = ((bch * 13) + 7) / 8; + + /* Do not bother supporting small page devices */ + if (nsmtd->oobsize < 64 || !eccsteps) { + NS_ERR("BCH not available on small page devices\n"); + return -EINVAL; + } + + if (((eccbytes * eccsteps) + 2) > nsmtd->oobsize) { + NS_ERR("Invalid BCH value %u\n", bch); + return -EINVAL; + } + + chip->ecc.size = 512; + chip->ecc.strength = bch; + chip->ecc.bytes = eccbytes; + + NS_INFO("Using %u-bit/%u bytes BCH ECC\n", bch, chip->ecc.size); + + return 0; +} + +static const struct nand_controller_ops ns_controller_ops = { + .attach_chip = ns_attach_chip, + .exec_op = ns_exec_op, +}; + +/* + * Module initialization function + */ +static int __init ns_init_module(void) +{ + struct list_head *pos, *n; + struct nand_chip *chip; + struct nandsim *ns; + int ret; + + if (bus_width != 8 && bus_width != 16) { + NS_ERR("wrong bus width (%d), use only 8 or 16\n", bus_width); + return -EINVAL; + } + + ns = kzalloc_obj(struct nandsim); + if (!ns) { + NS_ERR("unable to allocate core structures.\n"); + return -ENOMEM; + } + chip = &ns->chip; + nsmtd = nand_to_mtd(chip); + nand_set_controller_data(chip, (void *)ns); + + /* The NAND_SKIP_BBTSCAN option is necessary for 'overridesize' */ + /* and 'badblocks' parameters to work */ + chip->options |= NAND_SKIP_BBTSCAN; + + switch (bbt) { + case 2: + chip->bbt_options |= NAND_BBT_NO_OOB; + fallthrough; + case 1: + chip->bbt_options |= NAND_BBT_USE_FLASH; + fallthrough; + case 0: + break; + default: + NS_ERR("bbt has to be 0..2\n"); + ret = -EINVAL; + goto free_ns_struct; + } + /* + * Perform minimum nandsim structure initialization to handle + * the initial ID read command correctly + */ + if (id_bytes[6] != 0xFF || id_bytes[7] != 0xFF) + ns->geom.idbytes = 8; + else if (id_bytes[4] != 0xFF || id_bytes[5] != 0xFF) + ns->geom.idbytes = 6; + else if (id_bytes[2] != 0xFF || id_bytes[3] != 0xFF) + ns->geom.idbytes = 4; + else + ns->geom.idbytes = 2; + ns->regs.status = NS_STATUS_OK(ns); + ns->nxstate = STATE_UNKNOWN; + ns->options |= OPT_PAGE512; /* temporary value */ + memcpy(ns->ids, id_bytes, sizeof(ns->ids)); + if (bus_width == 16) { + ns->busw = 16; + chip->options |= NAND_BUSWIDTH_16; + } + + nsmtd->owner = THIS_MODULE; + + ret = ns_parse_weakblocks(); + if (ret) + goto free_ns_struct; + + ret = ns_parse_weakpages(); + if (ret) + goto free_wb_list; + + ret = ns_parse_gravepages(); + if (ret) + goto free_wp_list; + + nand_controller_init(&ns->base); + ns->base.ops = &ns_controller_ops; + chip->controller = &ns->base; + + ret = nand_scan(chip, 1); + if (ret) { + NS_ERR("Could not scan NAND Simulator device\n"); + goto free_gp_list; + } + + if (overridesize) { + uint64_t new_size = (uint64_t)nsmtd->erasesize << overridesize; + struct nand_memory_organization *memorg; + u64 targetsize; + + memorg = nanddev_get_memorg(&chip->base); + + if (new_size >> overridesize != nsmtd->erasesize) { + NS_ERR("overridesize is too big\n"); + ret = -EINVAL; + goto cleanup_nand; + } + + /* N.B. This relies on nand_scan not doing anything with the size before we change it */ + nsmtd->size = new_size; + memorg->eraseblocks_per_lun = 1 << overridesize; + targetsize = nanddev_target_size(&chip->base); + chip->chip_shift = ffs(nsmtd->erasesize) + overridesize - 1; + chip->pagemask = (targetsize >> chip->page_shift) - 1; + } + + ret = ns_setup_wear_reporting(nsmtd); + if (ret) + goto cleanup_nand; + + ret = ns_init(nsmtd); + if (ret) + goto free_ebw; + + ret = nand_create_bbt(chip); + if (ret) + goto free_ns_object; + + ret = ns_parse_badblocks(ns, nsmtd); + if (ret) + goto free_ns_object; + + /* Register NAND partitions */ + ret = mtd_device_register(nsmtd, &ns->partitions[0], ns->nbparts); + if (ret) + goto free_ns_object; + + ret = ns_debugfs_create(ns); + if (ret) + goto unregister_mtd; + + return 0; + +unregister_mtd: + WARN_ON(mtd_device_unregister(nsmtd)); +free_ns_object: + ns_free(ns); +free_ebw: + kfree(erase_block_wear); +cleanup_nand: + nand_cleanup(chip); +free_gp_list: + list_for_each_safe(pos, n, &grave_pages) { + list_del(pos); + kfree(list_entry(pos, struct grave_page, list)); + } +free_wp_list: + list_for_each_safe(pos, n, &weak_pages) { + list_del(pos); + kfree(list_entry(pos, struct weak_page, list)); + } +free_wb_list: + list_for_each_safe(pos, n, &weak_blocks) { + list_del(pos); + kfree(list_entry(pos, struct weak_block, list)); + } +free_ns_struct: + kfree(ns); + + return ret; +} + +module_init(ns_init_module); + +/* + * Module clean-up function + */ +static void __exit ns_cleanup_module(void) +{ + struct nand_chip *chip = mtd_to_nand(nsmtd); + struct nandsim *ns = nand_get_controller_data(chip); + struct list_head *pos, *n; + + ns_debugfs_remove(ns); + WARN_ON(mtd_device_unregister(nsmtd)); + ns_free(ns); + kfree(erase_block_wear); + nand_cleanup(chip); + + list_for_each_safe(pos, n, &grave_pages) { + list_del(pos); + kfree(list_entry(pos, struct grave_page, list)); + } + + list_for_each_safe(pos, n, &weak_pages) { + list_del(pos); + kfree(list_entry(pos, struct weak_page, list)); + } + + list_for_each_safe(pos, n, &weak_blocks) { + list_del(pos); + kfree(list_entry(pos, struct weak_block, list)); + } + + kfree(ns); +} + +module_exit(ns_cleanup_module); + +MODULE_LICENSE ("GPL"); +MODULE_AUTHOR ("Artem B. Bityuckiy"); +MODULE_DESCRIPTION ("The NAND flash simulator"); diff --git a/drivers/mtd/nand/raw/ndfc.c b/drivers/mtd/nand/raw/ndfc.c new file mode 100644 index 000000000..a48274297 --- /dev/null +++ b/drivers/mtd/nand/raw/ndfc.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Overview: + * Platform independent driver for NDFC (NanD Flash Controller) + * integrated into EP440 cores + * + * Ported to an OF platform driver by Sean MacLennan + * + * The NDFC supports multiple chips, but this driver only supports a + * single chip since I do not have access to any boards with + * multiple chips. + * + * Author: Thomas Gleixner + * + * Copyright 2006 IBM + * Copyright 2008 PIKA Technologies + * Sean MacLennan + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NDFC_MAX_CS 4 + +struct ndfc_controller { + struct platform_device *ofdev; + void __iomem *ndfcbase; + struct nand_chip chip; + int chip_select; + struct nand_controller ndfc_control; +}; + +static struct ndfc_controller ndfc_ctrl[NDFC_MAX_CS]; + +static void ndfc_select_chip(struct nand_chip *nchip, int chip) +{ + uint32_t ccr; + struct ndfc_controller *ndfc = nand_get_controller_data(nchip); + + 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; + iowrite32be(ccr, ndfc->ndfcbase + NDFC_CCR); +} + +static void ndfc_hwcontrol(struct nand_chip *chip, int cmd, unsigned int ctrl) +{ + struct ndfc_controller *ndfc = nand_get_controller_data(chip); + + if (cmd == NAND_CMD_NONE) + return; + + if (ctrl & NAND_CLE) + writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_CMD); + else + writel(cmd & 0xFF, ndfc->ndfcbase + NDFC_ALE); +} + +static int ndfc_ready(struct nand_chip *chip) +{ + struct ndfc_controller *ndfc = nand_get_controller_data(chip); + + return ioread32be(ndfc->ndfcbase + NDFC_STAT) & NDFC_STAT_IS_READY; +} + +static void ndfc_enable_hwecc(struct nand_chip *chip, int mode) +{ + uint32_t ccr; + struct ndfc_controller *ndfc = nand_get_controller_data(chip); + + ccr = ioread32be(ndfc->ndfcbase + NDFC_CCR); + ccr |= NDFC_CCR_RESET_ECC; + iowrite32be(ccr, ndfc->ndfcbase + NDFC_CCR); + wmb(); +} + +static int ndfc_calculate_ecc(struct nand_chip *chip, + const u_char *dat, u_char *ecc_code) +{ + struct ndfc_controller *ndfc = nand_get_controller_data(chip); + uint32_t ecc; + uint8_t *p = (uint8_t *)&ecc; + + wmb(); + ecc = ioread32be(ndfc->ndfcbase + NDFC_ECC); + /* The NDFC uses Smart Media (SMC) bytes order */ + ecc_code[0] = p[1]; + ecc_code[1] = p[2]; + ecc_code[2] = p[3]; + + return 0; +} + +/* + * Speedups for buffer read/write/verify + * + * NDFC allows 32bit read/write of data. So we can speed up the buffer + * functions. No further checking, as nand_base will always read/write + * page aligned. + */ +static void ndfc_read_buf(struct nand_chip *chip, uint8_t *buf, int len) +{ + struct ndfc_controller *ndfc = nand_get_controller_data(chip); + uint32_t *p = (uint32_t *) buf; + + for(;len > 0; len -= 4) + *p++ = ioread32be(ndfc->ndfcbase + NDFC_DATA); +} + +static void ndfc_write_buf(struct nand_chip *chip, const uint8_t *buf, int len) +{ + struct ndfc_controller *ndfc = nand_get_controller_data(chip); + uint32_t *p = (uint32_t *) buf; + + for(;len > 0; len -= 4) + iowrite32be(*p++, ndfc->ndfcbase + NDFC_DATA); +} + +/* + * Initialize chip structure + */ +static int ndfc_chip_init(struct ndfc_controller *ndfc, + struct device_node *node) +{ + struct device_node *flash_np; + struct nand_chip *chip = &ndfc->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + chip->legacy.IO_ADDR_R = ndfc->ndfcbase + NDFC_DATA; + chip->legacy.IO_ADDR_W = ndfc->ndfcbase + NDFC_DATA; + chip->legacy.cmd_ctrl = ndfc_hwcontrol; + chip->legacy.dev_ready = ndfc_ready; + chip->legacy.select_chip = ndfc_select_chip; + chip->legacy.chip_delay = 50; + chip->controller = &ndfc->ndfc_control; + chip->legacy.read_buf = ndfc_read_buf; + chip->legacy.write_buf = ndfc_write_buf; + chip->ecc.correct = rawnand_sw_hamming_correct; + chip->ecc.hwctl = ndfc_enable_hwecc; + chip->ecc.calculate = ndfc_calculate_ecc; + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + chip->ecc.size = 256; + chip->ecc.bytes = 3; + chip->ecc.strength = 1; + nand_set_controller_data(chip, ndfc); + + mtd->dev.parent = &ndfc->ofdev->dev; + + flash_np = of_get_next_child(node, NULL); + if (!flash_np) + return -ENODEV; + nand_set_flash_node(chip, flash_np); + + mtd->name = kasprintf(GFP_KERNEL, "%s.%pOFn", dev_name(&ndfc->ofdev->dev), + flash_np); + if (!mtd->name) { + ret = -ENOMEM; + goto err; + } + + ret = nand_scan(chip, 1); + if (ret) + goto err; + + ret = mtd_device_register(mtd, NULL, 0); + +err: + of_node_put(flash_np); + if (ret) + kfree(mtd->name); + return ret; +} + +static int ndfc_probe(struct platform_device *ofdev) +{ + struct ndfc_controller *ndfc; + const __be32 *reg; + u32 ccr; + u32 cs; + int err, len = 0; + + /* Read the reg property to get the chip select */ + reg = of_get_property(ofdev->dev.of_node, "reg", &len); + if (reg == NULL || len != 12) { + dev_err(&ofdev->dev, "unable read reg property (%d)\n", len); + return -ENOENT; + } + + cs = be32_to_cpu(reg[0]); + if (cs >= NDFC_MAX_CS) { + dev_err(&ofdev->dev, "invalid CS number (%d)\n", cs); + return -EINVAL; + } + + ndfc = &ndfc_ctrl[cs]; + ndfc->chip_select = cs; + + nand_controller_init(&ndfc->ndfc_control); + ndfc->ofdev = ofdev; + dev_set_drvdata(&ofdev->dev, ndfc); + + ndfc->ndfcbase = of_iomap(ofdev->dev.of_node, 0); + if (!ndfc->ndfcbase) { + dev_err(&ofdev->dev, "failed to get memory\n"); + return -EIO; + } + + ccr = NDFC_CCR_BS(ndfc->chip_select); + + /* It is ok if ccr does not exist - just default to 0 */ + reg = of_get_property(ofdev->dev.of_node, "ccr", NULL); + if (reg) + ccr |= be32_to_cpup(reg); + + 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); + iowrite32be(be32_to_cpup(reg), ndfc->ndfcbase + offset); + } + + err = ndfc_chip_init(ndfc, ofdev->dev.of_node); + if (err) { + iounmap(ndfc->ndfcbase); + return err; + } + + return 0; +} + +static void ndfc_remove(struct platform_device *ofdev) +{ + struct ndfc_controller *ndfc = dev_get_drvdata(&ofdev->dev); + struct nand_chip *chip = &ndfc->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + ret = mtd_device_unregister(mtd); + WARN_ON(ret); + nand_cleanup(chip); + kfree(mtd->name); +} + +static const struct of_device_id ndfc_match[] = { + { .compatible = "ibm,ndfc", }, + {} +}; +MODULE_DEVICE_TABLE(of, ndfc_match); + +static struct platform_driver ndfc_driver = { + .driver = { + .name = "ndfc", + .of_match_table = ndfc_match, + }, + .probe = ndfc_probe, + .remove = ndfc_remove, +}; + +module_platform_driver(ndfc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Thomas Gleixner "); +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 new file mode 100644 index 000000000..1a285cd8f --- /dev/null +++ b/drivers/mtd/nand/raw/nuvoton-ma35d1-nand-controller.c @@ -0,0 +1,1029 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Nuvoton Technology Corp. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* NFI Registers */ +#define MA35_NFI_REG_DMACTL 0x400 +#define DMA_EN BIT(0) +#define DMA_RST BIT(1) +#define DMA_BUSY BIT(9) + +#define MA35_NFI_REG_DMASA 0x408 +#define MA35_NFI_REG_GCTL 0x800 +#define GRST BIT(0) +#define NAND_EN BIT(3) + +#define MA35_NFI_REG_NANDCTL 0x8A0 +#define SWRST BIT(0) +#define DMA_R_EN BIT(1) +#define DMA_W_EN BIT(2) +#define ECC_CHK BIT(7) +#define PROT3BEN BIT(8) +#define PSIZE_2K BIT(16) +#define PSIZE_4K BIT(17) +#define PSIZE_8K GENMASK(17, 16) +#define PSIZE_MASK GENMASK(17, 16) +#define BCH_T24 BIT(18) +#define BCH_T8 BIT(20) +#define BCH_T12 BIT(21) +#define BCH_NONE (0x0) +#define BCH_MASK GENMASK(22, 18) +#define ECC_EN BIT(23) +#define DISABLE_CS0 BIT(25) + +#define MA35_NFI_REG_NANDINTEN 0x8A8 +#define MA35_NFI_REG_NANDINTSTS 0x8AC +#define INT_DMA BIT(0) +#define INT_ECC BIT(2) +#define INT_RB0 BIT(10) + +#define MA35_NFI_REG_NANDCMD 0x8B0 +#define MA35_NFI_REG_NANDADDR 0x8B4 +#define ENDADDR BIT(31) + +#define MA35_NFI_REG_NANDDATA 0x8B8 +#define MA35_NFI_REG_NANDRACTL 0x8BC +#define MA35_NFI_REG_NANDECTL 0x8C0 +#define ENABLE_WP 0x0 +#define DISABLE_WP BIT(0) + +#define MA35_NFI_REG_NANDECCES0 0x8D0 +#define ECC_STATUS_MASK GENMASK(1, 0) +#define ECC_ERR_CNT_MASK GENMASK(4, 0) + +#define MA35_NFI_REG_NANDECCEA0 0x900 +#define MA35_NFI_REG_NANDECCED0 0x960 +#define MA35_NFI_REG_NANDRA0 0xA00 + +/* Define for the BCH hardware ECC engine */ +/* define the total padding bytes for 512/1024 data segment */ +#define MA35_BCH_PADDING_512 32 +#define MA35_BCH_PADDING_1024 64 +/* define the BCH parity code length for 512 bytes data pattern */ +#define MA35_PARITY_BCH8 15 +#define MA35_PARITY_BCH12 23 +/* define the BCH parity code length for 1024 bytes data pattern */ +#define MA35_PARITY_BCH24 45 + +#define MA35_MAX_NSELS (2) +#define PREFIX_RA_IS_EMPTY(reg) FIELD_GET(GENMASK(31, 16), (reg)) + +struct ma35_nand_chip { + struct list_head node; + struct nand_chip chip; + + u32 eccstatus; + u8 nsels; + u8 sels[] __counted_by(nsels); +}; + +struct ma35_nand_info { + struct nand_controller controller; + struct device *dev; + void __iomem *regs; + int irq; + struct clk *clk; + struct completion complete; + struct list_head chips; + + u8 *buffer; + unsigned long assigned_cs; +}; + +static inline struct ma35_nand_chip *to_ma35_nand(struct nand_chip *chip) +{ + return container_of(chip, struct ma35_nand_chip, chip); +} + +static int ma35_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oob_region) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section) + return -ERANGE; + + oob_region->length = chip->ecc.total; + oob_region->offset = mtd->oobsize - oob_region->length; + + return 0; +} + +static int ma35_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oob_region) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section) + return -ERANGE; + + oob_region->length = mtd->oobsize - chip->ecc.total - 2; + oob_region->offset = 2; + + return 0; +} + +static const struct mtd_ooblayout_ops ma35_ooblayout_ops = { + .free = ma35_ooblayout_free, + .ecc = ma35_ooblayout_ecc, +}; + +static inline void ma35_clear_spare(struct nand_chip *chip, int size) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + int i; + + for (i = 0; i < size / 4; i++) + writel(0xff, nand->regs + MA35_NFI_REG_NANDRA0); +} + +static inline void read_remaining_bytes(struct ma35_nand_info *nand, u32 *buf, + u32 offset, int size, int swap) +{ + u32 value = readl(nand->regs + MA35_NFI_REG_NANDRA0 + offset); + u8 *ptr = (u8 *)buf; + int i, shift; + + for (i = 0; i < size; i++) { + shift = (swap ? 3 - i : i) * 8; + ptr[i] = (value >> shift) & 0xff; + } +} + +static inline void ma35_read_spare(struct nand_chip *chip, int size, u32 *buf, u32 offset) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + u32 off = round_down(offset, 4); + int len = offset % 4; + int i; + + if (len) { + read_remaining_bytes(nand, buf, off, 4 - len, 1); + off += 4; + size -= (4 - len); + } + + for (i = 0; i < size / 4; i++) + *buf++ = readl(nand->regs + MA35_NFI_REG_NANDRA0 + off + (i * 4)); + + read_remaining_bytes(nand, buf, off + (size & ~3), size % 4, 0); +} + +static inline void ma35_write_spare(struct nand_chip *chip, int size, u32 *buf) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + u32 value; + int i, j; + u8 *ptr; + + for (i = 0, j = 0; i < size / 4; i++, j += 4) + writel(*buf++, nand->regs + MA35_NFI_REG_NANDRA0 + j); + + ptr = (u8 *)buf; + switch (size % 4) { + case 1: + writel(*ptr, nand->regs + MA35_NFI_REG_NANDRA0 + j); + break; + case 2: + value = *ptr | (*(ptr + 1) << 8); + writel(value, nand->regs + MA35_NFI_REG_NANDRA0 + j); + break; + case 3: + value = *ptr | (*(ptr + 1) << 8) | (*(ptr + 2) << 16); + writel(value, nand->regs + MA35_NFI_REG_NANDRA0 + j); + break; + default: + break; + } +} + +static void ma35_nand_target_enable(struct nand_chip *chip, unsigned int cs) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + u32 reg; + + switch (cs) { + case 0: + reg = readl(nand->regs + MA35_NFI_REG_NANDCTL); + writel(reg & ~DISABLE_CS0, nand->regs + MA35_NFI_REG_NANDCTL); + + reg = readl(nand->regs + MA35_NFI_REG_NANDINTSTS); + reg |= INT_RB0; + writel(reg, nand->regs + MA35_NFI_REG_NANDINTSTS); + break; + default: + break; + } +} + +static int ma35_nand_hwecc_init(struct nand_chip *chip, struct ma35_nand_info *nand) +{ + struct ma35_nand_chip *nvtnand = to_ma35_nand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct device *dev = mtd->dev.parent; + u32 reg; + + nand->buffer = devm_kzalloc(dev, mtd->writesize, GFP_KERNEL); + if (!nand->buffer) + return -ENOMEM; + + /* Redundant area size */ + writel(mtd->oobsize, nand->regs + MA35_NFI_REG_NANDRACTL); + + /* Protect redundant 3 bytes and disable ECC engine */ + reg = readl(nand->regs + MA35_NFI_REG_NANDCTL); + reg |= (PROT3BEN | ECC_CHK); + reg &= ~ECC_EN; + + if (chip->ecc.strength != 0) { + chip->ecc.steps = mtd->writesize / chip->ecc.size; + nvtnand->eccstatus = (chip->ecc.steps < 4) ? 1 : chip->ecc.steps / 4; + /* Set BCH algorithm */ + reg &= ~BCH_MASK; + switch (chip->ecc.strength) { + case 8: + chip->ecc.total = chip->ecc.steps * MA35_PARITY_BCH8; + reg |= BCH_T8; + break; + case 12: + chip->ecc.total = chip->ecc.steps * MA35_PARITY_BCH12; + reg |= BCH_T12; + break; + case 24: + chip->ecc.total = chip->ecc.steps * MA35_PARITY_BCH24; + reg |= BCH_T24; + break; + default: + dev_err(nand->dev, "ECC strength unsupported\n"); + return -EINVAL; + } + + chip->ecc.bytes = chip->ecc.total / chip->ecc.steps; + } + writel(reg, nand->regs + MA35_NFI_REG_NANDCTL); + return 0; +} + +/* Correct data by BCH alrogithm */ +static void ma35_nfi_correct(struct nand_chip *chip, u8 index, + u8 err_cnt, u8 *addr) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + u32 temp_data[24], temp_addr[24]; + u32 padding_len, parity_len; + u32 value, offset, remain; + u32 err_data[6]; + u8 i, j; + + /* Configurations */ + if (chip->ecc.strength <= 8) { + parity_len = MA35_PARITY_BCH8; + padding_len = MA35_BCH_PADDING_512; + } else if (chip->ecc.strength <= 12) { + parity_len = MA35_PARITY_BCH12; + padding_len = MA35_BCH_PADDING_512; + } else if (chip->ecc.strength <= 24) { + parity_len = MA35_PARITY_BCH24; + padding_len = MA35_BCH_PADDING_1024; + } else { + dev_err(nand->dev, "Invalid BCH_TSEL = 0x%lx\n", + readl(nand->regs + MA35_NFI_REG_NANDCTL) & BCH_MASK); + return; + } + + /* + * got valid BCH_ECC_DATAx and parse them to temp_data[] + * got the valid register number of BCH_ECC_DATAx since + * one register include 4 error bytes + */ + j = (err_cnt + 3) / 4; + j = (j > 6) ? 6 : j; + for (i = 0; i < j; i++) + err_data[i] = readl(nand->regs + MA35_NFI_REG_NANDECCED0 + i * 4); + + for (i = 0; i < j; i++) { + temp_data[i * 4 + 0] = err_data[i] & 0xff; + temp_data[i * 4 + 1] = (err_data[i] >> 8) & 0xff; + temp_data[i * 4 + 2] = (err_data[i] >> 16) & 0xff; + temp_data[i * 4 + 3] = (err_data[i] >> 24) & 0xff; + } + + /* + * got valid REG_BCH_ECC_ADDRx and parse them to temp_addr[] + * got the valid register number of REG_BCH_ECC_ADDRx since + * one register include 2 error addresses + */ + j = (err_cnt + 1) / 2; + j = (j > 12) ? 12 : j; + for (i = 0; i < j; i++) { + temp_addr[i * 2 + 0] = readl(nand->regs + MA35_NFI_REG_NANDECCEA0 + i * 4) + & 0x07ff; + temp_addr[i * 2 + 1] = (readl(nand->regs + MA35_NFI_REG_NANDECCEA0 + i * 4) + >> 16) & 0x07ff; + } + + /* pointer to begin address of field that with data error */ + addr += index * chip->ecc.size; + + /* correct each error bytes */ + for (i = 0; i < err_cnt; i++) { + u32 corrected_index = temp_addr[i]; + + if (corrected_index < chip->ecc.size) { + /* for wrong data in field */ + *(addr + corrected_index) ^= temp_data[i]; + } else if (corrected_index < (chip->ecc.size + 3)) { + /* for wrong first-3-bytes in redundancy area */ + corrected_index -= chip->ecc.size; + temp_addr[i] += (parity_len * index); /* field offset */ + + value = readl(nand->regs + MA35_NFI_REG_NANDRA0); + value ^= temp_data[i] << (8 * corrected_index); + writel(value, nand->regs + MA35_NFI_REG_NANDRA0); + } else { + /* + * for wrong parity code in redundancy area + * ERR_ADDRx = [data in field] + [3 bytes] + [xx] + [parity code] + * |<-- padding bytes -->| + * The ERR_ADDRx for last parity code always = field size + padding size. + * The first parity code = field size + padding size - parity code length. + * For example, for BCH T12, the first parity code = 512 + 32 - 23 = 521. + * That is, error byte address offset within field is + */ + corrected_index -= (chip->ecc.size + padding_len - parity_len); + + /* + * final address = first parity code of first field + + * offset of fields + + * offset within field + */ + offset = (readl(nand->regs + MA35_NFI_REG_NANDRACTL) & 0x1ff) - + (parity_len * chip->ecc.steps) + + (parity_len * index) + corrected_index; + + remain = offset % 4; + value = readl(nand->regs + MA35_NFI_REG_NANDRA0 + offset - remain); + value ^= temp_data[i] << (8 * remain); + writel(value, nand->regs + MA35_NFI_REG_NANDRA0 + offset - remain); + } + } +} + +static int ma35_nfi_ecc_check(struct nand_chip *chip, u8 *addr) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + struct ma35_nand_chip *nvtnand = to_ma35_nand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int maxbitflips = 0; + int cnt = 0; + u32 status; + int i, j; + + for (j = 0; j < nvtnand->eccstatus; j++) { + status = readl(nand->regs + MA35_NFI_REG_NANDECCES0 + j * 4); + if (!status) + continue; + + for (i = 0; i < 4; i++) { + if ((status & ECC_STATUS_MASK) == 0x01) { + /* Correctable error */ + cnt = (status >> 2) & ECC_ERR_CNT_MASK; + ma35_nfi_correct(chip, j * 4 + i, cnt, addr); + maxbitflips = max_t(u32, maxbitflips, cnt); + mtd->ecc_stats.corrected += cnt; + } else { + /* Uncorrectable error */ + mtd->ecc_stats.failed++; + dev_err(nand->dev, "uncorrectable error! 0x%4x\n", status); + return -EBADMSG; + } + status >>= 8; + } + } + return maxbitflips; +} + +static void ma35_nand_dmac_init(struct ma35_nand_info *nand) +{ + /* DMAC reset and enable */ + writel(DMA_RST | DMA_EN, nand->regs + MA35_NFI_REG_DMACTL); + writel(DMA_EN, nand->regs + MA35_NFI_REG_DMACTL); + + /* Clear DMA finished flag and enable */ + writel(INT_DMA | INT_ECC, nand->regs + MA35_NFI_REG_NANDINTSTS); + writel(INT_DMA, nand->regs + MA35_NFI_REG_NANDINTEN); +} + +static int ma35_nand_do_write(struct nand_chip *chip, const u8 *addr, u32 len) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + dma_addr_t dma_addr; + int ret = 0, i; + u32 reg; + + if (len != mtd->writesize) { + for (i = 0; i < len; i++) + writel(addr[i], nand->regs + MA35_NFI_REG_NANDDATA); + return 0; + } + + ma35_nand_dmac_init(nand); + + /* To mark this page as dirty. */ + reg = readl(nand->regs + MA35_NFI_REG_NANDRA0); + if (reg & 0xffff0000) + writel(reg & 0xffff, nand->regs + MA35_NFI_REG_NANDRA0); + + dma_addr = dma_map_single(nand->dev, (void *)addr, len, DMA_TO_DEVICE); + ret = dma_mapping_error(nand->dev, dma_addr); + if (ret) { + dev_err(nand->dev, "dma mapping error\n"); + return -EINVAL; + } + dma_sync_single_for_device(nand->dev, dma_addr, len, DMA_TO_DEVICE); + + reinit_completion(&nand->complete); + writel(dma_addr, nand->regs + MA35_NFI_REG_DMASA); + writel(readl(nand->regs + MA35_NFI_REG_NANDCTL) | DMA_W_EN, + nand->regs + MA35_NFI_REG_NANDCTL); + ret = wait_for_completion_timeout(&nand->complete, msecs_to_jiffies(1000)); + if (!ret) { + dev_err(nand->dev, "write timeout\n"); + ret = -ETIMEDOUT; + } + + dma_unmap_single(nand->dev, dma_addr, len, DMA_TO_DEVICE); + + return ret; +} + +static int ma35_nand_do_read(struct nand_chip *chip, u8 *addr, u32 len) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int ret = 0, cnt = 0, i; + dma_addr_t dma_addr; + u32 reg; + + if (len != mtd->writesize) { + for (i = 0; i < len; i++) + addr[i] = readb(nand->regs + MA35_NFI_REG_NANDDATA); + return 0; + } + + ma35_nand_dmac_init(nand); + + /* Setup and start DMA using dma_addr */ + dma_addr = dma_map_single(nand->dev, (void *)addr, len, DMA_FROM_DEVICE); + ret = dma_mapping_error(nand->dev, dma_addr); + if (ret) { + dev_err(nand->dev, "dma mapping error\n"); + return -EINVAL; + } + + reinit_completion(&nand->complete); + writel(dma_addr, nand->regs + MA35_NFI_REG_DMASA); + writel(readl(nand->regs + MA35_NFI_REG_NANDCTL) | DMA_R_EN, + nand->regs + MA35_NFI_REG_NANDCTL); + ret = wait_for_completion_timeout(&nand->complete, msecs_to_jiffies(1000)); + if (!ret) { + dev_err(nand->dev, "read timeout\n"); + ret = -ETIMEDOUT; + } + + dma_unmap_single(nand->dev, dma_addr, len, DMA_FROM_DEVICE); + + reg = readl(nand->regs + MA35_NFI_REG_NANDINTSTS); + if (reg & INT_ECC) { + cnt = ma35_nfi_ecc_check(chip, addr); + if (cnt < 0) { + writel(DMA_RST | DMA_EN, nand->regs + MA35_NFI_REG_DMACTL); + writel(readl(nand->regs + MA35_NFI_REG_NANDCTL) | SWRST, + nand->regs + MA35_NFI_REG_NANDCTL); + } + writel(INT_ECC, nand->regs + MA35_NFI_REG_NANDINTSTS); + } + + ret = ret < 0 ? ret : cnt; + return ret; +} + +static int ma35_nand_format_subpage(struct nand_chip *chip, u32 offset, + u32 len, const u8 *buf) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + u32 page_off = round_down(offset, chip->ecc.size); + u32 end = DIV_ROUND_UP(page_off + len, chip->ecc.size); + u32 start = page_off / chip->ecc.size; + u32 reg; + int i; + + reg = readl(nand->regs + MA35_NFI_REG_NANDRACTL) | 0xffff0000; + memset(nand->buffer, 0xff, mtd->writesize); + for (i = start; i < end; i++) { + memcpy(nand->buffer + i * chip->ecc.size, + buf + i * chip->ecc.size, chip->ecc.size); + reg &= ~(1 << (i + 16)); + } + writel(reg, nand->regs + MA35_NFI_REG_NANDRACTL); + + return 0; +} + +static int ma35_nand_write_subpage_hwecc(struct nand_chip *chip, u32 offset, + u32 data_len, const u8 *buf, + int oob_required, int page) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + u32 reg, oobpoi, index; + int i; + + /* Enable HW ECC engine */ + reg = readl(nand->regs + MA35_NFI_REG_NANDCTL); + writel(reg | ECC_EN, nand->regs + MA35_NFI_REG_NANDCTL); + + ma35_nand_target_enable(chip, chip->cur_cs); + + ma35_clear_spare(chip, mtd->oobsize); + ma35_write_spare(chip, mtd->oobsize - chip->ecc.total, + (u32 *)chip->oob_poi); + + ma35_nand_format_subpage(chip, offset, data_len, buf); + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + ma35_nand_do_write(chip, nand->buffer, mtd->writesize); + nand_prog_page_end_op(chip); + + oobpoi = mtd->oobsize - chip->ecc.total; + reg = readl(nand->regs + MA35_NFI_REG_NANDRACTL); + for (i = 0; i < chip->ecc.steps; i++) { + index = i * chip->ecc.bytes; + if (!(reg & (1 << (i + 16)))) { + ma35_read_spare(chip, chip->ecc.bytes, + (u32 *)(chip->oob_poi + oobpoi + index), + oobpoi + index); + } + } + + writel(mtd->oobsize, nand->regs + MA35_NFI_REG_NANDRACTL); + /* Disable HW ECC engine */ + reg = readl(nand->regs + MA35_NFI_REG_NANDCTL); + writel(reg & ~ECC_EN, nand->regs + MA35_NFI_REG_NANDCTL); + + return 0; +} + +static int ma35_nand_write_page_hwecc(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + u32 reg; + + /* Enable HW ECC engine */ + reg = readl(nand->regs + MA35_NFI_REG_NANDCTL); + writel(reg | ECC_EN, nand->regs + MA35_NFI_REG_NANDCTL); + + ma35_nand_target_enable(chip, chip->cur_cs); + + ma35_clear_spare(chip, mtd->oobsize); + ma35_write_spare(chip, mtd->oobsize - chip->ecc.total, + (u32 *)chip->oob_poi); + + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + ma35_nand_do_write(chip, buf, mtd->writesize); + nand_prog_page_end_op(chip); + + ma35_read_spare(chip, chip->ecc.total, + (u32 *)(chip->oob_poi + (mtd->oobsize - chip->ecc.total)), + mtd->oobsize - chip->ecc.total); + + /* Disable HW ECC engine */ + writel(reg & ~ECC_EN, nand->regs + MA35_NFI_REG_NANDCTL); + + return 0; +} + +static int ma35_nand_read_subpage_hwecc(struct nand_chip *chip, u32 offset, + u32 data_len, u8 *buf, int page) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int bitflips = 0; + u32 reg; + + /* Enable HW ECC engine */ + reg = readl(nand->regs + MA35_NFI_REG_NANDCTL); + writel(reg | ECC_EN, nand->regs + MA35_NFI_REG_NANDCTL); + + ma35_nand_target_enable(chip, chip->cur_cs); + nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize); + ma35_write_spare(chip, mtd->oobsize, (u32 *)chip->oob_poi); + + reg = readl(nand->regs + MA35_NFI_REG_NANDRA0); + if (PREFIX_RA_IS_EMPTY(reg)) { + memset((void *)buf, 0xff, mtd->writesize); + } else { + nand_read_page_op(chip, page, offset, NULL, 0); + bitflips = ma35_nand_do_read(chip, buf + offset, data_len); + ma35_read_spare(chip, mtd->oobsize, (u32 *)chip->oob_poi, 0); + } + + /* Disable HW ECC engine */ + reg = readl(nand->regs + MA35_NFI_REG_NANDCTL); + writel(reg & ~ECC_EN, nand->regs + MA35_NFI_REG_NANDCTL); + + return bitflips; +} + +static int ma35_nand_read_page_hwecc(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int bitflips = 0; + u32 reg; + + /* Enable HW ECC engine */ + reg = readl(nand->regs + MA35_NFI_REG_NANDCTL); + writel(reg | ECC_EN, nand->regs + MA35_NFI_REG_NANDCTL); + + ma35_nand_target_enable(chip, chip->cur_cs); + nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize); + ma35_write_spare(chip, mtd->oobsize, (u32 *)chip->oob_poi); + + reg = readl(nand->regs + MA35_NFI_REG_NANDRA0); + if (PREFIX_RA_IS_EMPTY(reg)) { + memset((void *)buf, 0xff, mtd->writesize); + } else { + nand_read_page_op(chip, page, 0, NULL, 0); + bitflips = ma35_nand_do_read(chip, buf, mtd->writesize); + ma35_read_spare(chip, mtd->oobsize, (u32 *)chip->oob_poi, 0); + } + + /* Disable HW ECC engine */ + reg = readl(nand->regs + MA35_NFI_REG_NANDCTL); + writel(reg & ~ECC_EN, nand->regs + MA35_NFI_REG_NANDCTL); + + return bitflips; +} + +static int ma35_nand_read_oob_hwecc(struct nand_chip *chip, int page) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + u32 reg; + + ma35_nand_target_enable(chip, chip->cur_cs); + nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize); + + /* copy OOB data to controller redundant area for page read */ + ma35_write_spare(chip, mtd->oobsize, (u32 *)chip->oob_poi); + + reg = readl(nand->regs + MA35_NFI_REG_NANDRA0); + if (PREFIX_RA_IS_EMPTY(reg)) + memset((void *)chip->oob_poi, 0xff, mtd->oobsize); + + return 0; +} + +static inline void ma35_hw_init(struct ma35_nand_info *nand) +{ + u32 reg; + + /* Disable flash wp. */ + writel(DISABLE_WP, nand->regs + MA35_NFI_REG_NANDECTL); + + /* resets the internal state machine and counters */ + reg = readl(nand->regs + MA35_NFI_REG_NANDCTL); + reg |= SWRST; + writel(reg, nand->regs + MA35_NFI_REG_NANDCTL); +} + +static irqreturn_t ma35_nand_irq(int irq, void *id) +{ + struct ma35_nand_info *nand = (struct ma35_nand_info *)id; + u32 isr; + + isr = readl(nand->regs + MA35_NFI_REG_NANDINTSTS); + if (isr & INT_DMA) { + writel(INT_DMA, nand->regs + MA35_NFI_REG_NANDINTSTS); + complete(&nand->complete); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int ma35_nand_attach_chip(struct nand_chip *chip) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct device *dev = mtd->dev.parent; + u32 reg; + + if (chip->options & NAND_BUSWIDTH_16) { + dev_err(dev, "16 bits bus width not supported"); + return -EINVAL; + } + + reg = readl(nand->regs + MA35_NFI_REG_NANDCTL) & (~PSIZE_MASK); + switch (mtd->writesize) { + case SZ_2K: + writel(reg | PSIZE_2K, nand->regs + MA35_NFI_REG_NANDCTL); + break; + case SZ_4K: + writel(reg | PSIZE_4K, nand->regs + MA35_NFI_REG_NANDCTL); + break; + case SZ_8K: + writel(reg | PSIZE_8K, nand->regs + MA35_NFI_REG_NANDCTL); + break; + default: + dev_err(dev, "Unsupported page size"); + return -EINVAL; + } + + switch (chip->ecc.engine_type) { + case NAND_ECC_ENGINE_TYPE_ON_HOST: + /* Do not store BBT bits in the OOB section as it is not protected */ + if (chip->bbt_options & NAND_BBT_USE_FLASH) + chip->bbt_options |= NAND_BBT_NO_OOB; + chip->options |= NAND_USES_DMA | NAND_SUBPAGE_READ; + chip->ecc.write_subpage = ma35_nand_write_subpage_hwecc; + chip->ecc.write_page = ma35_nand_write_page_hwecc; + chip->ecc.read_subpage = ma35_nand_read_subpage_hwecc; + chip->ecc.read_page = ma35_nand_read_page_hwecc; + chip->ecc.read_oob = ma35_nand_read_oob_hwecc; + return ma35_nand_hwecc_init(chip, nand); + case NAND_ECC_ENGINE_TYPE_NONE: + case NAND_ECC_ENGINE_TYPE_SOFT: + case NAND_ECC_ENGINE_TYPE_ON_DIE: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ma35_nfc_exec_instr(struct nand_chip *chip, + const struct nand_op_instr *instr) +{ + struct ma35_nand_info *nand = nand_get_controller_data(chip); + unsigned int i; + int ret = 0; + u32 status; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + writel(instr->ctx.cmd.opcode, nand->regs + MA35_NFI_REG_NANDCMD); + break; + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) { + if (i == (instr->ctx.addr.naddrs - 1)) + writel(instr->ctx.addr.addrs[i] | ENDADDR, + nand->regs + MA35_NFI_REG_NANDADDR); + else + writel(instr->ctx.addr.addrs[i], + nand->regs + MA35_NFI_REG_NANDADDR); + } + break; + case NAND_OP_DATA_IN_INSTR: + ret = ma35_nand_do_read(chip, instr->ctx.data.buf.in, instr->ctx.data.len); + break; + case NAND_OP_DATA_OUT_INSTR: + ret = ma35_nand_do_write(chip, instr->ctx.data.buf.out, instr->ctx.data.len); + break; + case NAND_OP_WAITRDY_INSTR: + return readl_poll_timeout(nand->regs + MA35_NFI_REG_NANDINTSTS, status, + status & INT_RB0, 20, + instr->ctx.waitrdy.timeout_ms * MSEC_PER_SEC); + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int ma35_nfc_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + int ret = 0; + u32 i; + + if (check_only) + return 0; + + ma35_nand_target_enable(chip, op->cs); + + for (i = 0; i < op->ninstrs; i++) { + ret = ma35_nfc_exec_instr(chip, &op->instrs[i]); + if (ret) + break; + } + + return ret; +} + +static const struct nand_controller_ops ma35_nfc_ops = { + .attach_chip = ma35_nand_attach_chip, + .exec_op = ma35_nfc_exec_op, +}; + +static int ma35_nand_chip_init(struct device *dev, struct ma35_nand_info *nand, + struct device_node *np) +{ + struct ma35_nand_chip *nvtnand; + struct nand_chip *chip; + struct mtd_info *mtd; + int nsels; + int ret; + u32 cs; + int i; + + nsels = of_property_count_elems_of_size(np, "reg", sizeof(u32)); + if (!nsels || nsels > MA35_MAX_NSELS) { + dev_err(dev, "invalid reg property size %d\n", nsels); + return -EINVAL; + } + + nvtnand = devm_kzalloc(dev, struct_size(nvtnand, sels, nsels), + GFP_KERNEL); + if (!nvtnand) + return -ENOMEM; + + nvtnand->nsels = nsels; + for (i = 0; i < nsels; i++) { + ret = of_property_read_u32_index(np, "reg", i, &cs); + if (ret) { + dev_err(dev, "reg property failure : %d\n", ret); + return ret; + } + + if (cs >= MA35_MAX_NSELS) { + dev_err(dev, "invalid CS: %u\n", cs); + return -EINVAL; + } + + if (test_and_set_bit(cs, &nand->assigned_cs)) { + dev_err(dev, "CS %u already assigned\n", cs); + return -EINVAL; + } + + nvtnand->sels[i] = cs; + } + + chip = &nvtnand->chip; + chip->controller = &nand->controller; + + nand_set_flash_node(chip, np); + nand_set_controller_data(chip, nand); + + mtd = nand_to_mtd(chip); + mtd->owner = THIS_MODULE; + mtd->dev.parent = dev; + + mtd_set_ooblayout(mtd, &ma35_ooblayout_ops); + ret = nand_scan(chip, nsels); + if (ret) + return ret; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + nand_cleanup(chip); + return ret; + } + + list_add_tail(&nvtnand->node, &nand->chips); + + return 0; +} + +static void ma35_chips_cleanup(struct ma35_nand_info *nand) +{ + struct ma35_nand_chip *nvtnand, *tmp; + struct nand_chip *chip; + int ret; + + list_for_each_entry_safe(nvtnand, tmp, &nand->chips, node) { + chip = &nvtnand->chip; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + list_del(&nvtnand->node); + } +} + +static int ma35_nand_chips_init(struct device *dev, struct ma35_nand_info *nand) +{ + struct device_node *np = dev->of_node; + int ret; + + for_each_child_of_node_scoped(np, nand_np) { + ret = ma35_nand_chip_init(dev, nand, nand_np); + if (ret) { + ma35_chips_cleanup(nand); + return ret; + } + } + return 0; +} + +static int ma35_nand_probe(struct platform_device *pdev) +{ + struct ma35_nand_info *nand; + int ret = 0; + + nand = devm_kzalloc(&pdev->dev, sizeof(*nand), GFP_KERNEL); + if (!nand) + return -ENOMEM; + + nand_controller_init(&nand->controller); + INIT_LIST_HEAD(&nand->chips); + nand->controller.ops = &ma35_nfc_ops; + + init_completion(&nand->complete); + + nand->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(nand->regs)) + return PTR_ERR(nand->regs); + + nand->dev = &pdev->dev; + + nand->clk = devm_clk_get_enabled(&pdev->dev, "nand_gate"); + if (IS_ERR(nand->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(nand->clk), + "failed to find NAND clock\n"); + + nand->irq = platform_get_irq(pdev, 0); + if (nand->irq < 0) + return dev_err_probe(&pdev->dev, nand->irq, + "failed to get platform irq\n"); + + ret = devm_request_irq(&pdev->dev, nand->irq, ma35_nand_irq, + IRQF_TRIGGER_HIGH, "ma35d1-nand-controller", nand); + if (ret) { + dev_err(&pdev->dev, "failed to request NAND irq\n"); + return -ENXIO; + } + + platform_set_drvdata(pdev, nand); + + writel(GRST | NAND_EN, nand->regs + MA35_NFI_REG_GCTL); + ma35_hw_init(nand); + ret = ma35_nand_chips_init(&pdev->dev, nand); + if (ret) { + dev_err(&pdev->dev, "failed to init NAND chips\n"); + clk_disable(nand->clk); + return ret; + } + + return ret; +} + +static void ma35_nand_remove(struct platform_device *pdev) +{ + struct ma35_nand_info *nand = platform_get_drvdata(pdev); + + ma35_chips_cleanup(nand); +} + +static const struct of_device_id ma35_nand_of_match[] = { + { .compatible = "nuvoton,ma35d1-nand-controller" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ma35_nand_of_match); + +static struct platform_driver ma35_nand_driver = { + .driver = { + .name = "ma35d1-nand-controller", + .of_match_table = ma35_nand_of_match, + }, + .probe = ma35_nand_probe, + .remove = ma35_nand_remove, +}; + +module_platform_driver(ma35_nand_driver); + +MODULE_DESCRIPTION("Nuvoton ma35 NAND driver"); +MODULE_AUTHOR("Hui-Ping Chen "); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/raw/omap2.c b/drivers/mtd/nand/raw/omap2.c new file mode 100644 index 000000000..552f36da7 --- /dev/null +++ b/drivers/mtd/nand/raw/omap2.c @@ -0,0 +1,2340 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright © 2004 Texas Instruments, Jian Zhang + * Copyright © 2004 Micron Technology Inc. + * Copyright © 2004 David Brownell + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define DRIVER_NAME "omap2-nand" +#define OMAP_NAND_TIMEOUT_MS 5000 + +#define NAND_Ecc_P1e (1 << 0) +#define NAND_Ecc_P2e (1 << 1) +#define NAND_Ecc_P4e (1 << 2) +#define NAND_Ecc_P8e (1 << 3) +#define NAND_Ecc_P16e (1 << 4) +#define NAND_Ecc_P32e (1 << 5) +#define NAND_Ecc_P64e (1 << 6) +#define NAND_Ecc_P128e (1 << 7) +#define NAND_Ecc_P256e (1 << 8) +#define NAND_Ecc_P512e (1 << 9) +#define NAND_Ecc_P1024e (1 << 10) +#define NAND_Ecc_P2048e (1 << 11) + +#define NAND_Ecc_P1o (1 << 16) +#define NAND_Ecc_P2o (1 << 17) +#define NAND_Ecc_P4o (1 << 18) +#define NAND_Ecc_P8o (1 << 19) +#define NAND_Ecc_P16o (1 << 20) +#define NAND_Ecc_P32o (1 << 21) +#define NAND_Ecc_P64o (1 << 22) +#define NAND_Ecc_P128o (1 << 23) +#define NAND_Ecc_P256o (1 << 24) +#define NAND_Ecc_P512o (1 << 25) +#define NAND_Ecc_P1024o (1 << 26) +#define NAND_Ecc_P2048o (1 << 27) + +#define TF(value) (value ? 1 : 0) + +#define P2048e(a) (TF(a & NAND_Ecc_P2048e) << 0) +#define P2048o(a) (TF(a & NAND_Ecc_P2048o) << 1) +#define P1e(a) (TF(a & NAND_Ecc_P1e) << 2) +#define P1o(a) (TF(a & NAND_Ecc_P1o) << 3) +#define P2e(a) (TF(a & NAND_Ecc_P2e) << 4) +#define P2o(a) (TF(a & NAND_Ecc_P2o) << 5) +#define P4e(a) (TF(a & NAND_Ecc_P4e) << 6) +#define P4o(a) (TF(a & NAND_Ecc_P4o) << 7) + +#define P8e(a) (TF(a & NAND_Ecc_P8e) << 0) +#define P8o(a) (TF(a & NAND_Ecc_P8o) << 1) +#define P16e(a) (TF(a & NAND_Ecc_P16e) << 2) +#define P16o(a) (TF(a & NAND_Ecc_P16o) << 3) +#define P32e(a) (TF(a & NAND_Ecc_P32e) << 4) +#define P32o(a) (TF(a & NAND_Ecc_P32o) << 5) +#define P64e(a) (TF(a & NAND_Ecc_P64e) << 6) +#define P64o(a) (TF(a & NAND_Ecc_P64o) << 7) + +#define P128e(a) (TF(a & NAND_Ecc_P128e) << 0) +#define P128o(a) (TF(a & NAND_Ecc_P128o) << 1) +#define P256e(a) (TF(a & NAND_Ecc_P256e) << 2) +#define P256o(a) (TF(a & NAND_Ecc_P256o) << 3) +#define P512e(a) (TF(a & NAND_Ecc_P512e) << 4) +#define P512o(a) (TF(a & NAND_Ecc_P512o) << 5) +#define P1024e(a) (TF(a & NAND_Ecc_P1024e) << 6) +#define P1024o(a) (TF(a & NAND_Ecc_P1024o) << 7) + +#define P8e_s(a) (TF(a & NAND_Ecc_P8e) << 0) +#define P8o_s(a) (TF(a & NAND_Ecc_P8o) << 1) +#define P16e_s(a) (TF(a & NAND_Ecc_P16e) << 2) +#define P16o_s(a) (TF(a & NAND_Ecc_P16o) << 3) +#define P1e_s(a) (TF(a & NAND_Ecc_P1e) << 4) +#define P1o_s(a) (TF(a & NAND_Ecc_P1o) << 5) +#define P2e_s(a) (TF(a & NAND_Ecc_P2e) << 6) +#define P2o_s(a) (TF(a & NAND_Ecc_P2o) << 7) + +#define P4e_s(a) (TF(a & NAND_Ecc_P4e) << 0) +#define P4o_s(a) (TF(a & NAND_Ecc_P4o) << 1) + +#define PREFETCH_CONFIG1_CS_SHIFT 24 +#define ECC_CONFIG_CS_SHIFT 1 +#define CS_MASK 0x7 +#define ENABLE_PREFETCH (0x1 << 7) +#define DMA_MPU_MODE_SHIFT 2 +#define ECCSIZE0_SHIFT 12 +#define ECCSIZE1_SHIFT 22 +#define ECC1RESULTSIZE 0x1 +#define ECCCLEAR 0x100 +#define ECC1 0x1 +#define PREFETCH_FIFOTHRESHOLD_MAX 0x40 +#define PREFETCH_FIFOTHRESHOLD(val) ((val) << 8) +#define PREFETCH_STATUS_COUNT(val) (val & 0x00003fff) +#define PREFETCH_STATUS_FIFO_CNT(val) ((val >> 24) & 0x7F) +#define STATUS_BUFF_EMPTY 0x00000001 + +#define SECTOR_BYTES 512 +/* 4 bit padding to make byte aligned, 56 = 52 + 4 */ +#define BCH4_BIT_PAD 4 + +/* GPMC ecc engine settings for read */ +#define BCH_WRAPMODE_1 1 /* BCH wrap mode 1 */ +#define BCH8R_ECC_SIZE0 0x1a /* ecc_size0 = 26 */ +#define BCH8R_ECC_SIZE1 0x2 /* ecc_size1 = 2 */ +#define BCH4R_ECC_SIZE0 0xd /* ecc_size0 = 13 */ +#define BCH4R_ECC_SIZE1 0x3 /* ecc_size1 = 3 */ + +/* GPMC ecc engine settings for write */ +#define BCH_WRAPMODE_6 6 /* BCH wrap mode 6 */ +#define BCH_ECC_SIZE0 0x0 /* ecc_size0 = 0, no oob protection */ +#define BCH_ECC_SIZE1 0x20 /* ecc_size1 = 32 */ + +#define BBM_LEN 2 + +static u_char bch16_vector[] = {0xf5, 0x24, 0x1c, 0xd0, 0x61, 0xb3, 0xf1, 0x55, + 0x2e, 0x2c, 0x86, 0xa3, 0xed, 0x36, 0x1b, 0x78, + 0x48, 0x76, 0xa9, 0x3b, 0x97, 0xd1, 0x7a, 0x93, + 0x07, 0x0e}; +static u_char bch8_vector[] = {0xf3, 0xdb, 0x14, 0x16, 0x8b, 0xd2, 0xbe, 0xcc, + 0xac, 0x6b, 0xff, 0x99, 0x7b}; +static u_char bch4_vector[] = {0x00, 0x6b, 0x31, 0xdd, 0x41, 0xbc, 0x10}; + +struct omap_nand_info { + struct nand_chip nand; + struct platform_device *pdev; + + int gpmc_cs; + bool dev_ready; + enum nand_io xfer_type; + enum omap_ecc ecc_opt; + struct device_node *elm_of_node; + + unsigned long phys_base; + struct completion comp; + struct dma_chan *dma; + int gpmc_irq_fifo; + int gpmc_irq_count; + enum { + OMAP_NAND_IO_READ = 0, /* read */ + OMAP_NAND_IO_WRITE, /* write */ + } iomode; + u_char *buf; + int buf_len; + /* Interface to GPMC */ + void __iomem *fifo; + struct gpmc_nand_regs reg; + struct gpmc_nand_ops *ops; + bool flash_bbt; + /* fields specific for BCHx_HW ECC scheme */ + struct device *elm_dev; + /* NAND ready gpio */ + struct gpio_desc *ready_gpiod; + unsigned int neccpg; + unsigned int nsteps_per_eccpg; + unsigned int eccpg_size; + unsigned int eccpg_bytes; + void (*data_in)(struct nand_chip *chip, void *buf, + unsigned int len, bool force_8bit); + void (*data_out)(struct nand_chip *chip, + const void *buf, unsigned int len, + bool force_8bit); +}; + +static inline struct omap_nand_info *mtd_to_omap(struct mtd_info *mtd) +{ + return container_of(mtd_to_nand(mtd), struct omap_nand_info, nand); +} + +static void omap_nand_data_in(struct nand_chip *chip, void *buf, + unsigned int len, bool force_8bit); + +static void omap_nand_data_out(struct nand_chip *chip, + const void *buf, unsigned int len, + bool force_8bit); + +/** + * omap_prefetch_enable - configures and starts prefetch transfer + * @cs: cs (chip select) number + * @fifo_th: fifo threshold to be used for read/ write + * @dma_mode: dma mode enable (1) or disable (0) + * @u32_count: number of bytes to be transferred + * @is_write: prefetch read(0) or write post(1) mode + * @info: NAND device structure containing platform data + */ +static int omap_prefetch_enable(int cs, int fifo_th, int dma_mode, + unsigned int u32_count, int is_write, struct omap_nand_info *info) +{ + u32 val; + + if (fifo_th > PREFETCH_FIFOTHRESHOLD_MAX) + return -1; + + if (readl(info->reg.gpmc_prefetch_control)) + return -EBUSY; + + /* Set the amount of bytes to be prefetched */ + writel(u32_count, info->reg.gpmc_prefetch_config2); + + /* Set dma/mpu mode, the prefetch read / post write and + * enable the engine. Set which cs is has requested for. + */ + val = ((cs << PREFETCH_CONFIG1_CS_SHIFT) | + PREFETCH_FIFOTHRESHOLD(fifo_th) | ENABLE_PREFETCH | + (dma_mode << DMA_MPU_MODE_SHIFT) | (is_write & 0x1)); + writel(val, info->reg.gpmc_prefetch_config1); + + /* Start the prefetch engine */ + writel(0x1, info->reg.gpmc_prefetch_control); + + return 0; +} + +/* + * omap_prefetch_reset - disables and stops the prefetch engine + */ +static int omap_prefetch_reset(int cs, struct omap_nand_info *info) +{ + u32 config1; + + /* check if the same module/cs is trying to reset */ + config1 = readl(info->reg.gpmc_prefetch_config1); + if (((config1 >> PREFETCH_CONFIG1_CS_SHIFT) & CS_MASK) != cs) + return -EINVAL; + + /* Stop the PFPW engine */ + writel(0x0, info->reg.gpmc_prefetch_control); + + /* Reset/disable the PFPW engine */ + writel(0x0, info->reg.gpmc_prefetch_config1); + + return 0; +} + +/** + * omap_nand_data_in_pref - NAND data in using prefetch engine + * @chip: NAND chip + * @buf: output buffer where NAND data is placed into + * @len: length of transfer + * @force_8bit: force 8-bit transfers + */ +static void omap_nand_data_in_pref(struct nand_chip *chip, void *buf, + unsigned int len, bool force_8bit) +{ + struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); + uint32_t r_count = 0; + int ret = 0; + u32 *p = (u32 *)buf; + unsigned int pref_len; + + if (force_8bit) { + omap_nand_data_in(chip, buf, len, force_8bit); + return; + } + + /* read 32-bit words using prefetch and remaining bytes normally */ + + /* configure and start prefetch transfer */ + pref_len = len - (len & 3); + ret = omap_prefetch_enable(info->gpmc_cs, + PREFETCH_FIFOTHRESHOLD_MAX, 0x0, pref_len, 0x0, info); + if (ret) { + /* prefetch engine is busy, use CPU copy method */ + omap_nand_data_in(chip, buf, len, false); + } else { + do { + r_count = readl(info->reg.gpmc_prefetch_status); + r_count = PREFETCH_STATUS_FIFO_CNT(r_count); + r_count = r_count >> 2; + ioread32_rep(info->fifo, p, r_count); + p += r_count; + pref_len -= r_count << 2; + } while (pref_len); + /* disable and stop the Prefetch engine */ + omap_prefetch_reset(info->gpmc_cs, info); + /* fetch any remaining bytes */ + if (len & 3) + omap_nand_data_in(chip, p, len & 3, false); + } +} + +/** + * omap_nand_data_out_pref - NAND data out using Write Posting engine + * @chip: NAND chip + * @buf: input buffer that is sent to NAND + * @len: length of transfer + * @force_8bit: force 8-bit transfers + */ +static void omap_nand_data_out_pref(struct nand_chip *chip, + const void *buf, unsigned int len, + bool force_8bit) +{ + struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); + uint32_t w_count = 0; + int i = 0, ret = 0; + u16 *p = (u16 *)buf; + unsigned long tim, limit; + u32 val; + + if (force_8bit) { + omap_nand_data_out(chip, buf, len, force_8bit); + return; + } + + /* take care of subpage writes */ + if (len % 2 != 0) { + writeb(*(u8 *)buf, info->fifo); + p = (u16 *)(buf + 1); + len--; + } + + /* configure and start prefetch transfer */ + ret = omap_prefetch_enable(info->gpmc_cs, + PREFETCH_FIFOTHRESHOLD_MAX, 0x0, len, 0x1, info); + if (ret) { + /* write posting engine is busy, use CPU copy method */ + omap_nand_data_out(chip, buf, len, false); + } else { + while (len) { + w_count = readl(info->reg.gpmc_prefetch_status); + w_count = PREFETCH_STATUS_FIFO_CNT(w_count); + w_count = w_count >> 1; + for (i = 0; (i < w_count) && len; i++, len -= 2) + iowrite16(*p++, info->fifo); + } + /* wait for data to flushed-out before reset the prefetch */ + tim = 0; + limit = (loops_per_jiffy * + msecs_to_jiffies(OMAP_NAND_TIMEOUT_MS)); + do { + cpu_relax(); + val = readl(info->reg.gpmc_prefetch_status); + val = PREFETCH_STATUS_COUNT(val); + } while (val && (tim++ < limit)); + + /* disable and stop the PFPW engine */ + omap_prefetch_reset(info->gpmc_cs, info); + } +} + +/* + * omap_nand_dma_callback: callback on the completion of dma transfer + * @data: pointer to completion data structure + */ +static void omap_nand_dma_callback(void *data) +{ + complete((struct completion *) data); +} + +/* + * omap_nand_dma_transfer: configure and start dma transfer + * @chip: nand chip structure + * @addr: virtual address in RAM of source/destination + * @len: number of data bytes to be transferred + * @is_write: flag for read/write operation + */ +static inline int omap_nand_dma_transfer(struct nand_chip *chip, + const void *addr, unsigned int len, + int is_write) +{ + struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); + struct dma_async_tx_descriptor *tx; + enum dma_data_direction dir = is_write ? DMA_TO_DEVICE : + DMA_FROM_DEVICE; + struct scatterlist sg; + unsigned long tim, limit; + unsigned n; + int ret; + u32 val; + + if (!virt_addr_valid(addr)) + goto out_copy; + + sg_init_one(&sg, addr, len); + n = dma_map_sg(info->dma->device->dev, &sg, 1, dir); + if (n == 0) { + dev_err(&info->pdev->dev, + "Couldn't DMA map a %d byte buffer\n", len); + goto out_copy; + } + + tx = dmaengine_prep_slave_sg(info->dma, &sg, n, + is_write ? DMA_MEM_TO_DEV : DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!tx) + goto out_copy_unmap; + + tx->callback = omap_nand_dma_callback; + tx->callback_param = &info->comp; + dmaengine_submit(tx); + + init_completion(&info->comp); + + /* setup and start DMA using dma_addr */ + dma_async_issue_pending(info->dma); + + /* configure and start prefetch transfer */ + ret = omap_prefetch_enable(info->gpmc_cs, + PREFETCH_FIFOTHRESHOLD_MAX, 0x1, len, is_write, info); + if (ret) + /* PFPW engine is busy, use cpu copy method */ + goto out_copy_unmap; + + wait_for_completion(&info->comp); + tim = 0; + limit = (loops_per_jiffy * msecs_to_jiffies(OMAP_NAND_TIMEOUT_MS)); + + do { + cpu_relax(); + val = readl(info->reg.gpmc_prefetch_status); + val = PREFETCH_STATUS_COUNT(val); + } while (val && (tim++ < limit)); + + /* disable and stop the PFPW engine */ + omap_prefetch_reset(info->gpmc_cs, info); + + dma_unmap_sg(info->dma->device->dev, &sg, 1, dir); + return 0; + +out_copy_unmap: + dma_unmap_sg(info->dma->device->dev, &sg, 1, dir); +out_copy: + is_write == 0 ? omap_nand_data_in(chip, (void *)addr, len, false) + : omap_nand_data_out(chip, addr, len, false); + + return 0; +} + +/** + * omap_nand_data_in_dma_pref - NAND data in using DMA and Prefetch + * @chip: NAND chip + * @buf: output buffer where NAND data is placed into + * @len: length of transfer + * @force_8bit: force 8-bit transfers + */ +static void omap_nand_data_in_dma_pref(struct nand_chip *chip, void *buf, + unsigned int len, bool force_8bit) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (force_8bit) { + omap_nand_data_in(chip, buf, len, force_8bit); + return; + } + + if (len <= mtd->oobsize) + omap_nand_data_in_pref(chip, buf, len, false); + else + /* start transfer in DMA mode */ + omap_nand_dma_transfer(chip, buf, len, 0x0); +} + +/** + * omap_nand_data_out_dma_pref - NAND data out using DMA and write posting + * @chip: NAND chip + * @buf: input buffer that is sent to NAND + * @len: length of transfer + * @force_8bit: force 8-bit transfers + */ +static void omap_nand_data_out_dma_pref(struct nand_chip *chip, + const void *buf, unsigned int len, + bool force_8bit) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (force_8bit) { + omap_nand_data_out(chip, buf, len, force_8bit); + return; + } + + if (len <= mtd->oobsize) + omap_nand_data_out_pref(chip, buf, len, false); + else + /* start transfer in DMA mode */ + omap_nand_dma_transfer(chip, buf, len, 0x1); +} + +/* + * omap_nand_irq - GPMC irq handler + * @this_irq: gpmc irq number + * @dev: omap_nand_info structure pointer is passed here + */ +static irqreturn_t omap_nand_irq(int this_irq, void *dev) +{ + struct omap_nand_info *info = (struct omap_nand_info *) dev; + u32 bytes; + + bytes = readl(info->reg.gpmc_prefetch_status); + bytes = PREFETCH_STATUS_FIFO_CNT(bytes); + bytes = bytes & 0xFFFC; /* io in multiple of 4 bytes */ + if (info->iomode == OMAP_NAND_IO_WRITE) { /* checks for write io */ + if (this_irq == info->gpmc_irq_count) + goto done; + + if (info->buf_len && (info->buf_len < bytes)) + bytes = info->buf_len; + else if (!info->buf_len) + bytes = 0; + iowrite32_rep(info->fifo, (u32 *)info->buf, + bytes >> 2); + info->buf = info->buf + bytes; + info->buf_len -= bytes; + + } else { + ioread32_rep(info->fifo, (u32 *)info->buf, + bytes >> 2); + info->buf = info->buf + bytes; + + if (this_irq == info->gpmc_irq_count) + goto done; + } + + return IRQ_HANDLED; + +done: + complete(&info->comp); + + disable_irq_nosync(info->gpmc_irq_fifo); + disable_irq_nosync(info->gpmc_irq_count); + + return IRQ_HANDLED; +} + +/* + * omap_nand_data_in_irq_pref - NAND data in using Prefetch and IRQ + */ +static void omap_nand_data_in_irq_pref(struct nand_chip *chip, void *buf, + unsigned int len, bool force_8bit) +{ + struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); + struct mtd_info *mtd = nand_to_mtd(&info->nand); + int ret = 0; + + if (len <= mtd->oobsize || force_8bit) { + omap_nand_data_in(chip, buf, len, force_8bit); + return; + } + + info->iomode = OMAP_NAND_IO_READ; + info->buf = buf; + init_completion(&info->comp); + + /* configure and start prefetch transfer */ + ret = omap_prefetch_enable(info->gpmc_cs, + PREFETCH_FIFOTHRESHOLD_MAX/2, 0x0, len, 0x0, info); + if (ret) { + /* PFPW engine is busy, use cpu copy method */ + omap_nand_data_in(chip, buf, len, false); + return; + } + + info->buf_len = len; + + enable_irq(info->gpmc_irq_count); + enable_irq(info->gpmc_irq_fifo); + + /* waiting for read to complete */ + wait_for_completion(&info->comp); + + /* disable and stop the PFPW engine */ + omap_prefetch_reset(info->gpmc_cs, info); + return; +} + +/* + * omap_nand_data_out_irq_pref - NAND out using write posting and IRQ + */ +static void omap_nand_data_out_irq_pref(struct nand_chip *chip, + const void *buf, unsigned int len, + bool force_8bit) +{ + struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); + struct mtd_info *mtd = nand_to_mtd(&info->nand); + int ret = 0; + unsigned long tim, limit; + u32 val; + + if (len <= mtd->oobsize || force_8bit) { + omap_nand_data_out(chip, buf, len, force_8bit); + return; + } + + info->iomode = OMAP_NAND_IO_WRITE; + info->buf = (u_char *) buf; + init_completion(&info->comp); + + /* configure and start prefetch transfer : size=24 */ + ret = omap_prefetch_enable(info->gpmc_cs, + (PREFETCH_FIFOTHRESHOLD_MAX * 3) / 8, 0x0, len, 0x1, info); + if (ret) { + /* PFPW engine is busy, use cpu copy method */ + omap_nand_data_out(chip, buf, len, false); + return; + } + + info->buf_len = len; + + enable_irq(info->gpmc_irq_count); + enable_irq(info->gpmc_irq_fifo); + + /* waiting for write to complete */ + wait_for_completion(&info->comp); + + /* wait for data to flushed-out before reset the prefetch */ + tim = 0; + limit = (loops_per_jiffy * msecs_to_jiffies(OMAP_NAND_TIMEOUT_MS)); + do { + val = readl(info->reg.gpmc_prefetch_status); + val = PREFETCH_STATUS_COUNT(val); + cpu_relax(); + } while (val && (tim++ < limit)); + + /* disable and stop the PFPW engine */ + omap_prefetch_reset(info->gpmc_cs, info); + return; +} + +/** + * gen_true_ecc - This function will generate true ECC value + * @ecc_buf: buffer to store ecc code + * + * This generated true ECC value can be used when correcting + * data read from NAND flash memory core + */ +static void gen_true_ecc(u8 *ecc_buf) +{ + u32 tmp = ecc_buf[0] | (ecc_buf[1] << 16) | + ((ecc_buf[2] & 0xF0) << 20) | ((ecc_buf[2] & 0x0F) << 8); + + ecc_buf[0] = ~(P64o(tmp) | P64e(tmp) | P32o(tmp) | P32e(tmp) | + P16o(tmp) | P16e(tmp) | P8o(tmp) | P8e(tmp)); + ecc_buf[1] = ~(P1024o(tmp) | P1024e(tmp) | P512o(tmp) | P512e(tmp) | + P256o(tmp) | P256e(tmp) | P128o(tmp) | P128e(tmp)); + ecc_buf[2] = ~(P4o(tmp) | P4e(tmp) | P2o(tmp) | P2e(tmp) | P1o(tmp) | + P1e(tmp) | P2048o(tmp) | P2048e(tmp)); +} + +/** + * omap_compare_ecc - Detect (2 bits) and correct (1 bit) error in data + * @ecc_data1: ecc code from nand spare area + * @ecc_data2: ecc code from hardware register obtained from hardware ecc + * @page_data: page data + * + * This function compares two ECC's and indicates if there is an error. + * If the error can be corrected it will be corrected to the buffer. + * If there is no error, %0 is returned. If there is an error but it + * was corrected, %1 is returned. Otherwise, %-1 is returned. + */ +static int omap_compare_ecc(u8 *ecc_data1, /* read from NAND memory */ + u8 *ecc_data2, /* read from register */ + u8 *page_data) +{ + uint i; + u8 tmp0_bit[8], tmp1_bit[8], tmp2_bit[8]; + u8 comp0_bit[8], comp1_bit[8], comp2_bit[8]; + u8 ecc_bit[24]; + u8 ecc_sum = 0; + u8 find_bit = 0; + uint find_byte = 0; + int isEccFF; + + isEccFF = ((*(u32 *)ecc_data1 & 0xFFFFFF) == 0xFFFFFF); + + gen_true_ecc(ecc_data1); + gen_true_ecc(ecc_data2); + + for (i = 0; i <= 2; i++) { + *(ecc_data1 + i) = ~(*(ecc_data1 + i)); + *(ecc_data2 + i) = ~(*(ecc_data2 + i)); + } + + for (i = 0; i < 8; i++) { + tmp0_bit[i] = *ecc_data1 % 2; + *ecc_data1 = *ecc_data1 / 2; + } + + for (i = 0; i < 8; i++) { + tmp1_bit[i] = *(ecc_data1 + 1) % 2; + *(ecc_data1 + 1) = *(ecc_data1 + 1) / 2; + } + + for (i = 0; i < 8; i++) { + tmp2_bit[i] = *(ecc_data1 + 2) % 2; + *(ecc_data1 + 2) = *(ecc_data1 + 2) / 2; + } + + for (i = 0; i < 8; i++) { + comp0_bit[i] = *ecc_data2 % 2; + *ecc_data2 = *ecc_data2 / 2; + } + + for (i = 0; i < 8; i++) { + comp1_bit[i] = *(ecc_data2 + 1) % 2; + *(ecc_data2 + 1) = *(ecc_data2 + 1) / 2; + } + + for (i = 0; i < 8; i++) { + comp2_bit[i] = *(ecc_data2 + 2) % 2; + *(ecc_data2 + 2) = *(ecc_data2 + 2) / 2; + } + + for (i = 0; i < 6; i++) + ecc_bit[i] = tmp2_bit[i + 2] ^ comp2_bit[i + 2]; + + for (i = 0; i < 8; i++) + ecc_bit[i + 6] = tmp0_bit[i] ^ comp0_bit[i]; + + for (i = 0; i < 8; i++) + ecc_bit[i + 14] = tmp1_bit[i] ^ comp1_bit[i]; + + ecc_bit[22] = tmp2_bit[0] ^ comp2_bit[0]; + ecc_bit[23] = tmp2_bit[1] ^ comp2_bit[1]; + + for (i = 0; i < 24; i++) + ecc_sum += ecc_bit[i]; + + switch (ecc_sum) { + case 0: + /* Not reached because this function is not called if + * ECC values are equal + */ + return 0; + + case 1: + /* Uncorrectable error */ + pr_debug("ECC UNCORRECTED_ERROR 1\n"); + return -EBADMSG; + + case 11: + /* UN-Correctable error */ + pr_debug("ECC UNCORRECTED_ERROR B\n"); + return -EBADMSG; + + case 12: + /* Correctable error */ + find_byte = (ecc_bit[23] << 8) + + (ecc_bit[21] << 7) + + (ecc_bit[19] << 6) + + (ecc_bit[17] << 5) + + (ecc_bit[15] << 4) + + (ecc_bit[13] << 3) + + (ecc_bit[11] << 2) + + (ecc_bit[9] << 1) + + ecc_bit[7]; + + find_bit = (ecc_bit[5] << 2) + (ecc_bit[3] << 1) + ecc_bit[1]; + + pr_debug("Correcting single bit ECC error at offset: " + "%d, bit: %d\n", find_byte, find_bit); + + page_data[find_byte] ^= (1 << find_bit); + + return 1; + default: + if (isEccFF) { + if (ecc_data2[0] == 0 && + ecc_data2[1] == 0 && + ecc_data2[2] == 0) + return 0; + } + pr_debug("UNCORRECTED_ERROR default\n"); + return -EBADMSG; + } +} + +/** + * omap_correct_data - Compares the ECC read with HW generated ECC + * @chip: NAND chip object + * @dat: page data + * @read_ecc: ecc read from nand flash + * @calc_ecc: ecc read from HW ECC registers + * + * Compares the ecc read from nand spare area with ECC registers values + * and if ECC's mismatched, it will call 'omap_compare_ecc' for error + * detection and correction. If there are no errors, %0 is returned. If + * there were errors and all of the errors were corrected, the number of + * corrected errors is returned. If uncorrectable errors exist, %-1 is + * returned. + */ +static int omap_correct_data(struct nand_chip *chip, u_char *dat, + u_char *read_ecc, u_char *calc_ecc) +{ + struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); + int blockCnt = 0, i = 0, ret = 0; + int stat = 0; + + /* Ex NAND_ECC_HW12_2048 */ + if (info->nand.ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST && + info->nand.ecc.size == 2048) + blockCnt = 4; + else + blockCnt = 1; + + for (i = 0; i < blockCnt; i++) { + if (memcmp(read_ecc, calc_ecc, 3) != 0) { + ret = omap_compare_ecc(read_ecc, calc_ecc, dat); + if (ret < 0) + return ret; + /* keep track of the number of corrected errors */ + stat += ret; + } + read_ecc += 3; + calc_ecc += 3; + dat += 512; + } + return stat; +} + +/** + * omap_calculate_ecc - Generate non-inverted ECC bytes. + * @chip: NAND chip object + * @dat: The pointer to data on which ecc is computed + * @ecc_code: The ecc_code buffer + * + * Using noninverted ECC can be considered ugly since writing a blank + * page ie. padding will clear the ECC bytes. This is no problem as long + * nobody is trying to write data on the seemingly unused page. Reading + * an erased page will produce an ECC mismatch between generated and read + * ECC bytes that has to be dealt with separately. + */ +static int omap_calculate_ecc(struct nand_chip *chip, const u_char *dat, + u_char *ecc_code) +{ + struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); + u32 val; + + val = readl(info->reg.gpmc_ecc_config); + if (((val >> ECC_CONFIG_CS_SHIFT) & CS_MASK) != info->gpmc_cs) + return -EINVAL; + + /* read ecc result */ + val = readl(info->reg.gpmc_ecc1_result); + *ecc_code++ = val; /* P128e, ..., P1e */ + *ecc_code++ = val >> 16; /* P128o, ..., P1o */ + /* P2048o, P1024o, P512o, P256o, P2048e, P1024e, P512e, P256e */ + *ecc_code++ = ((val >> 8) & 0x0f) | ((val >> 20) & 0xf0); + + return 0; +} + +/** + * omap_enable_hwecc - This function enables the hardware ecc functionality + * @chip: NAND chip object + * @mode: Read/Write mode + */ +static void omap_enable_hwecc(struct nand_chip *chip, int mode) +{ + struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); + unsigned int dev_width = (chip->options & NAND_BUSWIDTH_16) ? 1 : 0; + u32 val; + + /* clear ecc and enable bits */ + val = ECCCLEAR | ECC1; + writel(val, info->reg.gpmc_ecc_control); + + /* program ecc and result sizes */ + val = ((((info->nand.ecc.size >> 1) - 1) << ECCSIZE1_SHIFT) | + ECC1RESULTSIZE); + writel(val, info->reg.gpmc_ecc_size_config); + + switch (mode) { + case NAND_ECC_READ: + case NAND_ECC_WRITE: + writel(ECCCLEAR | ECC1, info->reg.gpmc_ecc_control); + break; + case NAND_ECC_READSYN: + writel(ECCCLEAR, info->reg.gpmc_ecc_control); + break; + default: + dev_info(&info->pdev->dev, + "error: unrecognized Mode[%d]!\n", mode); + break; + } + + /* (ECC 16 or 8 bit col) | ( CS ) | ECC Enable */ + val = (dev_width << 7) | (info->gpmc_cs << 1) | (0x1); + writel(val, info->reg.gpmc_ecc_config); +} + +/** + * omap_enable_hwecc_bch - Program GPMC to perform BCH ECC calculation + * @chip: NAND chip object + * @mode: Read/Write mode + * + * When using BCH with SW correction (i.e. no ELM), sector size is set + * to 512 bytes and we use BCH_WRAPMODE_6 wrapping mode + * for both reading and writing with: + * eccsize0 = 0 (no additional protected byte in spare area) + * eccsize1 = 32 (skip 32 nibbles = 16 bytes per sector in spare area) + */ +static void __maybe_unused omap_enable_hwecc_bch(struct nand_chip *chip, + int mode) +{ + unsigned int bch_type; + unsigned int dev_width, nsectors; + struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); + enum omap_ecc ecc_opt = info->ecc_opt; + u32 val, wr_mode; + unsigned int ecc_size1, ecc_size0; + + /* GPMC configurations for calculating ECC */ + switch (ecc_opt) { + case OMAP_ECC_BCH4_CODE_HW_DETECTION_SW: + bch_type = 0; + nsectors = 1; + wr_mode = BCH_WRAPMODE_6; + ecc_size0 = BCH_ECC_SIZE0; + ecc_size1 = BCH_ECC_SIZE1; + break; + case OMAP_ECC_BCH4_CODE_HW: + bch_type = 0; + nsectors = chip->ecc.steps; + if (mode == NAND_ECC_READ) { + wr_mode = BCH_WRAPMODE_1; + ecc_size0 = BCH4R_ECC_SIZE0; + ecc_size1 = BCH4R_ECC_SIZE1; + } else { + wr_mode = BCH_WRAPMODE_6; + ecc_size0 = BCH_ECC_SIZE0; + ecc_size1 = BCH_ECC_SIZE1; + } + break; + case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: + bch_type = 1; + nsectors = 1; + wr_mode = BCH_WRAPMODE_6; + ecc_size0 = BCH_ECC_SIZE0; + ecc_size1 = BCH_ECC_SIZE1; + break; + case OMAP_ECC_BCH8_CODE_HW: + bch_type = 1; + nsectors = chip->ecc.steps; + if (mode == NAND_ECC_READ) { + wr_mode = BCH_WRAPMODE_1; + ecc_size0 = BCH8R_ECC_SIZE0; + ecc_size1 = BCH8R_ECC_SIZE1; + } else { + wr_mode = BCH_WRAPMODE_6; + ecc_size0 = BCH_ECC_SIZE0; + ecc_size1 = BCH_ECC_SIZE1; + } + break; + case OMAP_ECC_BCH16_CODE_HW: + bch_type = 0x2; + nsectors = chip->ecc.steps; + if (mode == NAND_ECC_READ) { + wr_mode = 0x01; + ecc_size0 = 52; /* ECC bits in nibbles per sector */ + ecc_size1 = 0; /* non-ECC bits in nibbles per sector */ + } else { + wr_mode = 0x01; + ecc_size0 = 0; /* extra bits in nibbles per sector */ + ecc_size1 = 52; /* OOB bits in nibbles per sector */ + } + break; + default: + return; + } + + writel(ECC1, info->reg.gpmc_ecc_control); + + /* Configure ecc size for BCH */ + val = (ecc_size1 << ECCSIZE1_SHIFT) | (ecc_size0 << ECCSIZE0_SHIFT); + writel(val, info->reg.gpmc_ecc_size_config); + + dev_width = (chip->options & NAND_BUSWIDTH_16) ? 1 : 0; + + /* BCH configuration */ + val = ((1 << 16) | /* enable BCH */ + (bch_type << 12) | /* BCH4/BCH8/BCH16 */ + (wr_mode << 8) | /* wrap mode */ + (dev_width << 7) | /* bus width */ + (((nsectors-1) & 0x7) << 4) | /* number of sectors */ + (info->gpmc_cs << 1) | /* ECC CS */ + (0x1)); /* enable ECC */ + + writel(val, info->reg.gpmc_ecc_config); + + /* Clear ecc and enable bits */ + writel(ECCCLEAR | ECC1, info->reg.gpmc_ecc_control); +} + +static u8 bch4_polynomial[] = {0x28, 0x13, 0xcc, 0x39, 0x96, 0xac, 0x7f}; +static u8 bch8_polynomial[] = {0xef, 0x51, 0x2e, 0x09, 0xed, 0x93, 0x9a, 0xc2, + 0x97, 0x79, 0xe5, 0x24, 0xb5}; + +/** + * _omap_calculate_ecc_bch - Generate ECC bytes for one sector + * @mtd: MTD device structure + * @dat: The pointer to data on which ecc is computed + * @ecc_calc: The ecc_code buffer + * @i: The sector number (for a multi sector page) + * + * Support calculating of BCH4/8/16 ECC vectors for one sector + * within a page. Sector number is in @i. + */ +static int _omap_calculate_ecc_bch(struct mtd_info *mtd, + const u_char *dat, u_char *ecc_calc, int i) +{ + struct omap_nand_info *info = mtd_to_omap(mtd); + int eccbytes = info->nand.ecc.bytes; + struct gpmc_nand_regs *gpmc_regs = &info->reg; + u8 *ecc_code; + unsigned long bch_val1, bch_val2, bch_val3, bch_val4; + u32 val; + int j; + + ecc_code = ecc_calc; + switch (info->ecc_opt) { + case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: + case OMAP_ECC_BCH8_CODE_HW: + bch_val1 = readl(gpmc_regs->gpmc_bch_result0[i]); + bch_val2 = readl(gpmc_regs->gpmc_bch_result1[i]); + bch_val3 = readl(gpmc_regs->gpmc_bch_result2[i]); + bch_val4 = readl(gpmc_regs->gpmc_bch_result3[i]); + *ecc_code++ = (bch_val4 & 0xFF); + *ecc_code++ = ((bch_val3 >> 24) & 0xFF); + *ecc_code++ = ((bch_val3 >> 16) & 0xFF); + *ecc_code++ = ((bch_val3 >> 8) & 0xFF); + *ecc_code++ = (bch_val3 & 0xFF); + *ecc_code++ = ((bch_val2 >> 24) & 0xFF); + *ecc_code++ = ((bch_val2 >> 16) & 0xFF); + *ecc_code++ = ((bch_val2 >> 8) & 0xFF); + *ecc_code++ = (bch_val2 & 0xFF); + *ecc_code++ = ((bch_val1 >> 24) & 0xFF); + *ecc_code++ = ((bch_val1 >> 16) & 0xFF); + *ecc_code++ = ((bch_val1 >> 8) & 0xFF); + *ecc_code++ = (bch_val1 & 0xFF); + break; + case OMAP_ECC_BCH4_CODE_HW_DETECTION_SW: + case OMAP_ECC_BCH4_CODE_HW: + bch_val1 = readl(gpmc_regs->gpmc_bch_result0[i]); + bch_val2 = readl(gpmc_regs->gpmc_bch_result1[i]); + *ecc_code++ = ((bch_val2 >> 12) & 0xFF); + *ecc_code++ = ((bch_val2 >> 4) & 0xFF); + *ecc_code++ = ((bch_val2 & 0xF) << 4) | + ((bch_val1 >> 28) & 0xF); + *ecc_code++ = ((bch_val1 >> 20) & 0xFF); + *ecc_code++ = ((bch_val1 >> 12) & 0xFF); + *ecc_code++ = ((bch_val1 >> 4) & 0xFF); + *ecc_code++ = ((bch_val1 & 0xF) << 4); + break; + case OMAP_ECC_BCH16_CODE_HW: + val = readl(gpmc_regs->gpmc_bch_result6[i]); + ecc_code[0] = ((val >> 8) & 0xFF); + ecc_code[1] = ((val >> 0) & 0xFF); + val = readl(gpmc_regs->gpmc_bch_result5[i]); + ecc_code[2] = ((val >> 24) & 0xFF); + ecc_code[3] = ((val >> 16) & 0xFF); + ecc_code[4] = ((val >> 8) & 0xFF); + ecc_code[5] = ((val >> 0) & 0xFF); + val = readl(gpmc_regs->gpmc_bch_result4[i]); + ecc_code[6] = ((val >> 24) & 0xFF); + ecc_code[7] = ((val >> 16) & 0xFF); + ecc_code[8] = ((val >> 8) & 0xFF); + ecc_code[9] = ((val >> 0) & 0xFF); + val = readl(gpmc_regs->gpmc_bch_result3[i]); + ecc_code[10] = ((val >> 24) & 0xFF); + ecc_code[11] = ((val >> 16) & 0xFF); + ecc_code[12] = ((val >> 8) & 0xFF); + ecc_code[13] = ((val >> 0) & 0xFF); + val = readl(gpmc_regs->gpmc_bch_result2[i]); + ecc_code[14] = ((val >> 24) & 0xFF); + ecc_code[15] = ((val >> 16) & 0xFF); + ecc_code[16] = ((val >> 8) & 0xFF); + ecc_code[17] = ((val >> 0) & 0xFF); + val = readl(gpmc_regs->gpmc_bch_result1[i]); + ecc_code[18] = ((val >> 24) & 0xFF); + ecc_code[19] = ((val >> 16) & 0xFF); + ecc_code[20] = ((val >> 8) & 0xFF); + ecc_code[21] = ((val >> 0) & 0xFF); + val = readl(gpmc_regs->gpmc_bch_result0[i]); + ecc_code[22] = ((val >> 24) & 0xFF); + ecc_code[23] = ((val >> 16) & 0xFF); + ecc_code[24] = ((val >> 8) & 0xFF); + ecc_code[25] = ((val >> 0) & 0xFF); + break; + default: + return -EINVAL; + } + + /* ECC scheme specific syndrome customizations */ + switch (info->ecc_opt) { + case OMAP_ECC_BCH4_CODE_HW_DETECTION_SW: + /* Add constant polynomial to remainder, so that + * ECC of blank pages results in 0x0 on reading back + */ + for (j = 0; j < eccbytes; j++) + ecc_calc[j] ^= bch4_polynomial[j]; + break; + case OMAP_ECC_BCH4_CODE_HW: + /* Set 8th ECC byte as 0x0 for ROM compatibility */ + ecc_calc[eccbytes - 1] = 0x0; + break; + case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: + /* Add constant polynomial to remainder, so that + * ECC of blank pages results in 0x0 on reading back + */ + for (j = 0; j < eccbytes; j++) + ecc_calc[j] ^= bch8_polynomial[j]; + break; + case OMAP_ECC_BCH8_CODE_HW: + /* Set 14th ECC byte as 0x0 for ROM compatibility */ + ecc_calc[eccbytes - 1] = 0x0; + break; + case OMAP_ECC_BCH16_CODE_HW: + break; + default: + return -EINVAL; + } + + return 0; +} + +/** + * omap_calculate_ecc_bch_sw - ECC generator for sector for SW based correction + * @chip: NAND chip object + * @dat: The pointer to data on which ecc is computed + * @ecc_calc: Buffer storing the calculated ECC bytes + * + * Support calculating of BCH4/8/16 ECC vectors for one sector. This is used + * when SW based correction is required as ECC is required for one sector + * at a time. + */ +static int omap_calculate_ecc_bch_sw(struct nand_chip *chip, + const u_char *dat, u_char *ecc_calc) +{ + return _omap_calculate_ecc_bch(nand_to_mtd(chip), dat, ecc_calc, 0); +} + +/** + * omap_calculate_ecc_bch_multi - Generate ECC for multiple sectors + * @mtd: MTD device structure + * @dat: The pointer to data on which ecc is computed + * @ecc_calc: Buffer storing the calculated ECC bytes + * + * Support calculating of BCH4/8/16 ecc vectors for the entire page in one go. + */ +static int omap_calculate_ecc_bch_multi(struct mtd_info *mtd, + const u_char *dat, u_char *ecc_calc) +{ + struct omap_nand_info *info = mtd_to_omap(mtd); + int eccbytes = info->nand.ecc.bytes; + unsigned long nsectors; + int i, ret; + + nsectors = ((readl(info->reg.gpmc_ecc_config) >> 4) & 0x7) + 1; + for (i = 0; i < nsectors; i++) { + ret = _omap_calculate_ecc_bch(mtd, dat, ecc_calc, i); + if (ret) + return ret; + + ecc_calc += eccbytes; + } + + return 0; +} + +/** + * erased_sector_bitflips - count bit flips + * @data: data sector buffer + * @oob: oob buffer + * @info: omap_nand_info + * + * Check the bit flips in erased page falls below correctable level. + * If falls below, report the page as erased with correctable bit + * flip, else report as uncorrectable page. + */ +static int erased_sector_bitflips(u_char *data, u_char *oob, + struct omap_nand_info *info) +{ + int flip_bits = 0, i; + + for (i = 0; i < info->nand.ecc.size; i++) { + flip_bits += hweight8(~data[i]); + if (flip_bits > info->nand.ecc.strength) + return 0; + } + + for (i = 0; i < info->nand.ecc.bytes - 1; i++) { + flip_bits += hweight8(~oob[i]); + if (flip_bits > info->nand.ecc.strength) + return 0; + } + + /* + * Bit flips falls in correctable level. + * Fill data area with 0xFF + */ + if (flip_bits) { + memset(data, 0xFF, info->nand.ecc.size); + memset(oob, 0xFF, info->nand.ecc.bytes); + } + + return flip_bits; +} + +/** + * omap_elm_correct_data - corrects page data area in case error reported + * @chip: NAND chip object + * @data: page data + * @read_ecc: ecc read from nand flash + * @calc_ecc: ecc read from HW ECC registers + * + * Calculated ecc vector reported as zero in case of non-error pages. + * In case of non-zero ecc vector, first filter out erased-pages, and + * then process data via ELM to detect bit-flips. + */ +static int omap_elm_correct_data(struct nand_chip *chip, u_char *data, + u_char *read_ecc, u_char *calc_ecc) +{ + struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); + struct nand_ecc_ctrl *ecc = &info->nand.ecc; + int eccsteps = info->nsteps_per_eccpg; + int i , j, stat = 0; + int eccflag, actual_eccbytes; + struct elm_errorvec err_vec[ERROR_VECTOR_MAX]; + u_char *ecc_vec = calc_ecc; + u_char *spare_ecc = read_ecc; + u_char *erased_ecc_vec; + u_char *buf; + int bitflip_count; + bool is_error_reported = false; + u32 bit_pos, byte_pos, error_max, pos; + int err; + + switch (info->ecc_opt) { + case OMAP_ECC_BCH4_CODE_HW: + /* omit 7th ECC byte reserved for ROM code compatibility */ + actual_eccbytes = ecc->bytes - 1; + erased_ecc_vec = bch4_vector; + break; + case OMAP_ECC_BCH8_CODE_HW: + /* omit 14th ECC byte reserved for ROM code compatibility */ + actual_eccbytes = ecc->bytes - 1; + erased_ecc_vec = bch8_vector; + break; + case OMAP_ECC_BCH16_CODE_HW: + actual_eccbytes = ecc->bytes; + erased_ecc_vec = bch16_vector; + break; + default: + dev_err(&info->pdev->dev, "invalid driver configuration\n"); + return -EINVAL; + } + + /* Initialize elm error vector to zero */ + memset(err_vec, 0, sizeof(err_vec)); + + for (i = 0; i < eccsteps ; i++) { + eccflag = 0; /* initialize eccflag */ + + /* + * Check any error reported, + * In case of error, non zero ecc reported. + */ + for (j = 0; j < actual_eccbytes; j++) { + if (calc_ecc[j] != 0) { + eccflag = 1; /* non zero ecc, error present */ + break; + } + } + + if (eccflag == 1) { + if (memcmp(calc_ecc, erased_ecc_vec, + actual_eccbytes) == 0) { + /* + * calc_ecc[] matches pattern for ECC(all 0xff) + * so this is definitely an erased-page + */ + } else { + buf = &data[info->nand.ecc.size * i]; + /* + * count number of 0-bits in read_buf. + * This check can be removed once a similar + * check is introduced in generic NAND driver + */ + bitflip_count = erased_sector_bitflips( + buf, read_ecc, info); + if (bitflip_count) { + /* + * number of 0-bits within ECC limits + * So this may be an erased-page + */ + stat += bitflip_count; + } else { + /* + * Too many 0-bits. It may be a + * - programmed-page, OR + * - erased-page with many bit-flips + * So this page requires check by ELM + */ + err_vec[i].error_reported = true; + is_error_reported = true; + } + } + } + + /* Update the ecc vector */ + calc_ecc += ecc->bytes; + read_ecc += ecc->bytes; + } + + /* Check if any error reported */ + if (!is_error_reported) + return stat; + + /* Decode BCH error using ELM module */ + elm_decode_bch_error_page(info->elm_dev, ecc_vec, err_vec); + + err = 0; + for (i = 0; i < eccsteps; i++) { + if (err_vec[i].error_uncorrectable) { + dev_err(&info->pdev->dev, + "uncorrectable bit-flips found\n"); + err = -EBADMSG; + } else if (err_vec[i].error_reported) { + for (j = 0; j < err_vec[i].error_count; j++) { + switch (info->ecc_opt) { + case OMAP_ECC_BCH4_CODE_HW: + /* Add 4 bits to take care of padding */ + pos = err_vec[i].error_loc[j] + + BCH4_BIT_PAD; + break; + case OMAP_ECC_BCH8_CODE_HW: + case OMAP_ECC_BCH16_CODE_HW: + pos = err_vec[i].error_loc[j]; + break; + default: + return -EINVAL; + } + error_max = (ecc->size + actual_eccbytes) * 8; + /* Calculate bit position of error */ + bit_pos = pos % 8; + + /* Calculate byte position of error */ + byte_pos = (error_max - pos - 1) / 8; + + if (pos < error_max) { + if (byte_pos < 512) { + pr_debug("bitflip@dat[%d]=%x\n", + byte_pos, data[byte_pos]); + data[byte_pos] ^= 1 << bit_pos; + } else { + pr_debug("bitflip@oob[%d]=%x\n", + (byte_pos - 512), + spare_ecc[byte_pos - 512]); + spare_ecc[byte_pos - 512] ^= + 1 << bit_pos; + } + } else { + dev_err(&info->pdev->dev, + "invalid bit-flip @ %d:%d\n", + byte_pos, bit_pos); + err = -EBADMSG; + } + } + } + + /* Update number of correctable errors */ + stat = max_t(unsigned int, stat, err_vec[i].error_count); + + /* Update page data with sector size */ + data += ecc->size; + spare_ecc += ecc->bytes; + } + + return (err) ? err : stat; +} + +/** + * omap_write_page_bch - BCH ecc based write page function for entire page + * @chip: nand chip info structure + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB + * @page: page + * + * Custom write page method evolved to support multi sector writing in one shot + */ +static int omap_write_page_bch(struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct omap_nand_info *info = mtd_to_omap(mtd); + uint8_t *ecc_calc = chip->ecc.calc_buf; + unsigned int eccpg; + int ret; + + ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0); + if (ret) + return ret; + + for (eccpg = 0; eccpg < info->neccpg; eccpg++) { + /* Enable GPMC ecc engine */ + chip->ecc.hwctl(chip, NAND_ECC_WRITE); + + /* Write data */ + info->data_out(chip, buf + (eccpg * info->eccpg_size), + info->eccpg_size, false); + + /* Update ecc vector from GPMC result registers */ + ret = omap_calculate_ecc_bch_multi(mtd, + buf + (eccpg * info->eccpg_size), + ecc_calc); + if (ret) + return ret; + + ret = mtd_ooblayout_set_eccbytes(mtd, ecc_calc, + chip->oob_poi, + eccpg * info->eccpg_bytes, + info->eccpg_bytes); + if (ret) + return ret; + } + + /* Write ecc vector to OOB area */ + info->data_out(chip, chip->oob_poi, mtd->oobsize, false); + + return nand_prog_page_end_op(chip); +} + +/** + * omap_write_subpage_bch - BCH hardware ECC based subpage write + * @chip: nand chip info structure + * @offset: column address of subpage within the page + * @data_len: data length + * @buf: data buffer + * @oob_required: must write chip->oob_poi to OOB + * @page: page number to write + * + * OMAP optimized subpage write method. + */ +static int omap_write_subpage_bch(struct nand_chip *chip, u32 offset, + u32 data_len, const u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct omap_nand_info *info = mtd_to_omap(mtd); + u8 *ecc_calc = chip->ecc.calc_buf; + int ecc_size = chip->ecc.size; + int ecc_bytes = chip->ecc.bytes; + u32 start_step = offset / ecc_size; + u32 end_step = (offset + data_len - 1) / ecc_size; + unsigned int eccpg; + int step, ret = 0; + + /* + * Write entire page at one go as it would be optimal + * as ECC is calculated by hardware. + * ECC is calculated for all subpages but we choose + * only what we want. + */ + ret = nand_prog_page_begin_op(chip, page, 0, NULL, 0); + if (ret) + return ret; + + for (eccpg = 0; eccpg < info->neccpg; eccpg++) { + /* Enable GPMC ECC engine */ + chip->ecc.hwctl(chip, NAND_ECC_WRITE); + + /* Write data */ + info->data_out(chip, buf + (eccpg * info->eccpg_size), + info->eccpg_size, false); + + for (step = 0; step < info->nsteps_per_eccpg; step++) { + unsigned int base_step = eccpg * info->nsteps_per_eccpg; + const u8 *bufoffs = buf + (eccpg * info->eccpg_size); + + /* Mask ECC of un-touched subpages with 0xFFs */ + if ((step + base_step) < start_step || + (step + base_step) > end_step) + memset(ecc_calc + (step * ecc_bytes), 0xff, + ecc_bytes); + else + ret = _omap_calculate_ecc_bch(mtd, + bufoffs + (step * ecc_size), + ecc_calc + (step * ecc_bytes), + step); + + if (ret) + return ret; + } + + /* + * Copy the calculated ECC for the whole page including the + * masked values (0xFF) corresponding to unwritten subpages. + */ + ret = mtd_ooblayout_set_eccbytes(mtd, ecc_calc, chip->oob_poi, + eccpg * info->eccpg_bytes, + info->eccpg_bytes); + if (ret) + return ret; + } + + /* write OOB buffer to NAND device */ + info->data_out(chip, chip->oob_poi, mtd->oobsize, false); + + return nand_prog_page_end_op(chip); +} + +/** + * omap_read_page_bch - BCH ecc based page read function for entire page + * @chip: nand chip info structure + * @buf: buffer to store read data + * @oob_required: caller requires OOB data read to chip->oob_poi + * @page: page number to read + * + * For BCH ecc scheme, GPMC used for syndrome calculation and ELM module + * used for error correction. + * Custom method evolved to support ELM error correction & multi sector + * reading. On reading page data area is read along with OOB data with + * ecc engine enabled. ecc vector updated after read of OOB data. + * For non error pages ecc vector reported as zero. + */ +static int omap_read_page_bch(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct omap_nand_info *info = mtd_to_omap(mtd); + uint8_t *ecc_calc = chip->ecc.calc_buf; + uint8_t *ecc_code = chip->ecc.code_buf; + unsigned int max_bitflips = 0, eccpg; + int stat, ret; + + ret = nand_read_page_op(chip, page, 0, NULL, 0); + if (ret) + return ret; + + for (eccpg = 0; eccpg < info->neccpg; eccpg++) { + /* Enable GPMC ecc engine */ + chip->ecc.hwctl(chip, NAND_ECC_READ); + + /* Read data */ + ret = nand_change_read_column_op(chip, eccpg * info->eccpg_size, + buf + (eccpg * info->eccpg_size), + info->eccpg_size, false); + if (ret) + return ret; + + /* Read oob bytes */ + ret = nand_change_read_column_op(chip, + mtd->writesize + BBM_LEN + + (eccpg * info->eccpg_bytes), + chip->oob_poi + BBM_LEN + + (eccpg * info->eccpg_bytes), + info->eccpg_bytes, false); + if (ret) + return ret; + + /* Calculate ecc bytes */ + ret = omap_calculate_ecc_bch_multi(mtd, + buf + (eccpg * info->eccpg_size), + ecc_calc); + if (ret) + return ret; + + ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, + chip->oob_poi, + eccpg * info->eccpg_bytes, + info->eccpg_bytes); + if (ret) + return ret; + + stat = chip->ecc.correct(chip, + buf + (eccpg * info->eccpg_size), + ecc_code, ecc_calc); + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + + return max_bitflips; +} + +/** + * is_elm_present - checks for presence of ELM module by scanning DT nodes + * @info: NAND device structure containing platform data + * @elm_node: ELM's DT node + */ +static bool is_elm_present(struct omap_nand_info *info, + struct device_node *elm_node) +{ + struct platform_device *pdev; + + /* check whether elm-id is passed via DT */ + if (!elm_node) { + dev_err(&info->pdev->dev, "ELM devicetree node not found\n"); + return false; + } + pdev = of_find_device_by_node(elm_node); + /* check whether ELM device is registered */ + if (!pdev) { + dev_err(&info->pdev->dev, "ELM device not found\n"); + return false; + } + /* ELM module available, now configure it */ + info->elm_dev = &pdev->dev; + return true; +} + +static bool omap2_nand_ecc_check(struct omap_nand_info *info) +{ + bool ecc_needs_bch, ecc_needs_omap_bch, ecc_needs_elm; + + switch (info->ecc_opt) { + case OMAP_ECC_BCH4_CODE_HW_DETECTION_SW: + case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: + ecc_needs_omap_bch = false; + ecc_needs_bch = true; + ecc_needs_elm = false; + break; + case OMAP_ECC_BCH4_CODE_HW: + case OMAP_ECC_BCH8_CODE_HW: + case OMAP_ECC_BCH16_CODE_HW: + ecc_needs_omap_bch = true; + ecc_needs_bch = false; + ecc_needs_elm = true; + break; + default: + ecc_needs_omap_bch = false; + ecc_needs_bch = false; + ecc_needs_elm = false; + break; + } + + if (ecc_needs_bch && !IS_ENABLED(CONFIG_MTD_NAND_ECC_SW_BCH)) { + dev_err(&info->pdev->dev, + "CONFIG_MTD_NAND_ECC_SW_BCH not enabled\n"); + return false; + } + if (ecc_needs_omap_bch && !IS_ENABLED(CONFIG_MTD_NAND_OMAP_BCH)) { + dev_err(&info->pdev->dev, + "CONFIG_MTD_NAND_OMAP_BCH not enabled\n"); + return false; + } + if (ecc_needs_elm && !is_elm_present(info, info->elm_of_node)) { + dev_err(&info->pdev->dev, "ELM not available\n"); + return false; + } + + return true; +} + +static const char * const nand_xfer_types[] = { + [NAND_OMAP_PREFETCH_POLLED] = "prefetch-polled", + [NAND_OMAP_POLLED] = "polled", + [NAND_OMAP_PREFETCH_DMA] = "prefetch-dma", + [NAND_OMAP_PREFETCH_IRQ] = "prefetch-irq", +}; + +static int omap_get_dt_info(struct device *dev, struct omap_nand_info *info) +{ + struct device_node *child = dev->of_node; + int i; + const char *s; + u32 cs; + + if (of_property_read_u32(child, "reg", &cs) < 0) { + dev_err(dev, "reg not found in DT\n"); + return -EINVAL; + } + + info->gpmc_cs = cs; + + /* detect availability of ELM module. Won't be present pre-OMAP4 */ + info->elm_of_node = of_parse_phandle(child, "ti,elm-id", 0); + if (!info->elm_of_node) { + info->elm_of_node = of_parse_phandle(child, "elm_id", 0); + if (!info->elm_of_node) + dev_dbg(dev, "ti,elm-id not in DT\n"); + } + + /* select ecc-scheme for NAND */ + if (of_property_read_string(child, "ti,nand-ecc-opt", &s)) { + dev_err(dev, "ti,nand-ecc-opt not found\n"); + return -EINVAL; + } + + if (!strcmp(s, "sw")) { + info->ecc_opt = OMAP_ECC_HAM1_CODE_SW; + } else if (!strcmp(s, "ham1") || + !strcmp(s, "hw") || !strcmp(s, "hw-romcode")) { + info->ecc_opt = OMAP_ECC_HAM1_CODE_HW; + } else if (!strcmp(s, "bch4")) { + if (info->elm_of_node) + info->ecc_opt = OMAP_ECC_BCH4_CODE_HW; + else + info->ecc_opt = OMAP_ECC_BCH4_CODE_HW_DETECTION_SW; + } else if (!strcmp(s, "bch8")) { + if (info->elm_of_node) + info->ecc_opt = OMAP_ECC_BCH8_CODE_HW; + else + info->ecc_opt = OMAP_ECC_BCH8_CODE_HW_DETECTION_SW; + } else if (!strcmp(s, "bch16")) { + info->ecc_opt = OMAP_ECC_BCH16_CODE_HW; + } else { + dev_err(dev, "unrecognized value for ti,nand-ecc-opt\n"); + return -EINVAL; + } + + /* select data transfer mode */ + if (!of_property_read_string(child, "ti,nand-xfer-type", &s)) { + for (i = 0; i < ARRAY_SIZE(nand_xfer_types); i++) { + if (!strcasecmp(s, nand_xfer_types[i])) { + info->xfer_type = i; + return 0; + } + } + + dev_err(dev, "unrecognized value for ti,nand-xfer-type\n"); + return -EINVAL; + } + + return 0; +} + +static int omap_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct omap_nand_info *info = mtd_to_omap(mtd); + struct nand_chip *chip = &info->nand; + int off = BBM_LEN; + + if (info->ecc_opt == OMAP_ECC_HAM1_CODE_HW && + !(chip->options & NAND_BUSWIDTH_16)) + off = 1; + + if (section) + return -ERANGE; + + oobregion->offset = off; + oobregion->length = chip->ecc.total; + + return 0; +} + +static int omap_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct omap_nand_info *info = mtd_to_omap(mtd); + struct nand_chip *chip = &info->nand; + int off = BBM_LEN; + + if (info->ecc_opt == OMAP_ECC_HAM1_CODE_HW && + !(chip->options & NAND_BUSWIDTH_16)) + off = 1; + + if (section) + return -ERANGE; + + off += chip->ecc.total; + if (off >= mtd->oobsize) + return -ERANGE; + + oobregion->offset = off; + oobregion->length = mtd->oobsize - off; + + return 0; +} + +static const struct mtd_ooblayout_ops omap_ooblayout_ops = { + .ecc = omap_ooblayout_ecc, + .free = omap_ooblayout_free, +}; + +static int omap_sw_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + unsigned int nsteps = nanddev_get_ecc_nsteps(nand); + unsigned int ecc_bytes = nanddev_get_ecc_bytes_per_step(nand); + int off = BBM_LEN; + + if (section >= nsteps) + return -ERANGE; + + /* + * When SW correction is employed, one OMAP specific marker byte is + * reserved after each ECC step. + */ + oobregion->offset = off + (section * (ecc_bytes + 1)); + oobregion->length = ecc_bytes; + + return 0; +} + +static int omap_sw_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + unsigned int nsteps = nanddev_get_ecc_nsteps(nand); + unsigned int ecc_bytes = nanddev_get_ecc_bytes_per_step(nand); + int off = BBM_LEN; + + if (section) + return -ERANGE; + + /* + * When SW correction is employed, one OMAP specific marker byte is + * reserved after each ECC step. + */ + off += ((ecc_bytes + 1) * nsteps); + if (off >= mtd->oobsize) + return -ERANGE; + + oobregion->offset = off; + oobregion->length = mtd->oobsize - off; + + return 0; +} + +static const struct mtd_ooblayout_ops omap_sw_ooblayout_ops = { + .ecc = omap_sw_ooblayout_ecc, + .free = omap_sw_ooblayout_free, +}; + +static int omap_nand_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct omap_nand_info *info = mtd_to_omap(mtd); + struct device *dev = &info->pdev->dev; + int min_oobbytes = BBM_LEN; + int elm_bch_strength = -1; + int oobbytes_per_step; + dma_cap_mask_t mask; + int err; + + if (chip->bbt_options & NAND_BBT_USE_FLASH) + chip->bbt_options |= NAND_BBT_NO_OOB; + else + chip->options |= NAND_SKIP_BBTSCAN; + + /* Re-populate low-level callbacks based on xfer modes */ + switch (info->xfer_type) { + case NAND_OMAP_PREFETCH_POLLED: + info->data_in = omap_nand_data_in_pref; + info->data_out = omap_nand_data_out_pref; + break; + + case NAND_OMAP_POLLED: + /* Use nand_base defaults for {read,write}_buf */ + break; + + case NAND_OMAP_PREFETCH_DMA: + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + info->dma = dma_request_chan(dev->parent, "rxtx"); + + if (IS_ERR(info->dma)) { + dev_err(dev, "DMA engine request failed\n"); + return PTR_ERR(info->dma); + } else { + struct dma_slave_config cfg; + + memset(&cfg, 0, sizeof(cfg)); + cfg.src_addr = info->phys_base; + cfg.dst_addr = info->phys_base; + cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + cfg.src_maxburst = 16; + cfg.dst_maxburst = 16; + err = dmaengine_slave_config(info->dma, &cfg); + if (err) { + dev_err(dev, + "DMA engine slave config failed: %d\n", + err); + return err; + } + + info->data_in = omap_nand_data_in_dma_pref; + info->data_out = omap_nand_data_out_dma_pref; + } + break; + + case NAND_OMAP_PREFETCH_IRQ: + info->gpmc_irq_fifo = platform_get_irq(info->pdev, 0); + if (info->gpmc_irq_fifo < 0) + return info->gpmc_irq_fifo; + err = devm_request_irq(dev, info->gpmc_irq_fifo, + omap_nand_irq, IRQF_SHARED, + "gpmc-nand-fifo", info); + if (err) { + dev_err(dev, "Requesting IRQ %d, error %d\n", + info->gpmc_irq_fifo, err); + info->gpmc_irq_fifo = 0; + return err; + } + + info->gpmc_irq_count = platform_get_irq(info->pdev, 1); + if (info->gpmc_irq_count < 0) + return info->gpmc_irq_count; + err = devm_request_irq(dev, info->gpmc_irq_count, + omap_nand_irq, IRQF_SHARED, + "gpmc-nand-count", info); + if (err) { + dev_err(dev, "Requesting IRQ %d, error %d\n", + info->gpmc_irq_count, err); + info->gpmc_irq_count = 0; + return err; + } + + info->data_in = omap_nand_data_in_irq_pref; + info->data_out = omap_nand_data_out_irq_pref; + break; + + default: + dev_err(dev, "xfer_type %d not supported!\n", info->xfer_type); + return -EINVAL; + } + + if (!omap2_nand_ecc_check(info)) + return -EINVAL; + + /* + * Bail out earlier to let NAND_ECC_ENGINE_TYPE_SOFT code create its own + * ooblayout instead of using ours. + */ + if (info->ecc_opt == OMAP_ECC_HAM1_CODE_SW) { + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + return 0; + } + + /* Populate MTD interface based on ECC scheme */ + switch (info->ecc_opt) { + case OMAP_ECC_HAM1_CODE_HW: + dev_info(dev, "nand: using OMAP_ECC_HAM1_CODE_HW\n"); + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + chip->ecc.bytes = 3; + chip->ecc.size = 512; + chip->ecc.strength = 1; + chip->ecc.calculate = omap_calculate_ecc; + chip->ecc.hwctl = omap_enable_hwecc; + chip->ecc.correct = omap_correct_data; + mtd_set_ooblayout(mtd, &omap_ooblayout_ops); + oobbytes_per_step = chip->ecc.bytes; + + if (!(chip->options & NAND_BUSWIDTH_16)) + min_oobbytes = 1; + + break; + + case OMAP_ECC_BCH4_CODE_HW_DETECTION_SW: + pr_info("nand: using OMAP_ECC_BCH4_CODE_HW_DETECTION_SW\n"); + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + chip->ecc.size = 512; + chip->ecc.bytes = 7; + chip->ecc.strength = 4; + chip->ecc.hwctl = omap_enable_hwecc_bch; + chip->ecc.correct = rawnand_sw_bch_correct; + chip->ecc.calculate = omap_calculate_ecc_bch_sw; + mtd_set_ooblayout(mtd, &omap_sw_ooblayout_ops); + /* Reserve one byte for the OMAP marker */ + oobbytes_per_step = chip->ecc.bytes + 1; + /* Software BCH library is used for locating errors */ + err = rawnand_sw_bch_init(chip); + if (err) { + dev_err(dev, "Unable to use BCH library\n"); + goto err_put_elm_dev; + } + break; + + case OMAP_ECC_BCH4_CODE_HW: + pr_info("nand: using OMAP_ECC_BCH4_CODE_HW ECC scheme\n"); + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + chip->ecc.size = 512; + /* 14th bit is kept reserved for ROM-code compatibility */ + chip->ecc.bytes = 7 + 1; + chip->ecc.strength = 4; + chip->ecc.hwctl = omap_enable_hwecc_bch; + chip->ecc.correct = omap_elm_correct_data; + chip->ecc.read_page = omap_read_page_bch; + chip->ecc.write_page = omap_write_page_bch; + chip->ecc.write_subpage = omap_write_subpage_bch; + mtd_set_ooblayout(mtd, &omap_ooblayout_ops); + oobbytes_per_step = chip->ecc.bytes; + elm_bch_strength = BCH4_ECC; + break; + + case OMAP_ECC_BCH8_CODE_HW_DETECTION_SW: + pr_info("nand: using OMAP_ECC_BCH8_CODE_HW_DETECTION_SW\n"); + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + chip->ecc.size = 512; + chip->ecc.bytes = 13; + chip->ecc.strength = 8; + chip->ecc.hwctl = omap_enable_hwecc_bch; + chip->ecc.correct = rawnand_sw_bch_correct; + chip->ecc.calculate = omap_calculate_ecc_bch_sw; + mtd_set_ooblayout(mtd, &omap_sw_ooblayout_ops); + /* Reserve one byte for the OMAP marker */ + oobbytes_per_step = chip->ecc.bytes + 1; + /* Software BCH library is used for locating errors */ + err = rawnand_sw_bch_init(chip); + if (err) { + dev_err(dev, "unable to use BCH library\n"); + goto err_put_elm_dev; + } + break; + + case OMAP_ECC_BCH8_CODE_HW: + pr_info("nand: using OMAP_ECC_BCH8_CODE_HW ECC scheme\n"); + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + chip->ecc.size = 512; + /* 14th bit is kept reserved for ROM-code compatibility */ + chip->ecc.bytes = 13 + 1; + chip->ecc.strength = 8; + chip->ecc.hwctl = omap_enable_hwecc_bch; + chip->ecc.correct = omap_elm_correct_data; + chip->ecc.read_page = omap_read_page_bch; + chip->ecc.write_page = omap_write_page_bch; + chip->ecc.write_subpage = omap_write_subpage_bch; + mtd_set_ooblayout(mtd, &omap_ooblayout_ops); + oobbytes_per_step = chip->ecc.bytes; + elm_bch_strength = BCH8_ECC; + break; + + case OMAP_ECC_BCH16_CODE_HW: + pr_info("Using OMAP_ECC_BCH16_CODE_HW ECC scheme\n"); + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + chip->ecc.size = 512; + chip->ecc.bytes = 26; + chip->ecc.strength = 16; + chip->ecc.hwctl = omap_enable_hwecc_bch; + chip->ecc.correct = omap_elm_correct_data; + chip->ecc.read_page = omap_read_page_bch; + chip->ecc.write_page = omap_write_page_bch; + chip->ecc.write_subpage = omap_write_subpage_bch; + mtd_set_ooblayout(mtd, &omap_ooblayout_ops); + oobbytes_per_step = chip->ecc.bytes; + elm_bch_strength = BCH16_ECC; + break; + default: + dev_err(dev, "Invalid or unsupported ECC scheme\n"); + err = -EINVAL; + goto err_put_elm_dev; + } + + if (elm_bch_strength >= 0) { + chip->ecc.steps = mtd->writesize / chip->ecc.size; + info->neccpg = chip->ecc.steps / ERROR_VECTOR_MAX; + if (info->neccpg) { + info->nsteps_per_eccpg = ERROR_VECTOR_MAX; + } else { + info->neccpg = 1; + info->nsteps_per_eccpg = chip->ecc.steps; + } + info->eccpg_size = info->nsteps_per_eccpg * chip->ecc.size; + info->eccpg_bytes = info->nsteps_per_eccpg * chip->ecc.bytes; + + err = elm_config(info->elm_dev, elm_bch_strength, + info->nsteps_per_eccpg, chip->ecc.size, + chip->ecc.bytes); + if (err < 0) + goto err_put_elm_dev; + } + + /* Check if NAND device's OOB is enough to store ECC signatures */ + min_oobbytes += (oobbytes_per_step * + (mtd->writesize / chip->ecc.size)); + if (mtd->oobsize < min_oobbytes) { + dev_err(dev, + "Not enough OOB bytes: required = %d, available=%d\n", + min_oobbytes, mtd->oobsize); + 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, + unsigned int len, bool force_8bit) +{ + struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); + u32 alignment = ((uintptr_t)buf | len) & 3; + + if (force_8bit || (alignment & 1)) + ioread8_rep(info->fifo, buf, len); + else if (alignment & 3) + ioread16_rep(info->fifo, buf, len >> 1); + else + ioread32_rep(info->fifo, buf, len >> 2); +} + +static void omap_nand_data_out(struct nand_chip *chip, + const void *buf, unsigned int len, + bool force_8bit) +{ + struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); + u32 alignment = ((uintptr_t)buf | len) & 3; + + if (force_8bit || (alignment & 1)) + iowrite8_rep(info->fifo, buf, len); + else if (alignment & 3) + iowrite16_rep(info->fifo, buf, len >> 1); + else + iowrite32_rep(info->fifo, buf, len >> 2); +} + +static int omap_nand_exec_instr(struct nand_chip *chip, + const struct nand_op_instr *instr) +{ + struct omap_nand_info *info = mtd_to_omap(nand_to_mtd(chip)); + unsigned int i; + int ret; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + iowrite8(instr->ctx.cmd.opcode, + info->reg.gpmc_nand_command); + break; + + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) { + iowrite8(instr->ctx.addr.addrs[i], + info->reg.gpmc_nand_address); + } + break; + + case NAND_OP_DATA_IN_INSTR: + info->data_in(chip, instr->ctx.data.buf.in, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + break; + + case NAND_OP_DATA_OUT_INSTR: + info->data_out(chip, instr->ctx.data.buf.out, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + break; + + case NAND_OP_WAITRDY_INSTR: + ret = info->ready_gpiod ? + nand_gpio_waitrdy(chip, info->ready_gpiod, instr->ctx.waitrdy.timeout_ms) : + nand_soft_waitrdy(chip, instr->ctx.waitrdy.timeout_ms); + if (ret) + return ret; + break; + } + + if (instr->delay_ns) + ndelay(instr->delay_ns); + + return 0; +} + +static int omap_nand_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + unsigned int i; + + if (check_only) + return 0; + + for (i = 0; i < op->ninstrs; i++) { + int ret; + + ret = omap_nand_exec_instr(chip, &op->instrs[i]); + if (ret) + return ret; + } + + return 0; +} + +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, +}; + +/* Shared among all NAND instances to synchronize access to the ECC Engine */ +static struct nand_controller omap_gpmc_controller; +static bool omap_gpmc_controller_initialized; + +static int omap_nand_probe(struct platform_device *pdev) +{ + struct omap_nand_info *info; + struct mtd_info *mtd; + struct nand_chip *nand_chip; + int err; + struct resource *res; + struct device *dev = &pdev->dev; + void __iomem *vaddr; + + info = devm_kzalloc(&pdev->dev, sizeof(struct omap_nand_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pdev = pdev; + + err = omap_get_dt_info(dev, info); + if (err) + return err; + + info->ops = gpmc_omap_get_nand_ops(&info->reg, info->gpmc_cs); + if (!info->ops) { + dev_err(&pdev->dev, "Failed to get GPMC->NAND interface\n"); + return -ENODEV; + } + + nand_chip = &info->nand; + mtd = nand_to_mtd(nand_chip); + mtd->dev.parent = &pdev->dev; + nand_set_flash_node(nand_chip, dev->of_node); + + if (!mtd->name) { + mtd->name = devm_kasprintf(&pdev->dev, GFP_KERNEL, + "omap2-nand.%d", info->gpmc_cs); + if (!mtd->name) { + dev_err(&pdev->dev, "Failed to set MTD name\n"); + return -ENOMEM; + } + } + + vaddr = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(vaddr)) + return PTR_ERR(vaddr); + + info->fifo = vaddr; + info->phys_base = res->start; + + if (!omap_gpmc_controller_initialized) { + omap_gpmc_controller.ops = &omap_nand_controller_ops; + nand_controller_init(&omap_gpmc_controller); + omap_gpmc_controller_initialized = true; + } + + nand_chip->controller = &omap_gpmc_controller; + + info->ready_gpiod = devm_gpiod_get_optional(&pdev->dev, "rb", + GPIOD_IN); + if (IS_ERR(info->ready_gpiod)) { + dev_err(dev, "failed to get ready gpio\n"); + return PTR_ERR(info->ready_gpiod); + } + + if (info->flash_bbt) + nand_chip->bbt_options |= NAND_BBT_USE_FLASH; + + /* default operations */ + info->data_in = omap_nand_data_in; + info->data_out = omap_nand_data_out; + + err = nand_scan(nand_chip, 1); + if (err) + goto return_error; + + err = mtd_device_register(mtd, NULL, 0); + if (err) + goto cleanup_nand; + + platform_set_drvdata(pdev, mtd); + + return 0; + +cleanup_nand: + nand_cleanup(nand_chip); + +return_error: + if (!IS_ERR_OR_NULL(info->dma)) + dma_release_channel(info->dma); + + rawnand_sw_bch_cleanup(nand_chip); + + return err; +} + +static void omap_nand_remove(struct platform_device *pdev) +{ + struct mtd_info *mtd = platform_get_drvdata(pdev); + struct nand_chip *nand_chip = mtd_to_nand(mtd); + struct omap_nand_info *info = mtd_to_omap(mtd); + + rawnand_sw_bch_cleanup(nand_chip); + + if (info->dma) + dma_release_channel(info->dma); + WARN_ON(mtd_device_unregister(mtd)); + nand_cleanup(nand_chip); +} + +static const struct of_device_id omap_nand_ids[] = { + { .compatible = "ti,omap2-nand" }, + { .compatible = "ti,am64-nand" }, + { } +}; +MODULE_DEVICE_TABLE(of, omap_nand_ids); + +static struct platform_driver omap_nand_driver = { + .probe = omap_nand_probe, + .remove = omap_nand_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = omap_nand_ids, + }, +}; + +module_platform_driver(omap_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Glue layer for NAND flash on TI OMAP boards"); diff --git a/drivers/mtd/nand/raw/omap_elm.c b/drivers/mtd/nand/raw/omap_elm.c new file mode 100644 index 000000000..c1d19b8e6 --- /dev/null +++ b/drivers/mtd/nand/raw/omap_elm.c @@ -0,0 +1,571 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Error Location Module + * + * Copyright (C) 2012 Texas Instruments Incorporated - https://www.ti.com/ + */ + +#define DRIVER_NAME "omap-elm" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ELM_SYSCONFIG 0x010 +#define ELM_IRQSTATUS 0x018 +#define ELM_IRQENABLE 0x01c +#define ELM_LOCATION_CONFIG 0x020 +#define ELM_PAGE_CTRL 0x080 +#define ELM_SYNDROME_FRAGMENT_0 0x400 +#define ELM_SYNDROME_FRAGMENT_1 0x404 +#define ELM_SYNDROME_FRAGMENT_2 0x408 +#define ELM_SYNDROME_FRAGMENT_3 0x40c +#define ELM_SYNDROME_FRAGMENT_4 0x410 +#define ELM_SYNDROME_FRAGMENT_5 0x414 +#define ELM_SYNDROME_FRAGMENT_6 0x418 +#define ELM_LOCATION_STATUS 0x800 +#define ELM_ERROR_LOCATION_0 0x880 + +/* ELM Interrupt Status Register */ +#define INTR_STATUS_PAGE_VALID BIT(8) + +/* ELM Interrupt Enable Register */ +#define INTR_EN_PAGE_MASK BIT(8) + +/* ELM Location Configuration Register */ +#define ECC_BCH_LEVEL_MASK 0x3 + +/* ELM syndrome */ +#define ELM_SYNDROME_VALID BIT(16) + +/* ELM_LOCATION_STATUS Register */ +#define ECC_CORRECTABLE_MASK BIT(8) +#define ECC_NB_ERRORS_MASK 0x1f + +/* ELM_ERROR_LOCATION_0-15 Registers */ +#define ECC_ERROR_LOCATION_MASK 0x1fff + +#define ELM_ECC_SIZE 0x7ff + +#define SYNDROME_FRAGMENT_REG_SIZE 0x40 +#define ERROR_LOCATION_SIZE 0x100 + +struct elm_registers { + u32 elm_irqenable; + u32 elm_sysconfig; + u32 elm_location_config; + u32 elm_page_ctrl; + u32 elm_syndrome_fragment_6[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_5[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_4[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_3[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_2[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_1[ERROR_VECTOR_MAX]; + u32 elm_syndrome_fragment_0[ERROR_VECTOR_MAX]; +}; + +struct elm_info { + struct device *dev; + void __iomem *elm_base; + struct completion elm_completion; + struct list_head list; + enum bch_ecc bch_type; + struct elm_registers elm_regs; + int ecc_steps; + int ecc_syndrome_size; +}; + +static LIST_HEAD(elm_devices); + +static void elm_write_reg(struct elm_info *info, int offset, u32 val) +{ + writel(val, info->elm_base + offset); +} + +static u32 elm_read_reg(struct elm_info *info, int offset) +{ + return readl(info->elm_base + offset); +} + +/** + * elm_config - Configure ELM module + * @dev: ELM device + * @bch_type: Type of BCH ecc + * @ecc_steps: ECC steps to assign to config + * @ecc_step_size: ECC step size to assign to config + * @ecc_syndrome_size: ECC syndrome size to assign to config + */ +int elm_config(struct device *dev, enum bch_ecc bch_type, + int ecc_steps, int ecc_step_size, int ecc_syndrome_size) +{ + u32 reg_val; + struct elm_info *info = dev_get_drvdata(dev); + + if (!info) { + dev_err(dev, "Unable to configure elm - device not probed?\n"); + return -EPROBE_DEFER; + } + /* ELM cannot detect ECC errors for chunks > 1KB */ + if (ecc_step_size > ((ELM_ECC_SIZE + 1) / 2)) { + dev_err(dev, "unsupported config ecc-size=%d\n", ecc_step_size); + return -EINVAL; + } + /* ELM support 8 error syndrome process */ + if (ecc_steps > ERROR_VECTOR_MAX && ecc_steps % ERROR_VECTOR_MAX) { + dev_err(dev, "unsupported config ecc-step=%d\n", ecc_steps); + return -EINVAL; + } + + reg_val = (bch_type & ECC_BCH_LEVEL_MASK) | (ELM_ECC_SIZE << 16); + elm_write_reg(info, ELM_LOCATION_CONFIG, reg_val); + info->bch_type = bch_type; + info->ecc_steps = ecc_steps; + info->ecc_syndrome_size = ecc_syndrome_size; + + return 0; +} +EXPORT_SYMBOL(elm_config); + +/** + * elm_configure_page_mode - Enable/Disable page mode + * @info: elm info + * @index: index number of syndrome fragment vector + * @enable: enable/disable flag for page mode + * + * Enable page mode for syndrome fragment index + */ +static void elm_configure_page_mode(struct elm_info *info, int index, + bool enable) +{ + u32 reg_val; + + reg_val = elm_read_reg(info, ELM_PAGE_CTRL); + if (enable) + reg_val |= BIT(index); /* enable page mode */ + else + reg_val &= ~BIT(index); /* disable page mode */ + + elm_write_reg(info, ELM_PAGE_CTRL, reg_val); +} + +/** + * elm_load_syndrome - Load ELM syndrome reg + * @info: elm info + * @err_vec: elm error vectors + * @ecc: buffer with calculated ecc + * + * Load syndrome fragment registers with calculated ecc in reverse order. + */ +static void elm_load_syndrome(struct elm_info *info, + struct elm_errorvec *err_vec, u8 *ecc) +{ + int i, offset; + u32 val; + + for (i = 0; i < info->ecc_steps; i++) { + + /* Check error reported */ + if (err_vec[i].error_reported) { + elm_configure_page_mode(info, i, true); + offset = ELM_SYNDROME_FRAGMENT_0 + + SYNDROME_FRAGMENT_REG_SIZE * i; + switch (info->bch_type) { + case BCH8_ECC: + /* syndrome fragment 0 = ecc[9-12B] */ + val = (__force u32)cpu_to_be32(*(u32 *)&ecc[9]); + elm_write_reg(info, offset, val); + + /* syndrome fragment 1 = ecc[5-8B] */ + offset += 4; + val = (__force u32)cpu_to_be32(*(u32 *)&ecc[5]); + elm_write_reg(info, offset, val); + + /* syndrome fragment 2 = ecc[1-4B] */ + offset += 4; + val = (__force u32)cpu_to_be32(*(u32 *)&ecc[1]); + elm_write_reg(info, offset, val); + + /* syndrome fragment 3 = ecc[0B] */ + offset += 4; + val = ecc[0]; + elm_write_reg(info, offset, val); + break; + case BCH4_ECC: + /* syndrome fragment 0 = ecc[20-52b] bits */ + val = ((__force u32)cpu_to_be32(*(u32 *)&ecc[3]) >> 4) | + ((ecc[2] & 0xf) << 28); + elm_write_reg(info, offset, val); + + /* syndrome fragment 1 = ecc[0-20b] bits */ + offset += 4; + val = (__force u32)cpu_to_be32(*(u32 *)&ecc[0]) >> 12; + elm_write_reg(info, offset, val); + break; + case BCH16_ECC: + val = (__force u32)cpu_to_be32(*(u32 *)&ecc[22]); + elm_write_reg(info, offset, val); + offset += 4; + val = (__force u32)cpu_to_be32(*(u32 *)&ecc[18]); + elm_write_reg(info, offset, val); + offset += 4; + val = (__force u32)cpu_to_be32(*(u32 *)&ecc[14]); + elm_write_reg(info, offset, val); + offset += 4; + val = (__force u32)cpu_to_be32(*(u32 *)&ecc[10]); + elm_write_reg(info, offset, val); + offset += 4; + val = (__force u32)cpu_to_be32(*(u32 *)&ecc[6]); + elm_write_reg(info, offset, val); + offset += 4; + val = (__force u32)cpu_to_be32(*(u32 *)&ecc[2]); + elm_write_reg(info, offset, val); + offset += 4; + val = (__force u32)cpu_to_be32(*(u32 *)&ecc[0]) >> 16; + elm_write_reg(info, offset, val); + break; + default: + pr_err("invalid config bch_type\n"); + } + } + + /* Update ecc pointer with ecc byte size */ + ecc += info->ecc_syndrome_size; + } +} + +/** + * elm_start_processing - start elm syndrome processing + * @info: elm info + * @err_vec: elm error vectors + * + * Set syndrome valid bit for syndrome fragment registers for which + * elm syndrome fragment registers are loaded. This enables elm module + * to start processing syndrome vectors. + */ +static void elm_start_processing(struct elm_info *info, + struct elm_errorvec *err_vec) +{ + int i, offset; + u32 reg_val; + + /* + * Set syndrome vector valid, so that ELM module + * will process it for vectors error is reported + */ + for (i = 0; i < info->ecc_steps; i++) { + if (err_vec[i].error_reported) { + offset = ELM_SYNDROME_FRAGMENT_6 + + SYNDROME_FRAGMENT_REG_SIZE * i; + reg_val = elm_read_reg(info, offset); + reg_val |= ELM_SYNDROME_VALID; + elm_write_reg(info, offset, reg_val); + } + } +} + +/** + * elm_error_correction - locate correctable error position + * @info: elm info + * @err_vec: elm error vectors + * + * On completion of processing by elm module, error location status + * register updated with correctable/uncorrectable error information. + * In case of correctable errors, number of errors located from + * elm location status register & read the positions from + * elm error location register. + */ +static void elm_error_correction(struct elm_info *info, + struct elm_errorvec *err_vec) +{ + int i, j; + int offset; + u32 reg_val; + + for (i = 0; i < info->ecc_steps; i++) { + + /* Check error reported */ + if (err_vec[i].error_reported) { + offset = ELM_LOCATION_STATUS + ERROR_LOCATION_SIZE * i; + reg_val = elm_read_reg(info, offset); + + /* Check correctable error or not */ + if (reg_val & ECC_CORRECTABLE_MASK) { + offset = ELM_ERROR_LOCATION_0 + + ERROR_LOCATION_SIZE * i; + + /* Read count of correctable errors */ + err_vec[i].error_count = reg_val & + ECC_NB_ERRORS_MASK; + + /* Update the error locations in error vector */ + for (j = 0; j < err_vec[i].error_count; j++) { + + reg_val = elm_read_reg(info, offset); + err_vec[i].error_loc[j] = reg_val & + ECC_ERROR_LOCATION_MASK; + + /* Update error location register */ + offset += 4; + } + } else { + err_vec[i].error_uncorrectable = true; + } + + /* Clearing interrupts for processed error vectors */ + elm_write_reg(info, ELM_IRQSTATUS, BIT(i)); + + /* Disable page mode */ + elm_configure_page_mode(info, i, false); + } + } +} + +/** + * elm_decode_bch_error_page - Locate error position + * @dev: device pointer + * @ecc_calc: calculated ECC bytes from GPMC + * @err_vec: elm error vectors + * + * Called with one or more error reported vectors & vectors with + * error reported is updated in err_vec[].error_reported + */ +void elm_decode_bch_error_page(struct device *dev, u8 *ecc_calc, + struct elm_errorvec *err_vec) +{ + struct elm_info *info = dev_get_drvdata(dev); + u32 reg_val; + + /* Enable page mode interrupt */ + reg_val = elm_read_reg(info, ELM_IRQSTATUS); + elm_write_reg(info, ELM_IRQSTATUS, reg_val & INTR_STATUS_PAGE_VALID); + elm_write_reg(info, ELM_IRQENABLE, INTR_EN_PAGE_MASK); + + /* Load valid ecc byte to syndrome fragment register */ + elm_load_syndrome(info, err_vec, ecc_calc); + + /* Enable syndrome processing for which syndrome fragment is updated */ + elm_start_processing(info, err_vec); + + /* Wait for ELM module to finish locating error correction */ + wait_for_completion(&info->elm_completion); + + /* Disable page mode interrupt */ + reg_val = elm_read_reg(info, ELM_IRQENABLE); + elm_write_reg(info, ELM_IRQENABLE, reg_val & ~INTR_EN_PAGE_MASK); + elm_error_correction(info, err_vec); +} +EXPORT_SYMBOL(elm_decode_bch_error_page); + +static irqreturn_t elm_isr(int this_irq, void *dev_id) +{ + u32 reg_val; + struct elm_info *info = dev_id; + + reg_val = elm_read_reg(info, ELM_IRQSTATUS); + + /* All error vectors processed */ + if (reg_val & INTR_STATUS_PAGE_VALID) { + elm_write_reg(info, ELM_IRQSTATUS, + reg_val & INTR_STATUS_PAGE_VALID); + complete(&info->elm_completion); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static int elm_probe(struct platform_device *pdev) +{ + int ret = 0; + struct elm_info *info; + int irq; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = &pdev->dev; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + info->elm_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(info->elm_base)) + return PTR_ERR(info->elm_base); + + ret = devm_request_irq(&pdev->dev, irq, elm_isr, 0, + pdev->name, info); + if (ret) { + dev_err(&pdev->dev, "failure requesting %d\n", irq); + return ret; + } + + pm_runtime_enable(&pdev->dev); + if (pm_runtime_get_sync(&pdev->dev) < 0) { + ret = -EINVAL; + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + dev_err(&pdev->dev, "can't enable clock\n"); + return ret; + } + + init_completion(&info->elm_completion); + INIT_LIST_HEAD(&info->list); + list_add(&info->list, &elm_devices); + platform_set_drvdata(pdev, info); + return ret; +} + +static void elm_remove(struct platform_device *pdev) +{ + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); +} + +#ifdef CONFIG_PM_SLEEP +/* + * elm_context_save + * saves ELM configurations to preserve them across Hardware powered-down + */ +static int elm_context_save(struct elm_info *info) +{ + struct elm_registers *regs = &info->elm_regs; + enum bch_ecc bch_type = info->bch_type; + u32 offset = 0, i; + + regs->elm_irqenable = elm_read_reg(info, ELM_IRQENABLE); + regs->elm_sysconfig = elm_read_reg(info, ELM_SYSCONFIG); + regs->elm_location_config = elm_read_reg(info, ELM_LOCATION_CONFIG); + regs->elm_page_ctrl = elm_read_reg(info, ELM_PAGE_CTRL); + for (i = 0; i < ERROR_VECTOR_MAX; i++) { + offset = i * SYNDROME_FRAGMENT_REG_SIZE; + switch (bch_type) { + case BCH16_ECC: + regs->elm_syndrome_fragment_6[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_6 + offset); + regs->elm_syndrome_fragment_5[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_5 + offset); + regs->elm_syndrome_fragment_4[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_4 + offset); + fallthrough; + case BCH8_ECC: + regs->elm_syndrome_fragment_3[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_3 + offset); + regs->elm_syndrome_fragment_2[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_2 + offset); + fallthrough; + case BCH4_ECC: + regs->elm_syndrome_fragment_1[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_1 + offset); + regs->elm_syndrome_fragment_0[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_0 + offset); + break; + default: + return -EINVAL; + } + /* ELM SYNDROME_VALID bit in SYNDROME_FRAGMENT_6[] needs + * to be saved for all BCH schemes*/ + regs->elm_syndrome_fragment_6[i] = elm_read_reg(info, + ELM_SYNDROME_FRAGMENT_6 + offset); + } + return 0; +} + +/* + * elm_context_restore + * writes configurations saved duing power-down back into ELM registers + */ +static int elm_context_restore(struct elm_info *info) +{ + struct elm_registers *regs = &info->elm_regs; + enum bch_ecc bch_type = info->bch_type; + u32 offset = 0, i; + + elm_write_reg(info, ELM_IRQENABLE, regs->elm_irqenable); + elm_write_reg(info, ELM_SYSCONFIG, regs->elm_sysconfig); + elm_write_reg(info, ELM_LOCATION_CONFIG, regs->elm_location_config); + elm_write_reg(info, ELM_PAGE_CTRL, regs->elm_page_ctrl); + for (i = 0; i < ERROR_VECTOR_MAX; i++) { + offset = i * SYNDROME_FRAGMENT_REG_SIZE; + switch (bch_type) { + case BCH16_ECC: + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_6 + offset, + regs->elm_syndrome_fragment_6[i]); + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_5 + offset, + regs->elm_syndrome_fragment_5[i]); + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_4 + offset, + regs->elm_syndrome_fragment_4[i]); + fallthrough; + case BCH8_ECC: + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_3 + offset, + regs->elm_syndrome_fragment_3[i]); + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_2 + offset, + regs->elm_syndrome_fragment_2[i]); + fallthrough; + case BCH4_ECC: + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_1 + offset, + regs->elm_syndrome_fragment_1[i]); + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_0 + offset, + regs->elm_syndrome_fragment_0[i]); + break; + default: + return -EINVAL; + } + /* ELM_SYNDROME_VALID bit to be set in last to trigger FSM */ + elm_write_reg(info, ELM_SYNDROME_FRAGMENT_6 + offset, + regs->elm_syndrome_fragment_6[i] & + ELM_SYNDROME_VALID); + } + return 0; +} + +static int elm_suspend(struct device *dev) +{ + struct elm_info *info = dev_get_drvdata(dev); + elm_context_save(info); + pm_runtime_put_sync(dev); + return 0; +} + +static int elm_resume(struct device *dev) +{ + struct elm_info *info = dev_get_drvdata(dev); + pm_runtime_get_sync(dev); + elm_context_restore(info); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(elm_pm_ops, elm_suspend, elm_resume); + +#ifdef CONFIG_OF +static const struct of_device_id elm_of_match[] = { + { .compatible = "ti,am3352-elm" }, + { .compatible = "ti,am64-elm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, elm_of_match); +#endif + +static struct platform_driver elm_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = of_match_ptr(elm_of_match), + .pm = &elm_pm_ops, + }, + .probe = elm_probe, + .remove = elm_remove, +}; + +module_platform_driver(elm_driver); + +MODULE_DESCRIPTION("ELM driver for BCH error correction"); +MODULE_AUTHOR("Texas Instruments"); +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/raw/orion_nand.c b/drivers/mtd/nand/raw/orion_nand.c new file mode 100644 index 000000000..47e80d5e5 --- /dev/null +++ b/drivers/mtd/nand/raw/orion_nand.c @@ -0,0 +1,229 @@ +/* + * NAND support for Marvell Orion SoC platforms + * + * Tzachi Perelstein + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct orion_nand_info { + struct nand_controller controller; + struct nand_chip chip; + struct clk *clk; +}; + +static void orion_nand_cmd_ctrl(struct nand_chip *nc, int cmd, + unsigned int ctrl) +{ + struct orion_nand_data *board = nand_get_controller_data(nc); + u32 offs; + + if (cmd == NAND_CMD_NONE) + return; + + if (ctrl & NAND_CLE) + offs = (1 << board->cle); + else if (ctrl & NAND_ALE) + offs = (1 << board->ale); + else + return; + + if (nc->options & NAND_BUSWIDTH_16) + offs <<= 1; + + writeb(cmd, nc->legacy.IO_ADDR_W + offs); +} + +static void orion_nand_read_buf(struct nand_chip *chip, uint8_t *buf, int len) +{ + void __iomem *io_base = chip->legacy.IO_ADDR_R; +#if defined(__LINUX_ARM_ARCH__) && __LINUX_ARM_ARCH__ >= 5 + uint64_t *buf64; +#endif + int i = 0; + + while (len && (unsigned long)buf & 7) { + *buf++ = readb(io_base); + len--; + } +#if defined(__LINUX_ARM_ARCH__) && __LINUX_ARM_ARCH__ >= 5 + buf64 = (uint64_t *)buf; + while (i < len/8) { + /* + * Since GCC has no proper constraint (PR 43518) + * force x variable to r2/r3 registers as ldrd instruction + * requires first register to be even. + */ + register uint64_t x asm ("r2"); + + asm volatile ("ldrd\t%0, [%1]" : "=&r" (x) : "r" (io_base)); + buf64[i++] = x; + } + i *= 8; +#else + readsl(io_base, buf, len/4); + i = len / 4 * 4; +#endif + while (i < len) + buf[i++] = readb(io_base); +} + +static int orion_nand_attach_chip(struct nand_chip *chip) +{ + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT && + chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + + return 0; +} + +static const struct nand_controller_ops orion_nand_ops = { + .attach_chip = orion_nand_attach_chip, +}; + +static int __init orion_nand_probe(struct platform_device *pdev) +{ + struct orion_nand_info *info; + struct mtd_info *mtd; + struct nand_chip *nc; + struct orion_nand_data *board; + void __iomem *io_base; + int ret = 0; + u32 val = 0; + + info = devm_kzalloc(&pdev->dev, + sizeof(struct orion_nand_info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + nc = &info->chip; + mtd = nand_to_mtd(nc); + + nand_controller_init(&info->controller); + info->controller.ops = &orion_nand_ops; + nc->controller = &info->controller; + + io_base = devm_platform_ioremap_resource(pdev, 0); + + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + if (pdev->dev.of_node) { + board = devm_kzalloc(&pdev->dev, sizeof(struct orion_nand_data), + GFP_KERNEL); + if (!board) + return -ENOMEM; + if (!of_property_read_u32(pdev->dev.of_node, "cle", &val)) + board->cle = (u8)val; + else + board->cle = 0; + if (!of_property_read_u32(pdev->dev.of_node, "ale", &val)) + board->ale = (u8)val; + else + board->ale = 1; + if (!of_property_read_u32(pdev->dev.of_node, + "bank-width", &val)) + board->width = (u8)val * 8; + else + board->width = 8; + if (!of_property_read_u32(pdev->dev.of_node, + "chip-delay", &val)) + board->chip_delay = (u8)val; + } else { + board = dev_get_platdata(&pdev->dev); + } + + mtd->dev.parent = &pdev->dev; + + nand_set_controller_data(nc, board); + nand_set_flash_node(nc, pdev->dev.of_node); + nc->legacy.IO_ADDR_R = nc->legacy.IO_ADDR_W = io_base; + nc->legacy.cmd_ctrl = orion_nand_cmd_ctrl; + nc->legacy.read_buf = orion_nand_read_buf; + + if (board->chip_delay) + nc->legacy.chip_delay = board->chip_delay; + + WARN(board->width > 16, + "%d bit bus width out of range", + board->width); + + if (board->width == 16) + nc->options |= NAND_BUSWIDTH_16; + + platform_set_drvdata(pdev, info); + + /* Not all platforms can gate the clock, so it is optional. */ + info->clk = devm_clk_get_optional_enabled(&pdev->dev, NULL); + if (IS_ERR(info->clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(info->clk), + "failed to get and enable clock!\n"); + + /* + * This driver assumes that the default ECC engine should be TYPE_SOFT. + * Set ->engine_type before registering the NAND devices in order to + * provide a driver specific default value. + */ + nc->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + + ret = nand_scan(nc, 1); + if (ret) + return ret; + + mtd->name = "orion_nand"; + ret = mtd_device_register(mtd, board->parts, board->nr_parts); + if (ret) + nand_cleanup(nc); + + return ret; +} + +static void orion_nand_remove(struct platform_device *pdev) +{ + struct orion_nand_info *info = platform_get_drvdata(pdev); + struct nand_chip *chip = &info->chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + + nand_cleanup(chip); +} + +#ifdef CONFIG_OF +static const struct of_device_id orion_nand_of_match_table[] = { + { .compatible = "marvell,orion-nand", }, + {}, +}; +MODULE_DEVICE_TABLE(of, orion_nand_of_match_table); +#endif + +static struct platform_driver orion_nand_driver = { + .remove = orion_nand_remove, + .driver = { + .name = "orion_nand", + .of_match_table = of_match_ptr(orion_nand_of_match_table), + }, +}; + +module_platform_driver_probe(orion_nand_driver, orion_nand_probe); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Tzachi Perelstein"); +MODULE_DESCRIPTION("NAND glue for Orion platforms"); +MODULE_ALIAS("platform:orion_nand"); diff --git a/drivers/mtd/nand/raw/pasemi_nand.c b/drivers/mtd/nand/raw/pasemi_nand.c new file mode 100644 index 000000000..09409b703 --- /dev/null +++ b/drivers/mtd/nand/raw/pasemi_nand.c @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2006-2007 PA Semi, Inc + * + * Author: Egor Martovetsky + * Maintained by: Olof Johansson + * + * Driver for the PWRficient onchip NAND flash interface + */ + +#undef DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define LBICTRL_LPCCTL_NR 0x00004000 +#define CLE_PIN_CTL 15 +#define ALE_PIN_CTL 14 + +struct pasemi_ddata { + struct nand_chip chip; + unsigned int lpcctl; + struct nand_controller controller; +}; + +static const char driver_name[] = "pasemi-nand"; + +static void pasemi_read_buf(struct nand_chip *chip, u_char *buf, int len) +{ + while (len > 0x800) { + memcpy_fromio(buf, chip->legacy.IO_ADDR_R, 0x800); + buf += 0x800; + len -= 0x800; + } + memcpy_fromio(buf, chip->legacy.IO_ADDR_R, len); +} + +static void pasemi_write_buf(struct nand_chip *chip, const u_char *buf, + int len) +{ + while (len > 0x800) { + memcpy_toio(chip->legacy.IO_ADDR_R, buf, 0x800); + buf += 0x800; + len -= 0x800; + } + memcpy_toio(chip->legacy.IO_ADDR_R, buf, len); +} + +static void pasemi_hwcontrol(struct nand_chip *chip, int cmd, + unsigned int ctrl) +{ + struct pasemi_ddata *ddata = container_of(chip, struct pasemi_ddata, chip); + + if (cmd == NAND_CMD_NONE) + return; + + if (ctrl & NAND_CLE) + out_8(chip->legacy.IO_ADDR_W + (1 << CLE_PIN_CTL), cmd); + else + out_8(chip->legacy.IO_ADDR_W + (1 << ALE_PIN_CTL), cmd); + + /* Push out posted writes */ + eieio(); + inl(ddata->lpcctl); +} + +static int pasemi_device_ready(struct nand_chip *chip) +{ + struct pasemi_ddata *ddata = container_of(chip, struct pasemi_ddata, chip); + + return !!(inl(ddata->lpcctl) & LBICTRL_LPCCTL_NR); +} + +static int pasemi_attach_chip(struct nand_chip *chip) +{ + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT && + chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + + return 0; +} + +static const struct nand_controller_ops pasemi_ops = { + .attach_chip = pasemi_attach_chip, +}; + +static int pasemi_nand_probe(struct platform_device *ofdev) +{ + struct device *dev = &ofdev->dev; + struct pci_dev *pdev; + struct device_node *np = dev->of_node; + struct resource res; + struct nand_chip *chip; + struct nand_controller *controller; + int err = 0; + struct pasemi_ddata *ddata; + struct mtd_info *pasemi_nand_mtd; + + err = of_address_to_resource(np, 0, &res); + + if (err) + return -EINVAL; + + dev_dbg(dev, "pasemi_nand at %pR\n", &res); + + /* Allocate memory for MTD device structure and private data */ + ddata = kzalloc_obj(*ddata); + if (!ddata) { + err = -ENOMEM; + goto out; + } + platform_set_drvdata(ofdev, ddata); + chip = &ddata->chip; + controller = &ddata->controller; + + controller->ops = &pasemi_ops; + nand_controller_init(controller); + chip->controller = controller; + + pasemi_nand_mtd = nand_to_mtd(chip); + + /* Link the private data with the MTD structure */ + pasemi_nand_mtd->dev.parent = dev; + + chip->legacy.IO_ADDR_R = of_iomap(np, 0); + chip->legacy.IO_ADDR_W = chip->legacy.IO_ADDR_R; + + if (!chip->legacy.IO_ADDR_R) { + err = -EIO; + goto out_mtd; + } + + pdev = pci_get_device(PCI_VENDOR_ID_PASEMI, 0xa008, NULL); + if (!pdev) { + err = -ENODEV; + goto out_ior; + } + + ddata->lpcctl = pci_resource_start(pdev, 0); + pci_dev_put(pdev); + + if (!request_region(ddata->lpcctl, 4, driver_name)) { + err = -EBUSY; + goto out_ior; + } + + chip->legacy.cmd_ctrl = pasemi_hwcontrol; + chip->legacy.dev_ready = pasemi_device_ready; + chip->legacy.read_buf = pasemi_read_buf; + chip->legacy.write_buf = pasemi_write_buf; + chip->legacy.chip_delay = 0; + + /* Enable the following for a flash based bad block table */ + chip->bbt_options = NAND_BBT_USE_FLASH; + + /* + * This driver assumes that the default ECC engine should be TYPE_SOFT. + * Set ->engine_type before registering the NAND devices in order to + * provide a driver specific default value. + */ + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + + /* Scan to find existence of the device */ + err = nand_scan(chip, 1); + if (err) + goto out_lpc; + + if (mtd_device_register(pasemi_nand_mtd, NULL, 0)) { + dev_err(dev, "Unable to register MTD device\n"); + err = -ENODEV; + goto out_cleanup_nand; + } + + dev_info(dev, "PA Semi NAND flash at %pR, control at I/O %x\n", &res, + ddata->lpcctl); + + return 0; + + out_cleanup_nand: + nand_cleanup(chip); + out_lpc: + release_region(ddata->lpcctl, 4); + out_ior: + iounmap(chip->legacy.IO_ADDR_R); + out_mtd: + kfree(ddata); + out: + return err; +} + +static void pasemi_nand_remove(struct platform_device *ofdev) +{ + struct pasemi_ddata *ddata = platform_get_drvdata(ofdev); + struct mtd_info *pasemi_nand_mtd; + int ret; + struct nand_chip *chip; + + chip = &ddata->chip; + pasemi_nand_mtd = nand_to_mtd(chip); + + /* Release resources, unregister device */ + ret = mtd_device_unregister(pasemi_nand_mtd); + WARN_ON(ret); + nand_cleanup(chip); + + release_region(ddata->lpcctl, 4); + + iounmap(chip->legacy.IO_ADDR_R); + + /* Free the MTD device structure */ + kfree(ddata); +} + +static const struct of_device_id pasemi_nand_match[] = +{ + { + .compatible = "pasemi,localbus-nand", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, pasemi_nand_match); + +static struct platform_driver pasemi_nand_driver = +{ + .driver = { + .name = driver_name, + .of_match_table = pasemi_nand_match, + }, + .probe = pasemi_nand_probe, + .remove = pasemi_nand_remove, +}; + +module_platform_driver(pasemi_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Egor Martovetsky "); +MODULE_DESCRIPTION("NAND flash interface driver for PA Semi PWRficient"); diff --git a/drivers/mtd/nand/raw/pl35x-nand-controller.c b/drivers/mtd/nand/raw/pl35x-nand-controller.c new file mode 100644 index 000000000..bd89aaadd --- /dev/null +++ b/drivers/mtd/nand/raw/pl35x-nand-controller.c @@ -0,0 +1,1210 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM PL35X NAND flash controller driver + * + * Copyright (C) 2017 Xilinx, Inc + * Author: + * Miquel Raynal + * Original work (rewritten): + * Punnaiah Choudary Kalluri + * Naga Sureshkumar Relli + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PL35X_NANDC_DRIVER_NAME "pl35x-nand-controller" + +/* SMC controller status register (RO) */ +#define PL35X_SMC_MEMC_STATUS 0x0 +#define PL35X_SMC_MEMC_STATUS_RAW_INT_STATUS1 BIT(6) +/* SMC clear config register (WO) */ +#define PL35X_SMC_MEMC_CFG_CLR 0xC +#define PL35X_SMC_MEMC_CFG_CLR_INT_DIS_1 BIT(1) +#define PL35X_SMC_MEMC_CFG_CLR_INT_CLR_1 BIT(4) +#define PL35X_SMC_MEMC_CFG_CLR_ECC_INT_DIS_1 BIT(6) +/* SMC direct command register (WO) */ +#define PL35X_SMC_DIRECT_CMD 0x10 +#define PL35X_SMC_DIRECT_CMD_NAND_CS (0x4 << 23) +#define PL35X_SMC_DIRECT_CMD_UPD_REGS (0x2 << 21) +/* SMC set cycles register (WO) */ +#define PL35X_SMC_CYCLES 0x14 +#define PL35X_SMC_NAND_TRC_CYCLES(x) ((x) << 0) +#define PL35X_SMC_NAND_TWC_CYCLES(x) ((x) << 4) +#define PL35X_SMC_NAND_TREA_CYCLES(x) ((x) << 8) +#define PL35X_SMC_NAND_TWP_CYCLES(x) ((x) << 11) +#define PL35X_SMC_NAND_TCLR_CYCLES(x) ((x) << 14) +#define PL35X_SMC_NAND_TAR_CYCLES(x) ((x) << 17) +#define PL35X_SMC_NAND_TRR_CYCLES(x) ((x) << 20) +/* SMC set opmode register (WO) */ +#define PL35X_SMC_OPMODE 0x18 +#define PL35X_SMC_OPMODE_BW_8 0 +#define PL35X_SMC_OPMODE_BW_16 1 +/* SMC ECC status register (RO) */ +#define PL35X_SMC_ECC_STATUS 0x400 +#define PL35X_SMC_ECC_STATUS_ECC_BUSY BIT(6) +/* SMC ECC configuration register */ +#define PL35X_SMC_ECC_CFG 0x404 +#define PL35X_SMC_ECC_CFG_MODE_MASK 0xC +#define PL35X_SMC_ECC_CFG_MODE_BYPASS 0 +#define PL35X_SMC_ECC_CFG_MODE_APB BIT(2) +#define PL35X_SMC_ECC_CFG_MODE_MEM BIT(3) +#define PL35X_SMC_ECC_CFG_PGSIZE_MASK 0x3 +/* SMC ECC command 1 register */ +#define PL35X_SMC_ECC_CMD1 0x408 +#define PL35X_SMC_ECC_CMD1_WRITE(x) ((x) << 0) +#define PL35X_SMC_ECC_CMD1_READ(x) ((x) << 8) +#define PL35X_SMC_ECC_CMD1_READ_END(x) ((x) << 16) +#define PL35X_SMC_ECC_CMD1_READ_END_VALID(x) ((x) << 24) +/* SMC ECC command 2 register */ +#define PL35X_SMC_ECC_CMD2 0x40C +#define PL35X_SMC_ECC_CMD2_WRITE_COL_CHG(x) ((x) << 0) +#define PL35X_SMC_ECC_CMD2_READ_COL_CHG(x) ((x) << 8) +#define PL35X_SMC_ECC_CMD2_READ_COL_CHG_END(x) ((x) << 16) +#define PL35X_SMC_ECC_CMD2_READ_COL_CHG_END_VALID(x) ((x) << 24) +/* SMC ECC value registers (RO) */ +#define PL35X_SMC_ECC_VALUE(x) (0x418 + (4 * (x))) +#define PL35X_SMC_ECC_VALUE_IS_CORRECTABLE(x) ((x) & BIT(27)) +#define PL35X_SMC_ECC_VALUE_HAS_FAILED(x) ((x) & BIT(28)) +#define PL35X_SMC_ECC_VALUE_IS_VALID(x) ((x) & BIT(30)) + +/* NAND AXI interface */ +#define PL35X_SMC_CMD_PHASE 0 +#define PL35X_SMC_CMD_PHASE_CMD0(x) ((x) << 3) +#define PL35X_SMC_CMD_PHASE_CMD1(x) ((x) << 11) +#define PL35X_SMC_CMD_PHASE_CMD1_VALID BIT(20) +#define PL35X_SMC_CMD_PHASE_ADDR(pos, x) ((x) << (8 * (pos))) +#define PL35X_SMC_CMD_PHASE_NADDRS(x) ((x) << 21) +#define PL35X_SMC_DATA_PHASE BIT(19) +#define PL35X_SMC_DATA_PHASE_ECC_LAST BIT(10) +#define PL35X_SMC_DATA_PHASE_CLEAR_CS BIT(21) + +#define PL35X_NAND_MAX_CS 1 +#define PL35X_NAND_LAST_XFER_SZ 4 +#define TO_CYCLES(ps, period_ns) (DIV_ROUND_UP((ps) / 1000, period_ns)) + +#define PL35X_NAND_ECC_BITS_MASK 0xFFF +#define PL35X_NAND_ECC_BYTE_OFF_MASK 0x1FF +#define PL35X_NAND_ECC_BIT_OFF_MASK 0x7 + +struct pl35x_nand_timings { + unsigned int t_rc:4; + unsigned int t_wc:4; + unsigned int t_rea:3; + unsigned int t_wp:3; + unsigned int t_clr:3; + unsigned int t_ar:3; + unsigned int t_rr:4; + unsigned int rsvd:8; +}; + +struct pl35x_nand { + struct list_head node; + struct nand_chip chip; + unsigned int cs; + unsigned int addr_cycles; + u32 ecc_cfg; + u32 timings; +}; + +/** + * struct pl35x_nandc - NAND flash controller driver structure + * @dev: Kernel device + * @conf_regs: SMC configuration registers for command phase + * @io_regs: NAND data registers for data phase + * @controller: Core NAND controller structure + * @chips: List of connected NAND chips + * @selected_chip: NAND chip currently selected by the controller + * @assigned_cs: List of assigned CS + * @ecc_buf: Temporary buffer to extract ECC bytes + */ +struct pl35x_nandc { + struct device *dev; + void __iomem *conf_regs; + void __iomem *io_regs; + struct nand_controller controller; + struct list_head chips; + struct nand_chip *selected_chip; + unsigned long assigned_cs; + u8 *ecc_buf; +}; + +static inline struct pl35x_nandc *to_pl35x_nandc(struct nand_controller *ctrl) +{ + return container_of(ctrl, struct pl35x_nandc, controller); +} + +static inline struct pl35x_nand *to_pl35x_nand(struct nand_chip *chip) +{ + return container_of(chip, struct pl35x_nand, chip); +} + +static int pl35x_ecc_ooblayout16_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section >= chip->ecc.steps) + return -ERANGE; + + oobregion->offset = (section * chip->ecc.bytes); + oobregion->length = chip->ecc.bytes; + + return 0; +} + +static int pl35x_ecc_ooblayout16_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section >= chip->ecc.steps) + return -ERANGE; + + oobregion->offset = (section * chip->ecc.bytes) + 8; + oobregion->length = 8; + + return 0; +} + +static const struct mtd_ooblayout_ops pl35x_ecc_ooblayout16_ops = { + .ecc = pl35x_ecc_ooblayout16_ecc, + .free = pl35x_ecc_ooblayout16_free, +}; + +/* Generic flash bbt descriptors */ +static u8 bbt_pattern[] = { 'B', 'b', 't', '0' }; +static u8 mirror_pattern[] = { '1', 't', 'b', 'B' }; + +static struct nand_bbt_descr bbt_main_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 4, + .len = 4, + .veroffs = 20, + .maxblocks = 4, + .pattern = bbt_pattern +}; + +static struct nand_bbt_descr bbt_mirror_descr = { + .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE + | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, + .offs = 4, + .len = 4, + .veroffs = 20, + .maxblocks = 4, + .pattern = mirror_pattern +}; + +static void pl35x_smc_update_regs(struct pl35x_nandc *nfc) +{ + writel(PL35X_SMC_DIRECT_CMD_NAND_CS | + PL35X_SMC_DIRECT_CMD_UPD_REGS, + nfc->conf_regs + PL35X_SMC_DIRECT_CMD); +} + +static int pl35x_smc_set_buswidth(struct pl35x_nandc *nfc, unsigned int bw) +{ + if (bw != PL35X_SMC_OPMODE_BW_8 && bw != PL35X_SMC_OPMODE_BW_16) + return -EINVAL; + + writel(bw, nfc->conf_regs + PL35X_SMC_OPMODE); + pl35x_smc_update_regs(nfc); + + return 0; +} + +static void pl35x_smc_clear_irq(struct pl35x_nandc *nfc) +{ + writel(PL35X_SMC_MEMC_CFG_CLR_INT_CLR_1, + nfc->conf_regs + PL35X_SMC_MEMC_CFG_CLR); +} + +static int pl35x_smc_wait_for_irq(struct pl35x_nandc *nfc) +{ + u32 reg; + int ret; + + ret = readl_poll_timeout(nfc->conf_regs + PL35X_SMC_MEMC_STATUS, reg, + reg & PL35X_SMC_MEMC_STATUS_RAW_INT_STATUS1, + 10, 1000000); + if (ret) + dev_err(nfc->dev, + "Timeout polling on NAND controller interrupt (0x%x)\n", + reg); + + pl35x_smc_clear_irq(nfc); + + return ret; +} + +static int pl35x_smc_wait_for_ecc_done(struct pl35x_nandc *nfc) +{ + u32 reg; + int ret; + + ret = readl_poll_timeout(nfc->conf_regs + PL35X_SMC_ECC_STATUS, reg, + !(reg & PL35X_SMC_ECC_STATUS_ECC_BUSY), + 10, 1000000); + if (ret) + dev_err(nfc->dev, + "Timeout polling on ECC controller interrupt\n"); + + return ret; +} + +static int pl35x_smc_set_ecc_mode(struct pl35x_nandc *nfc, + struct nand_chip *chip, + unsigned int mode) +{ + struct pl35x_nand *plnand; + u32 ecc_cfg; + + ecc_cfg = readl(nfc->conf_regs + PL35X_SMC_ECC_CFG); + ecc_cfg &= ~PL35X_SMC_ECC_CFG_MODE_MASK; + ecc_cfg |= mode; + writel(ecc_cfg, nfc->conf_regs + PL35X_SMC_ECC_CFG); + + if (chip) { + plnand = to_pl35x_nand(chip); + plnand->ecc_cfg = ecc_cfg; + } + + if (mode != PL35X_SMC_ECC_CFG_MODE_BYPASS) + return pl35x_smc_wait_for_ecc_done(nfc); + + return 0; +} + +static void pl35x_smc_force_byte_access(struct nand_chip *chip, + bool force_8bit) +{ + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + int ret; + + if (!(chip->options & NAND_BUSWIDTH_16)) + return; + + if (force_8bit) + ret = pl35x_smc_set_buswidth(nfc, PL35X_SMC_OPMODE_BW_8); + else + ret = pl35x_smc_set_buswidth(nfc, PL35X_SMC_OPMODE_BW_16); + + if (ret) + dev_err(nfc->dev, "Error in Buswidth\n"); +} + +static void pl35x_nand_select_target(struct nand_chip *chip, + unsigned int die_nr) +{ + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + struct pl35x_nand *plnand = to_pl35x_nand(chip); + + if (chip == nfc->selected_chip) + return; + + /* Setup the timings */ + writel(plnand->timings, nfc->conf_regs + PL35X_SMC_CYCLES); + pl35x_smc_update_regs(nfc); + + /* Configure the ECC engine */ + writel(plnand->ecc_cfg, nfc->conf_regs + PL35X_SMC_ECC_CFG); + + nfc->selected_chip = chip; +} + +static void pl35x_nand_read_data_op(struct nand_chip *chip, u8 *in, + unsigned int len, bool force_8bit, + unsigned int flags, unsigned int last_flags) +{ + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + unsigned int buf_end = len / 4; + unsigned int in_start = round_down(len, 4); + unsigned int data_phase_addr; + u32 *buf32 = (u32 *)in; + u8 *buf8 = (u8 *)in; + int i; + + if (force_8bit) + pl35x_smc_force_byte_access(chip, true); + + for (i = 0; i < buf_end; i++) { + data_phase_addr = PL35X_SMC_DATA_PHASE + flags; + if (i + 1 == buf_end) + data_phase_addr = PL35X_SMC_DATA_PHASE + last_flags; + + buf32[i] = readl(nfc->io_regs + data_phase_addr); + } + + /* No working extra flags on unaligned data accesses */ + for (i = in_start; i < len; i++) + buf8[i] = readb(nfc->io_regs + PL35X_SMC_DATA_PHASE); + + if (force_8bit) + pl35x_smc_force_byte_access(chip, false); +} + +static void pl35x_nand_write_data_op(struct nand_chip *chip, const u8 *out, + int len, bool force_8bit, + unsigned int flags, + unsigned int last_flags) +{ + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + unsigned int buf_end = len / 4; + unsigned int in_start = round_down(len, 4); + const u32 *buf32 = (const u32 *)out; + const u8 *buf8 = (const u8 *)out; + unsigned int data_phase_addr; + int i; + + if (force_8bit) + pl35x_smc_force_byte_access(chip, true); + + for (i = 0; i < buf_end; i++) { + data_phase_addr = PL35X_SMC_DATA_PHASE + flags; + if (i + 1 == buf_end) + data_phase_addr = PL35X_SMC_DATA_PHASE + last_flags; + + writel(buf32[i], nfc->io_regs + data_phase_addr); + } + + /* No working extra flags on unaligned data accesses */ + for (i = in_start; i < len; i++) + writeb(buf8[i], nfc->io_regs + PL35X_SMC_DATA_PHASE); + + if (force_8bit) + pl35x_smc_force_byte_access(chip, false); +} + +static int pl35x_nand_correct_data(struct pl35x_nandc *nfc, unsigned char *buf, + unsigned char *read_ecc, + unsigned char *calc_ecc) +{ + unsigned short ecc_odd, ecc_even, read_ecc_lower, read_ecc_upper; + unsigned short calc_ecc_lower, calc_ecc_upper; + unsigned short byte_addr, bit_addr; + + read_ecc_lower = (read_ecc[0] | (read_ecc[1] << 8)) & + PL35X_NAND_ECC_BITS_MASK; + read_ecc_upper = ((read_ecc[1] >> 4) | (read_ecc[2] << 4)) & + PL35X_NAND_ECC_BITS_MASK; + + calc_ecc_lower = (calc_ecc[0] | (calc_ecc[1] << 8)) & + PL35X_NAND_ECC_BITS_MASK; + calc_ecc_upper = ((calc_ecc[1] >> 4) | (calc_ecc[2] << 4)) & + PL35X_NAND_ECC_BITS_MASK; + + ecc_odd = read_ecc_lower ^ calc_ecc_lower; + ecc_even = read_ecc_upper ^ calc_ecc_upper; + + /* No error */ + if (likely(!ecc_odd && !ecc_even)) + return 0; + + /* One error in the main data; to be corrected */ + if (ecc_odd == (~ecc_even & PL35X_NAND_ECC_BITS_MASK)) { + /* Bits [11:3] of error code give the byte offset */ + byte_addr = (ecc_odd >> 3) & PL35X_NAND_ECC_BYTE_OFF_MASK; + /* Bits [2:0] of error code give the bit offset */ + bit_addr = ecc_odd & PL35X_NAND_ECC_BIT_OFF_MASK; + /* Toggle the faulty bit */ + buf[byte_addr] ^= (BIT(bit_addr)); + + return 1; + } + + /* One error in the ECC data; no action needed */ + if (hweight32(ecc_odd | ecc_even) == 1) + return 1; + + return -EBADMSG; +} + +static void pl35x_nand_ecc_reg_to_array(struct nand_chip *chip, u32 ecc_reg, + u8 *ecc_array) +{ + u32 ecc_value = ~ecc_reg; + unsigned int ecc_byte; + + for (ecc_byte = 0; ecc_byte < chip->ecc.bytes; ecc_byte++) + ecc_array[ecc_byte] = ecc_value >> (8 * ecc_byte); +} + +static int pl35x_nand_read_eccbytes(struct pl35x_nandc *nfc, + struct nand_chip *chip, u8 *read_ecc) +{ + u32 ecc_value; + int chunk; + + for (chunk = 0; chunk < chip->ecc.steps; + chunk++, read_ecc += chip->ecc.bytes) { + ecc_value = readl(nfc->conf_regs + PL35X_SMC_ECC_VALUE(chunk)); + if (!PL35X_SMC_ECC_VALUE_IS_VALID(ecc_value)) + return -EINVAL; + + pl35x_nand_ecc_reg_to_array(chip, ecc_value, read_ecc); + } + + return 0; +} + +static int pl35x_nand_recover_data_hwecc(struct pl35x_nandc *nfc, + struct nand_chip *chip, u8 *data, + u8 *read_ecc) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int max_bitflips = 0, chunk; + u8 calc_ecc[3]; + u32 ecc_value; + int stats; + + for (chunk = 0; chunk < chip->ecc.steps; + chunk++, data += chip->ecc.size, read_ecc += chip->ecc.bytes) { + /* Read ECC value for each chunk */ + ecc_value = readl(nfc->conf_regs + PL35X_SMC_ECC_VALUE(chunk)); + + if (!PL35X_SMC_ECC_VALUE_IS_VALID(ecc_value)) + return -EINVAL; + + if (PL35X_SMC_ECC_VALUE_HAS_FAILED(ecc_value)) { + mtd->ecc_stats.failed++; + continue; + } + + pl35x_nand_ecc_reg_to_array(chip, ecc_value, calc_ecc); + stats = pl35x_nand_correct_data(nfc, data, read_ecc, calc_ecc); + if (stats < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stats; + max_bitflips = max_t(unsigned int, max_bitflips, stats); + } + } + + return max_bitflips; +} + +static int pl35x_nand_write_page_hwecc(struct nand_chip *chip, + const u8 *buf, int oob_required, + int page) +{ + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + struct pl35x_nand *plnand = to_pl35x_nand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int first_row = (mtd->writesize <= 512) ? 1 : 2; + unsigned int nrows = plnand->addr_cycles; + u32 addr1 = 0, addr2 = 0, row; + u32 cmd_addr; + int i, ret; + u8 status; + + ret = pl35x_smc_set_ecc_mode(nfc, chip, PL35X_SMC_ECC_CFG_MODE_APB); + if (ret) + return ret; + + cmd_addr = PL35X_SMC_CMD_PHASE | + PL35X_SMC_CMD_PHASE_NADDRS(plnand->addr_cycles) | + PL35X_SMC_CMD_PHASE_CMD0(NAND_CMD_SEQIN); + + for (i = 0, row = first_row; row < nrows; i++, row++) { + u8 addr = page >> ((i * 8) & 0xFF); + + if (row < 4) + addr1 |= PL35X_SMC_CMD_PHASE_ADDR(row, addr); + else + addr2 |= PL35X_SMC_CMD_PHASE_ADDR(row - 4, addr); + } + + /* Send the command and address cycles */ + writel(addr1, nfc->io_regs + cmd_addr); + if (plnand->addr_cycles > 4) + writel(addr2, nfc->io_regs + cmd_addr); + + /* Write the data with the engine enabled */ + pl35x_nand_write_data_op(chip, buf, mtd->writesize, false, + 0, PL35X_SMC_DATA_PHASE_ECC_LAST); + ret = pl35x_smc_wait_for_ecc_done(nfc); + if (ret) + goto disable_ecc_engine; + + /* Copy the HW calculated ECC bytes in the OOB buffer */ + ret = pl35x_nand_read_eccbytes(nfc, chip, nfc->ecc_buf); + if (ret) + goto disable_ecc_engine; + + if (!oob_required) + memset(chip->oob_poi, 0xFF, mtd->oobsize); + + ret = mtd_ooblayout_set_eccbytes(mtd, nfc->ecc_buf, chip->oob_poi, + 0, chip->ecc.total); + if (ret) + goto disable_ecc_engine; + + /* Write the spare area with ECC bytes */ + pl35x_nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, false, 0, + PL35X_SMC_CMD_PHASE_CMD1(NAND_CMD_PAGEPROG) | + PL35X_SMC_CMD_PHASE_CMD1_VALID | + PL35X_SMC_DATA_PHASE_CLEAR_CS); + ret = pl35x_smc_wait_for_irq(nfc); + if (ret) + goto disable_ecc_engine; + + /* Check write status on the chip side */ + ret = nand_status_op(chip, &status); + if (ret) + goto disable_ecc_engine; + + if (status & NAND_STATUS_FAIL) + ret = -EIO; + +disable_ecc_engine: + pl35x_smc_set_ecc_mode(nfc, chip, PL35X_SMC_ECC_CFG_MODE_BYPASS); + + return ret; +} + +/* + * This functions reads data and checks the data integrity by comparing hardware + * generated ECC values and read ECC values from spare area. + * + * There is a limitation with SMC controller: ECC_LAST must be set on the + * last data access to tell the ECC engine not to expect any further data. + * In practice, this implies to shrink the last data transfert by eg. 4 bytes, + * and doing a last 4-byte transfer with the additional bit set. The last block + * should be aligned with the end of an ECC block. Because of this limitation, + * it is not possible to use the core routines. + */ +static int pl35x_nand_read_page_hwecc(struct nand_chip *chip, + u8 *buf, int oob_required, int page) +{ + const struct nand_sdr_timings *sdr = + nand_get_sdr_timings(nand_get_interface_config(chip)); + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + struct pl35x_nand *plnand = to_pl35x_nand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int first_row = (mtd->writesize <= 512) ? 1 : 2; + unsigned int nrows = plnand->addr_cycles; + unsigned int addr1 = 0, addr2 = 0, row; + u32 cmd_addr; + int i, ret; + + ret = pl35x_smc_set_ecc_mode(nfc, chip, PL35X_SMC_ECC_CFG_MODE_APB); + if (ret) + return ret; + + cmd_addr = PL35X_SMC_CMD_PHASE | + PL35X_SMC_CMD_PHASE_NADDRS(plnand->addr_cycles) | + PL35X_SMC_CMD_PHASE_CMD0(NAND_CMD_READ0) | + PL35X_SMC_CMD_PHASE_CMD1(NAND_CMD_READSTART) | + PL35X_SMC_CMD_PHASE_CMD1_VALID; + + for (i = 0, row = first_row; row < nrows; i++, row++) { + u8 addr = page >> ((i * 8) & 0xFF); + + if (row < 4) + addr1 |= PL35X_SMC_CMD_PHASE_ADDR(row, addr); + else + addr2 |= PL35X_SMC_CMD_PHASE_ADDR(row - 4, addr); + } + + /* Send the command and address cycles */ + writel(addr1, nfc->io_regs + cmd_addr); + if (plnand->addr_cycles > 4) + writel(addr2, nfc->io_regs + cmd_addr); + + /* Wait the data to be available in the NAND cache */ + ndelay(PSEC_TO_NSEC(sdr->tRR_min)); + ret = pl35x_smc_wait_for_irq(nfc); + if (ret) + goto disable_ecc_engine; + + /* Retrieve the raw data with the engine enabled */ + pl35x_nand_read_data_op(chip, buf, mtd->writesize, false, + 0, PL35X_SMC_DATA_PHASE_ECC_LAST); + ret = pl35x_smc_wait_for_ecc_done(nfc); + if (ret) + goto disable_ecc_engine; + + /* Retrieve the stored ECC bytes */ + pl35x_nand_read_data_op(chip, chip->oob_poi, mtd->oobsize, false, + 0, PL35X_SMC_DATA_PHASE_CLEAR_CS); + ret = mtd_ooblayout_get_eccbytes(mtd, nfc->ecc_buf, chip->oob_poi, 0, + chip->ecc.total); + if (ret) + goto disable_ecc_engine; + + pl35x_smc_set_ecc_mode(nfc, chip, PL35X_SMC_ECC_CFG_MODE_BYPASS); + + /* Correct the data and report failures */ + return pl35x_nand_recover_data_hwecc(nfc, chip, buf, nfc->ecc_buf); + +disable_ecc_engine: + pl35x_smc_set_ecc_mode(nfc, chip, PL35X_SMC_ECC_CFG_MODE_BYPASS); + + return ret; +} + +static int pl35x_nand_exec_op(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + const struct nand_op_instr *instr, *data_instr = NULL; + unsigned int rdy_tim_ms = 0, naddrs = 0, cmds = 0, last_flags = 0; + u32 addr1 = 0, addr2 = 0, cmd0 = 0, cmd1 = 0, cmd_addr = 0; + unsigned int op_id, len, offset, rdy_del_ns; + int last_instr_type = -1; + bool cmd1_valid = false; + const u8 *addrs; + int i, ret; + + for (op_id = 0; op_id < subop->ninstrs; op_id++) { + instr = &subop->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + if (!cmds) { + cmd0 = PL35X_SMC_CMD_PHASE_CMD0(instr->ctx.cmd.opcode); + } else { + cmd1 = PL35X_SMC_CMD_PHASE_CMD1(instr->ctx.cmd.opcode); + if (last_instr_type != NAND_OP_DATA_OUT_INSTR) + cmd1_valid = true; + } + cmds++; + break; + + case NAND_OP_ADDR_INSTR: + offset = nand_subop_get_addr_start_off(subop, op_id); + naddrs = nand_subop_get_num_addr_cyc(subop, op_id); + addrs = &instr->ctx.addr.addrs[offset]; + cmd_addr |= PL35X_SMC_CMD_PHASE_NADDRS(naddrs); + + for (i = offset; i < naddrs; i++) { + if (i < 4) + addr1 |= PL35X_SMC_CMD_PHASE_ADDR(i, addrs[i]); + else + addr2 |= PL35X_SMC_CMD_PHASE_ADDR(i - 4, addrs[i]); + } + break; + + case NAND_OP_DATA_IN_INSTR: + case NAND_OP_DATA_OUT_INSTR: + data_instr = instr; + len = nand_subop_get_data_len(subop, op_id); + break; + + case NAND_OP_WAITRDY_INSTR: + rdy_tim_ms = instr->ctx.waitrdy.timeout_ms; + rdy_del_ns = instr->delay_ns; + break; + } + + last_instr_type = instr->type; + } + + /* Command phase */ + cmd_addr |= PL35X_SMC_CMD_PHASE | cmd0 | cmd1 | + (cmd1_valid ? PL35X_SMC_CMD_PHASE_CMD1_VALID : 0); + writel(addr1, nfc->io_regs + cmd_addr); + if (naddrs > 4) + writel(addr2, nfc->io_regs + cmd_addr); + + /* Data phase */ + if (data_instr && data_instr->type == NAND_OP_DATA_OUT_INSTR) { + last_flags = PL35X_SMC_DATA_PHASE_CLEAR_CS; + if (cmds == 2) + last_flags |= cmd1 | PL35X_SMC_CMD_PHASE_CMD1_VALID; + + pl35x_nand_write_data_op(chip, data_instr->ctx.data.buf.out, + len, data_instr->ctx.data.force_8bit, + 0, last_flags); + } + + if (rdy_tim_ms) { + ndelay(rdy_del_ns); + ret = pl35x_smc_wait_for_irq(nfc); + if (ret) + return ret; + } + + if (data_instr && data_instr->type == NAND_OP_DATA_IN_INSTR) + pl35x_nand_read_data_op(chip, data_instr->ctx.data.buf.in, + len, data_instr->ctx.data.force_8bit, + 0, PL35X_SMC_DATA_PHASE_CLEAR_CS); + + return 0; +} + +static const struct nand_op_parser pl35x_nandc_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN(pl35x_nand_exec_op, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 7), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, 2112)), + NAND_OP_PARSER_PATTERN(pl35x_nand_exec_op, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, 7), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 2112), + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), + NAND_OP_PARSER_PATTERN(pl35x_nand_exec_op, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, 7), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 2112), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), + ); + +static int pl35x_nfc_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + if (!check_only) + pl35x_nand_select_target(chip, op->cs); + + return nand_op_parser_exec_op(chip, &pl35x_nandc_op_parser, + op, check_only); +} + +static int pl35x_nfc_setup_interface(struct nand_chip *chip, int cs, + const struct nand_interface_config *conf) +{ + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + struct pl35x_nand *plnand = to_pl35x_nand(chip); + struct pl35x_nand_timings tmgs = {}; + const struct nand_sdr_timings *sdr; + unsigned int period_ns, val; + struct clk *mclk; + + sdr = nand_get_sdr_timings(conf); + if (IS_ERR(sdr)) + return PTR_ERR(sdr); + + mclk = of_clk_get_by_name(nfc->dev->parent->of_node, "memclk"); + if (IS_ERR(mclk)) { + dev_err(nfc->dev, "Failed to retrieve SMC memclk\n"); + return PTR_ERR(mclk); + } + + /* + * SDR timings are given in pico-seconds while NFC timings must be + * expressed in NAND controller clock cycles. We use the TO_CYCLE() + * macro to convert from one to the other. + */ + period_ns = NSEC_PER_SEC / clk_get_rate(mclk); + + /* + * PL35X SMC needs one extra read cycle in SDR Mode 5. This is not + * written anywhere in the datasheet but is an empirical observation. + */ + val = TO_CYCLES(sdr->tRC_min, period_ns); + if (sdr->tRC_min <= 20000) + val++; + + tmgs.t_rc = val; + if (tmgs.t_rc != val || tmgs.t_rc < 2) + return -EINVAL; + + val = TO_CYCLES(sdr->tWC_min, period_ns); + tmgs.t_wc = val; + if (tmgs.t_wc != val || tmgs.t_wc < 2) + return -EINVAL; + + /* + * For all SDR modes, PL35X SMC needs tREA_max being 1, + * this is also an empirical result. + */ + tmgs.t_rea = 1; + + val = TO_CYCLES(sdr->tWP_min, period_ns); + tmgs.t_wp = val; + if (tmgs.t_wp != val || tmgs.t_wp < 1) + return -EINVAL; + + val = TO_CYCLES(sdr->tCLR_min, period_ns); + tmgs.t_clr = val; + if (tmgs.t_clr != val) + return -EINVAL; + + val = TO_CYCLES(sdr->tAR_min, period_ns); + tmgs.t_ar = val; + if (tmgs.t_ar != val) + return -EINVAL; + + val = TO_CYCLES(sdr->tRR_min, period_ns); + tmgs.t_rr = val; + if (tmgs.t_rr != val) + return -EINVAL; + + if (cs == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + plnand->timings = PL35X_SMC_NAND_TRC_CYCLES(tmgs.t_rc) | + PL35X_SMC_NAND_TWC_CYCLES(tmgs.t_wc) | + PL35X_SMC_NAND_TREA_CYCLES(tmgs.t_rea) | + PL35X_SMC_NAND_TWP_CYCLES(tmgs.t_wp) | + PL35X_SMC_NAND_TCLR_CYCLES(tmgs.t_clr) | + PL35X_SMC_NAND_TAR_CYCLES(tmgs.t_ar) | + PL35X_SMC_NAND_TRR_CYCLES(tmgs.t_rr); + + /* + * Reset nfc->selected_chip so the next command will cause the timing + * registers to be updated in ->*_select_target(). + */ + nfc->selected_chip = NULL; + + return 0; +} + +static void pl35x_smc_set_ecc_pg_size(struct pl35x_nandc *nfc, + struct nand_chip *chip, + unsigned int pg_sz) +{ + struct pl35x_nand *plnand = to_pl35x_nand(chip); + u32 sz; + + switch (pg_sz) { + case SZ_512: + sz = 1; + break; + case SZ_1K: + sz = 2; + break; + case SZ_2K: + sz = 3; + break; + default: + sz = 0; + break; + } + + plnand->ecc_cfg = readl(nfc->conf_regs + PL35X_SMC_ECC_CFG); + plnand->ecc_cfg &= ~PL35X_SMC_ECC_CFG_PGSIZE_MASK; + plnand->ecc_cfg |= sz; + writel(plnand->ecc_cfg, nfc->conf_regs + PL35X_SMC_ECC_CFG); +} + +static int pl35x_nand_init_hw_ecc_controller(struct pl35x_nandc *nfc, + struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret = 0; + + if (mtd->writesize < SZ_512 || mtd->writesize > SZ_2K) { + dev_err(nfc->dev, + "The hardware ECC engine is limited to pages up to 2kiB\n"); + return -EOPNOTSUPP; + } + + chip->ecc.strength = 1; + chip->ecc.bytes = 3; + chip->ecc.size = SZ_512; + chip->ecc.steps = mtd->writesize / chip->ecc.size; + chip->ecc.read_page = pl35x_nand_read_page_hwecc; + chip->ecc.write_page = pl35x_nand_write_page_hwecc; + pl35x_smc_set_ecc_pg_size(nfc, chip, mtd->writesize); + + nfc->ecc_buf = devm_kmalloc(nfc->dev, chip->ecc.bytes * chip->ecc.steps, + GFP_KERNEL); + if (!nfc->ecc_buf) + return -ENOMEM; + + switch (mtd->oobsize) { + case 16: + /* Legacy Xilinx layout */ + mtd_set_ooblayout(mtd, &pl35x_ecc_ooblayout16_ops); + chip->bbt_options |= NAND_BBT_NO_OOB_BBM; + break; + case 64: + mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout()); + break; + default: + dev_err(nfc->dev, "Unsupported OOB size\n"); + return -EOPNOTSUPP; + } + + return ret; +} + +static int pl35x_nand_attach_chip(struct nand_chip *chip) +{ + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&chip->base); + struct pl35x_nandc *nfc = to_pl35x_nandc(chip->controller); + struct pl35x_nand *plnand = to_pl35x_nand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_NONE && + (!chip->ecc.size || !chip->ecc.strength)) { + if (requirements->step_size && requirements->strength) { + chip->ecc.size = requirements->step_size; + chip->ecc.strength = requirements->strength; + } else { + dev_info(nfc->dev, + "No minimum ECC strength, using 1b/512B\n"); + chip->ecc.size = 512; + chip->ecc.strength = 1; + } + } + + if (mtd->writesize <= SZ_512) + plnand->addr_cycles = 1; + else + plnand->addr_cycles = 2; + + if (chip->options & NAND_ROW_ADDR_3) + plnand->addr_cycles += 3; + else + plnand->addr_cycles += 2; + + switch (chip->ecc.engine_type) { + case NAND_ECC_ENGINE_TYPE_ON_DIE: + dev_dbg(nfc->dev, "Using on-die hardware 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: + dev_dbg(nfc->dev, "Using no ECC engine\n"); + break; + case NAND_ECC_ENGINE_TYPE_SOFT: + dev_dbg(nfc->dev, "Using software ECC\n"); + break; + case NAND_ECC_ENGINE_TYPE_ON_HOST: + dev_dbg(nfc->dev, "Using on-host hardware ECC\n"); + ret = pl35x_nand_init_hw_ecc_controller(nfc, chip); + if (ret) + return ret; + break; + default: + dev_err(nfc->dev, "Unsupported ECC mode: %d\n", + chip->ecc.engine_type); + return -EINVAL; + } + + 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 pl35x_nandc_ops = { + .attach_chip = pl35x_nand_attach_chip, + .exec_op = pl35x_nfc_exec_op, + .setup_interface = pl35x_nfc_setup_interface, +}; + +static int pl35x_nand_reset_state(struct pl35x_nandc *nfc) +{ + int ret; + + /* Disable interrupts and clear their status */ + writel(PL35X_SMC_MEMC_CFG_CLR_INT_CLR_1 | + PL35X_SMC_MEMC_CFG_CLR_ECC_INT_DIS_1 | + PL35X_SMC_MEMC_CFG_CLR_INT_DIS_1, + nfc->conf_regs + PL35X_SMC_MEMC_CFG_CLR); + + /* Set default bus width to 8-bit */ + ret = pl35x_smc_set_buswidth(nfc, PL35X_SMC_OPMODE_BW_8); + if (ret) + return ret; + + /* Ensure the ECC controller is bypassed by default */ + ret = pl35x_smc_set_ecc_mode(nfc, NULL, PL35X_SMC_ECC_CFG_MODE_BYPASS); + if (ret) + return ret; + + /* + * Configure the commands that the ECC block uses to detect the + * operations it should start/end. + */ + writel(PL35X_SMC_ECC_CMD1_WRITE(NAND_CMD_SEQIN) | + PL35X_SMC_ECC_CMD1_READ(NAND_CMD_READ0) | + PL35X_SMC_ECC_CMD1_READ_END(NAND_CMD_READSTART) | + PL35X_SMC_ECC_CMD1_READ_END_VALID(NAND_CMD_READ1), + nfc->conf_regs + PL35X_SMC_ECC_CMD1); + writel(PL35X_SMC_ECC_CMD2_WRITE_COL_CHG(NAND_CMD_RNDIN) | + PL35X_SMC_ECC_CMD2_READ_COL_CHG(NAND_CMD_RNDOUT) | + PL35X_SMC_ECC_CMD2_READ_COL_CHG_END(NAND_CMD_RNDOUTSTART) | + PL35X_SMC_ECC_CMD2_READ_COL_CHG_END_VALID(NAND_CMD_READ1), + nfc->conf_regs + PL35X_SMC_ECC_CMD2); + + return 0; +} + +static int pl35x_nand_chip_init(struct pl35x_nandc *nfc, + struct device_node *np) +{ + struct pl35x_nand *plnand; + struct nand_chip *chip; + struct mtd_info *mtd; + int cs, ret; + + plnand = devm_kzalloc(nfc->dev, sizeof(*plnand), GFP_KERNEL); + if (!plnand) + return -ENOMEM; + + ret = of_property_read_u32(np, "reg", &cs); + if (ret) + return ret; + + if (cs >= PL35X_NAND_MAX_CS) { + dev_err(nfc->dev, "Wrong CS %d\n", cs); + return -EINVAL; + } + + if (test_and_set_bit(cs, &nfc->assigned_cs)) { + dev_err(nfc->dev, "Already assigned CS %d\n", cs); + return -EINVAL; + } + + plnand->cs = cs; + + chip = &plnand->chip; + chip->options = NAND_BUSWIDTH_AUTO | NAND_USES_DMA | NAND_NO_SUBPAGE_WRITE; + chip->bbt_options = NAND_BBT_USE_FLASH; + chip->controller = &nfc->controller; + mtd = nand_to_mtd(chip); + mtd->dev.parent = nfc->dev; + nand_set_flash_node(chip, np); + if (!mtd->name) { + mtd->name = devm_kasprintf(nfc->dev, GFP_KERNEL, + "%s", PL35X_NANDC_DRIVER_NAME); + if (!mtd->name) { + dev_err(nfc->dev, "Failed to allocate mtd->name\n"); + return -ENOMEM; + } + } + + ret = nand_scan(chip, 1); + if (ret) + return ret; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + nand_cleanup(chip); + return ret; + } + + list_add_tail(&plnand->node, &nfc->chips); + + return ret; +} + +static void pl35x_nand_chips_cleanup(struct pl35x_nandc *nfc) +{ + struct pl35x_nand *plnand, *tmp; + struct nand_chip *chip; + int ret; + + list_for_each_entry_safe(plnand, tmp, &nfc->chips, node) { + chip = &plnand->chip; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + list_del(&plnand->node); + } +} + +static int pl35x_nand_chips_init(struct pl35x_nandc *nfc) +{ + struct device_node *np = nfc->dev->of_node; + int nchips = of_get_child_count(np); + int ret; + + if (!nchips || nchips > PL35X_NAND_MAX_CS) { + dev_err(nfc->dev, "Incorrect number of NAND chips (%d)\n", + nchips); + return -EINVAL; + } + + for_each_child_of_node_scoped(np, nand_np) { + ret = pl35x_nand_chip_init(nfc, nand_np); + if (ret) { + pl35x_nand_chips_cleanup(nfc); + break; + } + } + + return ret; +} + +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; + int ret; + + nfc = devm_kzalloc(&pdev->dev, sizeof(*nfc), GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->dev = &pdev->dev; + nand_controller_init(&nfc->controller); + nfc->controller.ops = &pl35x_nandc_ops; + INIT_LIST_HEAD(&nfc->chips); + + nfc->conf_regs = devm_ioremap_resource(nfc->dev, &smc_amba->res); + if (IS_ERR(nfc->conf_regs)) + return PTR_ERR(nfc->conf_regs); + + nfc->io_regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(nfc->io_regs)) + return PTR_ERR(nfc->io_regs); + + ret = pl35x_nand_reset_state(nfc); + if (ret) + return ret; + + ret = pl35x_nand_chips_init(nfc); + if (ret) + return ret; + + platform_set_drvdata(pdev, nfc); + + return 0; +} + +static void pl35x_nand_remove(struct platform_device *pdev) +{ + struct pl35x_nandc *nfc = platform_get_drvdata(pdev); + + pl35x_nand_chips_cleanup(nfc); +} + +static const struct of_device_id pl35x_nand_of_match[] = { + { .compatible = "arm,pl353-nand-r2p1" }, + {}, +}; +MODULE_DEVICE_TABLE(of, pl35x_nand_of_match); + +static struct platform_driver pl35x_nandc_driver = { + .probe = pl35x_nand_probe, + .remove = pl35x_nand_remove, + .driver = { + .name = PL35X_NANDC_DRIVER_NAME, + .of_match_table = pl35x_nand_of_match, + }, +}; +module_platform_driver(pl35x_nandc_driver); + +MODULE_AUTHOR("Xilinx, Inc."); +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 new file mode 100644 index 000000000..fe31551bc --- /dev/null +++ b/drivers/mtd/nand/raw/plat_nand.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Generic NAND driver + * + * Author: Vitaly Wool + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +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) +{ + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT && + chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + + return 0; +} + +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. + */ +static int plat_nand_probe(struct platform_device *pdev) +{ + struct platform_nand_data *pdata = dev_get_platdata(&pdev->dev); + struct plat_nand_data *data; + struct mtd_info *mtd; + const char **part_types; + struct nand_chip *chip; + int err = 0; + + if (!pdata) { + dev_err(&pdev->dev, "platform_nand_data is missing\n"); + return -EINVAL; + } + + if (pdata->chip.nr_chips < 1) { + dev_err(&pdev->dev, "invalid number of chips specified\n"); + return -EINVAL; + } + + /* Allocate memory for the device structure (and zero it) */ + data = devm_kzalloc(&pdev->dev, sizeof(struct plat_nand_data), + GFP_KERNEL); + 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)) + return PTR_ERR(data->io_base); + + nand_set_flash_node(&data->chip, pdev->dev.of_node); + mtd = nand_to_mtd(&data->chip); + mtd->dev.parent = &pdev->dev; + + 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; + 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; + data->chip.legacy.chip_delay = pdata->chip.chip_delay; + data->chip.options |= pdata->chip.options; + data->chip.bbt_options |= pdata->chip.bbt_options; + + platform_set_drvdata(pdev, data); + + /* Handle any platform specific setup */ + if (pdata->ctrl.probe) { + err = pdata->ctrl.probe(pdev); + if (err) + goto out; + } + + /* + * This driver assumes that the default ECC engine should be TYPE_SOFT. + * Set ->engine_type before registering the NAND devices in order to + * provide a driver specific default value. + */ + data->chip.ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + + /* Scan to find existence of the device */ + err = nand_scan(&data->chip, pdata->chip.nr_chips); + if (err) + goto out; + + part_types = pdata->chip.part_probe_types; + + err = mtd_device_parse_register(mtd, part_types, NULL, + pdata->chip.partitions, + pdata->chip.nr_partitions); + + if (!err) + return err; + + nand_cleanup(&data->chip); +out: + if (pdata->ctrl.remove) + pdata->ctrl.remove(pdev); + return err; +} + +/* + * Remove a NAND device. + */ +static void plat_nand_remove(struct platform_device *pdev) +{ + struct plat_nand_data *data = platform_get_drvdata(pdev); + struct platform_nand_data *pdata = dev_get_platdata(&pdev->dev); + struct nand_chip *chip = &data->chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + if (pdata->ctrl.remove) + pdata->ctrl.remove(pdev); +} + +static const struct of_device_id plat_nand_match[] = { + { .compatible = "gen_nand" }, + {}, +}; +MODULE_DEVICE_TABLE(of, plat_nand_match); + +static struct platform_driver plat_nand_driver = { + .probe = plat_nand_probe, + .remove = plat_nand_remove, + .driver = { + .name = "gen_nand", + .of_match_table = plat_nand_match, + }, +}; + +module_platform_driver(plat_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Vitaly Wool"); +MODULE_DESCRIPTION("Simple generic NAND driver"); +MODULE_ALIAS("platform:gen_nand"); diff --git a/drivers/mtd/nand/raw/qcom_nandc.c b/drivers/mtd/nand/raw/qcom_nandc.c new file mode 100644 index 000000000..d7642db2e --- /dev/null +++ b/drivers/mtd/nand/raw/qcom_nandc.c @@ -0,0 +1,2448 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * NAND special boot partitions + * + * @page_offset: offset of the partition where spare data is not protected + * by ECC (value in pages) + * @page_offset: size of the partition where spare data is not protected + * by ECC (value in pages) + */ +struct qcom_nand_boot_partition { + u32 page_offset; + u32 page_size; +}; + +/* + * Qcom op for each exec_op transfer + * + * @data_instr: data instruction pointer + * @data_instr_idx: data instruction index + * @rdy_timeout_ms: wait ready timeout in ms + * @rdy_delay_ns: Additional delay in ns + * @addr1_reg: Address1 register value + * @addr2_reg: Address2 register value + * @cmd_reg: CMD register value + * @flag: flag for misc instruction + */ +struct qcom_op { + const struct nand_op_instr *data_instr; + unsigned int data_instr_idx; + unsigned int rdy_timeout_ms; + unsigned int rdy_delay_ns; + __le32 addr1_reg; + __le32 addr2_reg; + __le32 cmd_reg; + u8 flag; +}; + +/* + * NAND chip structure + * + * @boot_partitions: array of boot partitions where offset and size of the + * boot partitions are stored + * + * @chip: base NAND chip structure + * @node: list node to add itself to host_list in + * qcom_nand_controller + * + * @nr_boot_partitions: count of the boot partitions where spare data is not + * protected by ECC + * + * @cs: chip select value for this chip + * @cw_size: the number of bytes in a single step/codeword + * of a page, consisting of all data, ecc, spare + * and reserved bytes + * @cw_data: the number of bytes within a codeword protected + * by ECC + * @ecc_bytes_hw: ECC bytes used by controller hardware for this + * chip + * + * @last_command: keeps track of last command on this chip. used + * for reading correct status + * + * @cfg0, cfg1, cfg0_raw..: NANDc register configurations needed for + * ecc/non-ecc mode for the current nand flash + * device + * + * @status: value to be returned if NAND_CMD_STATUS command + * is executed + * @codeword_fixup: keep track of the current layout used by + * the driver for read/write operation. + * @use_ecc: request the controller to use ECC for the + * upcoming read/write + * @bch_enabled: flag to tell whether BCH ECC mode is used + */ +struct qcom_nand_host { + struct qcom_nand_boot_partition *boot_partitions; + + struct nand_chip chip; + struct list_head node; + + int nr_boot_partitions; + + int cs; + int cw_size; + int cw_data; + int ecc_bytes_hw; + int spare_bytes; + int bbm_size; + + int last_command; + + u32 cfg0, cfg1; + u32 cfg0_raw, cfg1_raw; + u32 ecc_buf_cfg; + u32 ecc_bch_cfg; + u32 clrflashstatus; + u32 clrreadstatus; + + u8 status; + bool codeword_fixup; + bool use_ecc; + bool bch_enabled; +}; + +static struct qcom_nand_host *to_qcom_nand_host(struct nand_chip *chip) +{ + return container_of(chip, struct qcom_nand_host, chip); +} + +static struct qcom_nand_controller * +get_qcom_nand_controller(struct nand_chip *chip) +{ + return container_of(chip->controller, struct qcom_nand_controller, + controller); +} + +static u32 nandc_read(struct qcom_nand_controller *nandc, int offset) +{ + return ioread32(nandc->base + offset); +} + +static void nandc_write(struct qcom_nand_controller *nandc, int offset, + u32 val) +{ + iowrite32(val, nandc->base + offset); +} + +/* Helper to check whether this is the last CW or not */ +static bool qcom_nandc_is_last_cw(struct nand_ecc_ctrl *ecc, int cw) +{ + return cw == (ecc->steps - 1); +} + +/** + * nandc_set_read_loc_first() - to set read location first register + * @chip: NAND Private Flash Chip Data + * @reg_base: location register base + * @cw_offset: code word offset + * @read_size: code word read length + * @is_last_read_loc: is this the last read location + * + * This function will set location register value + */ +static void nandc_set_read_loc_first(struct nand_chip *chip, + int reg_base, u32 cw_offset, + u32 read_size, u32 is_last_read_loc) +{ + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + __le32 locreg_val; + 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); + + if (reg_base == NAND_READ_LOCATION_0) + nandc->regs->read_location0 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_1) + nandc->regs->read_location1 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_2) + nandc->regs->read_location2 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_3) + nandc->regs->read_location3 = locreg_val; +} + +/** + * nandc_set_read_loc_last - to set read location last register + * @chip: NAND Private Flash Chip Data + * @reg_base: location register base + * @cw_offset: code word offset + * @read_size: code word read length + * @is_last_read_loc: is this the last read location + * + * This function will set location last register value + */ +static void nandc_set_read_loc_last(struct nand_chip *chip, + int reg_base, u32 cw_offset, + u32 read_size, u32 is_last_read_loc) +{ + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + __le32 locreg_val; + 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); + + if (reg_base == NAND_READ_LOCATION_LAST_CW_0) + nandc->regs->read_location_last0 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_LAST_CW_1) + nandc->regs->read_location_last1 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_LAST_CW_2) + nandc->regs->read_location_last2 = locreg_val; + else if (reg_base == NAND_READ_LOCATION_LAST_CW_3) + nandc->regs->read_location_last3 = locreg_val; +} + +/* helper to configure location register values */ +static void nandc_set_read_loc(struct nand_chip *chip, int cw, int reg, + u32 cw_offset, u32 read_size, u32 is_last_read_loc) +{ + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int reg_base = NAND_READ_LOCATION_0; + + if (nandc->props->qpic_version2 && qcom_nandc_is_last_cw(ecc, cw)) + reg_base = NAND_READ_LOCATION_LAST_CW_0; + + reg_base += reg * 4; + + if (nandc->props->qpic_version2 && qcom_nandc_is_last_cw(ecc, cw)) + return nandc_set_read_loc_last(chip, reg_base, cw_offset, + read_size, is_last_read_loc); + else + return nandc_set_read_loc_first(chip, reg_base, cw_offset, + read_size, is_last_read_loc); +} + +/* helper to configure address register values */ +static void set_address(struct qcom_nand_host *host, u16 column, int page) +{ + struct nand_chip *chip = &host->chip; + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + + if (chip->options & NAND_BUSWIDTH_16) + column >>= 1; + + nandc->regs->addr0 = cpu_to_le32(page << 16 | column); + nandc->regs->addr1 = cpu_to_le32(page >> 16 & 0xff); +} + +/* + * update_rw_regs: set up read/write register values, these will be + * written to the NAND controller registers via DMA + * + * @num_cw: number of steps for the read/write operation + * @read: read or write operation + * @cw : which code word + */ +static void update_rw_regs(struct qcom_nand_host *host, int num_cw, bool read, int cw) +{ + struct nand_chip *chip = &host->chip; + __le32 cmd, cfg0, cfg1, ecc_bch_cfg; + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + + if (read) { + if (host->use_ecc) + cmd = cpu_to_le32(OP_PAGE_READ_WITH_ECC | PAGE_ACC | LAST_PAGE); + else + cmd = cpu_to_le32(OP_PAGE_READ | PAGE_ACC | LAST_PAGE); + } else { + cmd = cpu_to_le32(OP_PROGRAM_PAGE | PAGE_ACC | LAST_PAGE); + } + + if (host->use_ecc) { + 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 & ~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); + } + + nandc->regs->cmd = cmd; + nandc->regs->cfg0 = cfg0; + nandc->regs->cfg1 = cfg1; + nandc->regs->ecc_bch_cfg = ecc_bch_cfg; + + if (!nandc->props->qpic_version2) + nandc->regs->ecc_buf_cfg = cpu_to_le32(host->ecc_buf_cfg); + + nandc->regs->clrflashstatus = cpu_to_le32(host->clrflashstatus); + nandc->regs->clrreadstatus = cpu_to_le32(host->clrreadstatus); + nandc->regs->exec = cpu_to_le32(1); + + if (read) + nandc_set_read_loc(chip, cw, 0, 0, host->use_ecc ? + host->cw_data : host->cw_size, 1); +} + +/* + * Helper to prepare DMA descriptors for configuring registers + * before reading a NAND page. + */ +static void config_nand_page_read(struct nand_chip *chip) +{ + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + + qcom_write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0); + qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); + if (!nandc->props->qpic_version2) + qcom_write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, 0); + qcom_write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_clr, + NAND_ERASED_CW_DETECT_CFG, 1, 0); + qcom_write_reg_dma(nandc, &nandc->regs->erased_cw_detect_cfg_set, + NAND_ERASED_CW_DETECT_CFG, 1, NAND_ERASED_CW_SET | NAND_BAM_NEXT_SGL); +} + +/* + * Helper to prepare DMA descriptors for configuring registers + * before reading each codeword in NAND page. + */ +static void +config_nand_cw_read(struct nand_chip *chip, bool use_ecc, int cw) +{ + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + + __le32 *reg = &nandc->regs->read_location0; + + if (nandc->props->qpic_version2 && qcom_nandc_is_last_cw(ecc, cw)) + reg = &nandc->regs->read_location_last0; + + if (nandc->props->supports_bam) + qcom_write_reg_dma(nandc, reg, NAND_READ_LOCATION_0, 4, NAND_BAM_NEXT_SGL); + + qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL); + qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL); + + if (use_ecc) { + qcom_read_reg_dma(nandc, NAND_FLASH_STATUS, 2, 0); + qcom_read_reg_dma(nandc, NAND_ERASED_CW_DETECT_STATUS, 1, + NAND_BAM_NEXT_SGL); + } else { + qcom_read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL); + } +} + +/* + * Helper to prepare dma descriptors to configure registers needed for reading a + * single codeword in page + */ +static void +config_nand_single_cw_page_read(struct nand_chip *chip, + bool use_ecc, int cw) +{ + config_nand_page_read(chip); + config_nand_cw_read(chip, use_ecc, cw); +} + +/* + * Helper to prepare DMA descriptors used to configure registers needed for + * before writing a NAND page. + */ +static void config_nand_page_write(struct nand_chip *chip) +{ + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + + qcom_write_reg_dma(nandc, &nandc->regs->addr0, NAND_ADDR0, 2, 0); + qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 3, 0); + if (!nandc->props->qpic_version2) + qcom_write_reg_dma(nandc, &nandc->regs->ecc_buf_cfg, NAND_EBI2_ECC_BUF_CFG, 1, + NAND_BAM_NEXT_SGL); +} + +/* + * Helper to prepare DMA descriptors for configuring registers + * before writing each codeword in NAND page. + */ +static void config_nand_cw_write(struct nand_chip *chip) +{ + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + + qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL); + qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL); + + qcom_read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL); + + qcom_write_reg_dma(nandc, &nandc->regs->clrflashstatus, NAND_FLASH_STATUS, 1, 0); + qcom_write_reg_dma(nandc, &nandc->regs->clrreadstatus, NAND_READ_STATUS, 1, + NAND_BAM_NEXT_SGL); +} + +/* + * when using BCH ECC, the HW flags an error in NAND_FLASH_STATUS if it read + * an erased CW, and reports an erased CW in NAND_ERASED_CW_DETECT_STATUS. + * + * when using RS ECC, the HW reports the same erros when reading an erased CW, + * but it notifies that it is an erased CW by placing special characters at + * certain offsets in the buffer. + * + * verify if the page is erased or not, and fix up the page for RS ECC by + * replacing the special characters with 0xff. + */ +static bool erased_chunk_check_and_fixup(u8 *data_buf, int data_len) +{ + u8 empty1, empty2; + + /* + * an erased page flags an error in NAND_FLASH_STATUS, check if the page + * is erased by looking for 0x54s at offsets 3 and 175 from the + * beginning of each codeword + */ + + empty1 = data_buf[3]; + empty2 = data_buf[175]; + + /* + * if the erased codework markers, if they exist override them with + * 0xffs + */ + if ((empty1 == 0x54 && empty2 == 0xff) || + (empty1 == 0xff && empty2 == 0x54)) { + data_buf[3] = 0xff; + data_buf[175] = 0xff; + } + + /* + * check if the entire chunk contains 0xffs or not. if it doesn't, then + * restore the original values at the special offsets + */ + if (memchr_inv(data_buf, 0xff, data_len)) { + data_buf[3] = empty1; + data_buf[175] = empty2; + + return false; + } + + return true; +} + +struct read_stats { + __le32 flash; + __le32 buffer; + __le32 erased_cw; +}; + +/* reads back FLASH_STATUS register set by the controller */ +static int check_flash_errors(struct qcom_nand_host *host, int cw_cnt) +{ + struct nand_chip *chip = &host->chip; + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + int i; + + qcom_nandc_dev_to_mem(nandc, true); + + for (i = 0; i < cw_cnt; i++) { + u32 flash = le32_to_cpu(nandc->reg_read_buf[i]); + + if (flash & (FS_OP_ERR | FS_MPU_ERR)) + return -EIO; + } + + return 0; +} + +/* performs raw read for one codeword */ +static int +qcom_nandc_read_cw_raw(struct mtd_info *mtd, struct nand_chip *chip, + u8 *data_buf, u8 *oob_buf, int page, int cw) +{ + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int data_size1, data_size2, oob_size1, oob_size2; + int ret, reg_off = FLASH_BUF_ACC, read_loc = 0; + int raw_cw = cw; + + nand_read_page_op(chip, page, 0, NULL, 0); + nandc->buf_count = 0; + nandc->buf_start = 0; + qcom_clear_read_regs(nandc); + host->use_ecc = false; + + if (nandc->props->qpic_version2) + raw_cw = ecc->steps - 1; + + qcom_clear_bam_transaction(nandc); + set_address(host, host->cw_size * cw, page); + update_rw_regs(host, 1, true, raw_cw); + config_nand_page_read(chip); + + data_size1 = mtd->writesize - host->cw_size * (ecc->steps - 1); + oob_size1 = host->bbm_size; + + if (qcom_nandc_is_last_cw(ecc, cw) && !host->codeword_fixup) { + data_size2 = ecc->size - data_size1 - + ((ecc->steps - 1) * 4); + oob_size2 = (ecc->steps * 4) + host->ecc_bytes_hw + + host->spare_bytes; + } else { + data_size2 = host->cw_data - data_size1; + oob_size2 = host->ecc_bytes_hw + host->spare_bytes; + } + + if (nandc->props->supports_bam) { + nandc_set_read_loc(chip, cw, 0, read_loc, data_size1, 0); + read_loc += data_size1; + + nandc_set_read_loc(chip, cw, 1, read_loc, oob_size1, 0); + read_loc += oob_size1; + + nandc_set_read_loc(chip, cw, 2, read_loc, data_size2, 0); + read_loc += data_size2; + + nandc_set_read_loc(chip, cw, 3, read_loc, oob_size2, 1); + } + + config_nand_cw_read(chip, false, raw_cw); + + qcom_read_data_dma(nandc, reg_off, data_buf, data_size1, 0); + reg_off += data_size1; + + qcom_read_data_dma(nandc, reg_off, oob_buf, oob_size1, 0); + reg_off += oob_size1; + + qcom_read_data_dma(nandc, reg_off, data_buf + data_size1, data_size2, 0); + reg_off += data_size2; + + qcom_read_data_dma(nandc, reg_off, oob_buf + oob_size1, oob_size2, 0); + + ret = qcom_submit_descs(nandc); + if (ret) { + dev_err(nandc->dev, "failure to read raw cw %d\n", cw); + return ret; + } + + return check_flash_errors(host, 1); +} + +/* + * Bitflips can happen in erased codewords also so this function counts the + * number of 0 in each CW for which ECC engine returns the uncorrectable + * error. The page will be assumed as erased if this count is less than or + * equal to the ecc->strength for each CW. + * + * 1. Both DATA and OOB need to be checked for number of 0. The + * top-level API can be called with only data buf or OOB buf so use + * chip->data_buf if data buf is null and chip->oob_poi if oob buf + * is null for copying the raw bytes. + * 2. Perform raw read for all the CW which has uncorrectable errors. + * 3. For each CW, check the number of 0 in cw_data and usable OOB bytes. + * The BBM and spare bytes bit flip won’t affect the ECC so don’t check + * the number of bitflips in this area. + */ +static int +check_for_erased_page(struct qcom_nand_host *host, u8 *data_buf, + u8 *oob_buf, unsigned long uncorrectable_cws, + int page, unsigned int max_bitflips) +{ + struct nand_chip *chip = &host->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + u8 *cw_data_buf, *cw_oob_buf; + int cw, data_size, oob_size, ret; + + if (!data_buf) + data_buf = nand_get_data_buf(chip); + + if (!oob_buf) { + nand_get_data_buf(chip); + oob_buf = chip->oob_poi; + } + + for_each_set_bit(cw, &uncorrectable_cws, ecc->steps) { + if (qcom_nandc_is_last_cw(ecc, cw) && !host->codeword_fixup) { + data_size = ecc->size - ((ecc->steps - 1) * 4); + oob_size = (ecc->steps * 4) + host->ecc_bytes_hw; + } else { + data_size = host->cw_data; + oob_size = host->ecc_bytes_hw; + } + + /* determine starting buffer address for current CW */ + cw_data_buf = data_buf + (cw * host->cw_data); + cw_oob_buf = oob_buf + (cw * ecc->bytes); + + ret = qcom_nandc_read_cw_raw(mtd, chip, cw_data_buf, + cw_oob_buf, page, cw); + if (ret) + return ret; + + /* + * make sure it isn't an erased page reported + * as not-erased by HW because of a few bitflips + */ + ret = nand_check_erased_ecc_chunk(cw_data_buf, data_size, + cw_oob_buf + host->bbm_size, + oob_size, NULL, + 0, ecc->strength); + if (ret < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += ret; + max_bitflips = max_t(unsigned int, max_bitflips, ret); + } + } + + return max_bitflips; +} + +/* + * reads back status registers set by the controller to notify page read + * errors. this is equivalent to what 'ecc->correct()' would do. + */ +static int parse_read_errors(struct qcom_nand_host *host, u8 *data_buf, + u8 *oob_buf, int page) +{ + struct nand_chip *chip = &host->chip; + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + unsigned int max_bitflips = 0, uncorrectable_cws = 0; + struct read_stats *buf; + bool flash_op_err = false, erased; + int i; + u8 *data_buf_start = data_buf, *oob_buf_start = oob_buf; + + buf = (struct read_stats *)nandc->reg_read_buf; + qcom_nandc_dev_to_mem(nandc, true); + + for (i = 0; i < ecc->steps; i++, buf++) { + u32 flash, buffer, erased_cw; + int data_len, oob_len; + + if (qcom_nandc_is_last_cw(ecc, i)) { + data_len = ecc->size - ((ecc->steps - 1) << 2); + oob_len = ecc->steps << 2; + } else { + data_len = host->cw_data; + oob_len = 0; + } + + flash = le32_to_cpu(buf->flash); + buffer = le32_to_cpu(buf->buffer); + erased_cw = le32_to_cpu(buf->erased_cw); + + /* + * Check ECC failure for each codeword. ECC failure can + * happen in either of the following conditions + * 1. If number of bitflips are greater than ECC engine + * capability. + * 2. If this codeword contains all 0xff for which erased + * codeword detection check will be done. + */ + if ((flash & FS_OP_ERR) && (buffer & BS_UNCORRECTABLE_BIT)) { + /* + * For BCH ECC, ignore erased codeword errors, if + * ERASED_CW bits are set. + */ + if (host->bch_enabled) { + erased = (erased_cw & ERASED_CW) == ERASED_CW; + /* + * For RS ECC, HW reports the erased CW by placing + * special characters at certain offsets in the buffer. + * These special characters will be valid only if + * complete page is read i.e. data_buf is not NULL. + */ + } else if (data_buf) { + erased = erased_chunk_check_and_fixup(data_buf, + data_len); + } else { + erased = false; + } + + if (!erased) + uncorrectable_cws |= BIT(i); + /* + * Check if MPU or any other operational error (timeout, + * device failure, etc.) happened for this codeword and + * make flash_op_err true. If flash_op_err is set, then + * EIO will be returned for page read. + */ + } else if (flash & (FS_OP_ERR | FS_MPU_ERR)) { + flash_op_err = true; + /* + * No ECC or operational errors happened. Check the number of + * bits corrected and update the ecc_stats.corrected. + */ + } else { + unsigned int stat; + + stat = buffer & BS_CORRECTABLE_ERR_MSK; + mtd->ecc_stats.corrected += stat; + max_bitflips = max(max_bitflips, stat); + } + + if (data_buf) + data_buf += data_len; + if (oob_buf) + oob_buf += oob_len + ecc->bytes; + } + + if (flash_op_err) + return -EIO; + + if (!uncorrectable_cws) + return max_bitflips; + + return check_for_erased_page(host, data_buf_start, oob_buf_start, + uncorrectable_cws, page, + max_bitflips); +} + +/* + * helper to perform the actual page read operation, used by ecc->read_page(), + * ecc->read_oob() + */ +static int read_page_ecc(struct qcom_nand_host *host, u8 *data_buf, + u8 *oob_buf, int page) +{ + struct nand_chip *chip = &host->chip; + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + u8 *data_buf_start = data_buf, *oob_buf_start = oob_buf; + int i, ret; + + config_nand_page_read(chip); + + /* queue cmd descs for each codeword */ + for (i = 0; i < ecc->steps; i++) { + int data_size, oob_size; + + if (qcom_nandc_is_last_cw(ecc, i) && !host->codeword_fixup) { + data_size = ecc->size - ((ecc->steps - 1) << 2); + oob_size = (ecc->steps << 2) + host->ecc_bytes_hw + + host->spare_bytes; + } else { + data_size = host->cw_data; + oob_size = host->ecc_bytes_hw + host->spare_bytes; + } + + if (nandc->props->supports_bam) { + if (data_buf && oob_buf) { + nandc_set_read_loc(chip, i, 0, 0, data_size, 0); + nandc_set_read_loc(chip, i, 1, data_size, + oob_size, 1); + } else if (data_buf) { + nandc_set_read_loc(chip, i, 0, 0, data_size, 1); + } else { + nandc_set_read_loc(chip, i, 0, data_size, + oob_size, 1); + } + } + + config_nand_cw_read(chip, true, i); + + if (data_buf) + qcom_read_data_dma(nandc, FLASH_BUF_ACC, data_buf, + data_size, 0); + + /* + * when ecc is enabled, the controller doesn't read the real + * or dummy bad block markers in each chunk. To maintain a + * consistent layout across RAW and ECC reads, we just + * leave the real/dummy BBM offsets empty (i.e, filled with + * 0xffs) + */ + if (oob_buf) { + int j; + + for (j = 0; j < host->bbm_size; j++) + *oob_buf++ = 0xff; + + qcom_read_data_dma(nandc, FLASH_BUF_ACC + data_size, + oob_buf, oob_size, 0); + } + + if (data_buf) + data_buf += data_size; + if (oob_buf) + oob_buf += oob_size; + } + + ret = qcom_submit_descs(nandc); + if (ret) { + dev_err(nandc->dev, "failure to read page/oob\n"); + return ret; + } + + return parse_read_errors(host, data_buf_start, oob_buf_start, page); +} + +/* + * a helper that copies the last step/codeword of a page (containing free oob) + * into our local buffer + */ +static int copy_last_cw(struct qcom_nand_host *host, int page) +{ + struct nand_chip *chip = &host->chip; + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int size; + int ret; + + qcom_clear_read_regs(nandc); + + size = host->use_ecc ? host->cw_data : host->cw_size; + + /* prepare a clean read buffer */ + memset(nandc->data_buffer, 0xff, size); + + set_address(host, host->cw_size * (ecc->steps - 1), page); + update_rw_regs(host, 1, true, ecc->steps - 1); + + config_nand_single_cw_page_read(chip, host->use_ecc, ecc->steps - 1); + + qcom_read_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer, size, 0); + + ret = qcom_submit_descs(nandc); + if (ret) + dev_err(nandc->dev, "failed to copy last codeword\n"); + + return ret; +} + +static bool qcom_nandc_is_boot_partition(struct qcom_nand_host *host, int page) +{ + struct qcom_nand_boot_partition *boot_partition; + u32 start, end; + int i; + + /* + * Since the frequent access will be to the non-boot partitions like rootfs, + * optimize the page check by: + * + * 1. Checking if the page lies after the last boot partition. + * 2. Checking from the boot partition end. + */ + + /* First check the last boot partition */ + boot_partition = &host->boot_partitions[host->nr_boot_partitions - 1]; + start = boot_partition->page_offset; + end = start + boot_partition->page_size; + + /* Page is after the last boot partition end. This is NOT a boot partition */ + if (page > end) + return false; + + /* Actually check if it's a boot partition */ + if (page < end && page >= start) + return true; + + /* Check the other boot partitions starting from the second-last partition */ + for (i = host->nr_boot_partitions - 2; i >= 0; i--) { + boot_partition = &host->boot_partitions[i]; + start = boot_partition->page_offset; + end = start + boot_partition->page_size; + + if (page < end && page >= start) + return true; + } + + return false; +} + +static void qcom_nandc_codeword_fixup(struct qcom_nand_host *host, int page) +{ + bool codeword_fixup = qcom_nandc_is_boot_partition(host, page); + + /* Skip conf write if we are already in the correct mode */ + if (codeword_fixup == host->codeword_fixup) + return; + + host->codeword_fixup = codeword_fixup; + + host->cw_data = codeword_fixup ? 512 : 516; + host->spare_bytes = host->cw_size - host->ecc_bytes_hw - + host->bbm_size - host->cw_data; + + host->cfg0 &= ~(SPARE_SIZE_BYTES_MASK | UD_SIZE_BYTES_MASK); + 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 |= 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() */ +static int qcom_nandc_read_page(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + u8 *data_buf, *oob_buf = NULL; + + if (host->nr_boot_partitions) + qcom_nandc_codeword_fixup(host, page); + + nand_read_page_op(chip, page, 0, NULL, 0); + nandc->buf_count = 0; + nandc->buf_start = 0; + host->use_ecc = true; + qcom_clear_read_regs(nandc); + set_address(host, 0, page); + update_rw_regs(host, ecc->steps, true, 0); + + data_buf = buf; + oob_buf = oob_required ? chip->oob_poi : NULL; + + qcom_clear_bam_transaction(nandc); + + return read_page_ecc(host, data_buf, oob_buf, page); +} + +/* implements ecc->read_page_raw() */ +static int qcom_nandc_read_page_raw(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int cw, ret; + u8 *data_buf = buf, *oob_buf = chip->oob_poi; + + if (host->nr_boot_partitions) + qcom_nandc_codeword_fixup(host, page); + + for (cw = 0; cw < ecc->steps; cw++) { + ret = qcom_nandc_read_cw_raw(mtd, chip, data_buf, oob_buf, + page, cw); + if (ret) + return ret; + + data_buf += host->cw_data; + oob_buf += ecc->bytes; + } + + return 0; +} + +/* implements ecc->read_oob() */ +static int qcom_nandc_read_oob(struct nand_chip *chip, int page) +{ + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + + if (host->nr_boot_partitions) + qcom_nandc_codeword_fixup(host, page); + + qcom_clear_read_regs(nandc); + qcom_clear_bam_transaction(nandc); + + host->use_ecc = true; + set_address(host, 0, page); + update_rw_regs(host, ecc->steps, true, 0); + + return read_page_ecc(host, NULL, chip->oob_poi, page); +} + +/* implements ecc->write_page() */ +static int qcom_nandc_write_page(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + u8 *data_buf, *oob_buf; + int i, ret; + + if (host->nr_boot_partitions) + qcom_nandc_codeword_fixup(host, page); + + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + + set_address(host, 0, page); + nandc->buf_count = 0; + nandc->buf_start = 0; + qcom_clear_read_regs(nandc); + qcom_clear_bam_transaction(nandc); + + data_buf = (u8 *)buf; + oob_buf = chip->oob_poi; + + host->use_ecc = true; + update_rw_regs(host, ecc->steps, false, 0); + config_nand_page_write(chip); + + for (i = 0; i < ecc->steps; i++) { + int data_size, oob_size; + + if (qcom_nandc_is_last_cw(ecc, i) && !host->codeword_fixup) { + data_size = ecc->size - ((ecc->steps - 1) << 2); + oob_size = (ecc->steps << 2) + host->ecc_bytes_hw + + host->spare_bytes; + } else { + data_size = host->cw_data; + oob_size = ecc->bytes; + } + + qcom_write_data_dma(nandc, FLASH_BUF_ACC, data_buf, data_size, + i == (ecc->steps - 1) ? NAND_BAM_NO_EOT : 0); + + /* + * when ECC is enabled, we don't really need to write anything + * to oob for the first n - 1 codewords since these oob regions + * just contain ECC bytes that's written by the controller + * itself. For the last codeword, we skip the bbm positions and + * write to the free oob area. + */ + if (qcom_nandc_is_last_cw(ecc, i)) { + oob_buf += host->bbm_size; + + qcom_write_data_dma(nandc, FLASH_BUF_ACC + data_size, + oob_buf, oob_size, 0); + } + + config_nand_cw_write(chip); + + data_buf += data_size; + oob_buf += oob_size; + } + + ret = qcom_submit_descs(nandc); + if (ret) { + dev_err(nandc->dev, "failure to write page\n"); + return ret; + } + + return nand_prog_page_end_op(chip); +} + +/* implements ecc->write_page_raw() */ +static int qcom_nandc_write_page_raw(struct nand_chip *chip, + const u8 *buf, int oob_required, + int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + u8 *data_buf, *oob_buf; + int i, ret; + + if (host->nr_boot_partitions) + qcom_nandc_codeword_fixup(host, page); + + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + qcom_clear_read_regs(nandc); + qcom_clear_bam_transaction(nandc); + + data_buf = (u8 *)buf; + oob_buf = chip->oob_poi; + + host->use_ecc = false; + update_rw_regs(host, ecc->steps, false, 0); + config_nand_page_write(chip); + + for (i = 0; i < ecc->steps; i++) { + int data_size1, data_size2, oob_size1, oob_size2; + int reg_off = FLASH_BUF_ACC; + + data_size1 = mtd->writesize - host->cw_size * (ecc->steps - 1); + oob_size1 = host->bbm_size; + + if (qcom_nandc_is_last_cw(ecc, i) && !host->codeword_fixup) { + data_size2 = ecc->size - data_size1 - + ((ecc->steps - 1) << 2); + oob_size2 = (ecc->steps << 2) + host->ecc_bytes_hw + + host->spare_bytes; + } else { + data_size2 = host->cw_data - data_size1; + oob_size2 = host->ecc_bytes_hw + host->spare_bytes; + } + + qcom_write_data_dma(nandc, reg_off, data_buf, data_size1, + NAND_BAM_NO_EOT); + reg_off += data_size1; + data_buf += data_size1; + + qcom_write_data_dma(nandc, reg_off, oob_buf, oob_size1, + NAND_BAM_NO_EOT); + reg_off += oob_size1; + oob_buf += oob_size1; + + qcom_write_data_dma(nandc, reg_off, data_buf, data_size2, + NAND_BAM_NO_EOT); + reg_off += data_size2; + data_buf += data_size2; + + qcom_write_data_dma(nandc, reg_off, oob_buf, oob_size2, 0); + oob_buf += oob_size2; + + config_nand_cw_write(chip); + } + + ret = qcom_submit_descs(nandc); + if (ret) { + dev_err(nandc->dev, "failure to write raw page\n"); + return ret; + } + + return nand_prog_page_end_op(chip); +} + +/* + * implements ecc->write_oob() + * + * the NAND controller cannot write only data or only OOB within a codeword + * since ECC is calculated for the combined codeword. So update the OOB from + * chip->oob_poi, and pad the data area with OxFF before writing. + */ +static int qcom_nandc_write_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + u8 *oob = chip->oob_poi; + int data_size, oob_size; + int ret; + + if (host->nr_boot_partitions) + qcom_nandc_codeword_fixup(host, page); + + host->use_ecc = true; + qcom_clear_bam_transaction(nandc); + + /* calculate the data and oob size for the last codeword/step */ + data_size = ecc->size - ((ecc->steps - 1) << 2); + oob_size = mtd->oobavail; + + memset(nandc->data_buffer, 0xff, host->cw_data); + /* override new oob content to last codeword */ + mtd_ooblayout_get_databytes(mtd, nandc->data_buffer + data_size, oob, + 0, mtd->oobavail); + + set_address(host, host->cw_size * (ecc->steps - 1), page); + update_rw_regs(host, 1, false, 0); + + config_nand_page_write(chip); + qcom_write_data_dma(nandc, FLASH_BUF_ACC, + nandc->data_buffer, data_size + oob_size, 0); + config_nand_cw_write(chip); + + ret = qcom_submit_descs(nandc); + if (ret) { + dev_err(nandc->dev, "failure to write oob\n"); + return ret; + } + + return nand_prog_page_end_op(chip); +} + +static int qcom_nandc_block_bad(struct nand_chip *chip, loff_t ofs) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int page, ret, bbpos, bad = 0; + + page = (int)(ofs >> chip->page_shift) & chip->pagemask; + + /* + * configure registers for a raw sub page read, the address is set to + * the beginning of the last codeword, we don't care about reading ecc + * portion of oob. we just want the first few bytes from this codeword + * that contains the BBM + */ + host->use_ecc = false; + + qcom_clear_bam_transaction(nandc); + ret = copy_last_cw(host, page); + if (ret) + goto err; + + if (check_flash_errors(host, 1)) { + dev_warn(nandc->dev, "error when trying to read BBM\n"); + goto err; + } + + bbpos = mtd->writesize - host->cw_size * (ecc->steps - 1); + + bad = nandc->data_buffer[bbpos] != 0xff; + + if (chip->options & NAND_BUSWIDTH_16) + bad = bad || (nandc->data_buffer[bbpos + 1] != 0xff); +err: + return bad; +} + +static int qcom_nandc_block_markbad(struct nand_chip *chip, loff_t ofs) +{ + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int page, ret; + + qcom_clear_read_regs(nandc); + qcom_clear_bam_transaction(nandc); + + /* + * to mark the BBM as bad, we flash the entire last codeword with 0s. + * we don't care about the rest of the content in the codeword since + * we aren't going to use this block again + */ + memset(nandc->data_buffer, 0x00, host->cw_size); + + page = (int)(ofs >> chip->page_shift) & chip->pagemask; + + /* prepare write */ + host->use_ecc = false; + set_address(host, host->cw_size * (ecc->steps - 1), page); + update_rw_regs(host, 1, false, ecc->steps - 1); + + config_nand_page_write(chip); + qcom_write_data_dma(nandc, FLASH_BUF_ACC, + nandc->data_buffer, host->cw_size, 0); + config_nand_cw_write(chip); + + ret = qcom_submit_descs(nandc); + if (ret) { + dev_err(nandc->dev, "failure to update BBM\n"); + return ret; + } + + return nand_prog_page_end_op(chip); +} + +/* + * NAND controller page layout info + * + * Layout with ECC enabled: + * + * |----------------------| |---------------------------------| + * | xx.......yy| | *********xx.......yy| + * | DATA xx..ECC..yy| | DATA **SPARE**xx..ECC..yy| + * | (516) xx.......yy| | (516-n*4) **(n*4)**xx.......yy| + * | xx.......yy| | *********xx.......yy| + * |----------------------| |---------------------------------| + * codeword 1,2..n-1 codeword n + * <---(528/532 Bytes)--> <-------(528/532 Bytes)---------> + * + * n = Number of codewords in the page + * . = ECC bytes + * * = Spare/free bytes + * x = Unused byte(s) + * y = Reserved byte(s) + * + * 2K page: n = 4, spare = 16 bytes + * 4K page: n = 8, spare = 32 bytes + * 8K page: n = 16, spare = 64 bytes + * + * the qcom nand controller operates at a sub page/codeword level. each + * codeword is 528 and 532 bytes for 4 bit and 8 bit ECC modes respectively. + * the number of ECC bytes vary based on the ECC strength and the bus width. + * + * the first n - 1 codewords contains 516 bytes of user data, the remaining + * 12/16 bytes consist of ECC and reserved data. The nth codeword contains + * both user data and spare(oobavail) bytes that sum up to 516 bytes. + * + * When we access a page with ECC enabled, the reserved bytes(s) are not + * accessible at all. When reading, we fill up these unreadable positions + * with 0xffs. When writing, the controller skips writing the inaccessible + * bytes. + * + * Layout with ECC disabled: + * + * |------------------------------| |---------------------------------------| + * | yy xx.......| | bb *********xx.......| + * | DATA1 yy DATA2 xx..ECC..| | DATA1 bb DATA2 **SPARE**xx..ECC..| + * | (size1) yy (size2) xx.......| | (size1) bb (size2) **(n*4)**xx.......| + * | yy xx.......| | bb *********xx.......| + * |------------------------------| |---------------------------------------| + * codeword 1,2..n-1 codeword n + * <-------(528/532 Bytes)------> <-----------(528/532 Bytes)-----------> + * + * n = Number of codewords in the page + * . = ECC bytes + * * = Spare/free bytes + * x = Unused byte(s) + * y = Dummy Bad Bock byte(s) + * b = Real Bad Block byte(s) + * size1/size2 = function of codeword size and 'n' + * + * when the ECC block is disabled, one reserved byte (or two for 16 bit bus + * width) is now accessible. For the first n - 1 codewords, these are dummy Bad + * Block Markers. In the last codeword, this position contains the real BBM + * + * In order to have a consistent layout between RAW and ECC modes, we assume + * the following OOB layout arrangement: + * + * |-----------| |--------------------| + * |yyxx.......| |bb*********xx.......| + * |yyxx..ECC..| |bb*FREEOOB*xx..ECC..| + * |yyxx.......| |bb*********xx.......| + * |yyxx.......| |bb*********xx.......| + * |-----------| |--------------------| + * first n - 1 nth OOB region + * OOB regions + * + * n = Number of codewords in the page + * . = ECC bytes + * * = FREE OOB bytes + * y = Dummy bad block byte(s) (inaccessible when ECC enabled) + * x = Unused byte(s) + * b = Real bad block byte(s) (inaccessible when ECC enabled) + * + * This layout is read as is when ECC is disabled. When ECC is enabled, the + * inaccessible Bad Block byte(s) are ignored when we write to a page/oob, + * and assumed as 0xffs when we read a page/oob. The ECC, unused and + * dummy/real bad block bytes are grouped as ecc bytes (i.e, ecc->bytes is + * the sum of the three). + */ +static int qcom_nand_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + + if (section > 1) + return -ERANGE; + + if (!section) { + oobregion->length = (ecc->bytes * (ecc->steps - 1)) + + host->bbm_size; + oobregion->offset = 0; + } else { + oobregion->length = host->ecc_bytes_hw + host->spare_bytes; + oobregion->offset = mtd->oobsize - oobregion->length; + } + + return 0; +} + +static int qcom_nand_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + + if (section) + return -ERANGE; + + oobregion->length = ecc->steps * 4; + oobregion->offset = ((ecc->steps - 1) * ecc->bytes) + host->bbm_size; + + return 0; +} + +static const struct mtd_ooblayout_ops qcom_nand_ooblayout_ops = { + .ecc = qcom_nand_ooblayout_ecc, + .free = qcom_nand_ooblayout_free, +}; + +static int +qcom_nandc_calc_ecc_bytes(int step_size, int strength) +{ + return strength == 4 ? 12 : 16; +} + +NAND_ECC_CAPS_SINGLE(qcom_nandc_ecc_caps, qcom_nandc_calc_ecc_bytes, + NANDC_STEP_SIZE, 4, 8); + +static int qcom_nand_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + int cwperpage, bad_block_byte, ret; + bool wide_bus; + int ecc_mode = ECC_MODE_8BIT; + + /* controller only supports 512 bytes data steps */ + ecc->size = NANDC_STEP_SIZE; + wide_bus = chip->options & NAND_BUSWIDTH_16 ? true : false; + cwperpage = mtd->writesize / NANDC_STEP_SIZE; + + /* + * Each CW has 4 available OOB bytes which will be protected with ECC + * so remaining bytes can be used for ECC. + */ + ret = nand_ecc_choose_conf(chip, &qcom_nandc_ecc_caps, + mtd->oobsize - (cwperpage * 4)); + if (ret) { + dev_err(nandc->dev, "No valid ECC settings possible\n"); + return ret; + } + + if (ecc->strength >= 8) { + /* 8 bit ECC defaults to BCH ECC on all platforms */ + host->bch_enabled = true; + ecc_mode = ECC_MODE_8BIT; + + if (wide_bus) { + host->ecc_bytes_hw = 14; + host->spare_bytes = 0; + host->bbm_size = 2; + } else { + host->ecc_bytes_hw = 13; + host->spare_bytes = 2; + host->bbm_size = 1; + } + } else { + /* + * if the controller supports BCH for 4 bit ECC, the controller + * uses lesser bytes for ECC. If RS is used, the ECC bytes is + * always 10 bytes + */ + if (nandc->props->ecc_modes & ECC_BCH_4BIT) { + /* BCH */ + host->bch_enabled = true; + ecc_mode = ECC_MODE_4BIT; + + if (wide_bus) { + host->ecc_bytes_hw = 8; + host->spare_bytes = 2; + host->bbm_size = 2; + } else { + host->ecc_bytes_hw = 7; + host->spare_bytes = 4; + host->bbm_size = 1; + } + } else { + /* RS */ + host->ecc_bytes_hw = 10; + + if (wide_bus) { + host->spare_bytes = 0; + host->bbm_size = 2; + } else { + host->spare_bytes = 1; + host->bbm_size = 1; + } + } + } + + /* + * we consider ecc->bytes as the sum of all the non-data content in a + * step. It gives us a clean representation of the oob area (even if + * all the bytes aren't used for ECC).It is always 16 bytes for 8 bit + * ECC and 12 bytes for 4 bit ECC + */ + ecc->bytes = host->ecc_bytes_hw + host->spare_bytes + host->bbm_size; + + ecc->read_page = qcom_nandc_read_page; + ecc->read_page_raw = qcom_nandc_read_page_raw; + ecc->read_oob = qcom_nandc_read_oob; + ecc->write_page = qcom_nandc_write_page; + ecc->write_page_raw = qcom_nandc_write_page_raw; + ecc->write_oob = qcom_nandc_write_oob; + + ecc->engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + + mtd_set_ooblayout(mtd, &qcom_nand_ooblayout_ops); + /* Free the initially allocated BAM transaction for reading the ONFI params */ + if (nandc->props->supports_bam) + qcom_free_bam_transaction(nandc); + + nandc->max_cwperpage = max_t(unsigned int, nandc->max_cwperpage, + cwperpage); + + /* Now allocate the BAM transaction based on updated max_cwperpage */ + if (nandc->props->supports_bam) { + nandc->bam_txn = qcom_alloc_bam_transaction(nandc); + if (!nandc->bam_txn) { + dev_err(nandc->dev, + "failed to allocate bam transaction\n"); + return -ENOMEM; + } + } + + /* + * DATA_UD_BYTES varies based on whether the read/write command protects + * spare data with ECC too. We protect spare data by default, so we set + * it to main + spare data, which are 512 and 4 bytes respectively. + */ + host->cw_data = 516; + + /* + * total bytes in a step, either 528 bytes for 4 bit ECC, or 532 bytes + * for 8 bit ECC + */ + host->cw_size = host->cw_data + ecc->bytes; + bad_block_byte = mtd->writesize - host->cw_size * (cwperpage - 1) + 1; + + host->cfg0 = FIELD_PREP(CW_PER_PAGE_MASK, (cwperpage - 1)) | + FIELD_PREP(UD_SIZE_BYTES_MASK, host->cw_data) | + FIELD_PREP(DISABLE_STATUS_AFTER_WRITE, 0) | + FIELD_PREP(NUM_ADDR_CYCLES_MASK, 5) | + FIELD_PREP(ECC_PARITY_SIZE_BYTES_RS, host->ecc_bytes_hw) | + FIELD_PREP(STATUS_BFR_READ, 0) | + FIELD_PREP(SET_RD_MODE_AFTER_STATUS, 1) | + FIELD_PREP(SPARE_SIZE_BYTES_MASK, host->spare_bytes); + + host->cfg1 = FIELD_PREP(NAND_RECOVERY_CYCLES_MASK, 7) | + FIELD_PREP(BAD_BLOCK_BYTE_NUM_MASK, bad_block_byte) | + FIELD_PREP(BAD_BLOCK_IN_SPARE_AREA, 0) | + FIELD_PREP(WR_RD_BSY_GAP_MASK, 2) | + FIELD_PREP(WIDE_FLASH, wide_bus) | + FIELD_PREP(ENABLE_BCH_ECC, host->bch_enabled); + + host->cfg0_raw = FIELD_PREP(CW_PER_PAGE_MASK, (cwperpage - 1)) | + FIELD_PREP(UD_SIZE_BYTES_MASK, host->cw_size) | + FIELD_PREP(NUM_ADDR_CYCLES_MASK, 5) | + FIELD_PREP(SPARE_SIZE_BYTES_MASK, 0); + + host->cfg1_raw = FIELD_PREP(NAND_RECOVERY_CYCLES_MASK, 7) | + FIELD_PREP(CS_ACTIVE_BSY, 0) | + FIELD_PREP(BAD_BLOCK_BYTE_NUM_MASK, 17) | + FIELD_PREP(BAD_BLOCK_IN_SPARE_AREA, 1) | + FIELD_PREP(WR_RD_BSY_GAP_MASK, 2) | + FIELD_PREP(WIDE_FLASH, wide_bus) | + FIELD_PREP(DEV0_CFG1_ECC_DISABLE, 1); + + host->ecc_bch_cfg = FIELD_PREP(ECC_CFG_ECC_DISABLE, !host->bch_enabled) | + FIELD_PREP(ECC_SW_RESET, 0) | + FIELD_PREP(ECC_NUM_DATA_BYTES_MASK, host->cw_data) | + FIELD_PREP(ECC_FORCE_CLK_OPEN, 1) | + FIELD_PREP(ECC_MODE_MASK, ecc_mode) | + FIELD_PREP(ECC_PARITY_SIZE_BYTES_BCH_MASK, host->ecc_bytes_hw); + + if (!nandc->props->qpic_version2) + host->ecc_buf_cfg = FIELD_PREP(NUM_STEPS_MASK, 0x203); + + host->clrflashstatus = FS_READY_BSY_N; + host->clrreadstatus = 0xc0; + nandc->regs->erased_cw_detect_cfg_clr = + cpu_to_le32(CLR_ERASED_PAGE_DET); + nandc->regs->erased_cw_detect_cfg_set = + cpu_to_le32(SET_ERASED_PAGE_DET); + + dev_dbg(nandc->dev, + "cfg0 %x cfg1 %x ecc_buf_cfg %x ecc_bch cfg %x cw_size %d cw_data %d strength %d parity_bytes %d steps %d\n", + host->cfg0, host->cfg1, host->ecc_buf_cfg, host->ecc_bch_cfg, + host->cw_size, host->cw_data, ecc->strength, ecc->bytes, + cwperpage); + + return 0; +} + +static int qcom_op_cmd_mapping(struct nand_chip *chip, u8 opcode, + struct qcom_op *q_op) +{ + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct qcom_nand_host *host = to_qcom_nand_host(chip); + int cmd; + + switch (opcode) { + case NAND_CMD_RESET: + cmd = OP_RESET_DEVICE; + break; + case NAND_CMD_READID: + cmd = OP_FETCH_ID; + break; + case NAND_CMD_PARAM: + if (nandc->props->has_onfi_read_op) + cmd = OP_PAGE_READ_ONFI_READ; + else + cmd = OP_PAGE_READ; + break; + case NAND_CMD_ERASE1: + case NAND_CMD_ERASE2: + cmd = OP_BLOCK_ERASE; + break; + case NAND_CMD_STATUS: + cmd = OP_CHECK_STATUS; + break; + case NAND_CMD_PAGEPROG: + cmd = OP_PROGRAM_PAGE; + q_op->flag = OP_PROGRAM_PAGE; + nandc->exec_opwrite = true; + break; + case NAND_CMD_READ0: + case NAND_CMD_READSTART: + if (host->use_ecc) + cmd = OP_PAGE_READ_WITH_ECC; + else + cmd = OP_PAGE_READ; + break; + default: + dev_err(nandc->dev, "Opcode not supported: %u\n", opcode); + return -EOPNOTSUPP; + } + + return cmd; +} + +/* NAND framework ->exec_op() hooks and related helpers */ +static int qcom_parse_instructions(struct nand_chip *chip, + const struct nand_subop *subop, + struct qcom_op *q_op) +{ + const struct nand_op_instr *instr = NULL; + unsigned int op_id; + int i, ret; + + for (op_id = 0; op_id < subop->ninstrs; op_id++) { + unsigned int offset, naddrs; + const u8 *addrs; + + instr = &subop->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + ret = qcom_op_cmd_mapping(chip, instr->ctx.cmd.opcode, q_op); + if (ret < 0) + return ret; + + q_op->cmd_reg = cpu_to_le32(ret); + q_op->rdy_delay_ns = instr->delay_ns; + break; + + case NAND_OP_ADDR_INSTR: + offset = nand_subop_get_addr_start_off(subop, op_id); + naddrs = nand_subop_get_num_addr_cyc(subop, op_id); + addrs = &instr->ctx.addr.addrs[offset]; + + for (i = 0; i < min_t(unsigned int, 4, naddrs); i++) + q_op->addr1_reg |= cpu_to_le32(addrs[i] << (i * 8)); + + if (naddrs > 4) + q_op->addr2_reg |= cpu_to_le32(addrs[4]); + + q_op->rdy_delay_ns = instr->delay_ns; + break; + + case NAND_OP_DATA_IN_INSTR: + q_op->data_instr = instr; + q_op->data_instr_idx = op_id; + q_op->rdy_delay_ns = instr->delay_ns; + fallthrough; + case NAND_OP_DATA_OUT_INSTR: + q_op->rdy_delay_ns = instr->delay_ns; + break; + + case NAND_OP_WAITRDY_INSTR: + q_op->rdy_timeout_ms = instr->ctx.waitrdy.timeout_ms; + q_op->rdy_delay_ns = instr->delay_ns; + break; + } + } + + return 0; +} + +static void qcom_delay_ns(unsigned int ns) +{ + if (!ns) + return; + + if (ns < 10000) + ndelay(ns); + else + udelay(DIV_ROUND_UP(ns, 1000)); +} + +static int qcom_wait_rdy_poll(struct nand_chip *chip, unsigned int time_ms) +{ + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + unsigned long start = jiffies + msecs_to_jiffies(time_ms); + u32 flash; + + qcom_nandc_dev_to_mem(nandc, true); + + do { + flash = le32_to_cpu(nandc->reg_read_buf[0]); + if (flash & FS_READY_BSY_N) + return 0; + cpu_relax(); + } while (time_after(start, jiffies)); + + dev_err(nandc->dev, "Timeout waiting for device to be ready:0x%08x\n", flash); + + return -ETIMEDOUT; +} + +static int qcom_read_status_exec(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + struct qcom_op q_op = {}; + const struct nand_op_instr *instr = NULL; + unsigned int op_id = 0; + unsigned int len = 0; + int ret, num_cw, i; + u32 flash_status; + + host->status = NAND_STATUS_READY | NAND_STATUS_WP; + + ret = qcom_parse_instructions(chip, subop, &q_op); + if (ret) + return ret; + + num_cw = nandc->exec_opwrite ? ecc->steps : 1; + nandc->exec_opwrite = false; + + nandc->buf_count = 0; + nandc->buf_start = 0; + host->use_ecc = false; + + qcom_clear_read_regs(nandc); + qcom_clear_bam_transaction(nandc); + + nandc->regs->cmd = q_op.cmd_reg; + nandc->regs->exec = cpu_to_le32(1); + + qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 1, NAND_BAM_NEXT_SGL); + qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL); + qcom_read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL); + + ret = qcom_submit_descs(nandc); + if (ret) { + dev_err(nandc->dev, "failure in submitting status descriptor\n"); + goto err_out; + } + + qcom_nandc_dev_to_mem(nandc, true); + + for (i = 0; i < num_cw; i++) { + flash_status = le32_to_cpu(nandc->reg_read_buf[i]); + + if (flash_status & FS_MPU_ERR) + host->status &= ~NAND_STATUS_WP; + + if (flash_status & FS_OP_ERR || + (i == (num_cw - 1) && (flash_status & FS_DEVICE_STS_ERR))) + host->status |= NAND_STATUS_FAIL; + } + + flash_status = host->status; + instr = q_op.data_instr; + op_id = q_op.data_instr_idx; + len = nand_subop_get_data_len(subop, op_id); + memcpy(instr->ctx.data.buf.in, &flash_status, len); + +err_out: + return ret; +} + +static int qcom_read_id_type_exec(struct nand_chip *chip, const struct nand_subop *subop) +{ + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct qcom_op q_op = {}; + const struct nand_op_instr *instr = NULL; + unsigned int op_id = 0; + unsigned int len = 0; + int ret; + + ret = qcom_parse_instructions(chip, subop, &q_op); + if (ret) + return ret; + + nandc->buf_count = 0; + nandc->buf_start = 0; + host->use_ecc = false; + + qcom_clear_read_regs(nandc); + qcom_clear_bam_transaction(nandc); + + nandc->regs->cmd = q_op.cmd_reg; + nandc->regs->addr0 = q_op.addr1_reg; + nandc->regs->addr1 = q_op.addr2_reg; + nandc->regs->chip_sel = cpu_to_le32(nandc->props->supports_bam ? 0 : DM_EN); + nandc->regs->exec = cpu_to_le32(1); + + qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, 4, NAND_BAM_NEXT_SGL); + qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL); + + qcom_read_reg_dma(nandc, NAND_READ_ID, 1, NAND_BAM_NEXT_SGL); + + ret = qcom_submit_descs(nandc); + if (ret) { + dev_err(nandc->dev, "failure in submitting read id descriptor\n"); + goto err_out; + } + + instr = q_op.data_instr; + op_id = q_op.data_instr_idx; + len = nand_subop_get_data_len(subop, op_id); + + qcom_nandc_dev_to_mem(nandc, true); + memcpy(instr->ctx.data.buf.in, nandc->reg_read_buf, len); + +err_out: + return ret; +} + +static int qcom_misc_cmd_type_exec(struct nand_chip *chip, const struct nand_subop *subop) +{ + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct qcom_op q_op = {}; + int ret; + int instrs = 1; + + ret = qcom_parse_instructions(chip, subop, &q_op); + if (ret) + return ret; + + if (q_op.flag == OP_PROGRAM_PAGE) { + goto wait_rdy; + } else if (q_op.cmd_reg == cpu_to_le32(OP_BLOCK_ERASE)) { + 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 & ~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)) { + return 0; + } + + nandc->buf_count = 0; + nandc->buf_start = 0; + host->use_ecc = false; + + qcom_clear_read_regs(nandc); + qcom_clear_bam_transaction(nandc); + + nandc->regs->cmd = q_op.cmd_reg; + nandc->regs->exec = cpu_to_le32(1); + + qcom_write_reg_dma(nandc, &nandc->regs->cmd, NAND_FLASH_CMD, instrs, NAND_BAM_NEXT_SGL); + if (q_op.cmd_reg == cpu_to_le32(OP_BLOCK_ERASE)) + qcom_write_reg_dma(nandc, &nandc->regs->cfg0, NAND_DEV0_CFG0, 2, NAND_BAM_NEXT_SGL); + + qcom_write_reg_dma(nandc, &nandc->regs->exec, NAND_EXEC_CMD, 1, NAND_BAM_NEXT_SGL); + qcom_read_reg_dma(nandc, NAND_FLASH_STATUS, 1, NAND_BAM_NEXT_SGL); + + ret = qcom_submit_descs(nandc); + if (ret) { + dev_err(nandc->dev, "failure in submitting misc descriptor\n"); + goto err_out; + } + +wait_rdy: + qcom_delay_ns(q_op.rdy_delay_ns); + ret = qcom_wait_rdy_poll(chip, q_op.rdy_timeout_ms); + +err_out: + return ret; +} + +static int qcom_param_page_type_exec(struct nand_chip *chip, const struct nand_subop *subop) +{ + struct qcom_nand_host *host = to_qcom_nand_host(chip); + struct qcom_nand_controller *nandc = get_qcom_nand_controller(chip); + struct qcom_op q_op = {}; + const struct nand_op_instr *instr = NULL; + unsigned int op_id = 0; + unsigned int len = 0; + 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) + return ret; + + q_op.cmd_reg |= cpu_to_le32(PAGE_ACC | LAST_PAGE); + + nandc->buf_count = 0; + nandc->buf_start = 0; + host->use_ecc = false; + qcom_clear_read_regs(nandc); + qcom_clear_bam_transaction(nandc); + + nandc->regs->cmd = q_op.cmd_reg; + nandc->regs->addr0 = 0; + nandc->regs->addr1 = 0; + + nandc->regs->cfg0 = cpu_to_le32(FIELD_PREP(CW_PER_PAGE_MASK, 0) | + FIELD_PREP(UD_SIZE_BYTES_MASK, 512) | + FIELD_PREP(NUM_ADDR_CYCLES_MASK, 5) | + FIELD_PREP(SPARE_SIZE_BYTES_MASK, 0)); + + nandc->regs->cfg1 = cpu_to_le32(FIELD_PREP(NAND_RECOVERY_CYCLES_MASK, 7) | + FIELD_PREP(BAD_BLOCK_BYTE_NUM_MASK, 17) | + FIELD_PREP(CS_ACTIVE_BSY, 0) | + FIELD_PREP(BAD_BLOCK_IN_SPARE_AREA, 1) | + FIELD_PREP(WR_RD_BSY_GAP_MASK, 2) | + FIELD_PREP(WIDE_FLASH, 0) | + FIELD_PREP(DEV0_CFG1_ECC_DISABLE, 1)); + + if (!nandc->props->qpic_version2) + nandc->regs->ecc_buf_cfg = cpu_to_le32(ECC_CFG_ECC_DISABLE); + + /* configure CMD1 and VLD for ONFI param probing in QPIC v1 */ + if (!nandc->props->has_onfi_read_op) { + nandc->regs->vld = cpu_to_le32((nandc->vld & ~READ_START_VLD)); + 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); + + if (!nandc->props->has_onfi_read_op) { + nandc->regs->orig_cmd1 = cpu_to_le32(nandc->cmd1); + nandc->regs->orig_vld = cpu_to_le32(nandc->vld); + } + + instr = q_op.data_instr; + op_id = q_op.data_instr_idx; + len = nand_subop_get_data_len(subop, op_id); + + 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->has_onfi_read_op) { + 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 = 512; + memset(nandc->data_buffer, 0xff, nandc->buf_count); + + config_nand_single_cw_page_read(chip, false, 0); + + qcom_read_data_dma(nandc, FLASH_BUF_ACC, nandc->data_buffer, + nandc->buf_count, 0); + + /* restore CMD1 and VLD regs */ + if (!nandc->props->has_onfi_read_op) { + qcom_write_reg_dma(nandc, &nandc->regs->orig_cmd1, NAND_DEV_CMD1_RESTORE, 1, 0); + qcom_write_reg_dma(nandc, &nandc->regs->orig_vld, NAND_DEV_CMD_VLD_RESTORE, 1, + NAND_BAM_NEXT_SGL); + } + + ret = qcom_submit_descs(nandc); + if (ret) { + dev_err(nandc->dev, "failure in submitting param page descriptor\n"); + goto err_out; + } + + ret = qcom_wait_rdy_poll(chip, q_op.rdy_timeout_ms); + if (ret) + goto err_out; + + memcpy(instr->ctx.data.buf.in, nandc->data_buffer, len); + +err_out: + return ret; +} + +static const struct nand_op_parser qcom_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN( + qcom_read_id_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYCLE), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 8)), + NAND_OP_PARSER_PATTERN( + qcom_read_status_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 1)), + NAND_OP_PARSER_PATTERN( + qcom_param_page_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(false, MAX_ADDRESS_CYCLE), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(false, 512)), + NAND_OP_PARSER_PATTERN( + qcom_misc_cmd_type_exec, + NAND_OP_PARSER_PAT_CMD_ELEM(false), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, MAX_ADDRESS_CYCLE), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), + ); + +static int qcom_check_op(struct nand_chip *chip, + const struct nand_operation *op) +{ + const struct nand_op_instr *instr; + int op_id; + + for (op_id = 0; op_id < op->ninstrs; op_id++) { + instr = &op->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + if (instr->ctx.cmd.opcode != NAND_CMD_RESET && + instr->ctx.cmd.opcode != NAND_CMD_READID && + instr->ctx.cmd.opcode != NAND_CMD_PARAM && + instr->ctx.cmd.opcode != NAND_CMD_ERASE1 && + instr->ctx.cmd.opcode != NAND_CMD_ERASE2 && + instr->ctx.cmd.opcode != NAND_CMD_STATUS && + instr->ctx.cmd.opcode != NAND_CMD_PAGEPROG && + instr->ctx.cmd.opcode != NAND_CMD_READ0 && + instr->ctx.cmd.opcode != NAND_CMD_READSTART) + return -EOPNOTSUPP; + break; + default: + break; + } + } + + return 0; +} + +static int qcom_nand_exec_op(struct nand_chip *chip, + const struct nand_operation *op, bool check_only) +{ + if (check_only) + return qcom_check_op(chip, op); + + return nand_op_parser_exec_op(chip, &qcom_op_parser, op, check_only); +} + +static const struct nand_controller_ops qcom_nandc_ops = { + .attach_chip = qcom_nand_attach_chip, + .exec_op = qcom_nand_exec_op, +}; + +/* one time setup of a few nand controller registers */ +static int qcom_nandc_setup(struct qcom_nand_controller *nandc) +{ + u32 nand_ctrl; + + nand_controller_init(&nandc->controller); + nandc->controller.ops = &qcom_nandc_ops; + + /* kill onenand */ + if (!nandc->props->nandc_part_of_qpic) + nandc_write(nandc, SFLASHC_BURST_CFG, 0); + + if (!nandc->props->has_onfi_read_op) + nandc_write(nandc, dev_cmd_reg_addr(nandc, NAND_DEV_CMD_VLD), + NAND_DEV_CMD_VLD_VAL); + + /* enable ADM or BAM DMA */ + if (nandc->props->supports_bam) { + nand_ctrl = nandc_read(nandc, NAND_CTRL); + + /* + *NAND_CTRL is an operational registers, and CPU + * access to operational registers are read only + * in BAM mode. So update the NAND_CTRL register + * only if it is not in BAM mode. In most cases BAM + * mode will be enabled in bootloader + */ + if (!(nand_ctrl & BAM_MODE_EN)) + nandc_write(nandc, NAND_CTRL, nand_ctrl | BAM_MODE_EN); + } else { + nandc_write(nandc, NAND_FLASH_CHIP_SELECT, DM_EN); + } + + /* save the original values of these registers */ + if (!nandc->props->has_onfi_read_op) { + nandc->cmd1 = nandc_read(nandc, dev_cmd_reg_addr(nandc, NAND_DEV_CMD1)); + nandc->vld = NAND_DEV_CMD_VLD_VAL; + } + + return 0; +} + +static const char * const probes[] = { "cmdlinepart", "ofpart", "qcomsmem", NULL }; + +static int qcom_nand_host_parse_boot_partitions(struct qcom_nand_controller *nandc, + struct qcom_nand_host *host, + struct device_node *dn) +{ + struct nand_chip *chip = &host->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + struct qcom_nand_boot_partition *boot_partition; + struct device *dev = nandc->dev; + int partitions_count, i, j, ret; + + if (!of_property_present(dn, "qcom,boot-partitions")) + return 0; + + partitions_count = of_property_count_u32_elems(dn, "qcom,boot-partitions"); + if (partitions_count <= 0) { + dev_err(dev, "Error parsing boot partition\n"); + return partitions_count ? partitions_count : -EINVAL; + } + + host->nr_boot_partitions = partitions_count / 2; + host->boot_partitions = devm_kcalloc(dev, host->nr_boot_partitions, + sizeof(*host->boot_partitions), GFP_KERNEL); + if (!host->boot_partitions) { + host->nr_boot_partitions = 0; + return -ENOMEM; + } + + for (i = 0, j = 0; i < host->nr_boot_partitions; i++, j += 2) { + boot_partition = &host->boot_partitions[i]; + + ret = of_property_read_u32_index(dn, "qcom,boot-partitions", j, + &boot_partition->page_offset); + if (ret) { + dev_err(dev, "Error parsing boot partition offset at index %d\n", i); + host->nr_boot_partitions = 0; + return ret; + } + + if (boot_partition->page_offset % mtd->writesize) { + dev_err(dev, "Boot partition offset not multiple of writesize at index %i\n", + i); + host->nr_boot_partitions = 0; + return -EINVAL; + } + /* Convert offset to nand pages */ + boot_partition->page_offset /= mtd->writesize; + + ret = of_property_read_u32_index(dn, "qcom,boot-partitions", j + 1, + &boot_partition->page_size); + if (ret) { + dev_err(dev, "Error parsing boot partition size at index %d\n", i); + host->nr_boot_partitions = 0; + return ret; + } + + if (boot_partition->page_size % mtd->writesize) { + dev_err(dev, "Boot partition size not multiple of writesize at index %i\n", + i); + host->nr_boot_partitions = 0; + return -EINVAL; + } + /* Convert size to nand pages */ + boot_partition->page_size /= mtd->writesize; + } + + return 0; +} + +static int qcom_nand_host_init_and_register(struct qcom_nand_controller *nandc, + struct qcom_nand_host *host, + struct device_node *dn) +{ + struct nand_chip *chip = &host->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + struct device *dev = nandc->dev; + int ret; + + ret = of_property_read_u32(dn, "reg", &host->cs); + if (ret) { + dev_err(dev, "can't get chip-select\n"); + return -ENXIO; + } + + nand_set_flash_node(chip, dn); + mtd->name = devm_kasprintf(dev, GFP_KERNEL, "qcom_nand.%d", host->cs); + if (!mtd->name) + return -ENOMEM; + + mtd->owner = THIS_MODULE; + mtd->dev.parent = dev; + + /* + * the bad block marker is readable only when we read the last codeword + * of a page with ECC disabled. currently, the nand_base and nand_bbt + * helpers don't allow us to read BB from a nand chip with ECC + * disabled (MTD_OPS_PLACE_OOB is set by default). use the block_bad + * and block_markbad helpers until we permanently switch to using + * MTD_OPS_RAW for all drivers (with the help of badblockbits) + */ + chip->legacy.block_bad = qcom_nandc_block_bad; + chip->legacy.block_markbad = qcom_nandc_block_markbad; + + chip->controller = &nandc->controller; + chip->options |= NAND_NO_SUBPAGE_WRITE | NAND_USES_DMA | + NAND_SKIP_BBTSCAN; + + /* set up initial status value */ + host->status = NAND_STATUS_READY | NAND_STATUS_WP; + + ret = nand_scan(chip, 1); + if (ret) + return ret; + + ret = mtd_device_parse_register(mtd, probes, NULL, NULL, 0); + if (ret) + goto err; + + if (nandc->props->use_codeword_fixup) { + ret = qcom_nand_host_parse_boot_partitions(nandc, host, dn); + if (ret) + goto err; + } + + return 0; + +err: + nand_cleanup(chip); + return ret; +} + +static int qcom_probe_nand_devices(struct qcom_nand_controller *nandc) +{ + struct device *dev = nandc->dev; + struct device_node *dn = dev->of_node; + struct qcom_nand_host *host; + int ret = -ENODEV; + + for_each_available_child_of_node_scoped(dn, child) { + host = devm_kzalloc(dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + ret = qcom_nand_host_init_and_register(nandc, host, child); + if (ret) { + devm_kfree(dev, host); + continue; + } + + list_add_tail(&host->node, &nandc->host_list); + } + + return ret; +} + +/* parse custom DT properties here */ +static int qcom_nandc_parse_dt(struct platform_device *pdev) +{ + struct qcom_nand_controller *nandc = platform_get_drvdata(pdev); + struct device_node *np = nandc->dev->of_node; + int ret; + + if (!nandc->props->supports_bam) { + ret = of_property_read_u32(np, "qcom,cmd-crci", + &nandc->cmd_crci); + if (ret) { + dev_err(nandc->dev, "command CRCI unspecified\n"); + return ret; + } + + ret = of_property_read_u32(np, "qcom,data-crci", + &nandc->data_crci); + if (ret) { + dev_err(nandc->dev, "data CRCI unspecified\n"); + return ret; + } + } + + return 0; +} + +static int qcom_nandc_probe(struct platform_device *pdev) +{ + struct qcom_nand_controller *nandc; + const void *dev_data; + struct device *dev = &pdev->dev; + struct resource *res; + int ret; + + nandc = devm_kzalloc(&pdev->dev, sizeof(*nandc), GFP_KERNEL); + if (!nandc) + return -ENOMEM; + + platform_set_drvdata(pdev, nandc); + nandc->dev = dev; + + dev_data = of_device_get_match_data(dev); + if (!dev_data) { + dev_err(&pdev->dev, "failed to get device data\n"); + return -ENODEV; + } + + nandc->props = dev_data; + + nandc->core_clk = devm_clk_get(dev, "core"); + if (IS_ERR(nandc->core_clk)) + return PTR_ERR(nandc->core_clk); + + nandc->aon_clk = devm_clk_get_optional(dev, "aon"); + if (IS_ERR(nandc->aon_clk)) + return PTR_ERR(nandc->aon_clk); + + ret = qcom_nandc_parse_dt(pdev); + if (ret) + return ret; + + nandc->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(nandc->base)) + return PTR_ERR(nandc->base); + + nandc->base_phys = res->start; + nandc->base_dma = dma_map_resource(dev, res->start, + resource_size(res), + DMA_BIDIRECTIONAL, 0); + if (dma_mapping_error(dev, nandc->base_dma)) + return -ENXIO; + + ret = clk_prepare_enable(nandc->core_clk); + if (ret) + goto err_core_clk; + + ret = clk_prepare_enable(nandc->aon_clk); + if (ret) + goto err_aon_clk; + + ret = qcom_nandc_alloc(nandc); + if (ret) + goto err_nandc_alloc; + + ret = qcom_nandc_setup(nandc); + if (ret) + goto err_setup; + + ret = qcom_probe_nand_devices(nandc); + if (ret) + goto err_setup; + + return 0; + +err_setup: + qcom_nandc_unalloc(nandc); +err_nandc_alloc: + clk_disable_unprepare(nandc->aon_clk); +err_aon_clk: + clk_disable_unprepare(nandc->core_clk); +err_core_clk: + dma_unmap_resource(dev, nandc->base_dma, resource_size(res), + DMA_BIDIRECTIONAL, 0); + return ret; +} + +static void qcom_nandc_remove(struct platform_device *pdev) +{ + struct qcom_nand_controller *nandc = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct qcom_nand_host *host; + struct nand_chip *chip; + int ret; + + list_for_each_entry(host, &nandc->host_list, node) { + chip = &host->chip; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + } + + qcom_nandc_unalloc(nandc); + + clk_disable_unprepare(nandc->aon_clk); + clk_disable_unprepare(nandc->core_clk); + + dma_unmap_resource(&pdev->dev, nandc->base_dma, resource_size(res), + DMA_BIDIRECTIONAL, 0); +} + +static const struct qcom_nandc_props ipq806x_nandc_props = { + .ecc_modes = (ECC_RS_4BIT | ECC_BCH_8BIT), + .supports_bam = false, + .use_codeword_fixup = true, + .dev_cmd_reg_start = 0x0, + .bam_offset = 0x30000, +}; + +static const struct qcom_nandc_props ipq4019_nandc_props = { + .ecc_modes = (ECC_BCH_4BIT | ECC_BCH_8BIT), + .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 = { + .ecc_modes = (ECC_BCH_4BIT | ECC_BCH_8BIT), + .supports_bam = true, + .nandc_part_of_qpic = true, + .dev_cmd_reg_start = 0x7000, + .bam_offset = 0x30000, +}; + +static const struct qcom_nandc_props mdm9607_nandc_props = { + .ecc_modes = (ECC_BCH_4BIT | ECC_BCH_8BIT), + .supports_bam = true, + .nandc_part_of_qpic = true, + .has_onfi_read_op = true, + .dev_cmd_reg_start = 0x7000, + .bam_offset = 0x30000, +}; + +static const struct qcom_nandc_props sdx55_nandc_props = { + .ecc_modes = (ECC_BCH_4BIT | ECC_BCH_8BIT), + .supports_bam = true, + .nandc_part_of_qpic = true, + .has_onfi_read_op = true, + .qpic_version2 = true, + .dev_cmd_reg_start = 0x7000, + .bam_offset = 0x30000, +}; + +/* + * data will hold a struct pointer containing more differences once we support + * more controller variants + */ +static const struct of_device_id qcom_nandc_of_match[] = { + { + .compatible = "qcom,ipq806x-nand", + .data = &ipq806x_nandc_props, + }, + { + .compatible = "qcom,ipq4019-nand", + .data = &ipq4019_nandc_props, + }, + { + .compatible = "qcom,ipq6018-nand", + .data = &ipq8074_nandc_props, + }, + { + .compatible = "qcom,ipq8074-nand", + .data = &ipq8074_nandc_props, + }, + { + .compatible = "qcom,mdm9607-nand", + .data = &mdm9607_nandc_props, + }, + { + .compatible = "qcom,sdx55-nand", + .data = &sdx55_nandc_props, + }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_nandc_of_match); + +static struct platform_driver qcom_nandc_driver = { + .driver = { + .name = "qcom-nandc", + .of_match_table = qcom_nandc_of_match, + }, + .probe = qcom_nandc_probe, + .remove = qcom_nandc_remove, +}; +module_platform_driver(qcom_nandc_driver); + +MODULE_AUTHOR("Archit Taneja "); +MODULE_DESCRIPTION("Qualcomm NAND Controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/raw/r852.c b/drivers/mtd/nand/raw/r852.c new file mode 100644 index 000000000..92c47d351 --- /dev/null +++ b/drivers/mtd/nand/raw/r852.c @@ -0,0 +1,1094 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright © 2009 - Maxim Levitsky + * driver for Ricoh xD readers + */ + +#define DRV_NAME "r852" +#define pr_fmt(fmt) DRV_NAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sm_common.h" +#include "r852.h" + + +static bool r852_enable_dma = 1; +module_param(r852_enable_dma, bool, S_IRUGO); +MODULE_PARM_DESC(r852_enable_dma, "Enable usage of the DMA (default)"); + +static int debug; +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug level (0-2)"); + +/* read register */ +static inline uint8_t r852_read_reg(struct r852_device *dev, int address) +{ + uint8_t reg = readb(dev->mmio + address); + return reg; +} + +/* write register */ +static inline void r852_write_reg(struct r852_device *dev, + int address, uint8_t value) +{ + writeb(value, dev->mmio + address); +} + + +/* read dword sized register */ +static inline uint32_t r852_read_reg_dword(struct r852_device *dev, int address) +{ + uint32_t reg = le32_to_cpu(readl(dev->mmio + address)); + return reg; +} + +/* write dword sized register */ +static inline void r852_write_reg_dword(struct r852_device *dev, + int address, uint32_t value) +{ + writel(cpu_to_le32(value), dev->mmio + address); +} + +/* returns pointer to our private structure */ +static inline struct r852_device *r852_get_dev(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + return nand_get_controller_data(chip); +} + + +/* check if controller supports dma */ +static void r852_dma_test(struct r852_device *dev) +{ + dev->dma_usable = (r852_read_reg(dev, R852_DMA_CAP) & + (R852_DMA1 | R852_DMA2)) == (R852_DMA1 | R852_DMA2); + + if (!dev->dma_usable) + message("Non dma capable device detected, dma disabled"); + + if (!r852_enable_dma) { + message("disabling dma on user request"); + dev->dma_usable = 0; + } +} + +/* + * Enable dma. Enables ether first or second stage of the DMA, + * Expects dev->dma_dir and dev->dma_state be set + */ +static void r852_dma_enable(struct r852_device *dev) +{ + uint8_t dma_reg, dma_irq_reg; + + /* Set up dma settings */ + dma_reg = r852_read_reg_dword(dev, R852_DMA_SETTINGS); + dma_reg &= ~(R852_DMA_READ | R852_DMA_INTERNAL | R852_DMA_MEMORY); + + if (dev->dma_dir) + dma_reg |= R852_DMA_READ; + + if (dev->dma_state == DMA_INTERNAL) { + dma_reg |= R852_DMA_INTERNAL; + /* Precaution to make sure HW doesn't write */ + /* to random kernel memory */ + r852_write_reg_dword(dev, R852_DMA_ADDR, + cpu_to_le32(dev->phys_bounce_buffer)); + } else { + dma_reg |= R852_DMA_MEMORY; + r852_write_reg_dword(dev, R852_DMA_ADDR, + cpu_to_le32(dev->phys_dma_addr)); + } + + /* Precaution: make sure write reached the device */ + r852_read_reg_dword(dev, R852_DMA_ADDR); + + r852_write_reg_dword(dev, R852_DMA_SETTINGS, dma_reg); + + /* Set dma irq */ + dma_irq_reg = r852_read_reg_dword(dev, R852_DMA_IRQ_ENABLE); + r852_write_reg_dword(dev, R852_DMA_IRQ_ENABLE, + dma_irq_reg | + R852_DMA_IRQ_INTERNAL | + R852_DMA_IRQ_ERROR | + R852_DMA_IRQ_MEMORY); +} + +/* + * Disable dma, called from the interrupt handler, which specifies + * success of the operation via 'error' argument + */ +static void r852_dma_done(struct r852_device *dev, int error) +{ + WARN_ON(dev->dma_stage == 0); + + r852_write_reg_dword(dev, R852_DMA_IRQ_STA, + r852_read_reg_dword(dev, R852_DMA_IRQ_STA)); + + r852_write_reg_dword(dev, R852_DMA_SETTINGS, 0); + r852_write_reg_dword(dev, R852_DMA_IRQ_ENABLE, 0); + + /* Precaution to make sure HW doesn't write to random kernel memory */ + r852_write_reg_dword(dev, R852_DMA_ADDR, + cpu_to_le32(dev->phys_bounce_buffer)); + r852_read_reg_dword(dev, R852_DMA_ADDR); + + dev->dma_error = error; + dev->dma_stage = 0; + + if (dev->phys_dma_addr && dev->phys_dma_addr != dev->phys_bounce_buffer) + dma_unmap_single(&dev->pci_dev->dev, dev->phys_dma_addr, + R852_DMA_LEN, + dev->dma_dir ? DMA_FROM_DEVICE : DMA_TO_DEVICE); +} + +/* + * Wait, till dma is done, which includes both phases of it + */ +static int r852_dma_wait(struct r852_device *dev) +{ + long timeout = wait_for_completion_timeout(&dev->dma_done, + msecs_to_jiffies(1000)); + if (!timeout) { + dbg("timeout waiting for DMA interrupt"); + return -ETIMEDOUT; + } + + return 0; +} + +/* + * Read/Write one page using dma. Only pages can be read (512 bytes) +*/ +static void r852_do_dma(struct r852_device *dev, uint8_t *buf, int do_read) +{ + int bounce = 0; + unsigned long flags; + int error; + + dev->dma_error = 0; + + /* Set dma direction */ + dev->dma_dir = do_read; + dev->dma_stage = 1; + reinit_completion(&dev->dma_done); + + dbg_verbose("doing dma %s ", do_read ? "read" : "write"); + + /* Set initial dma state: for reading first fill on board buffer, + from device, for writes first fill the buffer from memory*/ + dev->dma_state = do_read ? DMA_INTERNAL : DMA_MEMORY; + + /* if incoming buffer is not page aligned, we should do bounce */ + if ((unsigned long)buf & (R852_DMA_LEN-1)) + bounce = 1; + + if (!bounce) { + dev->phys_dma_addr = dma_map_single(&dev->pci_dev->dev, buf, + R852_DMA_LEN, + do_read ? DMA_FROM_DEVICE : DMA_TO_DEVICE); + if (dma_mapping_error(&dev->pci_dev->dev, dev->phys_dma_addr)) + bounce = 1; + } + + if (bounce) { + dbg_verbose("dma: using bounce buffer"); + dev->phys_dma_addr = dev->phys_bounce_buffer; + if (!do_read) + memcpy(dev->bounce_buffer, buf, R852_DMA_LEN); + } + + /* Enable DMA */ + spin_lock_irqsave(&dev->irqlock, flags); + r852_dma_enable(dev); + spin_unlock_irqrestore(&dev->irqlock, flags); + + /* Wait till complete */ + error = r852_dma_wait(dev); + + if (error) { + r852_dma_done(dev, error); + return; + } + + if (do_read && bounce) + memcpy((void *)buf, dev->bounce_buffer, R852_DMA_LEN); +} + +/* + * Program data lines of the nand chip to send data to it + */ +static void r852_write_buf(struct nand_chip *chip, const uint8_t *buf, int len) +{ + struct r852_device *dev = r852_get_dev(nand_to_mtd(chip)); + uint32_t reg; + + /* Don't allow any access to hardware if we suspect card removal */ + if (dev->card_unstable) + return; + + /* Special case for whole sector read */ + if (len == R852_DMA_LEN && dev->dma_usable) { + r852_do_dma(dev, (uint8_t *)buf, 0); + return; + } + + /* write DWORD chinks - faster */ + while (len >= 4) { + reg = buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24; + r852_write_reg_dword(dev, R852_DATALINE, reg); + buf += 4; + len -= 4; + + } + + /* write rest */ + while (len > 0) { + r852_write_reg(dev, R852_DATALINE, *buf++); + len--; + } +} + +/* + * Read data lines of the nand chip to retrieve data + */ +static void r852_read_buf(struct nand_chip *chip, uint8_t *buf, int len) +{ + struct r852_device *dev = r852_get_dev(nand_to_mtd(chip)); + uint32_t reg; + + if (dev->card_unstable) { + /* since we can't signal error here, at least, return + predictable buffer */ + memset(buf, 0, len); + return; + } + + /* special case for whole sector read */ + if (len == R852_DMA_LEN && dev->dma_usable) { + r852_do_dma(dev, buf, 1); + return; + } + + /* read in dword sized chunks */ + while (len >= 4) { + + reg = r852_read_reg_dword(dev, R852_DATALINE); + *buf++ = reg & 0xFF; + *buf++ = (reg >> 8) & 0xFF; + *buf++ = (reg >> 16) & 0xFF; + *buf++ = (reg >> 24) & 0xFF; + len -= 4; + } + + /* read the reset by bytes */ + while (len--) + *buf++ = r852_read_reg(dev, R852_DATALINE); +} + +/* + * Read one byte from nand chip + */ +static uint8_t r852_read_byte(struct nand_chip *chip) +{ + struct r852_device *dev = r852_get_dev(nand_to_mtd(chip)); + + /* Same problem as in r852_read_buf.... */ + if (dev->card_unstable) + return 0; + + return r852_read_reg(dev, R852_DATALINE); +} + +/* + * Control several chip lines & send commands + */ +static void r852_cmdctl(struct nand_chip *chip, int dat, unsigned int ctrl) +{ + struct r852_device *dev = r852_get_dev(nand_to_mtd(chip)); + + if (dev->card_unstable) + return; + + if (ctrl & NAND_CTRL_CHANGE) { + + dev->ctlreg &= ~(R852_CTL_DATA | R852_CTL_COMMAND | + R852_CTL_ON | R852_CTL_CARDENABLE); + + if (ctrl & NAND_ALE) + dev->ctlreg |= R852_CTL_DATA; + + if (ctrl & NAND_CLE) + dev->ctlreg |= R852_CTL_COMMAND; + + if (ctrl & NAND_NCE) + dev->ctlreg |= (R852_CTL_CARDENABLE | R852_CTL_ON); + else + dev->ctlreg &= ~R852_CTL_WRITE; + + /* when write is started, enable write access */ + if (dat == NAND_CMD_ERASE1) + dev->ctlreg |= R852_CTL_WRITE; + + r852_write_reg(dev, R852_CTL, dev->ctlreg); + } + + /* HACK: NAND_CMD_SEQIN is called without NAND_CTRL_CHANGE, but we need + to set write mode */ + if (dat == NAND_CMD_SEQIN && (dev->ctlreg & R852_CTL_COMMAND)) { + dev->ctlreg |= R852_CTL_WRITE; + r852_write_reg(dev, R852_CTL, dev->ctlreg); + } + + if (dat != NAND_CMD_NONE) + r852_write_reg(dev, R852_DATALINE, dat); +} + +/* + * Wait till card is ready. + * based on nand_wait, but returns errors on DMA error + */ +static int r852_wait(struct nand_chip *chip) +{ + struct r852_device *dev = nand_get_controller_data(chip); + + unsigned long timeout; + u8 status; + + timeout = jiffies + msecs_to_jiffies(400); + + while (time_before(jiffies, timeout)) + if (chip->legacy.dev_ready(chip)) + break; + + nand_status_op(chip, &status); + + /* Unfortunately, no way to send detailed error status... */ + if (dev->dma_error) { + status |= NAND_STATUS_FAIL; + dev->dma_error = 0; + } + return status; +} + +/* + * Check if card is ready + */ + +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); +} + + +/* + * Set ECC engine mode +*/ + +static void r852_ecc_hwctl(struct nand_chip *chip, int mode) +{ + struct r852_device *dev = r852_get_dev(nand_to_mtd(chip)); + + if (dev->card_unstable) + return; + + switch (mode) { + case NAND_ECC_READ: + case NAND_ECC_WRITE: + /* enable ecc generation/check*/ + dev->ctlreg |= R852_CTL_ECC_ENABLE; + + /* flush ecc buffer */ + r852_write_reg(dev, R852_CTL, + dev->ctlreg | R852_CTL_ECC_ACCESS); + + r852_read_reg_dword(dev, R852_DATALINE); + r852_write_reg(dev, R852_CTL, dev->ctlreg); + return; + + case NAND_ECC_READSYN: + /* disable ecc generation */ + dev->ctlreg &= ~R852_CTL_ECC_ENABLE; + r852_write_reg(dev, R852_CTL, dev->ctlreg); + } +} + +/* + * Calculate ECC, only used for writes + */ + +static int r852_ecc_calculate(struct nand_chip *chip, const uint8_t *dat, + uint8_t *ecc_code) +{ + struct r852_device *dev = r852_get_dev(nand_to_mtd(chip)); + struct sm_oob *oob = (struct sm_oob *)ecc_code; + uint32_t ecc1, ecc2; + + if (dev->card_unstable) + return 0; + + dev->ctlreg &= ~R852_CTL_ECC_ENABLE; + r852_write_reg(dev, R852_CTL, dev->ctlreg | R852_CTL_ECC_ACCESS); + + ecc1 = r852_read_reg_dword(dev, R852_DATALINE); + ecc2 = r852_read_reg_dword(dev, R852_DATALINE); + + oob->ecc1[0] = (ecc1) & 0xFF; + oob->ecc1[1] = (ecc1 >> 8) & 0xFF; + oob->ecc1[2] = (ecc1 >> 16) & 0xFF; + + oob->ecc2[0] = (ecc2) & 0xFF; + oob->ecc2[1] = (ecc2 >> 8) & 0xFF; + oob->ecc2[2] = (ecc2 >> 16) & 0xFF; + + r852_write_reg(dev, R852_CTL, dev->ctlreg); + return 0; +} + +/* + * Correct the data using ECC, hw did almost everything for us + */ + +static int r852_ecc_correct(struct nand_chip *chip, uint8_t *dat, + uint8_t *read_ecc, uint8_t *calc_ecc) +{ + uint32_t ecc_reg; + uint8_t ecc_status, err_byte; + int i, error = 0; + + struct r852_device *dev = r852_get_dev(nand_to_mtd(chip)); + + if (dev->card_unstable) + return 0; + + if (dev->dma_error) { + dev->dma_error = 0; + return -EIO; + } + + r852_write_reg(dev, R852_CTL, dev->ctlreg | R852_CTL_ECC_ACCESS); + ecc_reg = r852_read_reg_dword(dev, R852_DATALINE); + r852_write_reg(dev, R852_CTL, dev->ctlreg); + + for (i = 0 ; i <= 1 ; i++) { + + ecc_status = (ecc_reg >> 8) & 0xFF; + + /* ecc uncorrectable error */ + if (ecc_status & R852_ECC_FAIL) { + dbg("ecc: unrecoverable error, in half %d", i); + error = -EBADMSG; + goto exit; + } + + /* correctable error */ + if (ecc_status & R852_ECC_CORRECTABLE) { + + err_byte = ecc_reg & 0xFF; + dbg("ecc: recoverable error, " + "in half %d, byte %d, bit %d", i, + err_byte, ecc_status & R852_ECC_ERR_BIT_MSK); + + dat[err_byte] ^= + 1 << (ecc_status & R852_ECC_ERR_BIT_MSK); + error++; + } + + dat += 256; + ecc_reg >>= 16; + } +exit: + return error; +} + +/* + * This is copy of nand_read_oob_std + * nand_read_oob_syndrome assumes we can send column address - we can't + */ +static int r852_read_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + return nand_read_oob_op(chip, page, 0, chip->oob_poi, mtd->oobsize); +} + +/* + * Start the nand engine + */ + +static void r852_engine_enable(struct r852_device *dev) +{ + if (r852_read_reg_dword(dev, R852_HW) & R852_HW_UNKNOWN) { + r852_write_reg(dev, R852_CTL, R852_CTL_RESET | R852_CTL_ON); + r852_write_reg_dword(dev, R852_HW, R852_HW_ENABLED); + } else { + r852_write_reg_dword(dev, R852_HW, R852_HW_ENABLED); + r852_write_reg(dev, R852_CTL, R852_CTL_RESET | R852_CTL_ON); + } + msleep(300); + r852_write_reg(dev, R852_CTL, 0); +} + + +/* + * Stop the nand engine + */ + +static void r852_engine_disable(struct r852_device *dev) +{ + r852_write_reg_dword(dev, R852_HW, 0); + r852_write_reg(dev, R852_CTL, R852_CTL_RESET); +} + +/* + * Test if card is present + */ + +static void r852_card_update_present(struct r852_device *dev) +{ + unsigned long flags; + uint8_t reg; + + spin_lock_irqsave(&dev->irqlock, flags); + reg = r852_read_reg(dev, R852_CARD_STA); + dev->card_detected = !!(reg & R852_CARD_STA_PRESENT); + spin_unlock_irqrestore(&dev->irqlock, flags); +} + +/* + * Update card detection IRQ state according to current card state + * which is read in r852_card_update_present + */ +static void r852_update_card_detect(struct r852_device *dev) +{ + int card_detect_reg = r852_read_reg(dev, R852_CARD_IRQ_ENABLE); + dev->card_unstable = 0; + + card_detect_reg &= ~(R852_CARD_IRQ_REMOVE | R852_CARD_IRQ_INSERT); + card_detect_reg |= R852_CARD_IRQ_GENABLE; + + card_detect_reg |= dev->card_detected ? + R852_CARD_IRQ_REMOVE : R852_CARD_IRQ_INSERT; + + r852_write_reg(dev, R852_CARD_IRQ_ENABLE, card_detect_reg); +} + +static ssize_t media_type_show(struct device *sys_dev, + struct device_attribute *attr, char *buf) +{ + struct mtd_info *mtd = container_of(sys_dev, struct mtd_info, dev); + struct r852_device *dev = r852_get_dev(mtd); + char *data = dev->sm ? "smartmedia" : "xd"; + + strcpy(buf, data); + return strlen(data); +} +static DEVICE_ATTR_RO(media_type); + + +/* Detect properties of card in slot */ +static void r852_update_media_status(struct r852_device *dev) +{ + uint8_t reg; + unsigned long flags; + int readonly; + + spin_lock_irqsave(&dev->irqlock, flags); + if (!dev->card_detected) { + message("card removed"); + spin_unlock_irqrestore(&dev->irqlock, flags); + return ; + } + + readonly = r852_read_reg(dev, R852_CARD_STA) & R852_CARD_STA_RO; + reg = r852_read_reg(dev, R852_DMA_CAP); + dev->sm = (reg & (R852_DMA1 | R852_DMA2)) && (reg & R852_SMBIT); + + message("detected %s %s card in slot", + dev->sm ? "SmartMedia" : "xD", + readonly ? "readonly" : "writeable"); + + dev->readonly = readonly; + spin_unlock_irqrestore(&dev->irqlock, flags); +} + +/* + * Register the nand device + * Called when the card is detected + */ +static int r852_register_nand_device(struct r852_device *dev) +{ + struct mtd_info *mtd = nand_to_mtd(dev->chip); + + WARN_ON(dev->card_registered); + + mtd->dev.parent = &dev->pci_dev->dev; + + if (dev->readonly) + dev->chip->options |= NAND_ROM; + + r852_engine_enable(dev); + + if (sm_register_device(mtd, dev->sm)) + goto error1; + + if (device_create_file(&mtd->dev, &dev_attr_media_type)) { + message("can't create media type sysfs attribute"); + goto error3; + } + + dev->card_registered = 1; + return 0; +error3: + WARN_ON(mtd_device_unregister(nand_to_mtd(dev->chip))); + nand_cleanup(dev->chip); +error1: + /* Force card redetect */ + dev->card_detected = 0; + return -1; +} + +/* + * Unregister the card + */ + +static void r852_unregister_nand_device(struct r852_device *dev) +{ + struct mtd_info *mtd = nand_to_mtd(dev->chip); + + if (!dev->card_registered) + return; + + device_remove_file(&mtd->dev, &dev_attr_media_type); + WARN_ON(mtd_device_unregister(mtd)); + nand_cleanup(dev->chip); + r852_engine_disable(dev); + dev->card_registered = 0; +} + +/* Card state updater */ +static void r852_card_detect_work(struct work_struct *work) +{ + struct r852_device *dev = + container_of(work, struct r852_device, card_detect_work.work); + + r852_card_update_present(dev); + r852_update_card_detect(dev); + dev->card_unstable = 0; + + /* False alarm */ + if (dev->card_detected == dev->card_registered) + goto exit; + + /* Read media properties */ + r852_update_media_status(dev); + + /* Register the card */ + if (dev->card_detected) + r852_register_nand_device(dev); + else + r852_unregister_nand_device(dev); +exit: + r852_update_card_detect(dev); +} + +/* Ack + disable IRQ generation */ +static void r852_disable_irqs(struct r852_device *dev) +{ + uint8_t reg; + reg = r852_read_reg(dev, R852_CARD_IRQ_ENABLE); + r852_write_reg(dev, R852_CARD_IRQ_ENABLE, reg & ~R852_CARD_IRQ_MASK); + + reg = r852_read_reg_dword(dev, R852_DMA_IRQ_ENABLE); + r852_write_reg_dword(dev, R852_DMA_IRQ_ENABLE, + reg & ~R852_DMA_IRQ_MASK); + + r852_write_reg(dev, R852_CARD_IRQ_STA, R852_CARD_IRQ_MASK); + r852_write_reg_dword(dev, R852_DMA_IRQ_STA, R852_DMA_IRQ_MASK); +} + +/* Interrupt handler */ +static irqreturn_t r852_irq(int irq, void *data) +{ + struct r852_device *dev = (struct r852_device *)data; + + uint8_t card_status, dma_status; + irqreturn_t ret = IRQ_NONE; + + spin_lock(&dev->irqlock); + + /* handle card detection interrupts first */ + card_status = r852_read_reg(dev, R852_CARD_IRQ_STA); + r852_write_reg(dev, R852_CARD_IRQ_STA, card_status); + + if (card_status & (R852_CARD_IRQ_INSERT|R852_CARD_IRQ_REMOVE)) { + + ret = IRQ_HANDLED; + dev->card_detected = !!(card_status & R852_CARD_IRQ_INSERT); + + /* we shouldn't receive any interrupts if we wait for card + to settle */ + WARN_ON(dev->card_unstable); + + /* disable irqs while card is unstable */ + /* this will timeout DMA if active, but better that garbage */ + r852_disable_irqs(dev); + + if (dev->card_unstable) + goto out; + + /* let, card state to settle a bit, and then do the work */ + dev->card_unstable = 1; + queue_delayed_work(dev->card_workqueue, + &dev->card_detect_work, msecs_to_jiffies(100)); + goto out; + } + + + /* Handle dma interrupts */ + dma_status = r852_read_reg_dword(dev, R852_DMA_IRQ_STA); + r852_write_reg_dword(dev, R852_DMA_IRQ_STA, dma_status); + + if (dma_status & R852_DMA_IRQ_MASK) { + + ret = IRQ_HANDLED; + + if (dma_status & R852_DMA_IRQ_ERROR) { + dbg("received dma error IRQ"); + r852_dma_done(dev, -EIO); + complete(&dev->dma_done); + goto out; + } + + /* received DMA interrupt out of nowhere? */ + WARN_ON_ONCE(dev->dma_stage == 0); + + if (dev->dma_stage == 0) + goto out; + + /* done device access */ + if (dev->dma_state == DMA_INTERNAL && + (dma_status & R852_DMA_IRQ_INTERNAL)) { + + dev->dma_state = DMA_MEMORY; + dev->dma_stage++; + } + + /* done memory DMA */ + if (dev->dma_state == DMA_MEMORY && + (dma_status & R852_DMA_IRQ_MEMORY)) { + dev->dma_state = DMA_INTERNAL; + dev->dma_stage++; + } + + /* Enable 2nd half of dma dance */ + if (dev->dma_stage == 2) + r852_dma_enable(dev); + + /* Operation done */ + if (dev->dma_stage == 3) { + r852_dma_done(dev, 0); + complete(&dev->dma_done); + } + goto out; + } + + /* Handle unknown interrupts */ + if (dma_status) + dbg("bad dma IRQ status = %x", dma_status); + + if (card_status & ~R852_CARD_STA_CD) + dbg("strange card status = %x", card_status); + +out: + spin_unlock(&dev->irqlock); + return ret; +} + +static int r852_attach_chip(struct nand_chip *chip) +{ + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return 0; + + chip->ecc.placement = NAND_ECC_PLACEMENT_INTERLEAVED; + chip->ecc.size = R852_DMA_LEN; + chip->ecc.bytes = SM_OOB_SIZE; + chip->ecc.strength = 2; + chip->ecc.hwctl = r852_ecc_hwctl; + chip->ecc.calculate = r852_ecc_calculate; + chip->ecc.correct = r852_ecc_correct; + + /* TODO: hack */ + chip->ecc.read_oob = r852_read_oob; + + return 0; +} + +static const struct nand_controller_ops r852_ops = { + .attach_chip = r852_attach_chip, +}; + +static int r852_probe(struct pci_dev *pci_dev, const struct pci_device_id *id) +{ + int error; + struct nand_chip *chip; + struct r852_device *dev; + + /* pci initialization */ + error = pci_enable_device(pci_dev); + + if (error) + goto error1; + + pci_set_master(pci_dev); + + error = dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32)); + if (error) + goto error2; + + error = pci_request_regions(pci_dev, DRV_NAME); + + if (error) + goto error3; + + error = -ENOMEM; + + /* init nand chip, but register it only on card insert */ + chip = kzalloc_obj(struct nand_chip); + + if (!chip) + goto error4; + + /* commands */ + chip->legacy.cmd_ctrl = r852_cmdctl; + chip->legacy.waitfunc = r852_wait; + chip->legacy.dev_ready = r852_ready; + + /* I/O */ + chip->legacy.read_byte = r852_read_byte; + chip->legacy.read_buf = r852_read_buf; + chip->legacy.write_buf = r852_write_buf; + + /* init our device structure */ + dev = kzalloc_obj(struct r852_device); + + if (!dev) + goto error5; + + nand_set_controller_data(chip, dev); + dev->chip = chip; + dev->pci_dev = pci_dev; + pci_set_drvdata(pci_dev, dev); + + nand_controller_init(&dev->controller); + dev->controller.ops = &r852_ops; + chip->controller = &dev->controller; + + dev->bounce_buffer = dma_alloc_coherent(&pci_dev->dev, R852_DMA_LEN, + &dev->phys_bounce_buffer, GFP_KERNEL); + + if (!dev->bounce_buffer) + goto error6; + + + error = -ENODEV; + dev->mmio = pci_ioremap_bar(pci_dev, 0); + + if (!dev->mmio) + goto error7; + + error = -ENOMEM; + dev->tmp_buffer = kzalloc(SM_SECTOR_SIZE, GFP_KERNEL); + + if (!dev->tmp_buffer) + goto error8; + + init_completion(&dev->dma_done); + + dev->card_workqueue = create_freezable_workqueue(DRV_NAME); + + if (!dev->card_workqueue) + goto error9; + + INIT_DELAYED_WORK(&dev->card_detect_work, r852_card_detect_work); + + /* shutdown everything - precation */ + r852_engine_disable(dev); + r852_disable_irqs(dev); + + r852_dma_test(dev); + + dev->irq = pci_dev->irq; + spin_lock_init(&dev->irqlock); + + dev->card_detected = 0; + r852_card_update_present(dev); + + /*register irq handler*/ + error = -ENODEV; + if (request_irq(pci_dev->irq, &r852_irq, IRQF_SHARED, + DRV_NAME, dev)) + goto error10; + + /* kick initial present test */ + queue_delayed_work(dev->card_workqueue, + &dev->card_detect_work, 0); + + + pr_notice("driver loaded successfully\n"); + return 0; + +error10: + destroy_workqueue(dev->card_workqueue); +error9: + kfree(dev->tmp_buffer); +error8: + pci_iounmap(pci_dev, dev->mmio); +error7: + dma_free_coherent(&pci_dev->dev, R852_DMA_LEN, dev->bounce_buffer, + dev->phys_bounce_buffer); +error6: + kfree(dev); +error5: + kfree(chip); +error4: + pci_release_regions(pci_dev); +error3: +error2: + pci_disable_device(pci_dev); +error1: + return error; +} + +static void r852_remove(struct pci_dev *pci_dev) +{ + struct r852_device *dev = pci_get_drvdata(pci_dev); + + /* Stop detect workqueue - + we are going to unregister the device anyway*/ + cancel_delayed_work_sync(&dev->card_detect_work); + destroy_workqueue(dev->card_workqueue); + + /* Unregister the device, this might make more IO */ + r852_unregister_nand_device(dev); + + /* Stop interrupts */ + r852_disable_irqs(dev); + free_irq(dev->irq, dev); + + /* Cleanup */ + kfree(dev->tmp_buffer); + pci_iounmap(pci_dev, dev->mmio); + dma_free_coherent(&pci_dev->dev, R852_DMA_LEN, dev->bounce_buffer, + dev->phys_bounce_buffer); + + kfree(dev->chip); + kfree(dev); + + /* Shutdown the PCI device */ + pci_release_regions(pci_dev); + pci_disable_device(pci_dev); +} + +static void r852_shutdown(struct pci_dev *pci_dev) +{ + struct r852_device *dev = pci_get_drvdata(pci_dev); + + cancel_delayed_work_sync(&dev->card_detect_work); + r852_disable_irqs(dev); + synchronize_irq(dev->irq); + pci_disable_device(pci_dev); +} + +#ifdef CONFIG_PM_SLEEP +static int r852_suspend(struct device *device) +{ + struct r852_device *dev = dev_get_drvdata(device); + + if (dev->ctlreg & R852_CTL_CARDENABLE) + return -EBUSY; + + /* First make sure the detect work is gone */ + cancel_delayed_work_sync(&dev->card_detect_work); + + /* Turn off the interrupts and stop the device */ + r852_disable_irqs(dev); + r852_engine_disable(dev); + + /* If card was pulled off just during the suspend, which is very + unlikely, we will remove it on resume, it too late now + anyway... */ + dev->card_unstable = 0; + return 0; +} + +static int r852_resume(struct device *device) +{ + struct r852_device *dev = dev_get_drvdata(device); + + r852_disable_irqs(dev); + r852_card_update_present(dev); + r852_engine_disable(dev); + + + /* If card status changed, just do the work */ + if (dev->card_detected != dev->card_registered) { + dbg("card was %s during low power state", + dev->card_detected ? "added" : "removed"); + + queue_delayed_work(dev->card_workqueue, + &dev->card_detect_work, msecs_to_jiffies(1000)); + return 0; + } + + /* Otherwise, initialize the card */ + if (dev->card_registered) { + r852_engine_enable(dev); + nand_select_target(dev->chip, 0); + nand_reset_op(dev->chip); + nand_deselect_target(dev->chip); + } + + /* Program card detection IRQ */ + r852_update_card_detect(dev); + return 0; +} +#endif + +static const struct pci_device_id r852_pci_id_tbl[] = { + + { PCI_VDEVICE(RICOH, 0x0852) }, + { } +}; + +MODULE_DEVICE_TABLE(pci, r852_pci_id_tbl); + +static SIMPLE_DEV_PM_OPS(r852_pm_ops, r852_suspend, r852_resume); + +static struct pci_driver r852_pci_driver = { + .name = DRV_NAME, + .id_table = r852_pci_id_tbl, + .probe = r852_probe, + .remove = r852_remove, + .shutdown = r852_shutdown, + .driver.pm = &r852_pm_ops, +}; + +module_pci_driver(r852_pci_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Maxim Levitsky "); +MODULE_DESCRIPTION("Ricoh 85xx xD/smartmedia card reader driver"); diff --git a/drivers/mtd/nand/raw/r852.h b/drivers/mtd/nand/raw/r852.h new file mode 100644 index 000000000..96fe301d1 --- /dev/null +++ b/drivers/mtd/nand/raw/r852.h @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright © 2009 - Maxim Levitsky + * driver for Ricoh xD readers + */ + +#include +#include +#include +#include +#include + + +/* nand interface + ecc + byte write/read does one cycle on nand data lines. + dword write/read does 4 cycles + if R852_CTL_ECC_ACCESS is set in R852_CTL, then dword read reads + results of ecc correction, if DMA read was done before. + If write was done two dword reads read generated ecc checksums +*/ +#define R852_DATALINE 0x00 + +/* control register */ +#define R852_CTL 0x04 +#define R852_CTL_COMMAND 0x01 /* send command (#CLE)*/ +#define R852_CTL_DATA 0x02 /* read/write data (#ALE)*/ +#define R852_CTL_ON 0x04 /* only seem to controls the hd led, */ + /* but has to be set on start...*/ +#define R852_CTL_RESET 0x08 /* unknown, set only on start once*/ +#define R852_CTL_CARDENABLE 0x10 /* probably (#CE) - always set*/ +#define R852_CTL_ECC_ENABLE 0x20 /* enable ecc engine */ +#define R852_CTL_ECC_ACCESS 0x40 /* read/write ecc via reg #0*/ +#define R852_CTL_WRITE 0x80 /* set when performing writes (#WP) */ + +/* card detection status */ +#define R852_CARD_STA 0x05 + +#define R852_CARD_STA_CD 0x01 /* state of #CD line, same as 0x04 */ +#define R852_CARD_STA_RO 0x02 /* card is readonly */ +#define R852_CARD_STA_PRESENT 0x04 /* card is present (#CD) */ +#define R852_CARD_STA_ABSENT 0x08 /* card is absent */ +#define R852_CARD_STA_BUSY 0x80 /* card is busy - (#R/B) */ + +/* card detection irq status & enable*/ +#define R852_CARD_IRQ_STA 0x06 /* IRQ status */ +#define R852_CARD_IRQ_ENABLE 0x07 /* IRQ enable */ + +#define R852_CARD_IRQ_CD 0x01 /* fire when #CD lights, same as 0x04*/ +#define R852_CARD_IRQ_REMOVE 0x04 /* detect card removal */ +#define R852_CARD_IRQ_INSERT 0x08 /* detect card insert */ +#define R852_CARD_IRQ_UNK1 0x10 /* unknown */ +#define R852_CARD_IRQ_GENABLE 0x80 /* general enable */ +#define R852_CARD_IRQ_MASK 0x1D + + + +/* hardware enable */ +#define R852_HW 0x08 +#define R852_HW_ENABLED 0x01 /* hw enabled */ +#define R852_HW_UNKNOWN 0x80 + + +/* dma capabilities */ +#define R852_DMA_CAP 0x09 +#define R852_SMBIT 0x20 /* if set with bit #6 or bit #7, then */ + /* hw is smartmedia */ +#define R852_DMA1 0x40 /* if set w/bit #7, dma is supported */ +#define R852_DMA2 0x80 /* if set w/bit #6, dma is supported */ + + +/* physical DMA address - 32 bit value*/ +#define R852_DMA_ADDR 0x0C + + +/* dma settings */ +#define R852_DMA_SETTINGS 0x10 +#define R852_DMA_MEMORY 0x01 /* (memory <-> internal hw buffer) */ +#define R852_DMA_READ 0x02 /* 0 = write, 1 = read */ +#define R852_DMA_INTERNAL 0x04 /* (internal hw buffer <-> card) */ + +/* dma IRQ status */ +#define R852_DMA_IRQ_STA 0x14 + +/* dma IRQ enable */ +#define R852_DMA_IRQ_ENABLE 0x18 + +#define R852_DMA_IRQ_MEMORY 0x01 /* (memory <-> internal hw buffer) */ +#define R852_DMA_IRQ_ERROR 0x02 /* error did happen */ +#define R852_DMA_IRQ_INTERNAL 0x04 /* (internal hw buffer <-> card) */ +#define R852_DMA_IRQ_MASK 0x07 /* mask of all IRQ bits */ + + +/* ECC syndrome format - read from reg #0 will return two copies of these for + each half of the page. + first byte is error byte location, and second, bit location + flags */ +#define R852_ECC_ERR_BIT_MSK 0x07 /* error bit location */ +#define R852_ECC_CORRECT 0x10 /* no errors - (guessed) */ +#define R852_ECC_CORRECTABLE 0x20 /* correctable error exist */ +#define R852_ECC_FAIL 0x40 /* non correctable error detected */ + +#define R852_DMA_LEN 512 + +#define DMA_INTERNAL 0 +#define DMA_MEMORY 1 + +struct r852_device { + struct nand_controller controller; + void __iomem *mmio; /* mmio */ + struct nand_chip *chip; /* nand chip backpointer */ + struct pci_dev *pci_dev; /* pci backpointer */ + + /* dma area */ + dma_addr_t phys_dma_addr; /* bus address of buffer*/ + struct completion dma_done; /* data transfer done */ + + dma_addr_t phys_bounce_buffer; /* bus address of bounce buffer */ + uint8_t *bounce_buffer; /* virtual address of bounce buffer */ + + int dma_dir; /* 1 = read, 0 = write */ + int dma_stage; /* 0 - idle, 1 - first step, + 2 - second step */ + + int dma_state; /* 0 = internal, 1 = memory */ + int dma_error; /* dma errors */ + int dma_usable; /* is it possible to use dma */ + + /* card status area */ + struct delayed_work card_detect_work; + struct workqueue_struct *card_workqueue; + int card_registered; /* card registered with mtd */ + int card_detected; /* card detected in slot */ + int card_unstable; /* whenever the card is inserted, + is not known yet */ + int readonly; /* card is readonly */ + int sm; /* Is card smartmedia */ + + /* interrupt handling */ + spinlock_t irqlock; /* IRQ protecting lock */ + int irq; /* irq num */ + /* misc */ + void *tmp_buffer; /* temporary buffer */ + uint8_t ctlreg; /* cached contents of control reg */ +}; + +#define dbg(format, ...) \ + if (debug) \ + pr_debug(format "\n", ## __VA_ARGS__) + +#define dbg_verbose(format, ...) \ + if (debug > 1) \ + pr_debug(format "\n", ## __VA_ARGS__) + + +#define message(format, ...) \ + pr_info(format "\n", ## __VA_ARGS__) diff --git a/drivers/mtd/nand/raw/renesas-nand-controller.c b/drivers/mtd/nand/raw/renesas-nand-controller.c new file mode 100644 index 000000000..201dd62b9 --- /dev/null +++ b/drivers/mtd/nand/raw/renesas-nand-controller.c @@ -0,0 +1,1420 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Evatronix/Renesas R-Car Gen3, RZ/N1D, RZ/N1S, RZ/N1L NAND controller driver + * + * Copyright (C) 2021 Schneider Electric + * Author: Miquel RAYNAL + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define COMMAND_REG 0x00 +#define COMMAND_SEQ(x) FIELD_PREP(GENMASK(5, 0), (x)) +#define COMMAND_SEQ_10 COMMAND_SEQ(0x2A) +#define COMMAND_SEQ_12 COMMAND_SEQ(0x0C) +#define COMMAND_SEQ_18 COMMAND_SEQ(0x32) +#define COMMAND_SEQ_19 COMMAND_SEQ(0x13) +#define COMMAND_SEQ_GEN_IN COMMAND_SEQ_18 +#define COMMAND_SEQ_GEN_OUT COMMAND_SEQ_19 +#define COMMAND_SEQ_READ_PAGE COMMAND_SEQ_10 +#define COMMAND_SEQ_WRITE_PAGE COMMAND_SEQ_12 +#define COMMAND_INPUT_SEL_AHBS 0 +#define COMMAND_INPUT_SEL_DMA BIT(6) +#define COMMAND_FIFO_SEL 0 +#define COMMAND_DATA_SEL BIT(7) +#define COMMAND_0(x) FIELD_PREP(GENMASK(15, 8), (x)) +#define COMMAND_1(x) FIELD_PREP(GENMASK(23, 16), (x)) +#define COMMAND_2(x) FIELD_PREP(GENMASK(31, 24), (x)) + +#define CONTROL_REG 0x04 +#define CONTROL_CHECK_RB_LINE 0 +#define CONTROL_ECC_BLOCK_SIZE(x) FIELD_PREP(GENMASK(2, 1), (x)) +#define CONTROL_ECC_BLOCK_SIZE_256 CONTROL_ECC_BLOCK_SIZE(0) +#define CONTROL_ECC_BLOCK_SIZE_512 CONTROL_ECC_BLOCK_SIZE(1) +#define CONTROL_ECC_BLOCK_SIZE_1024 CONTROL_ECC_BLOCK_SIZE(2) +#define CONTROL_INT_EN BIT(4) +#define CONTROL_ECC_EN BIT(5) +#define CONTROL_BLOCK_SIZE(x) FIELD_PREP(GENMASK(7, 6), (x)) +#define CONTROL_BLOCK_SIZE_32P CONTROL_BLOCK_SIZE(0) +#define CONTROL_BLOCK_SIZE_64P CONTROL_BLOCK_SIZE(1) +#define CONTROL_BLOCK_SIZE_128P CONTROL_BLOCK_SIZE(2) +#define CONTROL_BLOCK_SIZE_256P CONTROL_BLOCK_SIZE(3) + +#define STATUS_REG 0x8 +#define MEM_RDY(cs, reg) (FIELD_GET(GENMASK(3, 0), (reg)) & BIT(cs)) +#define CTRL_RDY(reg) (FIELD_GET(BIT(8), (reg)) == 0) + +#define ECC_CTRL_REG 0x18 +#define ECC_CTRL_CAP(x) FIELD_PREP(GENMASK(2, 0), (x)) +#define ECC_CTRL_CAP_2B ECC_CTRL_CAP(0) +#define ECC_CTRL_CAP_4B ECC_CTRL_CAP(1) +#define ECC_CTRL_CAP_8B ECC_CTRL_CAP(2) +#define ECC_CTRL_CAP_16B ECC_CTRL_CAP(3) +#define ECC_CTRL_CAP_24B ECC_CTRL_CAP(4) +#define ECC_CTRL_CAP_32B ECC_CTRL_CAP(5) +#define ECC_CTRL_ERR_THRESHOLD(x) FIELD_PREP(GENMASK(13, 8), (x)) + +#define INT_MASK_REG 0x10 +#define INT_STATUS_REG 0x14 +#define INT_CMD_END BIT(1) +#define INT_DMA_END BIT(3) +#define INT_MEM_RDY(cs) FIELD_PREP(GENMASK(11, 8), BIT(cs)) +#define INT_DMA_ENDED BIT(3) +#define MEM_IS_RDY(cs, reg) (FIELD_GET(GENMASK(11, 8), (reg)) & BIT(cs)) +#define DMA_HAS_ENDED(reg) FIELD_GET(BIT(3), (reg)) + +#define ECC_OFFSET_REG 0x1C +#define ECC_OFFSET(x) FIELD_PREP(GENMASK(15, 0), (x)) + +#define ECC_STAT_REG 0x20 +#define ECC_STAT_CORRECTABLE(cs, reg) (FIELD_GET(GENMASK(3, 0), (reg)) & BIT(cs)) +#define ECC_STAT_UNCORRECTABLE(cs, reg) (FIELD_GET(GENMASK(11, 8), (reg)) & BIT(cs)) + +#define ADDR0_COL_REG 0x24 +#define ADDR0_COL(x) FIELD_PREP(GENMASK(15, 0), (x)) + +#define ADDR0_ROW_REG 0x28 +#define ADDR0_ROW(x) FIELD_PREP(GENMASK(23, 0), (x)) + +#define ADDR1_COL_REG 0x2C +#define ADDR1_COL(x) FIELD_PREP(GENMASK(15, 0), (x)) + +#define ADDR1_ROW_REG 0x30 +#define ADDR1_ROW(x) FIELD_PREP(GENMASK(23, 0), (x)) + +#define FIFO_DATA_REG 0x38 + +#define DATA_REG 0x3C + +#define DATA_REG_SIZE_REG 0x40 + +#define DMA_ADDR_LOW_REG 0x64 + +#define DMA_ADDR_HIGH_REG 0x68 + +#define DMA_CNT_REG 0x6C + +#define DMA_CTRL_REG 0x70 +#define DMA_CTRL_INCREMENT_BURST_4 0 +#define DMA_CTRL_REGISTER_MANAGED_MODE 0 +#define DMA_CTRL_START BIT(7) + +#define MEM_CTRL_REG 0x80 +#define MEM_CTRL_CS(cs) FIELD_PREP(GENMASK(1, 0), (cs)) +#define MEM_CTRL_DIS_WP(cs) FIELD_PREP(GENMASK(11, 8), BIT((cs))) + +#define DATA_SIZE_REG 0x84 +#define DATA_SIZE(x) FIELD_PREP(GENMASK(14, 0), (x)) + +#define TIMINGS_ASYN_REG 0x88 +#define TIMINGS_ASYN_TRWP(x) FIELD_PREP(GENMASK(3, 0), max((x), 1U) - 1) +#define TIMINGS_ASYN_TRWH(x) FIELD_PREP(GENMASK(7, 4), max((x), 1U) - 1) + +#define TIM_SEQ0_REG 0x90 +#define TIM_SEQ0_TCCS(x) FIELD_PREP(GENMASK(5, 0), max((x), 1U) - 1) +#define TIM_SEQ0_TADL(x) FIELD_PREP(GENMASK(13, 8), max((x), 1U) - 1) +#define TIM_SEQ0_TRHW(x) FIELD_PREP(GENMASK(21, 16), max((x), 1U) - 1) +#define TIM_SEQ0_TWHR(x) FIELD_PREP(GENMASK(29, 24), max((x), 1U) - 1) + +#define TIM_SEQ1_REG 0x94 +#define TIM_SEQ1_TWB(x) FIELD_PREP(GENMASK(5, 0), max((x), 1U) - 1) +#define TIM_SEQ1_TRR(x) FIELD_PREP(GENMASK(13, 8), max((x), 1U) - 1) +#define TIM_SEQ1_TWW(x) FIELD_PREP(GENMASK(21, 16), max((x), 1U) - 1) + +#define TIM_GEN_SEQ0_REG 0x98 +#define TIM_GEN_SEQ0_D0(x) FIELD_PREP(GENMASK(5, 0), max((x), 1U) - 1) +#define TIM_GEN_SEQ0_D1(x) FIELD_PREP(GENMASK(13, 8), max((x), 1U) - 1) +#define TIM_GEN_SEQ0_D2(x) FIELD_PREP(GENMASK(21, 16), max((x), 1U) - 1) +#define TIM_GEN_SEQ0_D3(x) FIELD_PREP(GENMASK(29, 24), max((x), 1U) - 1) + +#define TIM_GEN_SEQ1_REG 0x9c +#define TIM_GEN_SEQ1_D4(x) FIELD_PREP(GENMASK(5, 0), max((x), 1U) - 1) +#define TIM_GEN_SEQ1_D5(x) FIELD_PREP(GENMASK(13, 8), max((x), 1U) - 1) +#define TIM_GEN_SEQ1_D6(x) FIELD_PREP(GENMASK(21, 16), max((x), 1U) - 1) +#define TIM_GEN_SEQ1_D7(x) FIELD_PREP(GENMASK(29, 24), max((x), 1U) - 1) + +#define TIM_GEN_SEQ2_REG 0xA0 +#define TIM_GEN_SEQ2_D8(x) FIELD_PREP(GENMASK(5, 0), max((x), 1U) - 1) +#define TIM_GEN_SEQ2_D9(x) FIELD_PREP(GENMASK(13, 8), max((x), 1U) - 1) +#define TIM_GEN_SEQ2_D10(x) FIELD_PREP(GENMASK(21, 16), max((x), 1U) - 1) +#define TIM_GEN_SEQ2_D11(x) FIELD_PREP(GENMASK(29, 24), max((x), 1U) - 1) + +#define FIFO_INIT_REG 0xB4 +#define FIFO_INIT BIT(0) + +#define FIFO_STATE_REG 0xB4 +#define FIFO_STATE_R_EMPTY(reg) FIELD_GET(BIT(0), (reg)) +#define FIFO_STATE_W_FULL(reg) FIELD_GET(BIT(1), (reg)) +#define FIFO_STATE_C_EMPTY(reg) FIELD_GET(BIT(2), (reg)) +#define FIFO_STATE_R_FULL(reg) FIELD_GET(BIT(6), (reg)) +#define FIFO_STATE_W_EMPTY(reg) FIELD_GET(BIT(7), (reg)) + +#define GEN_SEQ_CTRL_REG 0xB8 +#define GEN_SEQ_CMD0_EN BIT(0) +#define GEN_SEQ_CMD1_EN BIT(1) +#define GEN_SEQ_CMD2_EN BIT(2) +#define GEN_SEQ_CMD3_EN BIT(3) +#define GEN_SEQ_COL_A0(x) FIELD_PREP(GENMASK(5, 4), min((x), 2U)) +#define GEN_SEQ_COL_A1(x) FIELD_PREP(GENMASK(7, 6), min((x), 2U)) +#define GEN_SEQ_ROW_A0(x) FIELD_PREP(GENMASK(9, 8), min((x), 3U)) +#define GEN_SEQ_ROW_A1(x) FIELD_PREP(GENMASK(11, 10), min((x), 3U)) +#define GEN_SEQ_DATA_EN BIT(12) +#define GEN_SEQ_DELAY_EN(x) FIELD_PREP(GENMASK(14, 13), (x)) +#define GEN_SEQ_DELAY0_EN GEN_SEQ_DELAY_EN(1) +#define GEN_SEQ_DELAY1_EN GEN_SEQ_DELAY_EN(2) +#define GEN_SEQ_IMD_SEQ BIT(15) +#define GEN_SEQ_COMMAND_3(x) FIELD_PREP(GENMASK(26, 16), (x)) + +#define DMA_TLVL_REG 0x114 +#define DMA_TLVL(x) FIELD_PREP(GENMASK(7, 0), (x)) +#define DMA_TLVL_MAX DMA_TLVL(0xFF) + +#define TIM_GEN_SEQ3_REG 0x134 +#define TIM_GEN_SEQ3_D12(x) FIELD_PREP(GENMASK(5, 0), max((x), 1U) - 1) + +#define ECC_CNT_REG 0x14C +#define ECC_CNT(cs, reg) FIELD_GET(GENMASK(5, 0), (reg) >> ((cs) * 8)) + +#define RNANDC_CS_NUM 4 + +#define TO_CYCLES64(ps, period_ns) ((unsigned int)DIV_ROUND_UP_ULL(div_u64(ps, 1000), \ + period_ns)) + +struct rnand_chip_sel { + unsigned int cs; +}; + +struct rnand_chip { + struct nand_chip chip; + struct list_head node; + int selected_die; + u32 ctrl; + unsigned int nsels; + u32 control; + u32 ecc_ctrl; + u32 timings_asyn; + u32 tim_seq0; + u32 tim_seq1; + u32 tim_gen_seq0; + u32 tim_gen_seq1; + u32 tim_gen_seq2; + u32 tim_gen_seq3; + struct rnand_chip_sel sels[] __counted_by(nsels); +}; + +struct rnandc { + struct nand_controller controller; + struct device *dev; + void __iomem *regs; + unsigned long ext_clk_rate; + unsigned long assigned_cs; + struct list_head chips; + struct nand_chip *selected_chip; + struct completion complete; + bool use_polling; + u8 *buf; + unsigned int buf_sz; +}; + +struct rnandc_op { + u32 command; + u32 addr0_col; + u32 addr0_row; + u32 addr1_col; + u32 addr1_row; + u32 data_size; + u32 ecc_offset; + u32 gen_seq_ctrl; + u8 *buf; + bool read; + unsigned int len; +}; + +static inline struct rnandc *to_rnandc(struct nand_controller *ctrl) +{ + return container_of(ctrl, struct rnandc, controller); +} + +static inline struct rnand_chip *to_rnand(struct nand_chip *chip) +{ + return container_of(chip, struct rnand_chip, chip); +} + +static inline unsigned int to_rnandc_cs(struct rnand_chip *nand) +{ + return nand->sels[nand->selected_die].cs; +} + +static void rnandc_dis_correction(struct rnandc *rnandc) +{ + u32 control; + + control = readl_relaxed(rnandc->regs + CONTROL_REG); + control &= ~CONTROL_ECC_EN; + writel_relaxed(control, rnandc->regs + CONTROL_REG); +} + +static void rnandc_en_correction(struct rnandc *rnandc) +{ + u32 control; + + control = readl_relaxed(rnandc->regs + CONTROL_REG); + control |= CONTROL_ECC_EN; + writel_relaxed(control, rnandc->regs + CONTROL_REG); +} + +static void rnandc_clear_status(struct rnandc *rnandc) +{ + writel_relaxed(0, rnandc->regs + INT_STATUS_REG); + writel_relaxed(0, rnandc->regs + ECC_STAT_REG); + writel_relaxed(0, rnandc->regs + ECC_CNT_REG); +} + +static void rnandc_dis_interrupts(struct rnandc *rnandc) +{ + writel_relaxed(0, rnandc->regs + INT_MASK_REG); +} + +static void rnandc_en_interrupts(struct rnandc *rnandc, u32 val) +{ + if (!rnandc->use_polling) + writel_relaxed(val, rnandc->regs + INT_MASK_REG); +} + +static void rnandc_clear_fifo(struct rnandc *rnandc) +{ + writel_relaxed(FIFO_INIT, rnandc->regs + FIFO_INIT_REG); +} + +static void rnandc_select_target(struct nand_chip *chip, int die_nr) +{ + struct rnand_chip *rnand = to_rnand(chip); + struct rnandc *rnandc = to_rnandc(chip->controller); + unsigned int cs = rnand->sels[die_nr].cs; + + if (chip == rnandc->selected_chip && die_nr == rnand->selected_die) + return; + + rnandc_clear_status(rnandc); + writel_relaxed(MEM_CTRL_CS(cs) | MEM_CTRL_DIS_WP(cs), rnandc->regs + MEM_CTRL_REG); + writel_relaxed(rnand->control, rnandc->regs + CONTROL_REG); + writel_relaxed(rnand->ecc_ctrl, rnandc->regs + ECC_CTRL_REG); + writel_relaxed(rnand->timings_asyn, rnandc->regs + TIMINGS_ASYN_REG); + writel_relaxed(rnand->tim_seq0, rnandc->regs + TIM_SEQ0_REG); + writel_relaxed(rnand->tim_seq1, rnandc->regs + TIM_SEQ1_REG); + writel_relaxed(rnand->tim_gen_seq0, rnandc->regs + TIM_GEN_SEQ0_REG); + writel_relaxed(rnand->tim_gen_seq1, rnandc->regs + TIM_GEN_SEQ1_REG); + writel_relaxed(rnand->tim_gen_seq2, rnandc->regs + TIM_GEN_SEQ2_REG); + writel_relaxed(rnand->tim_gen_seq3, rnandc->regs + TIM_GEN_SEQ3_REG); + + rnandc->selected_chip = chip; + rnand->selected_die = die_nr; +} + +static void rnandc_trigger_op(struct rnandc *rnandc, struct rnandc_op *rop) +{ + writel_relaxed(rop->addr0_col, rnandc->regs + ADDR0_COL_REG); + writel_relaxed(rop->addr0_row, rnandc->regs + ADDR0_ROW_REG); + writel_relaxed(rop->addr1_col, rnandc->regs + ADDR1_COL_REG); + writel_relaxed(rop->addr1_row, rnandc->regs + ADDR1_ROW_REG); + writel_relaxed(rop->ecc_offset, rnandc->regs + ECC_OFFSET_REG); + writel_relaxed(rop->gen_seq_ctrl, rnandc->regs + GEN_SEQ_CTRL_REG); + writel_relaxed(DATA_SIZE(rop->len), rnandc->regs + DATA_SIZE_REG); + writel_relaxed(rop->command, rnandc->regs + COMMAND_REG); +} + +static void rnandc_trigger_dma(struct rnandc *rnandc) +{ + writel_relaxed(DMA_CTRL_INCREMENT_BURST_4 | + DMA_CTRL_REGISTER_MANAGED_MODE | + DMA_CTRL_START, rnandc->regs + DMA_CTRL_REG); +} + +static irqreturn_t rnandc_irq_handler(int irq, void *private) +{ + struct rnandc *rnandc = private; + + rnandc_dis_interrupts(rnandc); + complete(&rnandc->complete); + + return IRQ_HANDLED; +} + +static int rnandc_wait_end_of_op(struct rnandc *rnandc, + struct nand_chip *chip) +{ + struct rnand_chip *rnand = to_rnand(chip); + unsigned int cs = to_rnandc_cs(rnand); + u32 status; + int ret; + + ret = readl_poll_timeout(rnandc->regs + STATUS_REG, status, + MEM_RDY(cs, status) && CTRL_RDY(status), + 1, 100000); + if (ret) + dev_err(rnandc->dev, "Operation timed out, status: 0x%08x\n", + status); + + return ret; +} + +static int rnandc_wait_end_of_io(struct rnandc *rnandc, + struct nand_chip *chip) +{ + int timeout_ms = 1000; + int ret; + + if (rnandc->use_polling) { + struct rnand_chip *rnand = to_rnand(chip); + unsigned int cs = to_rnandc_cs(rnand); + u32 status; + + ret = readl_poll_timeout(rnandc->regs + INT_STATUS_REG, status, + MEM_IS_RDY(cs, status) & + DMA_HAS_ENDED(status), + 0, timeout_ms * 1000); + } else { + ret = wait_for_completion_timeout(&rnandc->complete, + msecs_to_jiffies(timeout_ms)); + if (!ret) + ret = -ETIMEDOUT; + else + ret = 0; + } + + return ret; +} + +static int rnandc_read_page_hw_ecc(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct rnandc *rnandc = to_rnandc(chip->controller); + struct mtd_info *mtd = nand_to_mtd(chip); + struct rnand_chip *rnand = to_rnand(chip); + unsigned int cs = to_rnandc_cs(rnand); + struct rnandc_op rop = { + .command = COMMAND_INPUT_SEL_DMA | COMMAND_0(NAND_CMD_READ0) | + COMMAND_2(NAND_CMD_READSTART) | COMMAND_FIFO_SEL | + COMMAND_SEQ_READ_PAGE, + .addr0_row = page, + .len = mtd->writesize, + .ecc_offset = ECC_OFFSET(mtd->writesize + 2), + }; + unsigned int max_bitflips = 0; + dma_addr_t dma_addr; + u32 ecc_stat; + int bf, ret, i; + + /* Prepare controller */ + rnandc_select_target(chip, chip->cur_cs); + rnandc_clear_status(rnandc); + reinit_completion(&rnandc->complete); + rnandc_en_interrupts(rnandc, INT_DMA_ENDED); + rnandc_en_correction(rnandc); + + /* 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); + + rnandc_trigger_op(rnandc, &rop); + rnandc_trigger_dma(rnandc); + + ret = rnandc_wait_end_of_io(rnandc, chip); + dma_unmap_single(rnandc->dev, dma_addr, mtd->writesize, DMA_FROM_DEVICE); + rnandc_dis_correction(rnandc); + if (ret) { + dev_err(rnandc->dev, "Read page operation never ending\n"); + return ret; + } + + ecc_stat = readl_relaxed(rnandc->regs + ECC_STAT_REG); + + if (oob_required || ECC_STAT_UNCORRECTABLE(cs, ecc_stat)) { + ret = nand_change_read_column_op(chip, mtd->writesize, + chip->oob_poi, mtd->oobsize, + false); + if (ret) + return ret; + } + + if (ECC_STAT_UNCORRECTABLE(cs, ecc_stat)) { + for (i = 0; i < chip->ecc.steps; i++) { + unsigned int off = i * chip->ecc.size; + unsigned int eccoff = i * chip->ecc.bytes; + + bf = nand_check_erased_ecc_chunk(rnandc->buf + off, + chip->ecc.size, + chip->oob_poi + 2 + eccoff, + chip->ecc.bytes, + NULL, 0, + chip->ecc.strength); + if (bf < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += bf; + max_bitflips = max_t(unsigned int, max_bitflips, bf); + } + } + } else if (ECC_STAT_CORRECTABLE(cs, ecc_stat)) { + bf = ECC_CNT(cs, readl_relaxed(rnandc->regs + ECC_CNT_REG)); + /* + * The number of bitflips is an approximation given the fact + * that this controller does not provide per-chunk details but + * only gives statistics on the entire page. + */ + mtd->ecc_stats.corrected += bf; + } + + memcpy(buf, rnandc->buf, mtd->writesize); + + return 0; +} + +static int rnandc_read_subpage_hw_ecc(struct nand_chip *chip, u32 req_offset, + u32 req_len, u8 *bufpoi, int page) +{ + struct rnandc *rnandc = to_rnandc(chip->controller); + struct mtd_info *mtd = nand_to_mtd(chip); + struct rnand_chip *rnand = to_rnand(chip); + unsigned int cs = to_rnandc_cs(rnand); + unsigned int page_off = round_down(req_offset, chip->ecc.size); + unsigned int real_len = round_up(req_offset + req_len - page_off, + chip->ecc.size); + unsigned int start_chunk = page_off / chip->ecc.size; + unsigned int nchunks = real_len / chip->ecc.size; + unsigned int ecc_off = 2 + (start_chunk * chip->ecc.bytes); + struct rnandc_op rop = { + .command = COMMAND_INPUT_SEL_AHBS | COMMAND_0(NAND_CMD_READ0) | + COMMAND_2(NAND_CMD_READSTART) | COMMAND_FIFO_SEL | + COMMAND_SEQ_READ_PAGE, + .addr0_row = page, + .addr0_col = page_off, + .len = real_len, + .ecc_offset = ECC_OFFSET(mtd->writesize + ecc_off), + }; + unsigned int max_bitflips = 0, i; + u32 ecc_stat; + int bf, ret; + + /* Prepare controller */ + rnandc_select_target(chip, chip->cur_cs); + rnandc_clear_status(rnandc); + rnandc_en_correction(rnandc); + rnandc_trigger_op(rnandc, &rop); + + while (!FIFO_STATE_C_EMPTY(readl(rnandc->regs + FIFO_STATE_REG))) + cpu_relax(); + + while (FIFO_STATE_R_EMPTY(readl(rnandc->regs + FIFO_STATE_REG))) + cpu_relax(); + + ioread32_rep(rnandc->regs + FIFO_DATA_REG, bufpoi + page_off, + real_len / 4); + + if (!FIFO_STATE_R_EMPTY(readl(rnandc->regs + FIFO_STATE_REG))) { + dev_err(rnandc->dev, "Clearing residual data in the read FIFO\n"); + rnandc_clear_fifo(rnandc); + } + + ret = rnandc_wait_end_of_op(rnandc, chip); + rnandc_dis_correction(rnandc); + if (ret) { + dev_err(rnandc->dev, "Read subpage operation never ending\n"); + return ret; + } + + ecc_stat = readl_relaxed(rnandc->regs + ECC_STAT_REG); + + if (ECC_STAT_UNCORRECTABLE(cs, ecc_stat)) { + ret = nand_change_read_column_op(chip, mtd->writesize, + chip->oob_poi, mtd->oobsize, + false); + if (ret) + return ret; + + for (i = start_chunk; i < nchunks; i++) { + unsigned int dataoff = i * chip->ecc.size; + unsigned int eccoff = 2 + (i * chip->ecc.bytes); + + bf = nand_check_erased_ecc_chunk(bufpoi + dataoff, + chip->ecc.size, + chip->oob_poi + eccoff, + chip->ecc.bytes, + NULL, 0, + chip->ecc.strength); + if (bf < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += bf; + max_bitflips = max_t(unsigned int, max_bitflips, bf); + } + } + } else if (ECC_STAT_CORRECTABLE(cs, ecc_stat)) { + bf = ECC_CNT(cs, readl_relaxed(rnandc->regs + ECC_CNT_REG)); + /* + * The number of bitflips is an approximation given the fact + * that this controller does not provide per-chunk details but + * only gives statistics on the entire page. + */ + mtd->ecc_stats.corrected += bf; + } + + return 0; +} + +static int rnandc_write_page_hw_ecc(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + struct rnandc *rnandc = to_rnandc(chip->controller); + struct mtd_info *mtd = nand_to_mtd(chip); + struct rnand_chip *rnand = to_rnand(chip); + unsigned int cs = to_rnandc_cs(rnand); + struct rnandc_op rop = { + .command = COMMAND_INPUT_SEL_DMA | COMMAND_0(NAND_CMD_SEQIN) | + COMMAND_1(NAND_CMD_PAGEPROG) | COMMAND_FIFO_SEL | + COMMAND_SEQ_WRITE_PAGE, + .addr0_row = page, + .len = mtd->writesize, + .ecc_offset = ECC_OFFSET(mtd->writesize + 2), + }; + dma_addr_t dma_addr; + int ret; + + memcpy(rnandc->buf, buf, mtd->writesize); + + /* Prepare controller */ + rnandc_select_target(chip, chip->cur_cs); + rnandc_clear_status(rnandc); + reinit_completion(&rnandc->complete); + rnandc_en_interrupts(rnandc, INT_MEM_RDY(cs)); + rnandc_en_correction(rnandc); + + /* 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); + + rnandc_trigger_op(rnandc, &rop); + rnandc_trigger_dma(rnandc); + + ret = rnandc_wait_end_of_io(rnandc, chip); + dma_unmap_single(rnandc->dev, dma_addr, mtd->writesize, DMA_TO_DEVICE); + rnandc_dis_correction(rnandc); + if (ret) { + dev_err(rnandc->dev, "Write page operation never ending\n"); + return ret; + } + + if (!oob_required) + return 0; + + return nand_change_write_column_op(chip, mtd->writesize, chip->oob_poi, + mtd->oobsize, false); +} + +static int rnandc_write_subpage_hw_ecc(struct nand_chip *chip, u32 req_offset, + u32 req_len, const u8 *bufpoi, + int oob_required, int page) +{ + struct rnandc *rnandc = to_rnandc(chip->controller); + struct mtd_info *mtd = nand_to_mtd(chip); + unsigned int page_off = round_down(req_offset, chip->ecc.size); + unsigned int real_len = round_up(req_offset + req_len - page_off, + chip->ecc.size); + unsigned int start_chunk = page_off / chip->ecc.size; + unsigned int ecc_off = 2 + (start_chunk * chip->ecc.bytes); + struct rnandc_op rop = { + .command = COMMAND_INPUT_SEL_AHBS | COMMAND_0(NAND_CMD_SEQIN) | + COMMAND_1(NAND_CMD_PAGEPROG) | COMMAND_FIFO_SEL | + COMMAND_SEQ_WRITE_PAGE, + .addr0_row = page, + .addr0_col = page_off, + .len = real_len, + .ecc_offset = ECC_OFFSET(mtd->writesize + ecc_off), + }; + int ret; + + /* Prepare controller */ + rnandc_select_target(chip, chip->cur_cs); + rnandc_clear_status(rnandc); + rnandc_en_correction(rnandc); + rnandc_trigger_op(rnandc, &rop); + + while (FIFO_STATE_W_FULL(readl(rnandc->regs + FIFO_STATE_REG))) + cpu_relax(); + + iowrite32_rep(rnandc->regs + FIFO_DATA_REG, bufpoi + page_off, + real_len / 4); + + while (!FIFO_STATE_W_EMPTY(readl(rnandc->regs + FIFO_STATE_REG))) + cpu_relax(); + + ret = rnandc_wait_end_of_op(rnandc, chip); + rnandc_dis_correction(rnandc); + if (ret) { + dev_err(rnandc->dev, "Write subpage operation never ending\n"); + return ret; + } + + return 0; +} + +/* + * This controller is simple enough and thus does not need to use the parser + * provided by the core, instead, handle every situation here. + */ +static int rnandc_exec_op(struct nand_chip *chip, + const struct nand_operation *op, bool check_only) +{ + struct rnandc *rnandc = to_rnandc(chip->controller); + const struct nand_op_instr *instr = NULL; + struct rnandc_op rop = { + .command = COMMAND_INPUT_SEL_AHBS, + .gen_seq_ctrl = GEN_SEQ_IMD_SEQ, + }; + unsigned int cmd_phase = 0, addr_phase = 0, data_phase = 0, + delay_phase = 0, delays = 0; + unsigned int op_id, col_addrs, row_addrs, naddrs, remainder, words, i; + const u8 *addrs; + u32 last_bytes; + int ret; + + if (!check_only) + rnandc_select_target(chip, op->cs); + + for (op_id = 0; op_id < op->ninstrs; op_id++) { + instr = &op->instrs[op_id]; + + nand_op_trace(" ", instr); + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + switch (cmd_phase++) { + case 0: + rop.command |= COMMAND_0(instr->ctx.cmd.opcode); + rop.gen_seq_ctrl |= GEN_SEQ_CMD0_EN; + break; + case 1: + rop.gen_seq_ctrl |= GEN_SEQ_COMMAND_3(instr->ctx.cmd.opcode); + rop.gen_seq_ctrl |= GEN_SEQ_CMD3_EN; + if (addr_phase == 0) + addr_phase = 1; + break; + case 2: + rop.command |= COMMAND_2(instr->ctx.cmd.opcode); + rop.gen_seq_ctrl |= GEN_SEQ_CMD2_EN; + if (addr_phase <= 1) + addr_phase = 2; + break; + case 3: + rop.command |= COMMAND_1(instr->ctx.cmd.opcode); + rop.gen_seq_ctrl |= GEN_SEQ_CMD1_EN; + if (addr_phase <= 1) + addr_phase = 2; + if (delay_phase == 0) + delay_phase = 1; + if (data_phase == 0) + data_phase = 1; + break; + default: + return -EOPNOTSUPP; + } + break; + + case NAND_OP_ADDR_INSTR: + addrs = instr->ctx.addr.addrs; + naddrs = instr->ctx.addr.naddrs; + if (naddrs > 5) + return -EOPNOTSUPP; + + col_addrs = min(2U, naddrs); + row_addrs = naddrs > 2 ? naddrs - col_addrs : 0; + + switch (addr_phase++) { + case 0: + for (i = 0; i < col_addrs; i++) + rop.addr0_col |= addrs[i] << (i * 8); + rop.gen_seq_ctrl |= GEN_SEQ_COL_A0(col_addrs); + + for (i = 0; i < row_addrs; i++) + rop.addr0_row |= addrs[2 + i] << (i * 8); + rop.gen_seq_ctrl |= GEN_SEQ_ROW_A0(row_addrs); + + if (cmd_phase == 0) + cmd_phase = 1; + break; + case 1: + for (i = 0; i < col_addrs; i++) + rop.addr1_col |= addrs[i] << (i * 8); + rop.gen_seq_ctrl |= GEN_SEQ_COL_A1(col_addrs); + + for (i = 0; i < row_addrs; i++) + rop.addr1_row |= addrs[2 + i] << (i * 8); + rop.gen_seq_ctrl |= GEN_SEQ_ROW_A1(row_addrs); + + if (cmd_phase <= 1) + cmd_phase = 2; + break; + default: + return -EOPNOTSUPP; + } + break; + + case NAND_OP_DATA_IN_INSTR: + rop.read = true; + fallthrough; + case NAND_OP_DATA_OUT_INSTR: + rop.gen_seq_ctrl |= GEN_SEQ_DATA_EN; + rop.buf = instr->ctx.data.buf.in; + rop.len = instr->ctx.data.len; + rop.command |= COMMAND_FIFO_SEL; + + switch (data_phase++) { + case 0: + if (cmd_phase <= 2) + cmd_phase = 3; + if (addr_phase <= 1) + addr_phase = 2; + if (delay_phase == 0) + delay_phase = 1; + break; + default: + return -EOPNOTSUPP; + } + break; + + case NAND_OP_WAITRDY_INSTR: + switch (delay_phase++) { + case 0: + rop.gen_seq_ctrl |= GEN_SEQ_DELAY0_EN; + + if (cmd_phase <= 2) + cmd_phase = 3; + break; + case 1: + rop.gen_seq_ctrl |= GEN_SEQ_DELAY1_EN; + + if (cmd_phase <= 3) + cmd_phase = 4; + if (data_phase == 0) + data_phase = 1; + break; + default: + return -EOPNOTSUPP; + } + break; + } + } + + /* + * Sequence 19 is generic and dedicated to write operations. + * Sequence 18 is also generic and works for all other operations. + */ + if (rop.buf && !rop.read) + rop.command |= COMMAND_SEQ_GEN_OUT; + else + rop.command |= COMMAND_SEQ_GEN_IN; + + if (delays > 1) { + dev_err(rnandc->dev, "Cannot handle more than one wait delay\n"); + return -EOPNOTSUPP; + } + + if (check_only) + return 0; + + rnandc_trigger_op(rnandc, &rop); + + words = rop.len / sizeof(u32); + remainder = rop.len % sizeof(u32); + if (rop.buf && rop.read) { + while (!FIFO_STATE_C_EMPTY(readl(rnandc->regs + FIFO_STATE_REG))) + cpu_relax(); + + while (FIFO_STATE_R_EMPTY(readl(rnandc->regs + FIFO_STATE_REG))) + cpu_relax(); + + ioread32_rep(rnandc->regs + FIFO_DATA_REG, rop.buf, words); + if (remainder) { + last_bytes = readl_relaxed(rnandc->regs + FIFO_DATA_REG); + memcpy(rop.buf + (words * sizeof(u32)), &last_bytes, + remainder); + } + + if (!FIFO_STATE_R_EMPTY(readl(rnandc->regs + FIFO_STATE_REG))) { + dev_warn(rnandc->dev, + "Clearing residual data in the read FIFO\n"); + rnandc_clear_fifo(rnandc); + } + } else if (rop.len && !rop.read) { + while (FIFO_STATE_W_FULL(readl(rnandc->regs + FIFO_STATE_REG))) + cpu_relax(); + + iowrite32_rep(rnandc->regs + FIFO_DATA_REG, rop.buf, + DIV_ROUND_UP(rop.len, 4)); + + if (remainder) { + last_bytes = 0; + memcpy(&last_bytes, rop.buf + (words * sizeof(u32)), remainder); + writel_relaxed(last_bytes, rnandc->regs + FIFO_DATA_REG); + } + + while (!FIFO_STATE_W_EMPTY(readl(rnandc->regs + FIFO_STATE_REG))) + cpu_relax(); + } + + ret = rnandc_wait_end_of_op(rnandc, chip); + if (ret) + return ret; + + return 0; +} + +static int rnandc_setup_interface(struct nand_chip *chip, int chipnr, + const struct nand_interface_config *conf) +{ + struct rnand_chip *rnand = to_rnand(chip); + struct rnandc *rnandc = to_rnandc(chip->controller); + unsigned int period_ns = 1000000000 / rnandc->ext_clk_rate; + const struct nand_sdr_timings *sdr; + unsigned int cyc, cle, ale, bef_dly, ca_to_data; + + sdr = nand_get_sdr_timings(conf); + if (IS_ERR(sdr)) + return PTR_ERR(sdr); + + if (sdr->tRP_min != sdr->tWP_min || sdr->tREH_min != sdr->tWH_min) { + dev_err(rnandc->dev, "Read and write hold times must be identical\n"); + return -EINVAL; + } + + if (chipnr < 0) + return 0; + + rnand->timings_asyn = + TIMINGS_ASYN_TRWP(TO_CYCLES64(sdr->tRP_min, period_ns)) | + TIMINGS_ASYN_TRWH(TO_CYCLES64(sdr->tREH_min, period_ns)); + rnand->tim_seq0 = + TIM_SEQ0_TCCS(TO_CYCLES64(sdr->tCCS_min, period_ns)) | + TIM_SEQ0_TADL(TO_CYCLES64(sdr->tADL_min, period_ns)) | + TIM_SEQ0_TRHW(TO_CYCLES64(sdr->tRHW_min, period_ns)) | + TIM_SEQ0_TWHR(TO_CYCLES64(sdr->tWHR_min, period_ns)); + rnand->tim_seq1 = + TIM_SEQ1_TWB(TO_CYCLES64(sdr->tWB_max, period_ns)) | + TIM_SEQ1_TRR(TO_CYCLES64(sdr->tRR_min, period_ns)) | + TIM_SEQ1_TWW(TO_CYCLES64(sdr->tWW_min, period_ns)); + + cyc = sdr->tDS_min + sdr->tDH_min; + cle = sdr->tCLH_min + sdr->tCLS_min; + ale = sdr->tALH_min + sdr->tALS_min; + bef_dly = sdr->tWB_max - sdr->tDH_min; + ca_to_data = sdr->tWHR_min + sdr->tREA_max - sdr->tDH_min; + + /* + * D0 = CMD -> ADDR = tCLH + tCLS - 1 cycle + * D1 = CMD -> CMD = tCLH + tCLS - 1 cycle + * D2 = CMD -> DLY = tWB - tDH + * D3 = CMD -> DATA = tWHR + tREA - tDH + */ + rnand->tim_gen_seq0 = + TIM_GEN_SEQ0_D0(TO_CYCLES64(cle - cyc, period_ns)) | + TIM_GEN_SEQ0_D1(TO_CYCLES64(cle - cyc, period_ns)) | + TIM_GEN_SEQ0_D2(TO_CYCLES64(bef_dly, period_ns)) | + TIM_GEN_SEQ0_D3(TO_CYCLES64(ca_to_data, period_ns)); + + /* + * D4 = ADDR -> CMD = tALH + tALS - 1 cyle + * D5 = ADDR -> ADDR = tALH + tALS - 1 cyle + * D6 = ADDR -> DLY = tWB - tDH + * D7 = ADDR -> DATA = tWHR + tREA - tDH + */ + rnand->tim_gen_seq1 = + TIM_GEN_SEQ1_D4(TO_CYCLES64(ale - cyc, period_ns)) | + TIM_GEN_SEQ1_D5(TO_CYCLES64(ale - cyc, period_ns)) | + TIM_GEN_SEQ1_D6(TO_CYCLES64(bef_dly, period_ns)) | + TIM_GEN_SEQ1_D7(TO_CYCLES64(ca_to_data, period_ns)); + + /* + * D8 = DLY -> DATA = tRR + tREA + * D9 = DLY -> CMD = tRR + * D10 = DATA -> CMD = tCLH + tCLS - 1 cycle + * D11 = DATA -> DLY = tWB - tDH + */ + rnand->tim_gen_seq2 = + TIM_GEN_SEQ2_D8(TO_CYCLES64(sdr->tRR_min + sdr->tREA_max, period_ns)) | + TIM_GEN_SEQ2_D9(TO_CYCLES64(sdr->tRR_min, period_ns)) | + TIM_GEN_SEQ2_D10(TO_CYCLES64(cle - cyc, period_ns)) | + TIM_GEN_SEQ2_D11(TO_CYCLES64(bef_dly, period_ns)); + + /* D12 = DATA -> END = tCLH - tDH */ + rnand->tim_gen_seq3 = + TIM_GEN_SEQ3_D12(TO_CYCLES64(sdr->tCLH_min - sdr->tDH_min, period_ns)); + + return 0; +} + +static int rnandc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + unsigned int eccbytes = round_up(chip->ecc.bytes, 4) * chip->ecc.steps; + + if (section) + return -ERANGE; + + oobregion->offset = 2; + oobregion->length = eccbytes; + + return 0; +} + +static int rnandc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + unsigned int eccbytes = round_up(chip->ecc.bytes, 4) * chip->ecc.steps; + + if (section) + return -ERANGE; + + oobregion->offset = 2 + eccbytes; + oobregion->length = mtd->oobsize - oobregion->offset; + + return 0; +} + +static const struct mtd_ooblayout_ops rnandc_ooblayout_ops = { + .ecc = rnandc_ooblayout_ecc, + .free = rnandc_ooblayout_free, +}; + +static int rnandc_hw_ecc_controller_init(struct nand_chip *chip) +{ + struct rnand_chip *rnand = to_rnand(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct rnandc *rnandc = to_rnandc(chip->controller); + + if (mtd->writesize > SZ_16K) { + dev_err(rnandc->dev, "Unsupported page size\n"); + return -EINVAL; + } + + switch (chip->ecc.size) { + case SZ_256: + rnand->control |= CONTROL_ECC_BLOCK_SIZE_256; + break; + case SZ_512: + rnand->control |= CONTROL_ECC_BLOCK_SIZE_512; + break; + case SZ_1K: + rnand->control |= CONTROL_ECC_BLOCK_SIZE_1024; + break; + default: + dev_err(rnandc->dev, "Unsupported ECC chunk size\n"); + return -EINVAL; + } + + switch (chip->ecc.strength) { + case 2: + chip->ecc.bytes = 4; + rnand->ecc_ctrl |= ECC_CTRL_CAP_2B; + break; + case 4: + chip->ecc.bytes = 7; + rnand->ecc_ctrl |= ECC_CTRL_CAP_4B; + break; + case 8: + chip->ecc.bytes = 14; + rnand->ecc_ctrl |= ECC_CTRL_CAP_8B; + break; + case 16: + chip->ecc.bytes = 28; + rnand->ecc_ctrl |= ECC_CTRL_CAP_16B; + break; + case 24: + chip->ecc.bytes = 42; + rnand->ecc_ctrl |= ECC_CTRL_CAP_24B; + break; + case 32: + chip->ecc.bytes = 56; + rnand->ecc_ctrl |= ECC_CTRL_CAP_32B; + break; + default: + dev_err(rnandc->dev, "Unsupported ECC strength\n"); + return -EINVAL; + } + + rnand->ecc_ctrl |= ECC_CTRL_ERR_THRESHOLD(chip->ecc.strength); + + mtd_set_ooblayout(mtd, &rnandc_ooblayout_ops); + chip->ecc.steps = mtd->writesize / chip->ecc.size; + chip->ecc.read_page = rnandc_read_page_hw_ecc; + chip->ecc.read_subpage = rnandc_read_subpage_hw_ecc; + chip->ecc.write_page = rnandc_write_page_hw_ecc; + chip->ecc.write_subpage = rnandc_write_subpage_hw_ecc; + + return 0; +} + +static int rnandc_ecc_init(struct nand_chip *chip) +{ + struct nand_ecc_ctrl *ecc = &chip->ecc; + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&chip->base); + struct rnandc *rnandc = to_rnandc(chip->controller); + int ret; + + if (ecc->engine_type != NAND_ECC_ENGINE_TYPE_NONE && + (!ecc->size || !ecc->strength)) { + if (requirements->step_size && requirements->strength) { + ecc->size = requirements->step_size; + ecc->strength = requirements->strength; + } else { + dev_err(rnandc->dev, "No minimum ECC strength\n"); + return -EINVAL; + } + } + + switch (ecc->engine_type) { + case NAND_ECC_ENGINE_TYPE_ON_HOST: + ret = rnandc_hw_ecc_controller_init(chip); + if (ret) + return ret; + break; + case NAND_ECC_ENGINE_TYPE_NONE: + case NAND_ECC_ENGINE_TYPE_SOFT: + case NAND_ECC_ENGINE_TYPE_ON_DIE: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rnandc_attach_chip(struct nand_chip *chip) +{ + struct rnand_chip *rnand = to_rnand(chip); + struct rnandc *rnandc = to_rnandc(chip->controller); + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_memory_organization *memorg = nanddev_get_memorg(&chip->base); + int ret; + + /* Do not store BBT bits in the OOB section as it is not protected */ + if (chip->bbt_options & NAND_BBT_USE_FLASH) + chip->bbt_options |= NAND_BBT_NO_OOB; + + if (mtd->writesize <= 512) { + dev_err(rnandc->dev, "Small page devices not supported\n"); + return -EINVAL; + } + + rnand->control |= CONTROL_CHECK_RB_LINE | CONTROL_INT_EN; + + switch (memorg->pages_per_eraseblock) { + case 32: + rnand->control |= CONTROL_BLOCK_SIZE_32P; + break; + case 64: + rnand->control |= CONTROL_BLOCK_SIZE_64P; + break; + case 128: + rnand->control |= CONTROL_BLOCK_SIZE_128P; + break; + case 256: + rnand->control |= CONTROL_BLOCK_SIZE_256P; + break; + default: + dev_err(rnandc->dev, "Unsupported memory organization\n"); + return -EINVAL; + } + + chip->options |= NAND_SUBPAGE_READ; + + ret = rnandc_ecc_init(chip); + if (ret) { + dev_err(rnandc->dev, "ECC initialization failed (%d)\n", ret); + return ret; + } + + /* Force an update of the configuration registers */ + rnand->selected_die = -1; + + return 0; +} + +static const struct nand_controller_ops rnandc_ops = { + .attach_chip = rnandc_attach_chip, + .exec_op = rnandc_exec_op, + .setup_interface = rnandc_setup_interface, +}; + +static int rnandc_alloc_dma_buf(struct rnandc *rnandc, + struct mtd_info *new_mtd) +{ + unsigned int max_len = new_mtd->writesize + new_mtd->oobsize; + struct rnand_chip *entry, *temp; + struct nand_chip *chip; + struct mtd_info *mtd; + + list_for_each_entry_safe(entry, temp, &rnandc->chips, node) { + chip = &entry->chip; + mtd = nand_to_mtd(chip); + max_len = max(max_len, mtd->writesize + mtd->oobsize); + } + + if (rnandc->buf && rnandc->buf_sz < max_len) { + devm_kfree(rnandc->dev, rnandc->buf); + rnandc->buf = NULL; + } + + if (!rnandc->buf) { + rnandc->buf_sz = max_len; + rnandc->buf = devm_kmalloc(rnandc->dev, max_len, + GFP_KERNEL | GFP_DMA); + if (!rnandc->buf) + return -ENOMEM; + } + + return 0; +} + +static int rnandc_chip_init(struct rnandc *rnandc, struct device_node *np) +{ + struct rnand_chip *rnand; + struct mtd_info *mtd; + struct nand_chip *chip; + int nsels, ret, i; + u32 cs; + + nsels = of_property_count_elems_of_size(np, "reg", sizeof(u32)); + if (nsels <= 0) { + ret = (nsels < 0) ? nsels : -EINVAL; + dev_err(rnandc->dev, "Invalid reg property (%d)\n", ret); + return ret; + } + + /* Alloc the driver's NAND chip structure */ + rnand = devm_kzalloc(rnandc->dev, struct_size(rnand, sels, nsels), + GFP_KERNEL); + if (!rnand) + return -ENOMEM; + + rnand->nsels = nsels; + rnand->selected_die = -1; + + for (i = 0; i < nsels; i++) { + ret = of_property_read_u32_index(np, "reg", i, &cs); + if (ret) { + dev_err(rnandc->dev, "Incomplete reg property (%d)\n", ret); + return ret; + } + + if (cs >= RNANDC_CS_NUM) { + dev_err(rnandc->dev, "Invalid reg property (%d)\n", cs); + return -EINVAL; + } + + if (test_and_set_bit(cs, &rnandc->assigned_cs)) { + dev_err(rnandc->dev, "CS %d already assigned\n", cs); + return -EINVAL; + } + + /* + * No need to check for RB or WP properties, there is a 1:1 + * mandatory mapping with the CS. + */ + rnand->sels[i].cs = cs; + } + + chip = &rnand->chip; + chip->controller = &rnandc->controller; + nand_set_flash_node(chip, np); + + mtd = nand_to_mtd(chip); + mtd->dev.parent = rnandc->dev; + if (!mtd->name) { + dev_err(rnandc->dev, "Missing MTD label\n"); + return -EINVAL; + } + + ret = nand_scan(chip, rnand->nsels); + if (ret) { + dev_err(rnandc->dev, "Failed to scan the NAND chip (%d)\n", ret); + return ret; + } + + ret = rnandc_alloc_dma_buf(rnandc, mtd); + if (ret) + goto cleanup_nand; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(rnandc->dev, "Failed to register MTD device (%d)\n", ret); + goto cleanup_nand; + } + + list_add_tail(&rnand->node, &rnandc->chips); + + return 0; + +cleanup_nand: + nand_cleanup(chip); + + return ret; +} + +static void rnandc_chips_cleanup(struct rnandc *rnandc) +{ + struct rnand_chip *entry, *temp; + struct nand_chip *chip; + int ret; + + list_for_each_entry_safe(entry, temp, &rnandc->chips, node) { + chip = &entry->chip; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + list_del(&entry->node); + } +} + +static int rnandc_chips_init(struct rnandc *rnandc) +{ + int ret; + + for_each_child_of_node_scoped(rnandc->dev->of_node, np) { + ret = rnandc_chip_init(rnandc, np); + if (ret) { + rnandc_chips_cleanup(rnandc); + return ret; + } + } + + return 0; +} + +static int rnandc_probe(struct platform_device *pdev) +{ + struct rnandc *rnandc; + struct clk *eclk; + int irq, ret; + + rnandc = devm_kzalloc(&pdev->dev, sizeof(*rnandc), GFP_KERNEL); + if (!rnandc) + return -ENOMEM; + + rnandc->dev = &pdev->dev; + nand_controller_init(&rnandc->controller); + rnandc->controller.ops = &rnandc_ops; + INIT_LIST_HEAD(&rnandc->chips); + init_completion(&rnandc->complete); + + rnandc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(rnandc->regs)) + return PTR_ERR(rnandc->regs); + + ret = devm_pm_runtime_enable(&pdev->dev); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret < 0) + return ret; + + /* The external NAND bus clock rate is needed for computing timings */ + eclk = clk_get(&pdev->dev, "eclk"); + if (IS_ERR(eclk)) { + ret = PTR_ERR(eclk); + goto dis_runtime_pm; + } + + rnandc->ext_clk_rate = clk_get_rate(eclk); + clk_put(eclk); + + rnandc_dis_interrupts(rnandc); + irq = platform_get_irq_optional(pdev, 0); + if (irq == -EPROBE_DEFER) { + ret = irq; + goto dis_runtime_pm; + } else if (irq < 0) { + dev_info(&pdev->dev, "No IRQ found, fallback to polling\n"); + rnandc->use_polling = true; + } else { + ret = devm_request_irq(&pdev->dev, irq, rnandc_irq_handler, 0, + "renesas-nand-controller", rnandc); + if (ret < 0) + goto dis_runtime_pm; + } + + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) + goto dis_runtime_pm; + + rnandc_clear_fifo(rnandc); + + platform_set_drvdata(pdev, rnandc); + + ret = rnandc_chips_init(rnandc); + if (ret) + goto dis_runtime_pm; + + return 0; + +dis_runtime_pm: + pm_runtime_put(&pdev->dev); + + return ret; +} + +static void rnandc_remove(struct platform_device *pdev) +{ + struct rnandc *rnandc = platform_get_drvdata(pdev); + + rnandc_chips_cleanup(rnandc); + + pm_runtime_put(&pdev->dev); +} + +static const struct of_device_id rnandc_id_table[] = { + { .compatible = "renesas,rcar-gen3-nandc" }, + { .compatible = "renesas,rzn1-nandc" }, + {} /* sentinel */ +}; +MODULE_DEVICE_TABLE(of, rnandc_id_table); + +static struct platform_driver rnandc_driver = { + .driver = { + .name = "renesas-nandc", + .of_match_table = rnandc_id_table, + }, + .probe = rnandc_probe, + .remove = rnandc_remove, +}; +module_platform_driver(rnandc_driver); + +MODULE_AUTHOR("Miquel Raynal "); +MODULE_DESCRIPTION("Renesas R-Car Gen3 & RZ/N1 NAND controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/raw/rockchip-nand-controller.c b/drivers/mtd/nand/raw/rockchip-nand-controller.c new file mode 100644 index 000000000..9444ba026 --- /dev/null +++ b/drivers/mtd/nand/raw/rockchip-nand-controller.c @@ -0,0 +1,1507 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT +/* + * Rockchip NAND Flash controller driver. + * Copyright (C) 2020 Rockchip Inc. + * Author: Yifeng Zhao + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * NFC Page Data Layout: + * 1024 bytes data + 4Bytes sys data + 28Bytes~124Bytes ECC data + + * 1024 bytes data + 4Bytes sys data + 28Bytes~124Bytes ECC data + + * ...... + * NAND Page Data Layout: + * 1024 * n data + m Bytes oob + * Original Bad Block Mask Location: + * First byte of oob(spare). + * nand_chip->oob_poi data layout: + * 4Bytes sys data + .... + 4Bytes sys data + ECC data. + */ + +/* NAND controller register definition */ +#define NFC_READ (0) +#define NFC_WRITE (1) + +#define NFC_FMCTL (0x00) +#define FMCTL_CE_SEL_M 0xFF +#define FMCTL_CE_SEL(x) (1 << (x)) +#define FMCTL_WP BIT(8) +#define FMCTL_RDY BIT(9) + +#define NFC_FMWAIT (0x04) +#define FLCTL_RST BIT(0) +#define FLCTL_WR (1) /* 0: read, 1: write */ +#define FLCTL_XFER_ST BIT(2) +#define FLCTL_XFER_EN BIT(3) +#define FLCTL_ACORRECT BIT(10) /* Auto correct error bits. */ +#define FLCTL_XFER_READY BIT(20) +#define FLCTL_XFER_SECTOR (22) +#define FLCTL_TOG_FIX BIT(29) + +#define BCHCTL_BANK_M (7 << 5) +#define BCHCTL_BANK (5) + +#define DMA_ST BIT(0) +#define DMA_WR (1) /* 0: write, 1: read */ +#define DMA_EN BIT(2) +#define DMA_AHB_SIZE (3) /* 0: 1, 1: 2, 2: 4 */ +#define DMA_BURST_SIZE (6) /* 0: 1, 3: 4, 5: 8, 7: 16 */ +#define DMA_INC_NUM (9) /* 1 - 16 */ + +#define ECC_ERR_CNT(x, e) ((((x) >> (e).low) & (e).low_mask) |\ + (((x) >> (e).high) & (e).high_mask) << (e).low_bn) +#define INT_DMA BIT(0) +#define NFC_BANK (0x800) +#define NFC_BANK_STEP (0x100) +#define BANK_DATA (0x00) +#define BANK_ADDR (0x04) +#define BANK_CMD (0x08) +#define NFC_SRAM0 (0x1000) +#define NFC_SRAM1 (0x1400) +#define NFC_SRAM_SIZE (0x400) +#define NFC_TIMEOUT (500000) +#define NFC_MAX_OOB_PER_STEP 128 +#define NFC_MIN_OOB_PER_STEP 64 +#define MAX_DATA_SIZE 0xFFFC +#define MAX_ADDRESS_CYC 6 +#define NFC_ECC_MAX_MODES 4 +#define NFC_MAX_NSELS (8) /* Some Socs only have 1 or 2 CSs. */ +#define NFC_SYS_DATA_SIZE (4) /* 4 bytes sys data in oob pre 1024 data.*/ +#define RK_DEFAULT_CLOCK_RATE (150 * 1000 * 1000) /* 150 Mhz */ +#define ACCTIMING(csrw, rwpw, rwcs) ((csrw) << 12 | (rwpw) << 5 | (rwcs)) + +enum nfc_type { + NFC_V6, + NFC_V8, + NFC_V9, +}; + +/** + * struct rk_ecc_cnt_status: represent a ecc status data. + * @err_flag_bit: error flag bit index at register. + * @low: ECC count low bit index at register. + * @low_mask: mask bit. + * @low_bn: ECC count low bit number. + * @high: ECC count high bit index at register. + * @high_mask: mask bit + */ +struct rk_ecc_cnt_status { + u8 err_flag_bit; + u8 low; + u8 low_mask; + u8 low_bn; + u8 high; + u8 high_mask; +}; + +/** + * struct nfc_cfg: Rockchip NAND controller configuration + * @type: NFC version + * @ecc_strengths: ECC strengths + * @ecc_cfgs: ECC config values + * @flctl_off: FLCTL register offset + * @bchctl_off: BCHCTL register offset + * @dma_data_buf_off: DMA_DATA_BUF register offset + * @dma_oob_buf_off: DMA_OOB_BUF register offset + * @dma_cfg_off: DMA_CFG register offset + * @dma_st_off: DMA_ST register offset + * @bch_st_off: BCG_ST register offset + * @randmz_off: RANDMZ register offset + * @int_en_off: interrupt enable register offset + * @int_clr_off: interrupt clean register offset + * @int_st_off: interrupt status register offset + * @oob0_off: oob0 register offset + * @oob1_off: oob1 register offset + * @ecc0: represent ECC0 status data + * @ecc1: represent ECC1 status data + */ +struct nfc_cfg { + enum nfc_type type; + u8 ecc_strengths[NFC_ECC_MAX_MODES]; + u32 ecc_cfgs[NFC_ECC_MAX_MODES]; + u32 flctl_off; + u32 bchctl_off; + u32 dma_cfg_off; + u32 dma_data_buf_off; + u32 dma_oob_buf_off; + u32 dma_st_off; + u32 bch_st_off; + u32 randmz_off; + u32 int_en_off; + u32 int_clr_off; + u32 int_st_off; + u32 oob0_off; + u32 oob1_off; + struct rk_ecc_cnt_status ecc0; + struct rk_ecc_cnt_status ecc1; +}; + +struct rk_nfc_nand_chip { + struct list_head node; + struct nand_chip chip; + + u16 boot_blks; + u16 metadata_size; + u32 boot_ecc; + u32 timing; + + u8 nsels; + u8 sels[] __counted_by(nsels); +}; + +struct rk_nfc { + struct nand_controller controller; + const struct nfc_cfg *cfg; + struct device *dev; + + struct clk *nfc_clk; + struct clk *ahb_clk; + void __iomem *regs; + + u32 selected_bank; + u32 band_offset; + u32 cur_ecc; + u32 cur_timing; + + struct completion done; + struct list_head chips; + + u8 *page_buf; + u32 *oob_buf; + u32 page_buf_size; + u32 oob_buf_size; + + unsigned long assigned_cs; +}; + +static inline struct rk_nfc_nand_chip *rk_nfc_to_rknand(struct nand_chip *chip) +{ + return container_of(chip, struct rk_nfc_nand_chip, chip); +} + +static inline u8 *rk_nfc_buf_to_data_ptr(struct nand_chip *chip, const u8 *p, int i) +{ + return (u8 *)p + i * chip->ecc.size; +} + +static inline u8 *rk_nfc_buf_to_oob_ptr(struct nand_chip *chip, int i) +{ + u8 *poi; + + poi = chip->oob_poi + i * NFC_SYS_DATA_SIZE; + + return poi; +} + +static inline u8 *rk_nfc_buf_to_oob_ecc_ptr(struct nand_chip *chip, int i) +{ + struct rk_nfc_nand_chip *rknand = rk_nfc_to_rknand(chip); + u8 *poi; + + poi = chip->oob_poi + rknand->metadata_size + chip->ecc.bytes * i; + + return poi; +} + +static inline int rk_nfc_data_len(struct nand_chip *chip) +{ + return chip->ecc.size + chip->ecc.bytes + NFC_SYS_DATA_SIZE; +} + +static inline u8 *rk_nfc_data_ptr(struct nand_chip *chip, int i) +{ + struct rk_nfc *nfc = nand_get_controller_data(chip); + + return nfc->page_buf + i * rk_nfc_data_len(chip); +} + +static inline u8 *rk_nfc_oob_ptr(struct nand_chip *chip, int i) +{ + struct rk_nfc *nfc = nand_get_controller_data(chip); + + return nfc->page_buf + i * rk_nfc_data_len(chip) + chip->ecc.size; +} + +static int rk_nfc_hw_ecc_setup(struct nand_chip *chip, u32 strength) +{ + struct rk_nfc *nfc = nand_get_controller_data(chip); + u32 reg, i; + + for (i = 0; i < NFC_ECC_MAX_MODES; i++) { + if (strength == nfc->cfg->ecc_strengths[i]) { + reg = nfc->cfg->ecc_cfgs[i]; + break; + } + } + + if (i >= NFC_ECC_MAX_MODES) + return -EINVAL; + + writel(reg, nfc->regs + nfc->cfg->bchctl_off); + + /* Save chip ECC setting */ + nfc->cur_ecc = strength; + + return 0; +} + +static void rk_nfc_select_chip(struct nand_chip *chip, int cs) +{ + struct rk_nfc *nfc = nand_get_controller_data(chip); + struct rk_nfc_nand_chip *rknand = rk_nfc_to_rknand(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + u32 val; + + if (cs < 0) { + nfc->selected_bank = -1; + /* Deselect the currently selected target. */ + val = readl_relaxed(nfc->regs + NFC_FMCTL); + val &= ~FMCTL_CE_SEL_M; + writel(val, nfc->regs + NFC_FMCTL); + return; + } + + nfc->selected_bank = rknand->sels[cs]; + nfc->band_offset = NFC_BANK + nfc->selected_bank * NFC_BANK_STEP; + + val = readl_relaxed(nfc->regs + NFC_FMCTL); + val &= ~FMCTL_CE_SEL_M; + val |= FMCTL_CE_SEL(nfc->selected_bank); + + writel(val, nfc->regs + NFC_FMCTL); + + /* + * Compare current chip timing with selected chip timing and + * change if needed. + */ + if (nfc->cur_timing != rknand->timing) { + writel(rknand->timing, nfc->regs + NFC_FMWAIT); + nfc->cur_timing = rknand->timing; + } + + /* + * Compare current chip ECC setting with selected chip ECC setting and + * change if needed. + */ + if (nfc->cur_ecc != ecc->strength) + rk_nfc_hw_ecc_setup(chip, ecc->strength); +} + +static inline int rk_nfc_wait_ioready(struct rk_nfc *nfc) +{ + int rc; + u32 val; + + rc = readl_relaxed_poll_timeout(nfc->regs + NFC_FMCTL, val, + val & FMCTL_RDY, 10, NFC_TIMEOUT); + + return rc; +} + +static void rk_nfc_read_buf(struct rk_nfc *nfc, u8 *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) + buf[i] = readb_relaxed(nfc->regs + nfc->band_offset + + BANK_DATA); +} + +static void rk_nfc_write_buf(struct rk_nfc *nfc, const u8 *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) + writeb(buf[i], nfc->regs + nfc->band_offset + BANK_DATA); +} + +static int rk_nfc_cmd(struct nand_chip *chip, + const struct nand_subop *subop) +{ + struct rk_nfc *nfc = nand_get_controller_data(chip); + unsigned int i, j, remaining, start; + int reg_offset = nfc->band_offset; + u8 *inbuf = NULL; + const u8 *outbuf; + u32 cnt = 0; + int ret = 0; + + for (i = 0; i < subop->ninstrs; i++) { + const struct nand_op_instr *instr = &subop->instrs[i]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + writeb(instr->ctx.cmd.opcode, + nfc->regs + reg_offset + BANK_CMD); + break; + + case NAND_OP_ADDR_INSTR: + remaining = nand_subop_get_num_addr_cyc(subop, i); + start = nand_subop_get_addr_start_off(subop, i); + + for (j = 0; j < 8 && j + start < remaining; j++) + writeb(instr->ctx.addr.addrs[j + start], + nfc->regs + reg_offset + BANK_ADDR); + break; + + case NAND_OP_DATA_IN_INSTR: + case NAND_OP_DATA_OUT_INSTR: + start = nand_subop_get_data_start_off(subop, i); + cnt = nand_subop_get_data_len(subop, i); + + if (instr->type == NAND_OP_DATA_OUT_INSTR) { + outbuf = instr->ctx.data.buf.out + start; + rk_nfc_write_buf(nfc, outbuf, cnt); + } else { + inbuf = instr->ctx.data.buf.in + start; + rk_nfc_read_buf(nfc, inbuf, cnt); + } + break; + + case NAND_OP_WAITRDY_INSTR: + if (rk_nfc_wait_ioready(nfc) < 0) { + ret = -ETIMEDOUT; + dev_err(nfc->dev, "IO not ready\n"); + } + break; + } + } + + return ret; +} + +static const struct nand_op_parser rk_nfc_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN( + rk_nfc_cmd, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, MAX_ADDRESS_CYC), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, MAX_DATA_SIZE)), + NAND_OP_PARSER_PATTERN( + rk_nfc_cmd, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, MAX_ADDRESS_CYC), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(true, MAX_DATA_SIZE), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), +); + +static int rk_nfc_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + if (!check_only) + rk_nfc_select_chip(chip, op->cs); + + return nand_op_parser_exec_op(chip, &rk_nfc_op_parser, op, + check_only); +} + +static int rk_nfc_setup_interface(struct nand_chip *chip, int target, + const struct nand_interface_config *conf) +{ + struct rk_nfc_nand_chip *rknand = rk_nfc_to_rknand(chip); + struct rk_nfc *nfc = nand_get_controller_data(chip); + const struct nand_sdr_timings *timings; + u32 rate, tc2rw, trwpw, trw2c; + u32 temp; + + timings = nand_get_sdr_timings(conf); + if (IS_ERR(timings)) + return -EOPNOTSUPP; + + if (target < 0) + return 0; + + if (IS_ERR(nfc->nfc_clk)) + rate = clk_get_rate(nfc->ahb_clk); + else + rate = clk_get_rate(nfc->nfc_clk); + + /* Turn clock rate into kHz. */ + rate /= 1000; + + tc2rw = 1; + trw2c = 1; + + trwpw = max(timings->tWC_min, timings->tRC_min) / 1000; + trwpw = DIV_ROUND_UP(trwpw * rate, 1000000); + + temp = timings->tREA_max / 1000; + temp = DIV_ROUND_UP(temp * rate, 1000000); + + if (trwpw < temp) + trwpw = temp; + + /* + * ACCON: access timing control register + * ------------------------------------- + * 31:18: reserved + * 17:12: csrw, clock cycles from the falling edge of CSn to the + * falling edge of RDn or WRn + * 11:11: reserved + * 10:05: rwpw, the width of RDn or WRn in processor clock cycles + * 04:00: rwcs, clock cycles from the rising edge of RDn or WRn to the + * rising edge of CSn + */ + + /* Save chip timing */ + rknand->timing = ACCTIMING(tc2rw, trwpw, trw2c); + + return 0; +} + +static void rk_nfc_xfer_start(struct rk_nfc *nfc, u8 rw, u8 n_KB, + dma_addr_t dma_data, dma_addr_t dma_oob) +{ + u32 dma_reg, fl_reg, bch_reg; + + dma_reg = DMA_ST | ((!rw) << DMA_WR) | DMA_EN | (2 << DMA_AHB_SIZE) | + (7 << DMA_BURST_SIZE) | (16 << DMA_INC_NUM); + + fl_reg = (rw << FLCTL_WR) | FLCTL_XFER_EN | FLCTL_ACORRECT | + (n_KB << FLCTL_XFER_SECTOR) | FLCTL_TOG_FIX; + + if (nfc->cfg->type == NFC_V6 || nfc->cfg->type == NFC_V8) { + bch_reg = readl_relaxed(nfc->regs + nfc->cfg->bchctl_off); + bch_reg = (bch_reg & (~BCHCTL_BANK_M)) | + (nfc->selected_bank << BCHCTL_BANK); + writel(bch_reg, nfc->regs + nfc->cfg->bchctl_off); + } + + writel(dma_reg, nfc->regs + nfc->cfg->dma_cfg_off); + writel((u32)dma_data, nfc->regs + nfc->cfg->dma_data_buf_off); + writel((u32)dma_oob, nfc->regs + nfc->cfg->dma_oob_buf_off); + writel(fl_reg, nfc->regs + nfc->cfg->flctl_off); + fl_reg |= FLCTL_XFER_ST; + writel(fl_reg, nfc->regs + nfc->cfg->flctl_off); +} + +static int rk_nfc_wait_for_xfer_done(struct rk_nfc *nfc) +{ + void __iomem *ptr; + u32 reg; + + ptr = nfc->regs + nfc->cfg->flctl_off; + + return readl_relaxed_poll_timeout(ptr, reg, + reg & FLCTL_XFER_READY, + 10, NFC_TIMEOUT); +} + +static int rk_nfc_write_page_raw(struct nand_chip *chip, const u8 *buf, + int oob_on, int page) +{ + struct rk_nfc_nand_chip *rknand = rk_nfc_to_rknand(chip); + struct rk_nfc *nfc = nand_get_controller_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int i, pages_per_blk; + + pages_per_blk = mtd->erasesize / mtd->writesize; + if ((chip->options & NAND_IS_BOOT_MEDIUM) && + (page < (pages_per_blk * rknand->boot_blks)) && + rknand->boot_ecc != ecc->strength) { + /* + * There's currently no method to notify the MTD framework that + * a different ECC strength is in use for the boot blocks. + */ + return -EIO; + } + + if (!buf) + memset(nfc->page_buf, 0xff, mtd->writesize + mtd->oobsize); + + for (i = 0; i < ecc->steps; i++) { + /* Copy data to the NFC buffer. */ + if (buf) + memcpy(rk_nfc_data_ptr(chip, i), + rk_nfc_buf_to_data_ptr(chip, buf, i), + ecc->size); + /* + * The first four bytes of OOB are reserved for the + * boot ROM. In some debugging cases, such as with a + * read, erase and write back test these 4 bytes stored + * in OOB also need to be written back. + * + * The function nand_block_bad detects bad blocks like: + * + * bad = chip->oob_poi[chip->badblockpos]; + * + * chip->badblockpos == 0 for a large page NAND Flash, + * so chip->oob_poi[0] is the bad block mask (BBM). + * + * The OOB data layout on the NFC is: + * + * PA0 PA1 PA2 PA3 | BBM OOB1 OOB2 OOB3 | ... + * + * or + * + * 0xFF 0xFF 0xFF 0xFF | BBM OOB1 OOB2 OOB3 | ... + * + * The code here just swaps the first 4 bytes with the last + * 4 bytes without losing any data. + * + * The chip->oob_poi data layout: + * + * BBM OOB1 OOB2 OOB3 |......| PA0 PA1 PA2 PA3 + * + * The rk_nfc_ooblayout_free() function already has reserved + * these 4 bytes together with 2 bytes for BBM + * by reducing it's length: + * + * oob_region->length = rknand->metadata_size - NFC_SYS_DATA_SIZE - 2; + */ + if (!i) + memcpy(rk_nfc_oob_ptr(chip, i), + rk_nfc_buf_to_oob_ptr(chip, ecc->steps - 1), + NFC_SYS_DATA_SIZE); + else + memcpy(rk_nfc_oob_ptr(chip, i), + rk_nfc_buf_to_oob_ptr(chip, i - 1), + NFC_SYS_DATA_SIZE); + /* Copy ECC data to the NFC buffer. */ + memcpy(rk_nfc_oob_ptr(chip, i) + NFC_SYS_DATA_SIZE, + rk_nfc_buf_to_oob_ecc_ptr(chip, i), + ecc->bytes); + } + + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + rk_nfc_write_buf(nfc, buf, mtd->writesize + mtd->oobsize); + return nand_prog_page_end_op(chip); +} + +static int rk_nfc_write_page_hwecc(struct nand_chip *chip, const u8 *buf, + int oob_on, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct rk_nfc *nfc = nand_get_controller_data(chip); + struct rk_nfc_nand_chip *rknand = rk_nfc_to_rknand(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int oob_step = (ecc->bytes > 60) ? NFC_MAX_OOB_PER_STEP : + NFC_MIN_OOB_PER_STEP; + int pages_per_blk = mtd->erasesize / mtd->writesize; + int ret = 0, i, boot_rom_mode = 0; + dma_addr_t dma_data, dma_oob; + u32 tmp; + u8 *oob; + + nand_prog_page_begin_op(chip, page, 0, NULL, 0); + + if (buf) + memcpy(nfc->page_buf, buf, mtd->writesize); + else + memset(nfc->page_buf, 0xFF, mtd->writesize); + + /* + * The first blocks (4, 8 or 16 depending on the device) are used + * by the boot ROM and the first 32 bits of OOB need to link to + * the next page address in the same block. We can't directly copy + * OOB data from the MTD framework, because this page address + * conflicts for example with the bad block marker (BBM), + * so we shift all OOB data including the BBM with 4 byte positions. + * As a consequence the OOB size available to the MTD framework is + * also reduced with 4 bytes. + * + * PA0 PA1 PA2 PA3 | BBM OOB1 OOB2 OOB3 | ... + * + * If a NAND is not a boot medium or the page is not a boot block, + * the first 4 bytes are left untouched by writing 0xFF to them. + * + * 0xFF 0xFF 0xFF 0xFF | BBM OOB1 OOB2 OOB3 | ... + * + * The code here just swaps the first 4 bytes with the last + * 4 bytes without losing any data. + * + * The chip->oob_poi data layout: + * + * BBM OOB1 OOB2 OOB3 |......| PA0 PA1 PA2 PA3 + * + * Configure the ECC algorithm supported by the boot ROM. + */ + if ((page < (pages_per_blk * rknand->boot_blks)) && + (chip->options & NAND_IS_BOOT_MEDIUM)) { + boot_rom_mode = 1; + if (rknand->boot_ecc != ecc->strength) + rk_nfc_hw_ecc_setup(chip, rknand->boot_ecc); + } + + for (i = 0; i < ecc->steps; i++) { + if (!i) + oob = chip->oob_poi + (ecc->steps - 1) * NFC_SYS_DATA_SIZE; + else + oob = chip->oob_poi + (i - 1) * NFC_SYS_DATA_SIZE; + + tmp = oob[0] | oob[1] << 8 | oob[2] << 16 | oob[3] << 24; + + if (nfc->cfg->type == NFC_V9) + nfc->oob_buf[i] = tmp; + else + nfc->oob_buf[i * (oob_step / 4)] = tmp; + } + + 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); + + rk_nfc_xfer_start(nfc, NFC_WRITE, ecc->steps, dma_data, + dma_oob); + ret = wait_for_completion_timeout(&nfc->done, + msecs_to_jiffies(100)); + if (!ret) + dev_warn(nfc->dev, "write: wait dma done timeout.\n"); + /* + * Whether the DMA transfer is completed or not. The driver + * needs to check the NFC`s status register to see if the data + * transfer was completed. + */ + ret = rk_nfc_wait_for_xfer_done(nfc); + + dma_unmap_single(nfc->dev, dma_data, mtd->writesize, + DMA_TO_DEVICE); + dma_unmap_single(nfc->dev, dma_oob, ecc->steps * oob_step, + DMA_TO_DEVICE); + + if (boot_rom_mode && rknand->boot_ecc != ecc->strength) + rk_nfc_hw_ecc_setup(chip, ecc->strength); + + if (ret) { + dev_err(nfc->dev, "write: wait transfer done timeout.\n"); + return -ETIMEDOUT; + } + + return nand_prog_page_end_op(chip); +} + +static int rk_nfc_write_oob(struct nand_chip *chip, int page) +{ + return rk_nfc_write_page_hwecc(chip, NULL, 1, page); +} + +static int rk_nfc_read_page_raw(struct nand_chip *chip, u8 *buf, int oob_on, + int page) +{ + struct rk_nfc_nand_chip *rknand = rk_nfc_to_rknand(chip); + struct rk_nfc *nfc = nand_get_controller_data(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int i, pages_per_blk; + + pages_per_blk = mtd->erasesize / mtd->writesize; + if ((chip->options & NAND_IS_BOOT_MEDIUM) && + (page < (pages_per_blk * rknand->boot_blks)) && + rknand->boot_ecc != ecc->strength) { + /* + * There's currently no method to notify the MTD framework that + * a different ECC strength is in use for the boot blocks. + */ + return -EIO; + } + + nand_read_page_op(chip, page, 0, NULL, 0); + rk_nfc_read_buf(nfc, nfc->page_buf, mtd->writesize + mtd->oobsize); + for (i = 0; i < ecc->steps; i++) { + /* + * The first four bytes of OOB are reserved for the + * boot ROM. In some debugging cases, such as with a read, + * erase and write back test, these 4 bytes also must be + * saved somewhere, otherwise this information will be + * lost during a write back. + */ + if (!i) + memcpy(rk_nfc_buf_to_oob_ptr(chip, ecc->steps - 1), + rk_nfc_oob_ptr(chip, i), + NFC_SYS_DATA_SIZE); + else + memcpy(rk_nfc_buf_to_oob_ptr(chip, i - 1), + rk_nfc_oob_ptr(chip, i), + NFC_SYS_DATA_SIZE); + + /* Copy ECC data from the NFC buffer. */ + memcpy(rk_nfc_buf_to_oob_ecc_ptr(chip, i), + rk_nfc_oob_ptr(chip, i) + NFC_SYS_DATA_SIZE, + ecc->bytes); + + /* Copy data from the NFC buffer. */ + if (buf) + memcpy(rk_nfc_buf_to_data_ptr(chip, buf, i), + rk_nfc_data_ptr(chip, i), + ecc->size); + } + + return 0; +} + +static int rk_nfc_read_page_hwecc(struct nand_chip *chip, u8 *buf, int oob_on, + int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct rk_nfc *nfc = nand_get_controller_data(chip); + struct rk_nfc_nand_chip *rknand = rk_nfc_to_rknand(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int oob_step = (ecc->bytes > 60) ? NFC_MAX_OOB_PER_STEP : + NFC_MIN_OOB_PER_STEP; + int pages_per_blk = mtd->erasesize / mtd->writesize; + dma_addr_t dma_data, dma_oob; + int ret = 0, i, cnt, boot_rom_mode = 0; + int max_bitflips = 0, bch_st, ecc_fail = 0; + u8 *oob; + u32 tmp; + + nand_read_page_op(chip, page, 0, NULL, 0); + + 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) + * are used by the boot ROM. + * Configure the ECC algorithm supported by the boot ROM. + */ + if ((page < (pages_per_blk * rknand->boot_blks)) && + (chip->options & NAND_IS_BOOT_MEDIUM)) { + boot_rom_mode = 1; + if (rknand->boot_ecc != ecc->strength) + rk_nfc_hw_ecc_setup(chip, rknand->boot_ecc); + } + + reinit_completion(&nfc->done); + writel(INT_DMA, nfc->regs + nfc->cfg->int_en_off); + rk_nfc_xfer_start(nfc, NFC_READ, ecc->steps, dma_data, + dma_oob); + ret = wait_for_completion_timeout(&nfc->done, + msecs_to_jiffies(100)); + if (!ret) + dev_warn(nfc->dev, "read: wait dma done timeout.\n"); + /* + * Whether the DMA transfer is completed or not. The driver + * needs to check the NFC`s status register to see if the data + * transfer was completed. + */ + ret = rk_nfc_wait_for_xfer_done(nfc); + + dma_unmap_single(nfc->dev, dma_data, mtd->writesize, + DMA_FROM_DEVICE); + dma_unmap_single(nfc->dev, dma_oob, ecc->steps * oob_step, + DMA_FROM_DEVICE); + + if (ret) { + ret = -ETIMEDOUT; + dev_err(nfc->dev, "read: wait transfer done timeout.\n"); + goto timeout_err; + } + + for (i = 0; i < ecc->steps; i++) { + if (!i) + oob = chip->oob_poi + (ecc->steps - 1) * NFC_SYS_DATA_SIZE; + else + oob = chip->oob_poi + (i - 1) * NFC_SYS_DATA_SIZE; + + if (nfc->cfg->type == NFC_V9) + tmp = nfc->oob_buf[i]; + else + tmp = nfc->oob_buf[i * (oob_step / 4)]; + + *oob++ = (u8)tmp; + *oob++ = (u8)(tmp >> 8); + *oob++ = (u8)(tmp >> 16); + *oob++ = (u8)(tmp >> 24); + } + + for (i = 0; i < (ecc->steps / 2); i++) { + bch_st = readl_relaxed(nfc->regs + + nfc->cfg->bch_st_off + i * 4); + if (bch_st & BIT(nfc->cfg->ecc0.err_flag_bit) || + bch_st & BIT(nfc->cfg->ecc1.err_flag_bit)) { + mtd->ecc_stats.failed++; + ecc_fail = 1; + } else { + cnt = ECC_ERR_CNT(bch_st, nfc->cfg->ecc0); + mtd->ecc_stats.corrected += cnt; + max_bitflips = max_t(u32, max_bitflips, cnt); + + cnt = ECC_ERR_CNT(bch_st, nfc->cfg->ecc1); + mtd->ecc_stats.corrected += cnt; + max_bitflips = max_t(u32, max_bitflips, cnt); + } + } + + if (buf) + memcpy(buf, nfc->page_buf, mtd->writesize); + +timeout_err: + if (boot_rom_mode && rknand->boot_ecc != ecc->strength) + rk_nfc_hw_ecc_setup(chip, ecc->strength); + + if (ret) + return ret; + + if (ecc_fail) { + dev_err(nfc->dev, "read page: %x ecc error!\n", page); + return 0; + } + + return max_bitflips; +} + +static int rk_nfc_read_oob(struct nand_chip *chip, int page) +{ + return rk_nfc_read_page_hwecc(chip, NULL, 1, page); +} + +static inline void rk_nfc_hw_init(struct rk_nfc *nfc) +{ + /* Disable flash wp. */ + writel(FMCTL_WP, nfc->regs + NFC_FMCTL); + /* Config default timing 40ns at 150 Mhz NFC clock. */ + writel(0x1081, nfc->regs + NFC_FMWAIT); + nfc->cur_timing = 0x1081; + /* Disable randomizer and DMA. */ + writel(0, nfc->regs + nfc->cfg->randmz_off); + writel(0, nfc->regs + nfc->cfg->dma_cfg_off); + writel(FLCTL_RST, nfc->regs + nfc->cfg->flctl_off); +} + +static irqreturn_t rk_nfc_irq(int irq, void *id) +{ + struct rk_nfc *nfc = id; + u32 sta, ien; + + sta = readl_relaxed(nfc->regs + nfc->cfg->int_st_off); + ien = readl_relaxed(nfc->regs + nfc->cfg->int_en_off); + + if (!(sta & ien)) + return IRQ_NONE; + + writel(sta, nfc->regs + nfc->cfg->int_clr_off); + writel(~sta & ien, nfc->regs + nfc->cfg->int_en_off); + + complete(&nfc->done); + + return IRQ_HANDLED; +} + +static int rk_nfc_enable_clks(struct device *dev, struct rk_nfc *nfc) +{ + int ret; + + if (!IS_ERR(nfc->nfc_clk)) { + ret = clk_prepare_enable(nfc->nfc_clk); + if (ret) { + dev_err(dev, "failed to enable NFC clk\n"); + return ret; + } + } + + ret = clk_prepare_enable(nfc->ahb_clk); + if (ret) { + dev_err(dev, "failed to enable ahb clk\n"); + clk_disable_unprepare(nfc->nfc_clk); + return ret; + } + + return 0; +} + +static void rk_nfc_disable_clks(struct rk_nfc *nfc) +{ + clk_disable_unprepare(nfc->nfc_clk); + clk_disable_unprepare(nfc->ahb_clk); +} + +static int rk_nfc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oob_region) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct rk_nfc_nand_chip *rknand = rk_nfc_to_rknand(chip); + + if (section) + return -ERANGE; + + oob_region->length = rknand->metadata_size - NFC_SYS_DATA_SIZE - 2; + oob_region->offset = 2; + + return 0; +} + +static int rk_nfc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oob_region) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct rk_nfc_nand_chip *rknand = rk_nfc_to_rknand(chip); + + if (section) + return -ERANGE; + + oob_region->length = mtd->oobsize - rknand->metadata_size; + oob_region->offset = rknand->metadata_size; + + return 0; +} + +static const struct mtd_ooblayout_ops rk_nfc_ooblayout_ops = { + .free = rk_nfc_ooblayout_free, + .ecc = rk_nfc_ooblayout_ecc, +}; + +static int rk_nfc_ecc_init(struct device *dev, struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct rk_nfc *nfc = nand_get_controller_data(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + const u8 *strengths = nfc->cfg->ecc_strengths; + u8 max_strength, nfc_max_strength; + int i; + + nfc_max_strength = nfc->cfg->ecc_strengths[0]; + /* If optional dt settings not present. */ + if (!ecc->size || !ecc->strength || + ecc->strength > nfc_max_strength) { + chip->ecc.size = 1024; + ecc->steps = mtd->writesize / ecc->size; + + /* + * HW ECC always requests the number of ECC bytes per 1024 byte + * blocks. The first 4 OOB bytes are reserved for sys data. + */ + max_strength = ((mtd->oobsize / ecc->steps) - 4) * 8 / + fls(8 * 1024); + if (max_strength > nfc_max_strength) + max_strength = nfc_max_strength; + + for (i = 0; i < 4; i++) { + if (max_strength >= strengths[i]) + break; + } + + if (i >= 4) { + dev_err(nfc->dev, "unsupported ECC strength\n"); + return -EOPNOTSUPP; + } + + ecc->strength = strengths[i]; + } + ecc->steps = mtd->writesize / ecc->size; + ecc->bytes = DIV_ROUND_UP(ecc->strength * fls(8 * chip->ecc.size), 8); + + return 0; +} + +static int rk_nfc_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct device *dev = mtd->dev.parent; + struct rk_nfc *nfc = nand_get_controller_data(chip); + struct rk_nfc_nand_chip *rknand = rk_nfc_to_rknand(chip); + struct nand_ecc_ctrl *ecc = &chip->ecc; + int new_page_len, new_oob_len; + void *buf; + int ret; + + if (chip->options & NAND_BUSWIDTH_16) { + dev_err(dev, "16 bits bus width not supported"); + return -EINVAL; + } + + if (ecc->engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return 0; + + ret = rk_nfc_ecc_init(dev, mtd); + if (ret) + return ret; + + rknand->metadata_size = NFC_SYS_DATA_SIZE * ecc->steps; + + if (rknand->metadata_size < NFC_SYS_DATA_SIZE + 2) { + dev_err(dev, + "driver needs at least %d bytes of meta data\n", + NFC_SYS_DATA_SIZE + 2); + return -EIO; + } + + /* Check buffer first, avoid duplicate alloc buffer. */ + new_page_len = mtd->writesize + mtd->oobsize; + if (nfc->page_buf && new_page_len > nfc->page_buf_size) { + buf = krealloc(nfc->page_buf, new_page_len, + GFP_KERNEL | GFP_DMA); + if (!buf) + return -ENOMEM; + nfc->page_buf = buf; + nfc->page_buf_size = new_page_len; + } + + new_oob_len = ecc->steps * NFC_MAX_OOB_PER_STEP; + if (nfc->oob_buf && new_oob_len > nfc->oob_buf_size) { + buf = krealloc(nfc->oob_buf, new_oob_len, + GFP_KERNEL | GFP_DMA); + if (!buf) { + kfree(nfc->page_buf); + nfc->page_buf = NULL; + return -ENOMEM; + } + nfc->oob_buf = buf; + nfc->oob_buf_size = new_oob_len; + } + + if (!nfc->page_buf) { + nfc->page_buf = kzalloc(new_page_len, GFP_KERNEL | GFP_DMA); + if (!nfc->page_buf) + return -ENOMEM; + nfc->page_buf_size = new_page_len; + } + + if (!nfc->oob_buf) { + nfc->oob_buf = kzalloc(new_oob_len, GFP_KERNEL | GFP_DMA); + if (!nfc->oob_buf) { + kfree(nfc->page_buf); + nfc->page_buf = NULL; + return -ENOMEM; + } + nfc->oob_buf_size = new_oob_len; + } + + chip->ecc.write_page_raw = rk_nfc_write_page_raw; + chip->ecc.write_page = rk_nfc_write_page_hwecc; + chip->ecc.write_oob = rk_nfc_write_oob; + + chip->ecc.read_page_raw = rk_nfc_read_page_raw; + chip->ecc.read_page = rk_nfc_read_page_hwecc; + chip->ecc.read_oob = rk_nfc_read_oob; + + return 0; +} + +static const struct nand_controller_ops rk_nfc_controller_ops = { + .attach_chip = rk_nfc_attach_chip, + .exec_op = rk_nfc_exec_op, + .setup_interface = rk_nfc_setup_interface, +}; + +static int rk_nfc_nand_chip_init(struct device *dev, struct rk_nfc *nfc, + struct device_node *np) +{ + struct rk_nfc_nand_chip *rknand; + struct nand_chip *chip; + struct mtd_info *mtd; + int nsels; + u32 tmp; + int ret; + int i; + + if (!of_get_property(np, "reg", &nsels)) + return -ENODEV; + nsels /= sizeof(u32); + if (!nsels || nsels > NFC_MAX_NSELS) { + dev_err(dev, "invalid reg property size %d\n", nsels); + return -EINVAL; + } + + rknand = devm_kzalloc(dev, struct_size(rknand, sels, nsels), + GFP_KERNEL); + if (!rknand) + return -ENOMEM; + + rknand->nsels = nsels; + for (i = 0; i < nsels; i++) { + ret = of_property_read_u32_index(np, "reg", i, &tmp); + if (ret) { + dev_err(dev, "reg property failure : %d\n", ret); + return ret; + } + + if (tmp >= NFC_MAX_NSELS) { + dev_err(dev, "invalid CS: %u\n", tmp); + return -EINVAL; + } + + if (test_and_set_bit(tmp, &nfc->assigned_cs)) { + dev_err(dev, "CS %u already assigned\n", tmp); + return -EINVAL; + } + + rknand->sels[i] = tmp; + } + + chip = &rknand->chip; + chip->controller = &nfc->controller; + + nand_set_flash_node(chip, np); + + nand_set_controller_data(chip, nfc); + + chip->options |= NAND_USES_DMA | NAND_NO_SUBPAGE_WRITE; + chip->bbt_options = NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB; + + /* Set default mode in case dt entry is missing. */ + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + + mtd = nand_to_mtd(chip); + mtd->owner = THIS_MODULE; + mtd->dev.parent = dev; + + if (!mtd->name) { + dev_err(nfc->dev, "NAND label property is mandatory\n"); + return -EINVAL; + } + + mtd_set_ooblayout(mtd, &rk_nfc_ooblayout_ops); + rk_nfc_hw_init(nfc); + ret = nand_scan(chip, nsels); + if (ret) + return ret; + + if (chip->options & NAND_IS_BOOT_MEDIUM) { + ret = of_property_read_u32(np, "rockchip,boot-blks", &tmp); + rknand->boot_blks = ret ? 0 : tmp; + + ret = of_property_read_u32(np, "rockchip,boot-ecc-strength", + &tmp); + rknand->boot_ecc = ret ? chip->ecc.strength : tmp; + } + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(dev, "MTD parse partition error\n"); + nand_cleanup(chip); + return ret; + } + + list_add_tail(&rknand->node, &nfc->chips); + + return 0; +} + +static void rk_nfc_chips_cleanup(struct rk_nfc *nfc) +{ + struct rk_nfc_nand_chip *rknand, *tmp; + struct nand_chip *chip; + int ret; + + list_for_each_entry_safe(rknand, tmp, &nfc->chips, node) { + chip = &rknand->chip; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + list_del(&rknand->node); + } +} + +static int rk_nfc_nand_chips_init(struct device *dev, struct rk_nfc *nfc) +{ + struct device_node *np = dev->of_node; + int nchips = of_get_child_count(np); + int ret; + + if (!nchips || nchips > NFC_MAX_NSELS) { + dev_err(nfc->dev, "incorrect number of NAND chips (%d)\n", + nchips); + return -EINVAL; + } + + for_each_child_of_node_scoped(np, nand_np) { + ret = rk_nfc_nand_chip_init(dev, nfc, nand_np); + if (ret) { + rk_nfc_chips_cleanup(nfc); + return ret; + } + } + + return 0; +} + +static struct nfc_cfg nfc_v6_cfg = { + .type = NFC_V6, + .ecc_strengths = {60, 40, 24, 16}, + .ecc_cfgs = { + 0x00040011, 0x00040001, 0x00000011, 0x00000001, + }, + .flctl_off = 0x08, + .bchctl_off = 0x0C, + .dma_cfg_off = 0x10, + .dma_data_buf_off = 0x14, + .dma_oob_buf_off = 0x18, + .dma_st_off = 0x1C, + .bch_st_off = 0x20, + .randmz_off = 0x150, + .int_en_off = 0x16C, + .int_clr_off = 0x170, + .int_st_off = 0x174, + .oob0_off = 0x200, + .oob1_off = 0x230, + .ecc0 = { + .err_flag_bit = 2, + .low = 3, + .low_mask = 0x1F, + .low_bn = 5, + .high = 27, + .high_mask = 0x1, + }, + .ecc1 = { + .err_flag_bit = 15, + .low = 16, + .low_mask = 0x1F, + .low_bn = 5, + .high = 29, + .high_mask = 0x1, + }, +}; + +static struct nfc_cfg nfc_v8_cfg = { + .type = NFC_V8, + .ecc_strengths = {16, 16, 16, 16}, + .ecc_cfgs = { + 0x00000001, 0x00000001, 0x00000001, 0x00000001, + }, + .flctl_off = 0x08, + .bchctl_off = 0x0C, + .dma_cfg_off = 0x10, + .dma_data_buf_off = 0x14, + .dma_oob_buf_off = 0x18, + .dma_st_off = 0x1C, + .bch_st_off = 0x20, + .randmz_off = 0x150, + .int_en_off = 0x16C, + .int_clr_off = 0x170, + .int_st_off = 0x174, + .oob0_off = 0x200, + .oob1_off = 0x230, + .ecc0 = { + .err_flag_bit = 2, + .low = 3, + .low_mask = 0x1F, + .low_bn = 5, + .high = 27, + .high_mask = 0x1, + }, + .ecc1 = { + .err_flag_bit = 15, + .low = 16, + .low_mask = 0x1F, + .low_bn = 5, + .high = 29, + .high_mask = 0x1, + }, +}; + +static struct nfc_cfg nfc_v9_cfg = { + .type = NFC_V9, + .ecc_strengths = {70, 60, 40, 16}, + .ecc_cfgs = { + 0x00000001, 0x06000001, 0x04000001, 0x02000001, + }, + .flctl_off = 0x10, + .bchctl_off = 0x20, + .dma_cfg_off = 0x30, + .dma_data_buf_off = 0x34, + .dma_oob_buf_off = 0x38, + .dma_st_off = 0x3C, + .bch_st_off = 0x150, + .randmz_off = 0x208, + .int_en_off = 0x120, + .int_clr_off = 0x124, + .int_st_off = 0x128, + .oob0_off = 0x200, + .oob1_off = 0x204, + .ecc0 = { + .err_flag_bit = 2, + .low = 3, + .low_mask = 0x7F, + .low_bn = 7, + .high = 0, + .high_mask = 0x0, + }, + .ecc1 = { + .err_flag_bit = 18, + .low = 19, + .low_mask = 0x7F, + .low_bn = 7, + .high = 0, + .high_mask = 0x0, + }, +}; + +static const struct of_device_id rk_nfc_id_table[] = { + { + .compatible = "rockchip,px30-nfc", + .data = &nfc_v9_cfg + }, + { + .compatible = "rockchip,rk2928-nfc", + .data = &nfc_v6_cfg + }, + { + .compatible = "rockchip,rv1108-nfc", + .data = &nfc_v8_cfg + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rk_nfc_id_table); + +static int rk_nfc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct rk_nfc *nfc; + int ret, irq; + + nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nand_controller_init(&nfc->controller); + INIT_LIST_HEAD(&nfc->chips); + nfc->controller.ops = &rk_nfc_controller_ops; + + nfc->cfg = of_device_get_match_data(dev); + nfc->dev = dev; + + init_completion(&nfc->done); + + nfc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(nfc->regs)) { + ret = PTR_ERR(nfc->regs); + goto release_nfc; + } + + nfc->nfc_clk = devm_clk_get(dev, "nfc"); + if (IS_ERR(nfc->nfc_clk)) { + dev_dbg(dev, "no NFC clk\n"); + /* Some earlier models, such as rk3066, have no NFC clk. */ + } + + nfc->ahb_clk = devm_clk_get(dev, "ahb"); + if (IS_ERR(nfc->ahb_clk)) { + dev_err(dev, "no ahb clk\n"); + ret = PTR_ERR(nfc->ahb_clk); + goto release_nfc; + } + + ret = rk_nfc_enable_clks(dev, nfc); + if (ret) + goto release_nfc; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = -EINVAL; + goto clk_disable; + } + + writel(0, nfc->regs + nfc->cfg->int_en_off); + ret = devm_request_irq(dev, irq, rk_nfc_irq, 0x0, "rk-nand", nfc); + if (ret) { + dev_err(dev, "failed to request NFC irq\n"); + goto clk_disable; + } + + platform_set_drvdata(pdev, nfc); + + ret = rk_nfc_nand_chips_init(dev, nfc); + if (ret) { + dev_err(dev, "failed to init NAND chips\n"); + goto clk_disable; + } + return 0; + +clk_disable: + rk_nfc_disable_clks(nfc); +release_nfc: + return ret; +} + +static void rk_nfc_remove(struct platform_device *pdev) +{ + struct rk_nfc *nfc = platform_get_drvdata(pdev); + + kfree(nfc->page_buf); + kfree(nfc->oob_buf); + rk_nfc_chips_cleanup(nfc); + rk_nfc_disable_clks(nfc); +} + +static int __maybe_unused rk_nfc_suspend(struct device *dev) +{ + struct rk_nfc *nfc = dev_get_drvdata(dev); + + rk_nfc_disable_clks(nfc); + + return 0; +} + +static int __maybe_unused rk_nfc_resume(struct device *dev) +{ + struct rk_nfc *nfc = dev_get_drvdata(dev); + struct rk_nfc_nand_chip *rknand; + struct nand_chip *chip; + int ret; + u32 i; + + ret = rk_nfc_enable_clks(dev, nfc); + if (ret) + return ret; + + /* Reset NAND chip if VCC was powered off. */ + list_for_each_entry(rknand, &nfc->chips, node) { + chip = &rknand->chip; + for (i = 0; i < rknand->nsels; i++) + nand_reset(chip, i); + } + + return 0; +} + +static const struct dev_pm_ops rk_nfc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(rk_nfc_suspend, rk_nfc_resume) +}; + +static struct platform_driver rk_nfc_driver = { + .probe = rk_nfc_probe, + .remove = rk_nfc_remove, + .driver = { + .name = "rockchip-nfc", + .of_match_table = rk_nfc_id_table, + .pm = &rk_nfc_pm_ops, + }, +}; + +module_platform_driver(rk_nfc_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_AUTHOR("Yifeng Zhao "); +MODULE_DESCRIPTION("Rockchip Nand Flash Controller Driver"); diff --git a/drivers/mtd/nand/raw/sh_flctl.c b/drivers/mtd/nand/raw/sh_flctl.c new file mode 100644 index 000000000..97f733e48 --- /dev/null +++ b/drivers/mtd/nand/raw/sh_flctl.c @@ -0,0 +1,1231 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SuperH FLCTL nand controller + * + * Copyright (c) 2008 Renesas Solutions Corp. + * Copyright (c) 2008 Atom Create Engineering Co., Ltd. + * + * Based on fsl_elbc_nand.c, Copyright (c) 2006-2007 Freescale Semiconductor + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +static int flctl_4secc_ooblayout_sp_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section) + return -ERANGE; + + oobregion->offset = 0; + oobregion->length = chip->ecc.bytes; + + return 0; +} + +static int flctl_4secc_ooblayout_sp_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section) + return -ERANGE; + + oobregion->offset = 12; + oobregion->length = 4; + + return 0; +} + +static const struct mtd_ooblayout_ops flctl_4secc_oob_smallpage_ops = { + .ecc = flctl_4secc_ooblayout_sp_ecc, + .free = flctl_4secc_ooblayout_sp_free, +}; + +static int flctl_4secc_ooblayout_lp_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section >= chip->ecc.steps) + return -ERANGE; + + oobregion->offset = (section * 16) + 6; + oobregion->length = chip->ecc.bytes; + + return 0; +} + +static int flctl_4secc_ooblayout_lp_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + + if (section >= chip->ecc.steps) + return -ERANGE; + + oobregion->offset = section * 16; + oobregion->length = 6; + + if (!section) { + oobregion->offset += 2; + oobregion->length -= 2; + } + + return 0; +} + +static const struct mtd_ooblayout_ops flctl_4secc_oob_largepage_ops = { + .ecc = flctl_4secc_ooblayout_lp_ecc, + .free = flctl_4secc_ooblayout_lp_free, +}; + +static uint8_t scan_ff_pattern[] = { 0xff, 0xff }; + +static struct nand_bbt_descr flctl_4secc_smallpage = { + .offs = 11, + .len = 1, + .pattern = scan_ff_pattern, +}; + +static struct nand_bbt_descr flctl_4secc_largepage = { + .offs = 0, + .len = 2, + .pattern = scan_ff_pattern, +}; + +static void empty_fifo(struct sh_flctl *flctl) +{ + writel(flctl->flintdmacr_base | AC1CLR | AC0CLR, FLINTDMACR(flctl)); + writel(flctl->flintdmacr_base, FLINTDMACR(flctl)); +} + +static void start_translation(struct sh_flctl *flctl) +{ + writeb(TRSTRT, FLTRCR(flctl)); +} + +static void timeout_error(struct sh_flctl *flctl, const char *str) +{ + dev_err(&flctl->pdev->dev, "Timeout occurred in %s\n", str); +} + +static void wait_completion(struct sh_flctl *flctl) +{ + uint32_t timeout = LOOP_TIMEOUT_MAX; + + while (timeout--) { + if (readb(FLTRCR(flctl)) & TREND) { + writeb(0x0, FLTRCR(flctl)); + return; + } + udelay(1); + } + + timeout_error(flctl, __func__); + writeb(0x0, FLTRCR(flctl)); +} + +static void flctl_dma_complete(void *param) +{ + struct sh_flctl *flctl = param; + + complete(&flctl->dma_complete); +} + +static void flctl_release_dma(struct sh_flctl *flctl) +{ + if (flctl->chan_fifo0_rx) { + dma_release_channel(flctl->chan_fifo0_rx); + flctl->chan_fifo0_rx = NULL; + } + if (flctl->chan_fifo0_tx) { + dma_release_channel(flctl->chan_fifo0_tx); + flctl->chan_fifo0_tx = NULL; + } +} + +static void flctl_setup_dma(struct sh_flctl *flctl) +{ + dma_cap_mask_t mask; + struct dma_slave_config cfg; + struct platform_device *pdev = flctl->pdev; + struct sh_flctl_platform_data *pdata = dev_get_platdata(&pdev->dev); + int ret; + + if (!pdata) + return; + + if (pdata->slave_id_fifo0_tx <= 0 || pdata->slave_id_fifo0_rx <= 0) + return; + + /* We can only either use DMA for both Tx and Rx or not use it at all */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + flctl->chan_fifo0_tx = dma_request_channel(mask, shdma_chan_filter, + (void *)(uintptr_t)pdata->slave_id_fifo0_tx); + dev_dbg(&pdev->dev, "%s: TX: got channel %p\n", __func__, + flctl->chan_fifo0_tx); + + if (!flctl->chan_fifo0_tx) + return; + + memset(&cfg, 0, sizeof(cfg)); + cfg.direction = DMA_MEM_TO_DEV; + cfg.dst_addr = flctl->fifo; + cfg.src_addr = 0; + ret = dmaengine_slave_config(flctl->chan_fifo0_tx, &cfg); + if (ret < 0) + goto err; + + flctl->chan_fifo0_rx = dma_request_channel(mask, shdma_chan_filter, + (void *)(uintptr_t)pdata->slave_id_fifo0_rx); + dev_dbg(&pdev->dev, "%s: RX: got channel %p\n", __func__, + flctl->chan_fifo0_rx); + + if (!flctl->chan_fifo0_rx) + goto err; + + cfg.direction = DMA_DEV_TO_MEM; + cfg.dst_addr = 0; + cfg.src_addr = flctl->fifo; + ret = dmaengine_slave_config(flctl->chan_fifo0_rx, &cfg); + if (ret < 0) + goto err; + + init_completion(&flctl->dma_complete); + + return; + +err: + flctl_release_dma(flctl); +} + +static void set_addr(struct mtd_info *mtd, int column, int page_addr) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + uint32_t addr = 0; + + if (column == -1) { + addr = page_addr; /* ERASE1 */ + } else if (page_addr != -1) { + /* SEQIN, READ0, etc.. */ + if (flctl->chip.options & NAND_BUSWIDTH_16) + column >>= 1; + if (flctl->page_size) { + addr = column & 0x0FFF; + addr |= (page_addr & 0xff) << 16; + addr |= ((page_addr >> 8) & 0xff) << 24; + /* big than 128MB */ + if (flctl->rw_ADRCNT == ADRCNT2_E) { + uint32_t addr2; + addr2 = (page_addr >> 16) & 0xff; + writel(addr2, FLADR2(flctl)); + } + } else { + addr = column; + addr |= (page_addr & 0xff) << 8; + addr |= ((page_addr >> 8) & 0xff) << 16; + addr |= ((page_addr >> 16) & 0xff) << 24; + } + } + writel(addr, FLADR(flctl)); +} + +static void wait_rfifo_ready(struct sh_flctl *flctl) +{ + uint32_t timeout = LOOP_TIMEOUT_MAX; + + while (timeout--) { + uint32_t val; + /* check FIFO */ + val = readl(FLDTCNTR(flctl)) >> 16; + if (val & 0xFF) + return; + udelay(1); + } + timeout_error(flctl, __func__); +} + +static void wait_wfifo_ready(struct sh_flctl *flctl) +{ + uint32_t len, timeout = LOOP_TIMEOUT_MAX; + + while (timeout--) { + /* check FIFO */ + len = (readl(FLDTCNTR(flctl)) >> 16) & 0xFF; + if (len >= 4) + return; + udelay(1); + } + timeout_error(flctl, __func__); +} + +static enum flctl_ecc_res_t wait_recfifo_ready + (struct sh_flctl *flctl, int sector_number) +{ + uint32_t timeout = LOOP_TIMEOUT_MAX; + void __iomem *ecc_reg[4]; + int i; + int state = FL_SUCCESS; + uint32_t data, size; + + /* + * First this loops checks in FLDTCNTR if we are ready to read out the + * oob data. This is the case if either all went fine without errors or + * if the bottom part of the loop corrected the errors or marked them as + * uncorrectable and the controller is given time to push the data into + * the FIFO. + */ + while (timeout--) { + /* check if all is ok and we can read out the OOB */ + size = readl(FLDTCNTR(flctl)) >> 24; + if ((size & 0xFF) == 4) + return state; + + /* check if a correction code has been calculated */ + if (!(readl(FL4ECCCR(flctl)) & _4ECCEND)) { + /* + * either we wait for the fifo to be filled or a + * correction pattern is being generated + */ + udelay(1); + continue; + } + + /* check for an uncorrectable error */ + if (readl(FL4ECCCR(flctl)) & _4ECCFA) { + /* check if we face a non-empty page */ + for (i = 0; i < 512; i++) { + if (flctl->done_buff[i] != 0xff) { + state = FL_ERROR; /* can't correct */ + break; + } + } + + if (state == FL_SUCCESS) + dev_dbg(&flctl->pdev->dev, + "reading empty sector %d, ecc error ignored\n", + sector_number); + + writel(0, FL4ECCCR(flctl)); + continue; + } + + /* start error correction */ + ecc_reg[0] = FL4ECCRESULT0(flctl); + ecc_reg[1] = FL4ECCRESULT1(flctl); + ecc_reg[2] = FL4ECCRESULT2(flctl); + ecc_reg[3] = FL4ECCRESULT3(flctl); + + for (i = 0; i < 3; i++) { + uint8_t org; + unsigned int index; + + data = readl(ecc_reg[i]); + + if (flctl->page_size) + index = (512 * sector_number) + + (data >> 16); + else + index = data >> 16; + + org = flctl->done_buff[index]; + flctl->done_buff[index] = org ^ (data & 0xFF); + } + state = FL_REPAIRABLE; + writel(0, FL4ECCCR(flctl)); + } + + timeout_error(flctl, __func__); + return FL_TIMEOUT; /* timeout */ +} + +static void wait_wecfifo_ready(struct sh_flctl *flctl) +{ + uint32_t timeout = LOOP_TIMEOUT_MAX; + uint32_t len; + + while (timeout--) { + /* check FLECFIFO */ + len = (readl(FLDTCNTR(flctl)) >> 24) & 0xFF; + if (len >= 4) + return; + udelay(1); + } + timeout_error(flctl, __func__); +} + +static int flctl_dma_fifo0_transfer(struct sh_flctl *flctl, unsigned long *buf, + int len, enum dma_data_direction dir) +{ + struct dma_async_tx_descriptor *desc = NULL; + struct dma_chan *chan; + enum dma_transfer_direction tr_dir; + dma_addr_t dma_addr; + dma_cookie_t cookie; + uint32_t reg; + int ret = 0; + unsigned long time_left; + + if (dir == DMA_FROM_DEVICE) { + chan = flctl->chan_fifo0_rx; + tr_dir = DMA_DEV_TO_MEM; + } else { + chan = flctl->chan_fifo0_tx; + tr_dir = DMA_MEM_TO_DEV; + } + + dma_addr = dma_map_single(chan->device->dev, buf, len, dir); + + if (!dma_mapping_error(chan->device->dev, dma_addr)) + desc = dmaengine_prep_slave_single(chan, dma_addr, len, + tr_dir, DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + + if (desc) { + reg = readl(FLINTDMACR(flctl)); + reg |= DREQ0EN; + writel(reg, FLINTDMACR(flctl)); + + desc->callback = flctl_dma_complete; + desc->callback_param = flctl; + cookie = dmaengine_submit(desc); + if (dma_submit_error(cookie)) { + ret = dma_submit_error(cookie); + dev_warn(&flctl->pdev->dev, + "DMA submit failed, falling back to PIO\n"); + goto out; + } + + dma_async_issue_pending(chan); + } else { + /* DMA failed, fall back to PIO */ + flctl_release_dma(flctl); + dev_warn(&flctl->pdev->dev, + "DMA failed, falling back to PIO\n"); + ret = -EIO; + goto out; + } + + time_left = + wait_for_completion_timeout(&flctl->dma_complete, + msecs_to_jiffies(3000)); + + if (time_left == 0) { + dmaengine_terminate_all(chan); + dev_err(&flctl->pdev->dev, "wait_for_completion_timeout\n"); + ret = -ETIMEDOUT; + } + +out: + reg = readl(FLINTDMACR(flctl)); + reg &= ~DREQ0EN; + writel(reg, FLINTDMACR(flctl)); + + dma_unmap_single(chan->device->dev, dma_addr, len, dir); + + /* ret == 0 is success */ + return ret; +} + +static void read_datareg(struct sh_flctl *flctl, int offset) +{ + unsigned long data; + unsigned long *buf = (unsigned long *)&flctl->done_buff[offset]; + + wait_completion(flctl); + + data = readl(FLDATAR(flctl)); + *buf = le32_to_cpu(data); +} + +static void read_fiforeg(struct sh_flctl *flctl, int rlen, int offset) +{ + int i, len_4align; + unsigned long *buf = (unsigned long *)&flctl->done_buff[offset]; + + len_4align = (rlen + 3) / 4; + + /* initiate DMA transfer */ + if (flctl->chan_fifo0_rx && rlen >= 32 && + !flctl_dma_fifo0_transfer(flctl, buf, rlen, DMA_FROM_DEVICE)) + goto convert; /* DMA success */ + + /* do polling transfer */ + for (i = 0; i < len_4align; i++) { + wait_rfifo_ready(flctl); + buf[i] = readl(FLDTFIFO(flctl)); + } + +convert: + for (i = 0; i < len_4align; i++) + buf[i] = be32_to_cpu(buf[i]); +} + +static enum flctl_ecc_res_t read_ecfiforeg + (struct sh_flctl *flctl, uint8_t *buff, int sector) +{ + int i; + enum flctl_ecc_res_t res; + unsigned long *ecc_buf = (unsigned long *)buff; + + res = wait_recfifo_ready(flctl , sector); + + if (res != FL_ERROR) { + for (i = 0; i < 4; i++) { + ecc_buf[i] = readl(FLECFIFO(flctl)); + ecc_buf[i] = be32_to_cpu(ecc_buf[i]); + } + } + + return res; +} + +static void write_fiforeg(struct sh_flctl *flctl, int rlen, + unsigned int offset) +{ + int i, len_4align; + unsigned long *buf = (unsigned long *)&flctl->done_buff[offset]; + + len_4align = (rlen + 3) / 4; + for (i = 0; i < len_4align; i++) { + wait_wfifo_ready(flctl); + writel(cpu_to_be32(buf[i]), FLDTFIFO(flctl)); + } +} + +static void write_ec_fiforeg(struct sh_flctl *flctl, int rlen, + unsigned int offset) +{ + int i, len_4align; + unsigned long *buf = (unsigned long *)&flctl->done_buff[offset]; + + len_4align = (rlen + 3) / 4; + + for (i = 0; i < len_4align; i++) + buf[i] = cpu_to_be32(buf[i]); + + /* initiate DMA transfer */ + if (flctl->chan_fifo0_tx && rlen >= 32 && + !flctl_dma_fifo0_transfer(flctl, buf, rlen, DMA_TO_DEVICE)) + return; /* DMA success */ + + /* do polling transfer */ + for (i = 0; i < len_4align; i++) { + wait_wecfifo_ready(flctl); + writel(buf[i], FLECFIFO(flctl)); + } +} + +static void set_cmd_regs(struct mtd_info *mtd, uint32_t cmd, uint32_t flcmcdr_val) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + uint32_t flcmncr_val = flctl->flcmncr_base & ~SEL_16BIT; + uint32_t flcmdcr_val, addr_len_bytes = 0; + + /* Set SNAND bit if page size is 2048byte */ + if (flctl->page_size) + flcmncr_val |= SNAND_E; + else + flcmncr_val &= ~SNAND_E; + + /* default FLCMDCR val */ + flcmdcr_val = DOCMD1_E | DOADR_E; + + /* Set for FLCMDCR */ + switch (cmd) { + case NAND_CMD_ERASE1: + addr_len_bytes = flctl->erase_ADRCNT; + flcmdcr_val |= DOCMD2_E; + break; + case NAND_CMD_READ0: + case NAND_CMD_READOOB: + case NAND_CMD_RNDOUT: + addr_len_bytes = flctl->rw_ADRCNT; + flcmdcr_val |= CDSRC_E; + if (flctl->chip.options & NAND_BUSWIDTH_16) + flcmncr_val |= SEL_16BIT; + break; + case NAND_CMD_SEQIN: + /* This case is that cmd is READ0 or READ1 or READ00 */ + flcmdcr_val &= ~DOADR_E; /* ONLY execute 1st cmd */ + break; + case NAND_CMD_PAGEPROG: + addr_len_bytes = flctl->rw_ADRCNT; + flcmdcr_val |= DOCMD2_E | CDSRC_E | SELRW; + if (flctl->chip.options & NAND_BUSWIDTH_16) + flcmncr_val |= SEL_16BIT; + break; + case NAND_CMD_READID: + flcmncr_val &= ~SNAND_E; + flcmdcr_val |= CDSRC_E; + addr_len_bytes = ADRCNT_1; + break; + case NAND_CMD_STATUS: + case NAND_CMD_RESET: + flcmncr_val &= ~SNAND_E; + flcmdcr_val &= ~(DOADR_E | DOSR_E); + break; + default: + break; + } + + /* Set address bytes parameter */ + flcmdcr_val |= addr_len_bytes; + + /* Now actually write */ + writel(flcmncr_val, FLCMNCR(flctl)); + writel(flcmdcr_val, FLCMDCR(flctl)); + writel(flcmcdr_val, FLCMCDR(flctl)); +} + +static int flctl_read_page_hwecc(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + nand_read_page_op(chip, page, 0, buf, mtd->writesize); + if (oob_required) + chip->legacy.read_buf(chip, chip->oob_poi, mtd->oobsize); + return 0; +} + +static int flctl_write_page_hwecc(struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize); + chip->legacy.write_buf(chip, chip->oob_poi, mtd->oobsize); + return nand_prog_page_end_op(chip); +} + +static void execmd_read_page_sector(struct mtd_info *mtd, int page_addr) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + int sector, page_sectors; + enum flctl_ecc_res_t ecc_result; + + page_sectors = flctl->page_size ? 4 : 1; + + set_cmd_regs(mtd, NAND_CMD_READ0, + (NAND_CMD_READSTART << 8) | NAND_CMD_READ0); + + writel(readl(FLCMNCR(flctl)) | ACM_SACCES_MODE | _4ECCCORRECT, + FLCMNCR(flctl)); + writel(readl(FLCMDCR(flctl)) | page_sectors, FLCMDCR(flctl)); + writel(page_addr << 2, FLADR(flctl)); + + empty_fifo(flctl); + start_translation(flctl); + + for (sector = 0; sector < page_sectors; sector++) { + read_fiforeg(flctl, 512, 512 * sector); + + ecc_result = read_ecfiforeg(flctl, + &flctl->done_buff[mtd->writesize + 16 * sector], + sector); + + switch (ecc_result) { + case FL_REPAIRABLE: + dev_info(&flctl->pdev->dev, + "applied ecc on page 0x%x", page_addr); + mtd->ecc_stats.corrected++; + break; + case FL_ERROR: + dev_warn(&flctl->pdev->dev, + "page 0x%x contains corrupted data\n", + page_addr); + mtd->ecc_stats.failed++; + break; + default: + ; + } + } + + wait_completion(flctl); + + writel(readl(FLCMNCR(flctl)) & ~(ACM_SACCES_MODE | _4ECCCORRECT), + FLCMNCR(flctl)); +} + +static void execmd_read_oob(struct mtd_info *mtd, int page_addr) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + int page_sectors = flctl->page_size ? 4 : 1; + int i; + + set_cmd_regs(mtd, NAND_CMD_READ0, + (NAND_CMD_READSTART << 8) | NAND_CMD_READ0); + + empty_fifo(flctl); + + for (i = 0; i < page_sectors; i++) { + set_addr(mtd, (512 + 16) * i + 512 , page_addr); + writel(16, FLDTCNTR(flctl)); + + start_translation(flctl); + read_fiforeg(flctl, 16, 16 * i); + wait_completion(flctl); + } +} + +static void execmd_write_page_sector(struct mtd_info *mtd) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + int page_addr = flctl->seqin_page_addr; + int sector, page_sectors; + + page_sectors = flctl->page_size ? 4 : 1; + + set_cmd_regs(mtd, NAND_CMD_PAGEPROG, + (NAND_CMD_PAGEPROG << 8) | NAND_CMD_SEQIN); + + empty_fifo(flctl); + writel(readl(FLCMNCR(flctl)) | ACM_SACCES_MODE, FLCMNCR(flctl)); + writel(readl(FLCMDCR(flctl)) | page_sectors, FLCMDCR(flctl)); + writel(page_addr << 2, FLADR(flctl)); + start_translation(flctl); + + for (sector = 0; sector < page_sectors; sector++) { + write_fiforeg(flctl, 512, 512 * sector); + write_ec_fiforeg(flctl, 16, mtd->writesize + 16 * sector); + } + + wait_completion(flctl); + writel(readl(FLCMNCR(flctl)) & ~ACM_SACCES_MODE, FLCMNCR(flctl)); +} + +static void execmd_write_oob(struct mtd_info *mtd) +{ + struct sh_flctl *flctl = mtd_to_flctl(mtd); + int page_addr = flctl->seqin_page_addr; + int sector, page_sectors; + + page_sectors = flctl->page_size ? 4 : 1; + + set_cmd_regs(mtd, NAND_CMD_PAGEPROG, + (NAND_CMD_PAGEPROG << 8) | NAND_CMD_SEQIN); + + for (sector = 0; sector < page_sectors; sector++) { + empty_fifo(flctl); + set_addr(mtd, sector * 528 + 512, page_addr); + writel(16, FLDTCNTR(flctl)); /* set read size */ + + start_translation(flctl); + write_fiforeg(flctl, 16, 16 * sector); + wait_completion(flctl); + } +} + +static void flctl_cmdfunc(struct nand_chip *chip, unsigned int command, + int column, int page_addr) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct sh_flctl *flctl = mtd_to_flctl(mtd); + uint32_t read_cmd = 0; + + pm_runtime_get_sync(&flctl->pdev->dev); + + flctl->read_bytes = 0; + if (command != NAND_CMD_PAGEPROG) + flctl->index = 0; + + switch (command) { + case NAND_CMD_READ1: + case NAND_CMD_READ0: + if (flctl->hwecc) { + /* read page with hwecc */ + execmd_read_page_sector(mtd, page_addr); + break; + } + if (flctl->page_size) + set_cmd_regs(mtd, command, (NAND_CMD_READSTART << 8) + | command); + else + set_cmd_regs(mtd, command, command); + + set_addr(mtd, 0, page_addr); + + flctl->read_bytes = mtd->writesize + mtd->oobsize; + if (flctl->chip.options & NAND_BUSWIDTH_16) + column >>= 1; + flctl->index += column; + goto read_normal_exit; + + case NAND_CMD_READOOB: + if (flctl->hwecc) { + /* read page with hwecc */ + execmd_read_oob(mtd, page_addr); + break; + } + + if (flctl->page_size) { + set_cmd_regs(mtd, command, (NAND_CMD_READSTART << 8) + | NAND_CMD_READ0); + set_addr(mtd, mtd->writesize, page_addr); + } else { + set_cmd_regs(mtd, command, command); + set_addr(mtd, 0, page_addr); + } + flctl->read_bytes = mtd->oobsize; + goto read_normal_exit; + + case NAND_CMD_RNDOUT: + if (flctl->hwecc) + break; + + if (flctl->page_size) + set_cmd_regs(mtd, command, (NAND_CMD_RNDOUTSTART << 8) + | command); + else + set_cmd_regs(mtd, command, command); + + set_addr(mtd, column, 0); + + flctl->read_bytes = mtd->writesize + mtd->oobsize - column; + goto read_normal_exit; + + case NAND_CMD_READID: + set_cmd_regs(mtd, command, command); + + /* READID is always performed using an 8-bit bus */ + if (flctl->chip.options & NAND_BUSWIDTH_16) + column <<= 1; + set_addr(mtd, column, 0); + + flctl->read_bytes = 8; + writel(flctl->read_bytes, FLDTCNTR(flctl)); /* set read size */ + empty_fifo(flctl); + start_translation(flctl); + read_fiforeg(flctl, flctl->read_bytes, 0); + wait_completion(flctl); + break; + + case NAND_CMD_ERASE1: + flctl->erase1_page_addr = page_addr; + break; + + case NAND_CMD_ERASE2: + set_cmd_regs(mtd, NAND_CMD_ERASE1, + (command << 8) | NAND_CMD_ERASE1); + set_addr(mtd, -1, flctl->erase1_page_addr); + start_translation(flctl); + wait_completion(flctl); + break; + + case NAND_CMD_SEQIN: + if (!flctl->page_size) { + /* output read command */ + if (column >= mtd->writesize) { + column -= mtd->writesize; + read_cmd = NAND_CMD_READOOB; + } else if (column < 256) { + read_cmd = NAND_CMD_READ0; + } else { + column -= 256; + read_cmd = NAND_CMD_READ1; + } + } + flctl->seqin_column = column; + flctl->seqin_page_addr = page_addr; + flctl->seqin_read_cmd = read_cmd; + break; + + case NAND_CMD_PAGEPROG: + empty_fifo(flctl); + if (!flctl->page_size) { + set_cmd_regs(mtd, NAND_CMD_SEQIN, + flctl->seqin_read_cmd); + set_addr(mtd, -1, -1); + writel(0, FLDTCNTR(flctl)); /* set 0 size */ + start_translation(flctl); + wait_completion(flctl); + } + if (flctl->hwecc) { + /* write page with hwecc */ + if (flctl->seqin_column == mtd->writesize) + execmd_write_oob(mtd); + else if (!flctl->seqin_column) + execmd_write_page_sector(mtd); + else + pr_err("Invalid address !?\n"); + break; + } + set_cmd_regs(mtd, command, (command << 8) | NAND_CMD_SEQIN); + set_addr(mtd, flctl->seqin_column, flctl->seqin_page_addr); + writel(flctl->index, FLDTCNTR(flctl)); /* set write size */ + start_translation(flctl); + write_fiforeg(flctl, flctl->index, 0); + wait_completion(flctl); + break; + + case NAND_CMD_STATUS: + set_cmd_regs(mtd, command, command); + set_addr(mtd, -1, -1); + + flctl->read_bytes = 1; + writel(flctl->read_bytes, FLDTCNTR(flctl)); /* set read size */ + start_translation(flctl); + read_datareg(flctl, 0); /* read and end */ + break; + + case NAND_CMD_RESET: + set_cmd_regs(mtd, command, command); + set_addr(mtd, -1, -1); + + writel(0, FLDTCNTR(flctl)); /* set 0 size */ + start_translation(flctl); + wait_completion(flctl); + break; + + default: + break; + } + goto runtime_exit; + +read_normal_exit: + writel(flctl->read_bytes, FLDTCNTR(flctl)); /* set read size */ + empty_fifo(flctl); + start_translation(flctl); + read_fiforeg(flctl, flctl->read_bytes, 0); + wait_completion(flctl); +runtime_exit: + pm_runtime_put_sync(&flctl->pdev->dev); + return; +} + +static void flctl_select_chip(struct nand_chip *chip, int chipnr) +{ + struct sh_flctl *flctl = mtd_to_flctl(nand_to_mtd(chip)); + int ret; + + switch (chipnr) { + case -1: + flctl->flcmncr_base &= ~CE0_ENABLE; + + pm_runtime_get_sync(&flctl->pdev->dev); + writel(flctl->flcmncr_base, FLCMNCR(flctl)); + + if (flctl->qos_request) { + dev_pm_qos_remove_request(&flctl->pm_qos); + flctl->qos_request = 0; + } + + pm_runtime_put_sync(&flctl->pdev->dev); + break; + case 0: + flctl->flcmncr_base |= CE0_ENABLE; + + if (!flctl->qos_request) { + ret = dev_pm_qos_add_request(&flctl->pdev->dev, + &flctl->pm_qos, + DEV_PM_QOS_RESUME_LATENCY, + 100); + if (ret < 0) + dev_err(&flctl->pdev->dev, + "PM QoS request failed: %d\n", ret); + flctl->qos_request = 1; + } + + if (flctl->holden) { + pm_runtime_get_sync(&flctl->pdev->dev); + writel(HOLDEN, FLHOLDCR(flctl)); + pm_runtime_put_sync(&flctl->pdev->dev); + } + break; + default: + BUG(); + } +} + +static void flctl_write_buf(struct nand_chip *chip, const uint8_t *buf, int len) +{ + struct sh_flctl *flctl = mtd_to_flctl(nand_to_mtd(chip)); + + memcpy(&flctl->done_buff[flctl->index], buf, len); + flctl->index += len; +} + +static uint8_t flctl_read_byte(struct nand_chip *chip) +{ + struct sh_flctl *flctl = mtd_to_flctl(nand_to_mtd(chip)); + uint8_t data; + + data = flctl->done_buff[flctl->index]; + flctl->index++; + return data; +} + +static void flctl_read_buf(struct nand_chip *chip, uint8_t *buf, int len) +{ + struct sh_flctl *flctl = mtd_to_flctl(nand_to_mtd(chip)); + + memcpy(buf, &flctl->done_buff[flctl->index], len); + flctl->index += len; +} + +static int flctl_chip_attach_chip(struct nand_chip *chip) +{ + u64 targetsize = nanddev_target_size(&chip->base); + struct mtd_info *mtd = nand_to_mtd(chip); + struct sh_flctl *flctl = mtd_to_flctl(mtd); + + /* + * NAND_BUSWIDTH_16 may have been set by nand_scan_ident(). + * Add the SEL_16BIT flag in flctl->flcmncr_base. + */ + if (chip->options & NAND_BUSWIDTH_16) + flctl->flcmncr_base |= SEL_16BIT; + + if (mtd->writesize == 512) { + flctl->page_size = 0; + if (targetsize > (32 << 20)) { + /* big than 32MB */ + flctl->rw_ADRCNT = ADRCNT_4; + flctl->erase_ADRCNT = ADRCNT_3; + } else if (targetsize > (2 << 16)) { + /* big than 128KB */ + flctl->rw_ADRCNT = ADRCNT_3; + flctl->erase_ADRCNT = ADRCNT_2; + } else { + flctl->rw_ADRCNT = ADRCNT_2; + flctl->erase_ADRCNT = ADRCNT_1; + } + } else { + flctl->page_size = 1; + if (targetsize > (128 << 20)) { + /* big than 128MB */ + flctl->rw_ADRCNT = ADRCNT2_E; + flctl->erase_ADRCNT = ADRCNT_3; + } else if (targetsize > (8 << 16)) { + /* big than 512KB */ + flctl->rw_ADRCNT = ADRCNT_4; + flctl->erase_ADRCNT = ADRCNT_2; + } else { + flctl->rw_ADRCNT = ADRCNT_3; + flctl->erase_ADRCNT = ADRCNT_1; + } + } + + if (flctl->hwecc) { + if (mtd->writesize == 512) { + mtd_set_ooblayout(mtd, &flctl_4secc_oob_smallpage_ops); + chip->badblock_pattern = &flctl_4secc_smallpage; + } else { + mtd_set_ooblayout(mtd, &flctl_4secc_oob_largepage_ops); + chip->badblock_pattern = &flctl_4secc_largepage; + } + + chip->ecc.size = 512; + chip->ecc.bytes = 10; + chip->ecc.strength = 4; + chip->ecc.read_page = flctl_read_page_hwecc; + chip->ecc.write_page = flctl_write_page_hwecc; + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + + /* 4 symbols ECC enabled */ + flctl->flcmncr_base |= _4ECCEN; + } else { + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + } + + return 0; +} + +static const struct nand_controller_ops flctl_nand_controller_ops = { + .attach_chip = flctl_chip_attach_chip, +}; + +static irqreturn_t flctl_handle_flste(int irq, void *dev_id) +{ + struct sh_flctl *flctl = dev_id; + + dev_err(&flctl->pdev->dev, "flste irq: %x\n", readl(FLINTDMACR(flctl))); + writel(flctl->flintdmacr_base, FLINTDMACR(flctl)); + + return IRQ_HANDLED; +} + +struct flctl_soc_config { + unsigned long flcmncr_val; + unsigned has_hwecc:1; + unsigned use_holden:1; +}; + +static struct flctl_soc_config flctl_sh7372_config = { + .flcmncr_val = CLK_16B_12L_4H | TYPESEL_SET | SHBUSSEL, + .has_hwecc = 1, + .use_holden = 1, +}; + +static const struct of_device_id of_flctl_match[] = { + { .compatible = "renesas,shmobile-flctl-sh7372", + .data = &flctl_sh7372_config }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_flctl_match); + +static struct sh_flctl_platform_data *flctl_parse_dt(struct device *dev) +{ + const struct flctl_soc_config *config; + struct sh_flctl_platform_data *pdata; + + config = of_device_get_match_data(dev); + if (!config) { + dev_err(dev, "%s: no OF configuration attached\n", __func__); + return NULL; + } + + pdata = devm_kzalloc(dev, sizeof(struct sh_flctl_platform_data), + GFP_KERNEL); + if (!pdata) + return NULL; + + /* set SoC specific options */ + pdata->flcmncr_val = config->flcmncr_val; + pdata->has_hwecc = config->has_hwecc; + pdata->use_holden = config->use_holden; + + return pdata; +} + +static int flctl_probe(struct platform_device *pdev) +{ + struct resource *res; + struct sh_flctl *flctl; + struct mtd_info *flctl_mtd; + struct nand_chip *nand; + struct sh_flctl_platform_data *pdata; + int ret; + int irq; + + flctl = devm_kzalloc(&pdev->dev, sizeof(struct sh_flctl), GFP_KERNEL); + if (!flctl) + return -ENOMEM; + + flctl->reg = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(flctl->reg)) + return PTR_ERR(flctl->reg); + flctl->fifo = res->start + 0x24; /* FLDTFIFO */ + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(&pdev->dev, irq, flctl_handle_flste, IRQF_SHARED, + "flste", flctl); + if (ret) { + dev_err(&pdev->dev, "request interrupt failed.\n"); + return ret; + } + + if (pdev->dev.of_node) + pdata = flctl_parse_dt(&pdev->dev); + else + pdata = dev_get_platdata(&pdev->dev); + + if (!pdata) { + dev_err(&pdev->dev, "no setup data defined\n"); + return -EINVAL; + } + + platform_set_drvdata(pdev, flctl); + nand = &flctl->chip; + flctl_mtd = nand_to_mtd(nand); + nand_set_flash_node(nand, pdev->dev.of_node); + flctl_mtd->dev.parent = &pdev->dev; + flctl->pdev = pdev; + flctl->hwecc = pdata->has_hwecc; + flctl->holden = pdata->use_holden; + flctl->flcmncr_base = pdata->flcmncr_val; + flctl->flintdmacr_base = flctl->hwecc ? (STERINTE | ECERB) : STERINTE; + + /* Set address of hardware control function */ + /* 20 us command delay time */ + nand->legacy.chip_delay = 20; + + nand->legacy.read_byte = flctl_read_byte; + nand->legacy.write_buf = flctl_write_buf; + nand->legacy.read_buf = flctl_read_buf; + nand->legacy.select_chip = flctl_select_chip; + nand->legacy.cmdfunc = flctl_cmdfunc; + nand->legacy.set_features = nand_get_set_features_notsupp; + nand->legacy.get_features = nand_get_set_features_notsupp; + + if (pdata->flcmncr_val & SEL_16BIT) + nand->options |= NAND_BUSWIDTH_16; + + nand->options |= NAND_BBM_FIRSTPAGE | NAND_BBM_SECONDPAGE; + + pm_runtime_enable(&pdev->dev); + pm_runtime_resume(&pdev->dev); + + flctl_setup_dma(flctl); + + nand->legacy.dummy_controller.ops = &flctl_nand_controller_ops; + ret = nand_scan(nand, 1); + if (ret) + goto err_chip; + + ret = mtd_device_register(flctl_mtd, pdata->parts, pdata->nr_parts); + if (ret) + goto cleanup_nand; + + return 0; + +cleanup_nand: + nand_cleanup(nand); +err_chip: + flctl_release_dma(flctl); + pm_runtime_disable(&pdev->dev); + return ret; +} + +static void flctl_remove(struct platform_device *pdev) +{ + struct sh_flctl *flctl = platform_get_drvdata(pdev); + struct nand_chip *chip = &flctl->chip; + int ret; + + flctl_release_dma(flctl); + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + pm_runtime_disable(&pdev->dev); +} + +static struct platform_driver flctl_driver = { + .probe = flctl_probe, + .remove = flctl_remove, + .driver = { + .name = "sh_flctl", + .of_match_table = of_flctl_match, + }, +}; + +module_platform_driver(flctl_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Yoshihiro Shimoda"); +MODULE_DESCRIPTION("SuperH FLCTL driver"); +MODULE_ALIAS("platform:sh_flctl"); diff --git a/drivers/mtd/nand/raw/sharpsl.c b/drivers/mtd/nand/raw/sharpsl.c new file mode 100644 index 000000000..4154ab74f --- /dev/null +++ b/drivers/mtd/nand/raw/sharpsl.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2004 Richard Purdie + * Copyright (C) 2008 Dmitry Baryshkov + * + * Based on Sharp's NAND driver sharp_sl.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct sharpsl_nand { + struct nand_controller controller; + struct nand_chip chip; + + void __iomem *io; +}; + +static inline struct sharpsl_nand *mtd_to_sharpsl(struct mtd_info *mtd) +{ + return container_of(mtd_to_nand(mtd), struct sharpsl_nand, chip); +} + +/* register offset */ +#define ECCLPLB 0x00 /* line parity 7 - 0 bit */ +#define ECCLPUB 0x04 /* line parity 15 - 8 bit */ +#define ECCCP 0x08 /* column parity 5 - 0 bit */ +#define ECCCNTR 0x0C /* ECC byte counter */ +#define ECCCLRR 0x10 /* cleare ECC */ +#define FLASHIO 0x14 /* Flash I/O */ +#define FLASHCTL 0x18 /* Flash Control */ + +/* Flash control bit */ +#define FLRYBY (1 << 5) +#define FLCE1 (1 << 4) +#define FLWP (1 << 3) +#define FLALE (1 << 2) +#define FLCLE (1 << 1) +#define FLCE0 (1 << 0) + +/* + * hardware specific access to control-lines + * ctrl: + * NAND_CNE: bit 0 -> ! bit 0 & 4 + * NAND_CLE: bit 1 -> bit 1 + * NAND_ALE: bit 2 -> bit 2 + * + */ +static void sharpsl_nand_hwcontrol(struct nand_chip *chip, int cmd, + unsigned int ctrl) +{ + struct sharpsl_nand *sharpsl = mtd_to_sharpsl(nand_to_mtd(chip)); + + if (ctrl & NAND_CTRL_CHANGE) { + unsigned char bits = ctrl & 0x07; + + bits |= (ctrl & 0x01) << 4; + + bits ^= 0x11; + + writeb((readb(sharpsl->io + FLASHCTL) & ~0x17) | bits, sharpsl->io + FLASHCTL); + } + + if (cmd != NAND_CMD_NONE) + writeb(cmd, chip->legacy.IO_ADDR_W); +} + +static int sharpsl_nand_dev_ready(struct nand_chip *chip) +{ + struct sharpsl_nand *sharpsl = mtd_to_sharpsl(nand_to_mtd(chip)); + return !((readb(sharpsl->io + FLASHCTL) & FLRYBY) == 0); +} + +static void sharpsl_nand_enable_hwecc(struct nand_chip *chip, int mode) +{ + struct sharpsl_nand *sharpsl = mtd_to_sharpsl(nand_to_mtd(chip)); + writeb(0, sharpsl->io + ECCCLRR); +} + +static int sharpsl_nand_calculate_ecc(struct nand_chip *chip, + const u_char * dat, u_char * ecc_code) +{ + struct sharpsl_nand *sharpsl = mtd_to_sharpsl(nand_to_mtd(chip)); + ecc_code[0] = ~readb(sharpsl->io + ECCLPUB); + ecc_code[1] = ~readb(sharpsl->io + ECCLPLB); + ecc_code[2] = (~readb(sharpsl->io + ECCCP) << 2) | 0x03; + return readb(sharpsl->io + ECCCNTR) != 0; +} + +static int sharpsl_attach_chip(struct nand_chip *chip) +{ + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return 0; + + chip->ecc.size = 256; + chip->ecc.bytes = 3; + chip->ecc.strength = 1; + chip->ecc.hwctl = sharpsl_nand_enable_hwecc; + chip->ecc.calculate = sharpsl_nand_calculate_ecc; + chip->ecc.correct = rawnand_sw_hamming_correct; + + return 0; +} + +static const struct nand_controller_ops sharpsl_ops = { + .attach_chip = sharpsl_attach_chip, +}; + +/* + * Main initialization routine + */ +static int sharpsl_nand_probe(struct platform_device *pdev) +{ + struct nand_chip *this; + struct mtd_info *mtd; + struct resource *r; + int err = 0; + struct sharpsl_nand *sharpsl; + struct sharpsl_nand_platform_data *data = dev_get_platdata(&pdev->dev); + + if (!data) { + dev_err(&pdev->dev, "no platform data!\n"); + return -EINVAL; + } + + /* Allocate memory for MTD device structure and private data */ + sharpsl = kzalloc_obj(struct sharpsl_nand); + if (!sharpsl) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + dev_err(&pdev->dev, "no io memory resource defined!\n"); + err = -ENODEV; + goto err_get_res; + } + + /* map physical address */ + sharpsl->io = ioremap(r->start, resource_size(r)); + if (!sharpsl->io) { + dev_err(&pdev->dev, "ioremap to access Sharp SL NAND chip failed\n"); + err = -EIO; + goto err_ioremap; + } + + /* Get pointer to private data */ + this = (struct nand_chip *)(&sharpsl->chip); + + nand_controller_init(&sharpsl->controller); + sharpsl->controller.ops = &sharpsl_ops; + this->controller = &sharpsl->controller; + + /* Link the private data with the MTD structure */ + mtd = nand_to_mtd(this); + mtd->dev.parent = &pdev->dev; + mtd_set_ooblayout(mtd, data->ecc_layout); + + platform_set_drvdata(pdev, sharpsl); + + /* + * PXA initialize + */ + writeb(readb(sharpsl->io + FLASHCTL) | FLWP, sharpsl->io + FLASHCTL); + + /* Set address of NAND IO lines */ + this->legacy.IO_ADDR_R = sharpsl->io + FLASHIO; + this->legacy.IO_ADDR_W = sharpsl->io + FLASHIO; + /* Set address of hardware control function */ + this->legacy.cmd_ctrl = sharpsl_nand_hwcontrol; + this->legacy.dev_ready = sharpsl_nand_dev_ready; + /* 15 us command delay time */ + this->legacy.chip_delay = 15; + this->badblock_pattern = data->badblock_pattern; + + /* Scan to find existence of the device */ + err = nand_scan(this, 1); + if (err) + goto err_scan; + + /* Register the partitions */ + mtd->name = "sharpsl-nand"; + + err = mtd_device_parse_register(mtd, data->part_parsers, NULL, + data->partitions, data->nr_partitions); + if (err) + goto err_add; + + /* Return happy */ + return 0; + +err_add: + nand_cleanup(this); + +err_scan: + iounmap(sharpsl->io); +err_ioremap: +err_get_res: + kfree(sharpsl); + return err; +} + +/* + * Clean up routine + */ +static void sharpsl_nand_remove(struct platform_device *pdev) +{ + struct sharpsl_nand *sharpsl = platform_get_drvdata(pdev); + struct nand_chip *chip = &sharpsl->chip; + int ret; + + /* Unregister device */ + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + + /* Release resources */ + nand_cleanup(chip); + + iounmap(sharpsl->io); + + /* Free the driver's structure */ + kfree(sharpsl); +} + +static struct platform_driver sharpsl_nand_driver = { + .driver = { + .name = "sharpsl-nand", + }, + .probe = sharpsl_nand_probe, + .remove = sharpsl_nand_remove, +}; + +module_platform_driver(sharpsl_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Richard Purdie "); +MODULE_DESCRIPTION("Device specific logic for NAND flash on Sharp SL-C7xx Series"); diff --git a/drivers/mtd/nand/raw/sm_common.c b/drivers/mtd/nand/raw/sm_common.c new file mode 100644 index 000000000..e238784c8 --- /dev/null +++ b/drivers/mtd/nand/raw/sm_common.c @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright © 2009 - Maxim Levitsky + * Common routines & support for xD format + */ +#include +#include +#include +#include +#include "sm_common.h" + +static int oob_sm_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section > 1) + return -ERANGE; + + oobregion->length = 3; + oobregion->offset = ((section + 1) * 8) - 3; + + return 0; +} + +static int oob_sm_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + switch (section) { + case 0: + /* reserved */ + oobregion->offset = 0; + oobregion->length = 4; + break; + case 1: + /* LBA1 */ + oobregion->offset = 6; + oobregion->length = 2; + break; + case 2: + /* LBA2 */ + oobregion->offset = 11; + oobregion->length = 2; + break; + default: + return -ERANGE; + } + + return 0; +} + +static const struct mtd_ooblayout_ops oob_sm_ops = { + .ecc = oob_sm_ooblayout_ecc, + .free = oob_sm_ooblayout_free, +}; + +/* NOTE: This layout is not compatible with SmartMedia, */ +/* because the 256 byte devices have page dependent oob layout */ +/* However it does preserve the bad block markers */ +/* If you use smftl, it will bypass this and work correctly */ +/* If you not, then you break SmartMedia compliance anyway */ + +static int oob_sm_small_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section) + return -ERANGE; + + oobregion->length = 3; + oobregion->offset = 0; + + return 0; +} + +static int oob_sm_small_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + switch (section) { + case 0: + /* reserved */ + oobregion->offset = 3; + oobregion->length = 2; + break; + case 1: + /* LBA1 */ + oobregion->offset = 6; + oobregion->length = 2; + break; + default: + return -ERANGE; + } + + return 0; +} + +static const struct mtd_ooblayout_ops oob_sm_small_ops = { + .ecc = oob_sm_small_ooblayout_ecc, + .free = oob_sm_small_ooblayout_free, +}; + +static int sm_block_markbad(struct nand_chip *chip, loff_t ofs) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct mtd_oob_ops ops = { }; + struct sm_oob oob; + int ret; + + memset(&oob, -1, SM_OOB_SIZE); + oob.block_status = 0x0F; + + /* As long as this function is called on erase block boundaries + it will work correctly for 256 byte nand */ + ops.mode = MTD_OPS_PLACE_OOB; + ops.ooboffs = 0; + ops.ooblen = mtd->oobsize; + ops.oobbuf = (void *)&oob; + ops.datbuf = NULL; + + + ret = mtd_write_oob(mtd, ofs, &ops); + if (ret < 0 || ops.oobretlen != SM_OOB_SIZE) { + pr_notice("sm_common: can't mark sector at %i as bad\n", + (int)ofs); + return -EIO; + } + + return 0; +} + +static struct nand_flash_dev nand_smartmedia_flash_ids[] = { + LEGACY_ID_NAND("SmartMedia 2MiB 3,3V ROM", 0x5d, 2, SZ_8K, NAND_ROM), + LEGACY_ID_NAND("SmartMedia 4MiB 3,3V", 0xe3, 4, SZ_8K, 0), + LEGACY_ID_NAND("SmartMedia 4MiB 3,3/5V", 0xe5, 4, SZ_8K, 0), + LEGACY_ID_NAND("SmartMedia 4MiB 5V", 0x6b, 4, SZ_8K, 0), + LEGACY_ID_NAND("SmartMedia 4MiB 3,3V ROM", 0xd5, 4, SZ_8K, NAND_ROM), + LEGACY_ID_NAND("SmartMedia 8MiB 3,3V", 0xe6, 8, SZ_8K, 0), + LEGACY_ID_NAND("SmartMedia 8MiB 3,3V ROM", 0xd6, 8, SZ_8K, NAND_ROM), + LEGACY_ID_NAND("SmartMedia 16MiB 3,3V", 0x73, 16, SZ_16K, 0), + LEGACY_ID_NAND("SmartMedia 16MiB 3,3V ROM", 0x57, 16, SZ_16K, NAND_ROM), + LEGACY_ID_NAND("SmartMedia 32MiB 3,3V", 0x75, 32, SZ_16K, 0), + LEGACY_ID_NAND("SmartMedia 32MiB 3,3V ROM", 0x58, 32, SZ_16K, NAND_ROM), + LEGACY_ID_NAND("SmartMedia 64MiB 3,3V", 0x76, 64, SZ_16K, 0), + LEGACY_ID_NAND("SmartMedia 64MiB 3,3V ROM", 0xd9, 64, SZ_16K, NAND_ROM), + LEGACY_ID_NAND("SmartMedia 128MiB 3,3V", 0x79, 128, SZ_16K, 0), + LEGACY_ID_NAND("SmartMedia 128MiB 3,3V ROM", 0xda, 128, SZ_16K, NAND_ROM), + LEGACY_ID_NAND("SmartMedia 256MiB 3, 3V", 0x71, 256, SZ_16K, 0), + LEGACY_ID_NAND("SmartMedia 256MiB 3,3V ROM", 0x5b, 256, SZ_16K, NAND_ROM), + {NULL} +}; + +static struct nand_flash_dev nand_xd_flash_ids[] = { + LEGACY_ID_NAND("xD 16MiB 3,3V", 0x73, 16, SZ_16K, 0), + LEGACY_ID_NAND("xD 32MiB 3,3V", 0x75, 32, SZ_16K, 0), + LEGACY_ID_NAND("xD 64MiB 3,3V", 0x76, 64, SZ_16K, 0), + LEGACY_ID_NAND("xD 128MiB 3,3V", 0x79, 128, SZ_16K, 0), + LEGACY_ID_NAND("xD 256MiB 3,3V", 0x71, 256, SZ_16K, NAND_BROKEN_XD), + LEGACY_ID_NAND("xD 512MiB 3,3V", 0xdc, 512, SZ_16K, NAND_BROKEN_XD), + LEGACY_ID_NAND("xD 1GiB 3,3V", 0xd3, 1024, SZ_16K, NAND_BROKEN_XD), + LEGACY_ID_NAND("xD 2GiB 3,3V", 0xd5, 2048, SZ_16K, NAND_BROKEN_XD), + {NULL} +}; + +static int sm_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + /* Bad block marker position */ + chip->badblockpos = 0x05; + chip->badblockbits = 7; + chip->legacy.block_markbad = sm_block_markbad; + + /* ECC layout */ + if (mtd->writesize == SM_SECTOR_SIZE) + mtd_set_ooblayout(mtd, &oob_sm_ops); + else if (mtd->writesize == SM_SMALL_PAGE) + mtd_set_ooblayout(mtd, &oob_sm_small_ops); + else + return -ENODEV; + + return 0; +} + +static const struct nand_controller_ops sm_controller_ops = { + .attach_chip = sm_attach_chip, +}; + +int sm_register_device(struct mtd_info *mtd, int smartmedia) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct nand_flash_dev *flash_ids; + int ret; + + chip->options |= NAND_SKIP_BBTSCAN; + + /* Scan for card properties */ + chip->legacy.dummy_controller.ops = &sm_controller_ops; + flash_ids = smartmedia ? nand_smartmedia_flash_ids : nand_xd_flash_ids; + ret = nand_scan_with_ids(chip, 1, flash_ids); + if (ret) + return ret; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) + nand_cleanup(chip); + + return ret; +} +EXPORT_SYMBOL_GPL(sm_register_device); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Maxim Levitsky "); +MODULE_DESCRIPTION("Common SmartMedia/xD functions"); diff --git a/drivers/mtd/nand/raw/sm_common.h b/drivers/mtd/nand/raw/sm_common.h new file mode 100644 index 000000000..57fc9f86f --- /dev/null +++ b/drivers/mtd/nand/raw/sm_common.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright © 2009 - Maxim Levitsky + * Common routines & support for SmartMedia/xD format + */ +#include +#include + +/* Full oob structure as written on the flash */ +struct sm_oob { + uint32_t reserved; + uint8_t data_status; + uint8_t block_status; + uint8_t lba_copy1[2]; + uint8_t ecc2[3]; + uint8_t lba_copy2[2]; + uint8_t ecc1[3]; +} __packed; + + +/* one sector is always 512 bytes, but it can consist of two nand pages */ +#define SM_SECTOR_SIZE 512 + +/* oob area is also 16 bytes, but might be from two pages */ +#define SM_OOB_SIZE 16 + +/* This is maximum zone size, and all devices that have more that one zone + have this size */ +#define SM_MAX_ZONE_SIZE 1024 + +/* support for small page nand */ +#define SM_SMALL_PAGE 256 +#define SM_SMALL_OOB_SIZE 8 + + +int sm_register_device(struct mtd_info *mtd, int smartmedia); + + +static inline int sm_sector_valid(struct sm_oob *oob) +{ + return hweight16(oob->data_status) >= 5; +} + +static inline int sm_block_valid(struct sm_oob *oob) +{ + return hweight16(oob->block_status) >= 7; +} + +static inline int sm_block_erased(struct sm_oob *oob) +{ + static const uint32_t erased_pattern[4] = { + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF }; + + /* First test for erased block */ + if (!memcmp(oob, erased_pattern, sizeof(*oob))) + return 1; + return 0; +} diff --git a/drivers/mtd/nand/raw/socrates_nand.c b/drivers/mtd/nand/raw/socrates_nand.c new file mode 100644 index 000000000..668584683 --- /dev/null +++ b/drivers/mtd/nand/raw/socrates_nand.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright © 2008 Ilya Yanok, Emcraft Systems + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FPGA_NAND_CMD_MASK (0x7 << 28) +#define FPGA_NAND_CMD_COMMAND (0x0 << 28) +#define FPGA_NAND_CMD_ADDR (0x1 << 28) +#define FPGA_NAND_CMD_READ (0x2 << 28) +#define FPGA_NAND_CMD_WRITE (0x3 << 28) +#define FPGA_NAND_BUSY (0x1 << 15) +#define FPGA_NAND_ENABLE (0x1 << 31) +#define FPGA_NAND_DATA_SHIFT 16 + +struct socrates_nand_host { + struct nand_controller controller; + struct nand_chip nand_chip; + void __iomem *io_base; + struct device *dev; +}; + +/** + * socrates_nand_write_buf - write buffer to chip + * @this: NAND chip object + * @buf: data buffer + * @len: number of bytes to write + */ +static void socrates_nand_write_buf(struct nand_chip *this, const uint8_t *buf, + int len) +{ + int i; + struct socrates_nand_host *host = nand_get_controller_data(this); + + for (i = 0; i < len; i++) { + out_be32(host->io_base, FPGA_NAND_ENABLE | + FPGA_NAND_CMD_WRITE | + (buf[i] << FPGA_NAND_DATA_SHIFT)); + } +} + +/** + * socrates_nand_read_buf - read chip data into buffer + * @this: NAND chip object + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void socrates_nand_read_buf(struct nand_chip *this, uint8_t *buf, + int len) +{ + int i; + struct socrates_nand_host *host = nand_get_controller_data(this); + uint32_t val; + + val = FPGA_NAND_ENABLE | FPGA_NAND_CMD_READ; + + out_be32(host->io_base, val); + for (i = 0; i < len; i++) { + buf[i] = (in_be32(host->io_base) >> + FPGA_NAND_DATA_SHIFT) & 0xff; + } +} + +/** + * socrates_nand_read_byte - read one byte from the chip + * @mtd: MTD device structure + */ +static uint8_t socrates_nand_read_byte(struct nand_chip *this) +{ + uint8_t byte; + socrates_nand_read_buf(this, &byte, sizeof(byte)); + return byte; +} + +/* + * Hardware specific access to control-lines + */ +static void socrates_nand_cmd_ctrl(struct nand_chip *nand_chip, int cmd, + unsigned int ctrl) +{ + struct socrates_nand_host *host = nand_get_controller_data(nand_chip); + uint32_t val; + + if (cmd == NAND_CMD_NONE) + return; + + if (ctrl & NAND_CLE) + val = FPGA_NAND_CMD_COMMAND; + else + val = FPGA_NAND_CMD_ADDR; + + if (ctrl & NAND_NCE) + val |= FPGA_NAND_ENABLE; + + val |= (cmd & 0xff) << FPGA_NAND_DATA_SHIFT; + + out_be32(host->io_base, val); +} + +/* + * Read the Device Ready pin. + */ +static int socrates_nand_device_ready(struct nand_chip *nand_chip) +{ + struct socrates_nand_host *host = nand_get_controller_data(nand_chip); + + if (in_be32(host->io_base) & FPGA_NAND_BUSY) + return 0; /* busy */ + return 1; +} + +static int socrates_attach_chip(struct nand_chip *chip) +{ + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT && + chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + + return 0; +} + +static const struct nand_controller_ops socrates_ops = { + .attach_chip = socrates_attach_chip, +}; + +/* + * Probe for the NAND device. + */ +static int socrates_nand_probe(struct platform_device *ofdev) +{ + struct socrates_nand_host *host; + struct mtd_info *mtd; + struct nand_chip *nand_chip; + int res; + + /* Allocate memory for the device structure (and zero it) */ + host = devm_kzalloc(&ofdev->dev, sizeof(*host), GFP_KERNEL); + if (!host) + return -ENOMEM; + + host->io_base = of_iomap(ofdev->dev.of_node, 0); + if (host->io_base == NULL) { + dev_err(&ofdev->dev, "ioremap failed\n"); + return -EIO; + } + + nand_chip = &host->nand_chip; + mtd = nand_to_mtd(nand_chip); + host->dev = &ofdev->dev; + + nand_controller_init(&host->controller); + host->controller.ops = &socrates_ops; + nand_chip->controller = &host->controller; + + /* link the private data structures */ + nand_set_controller_data(nand_chip, host); + nand_set_flash_node(nand_chip, ofdev->dev.of_node); + mtd->name = "socrates_nand"; + mtd->dev.parent = &ofdev->dev; + + nand_chip->legacy.cmd_ctrl = socrates_nand_cmd_ctrl; + nand_chip->legacy.read_byte = socrates_nand_read_byte; + nand_chip->legacy.write_buf = socrates_nand_write_buf; + nand_chip->legacy.read_buf = socrates_nand_read_buf; + nand_chip->legacy.dev_ready = socrates_nand_device_ready; + + /* TODO: I have no idea what real delay is. */ + nand_chip->legacy.chip_delay = 20; /* 20us command delay time */ + + /* + * This driver assumes that the default ECC engine should be TYPE_SOFT. + * Set ->engine_type before registering the NAND devices in order to + * provide a driver specific default value. + */ + nand_chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + + dev_set_drvdata(&ofdev->dev, host); + + res = nand_scan(nand_chip, 1); + if (res) + goto out; + + res = mtd_device_register(mtd, NULL, 0); + if (!res) + return res; + + nand_cleanup(nand_chip); + +out: + iounmap(host->io_base); + return res; +} + +/* + * Remove a NAND device. + */ +static void socrates_nand_remove(struct platform_device *ofdev) +{ + struct socrates_nand_host *host = dev_get_drvdata(&ofdev->dev); + struct nand_chip *chip = &host->nand_chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + + iounmap(host->io_base); +} + +static const struct of_device_id socrates_nand_match[] = +{ + { + .compatible = "abb,socrates-nand", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, socrates_nand_match); + +static struct platform_driver socrates_nand_driver = { + .driver = { + .name = "socrates_nand", + .of_match_table = socrates_nand_match, + }, + .probe = socrates_nand_probe, + .remove = socrates_nand_remove, +}; + +module_platform_driver(socrates_nand_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ilya Yanok"); +MODULE_DESCRIPTION("NAND driver for Socrates board"); diff --git a/drivers/mtd/nand/raw/stm32_fmc2_nand.c b/drivers/mtd/nand/raw/stm32_fmc2_nand.c new file mode 100644 index 000000000..c08d6b176 --- /dev/null +++ b/drivers/mtd/nand/raw/stm32_fmc2_nand.c @@ -0,0 +1,2163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics 2018 + * Author: Christophe Kerello + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Bad block marker length */ +#define FMC2_BBM_LEN 2 + +/* ECC step size */ +#define FMC2_ECC_STEP_SIZE 512 + +/* BCHDSRx registers length */ +#define FMC2_BCHDSRS_LEN 20 + +/* HECCR length */ +#define FMC2_HECCR_LEN 4 + +/* Max requests done for a 8k nand page size */ +#define FMC2_MAX_SG 16 + +/* Max chip enable */ +#define FMC2_MAX_CE 4 + +/* Max ECC buffer length */ +#define FMC2_MAX_ECC_BUF_LEN (FMC2_BCHDSRS_LEN * FMC2_MAX_SG) + +#define FMC2_TIMEOUT_MS 5000 + +/* Timings */ +#define FMC2_THIZ 1 +#define FMC2_TIO 8000 +#define FMC2_TSYNC 3000 +#define FMC2_PCR_TIMING_MASK 0xf +#define FMC2_PMEM_PATT_TIMING_MASK 0xff + +/* FMC2 Controller Registers */ +#define FMC2_BCR1 0x0 +#define FMC2_PCR 0x80 +#define FMC2_SR 0x84 +#define FMC2_PMEM 0x88 +#define FMC2_PATT 0x8c +#define FMC2_HECCR 0x94 +#define FMC2_ISR 0x184 +#define FMC2_ICR 0x188 +#define FMC2_CSQCR 0x200 +#define FMC2_CSQCFGR1 0x204 +#define FMC2_CSQCFGR2 0x208 +#define FMC2_CSQCFGR3 0x20c +#define FMC2_CSQAR1 0x210 +#define FMC2_CSQAR2 0x214 +#define FMC2_CSQIER 0x220 +#define FMC2_CSQISR 0x224 +#define FMC2_CSQICR 0x228 +#define FMC2_CSQEMSR 0x230 +#define FMC2_BCHIER 0x250 +#define FMC2_BCHISR 0x254 +#define FMC2_BCHICR 0x258 +#define FMC2_BCHPBR1 0x260 +#define FMC2_BCHPBR2 0x264 +#define FMC2_BCHPBR3 0x268 +#define FMC2_BCHPBR4 0x26c +#define FMC2_BCHDSR0 0x27c +#define FMC2_BCHDSR1 0x280 +#define FMC2_BCHDSR2 0x284 +#define FMC2_BCHDSR3 0x288 +#define FMC2_BCHDSR4 0x28c + +/* Register: FMC2_BCR1 */ +#define FMC2_BCR1_FMC2EN BIT(31) + +/* Register: FMC2_PCR */ +#define FMC2_PCR_PWAITEN BIT(1) +#define FMC2_PCR_PBKEN BIT(2) +#define FMC2_PCR_PWID GENMASK(5, 4) +#define FMC2_PCR_PWID_BUSWIDTH_8 0 +#define FMC2_PCR_PWID_BUSWIDTH_16 1 +#define FMC2_PCR_ECCEN BIT(6) +#define FMC2_PCR_ECCALG BIT(8) +#define FMC2_PCR_TCLR GENMASK(12, 9) +#define FMC2_PCR_TCLR_DEFAULT 0xf +#define FMC2_PCR_TAR GENMASK(16, 13) +#define FMC2_PCR_TAR_DEFAULT 0xf +#define FMC2_PCR_ECCSS GENMASK(19, 17) +#define FMC2_PCR_ECCSS_512 1 +#define FMC2_PCR_ECCSS_2048 3 +#define FMC2_PCR_BCHECC BIT(24) +#define FMC2_PCR_WEN BIT(25) + +/* Register: FMC2_SR */ +#define FMC2_SR_NWRF BIT(6) + +/* Register: FMC2_PMEM */ +#define FMC2_PMEM_MEMSET GENMASK(7, 0) +#define FMC2_PMEM_MEMWAIT GENMASK(15, 8) +#define FMC2_PMEM_MEMHOLD GENMASK(23, 16) +#define FMC2_PMEM_MEMHIZ GENMASK(31, 24) +#define FMC2_PMEM_DEFAULT 0x0a0a0a0a + +/* Register: FMC2_PATT */ +#define FMC2_PATT_ATTSET GENMASK(7, 0) +#define FMC2_PATT_ATTWAIT GENMASK(15, 8) +#define FMC2_PATT_ATTHOLD GENMASK(23, 16) +#define FMC2_PATT_ATTHIZ GENMASK(31, 24) +#define FMC2_PATT_DEFAULT 0x0a0a0a0a + +/* Register: FMC2_ISR */ +#define FMC2_ISR_IHLF BIT(1) + +/* Register: FMC2_ICR */ +#define FMC2_ICR_CIHLF BIT(1) + +/* Register: FMC2_CSQCR */ +#define FMC2_CSQCR_CSQSTART BIT(0) + +/* Register: FMC2_CSQCFGR1 */ +#define FMC2_CSQCFGR1_CMD2EN BIT(1) +#define FMC2_CSQCFGR1_DMADEN BIT(2) +#define FMC2_CSQCFGR1_ACYNBR GENMASK(6, 4) +#define FMC2_CSQCFGR1_CMD1 GENMASK(15, 8) +#define FMC2_CSQCFGR1_CMD2 GENMASK(23, 16) +#define FMC2_CSQCFGR1_CMD1T BIT(24) +#define FMC2_CSQCFGR1_CMD2T BIT(25) + +/* Register: FMC2_CSQCFGR2 */ +#define FMC2_CSQCFGR2_SQSDTEN BIT(0) +#define FMC2_CSQCFGR2_RCMD2EN BIT(1) +#define FMC2_CSQCFGR2_DMASEN BIT(2) +#define FMC2_CSQCFGR2_RCMD1 GENMASK(15, 8) +#define FMC2_CSQCFGR2_RCMD2 GENMASK(23, 16) +#define FMC2_CSQCFGR2_RCMD1T BIT(24) +#define FMC2_CSQCFGR2_RCMD2T BIT(25) + +/* Register: FMC2_CSQCFGR3 */ +#define FMC2_CSQCFGR3_SNBR GENMASK(13, 8) +#define FMC2_CSQCFGR3_AC1T BIT(16) +#define FMC2_CSQCFGR3_AC2T BIT(17) +#define FMC2_CSQCFGR3_AC3T BIT(18) +#define FMC2_CSQCFGR3_AC4T BIT(19) +#define FMC2_CSQCFGR3_AC5T BIT(20) +#define FMC2_CSQCFGR3_SDT BIT(21) +#define FMC2_CSQCFGR3_RAC1T BIT(22) +#define FMC2_CSQCFGR3_RAC2T BIT(23) + +/* Register: FMC2_CSQCAR1 */ +#define FMC2_CSQCAR1_ADDC1 GENMASK(7, 0) +#define FMC2_CSQCAR1_ADDC2 GENMASK(15, 8) +#define FMC2_CSQCAR1_ADDC3 GENMASK(23, 16) +#define FMC2_CSQCAR1_ADDC4 GENMASK(31, 24) + +/* Register: FMC2_CSQCAR2 */ +#define FMC2_CSQCAR2_ADDC5 GENMASK(7, 0) +#define FMC2_CSQCAR2_NANDCEN GENMASK(11, 10) +#define FMC2_CSQCAR2_SAO GENMASK(31, 16) + +/* Register: FMC2_CSQIER */ +#define FMC2_CSQIER_TCIE BIT(0) + +/* Register: FMC2_CSQICR */ +#define FMC2_CSQICR_CLEAR_IRQ GENMASK(4, 0) + +/* Register: FMC2_CSQEMSR */ +#define FMC2_CSQEMSR_SEM GENMASK(15, 0) + +/* Register: FMC2_BCHIER */ +#define FMC2_BCHIER_DERIE BIT(1) +#define FMC2_BCHIER_EPBRIE BIT(4) + +/* Register: FMC2_BCHICR */ +#define FMC2_BCHICR_CLEAR_IRQ GENMASK(4, 0) + +/* Register: FMC2_BCHDSR0 */ +#define FMC2_BCHDSR0_DUE BIT(0) +#define FMC2_BCHDSR0_DEF BIT(1) +#define FMC2_BCHDSR0_DEN GENMASK(7, 4) + +/* Register: FMC2_BCHDSR1 */ +#define FMC2_BCHDSR1_EBP1 GENMASK(12, 0) +#define FMC2_BCHDSR1_EBP2 GENMASK(28, 16) + +/* Register: FMC2_BCHDSR2 */ +#define FMC2_BCHDSR2_EBP3 GENMASK(12, 0) +#define FMC2_BCHDSR2_EBP4 GENMASK(28, 16) + +/* Register: FMC2_BCHDSR3 */ +#define FMC2_BCHDSR3_EBP5 GENMASK(12, 0) +#define FMC2_BCHDSR3_EBP6 GENMASK(28, 16) + +/* Register: FMC2_BCHDSR4 */ +#define FMC2_BCHDSR4_EBP7 GENMASK(12, 0) +#define FMC2_BCHDSR4_EBP8 GENMASK(28, 16) + +enum stm32_fmc2_ecc { + FMC2_ECC_HAM = 1, + FMC2_ECC_BCH4 = 4, + FMC2_ECC_BCH8 = 8 +}; + +enum stm32_fmc2_irq_state { + FMC2_IRQ_UNKNOWN = 0, + FMC2_IRQ_BCH, + FMC2_IRQ_SEQ +}; + +struct stm32_fmc2_timings { + u8 tclr; + u8 tar; + u8 thiz; + u8 twait; + u8 thold_mem; + u8 tset_mem; + u8 thold_att; + u8 tset_att; +}; + +struct stm32_fmc2_nand { + struct nand_chip chip; + struct gpio_desc *wp_gpio; + struct stm32_fmc2_timings timings; + int ncs; + int cs_used[FMC2_MAX_CE]; +}; + +static inline struct stm32_fmc2_nand *to_fmc2_nand(struct nand_chip *chip) +{ + return container_of(chip, struct stm32_fmc2_nand, chip); +} + +struct stm32_fmc2_nfc; + +struct stm32_fmc2_nfc_data { + int max_ncs; + int (*set_cdev)(struct stm32_fmc2_nfc *nfc); +}; + +struct stm32_fmc2_nfc { + struct nand_controller base; + struct stm32_fmc2_nand nand; + struct device *dev; + struct device *cdev; + struct regmap *regmap; + void __iomem *data_base[FMC2_MAX_CE]; + void __iomem *cmd_base[FMC2_MAX_CE]; + void __iomem *addr_base[FMC2_MAX_CE]; + phys_addr_t io_phys_addr; + phys_addr_t data_phys_addr[FMC2_MAX_CE]; + struct clk *clk; + u8 irq_state; + const struct stm32_fmc2_nfc_data *data; + + struct dma_chan *dma_tx_ch; + struct dma_chan *dma_rx_ch; + struct dma_chan *dma_ecc_ch; + 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; + + struct completion complete; + struct completion dma_data_complete; + struct completion dma_ecc_complete; + + u8 cs_assigned; + int cs_sel; +}; + +static inline struct stm32_fmc2_nfc *to_stm32_nfc(struct nand_controller *base) +{ + return container_of(base, struct stm32_fmc2_nfc, base); +} + +static void stm32_fmc2_nfc_timings_init(struct nand_chip *chip) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + struct stm32_fmc2_nand *nand = to_fmc2_nand(chip); + struct stm32_fmc2_timings *timings = &nand->timings; + u32 pmem, patt; + + /* Set tclr/tar timings */ + regmap_update_bits(nfc->regmap, FMC2_PCR, + FMC2_PCR_TCLR | FMC2_PCR_TAR, + FIELD_PREP(FMC2_PCR_TCLR, timings->tclr) | + FIELD_PREP(FMC2_PCR_TAR, timings->tar)); + + /* Set tset/twait/thold/thiz timings in common bank */ + pmem = FIELD_PREP(FMC2_PMEM_MEMSET, timings->tset_mem); + pmem |= FIELD_PREP(FMC2_PMEM_MEMWAIT, timings->twait); + pmem |= FIELD_PREP(FMC2_PMEM_MEMHOLD, timings->thold_mem); + pmem |= FIELD_PREP(FMC2_PMEM_MEMHIZ, timings->thiz); + regmap_write(nfc->regmap, FMC2_PMEM, pmem); + + /* Set tset/twait/thold/thiz timings in attribut bank */ + patt = FIELD_PREP(FMC2_PATT_ATTSET, timings->tset_att); + patt |= FIELD_PREP(FMC2_PATT_ATTWAIT, timings->twait); + patt |= FIELD_PREP(FMC2_PATT_ATTHOLD, timings->thold_att); + patt |= FIELD_PREP(FMC2_PATT_ATTHIZ, timings->thiz); + regmap_write(nfc->regmap, FMC2_PATT, patt); +} + +static void stm32_fmc2_nfc_setup(struct nand_chip *chip) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + u32 pcr = 0, pcr_mask; + + /* Configure ECC algorithm (default configuration is Hamming) */ + pcr_mask = FMC2_PCR_ECCALG; + pcr_mask |= FMC2_PCR_BCHECC; + if (chip->ecc.strength == FMC2_ECC_BCH8) { + pcr |= FMC2_PCR_ECCALG; + pcr |= FMC2_PCR_BCHECC; + } else if (chip->ecc.strength == FMC2_ECC_BCH4) { + pcr |= FMC2_PCR_ECCALG; + } + + /* Set buswidth */ + pcr_mask |= FMC2_PCR_PWID; + if (chip->options & NAND_BUSWIDTH_16) + pcr |= FIELD_PREP(FMC2_PCR_PWID, FMC2_PCR_PWID_BUSWIDTH_16); + + /* Set ECC sector size */ + pcr_mask |= FMC2_PCR_ECCSS; + pcr |= FIELD_PREP(FMC2_PCR_ECCSS, FMC2_PCR_ECCSS_512); + + regmap_update_bits(nfc->regmap, FMC2_PCR, pcr_mask, pcr); +} + +static int stm32_fmc2_nfc_select_chip(struct nand_chip *chip, int chipnr) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + struct stm32_fmc2_nand *nand = to_fmc2_nand(chip); + struct dma_slave_config dma_cfg; + int ret; + + if (nand->cs_used[chipnr] == nfc->cs_sel) + return 0; + + nfc->cs_sel = nand->cs_used[chipnr]; + stm32_fmc2_nfc_setup(chip); + stm32_fmc2_nfc_timings_init(chip); + + if (nfc->dma_tx_ch) { + memset(&dma_cfg, 0, sizeof(dma_cfg)); + dma_cfg.dst_addr = nfc->data_phys_addr[nfc->cs_sel]; + dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_cfg.dst_maxburst = nfc->tx_dma_max_burst / + dma_cfg.dst_addr_width; + + ret = dmaengine_slave_config(nfc->dma_tx_ch, &dma_cfg); + if (ret) { + dev_err(nfc->dev, "tx DMA engine slave config failed\n"); + return ret; + } + } + + if (nfc->dma_rx_ch) { + memset(&dma_cfg, 0, sizeof(dma_cfg)); + dma_cfg.src_addr = nfc->data_phys_addr[nfc->cs_sel]; + dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_cfg.src_maxburst = nfc->rx_dma_max_burst / + dma_cfg.src_addr_width; + + ret = dmaengine_slave_config(nfc->dma_rx_ch, &dma_cfg); + if (ret) { + dev_err(nfc->dev, "rx DMA engine slave config failed\n"); + return ret; + } + } + + if (nfc->dma_ecc_ch) { + /* + * Hamming: we read HECCR register + * BCH4/BCH8: we read BCHDSRSx registers + */ + memset(&dma_cfg, 0, sizeof(dma_cfg)); + dma_cfg.src_addr = nfc->io_phys_addr; + dma_cfg.src_addr += chip->ecc.strength == FMC2_ECC_HAM ? + FMC2_HECCR : FMC2_BCHDSR0; + dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + + ret = dmaengine_slave_config(nfc->dma_ecc_ch, &dma_cfg); + if (ret) { + dev_err(nfc->dev, "ECC DMA engine slave config failed\n"); + return ret; + } + + /* Calculate ECC length needed for one sector */ + nfc->dma_ecc_len = chip->ecc.strength == FMC2_ECC_HAM ? + FMC2_HECCR_LEN : FMC2_BCHDSRS_LEN; + } + + return 0; +} + +static void stm32_fmc2_nfc_set_buswidth_16(struct stm32_fmc2_nfc *nfc, bool set) +{ + u32 pcr; + + pcr = set ? FIELD_PREP(FMC2_PCR_PWID, FMC2_PCR_PWID_BUSWIDTH_16) : + FIELD_PREP(FMC2_PCR_PWID, FMC2_PCR_PWID_BUSWIDTH_8); + + regmap_update_bits(nfc->regmap, FMC2_PCR, FMC2_PCR_PWID, pcr); +} + +static void stm32_fmc2_nfc_set_ecc(struct stm32_fmc2_nfc *nfc, bool enable) +{ + regmap_update_bits(nfc->regmap, FMC2_PCR, FMC2_PCR_ECCEN, + enable ? FMC2_PCR_ECCEN : 0); +} + +static void stm32_fmc2_nfc_enable_seq_irq(struct stm32_fmc2_nfc *nfc) +{ + nfc->irq_state = FMC2_IRQ_SEQ; + + regmap_update_bits(nfc->regmap, FMC2_CSQIER, + FMC2_CSQIER_TCIE, FMC2_CSQIER_TCIE); +} + +static void stm32_fmc2_nfc_disable_seq_irq(struct stm32_fmc2_nfc *nfc) +{ + regmap_update_bits(nfc->regmap, FMC2_CSQIER, FMC2_CSQIER_TCIE, 0); + + nfc->irq_state = FMC2_IRQ_UNKNOWN; +} + +static void stm32_fmc2_nfc_clear_seq_irq(struct stm32_fmc2_nfc *nfc) +{ + regmap_write(nfc->regmap, FMC2_CSQICR, FMC2_CSQICR_CLEAR_IRQ); +} + +static void stm32_fmc2_nfc_enable_bch_irq(struct stm32_fmc2_nfc *nfc, int mode) +{ + nfc->irq_state = FMC2_IRQ_BCH; + + if (mode == NAND_ECC_WRITE) + regmap_update_bits(nfc->regmap, FMC2_BCHIER, + FMC2_BCHIER_EPBRIE, FMC2_BCHIER_EPBRIE); + else + regmap_update_bits(nfc->regmap, FMC2_BCHIER, + FMC2_BCHIER_DERIE, FMC2_BCHIER_DERIE); +} + +static void stm32_fmc2_nfc_disable_bch_irq(struct stm32_fmc2_nfc *nfc) +{ + regmap_update_bits(nfc->regmap, FMC2_BCHIER, + FMC2_BCHIER_DERIE | FMC2_BCHIER_EPBRIE, 0); + + nfc->irq_state = FMC2_IRQ_UNKNOWN; +} + +static void stm32_fmc2_nfc_clear_bch_irq(struct stm32_fmc2_nfc *nfc) +{ + regmap_write(nfc->regmap, FMC2_BCHICR, FMC2_BCHICR_CLEAR_IRQ); +} + +/* + * Enable ECC logic and reset syndrome/parity bits previously calculated + * Syndrome/parity bits is cleared by setting the ECCEN bit to 0 + */ +static void stm32_fmc2_nfc_hwctl(struct nand_chip *chip, int mode) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + + stm32_fmc2_nfc_set_ecc(nfc, false); + + if (chip->ecc.strength != FMC2_ECC_HAM) { + regmap_update_bits(nfc->regmap, FMC2_PCR, FMC2_PCR_WEN, + mode == NAND_ECC_WRITE ? FMC2_PCR_WEN : 0); + + reinit_completion(&nfc->complete); + stm32_fmc2_nfc_clear_bch_irq(nfc); + stm32_fmc2_nfc_enable_bch_irq(nfc, mode); + } + + stm32_fmc2_nfc_set_ecc(nfc, true); +} + +/* + * ECC Hamming calculation + * ECC is 3 bytes for 512 bytes of data (supports error correction up to + * max of 1-bit) + */ +static void stm32_fmc2_nfc_ham_set_ecc(const u32 ecc_sta, u8 *ecc) +{ + ecc[0] = ecc_sta; + ecc[1] = ecc_sta >> 8; + ecc[2] = ecc_sta >> 16; +} + +static int stm32_fmc2_nfc_ham_calculate(struct nand_chip *chip, const u8 *data, + u8 *ecc) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + u32 sr, heccr; + int ret; + + ret = regmap_read_poll_timeout(nfc->regmap, FMC2_SR, sr, + sr & FMC2_SR_NWRF, 1, + 1000 * FMC2_TIMEOUT_MS); + if (ret) { + dev_err(nfc->dev, "ham timeout\n"); + return ret; + } + + regmap_read(nfc->regmap, FMC2_HECCR, &heccr); + stm32_fmc2_nfc_ham_set_ecc(heccr, ecc); + stm32_fmc2_nfc_set_ecc(nfc, false); + + return 0; +} + +static int stm32_fmc2_nfc_ham_correct(struct nand_chip *chip, u8 *dat, + u8 *read_ecc, u8 *calc_ecc) +{ + u8 bit_position = 0, b0, b1, b2; + u32 byte_addr = 0, b; + u32 i, shifting = 1; + + /* Indicate which bit and byte is faulty (if any) */ + b0 = read_ecc[0] ^ calc_ecc[0]; + b1 = read_ecc[1] ^ calc_ecc[1]; + b2 = read_ecc[2] ^ calc_ecc[2]; + b = b0 | (b1 << 8) | (b2 << 16); + + /* No errors */ + if (likely(!b)) + return 0; + + /* Calculate bit position */ + for (i = 0; i < 3; i++) { + switch (b % 4) { + case 2: + bit_position += shifting; + break; + case 1: + break; + default: + return -EBADMSG; + } + shifting <<= 1; + b >>= 2; + } + + /* Calculate byte position */ + shifting = 1; + for (i = 0; i < 9; i++) { + switch (b % 4) { + case 2: + byte_addr += shifting; + break; + case 1: + break; + default: + return -EBADMSG; + } + shifting <<= 1; + b >>= 2; + } + + /* Flip the bit */ + dat[byte_addr] ^= (1 << bit_position); + + return 1; +} + +/* + * ECC BCH calculation and correction + * ECC is 7/13 bytes for 512 bytes of data (supports error correction up to + * max of 4-bit/8-bit) + */ +static int stm32_fmc2_nfc_bch_calculate(struct nand_chip *chip, const u8 *data, + u8 *ecc) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + u32 bchpbr; + + /* Wait until the BCH code is ready */ + if (!wait_for_completion_timeout(&nfc->complete, + msecs_to_jiffies(FMC2_TIMEOUT_MS))) { + dev_err(nfc->dev, "bch timeout\n"); + stm32_fmc2_nfc_disable_bch_irq(nfc); + return -ETIMEDOUT; + } + + /* Read parity bits */ + regmap_read(nfc->regmap, FMC2_BCHPBR1, &bchpbr); + ecc[0] = bchpbr; + ecc[1] = bchpbr >> 8; + ecc[2] = bchpbr >> 16; + ecc[3] = bchpbr >> 24; + + regmap_read(nfc->regmap, FMC2_BCHPBR2, &bchpbr); + ecc[4] = bchpbr; + ecc[5] = bchpbr >> 8; + ecc[6] = bchpbr >> 16; + + if (chip->ecc.strength == FMC2_ECC_BCH8) { + ecc[7] = bchpbr >> 24; + + regmap_read(nfc->regmap, FMC2_BCHPBR3, &bchpbr); + ecc[8] = bchpbr; + ecc[9] = bchpbr >> 8; + ecc[10] = bchpbr >> 16; + ecc[11] = bchpbr >> 24; + + regmap_read(nfc->regmap, FMC2_BCHPBR4, &bchpbr); + ecc[12] = bchpbr; + } + + stm32_fmc2_nfc_set_ecc(nfc, false); + + return 0; +} + +static int stm32_fmc2_nfc_bch_decode(int eccsize, u8 *dat, u32 *ecc_sta) +{ + u32 bchdsr0 = ecc_sta[0]; + u32 bchdsr1 = ecc_sta[1]; + u32 bchdsr2 = ecc_sta[2]; + u32 bchdsr3 = ecc_sta[3]; + u32 bchdsr4 = ecc_sta[4]; + u16 pos[8]; + int i, den; + unsigned int nb_errs = 0; + + /* No errors found */ + if (likely(!(bchdsr0 & FMC2_BCHDSR0_DEF))) + return 0; + + /* Too many errors detected */ + if (unlikely(bchdsr0 & FMC2_BCHDSR0_DUE)) + return -EBADMSG; + + pos[0] = FIELD_GET(FMC2_BCHDSR1_EBP1, bchdsr1); + pos[1] = FIELD_GET(FMC2_BCHDSR1_EBP2, bchdsr1); + pos[2] = FIELD_GET(FMC2_BCHDSR2_EBP3, bchdsr2); + pos[3] = FIELD_GET(FMC2_BCHDSR2_EBP4, bchdsr2); + pos[4] = FIELD_GET(FMC2_BCHDSR3_EBP5, bchdsr3); + pos[5] = FIELD_GET(FMC2_BCHDSR3_EBP6, bchdsr3); + pos[6] = FIELD_GET(FMC2_BCHDSR4_EBP7, bchdsr4); + pos[7] = FIELD_GET(FMC2_BCHDSR4_EBP8, bchdsr4); + + den = FIELD_GET(FMC2_BCHDSR0_DEN, bchdsr0); + for (i = 0; i < den; i++) { + if (pos[i] < eccsize * 8) { + change_bit(pos[i], (unsigned long *)dat); + nb_errs++; + } + } + + return nb_errs; +} + +static int stm32_fmc2_nfc_bch_correct(struct nand_chip *chip, u8 *dat, + u8 *read_ecc, u8 *calc_ecc) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + u32 ecc_sta[5]; + + /* Wait until the decoding error is ready */ + if (!wait_for_completion_timeout(&nfc->complete, + msecs_to_jiffies(FMC2_TIMEOUT_MS))) { + dev_err(nfc->dev, "bch timeout\n"); + stm32_fmc2_nfc_disable_bch_irq(nfc); + return -ETIMEDOUT; + } + + regmap_bulk_read(nfc->regmap, FMC2_BCHDSR0, ecc_sta, 5); + + stm32_fmc2_nfc_set_ecc(nfc, false); + + return stm32_fmc2_nfc_bch_decode(chip->ecc.size, dat, ecc_sta); +} + +static int stm32_fmc2_nfc_read_page(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret, i, s, stat, eccsize = chip->ecc.size; + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + int eccstrength = chip->ecc.strength; + u8 *p = buf; + u8 *ecc_calc = chip->ecc.calc_buf; + u8 *ecc_code = chip->ecc.code_buf; + unsigned int max_bitflips = 0; + + ret = nand_read_page_op(chip, page, 0, NULL, 0); + if (ret) + return ret; + + for (i = mtd->writesize + FMC2_BBM_LEN, s = 0; s < eccsteps; + s++, i += eccbytes, p += eccsize) { + chip->ecc.hwctl(chip, NAND_ECC_READ); + + /* Read the nand page sector (512 bytes) */ + ret = nand_change_read_column_op(chip, s * eccsize, p, + eccsize, false); + if (ret) + return ret; + + /* Read the corresponding ECC bytes */ + ret = nand_change_read_column_op(chip, i, ecc_code, + eccbytes, false); + if (ret) + return ret; + + /* Correct the data */ + stat = chip->ecc.correct(chip, p, ecc_code, ecc_calc); + if (stat == -EBADMSG) + /* Check for empty pages with bitflips */ + stat = nand_check_erased_ecc_chunk(p, eccsize, + ecc_code, eccbytes, + NULL, 0, + eccstrength); + + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + + /* Read oob */ + if (oob_required) { + ret = nand_change_read_column_op(chip, mtd->writesize, + chip->oob_poi, mtd->oobsize, + false); + if (ret) + return ret; + } + + return max_bitflips; +} + +/* Sequencer read/write configuration */ +static void stm32_fmc2_nfc_rw_page_init(struct nand_chip *chip, int page, + int raw, bool write_data) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + struct mtd_info *mtd = nand_to_mtd(chip); + u32 ecc_offset = mtd->writesize + FMC2_BBM_LEN; + /* + * cfg[0] => csqcfgr1, cfg[1] => csqcfgr2, cfg[2] => csqcfgr3 + * cfg[3] => csqar1, cfg[4] => csqar2 + */ + u32 cfg[5]; + + regmap_update_bits(nfc->regmap, FMC2_PCR, FMC2_PCR_WEN, + write_data ? FMC2_PCR_WEN : 0); + + /* + * - Set Program Page/Page Read command + * - Enable DMA request data + * - Set timings + */ + cfg[0] = FMC2_CSQCFGR1_DMADEN | FMC2_CSQCFGR1_CMD1T; + if (write_data) + cfg[0] |= FIELD_PREP(FMC2_CSQCFGR1_CMD1, NAND_CMD_SEQIN); + else + cfg[0] |= FIELD_PREP(FMC2_CSQCFGR1_CMD1, NAND_CMD_READ0) | + FMC2_CSQCFGR1_CMD2EN | + FIELD_PREP(FMC2_CSQCFGR1_CMD2, NAND_CMD_READSTART) | + FMC2_CSQCFGR1_CMD2T; + + /* + * - Set Random Data Input/Random Data Read command + * - Enable the sequencer to access the Spare data area + * - Enable DMA request status decoding for read + * - Set timings + */ + if (write_data) + cfg[1] = FIELD_PREP(FMC2_CSQCFGR2_RCMD1, NAND_CMD_RNDIN); + else + cfg[1] = FIELD_PREP(FMC2_CSQCFGR2_RCMD1, NAND_CMD_RNDOUT) | + FMC2_CSQCFGR2_RCMD2EN | + FIELD_PREP(FMC2_CSQCFGR2_RCMD2, NAND_CMD_RNDOUTSTART) | + FMC2_CSQCFGR2_RCMD1T | + FMC2_CSQCFGR2_RCMD2T; + if (!raw) { + cfg[1] |= write_data ? 0 : FMC2_CSQCFGR2_DMASEN; + cfg[1] |= FMC2_CSQCFGR2_SQSDTEN; + } + + /* + * - Set the number of sectors to be written + * - Set timings + */ + cfg[2] = FIELD_PREP(FMC2_CSQCFGR3_SNBR, chip->ecc.steps - 1); + if (write_data) { + cfg[2] |= FMC2_CSQCFGR3_RAC2T; + if (chip->options & NAND_ROW_ADDR_3) + cfg[2] |= FMC2_CSQCFGR3_AC5T; + else + cfg[2] |= FMC2_CSQCFGR3_AC4T; + } + + /* + * Set the fourth first address cycles + * Byte 1 and byte 2 => column, we start at 0x0 + * Byte 3 and byte 4 => page + */ + cfg[3] = FIELD_PREP(FMC2_CSQCAR1_ADDC3, page); + cfg[3] |= FIELD_PREP(FMC2_CSQCAR1_ADDC4, page >> 8); + + /* + * - Set chip enable number + * - Set ECC byte offset in the spare area + * - Calculate the number of address cycles to be issued + * - Set byte 5 of address cycle if needed + */ + cfg[4] = FIELD_PREP(FMC2_CSQCAR2_NANDCEN, nfc->cs_sel); + if (chip->options & NAND_BUSWIDTH_16) + cfg[4] |= FIELD_PREP(FMC2_CSQCAR2_SAO, ecc_offset >> 1); + else + cfg[4] |= FIELD_PREP(FMC2_CSQCAR2_SAO, ecc_offset); + if (chip->options & NAND_ROW_ADDR_3) { + cfg[0] |= FIELD_PREP(FMC2_CSQCFGR1_ACYNBR, 5); + cfg[4] |= FIELD_PREP(FMC2_CSQCAR2_ADDC5, page >> 16); + } else { + cfg[0] |= FIELD_PREP(FMC2_CSQCFGR1_ACYNBR, 4); + } + + regmap_bulk_write(nfc->regmap, FMC2_CSQCFGR1, cfg, 5); +} + +static void stm32_fmc2_nfc_dma_callback(void *arg) +{ + complete((struct completion *)arg); +} + +/* Read/write data from/to a page */ +static int stm32_fmc2_nfc_xfer(struct nand_chip *chip, const u8 *buf, + int raw, bool write_data) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + struct dma_async_tx_descriptor *desc_data, *desc_ecc; + struct scatterlist *sg; + struct dma_chan *dma_ch = nfc->dma_rx_ch; + enum dma_data_direction dma_data_dir = DMA_FROM_DEVICE; + enum dma_transfer_direction dma_transfer_dir = DMA_DEV_TO_MEM; + int eccsteps = chip->ecc.steps; + int eccsize = chip->ecc.size; + unsigned long timeout = msecs_to_jiffies(FMC2_TIMEOUT_MS); + const u8 *p = buf; + int s, ret; + + /* Configure DMA data */ + if (write_data) { + dma_data_dir = DMA_TO_DEVICE; + dma_transfer_dir = DMA_MEM_TO_DEV; + dma_ch = nfc->dma_tx_ch; + } + + for_each_sg(nfc->dma_data_sg.sgl, sg, eccsteps, s) { + sg_set_buf(sg, p, eccsize); + p += eccsize; + } + + ret = dma_map_sg(nfc->dev, nfc->dma_data_sg.sgl, + eccsteps, dma_data_dir); + if (!ret) + return -EIO; + + desc_data = dmaengine_prep_slave_sg(dma_ch, nfc->dma_data_sg.sgl, + eccsteps, dma_transfer_dir, + DMA_PREP_INTERRUPT); + if (!desc_data) { + ret = -ENOMEM; + goto err_unmap_data; + } + + reinit_completion(&nfc->dma_data_complete); + reinit_completion(&nfc->complete); + desc_data->callback = stm32_fmc2_nfc_dma_callback; + desc_data->callback_param = &nfc->dma_data_complete; + ret = dma_submit_error(dmaengine_submit(desc_data)); + if (ret) + goto err_unmap_data; + + dma_async_issue_pending(dma_ch); + + if (!write_data && !raw) { + /* Configure DMA ECC status */ + for_each_sg(nfc->dma_ecc_sg.sgl, sg, eccsteps, s) { + 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, + nfc->dma_ecc_sg.sgl, + eccsteps, dma_transfer_dir, + DMA_PREP_INTERRUPT); + if (!desc_ecc) { + ret = -ENOMEM; + goto err_unmap_data; + } + + reinit_completion(&nfc->dma_ecc_complete); + desc_ecc->callback = stm32_fmc2_nfc_dma_callback; + desc_ecc->callback_param = &nfc->dma_ecc_complete; + ret = dma_submit_error(dmaengine_submit(desc_ecc)); + if (ret) + goto err_unmap_data; + + dma_async_issue_pending(nfc->dma_ecc_ch); + } + + stm32_fmc2_nfc_clear_seq_irq(nfc); + stm32_fmc2_nfc_enable_seq_irq(nfc); + + /* Start the transfer */ + regmap_update_bits(nfc->regmap, FMC2_CSQCR, + FMC2_CSQCR_CSQSTART, FMC2_CSQCR_CSQSTART); + + /* Wait end of sequencer transfer */ + if (!wait_for_completion_timeout(&nfc->complete, timeout)) { + dev_err(nfc->dev, "seq timeout\n"); + stm32_fmc2_nfc_disable_seq_irq(nfc); + dmaengine_terminate_all(dma_ch); + if (!write_data && !raw) + dmaengine_terminate_all(nfc->dma_ecc_ch); + ret = -ETIMEDOUT; + goto err_unmap_data; + } + + /* Wait DMA data transfer completion */ + if (!wait_for_completion_timeout(&nfc->dma_data_complete, timeout)) { + dev_err(nfc->dev, "data DMA timeout\n"); + dmaengine_terminate_all(dma_ch); + ret = -ETIMEDOUT; + } + + /* Wait DMA ECC transfer completion */ + if (!write_data && !raw) { + if (!wait_for_completion_timeout(&nfc->dma_ecc_complete, + timeout)) { + dev_err(nfc->dev, "ECC DMA timeout\n"); + dmaengine_terminate_all(nfc->dma_ecc_ch); + ret = -ETIMEDOUT; + } + } + +err_unmap_data: + dma_unmap_sg(nfc->dev, nfc->dma_data_sg.sgl, eccsteps, dma_data_dir); + + return ret; +} + +static int stm32_fmc2_nfc_seq_write(struct nand_chip *chip, const u8 *buf, + int oob_required, int page, int raw) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + /* Configure the sequencer */ + stm32_fmc2_nfc_rw_page_init(chip, page, raw, true); + + /* Write the page */ + ret = stm32_fmc2_nfc_xfer(chip, buf, raw, true); + if (ret) + return ret; + + /* Write oob */ + if (oob_required) { + 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; + } + + return nand_prog_page_end_op(chip); +} + +static int stm32_fmc2_nfc_seq_write_page(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + int ret; + + ret = stm32_fmc2_nfc_select_chip(chip, chip->cur_cs); + if (ret) + return ret; + + return stm32_fmc2_nfc_seq_write(chip, buf, oob_required, page, false); +} + +static int stm32_fmc2_nfc_seq_write_page_raw(struct nand_chip *chip, + const u8 *buf, int oob_required, + int page) +{ + int ret; + + ret = stm32_fmc2_nfc_select_chip(chip, chip->cur_cs); + if (ret) + return ret; + + return stm32_fmc2_nfc_seq_write(chip, buf, oob_required, page, true); +} + +/* Get a status indicating which sectors have errors */ +static u16 stm32_fmc2_nfc_get_mapping_status(struct stm32_fmc2_nfc *nfc) +{ + u32 csqemsr; + + regmap_read(nfc->regmap, FMC2_CSQEMSR, &csqemsr); + + return FIELD_GET(FMC2_CSQEMSR_SEM, csqemsr); +} + +static int stm32_fmc2_nfc_seq_correct(struct nand_chip *chip, u8 *dat, + u8 *read_ecc, u8 *calc_ecc) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + int eccbytes = chip->ecc.bytes; + int eccsteps = chip->ecc.steps; + int eccstrength = chip->ecc.strength; + int i, s, eccsize = chip->ecc.size; + u32 *ecc_sta = (u32 *)nfc->ecc_buf; + u16 sta_map = stm32_fmc2_nfc_get_mapping_status(nfc); + unsigned int max_bitflips = 0; + + for (i = 0, s = 0; s < eccsteps; s++, i += eccbytes, dat += eccsize) { + int stat = 0; + + if (eccstrength == FMC2_ECC_HAM) { + /* Ecc_sta = FMC2_HECCR */ + if (sta_map & BIT(s)) { + stm32_fmc2_nfc_ham_set_ecc(*ecc_sta, + &calc_ecc[i]); + stat = stm32_fmc2_nfc_ham_correct(chip, dat, + &read_ecc[i], + &calc_ecc[i]); + } + ecc_sta++; + } else { + /* + * Ecc_sta[0] = FMC2_BCHDSR0 + * Ecc_sta[1] = FMC2_BCHDSR1 + * Ecc_sta[2] = FMC2_BCHDSR2 + * Ecc_sta[3] = FMC2_BCHDSR3 + * Ecc_sta[4] = FMC2_BCHDSR4 + */ + if (sta_map & BIT(s)) + stat = stm32_fmc2_nfc_bch_decode(eccsize, dat, + ecc_sta); + ecc_sta += 5; + } + + if (stat == -EBADMSG) + /* Check for empty pages with bitflips */ + stat = nand_check_erased_ecc_chunk(dat, eccsize, + &read_ecc[i], + eccbytes, + NULL, 0, + eccstrength); + + if (stat < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += stat; + max_bitflips = max_t(unsigned int, max_bitflips, stat); + } + } + + return max_bitflips; +} + +static int stm32_fmc2_nfc_seq_read_page(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + u8 *ecc_calc = chip->ecc.calc_buf; + u8 *ecc_code = chip->ecc.code_buf; + u16 sta_map; + int ret; + + ret = stm32_fmc2_nfc_select_chip(chip, chip->cur_cs); + if (ret) + return ret; + + /* Configure the sequencer */ + stm32_fmc2_nfc_rw_page_init(chip, page, 0, false); + + /* Read the page */ + ret = stm32_fmc2_nfc_xfer(chip, buf, 0, false); + if (ret) + return ret; + + sta_map = stm32_fmc2_nfc_get_mapping_status(nfc); + + /* Check if errors happen */ + if (likely(!sta_map)) { + if (oob_required) + return nand_change_read_column_op(chip, mtd->writesize, + chip->oob_poi, + mtd->oobsize, false); + + return 0; + } + + /* Read oob */ + ret = nand_change_read_column_op(chip, mtd->writesize, + chip->oob_poi, mtd->oobsize, false); + if (ret) + return ret; + + ret = mtd_ooblayout_get_eccbytes(mtd, ecc_code, chip->oob_poi, 0, + chip->ecc.total); + if (ret) + return ret; + + /* Correct data */ + return chip->ecc.correct(chip, buf, ecc_code, ecc_calc); +} + +static int stm32_fmc2_nfc_seq_read_page_raw(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + ret = stm32_fmc2_nfc_select_chip(chip, chip->cur_cs); + if (ret) + return ret; + + /* Configure the sequencer */ + stm32_fmc2_nfc_rw_page_init(chip, page, 1, false); + + /* Read the page */ + ret = stm32_fmc2_nfc_xfer(chip, buf, 1, false); + if (ret) + return ret; + + /* Read oob */ + if (oob_required) + return nand_change_read_column_op(chip, mtd->writesize, + chip->oob_poi, mtd->oobsize, + false); + + return 0; +} + +static irqreturn_t stm32_fmc2_nfc_irq(int irq, void *dev_id) +{ + struct stm32_fmc2_nfc *nfc = (struct stm32_fmc2_nfc *)dev_id; + + if (nfc->irq_state == FMC2_IRQ_SEQ) + /* Sequencer is used */ + stm32_fmc2_nfc_disable_seq_irq(nfc); + else if (nfc->irq_state == FMC2_IRQ_BCH) + /* BCH is used */ + stm32_fmc2_nfc_disable_bch_irq(nfc); + + complete(&nfc->complete); + + return IRQ_HANDLED; +} + +static void stm32_fmc2_nfc_read_data(struct nand_chip *chip, void *buf, + unsigned int len, bool force_8bit) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + void __iomem *io_addr_r = nfc->data_base[nfc->cs_sel]; + + if (force_8bit && chip->options & NAND_BUSWIDTH_16) + /* Reconfigure bus width to 8-bit */ + stm32_fmc2_nfc_set_buswidth_16(nfc, false); + + if (!IS_ALIGNED((uintptr_t)buf, sizeof(u32))) { + if (!IS_ALIGNED((uintptr_t)buf, sizeof(u16)) && len) { + *(u8 *)buf = readb_relaxed(io_addr_r); + buf += sizeof(u8); + len -= sizeof(u8); + } + + if (!IS_ALIGNED((uintptr_t)buf, sizeof(u32)) && + len >= sizeof(u16)) { + *(u16 *)buf = readw_relaxed(io_addr_r); + buf += sizeof(u16); + len -= sizeof(u16); + } + } + + /* Buf is aligned */ + while (len >= sizeof(u32)) { + *(u32 *)buf = readl_relaxed(io_addr_r); + buf += sizeof(u32); + len -= sizeof(u32); + } + + /* Read remaining bytes */ + if (len >= sizeof(u16)) { + *(u16 *)buf = readw_relaxed(io_addr_r); + buf += sizeof(u16); + len -= sizeof(u16); + } + + if (len) + *(u8 *)buf = readb_relaxed(io_addr_r); + + if (force_8bit && chip->options & NAND_BUSWIDTH_16) + /* Reconfigure bus width to 16-bit */ + stm32_fmc2_nfc_set_buswidth_16(nfc, true); +} + +static void stm32_fmc2_nfc_write_data(struct nand_chip *chip, const void *buf, + unsigned int len, bool force_8bit) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + void __iomem *io_addr_w = nfc->data_base[nfc->cs_sel]; + + if (force_8bit && chip->options & NAND_BUSWIDTH_16) + /* Reconfigure bus width to 8-bit */ + stm32_fmc2_nfc_set_buswidth_16(nfc, false); + + if (!IS_ALIGNED((uintptr_t)buf, sizeof(u32))) { + if (!IS_ALIGNED((uintptr_t)buf, sizeof(u16)) && len) { + writeb_relaxed(*(u8 *)buf, io_addr_w); + buf += sizeof(u8); + len -= sizeof(u8); + } + + if (!IS_ALIGNED((uintptr_t)buf, sizeof(u32)) && + len >= sizeof(u16)) { + writew_relaxed(*(u16 *)buf, io_addr_w); + buf += sizeof(u16); + len -= sizeof(u16); + } + } + + /* Buf is aligned */ + while (len >= sizeof(u32)) { + writel_relaxed(*(u32 *)buf, io_addr_w); + buf += sizeof(u32); + len -= sizeof(u32); + } + + /* Write remaining bytes */ + if (len >= sizeof(u16)) { + writew_relaxed(*(u16 *)buf, io_addr_w); + buf += sizeof(u16); + len -= sizeof(u16); + } + + if (len) + writeb_relaxed(*(u8 *)buf, io_addr_w); + + if (force_8bit && chip->options & NAND_BUSWIDTH_16) + /* Reconfigure bus width to 16-bit */ + stm32_fmc2_nfc_set_buswidth_16(nfc, true); +} + +static int stm32_fmc2_nfc_waitrdy(struct nand_chip *chip, + unsigned long timeout_ms) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + const struct nand_sdr_timings *timings; + u32 isr, sr; + + /* Check if there is no pending requests to the NAND flash */ + if (regmap_read_poll_timeout(nfc->regmap, FMC2_SR, sr, + sr & FMC2_SR_NWRF, 1, + 1000 * FMC2_TIMEOUT_MS)) + dev_warn(nfc->dev, "Waitrdy timeout\n"); + + /* Wait tWB before R/B# signal is low */ + timings = nand_get_sdr_timings(nand_get_interface_config(chip)); + ndelay(PSEC_TO_NSEC(timings->tWB_max)); + + /* R/B# signal is low, clear high level flag */ + regmap_write(nfc->regmap, FMC2_ICR, FMC2_ICR_CIHLF); + + /* Wait R/B# signal is high */ + return regmap_read_poll_timeout(nfc->regmap, FMC2_ISR, isr, + isr & FMC2_ISR_IHLF, 5, + 1000 * FMC2_TIMEOUT_MS); +} + +static int stm32_fmc2_nfc_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + const struct nand_op_instr *instr = NULL; + unsigned int op_id, i, timeout; + int ret; + + if (check_only) + return 0; + + ret = stm32_fmc2_nfc_select_chip(chip, op->cs); + if (ret) + return ret; + + for (op_id = 0; op_id < op->ninstrs; op_id++) { + instr = &op->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + writeb_relaxed(instr->ctx.cmd.opcode, + nfc->cmd_base[nfc->cs_sel]); + break; + + case NAND_OP_ADDR_INSTR: + for (i = 0; i < instr->ctx.addr.naddrs; i++) + writeb_relaxed(instr->ctx.addr.addrs[i], + nfc->addr_base[nfc->cs_sel]); + break; + + case NAND_OP_DATA_IN_INSTR: + stm32_fmc2_nfc_read_data(chip, instr->ctx.data.buf.in, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + break; + + case NAND_OP_DATA_OUT_INSTR: + stm32_fmc2_nfc_write_data(chip, instr->ctx.data.buf.out, + instr->ctx.data.len, + instr->ctx.data.force_8bit); + break; + + case NAND_OP_WAITRDY_INSTR: + timeout = instr->ctx.waitrdy.timeout_ms; + ret = stm32_fmc2_nfc_waitrdy(chip, timeout); + break; + } + } + + return ret; +} + +static void stm32_fmc2_nfc_init(struct stm32_fmc2_nfc *nfc) +{ + u32 pcr; + + regmap_read(nfc->regmap, FMC2_PCR, &pcr); + + /* Set CS used to undefined */ + nfc->cs_sel = -1; + + /* Enable wait feature and nand flash memory bank */ + pcr |= FMC2_PCR_PWAITEN; + pcr |= FMC2_PCR_PBKEN; + + /* Set buswidth to 8 bits mode for identification */ + pcr &= ~FMC2_PCR_PWID; + + /* ECC logic is disabled */ + pcr &= ~FMC2_PCR_ECCEN; + + /* Default mode */ + pcr &= ~FMC2_PCR_ECCALG; + pcr &= ~FMC2_PCR_BCHECC; + pcr &= ~FMC2_PCR_WEN; + + /* Set default ECC sector size */ + pcr &= ~FMC2_PCR_ECCSS; + pcr |= FIELD_PREP(FMC2_PCR_ECCSS, FMC2_PCR_ECCSS_2048); + + /* Set default tclr/tar timings */ + pcr &= ~FMC2_PCR_TCLR; + pcr |= FIELD_PREP(FMC2_PCR_TCLR, FMC2_PCR_TCLR_DEFAULT); + pcr &= ~FMC2_PCR_TAR; + pcr |= FIELD_PREP(FMC2_PCR_TAR, FMC2_PCR_TAR_DEFAULT); + + /* Enable FMC2 controller */ + if (nfc->dev == nfc->cdev) + regmap_update_bits(nfc->regmap, FMC2_BCR1, + FMC2_BCR1_FMC2EN, FMC2_BCR1_FMC2EN); + + regmap_write(nfc->regmap, FMC2_PCR, pcr); + regmap_write(nfc->regmap, FMC2_PMEM, FMC2_PMEM_DEFAULT); + regmap_write(nfc->regmap, FMC2_PATT, FMC2_PATT_DEFAULT); +} + +static void stm32_fmc2_nfc_calc_timings(struct nand_chip *chip, + const struct nand_sdr_timings *sdrt) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + struct stm32_fmc2_nand *nand = to_fmc2_nand(chip); + struct stm32_fmc2_timings *tims = &nand->timings; + unsigned long hclk = clk_get_rate(nfc->clk); + unsigned long hclkp = NSEC_PER_SEC / (hclk / 1000); + unsigned long timing, tar, tclr, thiz, twait; + unsigned long tset_mem, tset_att, thold_mem, thold_att; + + tar = max_t(unsigned long, hclkp, sdrt->tAR_min); + timing = DIV_ROUND_UP(tar, hclkp) - 1; + tims->tar = min_t(unsigned long, timing, FMC2_PCR_TIMING_MASK); + + tclr = max_t(unsigned long, hclkp, sdrt->tCLR_min); + timing = DIV_ROUND_UP(tclr, hclkp) - 1; + tims->tclr = min_t(unsigned long, timing, FMC2_PCR_TIMING_MASK); + + tims->thiz = FMC2_THIZ; + thiz = (tims->thiz + 1) * hclkp; + + /* + * tWAIT > tRP + * tWAIT > tWP + * tWAIT > tREA + tIO + */ + twait = max_t(unsigned long, hclkp, sdrt->tRP_min); + twait = max_t(unsigned long, twait, sdrt->tWP_min); + twait = max_t(unsigned long, twait, sdrt->tREA_max + FMC2_TIO); + timing = DIV_ROUND_UP(twait, hclkp); + tims->twait = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK); + + /* + * tSETUP_MEM > tCS - tWAIT + * tSETUP_MEM > tALS - tWAIT + * tSETUP_MEM > tDS - (tWAIT - tHIZ) + */ + tset_mem = hclkp; + if (sdrt->tCS_min > twait && (tset_mem < sdrt->tCS_min - twait)) + tset_mem = sdrt->tCS_min - twait; + if (sdrt->tALS_min > twait && (tset_mem < sdrt->tALS_min - twait)) + tset_mem = sdrt->tALS_min - twait; + if (twait > thiz && (sdrt->tDS_min > twait - thiz) && + (tset_mem < sdrt->tDS_min - (twait - thiz))) + tset_mem = sdrt->tDS_min - (twait - thiz); + timing = DIV_ROUND_UP(tset_mem, hclkp); + tims->tset_mem = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK); + + /* + * tHOLD_MEM > tCH + * tHOLD_MEM > tREH - tSETUP_MEM + * tHOLD_MEM > max(tRC, tWC) - (tSETUP_MEM + tWAIT) + */ + thold_mem = max_t(unsigned long, hclkp, sdrt->tCH_min); + if (sdrt->tREH_min > tset_mem && + (thold_mem < sdrt->tREH_min - tset_mem)) + thold_mem = sdrt->tREH_min - tset_mem; + if ((sdrt->tRC_min > tset_mem + twait) && + (thold_mem < sdrt->tRC_min - (tset_mem + twait))) + thold_mem = sdrt->tRC_min - (tset_mem + twait); + if ((sdrt->tWC_min > tset_mem + twait) && + (thold_mem < sdrt->tWC_min - (tset_mem + twait))) + thold_mem = sdrt->tWC_min - (tset_mem + twait); + timing = DIV_ROUND_UP(thold_mem, hclkp); + tims->thold_mem = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK); + + /* + * tSETUP_ATT > tCS - tWAIT + * tSETUP_ATT > tCLS - tWAIT + * tSETUP_ATT > tALS - tWAIT + * tSETUP_ATT > tRHW - tHOLD_MEM + * tSETUP_ATT > tDS - (tWAIT - tHIZ) + */ + tset_att = hclkp; + if (sdrt->tCS_min > twait && (tset_att < sdrt->tCS_min - twait)) + tset_att = sdrt->tCS_min - twait; + if (sdrt->tCLS_min > twait && (tset_att < sdrt->tCLS_min - twait)) + tset_att = sdrt->tCLS_min - twait; + if (sdrt->tALS_min > twait && (tset_att < sdrt->tALS_min - twait)) + tset_att = sdrt->tALS_min - twait; + if (sdrt->tRHW_min > thold_mem && + (tset_att < sdrt->tRHW_min - thold_mem)) + tset_att = sdrt->tRHW_min - thold_mem; + if (twait > thiz && (sdrt->tDS_min > twait - thiz) && + (tset_att < sdrt->tDS_min - (twait - thiz))) + tset_att = sdrt->tDS_min - (twait - thiz); + timing = DIV_ROUND_UP(tset_att, hclkp); + tims->tset_att = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK); + + /* + * tHOLD_ATT > tALH + * tHOLD_ATT > tCH + * tHOLD_ATT > tCLH + * tHOLD_ATT > tCOH + * tHOLD_ATT > tDH + * tHOLD_ATT > tWB + tIO + tSYNC - tSETUP_MEM + * tHOLD_ATT > tADL - tSETUP_MEM + * tHOLD_ATT > tWH - tSETUP_MEM + * tHOLD_ATT > tWHR - tSETUP_MEM + * tHOLD_ATT > tRC - (tSETUP_ATT + tWAIT) + * tHOLD_ATT > tWC - (tSETUP_ATT + tWAIT) + */ + thold_att = max_t(unsigned long, hclkp, sdrt->tALH_min); + thold_att = max_t(unsigned long, thold_att, sdrt->tCH_min); + thold_att = max_t(unsigned long, thold_att, sdrt->tCLH_min); + thold_att = max_t(unsigned long, thold_att, sdrt->tCOH_min); + thold_att = max_t(unsigned long, thold_att, sdrt->tDH_min); + if ((sdrt->tWB_max + FMC2_TIO + FMC2_TSYNC > tset_mem) && + (thold_att < sdrt->tWB_max + FMC2_TIO + FMC2_TSYNC - tset_mem)) + thold_att = sdrt->tWB_max + FMC2_TIO + FMC2_TSYNC - tset_mem; + if (sdrt->tADL_min > tset_mem && + (thold_att < sdrt->tADL_min - tset_mem)) + thold_att = sdrt->tADL_min - tset_mem; + if (sdrt->tWH_min > tset_mem && + (thold_att < sdrt->tWH_min - tset_mem)) + thold_att = sdrt->tWH_min - tset_mem; + if (sdrt->tWHR_min > tset_mem && + (thold_att < sdrt->tWHR_min - tset_mem)) + thold_att = sdrt->tWHR_min - tset_mem; + if ((sdrt->tRC_min > tset_att + twait) && + (thold_att < sdrt->tRC_min - (tset_att + twait))) + thold_att = sdrt->tRC_min - (tset_att + twait); + if ((sdrt->tWC_min > tset_att + twait) && + (thold_att < sdrt->tWC_min - (tset_att + twait))) + thold_att = sdrt->tWC_min - (tset_att + twait); + timing = DIV_ROUND_UP(thold_att, hclkp); + tims->thold_att = clamp_val(timing, 1, FMC2_PMEM_PATT_TIMING_MASK); +} + +static int stm32_fmc2_nfc_setup_interface(struct nand_chip *chip, int chipnr, + const struct nand_interface_config *conf) +{ + const struct nand_sdr_timings *sdrt; + + sdrt = nand_get_sdr_timings(conf); + if (IS_ERR(sdrt)) + return PTR_ERR(sdrt); + + if (conf->timings.mode > 3) + return -EOPNOTSUPP; + + if (chipnr == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + stm32_fmc2_nfc_calc_timings(chip, sdrt); + stm32_fmc2_nfc_timings_init(chip); + + return 0; +} + +static int stm32_fmc2_nfc_dma_setup(struct stm32_fmc2_nfc *nfc) +{ + struct dma_slave_caps caps; + int ret = 0; + + nfc->dma_tx_ch = dma_request_chan(nfc->dev, "tx"); + if (IS_ERR(nfc->dma_tx_ch)) { + ret = PTR_ERR(nfc->dma_tx_ch); + if (ret != -ENODEV && ret != -EPROBE_DEFER) + dev_err(nfc->dev, + "failed to request tx DMA channel: %d\n", ret); + nfc->dma_tx_ch = NULL; + goto err_dma; + } + + ret = dma_get_slave_caps(nfc->dma_tx_ch, &caps); + if (ret) + return ret; + nfc->tx_dma_max_burst = caps.max_burst; + + nfc->dma_rx_ch = dma_request_chan(nfc->dev, "rx"); + if (IS_ERR(nfc->dma_rx_ch)) { + ret = PTR_ERR(nfc->dma_rx_ch); + if (ret != -ENODEV && ret != -EPROBE_DEFER) + dev_err(nfc->dev, + "failed to request rx DMA channel: %d\n", ret); + nfc->dma_rx_ch = NULL; + goto err_dma; + } + + ret = dma_get_slave_caps(nfc->dma_rx_ch, &caps); + if (ret) + return ret; + nfc->rx_dma_max_burst = caps.max_burst; + + nfc->dma_ecc_ch = dma_request_chan(nfc->dev, "ecc"); + if (IS_ERR(nfc->dma_ecc_ch)) { + ret = PTR_ERR(nfc->dma_ecc_ch); + if (ret != -ENODEV && ret != -EPROBE_DEFER) + dev_err(nfc->dev, + "failed to request ecc DMA channel: %d\n", ret); + nfc->dma_ecc_ch = NULL; + goto err_dma; + } + + ret = sg_alloc_table(&nfc->dma_ecc_sg, FMC2_MAX_SG, GFP_KERNEL); + if (ret) + return ret; + + /* Allocate a buffer to store ECC status registers */ + 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; + + ret = sg_alloc_table(&nfc->dma_data_sg, FMC2_MAX_SG, GFP_KERNEL); + if (ret) + return ret; + + init_completion(&nfc->dma_data_complete); + init_completion(&nfc->dma_ecc_complete); + + return 0; + +err_dma: + if (ret == -ENODEV) { + dev_warn(nfc->dev, + "DMAs not defined in the DT, polling mode is used\n"); + ret = 0; + } + + return ret; +} + +static void stm32_fmc2_nfc_nand_callbacks_setup(struct nand_chip *chip) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + + /* + * Specific callbacks to read/write a page depending on + * the mode (polling/sequencer) and the algo used (Hamming, BCH). + */ + if (nfc->dma_tx_ch && nfc->dma_rx_ch && nfc->dma_ecc_ch) { + /* DMA => use sequencer mode callbacks */ + chip->ecc.correct = stm32_fmc2_nfc_seq_correct; + chip->ecc.write_page = stm32_fmc2_nfc_seq_write_page; + chip->ecc.read_page = stm32_fmc2_nfc_seq_read_page; + chip->ecc.write_page_raw = stm32_fmc2_nfc_seq_write_page_raw; + chip->ecc.read_page_raw = stm32_fmc2_nfc_seq_read_page_raw; + } else { + /* No DMA => use polling mode callbacks */ + chip->ecc.hwctl = stm32_fmc2_nfc_hwctl; + if (chip->ecc.strength == FMC2_ECC_HAM) { + /* Hamming is used */ + chip->ecc.calculate = stm32_fmc2_nfc_ham_calculate; + chip->ecc.correct = stm32_fmc2_nfc_ham_correct; + chip->ecc.options |= NAND_ECC_GENERIC_ERASED_CHECK; + } else { + /* BCH is used */ + chip->ecc.calculate = stm32_fmc2_nfc_bch_calculate; + chip->ecc.correct = stm32_fmc2_nfc_bch_correct; + chip->ecc.read_page = stm32_fmc2_nfc_read_page; + } + } + + /* Specific configurations depending on the algo used */ + if (chip->ecc.strength == FMC2_ECC_HAM) + chip->ecc.bytes = chip->options & NAND_BUSWIDTH_16 ? 4 : 3; + else if (chip->ecc.strength == FMC2_ECC_BCH8) + chip->ecc.bytes = chip->options & NAND_BUSWIDTH_16 ? 14 : 13; + else + chip->ecc.bytes = chip->options & NAND_BUSWIDTH_16 ? 8 : 7; +} + +static int stm32_fmc2_nfc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct nand_ecc_ctrl *ecc = &chip->ecc; + + if (section) + return -ERANGE; + + oobregion->length = ecc->total; + oobregion->offset = FMC2_BBM_LEN; + + return 0; +} + +static int stm32_fmc2_nfc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct nand_ecc_ctrl *ecc = &chip->ecc; + + if (section) + return -ERANGE; + + oobregion->length = mtd->oobsize - ecc->total - FMC2_BBM_LEN; + oobregion->offset = ecc->total + FMC2_BBM_LEN; + + return 0; +} + +static const struct mtd_ooblayout_ops stm32_fmc2_nfc_ooblayout_ops = { + .ecc = stm32_fmc2_nfc_ooblayout_ecc, + .free = stm32_fmc2_nfc_ooblayout_free, +}; + +static int stm32_fmc2_nfc_calc_ecc_bytes(int step_size, int strength) +{ + /* Hamming */ + if (strength == FMC2_ECC_HAM) + return 4; + + /* BCH8 */ + if (strength == FMC2_ECC_BCH8) + return 14; + + /* BCH4 */ + return 8; +} + +NAND_ECC_CAPS_SINGLE(stm32_fmc2_nfc_ecc_caps, stm32_fmc2_nfc_calc_ecc_bytes, + FMC2_ECC_STEP_SIZE, + FMC2_ECC_HAM, FMC2_ECC_BCH4, FMC2_ECC_BCH8); + +static int stm32_fmc2_nfc_attach_chip(struct nand_chip *chip) +{ + struct stm32_fmc2_nfc *nfc = to_stm32_nfc(chip->controller); + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + /* + * Only NAND_ECC_ENGINE_TYPE_ON_HOST mode is actually supported + * Hamming => ecc.strength = 1 + * BCH4 => ecc.strength = 4 + * BCH8 => ecc.strength = 8 + * ECC sector size = 512 + */ + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) { + dev_err(nfc->dev, + "nand_ecc_engine_type is not well defined in the DT\n"); + return -EINVAL; + } + + /* Default ECC settings in case they are not set in the device tree */ + if (!chip->ecc.size) + chip->ecc.size = FMC2_ECC_STEP_SIZE; + + if (!chip->ecc.strength) + chip->ecc.strength = FMC2_ECC_BCH8; + + ret = nand_ecc_choose_conf(chip, &stm32_fmc2_nfc_ecc_caps, + mtd->oobsize - FMC2_BBM_LEN); + if (ret) { + dev_err(nfc->dev, "no valid ECC settings set\n"); + return ret; + } + + if (mtd->writesize / chip->ecc.size > FMC2_MAX_SG) { + dev_err(nfc->dev, "nand page size is not supported\n"); + return -EINVAL; + } + + if (chip->bbt_options & NAND_BBT_USE_FLASH) + chip->bbt_options |= NAND_BBT_NO_OOB; + + stm32_fmc2_nfc_nand_callbacks_setup(chip); + + mtd_set_ooblayout(mtd, &stm32_fmc2_nfc_ooblayout_ops); + + stm32_fmc2_nfc_setup(chip); + + return 0; +} + +static const struct nand_controller_ops stm32_fmc2_nfc_controller_ops = { + .attach_chip = stm32_fmc2_nfc_attach_chip, + .exec_op = stm32_fmc2_nfc_exec_op, + .setup_interface = stm32_fmc2_nfc_setup_interface, +}; + +static void stm32_fmc2_nfc_wp_enable(struct stm32_fmc2_nand *nand) +{ + if (nand->wp_gpio) + gpiod_set_value(nand->wp_gpio, 1); +} + +static void stm32_fmc2_nfc_wp_disable(struct stm32_fmc2_nand *nand) +{ + if (nand->wp_gpio) + gpiod_set_value(nand->wp_gpio, 0); +} + +static int stm32_fmc2_nfc_parse_child(struct stm32_fmc2_nfc *nfc, + struct device_node *dn) +{ + struct stm32_fmc2_nand *nand = &nfc->nand; + u32 cs; + int ret, i; + + if (!of_get_property(dn, "reg", &nand->ncs)) + return -EINVAL; + + nand->ncs /= sizeof(u32); + if (!nand->ncs) { + dev_err(nfc->dev, "invalid reg property size\n"); + return -EINVAL; + } + + for (i = 0; i < nand->ncs; i++) { + ret = of_property_read_u32_index(dn, "reg", i, &cs); + if (ret) { + dev_err(nfc->dev, "could not retrieve reg property: %d\n", + ret); + return ret; + } + + if (cs >= nfc->data->max_ncs) { + dev_err(nfc->dev, "invalid reg value: %d\n", cs); + return -EINVAL; + } + + if (nfc->cs_assigned & BIT(cs)) { + dev_err(nfc->dev, "cs already assigned: %d\n", cs); + return -EINVAL; + } + + nfc->cs_assigned |= BIT(cs); + nand->cs_used[i] = cs; + } + + nand->wp_gpio = devm_fwnode_gpiod_get(nfc->dev, of_fwnode_handle(dn), + "wp", GPIOD_OUT_HIGH, "wp"); + if (IS_ERR(nand->wp_gpio)) { + ret = PTR_ERR(nand->wp_gpio); + if (ret != -ENOENT) + return dev_err_probe(nfc->dev, ret, + "failed to request WP GPIO\n"); + + nand->wp_gpio = NULL; + } + + nand_set_flash_node(&nand->chip, dn); + + return 0; +} + +static int stm32_fmc2_nfc_parse_dt(struct stm32_fmc2_nfc *nfc) +{ + struct device_node *dn = nfc->dev->of_node; + int nchips = of_get_child_count(dn); + int ret = 0; + + if (!nchips) { + dev_err(nfc->dev, "NAND chip not defined\n"); + return -EINVAL; + } + + if (nchips > 1) { + dev_err(nfc->dev, "too many NAND chips defined\n"); + return -EINVAL; + } + + for_each_child_of_node_scoped(dn, child) { + ret = stm32_fmc2_nfc_parse_child(nfc, child); + if (ret < 0) + return ret; + } + + return ret; +} + +static int stm32_fmc2_nfc_set_cdev(struct stm32_fmc2_nfc *nfc) +{ + struct device *dev = nfc->dev; + bool ebi_found = false; + + if (dev->parent && of_device_is_compatible(dev->parent->of_node, + "st,stm32mp1-fmc2-ebi")) + ebi_found = true; + + if (of_device_is_compatible(dev->of_node, "st,stm32mp1-fmc2-nfc")) { + if (ebi_found) { + nfc->cdev = dev->parent; + + return 0; + } + + return -EINVAL; + } + + if (ebi_found) + return -EINVAL; + + nfc->cdev = dev; + + return 0; +} + +static int stm32_fmc2_nfc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct reset_control *rstc; + struct stm32_fmc2_nfc *nfc; + struct stm32_fmc2_nand *nand; + struct resource *res; + struct mtd_info *mtd; + struct nand_chip *chip; + struct resource cres; + int chip_cs, mem_region, ret, irq; + int start_region = 0; + + nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->dev = dev; + nand_controller_init(&nfc->base); + nfc->base.ops = &stm32_fmc2_nfc_controller_ops; + + nfc->data = of_device_get_match_data(dev); + if (!nfc->data) + return -EINVAL; + + if (nfc->data->set_cdev) { + ret = nfc->data->set_cdev(nfc); + if (ret) + return ret; + } else { + nfc->cdev = dev->parent; + } + + ret = stm32_fmc2_nfc_parse_dt(nfc); + if (ret) + return ret; + + ret = of_address_to_resource(nfc->cdev->of_node, 0, &cres); + if (ret) + return ret; + + nfc->io_phys_addr = cres.start; + + nfc->regmap = device_node_to_regmap(nfc->cdev->of_node); + if (IS_ERR(nfc->regmap)) + return PTR_ERR(nfc->regmap); + + if (nfc->dev == nfc->cdev) + start_region = 1; + + for (chip_cs = 0, mem_region = start_region; chip_cs < nfc->data->max_ncs; + chip_cs++, mem_region += 3) { + if (!(nfc->cs_assigned & BIT(chip_cs))) + continue; + + nfc->data_base[chip_cs] = devm_platform_get_and_ioremap_resource(pdev, + mem_region, &res); + if (IS_ERR(nfc->data_base[chip_cs])) + return PTR_ERR(nfc->data_base[chip_cs]); + + nfc->data_phys_addr[chip_cs] = res->start; + + nfc->cmd_base[chip_cs] = devm_platform_ioremap_resource(pdev, mem_region + 1); + if (IS_ERR(nfc->cmd_base[chip_cs])) + return PTR_ERR(nfc->cmd_base[chip_cs]); + + nfc->addr_base[chip_cs] = devm_platform_ioremap_resource(pdev, mem_region + 2); + if (IS_ERR(nfc->addr_base[chip_cs])) + return PTR_ERR(nfc->addr_base[chip_cs]); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, stm32_fmc2_nfc_irq, 0, + dev_name(dev), nfc); + if (ret) { + dev_err(dev, "failed to request irq\n"); + return ret; + } + + init_completion(&nfc->complete); + + nfc->clk = devm_clk_get_enabled(nfc->cdev, NULL); + if (IS_ERR(nfc->clk)) { + dev_err(dev, "can not get and enable the clock\n"); + return PTR_ERR(nfc->clk); + } + + rstc = devm_reset_control_get(dev, NULL); + if (IS_ERR(rstc)) { + ret = PTR_ERR(rstc); + if (ret == -EPROBE_DEFER) + return ret; + } else { + reset_control_assert(rstc); + reset_control_deassert(rstc); + } + + ret = stm32_fmc2_nfc_dma_setup(nfc); + if (ret) + goto err_release_dma; + + stm32_fmc2_nfc_init(nfc); + + nand = &nfc->nand; + chip = &nand->chip; + mtd = nand_to_mtd(chip); + mtd->dev.parent = dev; + + chip->controller = &nfc->base; + chip->options |= NAND_BUSWIDTH_AUTO | NAND_NO_SUBPAGE_WRITE | + NAND_USES_DMA; + + stm32_fmc2_nfc_wp_disable(nand); + + /* Scan to find existence of the device */ + ret = nand_scan(chip, nand->ncs); + if (ret) + goto err_wp_enable; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) + goto err_nand_cleanup; + + platform_set_drvdata(pdev, nfc); + + return 0; + +err_nand_cleanup: + nand_cleanup(chip); + +err_wp_enable: + stm32_fmc2_nfc_wp_enable(nand); + +err_release_dma: + if (nfc->dma_ecc_ch) + dma_release_channel(nfc->dma_ecc_ch); + if (nfc->dma_tx_ch) + dma_release_channel(nfc->dma_tx_ch); + if (nfc->dma_rx_ch) + dma_release_channel(nfc->dma_rx_ch); + + sg_free_table(&nfc->dma_data_sg); + sg_free_table(&nfc->dma_ecc_sg); + + return ret; +} + +static void stm32_fmc2_nfc_remove(struct platform_device *pdev) +{ + struct stm32_fmc2_nfc *nfc = platform_get_drvdata(pdev); + struct stm32_fmc2_nand *nand = &nfc->nand; + struct nand_chip *chip = &nand->chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + + if (nfc->dma_ecc_ch) + dma_release_channel(nfc->dma_ecc_ch); + if (nfc->dma_tx_ch) + dma_release_channel(nfc->dma_tx_ch); + if (nfc->dma_rx_ch) + dma_release_channel(nfc->dma_rx_ch); + + sg_free_table(&nfc->dma_data_sg); + sg_free_table(&nfc->dma_ecc_sg); + + stm32_fmc2_nfc_wp_enable(nand); +} + +static int __maybe_unused stm32_fmc2_nfc_suspend(struct device *dev) +{ + struct stm32_fmc2_nfc *nfc = dev_get_drvdata(dev); + struct stm32_fmc2_nand *nand = &nfc->nand; + + clk_disable_unprepare(nfc->clk); + + stm32_fmc2_nfc_wp_enable(nand); + + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static int __maybe_unused stm32_fmc2_nfc_resume(struct device *dev) +{ + struct stm32_fmc2_nfc *nfc = dev_get_drvdata(dev); + struct stm32_fmc2_nand *nand = &nfc->nand; + int chip_cs, ret; + + pinctrl_pm_select_default_state(dev); + + ret = clk_prepare_enable(nfc->clk); + if (ret) { + dev_err(dev, "can not enable the clock\n"); + return ret; + } + + stm32_fmc2_nfc_init(nfc); + + stm32_fmc2_nfc_wp_disable(nand); + + for (chip_cs = 0; chip_cs < nfc->data->max_ncs; chip_cs++) { + if (!(nfc->cs_assigned & BIT(chip_cs))) + continue; + + nand_reset(&nand->chip, chip_cs); + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(stm32_fmc2_nfc_pm_ops, stm32_fmc2_nfc_suspend, + stm32_fmc2_nfc_resume); + +static const struct stm32_fmc2_nfc_data stm32_fmc2_nfc_mp1_data = { + .max_ncs = 2, + .set_cdev = stm32_fmc2_nfc_set_cdev, +}; + +static const struct stm32_fmc2_nfc_data stm32_fmc2_nfc_mp25_data = { + .max_ncs = 4, +}; + +static const struct of_device_id stm32_fmc2_nfc_match[] = { + { + .compatible = "st,stm32mp15-fmc2", + .data = &stm32_fmc2_nfc_mp1_data, + }, + { + .compatible = "st,stm32mp1-fmc2-nfc", + .data = &stm32_fmc2_nfc_mp1_data, + }, + { + .compatible = "st,stm32mp25-fmc2-nfc", + .data = &stm32_fmc2_nfc_mp25_data, + }, + {} +}; +MODULE_DEVICE_TABLE(of, stm32_fmc2_nfc_match); + +static struct platform_driver stm32_fmc2_nfc_driver = { + .probe = stm32_fmc2_nfc_probe, + .remove = stm32_fmc2_nfc_remove, + .driver = { + .name = "stm32_fmc2_nfc", + .of_match_table = stm32_fmc2_nfc_match, + .pm = &stm32_fmc2_nfc_pm_ops, + }, +}; +module_platform_driver(stm32_fmc2_nfc_driver); + +MODULE_AUTHOR("Christophe Kerello "); +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 new file mode 100644 index 000000000..45ccbce91 --- /dev/null +++ b/drivers/mtd/nand/raw/sunxi_nand.c @@ -0,0 +1,2816 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2013 Boris BREZILLON + * + * Derived from: + * https://github.com/yuq/sunxi-nfc-mtd + * Copyright (C) 2013 Qiang Yu + * + * https://github.com/hno/Allwinner-Info + * Copyright (C) 2013 Henrik Nordström + * + * Copyright (C) 2013 Dmitriy B. + * Copyright (C) 2013 Sergey Lapin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NFC_REG_CTL 0x0000 +#define NFC_REG_ST 0x0004 +#define NFC_REG_INT 0x0008 +#define NFC_REG_TIMING_CTL 0x000C +#define NFC_REG_TIMING_CFG 0x0010 +#define NFC_REG_ADDR_LOW 0x0014 +#define NFC_REG_ADDR_HIGH 0x0018 +#define NFC_REG_SECTOR_NUM 0x001C +#define NFC_REG_CNT 0x0020 +#define NFC_REG_CMD 0x0024 +#define NFC_REG_RCMD_SET 0x0028 +#define NFC_REG_WCMD_SET 0x002C +#define NFC_REG_A10_IO_DATA 0x0030 +#define NFC_REG_A23_IO_DATA 0x0300 +#define NFC_REG_ECC_CTL 0x0034 +#define NFC_REG_ECC_ST 0x0038 +#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_H6_MDMA_STA_DESC0_COMPLETE BIT(0) + +#define NFC_MDMA_DESC_LAST BIT(2) +#define NFC_MDMA_DESC_FIRST BIT(3) +#define NFC_MDMA_DESC_SIZE_MASK GENMASK(15, 0) + +#define NFC_RAM0_BASE 0x0400 +#define NFC_RAM1_BASE 0x0800 + +/* define bit use in NFC_CTL */ +#define NFC_EN BIT(0) +#define NFC_RESET BIT(1) +#define NFC_BUS_WIDTH_MSK BIT(2) +#define NFC_BUS_WIDTH_8 (0 << 2) +#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) +#define NFC_PAGE_SHIFT_MSK GENMASK(11, 8) +#define NFC_PAGE_SHIFT(x) (((x) < 10 ? 0 : (x) - 10) << 8) +#define NFC_SAM BIT(12) +#define NFC_RAM_METHOD BIT(14) +#define NFC_DMA_TYPE_NORMAL BIT(15) +#define NFC_DEBUG_CTL BIT(31) + +/* define bit use in NFC_ST */ +#define NFC_RB_B2R BIT(0) +#define NFC_CMD_INT_FLAG BIT(1) +#define NFC_DMA_INT_FLAG BIT(2) +#define NFC_CMD_FIFO_STATUS BIT(3) +#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) +#define NFC_CMD_INT_ENABLE BIT(1) +#define NFC_DMA_INT_ENABLE BIT(2) +#define NFC_INT_MASK (NFC_B2R_INT_ENABLE | \ + NFC_CMD_INT_ENABLE | \ + NFC_DMA_INT_ENABLE) + +/* 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) \ + (((tWB) & 0x3) | (((tADL) & 0x3) << 2) | \ + (((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) /* 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) +#define NFC_SEND_ADR BIT(19) +#define NFC_ACCESS_DIR BIT(20) +#define NFC_DATA_TRANS BIT(21) +#define NFC_SEND_CMD1 BIT(22) +#define NFC_WAIT_FLAG BIT(23) +#define NFC_SEND_CMD2 BIT(24) +#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) +#define NFC_NORMAL_OP (0 << 30) +#define NFC_ECC_OP (1 << 30) +#define NFC_PAGE_OP (2U << 30) + +/* define bit use in NFC_RCMD_SET */ +#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) +#define NFC_RND_WRITE_CMD_MSK GENMASK(15, 8) +#define NFC_READ_CMD0_MSK GENMASK(23, 16) +#define NFC_READ_CMD1_MSK GENMASK(31, 24) + +/* define bit use in NFC_ECC_CTL */ +#define NFC_ECC_EN BIT(0) +#define NFC_ECC_PIPELINE BIT(3) +#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(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(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_USER_DATA_LEN_MSK(step) \ + (0xf << (((step) % NFC_REG_USER_DATA_LEN_CAPACITY) * 4)) + +#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 + * + * @cs: the NAND CS id used to communicate with a NAND Chip + * @rb: the Ready/Busy pin ID. -1 means no R/B pin connected to the NFC + */ +struct sunxi_nand_chip_sel { + u8 cs; + s8 rb; +}; + +/** + * struct sunxi_nand_hw_ecc - stores information related to HW ECC support + * + * @ecc_ctl: ECC_CTL register value for this NAND chip + */ +struct sunxi_nand_hw_ecc { + u32 ecc_ctl; +}; + +#define SUNXI_NFC_TIMING_STEPS 4 + +/* Delay arrays contain internal NDFC clock cycles for field values 0 to 3. */ +struct sunxi_nfc_timings { + /* Internal clock cycles used by T1-T4, T7 and T11. */ + u8 setup_cycles; + s32 tWB[SUNXI_NFC_TIMING_STEPS]; + s32 tADL[SUNXI_NFC_TIMING_STEPS]; + s32 tWHR[SUNXI_NFC_TIMING_STEPS]; + s32 tRHW[SUNXI_NFC_TIMING_STEPS]; +}; + +/** + * struct sunxi_nand_chip - stores NAND chip device related information + * + * @node: used to store NAND chips into a list + * @nand: base NAND chip structure + * @ecc: ECC controller structure + * @clk_rate: clk_rate required for this NAND chip + * @timing_cfg: TIMING_CFG register value for this NAND chip + * @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; + struct nand_chip nand; + struct sunxi_nand_hw_ecc ecc; + 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); +}; + +static inline struct sunxi_nand_chip *to_sunxi_nand(struct nand_chip *nand) +{ + return container_of(nand, struct sunxi_nand_chip, nand); +} + +struct sunxi_nfc_mdma_desc { + __le32 config; + __le32 size; + __le32 buf; + __le32 next; +} __packed __aligned(4); + +/* + * NAND Controller capabilities structure: stores NAND controller capabilities + * for distinction between compatible strings. + * + * @has_mdma: Use A23/A33-style MBUS DMA registers + * @has_mdma_desc: MBUS DMA uses H6-style descriptors + * @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 + * @timings: Controller timing characteristics + */ +struct sunxi_nfc_caps { + bool has_mdma; + bool has_mdma_desc; + 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; + const struct sunxi_nfc_timings *timings; +}; + +/** + * struct sunxi_nfc - stores sunxi NAND controller information + * + * @controller: base controller structure + * @dev: parent device (used to print error messages) + * @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 + * @chips: a list containing all the NAND chips attached to this NAND + * controller + * @complete: a completion object used to wait for NAND controller events + * @dmac: the DMA channel attached to the NAND controller + * @use_mdma: use an internal MBUS DMA backend + * @mdma_desc: H6-style MBUS DMA descriptor + * @mdma_desc_dma: DMA address of @mdma_desc + * @caps: NAND Controller capabilities + */ +struct sunxi_nfc { + struct nand_controller controller; + struct device *dev; + 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; + struct list_head chips; + struct completion complete; + struct dma_chan *dmac; + bool use_mdma; + struct sunxi_nfc_mdma_desc *mdma_desc; + dma_addr_t mdma_desc_dma; + const struct sunxi_nfc_caps *caps; +}; + +static inline struct sunxi_nfc *to_sunxi_nfc(struct nand_controller *ctrl) +{ + return container_of(ctrl, struct sunxi_nfc, controller); +} + +static irqreturn_t sunxi_nfc_interrupt(int irq, void *dev_id) +{ + struct sunxi_nfc *nfc = dev_id; + u32 st = readl(nfc->regs + NFC_REG_ST); + u32 ien = readl(nfc->regs + NFC_REG_INT); + + if (!(ien & st)) + return IRQ_NONE; + + if ((ien & st) == ien) + complete(&nfc->complete); + + writel(st & NFC_INT_MASK, nfc->regs + NFC_REG_ST); + writel(~st & ien & NFC_INT_MASK, nfc->regs + NFC_REG_INT); + + return IRQ_HANDLED; +} + +static int sunxi_nfc_wait_events(struct sunxi_nfc *nfc, u32 events, + bool use_polling, unsigned int timeout_ms) +{ + int ret; + + if (events & ~NFC_INT_MASK) + return -EINVAL; + + if (!timeout_ms) + timeout_ms = NFC_DEFAULT_TIMEOUT_MS; + + if (!use_polling) { + init_completion(&nfc->complete); + + writel(events, nfc->regs + NFC_REG_INT); + + ret = wait_for_completion_timeout(&nfc->complete, + msecs_to_jiffies(timeout_ms)); + if (!ret) + ret = -ETIMEDOUT; + else + ret = 0; + + writel(0, nfc->regs + NFC_REG_INT); + } else { + u32 status; + + ret = readl_poll_timeout(nfc->regs + NFC_REG_ST, status, + (status & events) == events, 1, + timeout_ms * 1000); + } + + writel(events & NFC_INT_MASK, nfc->regs + NFC_REG_ST); + + if (ret) + dev_err(nfc->dev, "wait interrupt timedout\n"); + + return ret; +} + +static int sunxi_nfc_wait_cmd_fifo_empty(struct sunxi_nfc *nfc) +{ + u32 status; + int ret; + + ret = readl_poll_timeout(nfc->regs + NFC_REG_ST, status, + !(status & NFC_CMD_FIFO_STATUS), 1, + NFC_DEFAULT_TIMEOUT_MS * 1000); + if (ret) + dev_err(nfc->dev, "wait for empty cmd FIFO timedout\n"); + + return ret; +} + +static int sunxi_nfc_rst(struct sunxi_nfc *nfc) +{ + u32 ctl; + int ret; + + writel(0, nfc->regs + NFC_REG_ECC_CTL); + writel(NFC_RESET, nfc->regs + NFC_REG_CTL); + + ret = readl_poll_timeout(nfc->regs + NFC_REG_CTL, ctl, + !(ctl & NFC_RESET), 1, + NFC_DEFAULT_TIMEOUT_MS * 1000); + if (ret) + dev_err(nfc->dev, "wait for NAND controller reset timedout\n"); + + return ret; +} + +static int sunxi_nfc_dma_op_prepare(struct sunxi_nfc *nfc, const void *buf, + int chunksize, int nchunks, + enum dma_data_direction ddir, + struct scatterlist *sg) +{ + struct dma_async_tx_descriptor *dmad; + enum dma_transfer_direction tdir; + dma_addr_t buf_dma; + dma_cookie_t dmat; + int len = chunksize * nchunks; + u32 data_blocks = nchunks; + int ret; + + if (ddir == DMA_FROM_DEVICE) + tdir = DMA_DEV_TO_MEM; + else + tdir = DMA_MEM_TO_DEV; + + sg_init_one(sg, buf, len); + ret = dma_map_sg(nfc->dev, sg, 1, ddir); + if (!ret) + return -ENOMEM; + + buf_dma = sg_dma_address(sg); + + if (nfc->mdma_desc && + (len > NFC_MDMA_DESC_SIZE_MASK || !IS_ALIGNED(len, 8) || + !IS_ALIGNED(buf_dma, 4))) { + ret = -EINVAL; + goto err_unmap_buf; + } + + if (!nfc->use_mdma) { + dmad = dmaengine_prep_slave_sg(nfc->dmac, sg, 1, tdir, DMA_CTRL_ACK); + if (!dmad) { + ret = -EINVAL; + goto err_unmap_buf; + } + } + + writel(readl(nfc->regs + NFC_REG_CTL) | NFC_RAM_METHOD, + nfc->regs + NFC_REG_CTL); + + /* H6/H616 use one enable bit per ECC data block. */ + if (nfc->caps->has_mdma_desc) + data_blocks = GENMASK(nchunks - 1, 0); + writel(data_blocks, nfc->regs + NFC_REG_SECTOR_NUM); + writel(chunksize, nfc->regs + NFC_REG_CNT); + + if (nfc->use_mdma) + writel(readl(nfc->regs + NFC_REG_CTL) & ~NFC_DMA_TYPE_NORMAL, + nfc->regs + NFC_REG_CTL); + + if (nfc->mdma_desc) { + struct sunxi_nfc_mdma_desc *desc = nfc->mdma_desc; + + desc->config = cpu_to_le32(NFC_MDMA_DESC_FIRST | + NFC_MDMA_DESC_LAST); + desc->size = cpu_to_le32(len); + /* Descriptor words are little-endian DMA memory, not MMIO. */ + desc->buf = cpu_to_le32(buf_dma); + desc->next = cpu_to_le32(nfc->mdma_desc_dma); + + writel(NFC_H6_MDMA_STA_DESC0_COMPLETE, + nfc->regs + NFC_REG_H6_MDMA_STA); + dma_wmb(); + writel(nfc->mdma_desc_dma, + nfc->regs + NFC_REG_H6_MDMA_DLBA_REG); + } else if (nfc->caps->has_mdma) { + writel(len, nfc->regs + NFC_REG_MDMA_CNT); + writel(buf_dma, nfc->regs + NFC_REG_MDMA_ADDR); + } else { + dmat = dmaengine_submit(dmad); + + ret = dma_submit_error(dmat); + if (ret) + goto err_clr_dma_flag; + } + + return 0; + +err_clr_dma_flag: + writel(readl(nfc->regs + NFC_REG_CTL) & ~NFC_RAM_METHOD, + nfc->regs + NFC_REG_CTL); + +err_unmap_buf: + dma_unmap_sg(nfc->dev, sg, 1, ddir); + return ret; +} + +static void sunxi_nfc_dma_op_cleanup(struct sunxi_nfc *nfc, + enum dma_data_direction ddir, + struct scatterlist *sg) +{ + dma_unmap_sg(nfc->dev, sg, 1, ddir); + writel(readl(nfc->regs + NFC_REG_CTL) & ~NFC_RAM_METHOD, + nfc->regs + NFC_REG_CTL); +} + +static void sunxi_nfc_dma_op_abort(struct sunxi_nfc *nfc) +{ + if (nfc->use_mdma) + sunxi_nfc_rst(nfc); + else + dmaengine_terminate_all(nfc->dmac); +} + +static void sunxi_nfc_select_chip(struct nand_chip *nand, unsigned int cs) +{ + struct mtd_info *mtd = nand_to_mtd(nand); + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller); + struct sunxi_nand_chip_sel *sel; + u32 ctl; + + if (cs >= sunxi_nand->nsels) + return; + + ctl = readl(nfc->regs + NFC_REG_CTL) & + ~(NFC_PAGE_SHIFT_MSK | NFC_CE_SEL_MSK | NFC_RB_SEL_MSK | NFC_EN); + + sel = &sunxi_nand->sels[cs]; + ctl |= NFC_CE_SEL(sel->cs) | NFC_EN | NFC_PAGE_SHIFT(nand->page_shift); + if (sel->rb >= 0) + ctl |= NFC_RB_SEL(sel->rb); + + 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); + nfc->clk_rate = sunxi_nand->clk_rate; + } + + writel(sunxi_nand->timing_ctl, nfc->regs + NFC_REG_TIMING_CTL); + writel(sunxi_nand->timing_cfg, nfc->regs + NFC_REG_TIMING_CFG); + writel(ctl, nfc->regs + NFC_REG_CTL); +} + +static void sunxi_nfc_read_buf(struct nand_chip *nand, uint8_t *buf, int len) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller); + int ret; + int cnt; + int offs = 0; + u32 tmp; + + while (len > offs) { + bool poll = false; + + cnt = min(len - offs, nfc->caps->sram_size); + + ret = sunxi_nfc_wait_cmd_fifo_empty(nfc); + if (ret) + break; + + writel(cnt, nfc->regs + NFC_REG_CNT); + tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD; + writel(tmp, nfc->regs + NFC_REG_CMD); + + /* Arbitrary limit for polling mode */ + if (cnt < 64) + poll = true; + + ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, poll, 0); + if (ret) + break; + + if (buf) + memcpy_fromio(buf + offs, nfc->regs + NFC_RAM0_BASE, + cnt); + offs += cnt; + } +} + +static void sunxi_nfc_write_buf(struct nand_chip *nand, const uint8_t *buf, + int len) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller); + int ret; + int cnt; + int offs = 0; + u32 tmp; + + while (len > offs) { + bool poll = false; + + cnt = min(len - offs, nfc->caps->sram_size); + + ret = sunxi_nfc_wait_cmd_fifo_empty(nfc); + if (ret) + break; + + writel(cnt, nfc->regs + NFC_REG_CNT); + memcpy_toio(nfc->regs + NFC_RAM0_BASE, buf + offs, cnt); + tmp = NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD | + NFC_ACCESS_DIR; + writel(tmp, nfc->regs + NFC_REG_CMD); + + /* Arbitrary limit for polling mode */ + if (cnt < 64) + poll = true; + + ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, poll, 0); + if (ret) + break; + + offs += cnt; + } +} + +/* These seed values have been extracted from Allwinner's BSP */ +static const u16 sunxi_nfc_randomizer_page_seeds[] = { + 0x2b75, 0x0bd0, 0x5ca3, 0x62d1, 0x1c93, 0x07e9, 0x2162, 0x3a72, + 0x0d67, 0x67f9, 0x1be7, 0x077d, 0x032f, 0x0dac, 0x2716, 0x2436, + 0x7922, 0x1510, 0x3860, 0x5287, 0x480f, 0x4252, 0x1789, 0x5a2d, + 0x2a49, 0x5e10, 0x437f, 0x4b4e, 0x2f45, 0x216e, 0x5cb7, 0x7130, + 0x2a3f, 0x60e4, 0x4dc9, 0x0ef0, 0x0f52, 0x1bb9, 0x6211, 0x7a56, + 0x226d, 0x4ea7, 0x6f36, 0x3692, 0x38bf, 0x0c62, 0x05eb, 0x4c55, + 0x60f4, 0x728c, 0x3b6f, 0x2037, 0x7f69, 0x0936, 0x651a, 0x4ceb, + 0x6218, 0x79f3, 0x383f, 0x18d9, 0x4f05, 0x5c82, 0x2912, 0x6f17, + 0x6856, 0x5938, 0x1007, 0x61ab, 0x3e7f, 0x57c2, 0x542f, 0x4f62, + 0x7454, 0x2eac, 0x7739, 0x42d4, 0x2f90, 0x435a, 0x2e52, 0x2064, + 0x637c, 0x66ad, 0x2c90, 0x0bad, 0x759c, 0x0029, 0x0986, 0x7126, + 0x1ca7, 0x1605, 0x386a, 0x27f5, 0x1380, 0x6d75, 0x24c3, 0x0f8e, + 0x2b7a, 0x1418, 0x1fd1, 0x7dc1, 0x2d8e, 0x43af, 0x2267, 0x7da3, + 0x4e3d, 0x1338, 0x50db, 0x454d, 0x764d, 0x40a3, 0x42e6, 0x262b, + 0x2d2e, 0x1aea, 0x2e17, 0x173d, 0x3a6e, 0x71bf, 0x25f9, 0x0a5d, + 0x7c57, 0x0fbe, 0x46ce, 0x4939, 0x6b17, 0x37bb, 0x3e91, 0x76db, +}; + +/* + * sunxi_nfc_randomizer_ecc512_seeds and sunxi_nfc_randomizer_ecc1024_seeds + * have been generated using + * sunxi_nfc_randomizer_step(seed, (step_size * 8) + 15), which is what + * the randomizer engine does internally before de/scrambling OOB data. + * + * Those tables are statically defined to avoid calculating randomizer state + * at runtime. + */ +static const u16 sunxi_nfc_randomizer_ecc512_seeds[] = { + 0x3346, 0x367f, 0x1f18, 0x769a, 0x4f64, 0x068c, 0x2ef1, 0x6b64, + 0x28a9, 0x15d7, 0x30f8, 0x3659, 0x53db, 0x7c5f, 0x71d4, 0x4409, + 0x26eb, 0x03cc, 0x655d, 0x47d4, 0x4daa, 0x0877, 0x712d, 0x3617, + 0x3264, 0x49aa, 0x7f9e, 0x588e, 0x4fbc, 0x7176, 0x7f91, 0x6c6d, + 0x4b95, 0x5fb7, 0x3844, 0x4037, 0x0184, 0x081b, 0x0ee8, 0x5b91, + 0x293d, 0x1f71, 0x0e6f, 0x402b, 0x5122, 0x1e52, 0x22be, 0x3d2d, + 0x75bc, 0x7c60, 0x6291, 0x1a2f, 0x61d4, 0x74aa, 0x4140, 0x29ab, + 0x472d, 0x2852, 0x017e, 0x15e8, 0x5ec2, 0x17cf, 0x7d0f, 0x06b8, + 0x117a, 0x6b94, 0x789b, 0x3126, 0x6ac5, 0x5be7, 0x150f, 0x51f8, + 0x7889, 0x0aa5, 0x663d, 0x77e8, 0x0b87, 0x3dcb, 0x360d, 0x218b, + 0x512f, 0x7dc9, 0x6a4d, 0x630a, 0x3547, 0x1dd2, 0x5aea, 0x69a5, + 0x7bfa, 0x5e4f, 0x1519, 0x6430, 0x3a0e, 0x5eb3, 0x5425, 0x0c7a, + 0x5540, 0x3670, 0x63c1, 0x31e9, 0x5a39, 0x2de7, 0x5979, 0x2891, + 0x1562, 0x014b, 0x5b05, 0x2756, 0x5a34, 0x13aa, 0x6cb5, 0x2c36, + 0x5e72, 0x1306, 0x0861, 0x15ef, 0x1ee8, 0x5a37, 0x7ac4, 0x45dd, + 0x44c4, 0x7266, 0x2f41, 0x3ccc, 0x045e, 0x7d40, 0x7c66, 0x0fa0, +}; + +static const u16 sunxi_nfc_randomizer_ecc1024_seeds[] = { + 0x2cf5, 0x35f1, 0x63a4, 0x5274, 0x2bd2, 0x778b, 0x7285, 0x32b6, + 0x6a5c, 0x70d6, 0x757d, 0x6769, 0x5375, 0x1e81, 0x0cf3, 0x3982, + 0x6787, 0x042a, 0x6c49, 0x1925, 0x56a8, 0x40a9, 0x063e, 0x7bd9, + 0x4dbf, 0x55ec, 0x672e, 0x7334, 0x5185, 0x4d00, 0x232a, 0x7e07, + 0x445d, 0x6b92, 0x528f, 0x4255, 0x53ba, 0x7d82, 0x2a2e, 0x3a4e, + 0x75eb, 0x450c, 0x6844, 0x1b5d, 0x581a, 0x4cc6, 0x0379, 0x37b2, + 0x419f, 0x0e92, 0x6b27, 0x5624, 0x01e3, 0x07c1, 0x44a5, 0x130c, + 0x13e8, 0x5910, 0x0876, 0x60c5, 0x54e3, 0x5b7f, 0x2269, 0x509f, + 0x7665, 0x36fd, 0x3e9a, 0x0579, 0x6295, 0x14ef, 0x0a81, 0x1bcc, + 0x4b16, 0x64db, 0x0514, 0x4f07, 0x0591, 0x3576, 0x6853, 0x0d9e, + 0x259f, 0x38b7, 0x64fb, 0x3094, 0x4693, 0x6ddd, 0x29bb, 0x0bc8, + 0x3f47, 0x490e, 0x0c0e, 0x7933, 0x3c9e, 0x5840, 0x398d, 0x3e68, + 0x4af1, 0x71f5, 0x57cf, 0x1121, 0x64eb, 0x3579, 0x15ac, 0x584d, + 0x5f2a, 0x47e2, 0x6528, 0x6eac, 0x196e, 0x6b96, 0x0450, 0x0179, + 0x609c, 0x06e1, 0x4626, 0x42c7, 0x273e, 0x486f, 0x0705, 0x1601, + 0x145b, 0x407e, 0x062b, 0x57a5, 0x53f9, 0x5659, 0x4410, 0x3ccd, +}; + +static u16 sunxi_nfc_randomizer_step(u16 state, int count) +{ + state &= 0x7fff; + + /* + * This loop is just a simple implementation of a Fibonacci LFSR using + * the x16 + x15 + 1 polynomial. + */ + while (count--) + state = ((state >> 1) | + (((state ^ (state >> 1)) & 1) << 14)) & 0x7fff; + + return state; +} + +static u16 sunxi_nfc_randomizer_state(struct nand_chip *nand, int page, + bool ecc) +{ + struct mtd_info *mtd = nand_to_mtd(nand); + const u16 *seeds = sunxi_nfc_randomizer_page_seeds; + int mod = mtd_div_by_ws(mtd->erasesize, mtd); + + if (mod > ARRAY_SIZE(sunxi_nfc_randomizer_page_seeds)) + mod = ARRAY_SIZE(sunxi_nfc_randomizer_page_seeds); + + if (ecc) { + if (mtd->ecc_step_size == 512) + seeds = sunxi_nfc_randomizer_ecc512_seeds; + else + seeds = sunxi_nfc_randomizer_ecc1024_seeds; + } + + return seeds[page % mod]; +} + +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; + u16 state; + + if (!(nand->options & NAND_NEED_SCRAMBLING)) + return; + + 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); +} + +static void sunxi_nfc_randomizer_enable(struct nand_chip *nand) +{ + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + + if (!(nand->options & NAND_NEED_SCRAMBLING)) + return; + + writel(readl(nfc->regs + NFC_REG_ECC_CTL) | NFC_RANDOM_EN(nfc), + nfc->regs + NFC_REG_ECC_CTL); +} + +static void sunxi_nfc_randomizer_disable(struct nand_chip *nand) +{ + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + + if (!(nand->options & NAND_NEED_SCRAMBLING)) + return; + + writel(readl(nfc->regs + NFC_REG_ECC_CTL) & ~NFC_RANDOM_EN(nfc), + nfc->regs + NFC_REG_ECC_CTL); +} + +static void sunxi_nfc_randomize_bbm(struct nand_chip *nand, int page, u8 *bbm) +{ + u16 state = sunxi_nfc_randomizer_state(nand, page, true); + + bbm[0] ^= state; + bbm[1] ^= sunxi_nfc_randomizer_step(state, 8); +} + +static void sunxi_nfc_randomizer_write_buf(struct nand_chip *nand, + const uint8_t *buf, int len, + bool ecc, int page) +{ + sunxi_nfc_randomizer_config(nand, page, ecc); + sunxi_nfc_randomizer_enable(nand); + sunxi_nfc_write_buf(nand, buf, len); + sunxi_nfc_randomizer_disable(nand); +} + +static void sunxi_nfc_randomizer_read_buf(struct nand_chip *nand, uint8_t *buf, + int len, bool ecc, int page) +{ + sunxi_nfc_randomizer_config(nand, page, ecc); + sunxi_nfc_randomizer_enable(nand); + sunxi_nfc_read_buf(nand, buf, len); + sunxi_nfc_randomizer_disable(nand); +} + +static void sunxi_nfc_hw_ecc_enable(struct nand_chip *nand) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + + writel(sunxi_nand->ecc.ecc_ctl, nfc->regs + NFC_REG_ECC_CTL); +} + +static void sunxi_nfc_hw_ecc_disable(struct nand_chip *nand) +{ + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + + writel(0, nfc->regs + NFC_REG_ECC_CTL); +} + +static inline void sunxi_nfc_user_data_to_buf(u32 user_data, u8 *buf) +{ + buf[0] = user_data; + buf[1] = user_data >> 8; + buf[2] = user_data >> 16; + buf[3] = user_data >> 24; +} + +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, + 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; + + 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); + 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)) { + 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; + } + + 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, + unsigned int *max_bitflips, int ret) +{ + struct mtd_info *mtd = nand_to_mtd(nand); + + if (ret < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += ret; + *max_bitflips = max_t(unsigned int, *max_bitflips, ret); + } +} + +static int sunxi_nfc_hw_ecc_correct(struct nand_chip *nand, u8 *data, u8 *oob, + 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; + + *erased = false; + + if (status & NFC_ECC_ERR(step)) + return -EBADMSG; + + if (pattern_found & BIT(step)) { + u8 pattern; + + if (unlikely(!(readl(nfc->regs + NFC_REG_PAT_ID(nfc)) & 0x1))) { + pattern = 0x0; + } else { + pattern = 0xff; + *erased = true; + } + + if (data) + memset(data, pattern, ecc->size); + + if (oob) + memset(oob, pattern, ecc->bytes + user_data_sz); + + return 0; + } + + tmp = readl(nfc->regs + NFC_REG_ECC_ERR_CNT(nfc, step)); + + return NFC_ECC_ERR_CNT(step, tmp); +} + +static int sunxi_nfc_hw_ecc_read_chunk(struct nand_chip *nand, + u8 *data, int data_off, + u8 *oob, int oob_off, + int *cur_off, + unsigned int *max_bitflips, + 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); + + sunxi_nfc_randomizer_read_buf(nand, NULL, ecc->size, false, page); + + if (data_off + ecc->size != oob_off) + nand_change_read_column_op(nand, oob_off, NULL, 0, false); + + ret = sunxi_nfc_wait_cmd_fifo_empty(nfc); + 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); + + ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, false, 0); + sunxi_nfc_randomizer_disable(nand); + if (ret) + return ret; + + *cur_off = oob_off + ecc->bytes + user_data_sz; + + 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; + + if (ret < 0) { + /* + * Re-read the data with the randomizer disabled to identify + * bitflips in erased pages. + */ + if (nand->options & NAND_NEED_SCRAMBLING) + nand_change_read_column_op(nand, data_off, data, + ecc->size, false); + else + memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, + ecc->size); + + 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 + user_data_sz, + NULL, 0, ecc->strength); + if (ret >= 0) + raw_mode = 1; + } else { + memcpy_fromio(data, nfc->regs + NFC_RAM0_BASE, ecc->size); + + if (oob_required) { + nand_change_read_column_op(nand, oob_off, NULL, 0, + false); + sunxi_nfc_randomizer_read_buf(nand, oob, ecc->bytes + user_data_sz, + true, page); + + sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, nfc_step, + bbm, page, user_data_sz); + } + } + + sunxi_nfc_hw_ecc_update_stats(nand, max_bitflips, ret); + + 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 = sunxi_get_oob_offset(sunxi_nand, ecc, ecc->steps); + int len = mtd->oobsize - offset; + + if (len <= 0) + return; + + 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); + else + sunxi_nfc_randomizer_read_buf(nand, oob + offset, len, + false, page); + + if (cur_off) + *cur_off = mtd->oobsize + mtd->writesize; +} + +static int sunxi_nfc_hw_ecc_read_chunks_dma(struct nand_chip *nand, uint8_t *buf, + int oob_required, int page, + 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, pattern_found, wait; + + ret = sunxi_nfc_wait_cmd_fifo_empty(nfc); + if (ret) + return ret; + + ret = sunxi_nfc_dma_op_prepare(nfc, buf, ecc->size, nchunks, + DMA_FROM_DEVICE, &sg); + if (ret) + 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); + + writel((NAND_CMD_RNDOUTSTART << 16) | (NAND_CMD_RNDOUT << 8) | + NAND_CMD_READSTART, nfc->regs + NFC_REG_RCMD_SET); + + wait = NFC_CMD_INT_FLAG; + + if (nfc->use_mdma) + wait |= NFC_DMA_INT_FLAG; + else + dma_async_issue_pending(nfc->dmac); + + writel(NFC_PAGE_OP | NFC_DATA_SWAP_METHOD | NFC_DATA_TRANS, + nfc->regs + NFC_REG_CMD); + + ret = sunxi_nfc_wait_events(nfc, wait, false, 0); + if (ret) + sunxi_nfc_dma_op_abort(nfc); + + sunxi_nfc_randomizer_disable(nand); + sunxi_nfc_hw_ecc_disable(nand); + + sunxi_nfc_dma_op_cleanup(nfc, DMA_FROM_DEVICE, &sg); + + if (ret) + 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; + 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, pattern_found, + &erased); + + /* ECC errors are handled in the second loop. */ + if (ret < 0) + continue; + + if (oob_required && !erased) { + /* TODO: use DMA to retrieve OOB */ + nand_change_read_column_op(nand, + mtd->writesize + oob_off, + oob, ecc->bytes + user_data_sz, false); + + sunxi_nfc_hw_ecc_get_prot_oob_bytes(nand, oob, i, !i, + page, user_data_sz); + } + + if (erased) + raw_mode = 1; + + sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, ret); + } + + if (status & NFC_ECC_ERR_MSK(nfc)) { + for (i = 0; i < nchunks; i++) { + int data_off = i * ecc->size; + 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; + + if (!(status & NFC_ECC_ERR(i))) + continue; + + /* + * Re-read the data with the randomizer disabled to + * identify bitflips in erased pages. + * TODO: use DMA to read page in raw mode + */ + if (randomized) + nand_change_read_column_op(nand, data_off, + data, ecc->size, + false); + + /* TODO: use DMA to retrieve OOB */ + nand_change_read_column_op(nand, + mtd->writesize + oob_off, + oob, ecc->bytes + user_data_sz, false); + + 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; + + sunxi_nfc_hw_ecc_update_stats(nand, &max_bitflips, ret); + } + } + + if (oob_required) + sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, + NULL, !raw_mode, + page); + + return max_bitflips; +} + +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, 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); + + sunxi_nfc_randomizer_write_buf(nand, data, ecc->size, false, page); + + if (data_off + ecc->size != oob_off) + nand_change_write_column_op(nand, oob_off, NULL, 0, false); + + ret = sunxi_nfc_wait_cmd_fifo_empty(nfc); + if (ret) + return ret; + + sunxi_nfc_randomizer_config(nand, page, false); + sunxi_nfc_randomizer_enable(nand); + 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, + nfc->regs + NFC_REG_CMD); + + ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, false, 0); + sunxi_nfc_randomizer_disable(nand); + if (ret) + return ret; + + *cur_off = oob_off + ecc->bytes + user_data_sz; + + return 0; +} + +static void sunxi_nfc_hw_ecc_write_extra_oob(struct nand_chip *nand, + u8 *oob, int *cur_off, + 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 = 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_write_column_op(nand, offset + mtd->writesize, + NULL, 0, false); + + sunxi_nfc_randomizer_write_buf(nand, oob + offset, len, false, page); + + if (cur_off) + *cur_off = mtd->oobsize + mtd->writesize; +} + +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; + int ret, i, cur_off = 0; + bool raw_mode = false; + + sunxi_nfc_select_chip(nand, nand->cur_cs); + + nand_read_page_op(nand, page, 0, NULL, 0); + + 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 = 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); + if (ret < 0) + return ret; + else if (ret) + raw_mode = true; + } + + if (oob_required) + sunxi_nfc_hw_ecc_read_extra_oob(nand, nand->oob_poi, &cur_off, + !raw_mode, page); + + sunxi_nfc_hw_ecc_disable(nand); + + return max_bitflips; +} + +static int sunxi_nfc_hw_ecc_read_page_dma(struct nand_chip *nand, u8 *buf, + int oob_required, int page) +{ + int ret; + + sunxi_nfc_select_chip(nand, nand->cur_cs); + + nand_read_page_op(nand, page, 0, NULL, 0); + + ret = sunxi_nfc_hw_ecc_read_chunks_dma(nand, buf, oob_required, page, + nand->ecc.steps); + if (ret >= 0) + return ret; + + /* Fallback to PIO mode */ + return sunxi_nfc_hw_ecc_read_page(nand, buf, oob_required, page); +} + +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; + unsigned int max_bitflips = 0; + + sunxi_nfc_select_chip(nand, nand->cur_cs); + + nand_read_page_op(nand, page, 0, NULL, 0); + + 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 = 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, + false, page); + if (ret < 0) + return ret; + } + + sunxi_nfc_hw_ecc_disable(nand); + + return max_bitflips; +} + +static int sunxi_nfc_hw_ecc_read_subpage_dma(struct nand_chip *nand, + u32 data_offs, u32 readlen, + u8 *buf, int page) +{ + int nchunks = DIV_ROUND_UP(data_offs + readlen, nand->ecc.size); + int ret; + + sunxi_nfc_select_chip(nand, nand->cur_cs); + + nand_read_page_op(nand, page, 0, NULL, 0); + + ret = sunxi_nfc_hw_ecc_read_chunks_dma(nand, buf, false, page, nchunks); + if (ret >= 0) + return ret; + + /* Fallback to PIO mode */ + return sunxi_nfc_hw_ecc_read_subpage(nand, data_offs, readlen, + buf, page); +} + +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; + + sunxi_nfc_select_chip(nand, nand->cur_cs); + + nand_prog_page_begin_op(nand, page, 0, NULL, 0); + + 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 = 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); + if (ret) + return ret; + } + + if (oob_required || (nand->options & NAND_NEED_SCRAMBLING)) + sunxi_nfc_hw_ecc_write_extra_oob(nand, nand->oob_poi, + &cur_off, page); + + sunxi_nfc_hw_ecc_disable(nand); + + return nand_prog_page_end_op(nand); +} + +static int sunxi_nfc_hw_ecc_write_subpage(struct nand_chip *nand, + u32 data_offs, u32 data_len, + 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; + + sunxi_nfc_select_chip(nand, nand->cur_cs); + + nand_prog_page_begin_op(nand, page, 0, NULL, 0); + + 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 = 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); + if (ret) + return ret; + } + + sunxi_nfc_hw_ecc_disable(nand); + + return nand_prog_page_end_op(nand); +} + +static int sunxi_nfc_hw_ecc_write_page_dma(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 nand_ecc_ctrl *ecc = &nand->ecc; + struct scatterlist sg; + u32 wait; + int ret, i; + + sunxi_nfc_select_chip(nand, nand->cur_cs); + + ret = sunxi_nfc_wait_cmd_fifo_empty(nfc); + if (ret) + return ret; + + ret = sunxi_nfc_dma_op_prepare(nfc, buf, ecc->size, ecc->steps, + DMA_TO_DEVICE, &sg); + if (ret) + goto pio_fallback; + + sunxi_nfc_reset_user_data_len(nfc); + for (i = 0; i < ecc->steps; i++) { + 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); + + sunxi_nfc_hw_ecc_enable(nand); + sunxi_nfc_randomizer_config(nand, page, false); + sunxi_nfc_randomizer_enable(nand); + + writel((NAND_CMD_RNDIN << 8) | NAND_CMD_PAGEPROG, + nfc->regs + NFC_REG_WCMD_SET); + + wait = NFC_CMD_INT_FLAG; + + if (nfc->use_mdma) + wait |= NFC_DMA_INT_FLAG; + else + dma_async_issue_pending(nfc->dmac); + + writel(NFC_PAGE_OP | NFC_DATA_SWAP_METHOD | + NFC_DATA_TRANS | NFC_ACCESS_DIR, + nfc->regs + NFC_REG_CMD); + + ret = sunxi_nfc_wait_events(nfc, wait, false, 0); + if (ret) + sunxi_nfc_dma_op_abort(nfc); + + sunxi_nfc_randomizer_disable(nand); + sunxi_nfc_hw_ecc_disable(nand); + + sunxi_nfc_dma_op_cleanup(nfc, DMA_TO_DEVICE, &sg); + + if (ret) + return ret; + + if (oob_required || (nand->options & NAND_NEED_SCRAMBLING)) + /* TODO: use DMA to transfer extra OOB bytes ? */ + sunxi_nfc_hw_ecc_write_extra_oob(nand, nand->oob_poi, + NULL, page); + + return nand_prog_page_end_op(nand); + +pio_fallback: + return sunxi_nfc_hw_ecc_write_page(nand, buf, oob_required, page); +} + +static int sunxi_nfc_hw_ecc_read_oob(struct nand_chip *nand, int page) +{ + u8 *buf = nand_get_data_buf(nand); + + return nand->ecc.read_page(nand, buf, 1, page); +} + +static int sunxi_nfc_hw_ecc_write_oob(struct nand_chip *nand, int page) +{ + struct mtd_info *mtd = nand_to_mtd(nand); + u8 *buf = nand_get_data_buf(nand); + int ret; + + memset(buf, 0xff, mtd->writesize); + ret = nand->ecc.write_page(nand, buf, 1, page); + if (ret) + return ret; + + /* Send command to program the OOB data */ + return nand_prog_page_end_op(nand); +} + +static const struct sunxi_nfc_timings sun4i_a10_nfc_timings = { + .setup_cycles = 1, + .tWB = { 6, 12, 16, 20 }, + .tADL = { 7, 15, 23, 31 }, + .tWHR = { 7, 15, 23, 31 }, + .tRHW = { 4, 8, 12, 20 }, +}; + +static const struct sunxi_nfc_timings sun50i_h616_nfc_timings = { + .setup_cycles = 2, + .tWB = { 28, 44, 60, 76 }, + .tADL = { 0, 12, 28, 44 }, + .tWHR = { 0, 12, 28, 44 }, + .tRHW = { 8, 24, 40, 56 }, +}; + +static int _sunxi_nand_lookup_timing(const s32 *lut, int lut_size, u32 duration, + u32 clk_period) +{ + u32 clk_cycles = DIV_ROUND_UP(duration, clk_period); + int i; + + for (i = 0; i < lut_size; i++) { + if (clk_cycles <= lut[i]) + return i; + } + + /* Doesn't fit */ + return -EINVAL; +} + +#define sunxi_nand_lookup_timing(l, p, c) \ + _sunxi_nand_lookup_timing(l, ARRAY_SIZE(l), p, c) + +static int sunxi_nfc_setup_interface(struct nand_chip *nand, int csline, + const struct nand_interface_config *conf) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + struct sunxi_nfc *nfc = to_sunxi_nfc(sunxi_nand->nand.controller); + const struct sunxi_nfc_timings *nfc_timings = nfc->caps->timings; + const struct nand_sdr_timings *timings; + u32 min_clk_period = 0; + s32 tWB, tADL, tWHR, tRHW, tCAD; + long real_clk_rate; + + timings = nand_get_sdr_timings(conf); + if (IS_ERR(timings)) + return -ENOTSUPP; + + /* T1 <=> tCLS */ + if (timings->tCLS_min > + min_clk_period * nfc_timings->setup_cycles) + min_clk_period = DIV_ROUND_UP(timings->tCLS_min, + nfc_timings->setup_cycles); + + /* T2 <=> tCLH */ + if (timings->tCLH_min > + min_clk_period * nfc_timings->setup_cycles) + min_clk_period = DIV_ROUND_UP(timings->tCLH_min, + nfc_timings->setup_cycles); + + /* T3 <=> tCS */ + if (timings->tCS_min > + min_clk_period * nfc_timings->setup_cycles) + min_clk_period = DIV_ROUND_UP(timings->tCS_min, + nfc_timings->setup_cycles); + + /* T4 <=> tCH */ + if (timings->tCH_min > + min_clk_period * nfc_timings->setup_cycles) + min_clk_period = DIV_ROUND_UP(timings->tCH_min, + nfc_timings->setup_cycles); + + /* T5 <=> tWP */ + if (timings->tWP_min > min_clk_period) + min_clk_period = timings->tWP_min; + + /* T6 <=> tWH */ + if (timings->tWH_min > min_clk_period) + min_clk_period = timings->tWH_min; + + /* T7 <=> tALS */ + if (timings->tALS_min > + min_clk_period * nfc_timings->setup_cycles) + min_clk_period = DIV_ROUND_UP(timings->tALS_min, + nfc_timings->setup_cycles); + + /* T8 <=> tDS */ + if (timings->tDS_min > min_clk_period) + min_clk_period = timings->tDS_min; + + /* T9 <=> tDH */ + if (timings->tDH_min > min_clk_period) + min_clk_period = timings->tDH_min; + + /* T10 <=> tRR */ + if (timings->tRR_min > (min_clk_period * 3)) + min_clk_period = DIV_ROUND_UP(timings->tRR_min, 3); + + /* T11 <=> tALH */ + if (timings->tALH_min > + min_clk_period * nfc_timings->setup_cycles) + min_clk_period = DIV_ROUND_UP(timings->tALH_min, + nfc_timings->setup_cycles); + + /* T12 <=> tRP */ + if (timings->tRP_min > min_clk_period) + min_clk_period = timings->tRP_min; + + /* T13 <=> tREH */ + if (timings->tREH_min > min_clk_period) + min_clk_period = timings->tREH_min; + + /* T14 <=> tRC */ + if (timings->tRC_min > (min_clk_period * 2)) + min_clk_period = DIV_ROUND_UP(timings->tRC_min, 2); + + /* T15 <=> tWC */ + if (timings->tWC_min > (min_clk_period * 2)) + min_clk_period = DIV_ROUND_UP(timings->tWC_min, 2); + + /* T16 - T19 + tCAD */ + if (timings->tWB_max > + (min_clk_period * nfc_timings->tWB[SUNXI_NFC_TIMING_STEPS - 1])) + min_clk_period = DIV_ROUND_UP(timings->tWB_max, + nfc_timings->tWB[SUNXI_NFC_TIMING_STEPS - 1]); + + if (timings->tADL_min > + (min_clk_period * nfc_timings->tADL[SUNXI_NFC_TIMING_STEPS - 1])) + min_clk_period = DIV_ROUND_UP(timings->tADL_min, + nfc_timings->tADL[SUNXI_NFC_TIMING_STEPS - 1]); + + if (timings->tWHR_min > + (min_clk_period * nfc_timings->tWHR[SUNXI_NFC_TIMING_STEPS - 1])) + min_clk_period = DIV_ROUND_UP(timings->tWHR_min, + nfc_timings->tWHR[SUNXI_NFC_TIMING_STEPS - 1]); + + if (timings->tRHW_min > + (min_clk_period * nfc_timings->tRHW[SUNXI_NFC_TIMING_STEPS - 1])) + min_clk_period = DIV_ROUND_UP(timings->tRHW_min, + nfc_timings->tRHW[SUNXI_NFC_TIMING_STEPS - 1]); + + /* + * In non-EDO, tREA should be less than tRP to guarantee that the + * controller does not sample the IO lines too early. Unfortunately, + * the sunxi NAND controller does not allow us to have different + * values for tRP and tREH (tRP = tREH = tRW / 2). + * + * We have 2 options to overcome this limitation: + * + * 1/ Extend tRC to fulfil the tREA <= tRC / 2 constraint + * 2/ Use EDO mode (only works if timings->tRLOH > 0) + */ + if (timings->tREA_max > min_clk_period && !timings->tRLOH_min) + min_clk_period = timings->tREA_max; + + tWB = sunxi_nand_lookup_timing(nfc_timings->tWB, timings->tWB_max, + min_clk_period); + if (tWB < 0) { + dev_err(nfc->dev, "unsupported tWB\n"); + return tWB; + } + + tADL = sunxi_nand_lookup_timing(nfc_timings->tADL, + timings->tADL_min, min_clk_period); + if (tADL < 0) { + dev_err(nfc->dev, "unsupported tADL\n"); + return tADL; + } + + tWHR = sunxi_nand_lookup_timing(nfc_timings->tWHR, + timings->tWHR_min, min_clk_period); + if (tWHR < 0) { + dev_err(nfc->dev, "unsupported tWHR\n"); + return tWHR; + } + + tRHW = sunxi_nand_lookup_timing(nfc_timings->tRHW, timings->tRHW_min, + min_clk_period); + if (tRHW < 0) { + dev_err(nfc->dev, "unsupported tRHW\n"); + return tRHW; + } + + if (csline == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + /* + * TODO: according to ONFI specs this value only applies for DDR NAND, + * but Allwinner seems to set this to 0x7. Mimic them for now. + */ + tCAD = 0x7; + + /* TODO: A83 has some more bits for CDQSS, CS, CLHZ, CCS, WC */ + sunxi_nand->timing_cfg = NFC_TIMING_CFG(tWB, tADL, tWHR, tRHW, tCAD); + + /* Convert min_clk_period from picoseconds to nanoseconds */ + min_clk_period = DIV_ROUND_UP(min_clk_period, 1000); + + /* + * Unlike what is stated in Allwinner datasheet, the clk_rate should + * be set to (1 / min_clk_period), and not (2 / min_clk_period). + * This new formula was verified with a scope and validated by + * Allwinner engineers. + */ + sunxi_nand->clk_rate = NSEC_PER_SEC / min_clk_period; + real_clk_rate = clk_round_rate(nfc->mod_clk, sunxi_nand->clk_rate); + if (real_clk_rate <= 0) { + dev_err(nfc->dev, "Unable to round clk %lu\n", + sunxi_nand->clk_rate); + return -EINVAL; + } + + sunxi_nand->timing_ctl = 0; + + /* + * ONFI specification 3.1, paragraph 4.15.2 dictates that EDO data + * output cycle timings shall be used if the host drives tRC less than + * 30 ns. We should also use EDO mode if tREA is bigger than tRP. + */ + min_clk_period = NSEC_PER_SEC / real_clk_rate; + if (min_clk_period * 2 < 30 || min_clk_period * 1000 < timings->tREA_max) + sunxi_nand->timing_ctl = NFC_TIMING_CTL_EDO; + + return 0; +} + +static int sunxi_nand_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + 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 = sunxi_get_ecc_offset(sunxi_nand, ecc, section); + oobregion->length = ecc->bytes; + + return 0; +} + +static int sunxi_nand_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + 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); + + /* + * 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 user_data_sz - 2 bytes available in the first user data + * section. + */ + if (section == 0) { + oobregion->offset = 2; + oobregion->length = user_data_sz - 2; + + return 0; + } + + oobregion->offset = sunxi_get_ecc_offset(sunxi_nand, ecc, section); + oobregion->length = user_data_sz; + + return 0; +} + +static const struct mtd_ooblayout_ops sunxi_nand_ooblayout_ops = { + .ecc = sunxi_nand_ooblayout_ecc, + .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) +{ + 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 = mtd->oobsize; + + ecc->size = 1024; + nsectors = mtd->writesize / ecc->size; + + 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); + + /* + * 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) + bytes--; + + ecc->strength = bytes * 8 / fls(8 * ecc->size); + + for (i = 0; i < nfc->caps->nstrengths; i++) { + if (strengths[i] > ecc->strength) + break; + } + + if (!i) + ecc->strength = 0; + else + ecc->strength = strengths[i - 1]; + } + + if (ecc->size != 512 && ecc->size != 1024) + return -EINVAL; + + /* Prefer 1k ECC chunk over 512 ones */ + if (ecc->size == 512 && mtd->writesize > 512) { + ecc->size = 1024; + ecc->strength *= 2; + } + + /* Add ECC info retrieval from DT */ + 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[ecc_mode]; + break; + } + } + + if (ecc_mode >= nfc->caps->nstrengths) { + dev_err(nfc->dev, "unsupported strength\n"); + return -ENOTSUPP; + } + + /* HW ECC always request ECC bytes for 1024 bytes blocks */ + ecc->bytes = DIV_ROUND_UP(ecc->strength * fls(8 * 1024), 8); + + /* HW ECC always work with even numbers of ECC bytes */ + ecc->bytes = ALIGN(ecc->bytes, 2); + + nsectors = mtd->writesize / ecc->size; + + /* + * 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; + ecc->write_oob = sunxi_nfc_hw_ecc_write_oob; + mtd_set_ooblayout(mtd, &sunxi_nand_ooblayout_ops); + + if (nfc->dmac || nfc->use_mdma) { + ecc->read_page = sunxi_nfc_hw_ecc_read_page_dma; + ecc->read_subpage = sunxi_nfc_hw_ecc_read_subpage_dma; + ecc->write_page = sunxi_nfc_hw_ecc_write_page_dma; + nand->options |= NAND_USES_DMA; + if (nfc->mdma_desc) + nand->buf_align = 4; + } else { + ecc->read_page = sunxi_nfc_hw_ecc_read_page; + ecc->read_subpage = sunxi_nfc_hw_ecc_read_subpage; + ecc->write_page = sunxi_nfc_hw_ecc_write_page; + } + + /* TODO: support DMA for raw accesses and subpage write */ + ecc->write_subpage = sunxi_nfc_hw_ecc_write_subpage; + ecc->read_oob_raw = nand_read_oob_std; + ecc->write_oob_raw = nand_write_oob_std; + + sunxi_nand->ecc.ecc_ctl = NFC_ECC_MODE(nfc, ecc_mode) | NFC_ECC_EXCEPTION | + NFC_ECC_PIPELINE | NFC_ECC_EN; + + 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; +} + +static int sunxi_nand_attach_chip(struct nand_chip *nand) +{ + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&nand->base); + struct nand_ecc_ctrl *ecc = &nand->ecc; + struct device_node *np = nand_get_flash_node(nand); + int ret; + + if (nand->bbt_options & NAND_BBT_USE_FLASH) + nand->bbt_options |= NAND_BBT_NO_OOB; + + if (nand->options & NAND_NEED_SCRAMBLING) + nand->options |= NAND_NO_SUBPAGE_WRITE; + + nand->options |= NAND_SUBPAGE_READ; + + if (!ecc->size) { + ecc->size = requirements->step_size; + ecc->strength = requirements->strength; + } + + if (!ecc->size || !ecc->strength) + return -EINVAL; + + switch (ecc->engine_type) { + case NAND_ECC_ENGINE_TYPE_ON_HOST: + ret = sunxi_nand_hw_ecc_ctrl_init(nand, ecc, np); + if (ret) + return ret; + break; + case NAND_ECC_ENGINE_TYPE_NONE: + case NAND_ECC_ENGINE_TYPE_SOFT: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int sunxi_nfc_exec_subop(struct nand_chip *nand, + const struct nand_subop *subop) +{ + struct sunxi_nfc *nfc = to_sunxi_nfc(nand->controller); + u32 cmd = 0, extcmd = 0, cnt = 0, addrs[2] = { }; + unsigned int i, j, remaining, start; + void *inbuf = NULL; + int ret; + + for (i = 0; i < subop->ninstrs; i++) { + const struct nand_op_instr *instr = &subop->instrs[i]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + if (cmd & NFC_SEND_CMD1) { + if (WARN_ON(cmd & NFC_SEND_CMD2)) + return -EINVAL; + + cmd |= NFC_SEND_CMD2; + extcmd |= instr->ctx.cmd.opcode; + } else { + cmd |= NFC_SEND_CMD1 | + NFC_CMD(instr->ctx.cmd.opcode); + } + break; + + case NAND_OP_ADDR_INSTR: + remaining = nand_subop_get_num_addr_cyc(subop, i); + start = nand_subop_get_addr_start_off(subop, i); + for (j = 0; j < 8 && j + start < remaining; j++) { + u32 addr = instr->ctx.addr.addrs[j + start]; + + addrs[j / 4] |= addr << (j % 4) * 8; + } + + if (j) + cmd |= NFC_SEND_ADR | NFC_ADR_NUM(j); + + break; + + case NAND_OP_DATA_IN_INSTR: + 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->caps->sram_size); + cmd |= NFC_DATA_TRANS | NFC_DATA_SWAP_METHOD; + + if (instr->type == NAND_OP_DATA_OUT_INSTR) { + cmd |= NFC_ACCESS_DIR; + memcpy_toio(nfc->regs + NFC_RAM0_BASE, + instr->ctx.data.buf.out + start, + cnt); + } else { + inbuf = instr->ctx.data.buf.in + start; + } + + break; + + case NAND_OP_WAITRDY_INSTR: + cmd |= NFC_WAIT_FLAG; + break; + } + } + + ret = sunxi_nfc_wait_cmd_fifo_empty(nfc); + if (ret) + return ret; + + if (cmd & NFC_SEND_ADR) { + writel(addrs[0], nfc->regs + NFC_REG_ADDR_LOW); + writel(addrs[1], nfc->regs + NFC_REG_ADDR_HIGH); + } + + if (cmd & NFC_SEND_CMD2) + writel(extcmd, + nfc->regs + + (cmd & NFC_ACCESS_DIR ? + NFC_REG_WCMD_SET : NFC_REG_RCMD_SET)); + + if (cmd & NFC_DATA_TRANS) + writel(cnt, nfc->regs + NFC_REG_CNT); + + writel(cmd, nfc->regs + NFC_REG_CMD); + + ret = sunxi_nfc_wait_events(nfc, NFC_CMD_INT_FLAG, + !(cmd & NFC_WAIT_FLAG) && cnt < 64, + 0); + if (ret) + return ret; + + if (inbuf) + memcpy_fromio(inbuf, nfc->regs + NFC_RAM0_BASE, cnt); + + return 0; +} + +static int sunxi_nfc_soft_waitrdy(struct nand_chip *nand, + const struct nand_subop *subop) +{ + return nand_soft_waitrdy(nand, + subop->instrs[0].ctx.waitrdy.timeout_ms); +} + +static const struct nand_op_parser sunxi_nfc_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN(sunxi_nfc_exec_subop, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, 1024)), + NAND_OP_PARSER_PATTERN(sunxi_nfc_exec_subop, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(true, 1024), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), +); + +static const struct nand_op_parser sunxi_nfc_norb_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN(sunxi_nfc_exec_subop, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, 1024)), + NAND_OP_PARSER_PATTERN(sunxi_nfc_exec_subop, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(true, 1024), + NAND_OP_PARSER_PAT_CMD_ELEM(true)), + NAND_OP_PARSER_PATTERN(sunxi_nfc_soft_waitrdy, + NAND_OP_PARSER_PAT_WAITRDY_ELEM(false)), +); + +static int sunxi_nfc_exec_op(struct nand_chip *nand, + const struct nand_operation *op, bool check_only) +{ + struct sunxi_nand_chip *sunxi_nand = to_sunxi_nand(nand); + const struct nand_op_parser *parser; + + if (!check_only) + sunxi_nfc_select_chip(nand, op->cs); + + if (sunxi_nand->sels[op->cs].rb >= 0) + parser = &sunxi_nfc_op_parser; + else + parser = &sunxi_nfc_norb_op_parser; + + return nand_op_parser_exec_op(nand, parser, op, check_only); +} + +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, +}; + +static void sunxi_nand_chips_cleanup(struct sunxi_nfc *nfc) +{ + struct sunxi_nand_chip *sunxi_nand; + struct nand_chip *chip; + int ret; + + while (!list_empty(&nfc->chips)) { + sunxi_nand = list_first_entry(&nfc->chips, + struct sunxi_nand_chip, + node); + chip = &sunxi_nand->nand; + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + list_del(&sunxi_nand->node); + } +} + +static int sunxi_nand_chip_init(struct device *dev, struct sunxi_nfc *nfc, + struct device_node *np) +{ + struct sunxi_nand_chip *sunxi_nand; + struct mtd_info *mtd; + struct nand_chip *nand; + int nsels; + int ret; + int i; + u32 tmp; + + if (!of_get_property(np, "reg", &nsels)) + return -EINVAL; + + nsels /= sizeof(u32); + if (!nsels) { + dev_err(dev, "invalid reg property size\n"); + return -EINVAL; + } + + sunxi_nand = devm_kzalloc(dev, struct_size(sunxi_nand, sels, nsels), + GFP_KERNEL); + if (!sunxi_nand) + return -ENOMEM; + + sunxi_nand->nsels = nsels; + + for (i = 0; i < nsels; i++) { + ret = of_property_read_u32_index(np, "reg", i, &tmp); + if (ret) { + dev_err(dev, "could not retrieve reg property: %d\n", + ret); + return ret; + } + + if (tmp > NFC_MAX_CS) { + dev_err(dev, + "invalid reg value: %u (max CS = 7)\n", + tmp); + return -EINVAL; + } + + if (test_and_set_bit(tmp, &nfc->assigned_cs)) { + dev_err(dev, "CS %d already assigned\n", tmp); + return -EINVAL; + } + + sunxi_nand->sels[i].cs = tmp; + + if (!of_property_read_u32_index(np, "allwinner,rb", i, &tmp) && + tmp < 2) + sunxi_nand->sels[i].rb = tmp; + else + sunxi_nand->sels[i].rb = -1; + } + + nand = &sunxi_nand->nand; + /* Default tR value specified in the ONFI spec (chapter 4.15.1) */ + nand->controller = &nfc->controller; + nand->controller->ops = &sunxi_nand_controller_ops; + + /* + * Set the ECC mode to the default value in case nothing is specified + * in the DT. + */ + nand->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + nand_set_flash_node(nand, np); + + mtd = nand_to_mtd(nand); + mtd->dev.parent = dev; + + ret = nand_scan(nand, nsels); + if (ret) + return ret; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(dev, "failed to register mtd device: %d\n", ret); + nand_cleanup(nand); + return ret; + } + + list_add_tail(&sunxi_nand->node, &nfc->chips); + + return 0; +} + +static int sunxi_nand_chips_init(struct device *dev, struct sunxi_nfc *nfc) +{ + struct device_node *np = dev->of_node; + int ret; + + for_each_child_of_node_scoped(np, nand_np) { + ret = sunxi_nand_chip_init(dev, nfc, nand_np); + if (ret) { + sunxi_nand_chips_cleanup(nfc); + return ret; + } + } + + return 0; +} + +static int sunxi_nfc_dma_init(struct sunxi_nfc *nfc, struct resource *r) +{ + int ret; + + if (nfc->caps->has_mdma_desc) { + ret = dma_set_mask_and_coherent(nfc->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_warn(nfc->dev, + "failed to set MBUS DMA mask, using PIO: %d\n", + ret); + return 0; + } + + nfc->mdma_desc = + dmam_alloc_coherent(nfc->dev, sizeof(*nfc->mdma_desc), + &nfc->mdma_desc_dma, GFP_KERNEL); + if (!nfc->mdma_desc) { + dev_warn(nfc->dev, + "failed to allocate MBUS DMA descriptor, using PIO\n"); + return 0; + } + + nfc->use_mdma = true; + return 0; + } + + if (nfc->caps->has_mdma) { + nfc->use_mdma = true; + return 0; + } + + nfc->dmac = dma_request_chan(nfc->dev, "rxtx"); + if (IS_ERR(nfc->dmac)) { + ret = PTR_ERR(nfc->dmac); + if (ret == -EPROBE_DEFER) + return ret; + + /* Ignore errors to fall back to PIO mode */ + dev_warn(nfc->dev, "failed to request rxtx DMA channel: %d\n", ret); + nfc->dmac = NULL; + } else { + struct dma_slave_config dmac_cfg = { }; + + dmac_cfg.src_addr = r->start + nfc->caps->reg_io_data; + dmac_cfg.dst_addr = dmac_cfg.src_addr; + dmac_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dmac_cfg.dst_addr_width = dmac_cfg.src_addr_width; + dmac_cfg.src_maxburst = nfc->caps->dma_maxburst; + dmac_cfg.dst_maxburst = nfc->caps->dma_maxburst; + dmaengine_slave_config(nfc->dmac, &dmac_cfg); + } + return 0; +} + +static int sunxi_nfc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *r; + struct sunxi_nfc *nfc; + int irq; + int ret; + + nfc = devm_kzalloc(dev, sizeof(*nfc), GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->dev = dev; + nand_controller_init(&nfc->controller); + INIT_LIST_HEAD(&nfc->chips); + + nfc->regs = devm_platform_get_and_ioremap_resource(pdev, 0, &r); + if (IS_ERR(nfc->regs)) + return PTR_ERR(nfc->regs); + + irq = platform_get_irq(pdev, 0); + 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"); + return PTR_ERR(nfc->ahb_clk); + } + + nfc->mod_clk = devm_clk_get_enabled(dev, "mod"); + if (IS_ERR(nfc->mod_clk)) { + dev_err(dev, "failed to retrieve mod clk\n"); + 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); + + ret = reset_control_deassert(nfc->reset); + if (ret) { + dev_err(dev, "reset err %d\n", ret); + return ret; + } + + ret = sunxi_nfc_rst(nfc); + if (ret) + goto out_ahb_reset_reassert; + + writel(0, nfc->regs + NFC_REG_INT); + ret = devm_request_irq(dev, irq, sunxi_nfc_interrupt, + 0, "sunxi-nand", nfc); + if (ret) + goto out_ahb_reset_reassert; + + ret = sunxi_nfc_dma_init(nfc, r); + + if (ret) + goto out_ahb_reset_reassert; + + platform_set_drvdata(pdev, nfc); + + ret = sunxi_nand_chips_init(dev, nfc); + if (ret) { + dev_err(dev, "failed to init nand chips\n"); + goto out_release_dmac; + } + + return 0; + +out_release_dmac: + if (nfc->dmac) + dma_release_channel(nfc->dmac); +out_ahb_reset_reassert: + reset_control_assert(nfc->reset); + + return ret; +} + +static void sunxi_nfc_remove(struct platform_device *pdev) +{ + struct sunxi_nfc *nfc = platform_get_drvdata(pdev); + + sunxi_nand_chips_cleanup(nfc); + + reset_control_assert(nfc->reset); + + if (nfc->dmac) + 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, + .timings = &sun4i_a10_nfc_timings, +}; + +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, + .timings = &sun4i_a10_nfc_timings, +}; + +static const struct sunxi_nfc_caps sunxi_nfc_h616_caps = { + .has_mdma_desc = true, + .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, + .timings = &sun50i_h616_nfc_timings, +}; + +static const struct of_device_id sunxi_nfc_ids[] = { + { + .compatible = "allwinner,sun4i-a10-nand", + .data = &sunxi_nfc_a10_caps, + }, + { + .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); + +static struct platform_driver sunxi_nfc_driver = { + .driver = { + .name = "sunxi_nand", + .of_match_table = sunxi_nfc_ids, + }, + .probe = sunxi_nfc_probe, + .remove = sunxi_nfc_remove, +}; +module_platform_driver(sunxi_nfc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Boris BREZILLON"); +MODULE_DESCRIPTION("Allwinner NAND Flash Controller driver"); diff --git a/drivers/mtd/nand/raw/technologic-nand-controller.c b/drivers/mtd/nand/raw/technologic-nand-controller.c new file mode 100644 index 000000000..a3294aaf4 --- /dev/null +++ b/drivers/mtd/nand/raw/technologic-nand-controller.c @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Technologic Systems TS72xx NAND controller driver + * + * Copyright (C) 2023 Nikita Shubin + * + * Derived from: plat_nand.c + * Author: Vitaly Wool + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define TS72XX_NAND_CONTROL_ADDR_LINE BIT(22) /* 0xN0400000 */ +#define TS72XX_NAND_BUSY_ADDR_LINE BIT(23) /* 0xN0800000 */ + +#define TS72XX_NAND_ALE BIT(0) +#define TS72XX_NAND_CLE BIT(1) +#define TS72XX_NAND_NCE BIT(2) + +#define TS72XX_NAND_CTRL_CLE (TS72XX_NAND_NCE | TS72XX_NAND_CLE) +#define TS72XX_NAND_CTRL_ALE (TS72XX_NAND_NCE | TS72XX_NAND_ALE) + +struct ts72xx_nand_data { + struct nand_controller controller; + struct nand_chip chip; + void __iomem *base; + void __iomem *ctrl; + void __iomem *busy; +}; + +static inline struct ts72xx_nand_data *chip_to_ts72xx(struct nand_chip *chip) +{ + return container_of(chip, struct ts72xx_nand_data, chip); +} + +static int ts72xx_nand_attach_chip(struct nand_chip *chip) +{ + switch (chip->ecc.engine_type) { + case NAND_ECC_ENGINE_TYPE_ON_HOST: + return -EINVAL; + case NAND_ECC_ENGINE_TYPE_SOFT: + if (chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + fallthrough; + default: + return 0; + } +} + +static void ts72xx_nand_ctrl(struct nand_chip *chip, u8 value) +{ + struct ts72xx_nand_data *data = chip_to_ts72xx(chip); + unsigned char bits = ioread8(data->ctrl) & ~GENMASK(2, 0); + + iowrite8(bits | value, data->ctrl); +} + +static int ts72xx_nand_exec_instr(struct nand_chip *chip, + const struct nand_op_instr *instr) +{ + struct ts72xx_nand_data *data = chip_to_ts72xx(chip); + unsigned int timeout_us; + u32 status; + int ret; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + ts72xx_nand_ctrl(chip, TS72XX_NAND_CTRL_CLE); + iowrite8(instr->ctx.cmd.opcode, data->base); + ts72xx_nand_ctrl(chip, TS72XX_NAND_NCE); + break; + + case NAND_OP_ADDR_INSTR: + ts72xx_nand_ctrl(chip, TS72XX_NAND_CTRL_ALE); + iowrite8_rep(data->base, instr->ctx.addr.addrs, instr->ctx.addr.naddrs); + ts72xx_nand_ctrl(chip, TS72XX_NAND_NCE); + break; + + case NAND_OP_DATA_IN_INSTR: + ioread8_rep(data->base, instr->ctx.data.buf.in, instr->ctx.data.len); + break; + + case NAND_OP_DATA_OUT_INSTR: + iowrite8_rep(data->base, instr->ctx.data.buf.in, instr->ctx.data.len); + break; + + case NAND_OP_WAITRDY_INSTR: + timeout_us = instr->ctx.waitrdy.timeout_ms * 1000; + ret = readb_poll_timeout(data->busy, status, status & BIT(5), 0, timeout_us); + if (ret) + return ret; + + break; + } + + if (instr->delay_ns) + ndelay(instr->delay_ns); + + return 0; +} + +static int ts72xx_nand_exec_op(struct nand_chip *chip, + const struct nand_operation *op, bool check_only) +{ + unsigned int i; + int ret; + + if (check_only) + return 0; + + for (i = 0; i < op->ninstrs; i++) { + ret = ts72xx_nand_exec_instr(chip, &op->instrs[i]); + if (ret) + return ret; + } + + return 0; +} + +static const struct nand_controller_ops ts72xx_nand_ops = { + .attach_chip = ts72xx_nand_attach_chip, + .exec_op = ts72xx_nand_exec_op, +}; + +static int ts72xx_nand_probe(struct platform_device *pdev) +{ + struct ts72xx_nand_data *data; + struct fwnode_handle *child; + struct mtd_info *mtd; + int err; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + nand_controller_init(&data->controller); + data->controller.ops = &ts72xx_nand_ops; + data->chip.controller = &data->controller; + + data->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(data->base)) + return PTR_ERR(data->base); + data->ctrl = data->base + TS72XX_NAND_CONTROL_ADDR_LINE; + data->busy = data->base + TS72XX_NAND_BUSY_ADDR_LINE; + + child = fwnode_get_next_child_node(dev_fwnode(&pdev->dev), NULL); + if (!child) + return dev_err_probe(&pdev->dev, -ENXIO, + "ts72xx controller node should have exactly one child\n"); + + nand_set_flash_node(&data->chip, to_of_node(child)); + mtd = nand_to_mtd(&data->chip); + mtd->dev.parent = &pdev->dev; + platform_set_drvdata(pdev, data); + + /* + * This driver assumes that the default ECC engine should be TYPE_SOFT. + * Set ->engine_type before registering the NAND devices in order to + * provide a driver specific default value. + */ + data->chip.ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + + /* Scan to find existence of the device */ + err = nand_scan(&data->chip, 1); + if (err) + goto err_handle_put; + + err = mtd_device_parse_register(mtd, NULL, NULL, NULL, 0); + if (err) + goto err_clean_nand; + + return 0; + +err_clean_nand: + nand_cleanup(&data->chip); +err_handle_put: + fwnode_handle_put(child); + return err; +} + +static void ts72xx_nand_remove(struct platform_device *pdev) +{ + struct ts72xx_nand_data *data = platform_get_drvdata(pdev); + struct fwnode_handle *fwnode = dev_fwnode(&pdev->dev); + struct nand_chip *chip = &data->chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + fwnode_handle_put(fwnode); +} + +static const struct of_device_id ts72xx_id_table[] = { + { .compatible = "technologic,ts7200-nand" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, ts72xx_id_table); + +static struct platform_driver ts72xx_nand_driver = { + .driver = { + .name = "ts72xx-nand", + .of_match_table = ts72xx_id_table, + }, + .probe = ts72xx_nand_probe, + .remove = ts72xx_nand_remove, +}; +module_platform_driver(ts72xx_nand_driver); + +MODULE_AUTHOR("Nikita Shubin "); +MODULE_DESCRIPTION("Technologic Systems TS72xx NAND controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/raw/tegra_nand.c b/drivers/mtd/nand/raw/tegra_nand.c new file mode 100644 index 000000000..7f9eb5f04 --- /dev/null +++ b/drivers/mtd/nand/raw/tegra_nand.c @@ -0,0 +1,1290 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Stefan Agner + * Copyright (C) 2014-2015 Lucas Stach + * Copyright (C) 2012 Avionic Design GmbH + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define COMMAND 0x00 +#define COMMAND_GO BIT(31) +#define COMMAND_CLE BIT(30) +#define COMMAND_ALE BIT(29) +#define COMMAND_PIO BIT(28) +#define COMMAND_TX BIT(27) +#define COMMAND_RX BIT(26) +#define COMMAND_SEC_CMD BIT(25) +#define COMMAND_AFT_DAT BIT(24) +#define COMMAND_TRANS_SIZE(size) ((((size) - 1) & 0xf) << 20) +#define COMMAND_A_VALID BIT(19) +#define COMMAND_B_VALID BIT(18) +#define COMMAND_RD_STATUS_CHK BIT(17) +#define COMMAND_RBSY_CHK BIT(16) +#define COMMAND_CE(x) BIT(8 + ((x) & 0x7)) +#define COMMAND_CLE_SIZE(size) ((((size) - 1) & 0x3) << 4) +#define COMMAND_ALE_SIZE(size) ((((size) - 1) & 0xf) << 0) + +#define STATUS 0x04 + +#define ISR 0x08 +#define ISR_CORRFAIL_ERR BIT(24) +#define ISR_UND BIT(7) +#define ISR_OVR BIT(6) +#define ISR_CMD_DONE BIT(5) +#define ISR_ECC_ERR BIT(4) + +#define IER 0x0c +#define IER_ERR_TRIG_VAL(x) (((x) & 0xf) << 16) +#define IER_UND BIT(7) +#define IER_OVR BIT(6) +#define IER_CMD_DONE BIT(5) +#define IER_ECC_ERR BIT(4) +#define IER_GIE BIT(0) + +#define CONFIG 0x10 +#define CONFIG_HW_ECC BIT(31) +#define CONFIG_ECC_SEL BIT(30) +#define CONFIG_ERR_COR BIT(29) +#define CONFIG_PIPE_EN BIT(28) +#define CONFIG_TVAL_4 (0 << 24) +#define CONFIG_TVAL_6 (1 << 24) +#define CONFIG_TVAL_8 (2 << 24) +#define CONFIG_SKIP_SPARE BIT(23) +#define CONFIG_BUS_WIDTH_16 BIT(21) +#define CONFIG_COM_BSY BIT(20) +#define CONFIG_PS_256 (0 << 16) +#define CONFIG_PS_512 (1 << 16) +#define CONFIG_PS_1024 (2 << 16) +#define CONFIG_PS_2048 (3 << 16) +#define CONFIG_PS_4096 (4 << 16) +#define CONFIG_SKIP_SPARE_SIZE_4 (0 << 14) +#define CONFIG_SKIP_SPARE_SIZE_8 (1 << 14) +#define CONFIG_SKIP_SPARE_SIZE_12 (2 << 14) +#define CONFIG_SKIP_SPARE_SIZE_16 (3 << 14) +#define CONFIG_TAG_BYTE_SIZE(x) ((x) & 0xff) + +#define TIMING_1 0x14 +#define TIMING_TRP_RESP(x) (((x) & 0xf) << 28) +#define TIMING_TWB(x) (((x) & 0xf) << 24) +#define TIMING_TCR_TAR_TRR(x) (((x) & 0xf) << 20) +#define TIMING_TWHR(x) (((x) & 0xf) << 16) +#define TIMING_TCS(x) (((x) & 0x3) << 14) +#define TIMING_TWH(x) (((x) & 0x3) << 12) +#define TIMING_TWP(x) (((x) & 0xf) << 8) +#define TIMING_TRH(x) (((x) & 0x3) << 4) +#define TIMING_TRP(x) (((x) & 0xf) << 0) + +#define RESP 0x18 + +#define TIMING_2 0x1c +#define TIMING_TADL(x) ((x) & 0xf) + +#define CMD_REG1 0x20 +#define CMD_REG2 0x24 +#define ADDR_REG1 0x28 +#define ADDR_REG2 0x2c + +#define DMA_MST_CTRL 0x30 +#define DMA_MST_CTRL_GO BIT(31) +#define DMA_MST_CTRL_IN (0 << 30) +#define DMA_MST_CTRL_OUT BIT(30) +#define DMA_MST_CTRL_PERF_EN BIT(29) +#define DMA_MST_CTRL_IE_DONE BIT(28) +#define DMA_MST_CTRL_REUSE BIT(27) +#define DMA_MST_CTRL_BURST_1 (2 << 24) +#define DMA_MST_CTRL_BURST_4 (3 << 24) +#define DMA_MST_CTRL_BURST_8 (4 << 24) +#define DMA_MST_CTRL_BURST_16 (5 << 24) +#define DMA_MST_CTRL_IS_DONE BIT(20) +#define DMA_MST_CTRL_EN_A BIT(2) +#define DMA_MST_CTRL_EN_B BIT(1) + +#define DMA_CFG_A 0x34 +#define DMA_CFG_B 0x38 + +#define FIFO_CTRL 0x3c +#define FIFO_CTRL_CLR_ALL BIT(3) + +#define DATA_PTR 0x40 +#define TAG_PTR 0x44 +#define ECC_PTR 0x48 + +#define DEC_STATUS 0x4c +#define DEC_STATUS_A_ECC_FAIL BIT(1) +#define DEC_STATUS_ERR_COUNT_MASK 0x00ff0000 +#define DEC_STATUS_ERR_COUNT_SHIFT 16 + +#define HWSTATUS_CMD 0x50 +#define HWSTATUS_MASK 0x54 +#define HWSTATUS_RDSTATUS_MASK(x) (((x) & 0xff) << 24) +#define HWSTATUS_RDSTATUS_VALUE(x) (((x) & 0xff) << 16) +#define HWSTATUS_RBSY_MASK(x) (((x) & 0xff) << 8) +#define HWSTATUS_RBSY_VALUE(x) (((x) & 0xff) << 0) + +#define BCH_CONFIG 0xcc +#define BCH_ENABLE BIT(0) +#define BCH_TVAL_4 (0 << 4) +#define BCH_TVAL_8 (1 << 4) +#define BCH_TVAL_14 (2 << 4) +#define BCH_TVAL_16 (3 << 4) + +#define DEC_STAT_RESULT 0xd0 +#define DEC_STAT_BUF 0xd4 +#define DEC_STAT_BUF_FAIL_SEC_FLAG_MASK 0xff000000 +#define DEC_STAT_BUF_FAIL_SEC_FLAG_SHIFT 24 +#define DEC_STAT_BUF_CORR_SEC_FLAG_MASK 0x00ff0000 +#define DEC_STAT_BUF_CORR_SEC_FLAG_SHIFT 16 +#define DEC_STAT_BUF_MAX_CORR_CNT_MASK 0x00001f00 +#define DEC_STAT_BUF_MAX_CORR_CNT_SHIFT 8 + +#define OFFSET(val, off) ((val) < (off) ? 0 : (val) - (off)) + +#define SKIP_SPARE_BYTES 4 +#define BITS_PER_STEP_RS 18 +#define BITS_PER_STEP_BCH 13 + +#define INT_MASK (IER_UND | IER_OVR | IER_CMD_DONE | IER_GIE) +#define HWSTATUS_CMD_DEFAULT NAND_STATUS_READY +#define HWSTATUS_MASK_DEFAULT (HWSTATUS_RDSTATUS_MASK(1) | \ + HWSTATUS_RDSTATUS_VALUE(0) | \ + HWSTATUS_RBSY_MASK(NAND_STATUS_READY) | \ + HWSTATUS_RBSY_VALUE(NAND_STATUS_READY)) + +struct tegra_nand_controller { + struct nand_controller controller; + struct device *dev; + void __iomem *regs; + int irq; + struct clk *clk; + struct completion command_complete; + struct completion dma_complete; + bool last_read_error; + int cur_cs; + struct nand_chip *chip; +}; + +struct tegra_nand_chip { + struct nand_chip chip; + struct gpio_desc *wp_gpio; + struct mtd_oob_region ecc; + u32 config; + u32 config_ecc; + u32 bch_config; + int cs[1]; +}; + +static inline struct tegra_nand_controller * + to_tegra_ctrl(struct nand_controller *hw_ctrl) +{ + return container_of(hw_ctrl, struct tegra_nand_controller, controller); +} + +static inline struct tegra_nand_chip *to_tegra_chip(struct nand_chip *chip) +{ + return container_of(chip, struct tegra_nand_chip, chip); +} + +static int tegra_nand_ooblayout_rs_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + int bytes_per_step = DIV_ROUND_UP(BITS_PER_STEP_RS * chip->ecc.strength, + BITS_PER_BYTE); + + if (section > 0) + return -ERANGE; + + oobregion->offset = SKIP_SPARE_BYTES; + oobregion->length = round_up(bytes_per_step * chip->ecc.steps, 4); + + return 0; +} + +static int tegra_nand_ooblayout_no_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + return -ERANGE; +} + +static const struct mtd_ooblayout_ops tegra_nand_oob_rs_ops = { + .ecc = tegra_nand_ooblayout_rs_ecc, + .free = tegra_nand_ooblayout_no_free, +}; + +static int tegra_nand_ooblayout_bch_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + int bytes_per_step = DIV_ROUND_UP(BITS_PER_STEP_BCH * chip->ecc.strength, + BITS_PER_BYTE); + + if (section > 0) + return -ERANGE; + + oobregion->offset = SKIP_SPARE_BYTES; + oobregion->length = round_up(bytes_per_step * chip->ecc.steps, 4); + + return 0; +} + +static const struct mtd_ooblayout_ops tegra_nand_oob_bch_ops = { + .ecc = tegra_nand_ooblayout_bch_ecc, + .free = tegra_nand_ooblayout_no_free, +}; + +static irqreturn_t tegra_nand_irq(int irq, void *data) +{ + struct tegra_nand_controller *ctrl = data; + u32 isr, dma; + + isr = readl_relaxed(ctrl->regs + ISR); + dma = readl_relaxed(ctrl->regs + DMA_MST_CTRL); + dev_dbg(ctrl->dev, "isr %08x\n", isr); + + if (!isr && !(dma & DMA_MST_CTRL_IS_DONE)) + return IRQ_NONE; + + /* + * The bit name is somewhat missleading: This is also set when + * HW ECC was successful. The data sheet states: + * Correctable OR Un-correctable errors occurred in the DMA transfer... + */ + if (isr & ISR_CORRFAIL_ERR) + ctrl->last_read_error = true; + + if (isr & ISR_CMD_DONE) + complete(&ctrl->command_complete); + + if (isr & ISR_UND) + dev_err(ctrl->dev, "FIFO underrun\n"); + + if (isr & ISR_OVR) + dev_err(ctrl->dev, "FIFO overrun\n"); + + /* handle DMA interrupts */ + if (dma & DMA_MST_CTRL_IS_DONE) { + writel_relaxed(dma, ctrl->regs + DMA_MST_CTRL); + complete(&ctrl->dma_complete); + } + + /* clear interrupts */ + writel_relaxed(isr, ctrl->regs + ISR); + + return IRQ_HANDLED; +} + +static const char * const tegra_nand_reg_names[] = { + "COMMAND", + "STATUS", + "ISR", + "IER", + "CONFIG", + "TIMING", + NULL, + "TIMING2", + "CMD_REG1", + "CMD_REG2", + "ADDR_REG1", + "ADDR_REG2", + "DMA_MST_CTRL", + "DMA_CFG_A", + "DMA_CFG_B", + "FIFO_CTRL", +}; + +static void tegra_nand_dump_reg(struct tegra_nand_controller *ctrl) +{ + u32 reg; + int i; + + dev_err(ctrl->dev, "Tegra NAND controller register dump\n"); + for (i = 0; i < ARRAY_SIZE(tegra_nand_reg_names); i++) { + const char *reg_name = tegra_nand_reg_names[i]; + + if (!reg_name) + continue; + + reg = readl_relaxed(ctrl->regs + (i * 4)); + dev_err(ctrl->dev, "%s: 0x%08x\n", reg_name, reg); + } +} + +static void tegra_nand_controller_abort(struct tegra_nand_controller *ctrl) +{ + u32 isr, dma; + + disable_irq(ctrl->irq); + + /* Abort current command/DMA operation */ + writel_relaxed(0, ctrl->regs + DMA_MST_CTRL); + writel_relaxed(0, ctrl->regs + COMMAND); + + /* clear interrupts */ + isr = readl_relaxed(ctrl->regs + ISR); + writel_relaxed(isr, ctrl->regs + ISR); + dma = readl_relaxed(ctrl->regs + DMA_MST_CTRL); + writel_relaxed(dma, ctrl->regs + DMA_MST_CTRL); + + reinit_completion(&ctrl->command_complete); + reinit_completion(&ctrl->dma_complete); + + enable_irq(ctrl->irq); +} + +static int tegra_nand_cmd(struct nand_chip *chip, + const struct nand_subop *subop) +{ + const struct nand_op_instr *instr; + const struct nand_op_instr *instr_data_in = NULL; + struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller); + unsigned int op_id, size = 0, offset = 0; + bool first_cmd = true; + u32 reg, cmd = 0; + int ret; + + for (op_id = 0; op_id < subop->ninstrs; op_id++) { + unsigned int naddrs, i; + const u8 *addrs; + u32 addr1 = 0, addr2 = 0; + + instr = &subop->instrs[op_id]; + + switch (instr->type) { + case NAND_OP_CMD_INSTR: + if (first_cmd) { + cmd |= COMMAND_CLE; + writel_relaxed(instr->ctx.cmd.opcode, + ctrl->regs + CMD_REG1); + } else { + cmd |= COMMAND_SEC_CMD; + writel_relaxed(instr->ctx.cmd.opcode, + ctrl->regs + CMD_REG2); + } + first_cmd = false; + break; + + case NAND_OP_ADDR_INSTR: + offset = nand_subop_get_addr_start_off(subop, op_id); + naddrs = nand_subop_get_num_addr_cyc(subop, op_id); + addrs = &instr->ctx.addr.addrs[offset]; + + cmd |= COMMAND_ALE | COMMAND_ALE_SIZE(naddrs); + for (i = 0; i < min_t(unsigned int, 4, naddrs); i++) + addr1 |= *addrs++ << (BITS_PER_BYTE * i); + naddrs -= i; + for (i = 0; i < min_t(unsigned int, 4, naddrs); i++) + addr2 |= *addrs++ << (BITS_PER_BYTE * i); + + writel_relaxed(addr1, ctrl->regs + ADDR_REG1); + writel_relaxed(addr2, ctrl->regs + ADDR_REG2); + break; + + case NAND_OP_DATA_IN_INSTR: + size = nand_subop_get_data_len(subop, op_id); + offset = nand_subop_get_data_start_off(subop, op_id); + + cmd |= COMMAND_TRANS_SIZE(size) | COMMAND_PIO | + COMMAND_RX | COMMAND_A_VALID; + + instr_data_in = instr; + break; + + case NAND_OP_DATA_OUT_INSTR: + size = nand_subop_get_data_len(subop, op_id); + offset = nand_subop_get_data_start_off(subop, op_id); + + cmd |= COMMAND_TRANS_SIZE(size) | COMMAND_PIO | + COMMAND_TX | COMMAND_A_VALID; + memcpy(®, instr->ctx.data.buf.out + offset, size); + + writel_relaxed(reg, ctrl->regs + RESP); + break; + + case NAND_OP_WAITRDY_INSTR: + cmd |= COMMAND_RBSY_CHK; + break; + } + } + + cmd |= COMMAND_GO | COMMAND_CE(ctrl->cur_cs); + writel_relaxed(cmd, ctrl->regs + COMMAND); + ret = wait_for_completion_timeout(&ctrl->command_complete, + msecs_to_jiffies(500)); + if (!ret) { + dev_err(ctrl->dev, "COMMAND timeout\n"); + tegra_nand_dump_reg(ctrl); + tegra_nand_controller_abort(ctrl); + return -ETIMEDOUT; + } + + if (instr_data_in) { + reg = readl_relaxed(ctrl->regs + RESP); + memcpy(instr_data_in->ctx.data.buf.in + offset, ®, size); + } + + return 0; +} + +static const struct nand_op_parser tegra_nand_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN(tegra_nand_cmd, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), + NAND_OP_PARSER_PATTERN(tegra_nand_cmd, + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(false, 4)), + NAND_OP_PARSER_PATTERN(tegra_nand_cmd, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 8), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, 4)), + ); + +static void tegra_nand_select_target(struct nand_chip *chip, + unsigned int die_nr) +{ + struct tegra_nand_chip *nand = to_tegra_chip(chip); + struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller); + + ctrl->cur_cs = nand->cs[die_nr]; +} + +static int tegra_nand_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + if (!check_only) + tegra_nand_select_target(chip, op->cs); + + return nand_op_parser_exec_op(chip, &tegra_nand_op_parser, op, + check_only); +} + +static void tegra_nand_hw_ecc(struct tegra_nand_controller *ctrl, + struct nand_chip *chip, bool enable) +{ + struct tegra_nand_chip *nand = to_tegra_chip(chip); + + if (chip->ecc.algo == NAND_ECC_ALGO_BCH && enable) + writel_relaxed(nand->bch_config, ctrl->regs + BCH_CONFIG); + else + writel_relaxed(0, ctrl->regs + BCH_CONFIG); + + if (enable) + writel_relaxed(nand->config_ecc, ctrl->regs + CONFIG); + else + writel_relaxed(nand->config, ctrl->regs + CONFIG); +} + +static int tegra_nand_page_xfer(struct mtd_info *mtd, struct nand_chip *chip, + void *buf, void *oob_buf, int oob_len, int page, + bool read) +{ + struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller); + enum dma_data_direction dir = read ? DMA_FROM_DEVICE : DMA_TO_DEVICE; + dma_addr_t dma_addr = 0, dma_addr_oob = 0; + u32 addr1, cmd, dma_ctrl; + int ret; + + tegra_nand_select_target(chip, chip->cur_cs); + + if (read) { + writel_relaxed(NAND_CMD_READ0, ctrl->regs + CMD_REG1); + writel_relaxed(NAND_CMD_READSTART, ctrl->regs + CMD_REG2); + } else { + writel_relaxed(NAND_CMD_SEQIN, ctrl->regs + CMD_REG1); + writel_relaxed(NAND_CMD_PAGEPROG, ctrl->regs + CMD_REG2); + } + cmd = COMMAND_CLE | COMMAND_SEC_CMD; + + /* Lower 16-bits are column, by default 0 */ + addr1 = page << 16; + + if (!buf) + addr1 |= mtd->writesize; + writel_relaxed(addr1, ctrl->regs + ADDR_REG1); + + if (chip->options & NAND_ROW_ADDR_3) { + writel_relaxed(page >> 16, ctrl->regs + ADDR_REG2); + cmd |= COMMAND_ALE | COMMAND_ALE_SIZE(5); + } else { + cmd |= COMMAND_ALE | COMMAND_ALE_SIZE(4); + } + + if (buf) { + dma_addr = dma_map_single(ctrl->dev, buf, mtd->writesize, dir); + ret = dma_mapping_error(ctrl->dev, dma_addr); + if (ret) { + dev_err(ctrl->dev, "dma mapping error\n"); + return -EINVAL; + } + + writel_relaxed(mtd->writesize - 1, ctrl->regs + DMA_CFG_A); + writel_relaxed(dma_addr, ctrl->regs + DATA_PTR); + } + + if (oob_buf) { + dma_addr_oob = dma_map_single(ctrl->dev, oob_buf, mtd->oobsize, + dir); + ret = dma_mapping_error(ctrl->dev, dma_addr_oob); + if (ret) { + dev_err(ctrl->dev, "dma mapping error\n"); + ret = -EINVAL; + goto err_unmap_dma_page; + } + + writel_relaxed(oob_len - 1, ctrl->regs + DMA_CFG_B); + writel_relaxed(dma_addr_oob, ctrl->regs + TAG_PTR); + } + + dma_ctrl = DMA_MST_CTRL_GO | DMA_MST_CTRL_PERF_EN | + DMA_MST_CTRL_IE_DONE | DMA_MST_CTRL_IS_DONE | + DMA_MST_CTRL_BURST_16; + + if (buf) + dma_ctrl |= DMA_MST_CTRL_EN_A; + if (oob_buf) + dma_ctrl |= DMA_MST_CTRL_EN_B; + + if (read) + dma_ctrl |= DMA_MST_CTRL_IN | DMA_MST_CTRL_REUSE; + else + dma_ctrl |= DMA_MST_CTRL_OUT; + + writel_relaxed(dma_ctrl, ctrl->regs + DMA_MST_CTRL); + + cmd |= COMMAND_GO | COMMAND_RBSY_CHK | COMMAND_TRANS_SIZE(9) | + COMMAND_CE(ctrl->cur_cs); + + if (buf) + cmd |= COMMAND_A_VALID; + if (oob_buf) + cmd |= COMMAND_B_VALID; + + if (read) + cmd |= COMMAND_RX; + else + cmd |= COMMAND_TX | COMMAND_AFT_DAT; + + writel_relaxed(cmd, ctrl->regs + COMMAND); + + ret = wait_for_completion_timeout(&ctrl->command_complete, + msecs_to_jiffies(500)); + if (!ret) { + dev_err(ctrl->dev, "COMMAND timeout\n"); + tegra_nand_dump_reg(ctrl); + tegra_nand_controller_abort(ctrl); + ret = -ETIMEDOUT; + goto err_unmap_dma; + } + + ret = wait_for_completion_timeout(&ctrl->dma_complete, + msecs_to_jiffies(500)); + if (!ret) { + dev_err(ctrl->dev, "DMA timeout\n"); + tegra_nand_dump_reg(ctrl); + tegra_nand_controller_abort(ctrl); + ret = -ETIMEDOUT; + goto err_unmap_dma; + } + ret = 0; + +err_unmap_dma: + if (oob_buf) + dma_unmap_single(ctrl->dev, dma_addr_oob, mtd->oobsize, dir); +err_unmap_dma_page: + if (buf) + dma_unmap_single(ctrl->dev, dma_addr, mtd->writesize, dir); + + return ret; +} + +static int tegra_nand_read_page_raw(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + void *oob_buf = oob_required ? chip->oob_poi : NULL; + + return tegra_nand_page_xfer(mtd, chip, buf, oob_buf, + mtd->oobsize, page, true); +} + +static int tegra_nand_write_page_raw(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + void *oob_buf = oob_required ? chip->oob_poi : NULL; + + return tegra_nand_page_xfer(mtd, chip, (void *)buf, oob_buf, + mtd->oobsize, page, false); +} + +static int tegra_nand_read_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + return tegra_nand_page_xfer(mtd, chip, NULL, chip->oob_poi, + mtd->oobsize, page, true); +} + +static int tegra_nand_write_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + return tegra_nand_page_xfer(mtd, chip, NULL, chip->oob_poi, + mtd->oobsize, page, false); +} + +static int tegra_nand_read_page_hwecc(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller); + struct tegra_nand_chip *nand = to_tegra_chip(chip); + void *oob_buf = oob_required ? chip->oob_poi : NULL; + u32 dec_stat, max_corr_cnt; + unsigned long fail_sec_flag; + int ret; + + tegra_nand_hw_ecc(ctrl, chip, true); + ret = tegra_nand_page_xfer(mtd, chip, buf, oob_buf, 0, page, true); + tegra_nand_hw_ecc(ctrl, chip, false); + if (ret) + return ret; + + /* No correctable or un-correctable errors, page must have 0 bitflips */ + if (!ctrl->last_read_error) + return 0; + + /* + * Correctable or un-correctable errors occurred. Use DEC_STAT_BUF + * which contains information for all ECC selections. + * + * Note that since we do not use Command Queues DEC_RESULT does not + * state the number of pages we can read from the DEC_STAT_BUF. But + * since CORRFAIL_ERR did occur during page read we do have a valid + * result in DEC_STAT_BUF. + */ + ctrl->last_read_error = false; + dec_stat = readl_relaxed(ctrl->regs + DEC_STAT_BUF); + + fail_sec_flag = (dec_stat & DEC_STAT_BUF_FAIL_SEC_FLAG_MASK) >> + DEC_STAT_BUF_FAIL_SEC_FLAG_SHIFT; + + max_corr_cnt = (dec_stat & DEC_STAT_BUF_MAX_CORR_CNT_MASK) >> + DEC_STAT_BUF_MAX_CORR_CNT_SHIFT; + + if (fail_sec_flag) { + int bit, max_bitflips = 0; + + /* + * Since we do not support subpage writes, a complete page + * is either written or not. We can take a shortcut here by + * checking wheather any of the sector has been successful + * read. If at least one sectors has been read successfully, + * the page must have been a written previously. It cannot + * be an erased page. + * + * E.g. controller might return fail_sec_flag with 0x4, which + * would mean only the third sector failed to correct. The + * page must have been written and the third sector is really + * not correctable anymore. + */ + if (fail_sec_flag ^ GENMASK(chip->ecc.steps - 1, 0)) { + mtd->ecc_stats.failed += hweight8(fail_sec_flag); + return max_corr_cnt; + } + + /* + * All sectors failed to correct, but the ECC isn't smart + * enough to figure out if a page is really just erased. + * Read OOB data and check whether data/OOB is completely + * erased or if error correction just failed for all sub- + * pages. + */ + ret = tegra_nand_read_oob(chip, page); + if (ret < 0) + return ret; + + for_each_set_bit(bit, &fail_sec_flag, chip->ecc.steps) { + u8 *data = buf + (chip->ecc.size * bit); + u8 *oob = chip->oob_poi + nand->ecc.offset + + (chip->ecc.bytes * bit); + + ret = nand_check_erased_ecc_chunk(data, chip->ecc.size, + oob, chip->ecc.bytes, + NULL, 0, + chip->ecc.strength); + if (ret < 0) { + mtd->ecc_stats.failed++; + } else { + mtd->ecc_stats.corrected += ret; + max_bitflips = max(ret, max_bitflips); + } + } + + return max_t(unsigned int, max_corr_cnt, max_bitflips); + } else { + int corr_sec_flag; + + corr_sec_flag = (dec_stat & DEC_STAT_BUF_CORR_SEC_FLAG_MASK) >> + DEC_STAT_BUF_CORR_SEC_FLAG_SHIFT; + + /* + * The value returned in the register is the maximum of + * bitflips encountered in any of the ECC regions. As there is + * no way to get the number of bitflips in a specific regions + * we are not able to deliver correct stats but instead + * overestimate the number of corrected bitflips by assuming + * that all regions where errors have been corrected + * encountered the maximum number of bitflips. + */ + mtd->ecc_stats.corrected += max_corr_cnt * hweight8(corr_sec_flag); + + return max_corr_cnt; + } +} + +static int tegra_nand_write_page_hwecc(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller); + void *oob_buf = oob_required ? chip->oob_poi : NULL; + int ret; + + tegra_nand_hw_ecc(ctrl, chip, true); + ret = tegra_nand_page_xfer(mtd, chip, (void *)buf, oob_buf, + 0, page, false); + tegra_nand_hw_ecc(ctrl, chip, false); + + return ret; +} + +static void tegra_nand_setup_timing(struct tegra_nand_controller *ctrl, + const struct nand_sdr_timings *timings) +{ + /* + * The period (and all other timings in this function) is in ps, + * so need to take care here to avoid integer overflows. + */ + unsigned int rate = clk_get_rate(ctrl->clk) / 1000000; + unsigned int period = DIV_ROUND_UP(1000000, rate); + u32 val, reg = 0; + + val = DIV_ROUND_UP(max3(timings->tAR_min, timings->tRR_min, + timings->tRC_min), period); + reg |= TIMING_TCR_TAR_TRR(OFFSET(val, 3)); + + val = DIV_ROUND_UP(max(max(timings->tCS_min, timings->tCH_min), + max(timings->tALS_min, timings->tALH_min)), + period); + reg |= TIMING_TCS(OFFSET(val, 2)); + + val = DIV_ROUND_UP(max(timings->tRP_min, timings->tREA_max) + 6000, + period); + reg |= TIMING_TRP(OFFSET(val, 1)) | TIMING_TRP_RESP(OFFSET(val, 1)); + + reg |= TIMING_TWB(OFFSET(DIV_ROUND_UP(timings->tWB_max, period), 1)); + reg |= TIMING_TWHR(OFFSET(DIV_ROUND_UP(timings->tWHR_min, period), 1)); + reg |= TIMING_TWH(OFFSET(DIV_ROUND_UP(timings->tWH_min, period), 1)); + reg |= TIMING_TWP(OFFSET(DIV_ROUND_UP(timings->tWP_min, period), 1)); + reg |= TIMING_TRH(OFFSET(DIV_ROUND_UP(timings->tREH_min, period), 1)); + + writel_relaxed(reg, ctrl->regs + TIMING_1); + + val = DIV_ROUND_UP(timings->tADL_min, period); + reg = TIMING_TADL(OFFSET(val, 3)); + + writel_relaxed(reg, ctrl->regs + TIMING_2); +} + +static int tegra_nand_setup_interface(struct nand_chip *chip, int csline, + const struct nand_interface_config *conf) +{ + struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller); + const struct nand_sdr_timings *timings; + + timings = nand_get_sdr_timings(conf); + if (IS_ERR(timings)) + return PTR_ERR(timings); + + if (csline == NAND_DATA_IFACE_CHECK_ONLY) + return 0; + + tegra_nand_setup_timing(ctrl, timings); + + return 0; +} + +static const int rs_strength_bootable[] = { 4 }; +static const int rs_strength[] = { 4, 6, 8 }; +static const int bch_strength_bootable[] = { 8, 16 }; +static const int bch_strength[] = { 4, 8, 14, 16 }; + +static int tegra_nand_get_strength(struct nand_chip *chip, const int *strength, + int strength_len, int bits_per_step, + int oobsize) +{ + struct nand_device *base = mtd_to_nanddev(nand_to_mtd(chip)); + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(base); + bool maximize = base->ecc.user_conf.flags & NAND_ECC_MAXIMIZE_STRENGTH; + int i; + + /* + * Loop through available strengths. Backwards in case we try to + * maximize the BCH strength. + */ + for (i = 0; i < strength_len; i++) { + int strength_sel, bytes_per_step, bytes_per_page; + + if (maximize) { + strength_sel = strength[strength_len - i - 1]; + } else { + strength_sel = strength[i]; + + if (strength_sel < requirements->strength) + continue; + } + + bytes_per_step = DIV_ROUND_UP(bits_per_step * strength_sel, + BITS_PER_BYTE); + bytes_per_page = round_up(bytes_per_step * chip->ecc.steps, 4); + + /* Check whether strength fits OOB */ + if (bytes_per_page < (oobsize - SKIP_SPARE_BYTES)) + return strength_sel; + } + + return -EINVAL; +} + +static int tegra_nand_select_strength(struct nand_chip *chip, int oobsize) +{ + const int *strength; + int strength_len, bits_per_step; + + switch (chip->ecc.algo) { + case NAND_ECC_ALGO_RS: + bits_per_step = BITS_PER_STEP_RS; + if (chip->options & NAND_IS_BOOT_MEDIUM) { + strength = rs_strength_bootable; + strength_len = ARRAY_SIZE(rs_strength_bootable); + } else { + strength = rs_strength; + strength_len = ARRAY_SIZE(rs_strength); + } + break; + case NAND_ECC_ALGO_BCH: + bits_per_step = BITS_PER_STEP_BCH; + if (chip->options & NAND_IS_BOOT_MEDIUM) { + strength = bch_strength_bootable; + strength_len = ARRAY_SIZE(bch_strength_bootable); + } else { + strength = bch_strength; + strength_len = ARRAY_SIZE(bch_strength); + } + break; + default: + return -EINVAL; + } + + return tegra_nand_get_strength(chip, strength, strength_len, + bits_per_step, oobsize); +} + +static int tegra_nand_attach_chip(struct nand_chip *chip) +{ + struct tegra_nand_controller *ctrl = to_tegra_ctrl(chip->controller); + const struct nand_ecc_props *requirements = + nanddev_get_ecc_requirements(&chip->base); + struct tegra_nand_chip *nand = to_tegra_chip(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int bits_per_step; + int ret; + + if (chip->bbt_options & NAND_BBT_USE_FLASH) + chip->bbt_options |= NAND_BBT_NO_OOB; + + chip->ecc.engine_type = NAND_ECC_ENGINE_TYPE_ON_HOST; + chip->ecc.size = 512; + chip->ecc.steps = mtd->writesize / chip->ecc.size; + if (requirements->step_size != 512) { + dev_err(ctrl->dev, "Unsupported step size %d\n", + requirements->step_size); + return -EINVAL; + } + + chip->ecc.read_page = tegra_nand_read_page_hwecc; + chip->ecc.write_page = tegra_nand_write_page_hwecc; + chip->ecc.read_page_raw = tegra_nand_read_page_raw; + chip->ecc.write_page_raw = tegra_nand_write_page_raw; + chip->ecc.read_oob = tegra_nand_read_oob; + chip->ecc.write_oob = tegra_nand_write_oob; + + if (chip->options & NAND_BUSWIDTH_16) + nand->config |= CONFIG_BUS_WIDTH_16; + + if (chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) { + if (mtd->writesize < 2048) + chip->ecc.algo = NAND_ECC_ALGO_RS; + else + chip->ecc.algo = NAND_ECC_ALGO_BCH; + } + + if (chip->ecc.algo == NAND_ECC_ALGO_BCH && mtd->writesize < 2048) { + dev_err(ctrl->dev, "BCH supports 2K or 4K page size only\n"); + return -EINVAL; + } + + if (!chip->ecc.strength) { + ret = tegra_nand_select_strength(chip, mtd->oobsize); + if (ret < 0) { + dev_err(ctrl->dev, + "No valid strength found, minimum %d\n", + requirements->strength); + return ret; + } + + chip->ecc.strength = ret; + } + + nand->config_ecc = CONFIG_PIPE_EN | CONFIG_SKIP_SPARE | + CONFIG_SKIP_SPARE_SIZE_4; + + switch (chip->ecc.algo) { + case NAND_ECC_ALGO_RS: + bits_per_step = BITS_PER_STEP_RS * chip->ecc.strength; + mtd_set_ooblayout(mtd, &tegra_nand_oob_rs_ops); + nand->config_ecc |= CONFIG_HW_ECC | CONFIG_ECC_SEL | + CONFIG_ERR_COR; + switch (chip->ecc.strength) { + case 4: + nand->config_ecc |= CONFIG_TVAL_4; + break; + case 6: + nand->config_ecc |= CONFIG_TVAL_6; + break; + case 8: + nand->config_ecc |= CONFIG_TVAL_8; + break; + default: + dev_err(ctrl->dev, "ECC strength %d not supported\n", + chip->ecc.strength); + return -EINVAL; + } + break; + case NAND_ECC_ALGO_BCH: + bits_per_step = BITS_PER_STEP_BCH * chip->ecc.strength; + mtd_set_ooblayout(mtd, &tegra_nand_oob_bch_ops); + nand->bch_config = BCH_ENABLE; + switch (chip->ecc.strength) { + case 4: + nand->bch_config |= BCH_TVAL_4; + break; + case 8: + nand->bch_config |= BCH_TVAL_8; + break; + case 14: + nand->bch_config |= BCH_TVAL_14; + break; + case 16: + nand->bch_config |= BCH_TVAL_16; + break; + default: + dev_err(ctrl->dev, "ECC strength %d not supported\n", + chip->ecc.strength); + return -EINVAL; + } + break; + default: + dev_err(ctrl->dev, "ECC algorithm not supported\n"); + return -EINVAL; + } + + dev_info(ctrl->dev, "Using %s with strength %d per 512 byte step\n", + chip->ecc.algo == NAND_ECC_ALGO_BCH ? "BCH" : "RS", + chip->ecc.strength); + + chip->ecc.bytes = DIV_ROUND_UP(bits_per_step, BITS_PER_BYTE); + + switch (mtd->writesize) { + case 256: + nand->config |= CONFIG_PS_256; + break; + case 512: + nand->config |= CONFIG_PS_512; + break; + case 1024: + nand->config |= CONFIG_PS_1024; + break; + case 2048: + nand->config |= CONFIG_PS_2048; + break; + case 4096: + nand->config |= CONFIG_PS_4096; + break; + default: + dev_err(ctrl->dev, "Unsupported writesize %d\n", + mtd->writesize); + return -ENODEV; + } + + /* Store complete configuration for HW ECC in config_ecc */ + nand->config_ecc |= nand->config; + + /* Non-HW ECC read/writes complete OOB */ + nand->config |= CONFIG_TAG_BYTE_SIZE(mtd->oobsize - 1); + writel_relaxed(nand->config, ctrl->regs + CONFIG); + + return 0; +} + +static const struct nand_controller_ops tegra_nand_controller_ops = { + .attach_chip = &tegra_nand_attach_chip, + .exec_op = tegra_nand_exec_op, + .setup_interface = tegra_nand_setup_interface, +}; + +static int tegra_nand_chips_init(struct device *dev, + struct tegra_nand_controller *ctrl) +{ + struct device_node *np = dev->of_node; + struct device_node *np_nand; + int nsels, nchips = of_get_child_count(np); + struct tegra_nand_chip *nand; + struct mtd_info *mtd; + struct nand_chip *chip; + int ret; + u32 cs; + + if (nchips != 1) { + dev_err(dev, "Currently only one NAND chip supported\n"); + return -EINVAL; + } + + np_nand = of_get_next_child(np, NULL); + + nsels = of_property_count_elems_of_size(np_nand, "reg", sizeof(u32)); + if (nsels != 1) { + dev_err(dev, "Missing/invalid reg property\n"); + return -EINVAL; + } + + /* Retrieve CS id, currently only single die NAND supported */ + ret = of_property_read_u32(np_nand, "reg", &cs); + if (ret) { + dev_err(dev, "could not retrieve reg property: %d\n", ret); + return ret; + } + + nand = devm_kzalloc(dev, sizeof(*nand), GFP_KERNEL); + if (!nand) + return -ENOMEM; + + nand->cs[0] = cs; + + nand->wp_gpio = devm_gpiod_get_optional(dev, "wp", GPIOD_OUT_LOW); + + if (IS_ERR(nand->wp_gpio)) { + ret = PTR_ERR(nand->wp_gpio); + dev_err(dev, "Failed to request WP GPIO: %d\n", ret); + return ret; + } + + chip = &nand->chip; + chip->controller = &ctrl->controller; + + mtd = nand_to_mtd(chip); + + mtd->dev.parent = dev; + mtd->owner = THIS_MODULE; + + nand_set_flash_node(chip, np_nand); + + if (!mtd->name) + mtd->name = "tegra_nand"; + + chip->options = NAND_NO_SUBPAGE_WRITE | NAND_USES_DMA; + + ret = nand_scan(chip, 1); + if (ret) + return ret; + + mtd_ooblayout_ecc(mtd, 0, &nand->ecc); + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) { + dev_err(dev, "Failed to register mtd device: %d\n", ret); + nand_cleanup(chip); + return ret; + } + + ctrl->chip = chip; + + return 0; +} + +static int tegra_nand_probe(struct platform_device *pdev) +{ + struct reset_control *rst; + struct tegra_nand_controller *ctrl; + int err = 0; + + ctrl = devm_kzalloc(&pdev->dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + ctrl->dev = &pdev->dev; + platform_set_drvdata(pdev, ctrl); + nand_controller_init(&ctrl->controller); + ctrl->controller.ops = &tegra_nand_controller_ops; + + ctrl->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctrl->regs)) + return PTR_ERR(ctrl->regs); + + rst = devm_reset_control_get(&pdev->dev, "nand"); + if (IS_ERR(rst)) + return PTR_ERR(rst); + + ctrl->clk = devm_clk_get(&pdev->dev, "nand"); + if (IS_ERR(ctrl->clk)) + return PTR_ERR(ctrl->clk); + + err = devm_tegra_core_dev_init_opp_table_common(&pdev->dev); + if (err) + return err; + + /* + * This driver doesn't support active power management yet, + * so we will simply keep device resumed. + */ + pm_runtime_enable(&pdev->dev); + err = pm_runtime_resume_and_get(&pdev->dev); + if (err) + goto err_dis_pm; + + err = reset_control_reset(rst); + if (err) { + dev_err(ctrl->dev, "Failed to reset HW: %d\n", err); + goto err_put_pm; + } + + writel_relaxed(HWSTATUS_CMD_DEFAULT, ctrl->regs + HWSTATUS_CMD); + writel_relaxed(HWSTATUS_MASK_DEFAULT, ctrl->regs + HWSTATUS_MASK); + writel_relaxed(INT_MASK, ctrl->regs + IER); + + init_completion(&ctrl->command_complete); + init_completion(&ctrl->dma_complete); + + ctrl->irq = platform_get_irq(pdev, 0); + if (ctrl->irq < 0) { + err = ctrl->irq; + goto err_put_pm; + } + err = devm_request_irq(&pdev->dev, ctrl->irq, tegra_nand_irq, 0, + dev_name(&pdev->dev), ctrl); + if (err) { + dev_err(ctrl->dev, "Failed to get IRQ: %d\n", err); + goto err_put_pm; + } + + writel_relaxed(DMA_MST_CTRL_IS_DONE, ctrl->regs + DMA_MST_CTRL); + + err = tegra_nand_chips_init(ctrl->dev, ctrl); + if (err) + goto err_put_pm; + + return 0; + +err_put_pm: + pm_runtime_put_sync_suspend(ctrl->dev); + pm_runtime_force_suspend(ctrl->dev); +err_dis_pm: + pm_runtime_disable(&pdev->dev); + return err; +} + +static void tegra_nand_remove(struct platform_device *pdev) +{ + struct tegra_nand_controller *ctrl = platform_get_drvdata(pdev); + struct nand_chip *chip = ctrl->chip; + struct mtd_info *mtd = nand_to_mtd(chip); + + WARN_ON(mtd_device_unregister(mtd)); + + nand_cleanup(chip); + + pm_runtime_put_sync_suspend(ctrl->dev); + pm_runtime_force_suspend(ctrl->dev); +} + +static int __maybe_unused tegra_nand_runtime_resume(struct device *dev) +{ + struct tegra_nand_controller *ctrl = dev_get_drvdata(dev); + int err; + + err = clk_prepare_enable(ctrl->clk); + if (err) { + dev_err(dev, "Failed to enable clock: %d\n", err); + return err; + } + + return 0; +} + +static int __maybe_unused tegra_nand_runtime_suspend(struct device *dev) +{ + struct tegra_nand_controller *ctrl = dev_get_drvdata(dev); + + clk_disable_unprepare(ctrl->clk); + + return 0; +} + +static const struct dev_pm_ops tegra_nand_pm = { + SET_RUNTIME_PM_OPS(tegra_nand_runtime_suspend, tegra_nand_runtime_resume, + NULL) +}; + +static const struct of_device_id tegra_nand_of_match[] = { + { .compatible = "nvidia,tegra20-nand" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tegra_nand_of_match); + +static struct platform_driver tegra_nand_driver = { + .driver = { + .name = "tegra-nand", + .of_match_table = tegra_nand_of_match, + .pm = &tegra_nand_pm, + }, + .probe = tegra_nand_probe, + .remove = tegra_nand_remove, +}; +module_platform_driver(tegra_nand_driver); + +MODULE_DESCRIPTION("NVIDIA Tegra NAND driver"); +MODULE_AUTHOR("Thierry Reding "); +MODULE_AUTHOR("Lucas Stach "); +MODULE_AUTHOR("Stefan Agner "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/raw/txx9ndfmc.c b/drivers/mtd/nand/raw/txx9ndfmc.c new file mode 100644 index 000000000..4cc6e91db --- /dev/null +++ b/drivers/mtd/nand/raw/txx9ndfmc.c @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TXx9 NAND flash memory controller driver + * Based on RBTX49xx patch from CELF patch archive. + * + * (C) Copyright TOSHIBA CORPORATION 2004-2007 + * All Rights Reserved. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* TXX9 NDFMC Registers */ +#define TXX9_NDFDTR 0x00 +#define TXX9_NDFMCR 0x04 +#define TXX9_NDFSR 0x08 +#define TXX9_NDFISR 0x0c +#define TXX9_NDFIMR 0x10 +#define TXX9_NDFSPR 0x14 +#define TXX9_NDFRSTR 0x18 /* not TX4939 */ + +/* NDFMCR : NDFMC Mode Control */ +#define TXX9_NDFMCR_WE 0x80 +#define TXX9_NDFMCR_ECC_ALL 0x60 +#define TXX9_NDFMCR_ECC_RESET 0x60 +#define TXX9_NDFMCR_ECC_READ 0x40 +#define TXX9_NDFMCR_ECC_ON 0x20 +#define TXX9_NDFMCR_ECC_OFF 0x00 +#define TXX9_NDFMCR_CE 0x10 +#define TXX9_NDFMCR_BSPRT 0x04 /* TX4925/TX4926 only */ +#define TXX9_NDFMCR_ALE 0x02 +#define TXX9_NDFMCR_CLE 0x01 +/* TX4939 only */ +#define TXX9_NDFMCR_X16 0x0400 +#define TXX9_NDFMCR_DMAREQ_MASK 0x0300 +#define TXX9_NDFMCR_DMAREQ_NODMA 0x0000 +#define TXX9_NDFMCR_DMAREQ_128 0x0100 +#define TXX9_NDFMCR_DMAREQ_256 0x0200 +#define TXX9_NDFMCR_DMAREQ_512 0x0300 +#define TXX9_NDFMCR_CS_MASK 0x0c +#define TXX9_NDFMCR_CS(ch) ((ch) << 2) + +/* NDFMCR : NDFMC Status */ +#define TXX9_NDFSR_BUSY 0x80 +/* TX4939 only */ +#define TXX9_NDFSR_DMARUN 0x40 + +/* NDFMCR : NDFMC Reset */ +#define TXX9_NDFRSTR_RST 0x01 + +struct txx9ndfmc_priv { + struct platform_device *dev; + struct nand_chip chip; + int cs; + const char *mtdname; +}; + +#define MAX_TXX9NDFMC_DEV 4 +struct txx9ndfmc_drvdata { + struct mtd_info *mtds[MAX_TXX9NDFMC_DEV]; + void __iomem *base; + unsigned char hold; /* in gbusclock */ + unsigned char spw; /* in gbusclock */ + struct nand_controller controller; +}; + +static struct platform_device *mtd_to_platdev(struct mtd_info *mtd) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct txx9ndfmc_priv *txx9_priv = nand_get_controller_data(chip); + return txx9_priv->dev; +} + +static void __iomem *ndregaddr(struct platform_device *dev, unsigned int reg) +{ + struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev); + struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev); + + return drvdata->base + (reg << plat->shift); +} + +static u32 txx9ndfmc_read(struct platform_device *dev, unsigned int reg) +{ + return __raw_readl(ndregaddr(dev, reg)); +} + +static void txx9ndfmc_write(struct platform_device *dev, + u32 val, unsigned int reg) +{ + __raw_writel(val, ndregaddr(dev, reg)); +} + +static uint8_t txx9ndfmc_read_byte(struct nand_chip *chip) +{ + struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip)); + + return txx9ndfmc_read(dev, TXX9_NDFDTR); +} + +static void txx9ndfmc_write_buf(struct nand_chip *chip, const uint8_t *buf, + int len) +{ + struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip)); + void __iomem *ndfdtr = ndregaddr(dev, TXX9_NDFDTR); + u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR); + + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_WE, TXX9_NDFMCR); + while (len--) + __raw_writel(*buf++, ndfdtr); + txx9ndfmc_write(dev, mcr, TXX9_NDFMCR); +} + +static void txx9ndfmc_read_buf(struct nand_chip *chip, uint8_t *buf, int len) +{ + struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip)); + void __iomem *ndfdtr = ndregaddr(dev, TXX9_NDFDTR); + + while (len--) + *buf++ = __raw_readl(ndfdtr); +} + +static void txx9ndfmc_cmd_ctrl(struct nand_chip *chip, int cmd, + unsigned int ctrl) +{ + struct txx9ndfmc_priv *txx9_priv = nand_get_controller_data(chip); + struct platform_device *dev = txx9_priv->dev; + struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev); + + if (ctrl & NAND_CTRL_CHANGE) { + u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR); + + mcr &= ~(TXX9_NDFMCR_CLE | TXX9_NDFMCR_ALE | TXX9_NDFMCR_CE); + mcr |= ctrl & NAND_CLE ? TXX9_NDFMCR_CLE : 0; + mcr |= ctrl & NAND_ALE ? TXX9_NDFMCR_ALE : 0; + /* TXX9_NDFMCR_CE bit is 0:high 1:low */ + mcr |= ctrl & NAND_NCE ? TXX9_NDFMCR_CE : 0; + if (txx9_priv->cs >= 0 && (ctrl & NAND_NCE)) { + mcr &= ~TXX9_NDFMCR_CS_MASK; + mcr |= TXX9_NDFMCR_CS(txx9_priv->cs); + } + txx9ndfmc_write(dev, mcr, TXX9_NDFMCR); + } + if (cmd != NAND_CMD_NONE) + txx9ndfmc_write(dev, cmd & 0xff, TXX9_NDFDTR); + if (plat->flags & NDFMC_PLAT_FLAG_DUMMYWRITE) { + /* dummy write to update external latch */ + if ((ctrl & NAND_CTRL_CHANGE) && cmd == NAND_CMD_NONE) + txx9ndfmc_write(dev, 0, TXX9_NDFDTR); + } +} + +static int txx9ndfmc_dev_ready(struct nand_chip *chip) +{ + struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip)); + + return !(txx9ndfmc_read(dev, TXX9_NDFSR) & TXX9_NDFSR_BUSY); +} + +static int txx9ndfmc_calculate_ecc(struct nand_chip *chip, const uint8_t *dat, + uint8_t *ecc_code) +{ + struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip)); + int eccbytes; + u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR); + + mcr &= ~TXX9_NDFMCR_ECC_ALL; + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR); + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_READ, TXX9_NDFMCR); + for (eccbytes = chip->ecc.bytes; eccbytes > 0; eccbytes -= 3) { + ecc_code[1] = txx9ndfmc_read(dev, TXX9_NDFDTR); + ecc_code[0] = txx9ndfmc_read(dev, TXX9_NDFDTR); + ecc_code[2] = txx9ndfmc_read(dev, TXX9_NDFDTR); + ecc_code += 3; + } + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR); + return 0; +} + +static int txx9ndfmc_correct_data(struct nand_chip *chip, unsigned char *buf, + unsigned char *read_ecc, + unsigned char *calc_ecc) +{ + int eccsize; + int corrected = 0; + int stat; + + for (eccsize = chip->ecc.size; eccsize > 0; eccsize -= 256) { + stat = rawnand_sw_hamming_correct(chip, buf, read_ecc, + calc_ecc); + if (stat < 0) + return stat; + corrected += stat; + buf += 256; + read_ecc += 3; + calc_ecc += 3; + } + return corrected; +} + +static void txx9ndfmc_enable_hwecc(struct nand_chip *chip, int mode) +{ + struct platform_device *dev = mtd_to_platdev(nand_to_mtd(chip)); + u32 mcr = txx9ndfmc_read(dev, TXX9_NDFMCR); + + mcr &= ~TXX9_NDFMCR_ECC_ALL; + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_RESET, TXX9_NDFMCR); + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_OFF, TXX9_NDFMCR); + txx9ndfmc_write(dev, mcr | TXX9_NDFMCR_ECC_ON, TXX9_NDFMCR); +} + +static void txx9ndfmc_initialize(struct platform_device *dev) +{ + struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev); + struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev); + int tmout = 100; + + if (plat->flags & NDFMC_PLAT_FLAG_NO_RSTR) + ; /* no NDFRSTR. Write to NDFSPR resets the NDFMC. */ + else { + /* reset NDFMC */ + txx9ndfmc_write(dev, + txx9ndfmc_read(dev, TXX9_NDFRSTR) | + TXX9_NDFRSTR_RST, + TXX9_NDFRSTR); + while (txx9ndfmc_read(dev, TXX9_NDFRSTR) & TXX9_NDFRSTR_RST) { + if (--tmout == 0) { + dev_err(&dev->dev, "reset failed.\n"); + break; + } + udelay(1); + } + } + /* setup Hold Time, Strobe Pulse Width */ + txx9ndfmc_write(dev, (drvdata->hold << 4) | drvdata->spw, TXX9_NDFSPR); + txx9ndfmc_write(dev, + (plat->flags & NDFMC_PLAT_FLAG_USE_BSPRT) ? + TXX9_NDFMCR_BSPRT : 0, TXX9_NDFMCR); +} + +#define TXX9NDFMC_NS_TO_CYC(gbusclk, ns) \ + DIV_ROUND_UP((ns) * DIV_ROUND_UP(gbusclk, 1000), 1000000) + +static int txx9ndfmc_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return 0; + + chip->ecc.strength = 1; + + if (mtd->writesize >= 512) { + chip->ecc.size = 512; + chip->ecc.bytes = 6; + } else { + chip->ecc.size = 256; + chip->ecc.bytes = 3; + } + + chip->ecc.calculate = txx9ndfmc_calculate_ecc; + chip->ecc.correct = txx9ndfmc_correct_data; + chip->ecc.hwctl = txx9ndfmc_enable_hwecc; + + return 0; +} + +static const struct nand_controller_ops txx9ndfmc_controller_ops = { + .attach_chip = txx9ndfmc_attach_chip, +}; + +static int txx9ndfmc_probe(struct platform_device *dev) +{ + struct txx9ndfmc_platform_data *plat = dev_get_platdata(&dev->dev); + int hold, spw; + int i; + struct txx9ndfmc_drvdata *drvdata; + unsigned long gbusclk = plat->gbus_clock; + + drvdata = devm_kzalloc(&dev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + drvdata->base = devm_platform_ioremap_resource(dev, 0); + if (IS_ERR(drvdata->base)) + return PTR_ERR(drvdata->base); + + hold = plat->hold ?: 20; /* tDH */ + spw = plat->spw ?: 90; /* max(tREADID, tWP, tRP) */ + + hold = TXX9NDFMC_NS_TO_CYC(gbusclk, hold); + spw = TXX9NDFMC_NS_TO_CYC(gbusclk, spw); + if (plat->flags & NDFMC_PLAT_FLAG_HOLDADD) + hold -= 2; /* actual hold time : (HOLD + 2) BUSCLK */ + spw -= 1; /* actual wait time : (SPW + 1) BUSCLK */ + hold = clamp(hold, 1, 15); + drvdata->hold = hold; + spw = clamp(spw, 1, 15); + drvdata->spw = spw; + dev_info(&dev->dev, "CLK:%ldMHz HOLD:%d SPW:%d\n", + (gbusclk + 500000) / 1000000, hold, spw); + + nand_controller_init(&drvdata->controller); + drvdata->controller.ops = &txx9ndfmc_controller_ops; + + platform_set_drvdata(dev, drvdata); + txx9ndfmc_initialize(dev); + + for (i = 0; i < MAX_TXX9NDFMC_DEV; i++) { + struct txx9ndfmc_priv *txx9_priv; + struct nand_chip *chip; + struct mtd_info *mtd; + + if (!(plat->ch_mask & (1 << i))) + continue; + txx9_priv = kzalloc_obj(struct txx9ndfmc_priv); + if (!txx9_priv) + continue; + chip = &txx9_priv->chip; + mtd = nand_to_mtd(chip); + mtd->dev.parent = &dev->dev; + + chip->legacy.read_byte = txx9ndfmc_read_byte; + chip->legacy.read_buf = txx9ndfmc_read_buf; + chip->legacy.write_buf = txx9ndfmc_write_buf; + chip->legacy.cmd_ctrl = txx9ndfmc_cmd_ctrl; + chip->legacy.dev_ready = txx9ndfmc_dev_ready; + chip->legacy.chip_delay = 100; + chip->controller = &drvdata->controller; + + nand_set_controller_data(chip, txx9_priv); + txx9_priv->dev = dev; + + if (plat->ch_mask != 1) { + txx9_priv->cs = i; + txx9_priv->mtdname = kasprintf(GFP_KERNEL, "%s.%u", + dev_name(&dev->dev), i); + } else { + txx9_priv->cs = -1; + txx9_priv->mtdname = kstrdup(dev_name(&dev->dev), + GFP_KERNEL); + } + if (!txx9_priv->mtdname) { + kfree(txx9_priv); + dev_err(&dev->dev, "Unable to allocate MTD name.\n"); + continue; + } + if (plat->wide_mask & (1 << i)) + chip->options |= NAND_BUSWIDTH_16; + + if (nand_scan(chip, 1)) { + kfree(txx9_priv->mtdname); + kfree(txx9_priv); + continue; + } + mtd->name = txx9_priv->mtdname; + + mtd_device_register(mtd, NULL, 0); + drvdata->mtds[i] = mtd; + } + + return 0; +} + +static void txx9ndfmc_remove(struct platform_device *dev) +{ + struct txx9ndfmc_drvdata *drvdata = platform_get_drvdata(dev); + int ret, i; + + for (i = 0; i < MAX_TXX9NDFMC_DEV; i++) { + struct mtd_info *mtd = drvdata->mtds[i]; + struct nand_chip *chip; + struct txx9ndfmc_priv *txx9_priv; + + if (!mtd) + continue; + chip = mtd_to_nand(mtd); + txx9_priv = nand_get_controller_data(chip); + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); + kfree(txx9_priv->mtdname); + kfree(txx9_priv); + } +} + +#ifdef CONFIG_PM +static int txx9ndfmc_resume(struct platform_device *dev) +{ + if (platform_get_drvdata(dev)) + txx9ndfmc_initialize(dev); + return 0; +} +#else +#define txx9ndfmc_resume NULL +#endif + +static struct platform_driver txx9ndfmc_driver = { + .probe = txx9ndfmc_probe, + .remove = txx9ndfmc_remove, + .resume = txx9ndfmc_resume, + .driver = { + .name = "txx9ndfmc", + }, +}; +module_platform_driver(txx9ndfmc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("TXx9 SoC NAND flash controller driver"); +MODULE_ALIAS("platform:txx9ndfmc"); diff --git a/drivers/mtd/nand/raw/vf610_nfc.c b/drivers/mtd/nand/raw/vf610_nfc.c new file mode 100644 index 000000000..994068181 --- /dev/null +++ b/drivers/mtd/nand/raw/vf610_nfc.c @@ -0,0 +1,949 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2009-2015 Freescale Semiconductor, Inc. and others + * + * Description: MPC5125, VF610, MCF54418 and Kinetis K70 Nand driver. + * Jason ported to M54418TWR and MVFA5 (VF610). + * Authors: Stefan Agner + * Bill Pringlemeir + * Shaohui Xie + * Jason Jin + * + * Based on original driver mpc5121_nfc.c. + * + * Limitations: + * - Untested on MPC5125 and M54418. + * - DMA and pipelining not used. + * - 2K pages or less. + * - HW ECC: Only 2K page with 64+ OOB. + * - HW ECC: Only 24 and 32-bit error correction implemented. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "vf610_nfc" + +/* Register Offsets */ +#define NFC_FLASH_CMD1 0x3F00 +#define NFC_FLASH_CMD2 0x3F04 +#define NFC_COL_ADDR 0x3F08 +#define NFC_ROW_ADDR 0x3F0c +#define NFC_ROW_ADDR_INC 0x3F14 +#define NFC_FLASH_STATUS1 0x3F18 +#define NFC_FLASH_STATUS2 0x3F1c +#define NFC_CACHE_SWAP 0x3F28 +#define NFC_SECTOR_SIZE 0x3F2c +#define NFC_FLASH_CONFIG 0x3F30 +#define NFC_IRQ_STATUS 0x3F38 + +/* Addresses for NFC MAIN RAM BUFFER areas */ +#define NFC_MAIN_AREA(n) ((n) * 0x1000) + +#define PAGE_2K 0x0800 +#define OOB_64 0x0040 +#define OOB_MAX 0x0100 + +/* NFC_CMD2[CODE] controller cycle bit masks */ +#define COMMAND_CMD_BYTE1 BIT(14) +#define COMMAND_CAR_BYTE1 BIT(13) +#define COMMAND_CAR_BYTE2 BIT(12) +#define COMMAND_RAR_BYTE1 BIT(11) +#define COMMAND_RAR_BYTE2 BIT(10) +#define COMMAND_RAR_BYTE3 BIT(9) +#define COMMAND_NADDR_BYTES(x) GENMASK(13, 13 - (x) + 1) +#define COMMAND_WRITE_DATA BIT(8) +#define COMMAND_CMD_BYTE2 BIT(7) +#define COMMAND_RB_HANDSHAKE BIT(6) +#define COMMAND_READ_DATA BIT(5) +#define COMMAND_CMD_BYTE3 BIT(4) +#define COMMAND_READ_STATUS BIT(3) +#define COMMAND_READ_ID BIT(2) + +/* NFC ECC mode define */ +#define ECC_BYPASS 0 +#define ECC_45_BYTE 6 +#define ECC_60_BYTE 7 + +/*** Register Mask and bit definitions */ + +/* NFC_FLASH_CMD1 Field */ +#define CMD_BYTE2_MASK 0xFF000000 +#define CMD_BYTE2_SHIFT 24 + +/* NFC_FLASH_CM2 Field */ +#define CMD_BYTE1_MASK 0xFF000000 +#define CMD_BYTE1_SHIFT 24 +#define CMD_CODE_MASK 0x00FFFF00 +#define CMD_CODE_SHIFT 8 +#define BUFNO_MASK 0x00000006 +#define BUFNO_SHIFT 1 +#define START_BIT BIT(0) + +/* NFC_COL_ADDR Field */ +#define COL_ADDR_MASK 0x0000FFFF +#define COL_ADDR_SHIFT 0 +#define COL_ADDR(pos, val) (((val) & 0xFF) << (8 * (pos))) + +/* NFC_ROW_ADDR Field */ +#define ROW_ADDR_MASK 0x00FFFFFF +#define ROW_ADDR_SHIFT 0 +#define ROW_ADDR(pos, val) (((val) & 0xFF) << (8 * (pos))) + +#define ROW_ADDR_CHIP_SEL_RB_MASK 0xF0000000 +#define ROW_ADDR_CHIP_SEL_RB_SHIFT 28 +#define ROW_ADDR_CHIP_SEL_MASK 0x0F000000 +#define ROW_ADDR_CHIP_SEL_SHIFT 24 + +/* NFC_FLASH_STATUS2 Field */ +#define STATUS_BYTE1_MASK 0x000000FF + +/* NFC_FLASH_CONFIG Field */ +#define CONFIG_ECC_SRAM_ADDR_MASK 0x7FC00000 +#define CONFIG_ECC_SRAM_ADDR_SHIFT 22 +#define CONFIG_ECC_SRAM_REQ_BIT BIT(21) +#define CONFIG_DMA_REQ_BIT BIT(20) +#define CONFIG_ECC_MODE_MASK 0x000E0000 +#define CONFIG_ECC_MODE_SHIFT 17 +#define CONFIG_FAST_FLASH_BIT BIT(16) +#define CONFIG_16BIT BIT(7) +#define CONFIG_BOOT_MODE_BIT BIT(6) +#define CONFIG_ADDR_AUTO_INCR_BIT BIT(5) +#define CONFIG_BUFNO_AUTO_INCR_BIT BIT(4) +#define CONFIG_PAGE_CNT_MASK 0xF +#define CONFIG_PAGE_CNT_SHIFT 0 + +/* NFC_IRQ_STATUS Field */ +#define IDLE_IRQ_BIT BIT(29) +#define IDLE_EN_BIT BIT(20) +#define CMD_DONE_CLEAR_BIT BIT(18) +#define IDLE_CLEAR_BIT BIT(17) + +/* + * ECC status - seems to consume 8 bytes (double word). The documented + * status byte is located in the lowest byte of the second word (which is + * the 4th or 7th byte depending on endianness). + * Calculate an offset to store the ECC status at the end of the buffer. + */ +#define ECC_SRAM_ADDR (PAGE_2K + OOB_MAX - 8) + +#define ECC_STATUS 0x4 +#define ECC_STATUS_MASK 0x80 +#define ECC_STATUS_ERR_COUNT 0x3F + +enum vf610_nfc_variant { + NFC_VFC610 = 1, +}; + +struct vf610_nfc { + struct nand_controller base; + struct nand_chip chip; + struct device *dev; + void __iomem *regs; + struct completion cmd_done; + /* Status and ID are in alternate locations. */ + enum vf610_nfc_variant variant; + struct clk *clk; + /* + * Indicate that user data is accessed (full page/oob). This is + * useful to indicate the driver whether to swap byte endianness. + * See comments in vf610_nfc_rd_from_sram/vf610_nfc_wr_to_sram. + */ + bool data_access; + u32 ecc_mode; +}; + +static inline struct vf610_nfc *chip_to_nfc(struct nand_chip *chip) +{ + return container_of(chip, struct vf610_nfc, chip); +} + +static inline u32 vf610_nfc_read(struct vf610_nfc *nfc, uint reg) +{ + return readl(nfc->regs + reg); +} + +static inline void vf610_nfc_write(struct vf610_nfc *nfc, uint reg, u32 val) +{ + writel(val, nfc->regs + reg); +} + +static inline void vf610_nfc_set(struct vf610_nfc *nfc, uint reg, u32 bits) +{ + vf610_nfc_write(nfc, reg, vf610_nfc_read(nfc, reg) | bits); +} + +static inline void vf610_nfc_clear(struct vf610_nfc *nfc, uint reg, u32 bits) +{ + vf610_nfc_write(nfc, reg, vf610_nfc_read(nfc, reg) & ~bits); +} + +static inline void vf610_nfc_set_field(struct vf610_nfc *nfc, u32 reg, + u32 mask, u32 shift, u32 val) +{ + vf610_nfc_write(nfc, reg, + (vf610_nfc_read(nfc, reg) & (~mask)) | val << shift); +} + +static inline bool vf610_nfc_kernel_is_little_endian(void) +{ +#ifdef __LITTLE_ENDIAN + return true; +#else + return false; +#endif +} + +/* + * Read accessor for internal SRAM buffer + * @dst: destination address in regular memory + * @src: source address in SRAM buffer + * @len: bytes to copy + * @fix_endian: Fix endianness if required + * + * Use this accessor for the internal SRAM buffers. On the ARM + * Freescale Vybrid SoC it's known that the driver can treat + * the SRAM buffer as if it's memory. Other platform might need + * to treat the buffers differently. + * + * The controller stores bytes from the NAND chip internally in big + * endianness. On little endian platforms such as Vybrid this leads + * to reversed byte order. + * For performance reason (and earlier probably due to unawareness) + * the driver avoids correcting endianness where it has control over + * write and read side (e.g. page wise data access). + */ +static inline void vf610_nfc_rd_from_sram(void *dst, const void __iomem *src, + size_t len, bool fix_endian) +{ + if (vf610_nfc_kernel_is_little_endian() && fix_endian) { + unsigned int i; + + for (i = 0; i < len; i += 4) { + u32 val = swab32(__raw_readl(src + i)); + + memcpy(dst + i, &val, min(sizeof(val), len - i)); + } + } else { + memcpy_fromio(dst, src, len); + } +} + +/* + * Write accessor for internal SRAM buffer + * @dst: destination address in SRAM buffer + * @src: source address in regular memory + * @len: bytes to copy + * @fix_endian: Fix endianness if required + * + * Use this accessor for the internal SRAM buffers. On the ARM + * Freescale Vybrid SoC it's known that the driver can treat + * the SRAM buffer as if it's memory. Other platform might need + * to treat the buffers differently. + * + * The controller stores bytes from the NAND chip internally in big + * endianness. On little endian platforms such as Vybrid this leads + * to reversed byte order. + * For performance reason (and earlier probably due to unawareness) + * the driver avoids correcting endianness where it has control over + * write and read side (e.g. page wise data access). + */ +static inline void vf610_nfc_wr_to_sram(void __iomem *dst, const void *src, + size_t len, bool fix_endian) +{ + if (vf610_nfc_kernel_is_little_endian() && fix_endian) { + unsigned int i; + + for (i = 0; i < len; i += 4) { + u32 val; + + memcpy(&val, src + i, min(sizeof(val), len - i)); + __raw_writel(swab32(val), dst + i); + } + } else { + memcpy_toio(dst, src, len); + } +} + +/* Clear flags for upcoming command */ +static inline void vf610_nfc_clear_status(struct vf610_nfc *nfc) +{ + u32 tmp = vf610_nfc_read(nfc, NFC_IRQ_STATUS); + + tmp |= CMD_DONE_CLEAR_BIT | IDLE_CLEAR_BIT; + vf610_nfc_write(nfc, NFC_IRQ_STATUS, tmp); +} + +static void vf610_nfc_done(struct vf610_nfc *nfc) +{ + unsigned long timeout = msecs_to_jiffies(100); + + /* + * Barrier is needed after this write. This write need + * to be done before reading the next register the first + * time. + * vf610_nfc_set implicates such a barrier by using writel + * to write to the register. + */ + vf610_nfc_set(nfc, NFC_IRQ_STATUS, IDLE_EN_BIT); + vf610_nfc_set(nfc, NFC_FLASH_CMD2, START_BIT); + + if (!wait_for_completion_timeout(&nfc->cmd_done, timeout)) + dev_warn(nfc->dev, "Timeout while waiting for BUSY.\n"); + + vf610_nfc_clear_status(nfc); +} + +static irqreturn_t vf610_nfc_irq(int irq, void *data) +{ + struct vf610_nfc *nfc = data; + + vf610_nfc_clear(nfc, NFC_IRQ_STATUS, IDLE_EN_BIT); + complete(&nfc->cmd_done); + + return IRQ_HANDLED; +} + +static inline void vf610_nfc_ecc_mode(struct vf610_nfc *nfc, int ecc_mode) +{ + vf610_nfc_set_field(nfc, NFC_FLASH_CONFIG, + CONFIG_ECC_MODE_MASK, + CONFIG_ECC_MODE_SHIFT, ecc_mode); +} + +static inline void vf610_nfc_run(struct vf610_nfc *nfc, u32 col, u32 row, + u32 cmd1, u32 cmd2, u32 trfr_sz) +{ + vf610_nfc_set_field(nfc, NFC_COL_ADDR, COL_ADDR_MASK, + COL_ADDR_SHIFT, col); + + vf610_nfc_set_field(nfc, NFC_ROW_ADDR, ROW_ADDR_MASK, + ROW_ADDR_SHIFT, row); + + vf610_nfc_write(nfc, NFC_SECTOR_SIZE, trfr_sz); + vf610_nfc_write(nfc, NFC_FLASH_CMD1, cmd1); + vf610_nfc_write(nfc, NFC_FLASH_CMD2, cmd2); + + dev_dbg(nfc->dev, + "col 0x%04x, row 0x%08x, cmd1 0x%08x, cmd2 0x%08x, len %d\n", + col, row, cmd1, cmd2, trfr_sz); + + vf610_nfc_done(nfc); +} + +static inline const struct nand_op_instr * +vf610_get_next_instr(const struct nand_subop *subop, int *op_id) +{ + if (*op_id + 1 >= subop->ninstrs) + return NULL; + + (*op_id)++; + + return &subop->instrs[*op_id]; +} + +static int vf610_nfc_cmd(struct nand_chip *chip, + const struct nand_subop *subop) +{ + const struct nand_op_instr *instr; + struct vf610_nfc *nfc = chip_to_nfc(chip); + int op_id = -1, trfr_sz = 0, offset = 0; + u32 col = 0, row = 0, cmd1 = 0, cmd2 = 0, code = 0; + bool force8bit = false; + + /* + * Some ops are optional, but the hardware requires the operations + * to be in this exact order. + * The op parser enforces the order and makes sure that there isn't + * a read and write element in a single operation. + */ + instr = vf610_get_next_instr(subop, &op_id); + if (!instr) + return -EINVAL; + + if (instr && instr->type == NAND_OP_CMD_INSTR) { + cmd2 |= instr->ctx.cmd.opcode << CMD_BYTE1_SHIFT; + code |= COMMAND_CMD_BYTE1; + + instr = vf610_get_next_instr(subop, &op_id); + } + + if (instr && instr->type == NAND_OP_ADDR_INSTR) { + int naddrs = nand_subop_get_num_addr_cyc(subop, op_id); + int i = nand_subop_get_addr_start_off(subop, op_id); + + for (; i < naddrs; i++) { + u8 val = instr->ctx.addr.addrs[i]; + + if (i < 2) + col |= COL_ADDR(i, val); + else + row |= ROW_ADDR(i - 2, val); + } + code |= COMMAND_NADDR_BYTES(naddrs); + + instr = vf610_get_next_instr(subop, &op_id); + } + + if (instr && instr->type == NAND_OP_DATA_OUT_INSTR) { + trfr_sz = nand_subop_get_data_len(subop, op_id); + offset = nand_subop_get_data_start_off(subop, op_id); + force8bit = instr->ctx.data.force_8bit; + + /* + * Don't fix endianness on page access for historical reasons. + * See comment in vf610_nfc_wr_to_sram + */ + vf610_nfc_wr_to_sram(nfc->regs + NFC_MAIN_AREA(0) + offset, + instr->ctx.data.buf.out + offset, + trfr_sz, !nfc->data_access); + code |= COMMAND_WRITE_DATA; + + instr = vf610_get_next_instr(subop, &op_id); + } + + if (instr && instr->type == NAND_OP_CMD_INSTR) { + cmd1 |= instr->ctx.cmd.opcode << CMD_BYTE2_SHIFT; + code |= COMMAND_CMD_BYTE2; + + instr = vf610_get_next_instr(subop, &op_id); + } + + if (instr && instr->type == NAND_OP_WAITRDY_INSTR) { + code |= COMMAND_RB_HANDSHAKE; + + instr = vf610_get_next_instr(subop, &op_id); + } + + if (instr && instr->type == NAND_OP_DATA_IN_INSTR) { + trfr_sz = nand_subop_get_data_len(subop, op_id); + offset = nand_subop_get_data_start_off(subop, op_id); + force8bit = instr->ctx.data.force_8bit; + + code |= COMMAND_READ_DATA; + } + + if (force8bit && (chip->options & NAND_BUSWIDTH_16)) + vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_16BIT); + + cmd2 |= code << CMD_CODE_SHIFT; + + vf610_nfc_run(nfc, col, row, cmd1, cmd2, trfr_sz); + + if (instr && instr->type == NAND_OP_DATA_IN_INSTR) { + /* + * Don't fix endianness on page access for historical reasons. + * See comment in vf610_nfc_rd_from_sram + */ + vf610_nfc_rd_from_sram(instr->ctx.data.buf.in + offset, + nfc->regs + NFC_MAIN_AREA(0) + offset, + trfr_sz, !nfc->data_access); + } + + if (force8bit && (chip->options & NAND_BUSWIDTH_16)) + vf610_nfc_set(nfc, NFC_FLASH_CONFIG, CONFIG_16BIT); + + return 0; +} + +static const struct nand_op_parser vf610_nfc_op_parser = NAND_OP_PARSER( + NAND_OP_PARSER_PATTERN(vf610_nfc_cmd, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 5), + NAND_OP_PARSER_PAT_DATA_OUT_ELEM(true, PAGE_2K + OOB_MAX), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true)), + NAND_OP_PARSER_PATTERN(vf610_nfc_cmd, + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_ADDR_ELEM(true, 5), + NAND_OP_PARSER_PAT_CMD_ELEM(true), + NAND_OP_PARSER_PAT_WAITRDY_ELEM(true), + NAND_OP_PARSER_PAT_DATA_IN_ELEM(true, PAGE_2K + OOB_MAX)), + ); + +/* + * This function supports Vybrid only (MPC5125 would have full RB and four CS) + */ +static void vf610_nfc_select_target(struct nand_chip *chip, unsigned int cs) +{ + struct vf610_nfc *nfc = chip_to_nfc(chip); + u32 tmp; + + /* Vybrid only (MPC5125 would have full RB and four CS) */ + if (nfc->variant != NFC_VFC610) + return; + + tmp = vf610_nfc_read(nfc, NFC_ROW_ADDR); + tmp &= ~(ROW_ADDR_CHIP_SEL_RB_MASK | ROW_ADDR_CHIP_SEL_MASK); + tmp |= 1 << ROW_ADDR_CHIP_SEL_RB_SHIFT; + tmp |= BIT(cs) << ROW_ADDR_CHIP_SEL_SHIFT; + + vf610_nfc_write(nfc, NFC_ROW_ADDR, tmp); +} + +static int vf610_nfc_exec_op(struct nand_chip *chip, + const struct nand_operation *op, + bool check_only) +{ + if (!check_only) + vf610_nfc_select_target(chip, op->cs); + + return nand_op_parser_exec_op(chip, &vf610_nfc_op_parser, op, + check_only); +} + +static inline int vf610_nfc_correct_data(struct nand_chip *chip, uint8_t *dat, + uint8_t *oob, int page) +{ + struct vf610_nfc *nfc = chip_to_nfc(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + u32 ecc_status_off = NFC_MAIN_AREA(0) + ECC_SRAM_ADDR + ECC_STATUS; + u8 ecc_status; + u8 ecc_count; + int flips_threshold = nfc->chip.ecc.strength / 2; + + ecc_status = vf610_nfc_read(nfc, ecc_status_off) & 0xff; + ecc_count = ecc_status & ECC_STATUS_ERR_COUNT; + + if (!(ecc_status & ECC_STATUS_MASK)) + return ecc_count; + + nfc->data_access = true; + nand_read_oob_op(&nfc->chip, page, 0, oob, mtd->oobsize); + nfc->data_access = false; + + /* + * On an erased page, bit count (including OOB) should be zero or + * at least less then half of the ECC strength. + */ + return nand_check_erased_ecc_chunk(dat, nfc->chip.ecc.size, oob, + mtd->oobsize, NULL, 0, + flips_threshold); +} + +static void vf610_nfc_fill_row(struct nand_chip *chip, int page, u32 *code, + u32 *row) +{ + *row = ROW_ADDR(0, page & 0xff) | ROW_ADDR(1, page >> 8); + *code |= COMMAND_RAR_BYTE1 | COMMAND_RAR_BYTE2; + + if (chip->options & NAND_ROW_ADDR_3) { + *row |= ROW_ADDR(2, page >> 16); + *code |= COMMAND_RAR_BYTE3; + } +} + +static int vf610_nfc_read_page(struct nand_chip *chip, uint8_t *buf, + int oob_required, int page) +{ + struct vf610_nfc *nfc = chip_to_nfc(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int trfr_sz = mtd->writesize + mtd->oobsize; + u32 row = 0, cmd1 = 0, cmd2 = 0, code = 0; + int stat; + + vf610_nfc_select_target(chip, chip->cur_cs); + + cmd2 |= NAND_CMD_READ0 << CMD_BYTE1_SHIFT; + code |= COMMAND_CMD_BYTE1 | COMMAND_CAR_BYTE1 | COMMAND_CAR_BYTE2; + + vf610_nfc_fill_row(chip, page, &code, &row); + + cmd1 |= NAND_CMD_READSTART << CMD_BYTE2_SHIFT; + code |= COMMAND_CMD_BYTE2 | COMMAND_RB_HANDSHAKE | COMMAND_READ_DATA; + + cmd2 |= code << CMD_CODE_SHIFT; + + vf610_nfc_ecc_mode(nfc, nfc->ecc_mode); + vf610_nfc_run(nfc, 0, row, cmd1, cmd2, trfr_sz); + vf610_nfc_ecc_mode(nfc, ECC_BYPASS); + + /* + * Don't fix endianness on page access for historical reasons. + * See comment in vf610_nfc_rd_from_sram + */ + vf610_nfc_rd_from_sram(buf, nfc->regs + NFC_MAIN_AREA(0), + mtd->writesize, false); + if (oob_required) + vf610_nfc_rd_from_sram(chip->oob_poi, + nfc->regs + NFC_MAIN_AREA(0) + + mtd->writesize, + mtd->oobsize, false); + + stat = vf610_nfc_correct_data(chip, buf, chip->oob_poi, page); + + if (stat < 0) { + mtd->ecc_stats.failed++; + return 0; + } else { + mtd->ecc_stats.corrected += stat; + return stat; + } +} + +static int vf610_nfc_write_page(struct nand_chip *chip, const uint8_t *buf, + int oob_required, int page) +{ + struct vf610_nfc *nfc = chip_to_nfc(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int trfr_sz = mtd->writesize + mtd->oobsize; + u32 row = 0, cmd1 = 0, cmd2 = 0, code = 0; + u8 status; + int ret; + + vf610_nfc_select_target(chip, chip->cur_cs); + + cmd2 |= NAND_CMD_SEQIN << CMD_BYTE1_SHIFT; + code |= COMMAND_CMD_BYTE1 | COMMAND_CAR_BYTE1 | COMMAND_CAR_BYTE2; + + vf610_nfc_fill_row(chip, page, &code, &row); + + cmd1 |= NAND_CMD_PAGEPROG << CMD_BYTE2_SHIFT; + code |= COMMAND_CMD_BYTE2 | COMMAND_WRITE_DATA; + + /* + * Don't fix endianness on page access for historical reasons. + * See comment in vf610_nfc_wr_to_sram + */ + vf610_nfc_wr_to_sram(nfc->regs + NFC_MAIN_AREA(0), buf, + mtd->writesize, false); + + code |= COMMAND_RB_HANDSHAKE; + cmd2 |= code << CMD_CODE_SHIFT; + + vf610_nfc_ecc_mode(nfc, nfc->ecc_mode); + vf610_nfc_run(nfc, 0, row, cmd1, cmd2, trfr_sz); + vf610_nfc_ecc_mode(nfc, ECC_BYPASS); + + ret = nand_status_op(chip, &status); + if (ret) + return ret; + + if (status & NAND_STATUS_FAIL) + return -EIO; + + return 0; +} + +static int vf610_nfc_read_page_raw(struct nand_chip *chip, u8 *buf, + int oob_required, int page) +{ + struct vf610_nfc *nfc = chip_to_nfc(chip); + int ret; + + nfc->data_access = true; + ret = nand_read_page_raw(chip, buf, oob_required, page); + nfc->data_access = false; + + return ret; +} + +static int vf610_nfc_write_page_raw(struct nand_chip *chip, const u8 *buf, + int oob_required, int page) +{ + struct vf610_nfc *nfc = chip_to_nfc(chip); + struct mtd_info *mtd = nand_to_mtd(chip); + int ret; + + nfc->data_access = true; + ret = nand_prog_page_begin_op(chip, page, 0, buf, mtd->writesize); + if (!ret && oob_required) + ret = nand_write_data_op(chip, chip->oob_poi, mtd->oobsize, + false); + nfc->data_access = false; + + if (ret) + return ret; + + return nand_prog_page_end_op(chip); +} + +static int vf610_nfc_read_oob(struct nand_chip *chip, int page) +{ + struct vf610_nfc *nfc = chip_to_nfc(chip); + int ret; + + nfc->data_access = true; + ret = nand_read_oob_std(chip, page); + nfc->data_access = false; + + return ret; +} + +static int vf610_nfc_write_oob(struct nand_chip *chip, int page) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct vf610_nfc *nfc = chip_to_nfc(chip); + int ret; + + nfc->data_access = true; + ret = nand_prog_page_begin_op(chip, page, mtd->writesize, + chip->oob_poi, mtd->oobsize); + nfc->data_access = false; + + if (ret) + return ret; + + return nand_prog_page_end_op(chip); +} + +static const struct of_device_id vf610_nfc_dt_ids[] = { + { .compatible = "fsl,vf610-nfc", .data = (void *)NFC_VFC610 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, vf610_nfc_dt_ids); + +static void vf610_nfc_preinit_controller(struct vf610_nfc *nfc) +{ + vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_16BIT); + vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_ADDR_AUTO_INCR_BIT); + vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_BUFNO_AUTO_INCR_BIT); + vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_BOOT_MODE_BIT); + vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_DMA_REQ_BIT); + vf610_nfc_set(nfc, NFC_FLASH_CONFIG, CONFIG_FAST_FLASH_BIT); + vf610_nfc_ecc_mode(nfc, ECC_BYPASS); + + /* Disable virtual pages, only one elementary transfer unit */ + vf610_nfc_set_field(nfc, NFC_FLASH_CONFIG, CONFIG_PAGE_CNT_MASK, + CONFIG_PAGE_CNT_SHIFT, 1); +} + +static void vf610_nfc_init_controller(struct vf610_nfc *nfc) +{ + if (nfc->chip.options & NAND_BUSWIDTH_16) + vf610_nfc_set(nfc, NFC_FLASH_CONFIG, CONFIG_16BIT); + else + vf610_nfc_clear(nfc, NFC_FLASH_CONFIG, CONFIG_16BIT); + + if (nfc->chip.ecc.engine_type == NAND_ECC_ENGINE_TYPE_ON_HOST) { + /* Set ECC status offset in SRAM */ + vf610_nfc_set_field(nfc, NFC_FLASH_CONFIG, + CONFIG_ECC_SRAM_ADDR_MASK, + CONFIG_ECC_SRAM_ADDR_SHIFT, + ECC_SRAM_ADDR >> 3); + + /* Enable ECC status in SRAM */ + vf610_nfc_set(nfc, NFC_FLASH_CONFIG, CONFIG_ECC_SRAM_REQ_BIT); + } +} + +static int vf610_nfc_attach_chip(struct nand_chip *chip) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + struct vf610_nfc *nfc = chip_to_nfc(chip); + + vf610_nfc_init_controller(nfc); + + /* Bad block options. */ + if (chip->bbt_options & NAND_BBT_USE_FLASH) + chip->bbt_options |= NAND_BBT_NO_OOB; + + /* Single buffer only, max 256 OOB minus ECC status */ + if (mtd->writesize + mtd->oobsize > PAGE_2K + OOB_MAX - 8) { + dev_err(nfc->dev, "Unsupported flash page size\n"); + return -ENXIO; + } + + if (chip->ecc.engine_type != NAND_ECC_ENGINE_TYPE_ON_HOST) + return 0; + + if (mtd->writesize != PAGE_2K && mtd->oobsize < 64) { + dev_err(nfc->dev, "Unsupported flash with hwecc\n"); + return -ENXIO; + } + + if (chip->ecc.size != mtd->writesize) { + dev_err(nfc->dev, "Step size needs to be page size\n"); + return -ENXIO; + } + + /* Only 64 byte ECC layouts known */ + if (mtd->oobsize > 64) + mtd->oobsize = 64; + + /* Use default large page ECC layout defined in NAND core */ + mtd_set_ooblayout(mtd, nand_get_large_page_ooblayout()); + if (chip->ecc.strength == 32) { + nfc->ecc_mode = ECC_60_BYTE; + chip->ecc.bytes = 60; + } else if (chip->ecc.strength == 24) { + nfc->ecc_mode = ECC_45_BYTE; + chip->ecc.bytes = 45; + } else { + dev_err(nfc->dev, "Unsupported ECC strength\n"); + return -ENXIO; + } + + chip->ecc.read_page = vf610_nfc_read_page; + chip->ecc.write_page = vf610_nfc_write_page; + chip->ecc.read_page_raw = vf610_nfc_read_page_raw; + chip->ecc.write_page_raw = vf610_nfc_write_page_raw; + chip->ecc.read_oob = vf610_nfc_read_oob; + chip->ecc.write_oob = vf610_nfc_write_oob; + + chip->ecc.size = PAGE_2K; + + return 0; +} + +static const struct nand_controller_ops vf610_nfc_controller_ops = { + .attach_chip = vf610_nfc_attach_chip, + .exec_op = vf610_nfc_exec_op, + +}; + +static int vf610_nfc_probe(struct platform_device *pdev) +{ + struct vf610_nfc *nfc; + struct mtd_info *mtd; + struct nand_chip *chip; + int err; + int irq; + + nfc = devm_kzalloc(&pdev->dev, sizeof(*nfc), GFP_KERNEL); + if (!nfc) + return -ENOMEM; + + nfc->dev = &pdev->dev; + chip = &nfc->chip; + mtd = nand_to_mtd(chip); + + mtd->owner = THIS_MODULE; + mtd->dev.parent = nfc->dev; + mtd->name = DRV_NAME; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + nfc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(nfc->regs)) + return PTR_ERR(nfc->regs); + + nfc->clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(nfc->clk)) { + dev_err(nfc->dev, "Unable to get and enable clock!\n"); + return PTR_ERR(nfc->clk); + } + + nfc->variant = (unsigned long)device_get_match_data(&pdev->dev); + if (!nfc->variant) + return -ENODEV; + + 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"); + return -EINVAL; + } + + nand_set_flash_node(chip, child); + } + } + + if (!nand_get_flash_node(chip)) { + dev_err(nfc->dev, "NAND chip sub-node missing!\n"); + return -ENODEV; + } + + chip->options |= NAND_NO_SUBPAGE_WRITE; + + init_completion(&nfc->cmd_done); + + err = devm_request_irq(nfc->dev, irq, vf610_nfc_irq, 0, DRV_NAME, nfc); + if (err) { + dev_err(nfc->dev, "Error requesting IRQ!\n"); + return err; + } + + vf610_nfc_preinit_controller(nfc); + + nand_controller_init(&nfc->base); + nfc->base.ops = &vf610_nfc_controller_ops; + chip->controller = &nfc->base; + + /* Scan the NAND chip */ + err = nand_scan(chip, 1); + if (err) + return err; + + platform_set_drvdata(pdev, nfc); + + /* Register device in MTD */ + err = mtd_device_register(mtd, NULL, 0); + if (err) + goto err_cleanup_nand; + return 0; + +err_cleanup_nand: + nand_cleanup(chip); + return err; +} + +static void vf610_nfc_remove(struct platform_device *pdev) +{ + struct vf610_nfc *nfc = platform_get_drvdata(pdev); + struct nand_chip *chip = &nfc->chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); +} + +#ifdef CONFIG_PM_SLEEP +static int vf610_nfc_suspend(struct device *dev) +{ + struct vf610_nfc *nfc = dev_get_drvdata(dev); + + clk_disable_unprepare(nfc->clk); + return 0; +} + +static int vf610_nfc_resume(struct device *dev) +{ + struct vf610_nfc *nfc = dev_get_drvdata(dev); + int err; + + err = clk_prepare_enable(nfc->clk); + if (err) + return err; + + vf610_nfc_preinit_controller(nfc); + vf610_nfc_init_controller(nfc); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(vf610_nfc_pm_ops, vf610_nfc_suspend, vf610_nfc_resume); + +static struct platform_driver vf610_nfc_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = vf610_nfc_dt_ids, + .pm = &vf610_nfc_pm_ops, + }, + .probe = vf610_nfc_probe, + .remove = vf610_nfc_remove, +}; + +module_platform_driver(vf610_nfc_driver); + +MODULE_AUTHOR("Stefan Agner "); +MODULE_DESCRIPTION("Freescale VF610/MPC5125 NFC MTD NAND driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/nand/raw/xway_nand.c b/drivers/mtd/nand/raw/xway_nand.c new file mode 100644 index 000000000..af84395dc --- /dev/null +++ b/drivers/mtd/nand/raw/xway_nand.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright © 2012 John Crispin + * Copyright © 2016 Hauke Mehrtens + */ + +#include +#include +#include + +#include + +/* nand registers */ +#define EBU_ADDSEL1 0x24 +#define EBU_NAND_CON 0xB0 +#define EBU_NAND_WAIT 0xB4 +#define NAND_WAIT_RD BIT(0) /* NAND flash status output */ +#define NAND_WAIT_WR_C BIT(3) /* NAND Write/Read complete */ +#define EBU_NAND_ECC0 0xB8 +#define EBU_NAND_ECC_AC 0xBC + +/* + * nand commands + * The pins of the NAND chip are selected based on the address bits of the + * "register" read and write. There are no special registers, but an + * address range and the lower address bits are used to activate the + * correct line. For example when the bit (1 << 2) is set in the address + * the ALE pin will be activated. + */ +#define NAND_CMD_ALE BIT(2) /* address latch enable */ +#define NAND_CMD_CLE BIT(3) /* command latch enable */ +#define NAND_CMD_CS BIT(4) /* chip select */ +#define NAND_CMD_SE BIT(5) /* spare area access latch */ +#define NAND_CMD_WP BIT(6) /* write protect */ +#define NAND_WRITE_CMD (NAND_CMD_CS | NAND_CMD_CLE) +#define NAND_WRITE_ADDR (NAND_CMD_CS | NAND_CMD_ALE) +#define NAND_WRITE_DATA (NAND_CMD_CS) +#define NAND_READ_DATA (NAND_CMD_CS) + +/* we need to tel the ebu which addr we mapped the nand to */ +#define ADDSEL1_MASK(x) (x << 4) +#define ADDSEL1_REGEN 1 + +/* we need to tell the EBU that we have nand attached and set it up properly */ +#define BUSCON1_SETUP (1 << 22) +#define BUSCON1_BCGEN_RES (0x3 << 12) +#define BUSCON1_WAITWRC2 (2 << 8) +#define BUSCON1_WAITRDC2 (2 << 6) +#define BUSCON1_HOLDC1 (1 << 4) +#define BUSCON1_RECOVC1 (1 << 2) +#define BUSCON1_CMULT4 1 + +#define NAND_CON_CE (1 << 20) +#define NAND_CON_OUT_CS1 (1 << 10) +#define NAND_CON_IN_CS1 (1 << 8) +#define NAND_CON_PRE_P (1 << 7) +#define NAND_CON_WP_P (1 << 6) +#define NAND_CON_SE_P (1 << 5) +#define NAND_CON_CS_P (1 << 4) +#define NAND_CON_CSMUX (1 << 1) +#define NAND_CON_NANDM 1 + +struct xway_nand_data { + struct nand_controller controller; + struct nand_chip chip; + unsigned long csflags; + void __iomem *nandaddr; +}; + +static u8 xway_readb(struct mtd_info *mtd, int op) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct xway_nand_data *data = nand_get_controller_data(chip); + + return readb(data->nandaddr + op); +} + +static void xway_writeb(struct mtd_info *mtd, int op, u8 value) +{ + struct nand_chip *chip = mtd_to_nand(mtd); + struct xway_nand_data *data = nand_get_controller_data(chip); + + writeb(value, data->nandaddr + op); +} + +static void xway_select_chip(struct nand_chip *chip, int select) +{ + struct xway_nand_data *data = nand_get_controller_data(chip); + + switch (select) { + case -1: + ltq_ebu_w32_mask(NAND_CON_CE, 0, EBU_NAND_CON); + ltq_ebu_w32_mask(NAND_CON_NANDM, 0, EBU_NAND_CON); + spin_unlock_irqrestore(&ebu_lock, data->csflags); + break; + case 0: + spin_lock_irqsave(&ebu_lock, data->csflags); + ltq_ebu_w32_mask(0, NAND_CON_NANDM, EBU_NAND_CON); + ltq_ebu_w32_mask(0, NAND_CON_CE, EBU_NAND_CON); + break; + default: + BUG(); + } +} + +static void xway_cmd_ctrl(struct nand_chip *chip, int cmd, unsigned int ctrl) +{ + struct mtd_info *mtd = nand_to_mtd(chip); + + if (cmd == NAND_CMD_NONE) + return; + + if (ctrl & NAND_CLE) + xway_writeb(mtd, NAND_WRITE_CMD, cmd); + else if (ctrl & NAND_ALE) + xway_writeb(mtd, NAND_WRITE_ADDR, cmd); + + while ((ltq_ebu_r32(EBU_NAND_WAIT) & NAND_WAIT_WR_C) == 0) + ; +} + +static int xway_dev_ready(struct nand_chip *chip) +{ + return ltq_ebu_r32(EBU_NAND_WAIT) & NAND_WAIT_RD; +} + +static unsigned char xway_read_byte(struct nand_chip *chip) +{ + return xway_readb(nand_to_mtd(chip), NAND_READ_DATA); +} + +static void xway_read_buf(struct nand_chip *chip, u_char *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) + buf[i] = xway_readb(nand_to_mtd(chip), NAND_WRITE_DATA); +} + +static void xway_write_buf(struct nand_chip *chip, const u_char *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) + xway_writeb(nand_to_mtd(chip), NAND_WRITE_DATA, buf[i]); +} + +static int xway_attach_chip(struct nand_chip *chip) +{ + if (chip->ecc.engine_type == NAND_ECC_ENGINE_TYPE_SOFT && + chip->ecc.algo == NAND_ECC_ALGO_UNKNOWN) + chip->ecc.algo = NAND_ECC_ALGO_HAMMING; + + return 0; +} + +static const struct nand_controller_ops xway_nand_ops = { + .attach_chip = xway_attach_chip, +}; + +/* + * Probe for the NAND device. + */ +static int xway_nand_probe(struct platform_device *pdev) +{ + struct xway_nand_data *data; + struct mtd_info *mtd; + int err; + u32 cs; + u32 cs_flag = 0; + + /* Allocate memory for the device structure (and zero it) */ + data = devm_kzalloc(&pdev->dev, sizeof(struct xway_nand_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->nandaddr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(data->nandaddr)) + return PTR_ERR(data->nandaddr); + + nand_set_flash_node(&data->chip, pdev->dev.of_node); + mtd = nand_to_mtd(&data->chip); + mtd->dev.parent = &pdev->dev; + + data->chip.legacy.cmd_ctrl = xway_cmd_ctrl; + data->chip.legacy.dev_ready = xway_dev_ready; + data->chip.legacy.select_chip = xway_select_chip; + data->chip.legacy.write_buf = xway_write_buf; + data->chip.legacy.read_buf = xway_read_buf; + data->chip.legacy.read_byte = xway_read_byte; + data->chip.legacy.chip_delay = 30; + + nand_controller_init(&data->controller); + data->controller.ops = &xway_nand_ops; + data->chip.controller = &data->controller; + + platform_set_drvdata(pdev, data); + nand_set_controller_data(&data->chip, data); + + /* load our CS from the DT. Either we find a valid 1 or default to 0 */ + err = of_property_read_u32(pdev->dev.of_node, "lantiq,cs", &cs); + if (!err && cs == 1) + cs_flag = NAND_CON_IN_CS1 | NAND_CON_OUT_CS1; + + /* setup the EBU to run in NAND mode on our base addr */ + ltq_ebu_w32(CPHYSADDR(data->nandaddr) + | ADDSEL1_MASK(3) | ADDSEL1_REGEN, EBU_ADDSEL1); + + ltq_ebu_w32(BUSCON1_SETUP | BUSCON1_BCGEN_RES | BUSCON1_WAITWRC2 + | BUSCON1_WAITRDC2 | BUSCON1_HOLDC1 | BUSCON1_RECOVC1 + | BUSCON1_CMULT4, LTQ_EBU_BUSCON1); + + ltq_ebu_w32(NAND_CON_NANDM | NAND_CON_CSMUX | NAND_CON_CS_P + | NAND_CON_SE_P | NAND_CON_WP_P | NAND_CON_PRE_P + | cs_flag, EBU_NAND_CON); + + /* + * This driver assumes that the default ECC engine should be TYPE_SOFT. + * Set ->engine_type before registering the NAND devices in order to + * provide a driver specific default value. + */ + data->chip.ecc.engine_type = NAND_ECC_ENGINE_TYPE_SOFT; + + /* Scan to find existence of the device */ + err = nand_scan(&data->chip, 1); + if (err) + return err; + + err = mtd_device_register(mtd, NULL, 0); + if (err) + nand_cleanup(&data->chip); + + return err; +} + +/* + * Remove a NAND device. + */ +static void xway_nand_remove(struct platform_device *pdev) +{ + struct xway_nand_data *data = platform_get_drvdata(pdev); + struct nand_chip *chip = &data->chip; + int ret; + + ret = mtd_device_unregister(nand_to_mtd(chip)); + WARN_ON(ret); + nand_cleanup(chip); +} + +static const struct of_device_id xway_nand_match[] = { + { .compatible = "lantiq,nand-xway" }, + {}, +}; + +static struct platform_driver xway_nand_driver = { + .probe = xway_nand_probe, + .remove = xway_nand_remove, + .driver = { + .name = "lantiq,nand-xway", + .of_match_table = xway_nand_match, + }, +}; + +builtin_platform_driver(xway_nand_driver); diff --git a/drivers/mtd/nand/spi/Kconfig b/drivers/mtd/nand/spi/Kconfig new file mode 100644 index 000000000..3d7649a2d --- /dev/null +++ b/drivers/mtd/nand/spi/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig MTD_SPI_NAND + tristate "SPI NAND device Support" + select MTD_NAND_CORE + select MTD_NAND_ECC + depends on SPI_MASTER + select SPI_MEM + help + This is the framework for the SPI NAND device drivers. diff --git a/drivers/mtd/nand/spi/Makefile b/drivers/mtd/nand/spi/Makefile new file mode 100644 index 000000000..b5ccb4486 --- /dev/null +++ b/drivers/mtd/nand/spi/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +spinand-objs := core.o otp.o +spinand-objs += alliancememory.o ato.o dosilicon.o esmt.o fmsh.o foresee.o gigadevice.o heyangtek.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 new file mode 100644 index 000000000..9e97c4095 --- /dev/null +++ b/drivers/mtd/nand/spi/alliancememory.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: Mario Kicherer + */ + +#include +#include +#include + +#define SPINAND_MFR_ALLIANCEMEMORY 0x52 + +#define AM_STATUS_ECC_BITMASK (3 << 4) + +#define AM_STATUS_ECC_NONE_DETECTED (0 << 4) +#define AM_STATUS_ECC_CORRECTED (1 << 4) +#define AM_STATUS_ECC_ERRORED (2 << 4) +#define AM_STATUS_ECC_MAX_CORRECTED (3 << 4) + +static SPINAND_OP_VARIANTS(read_cache_variants, + 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_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 am_get_eccsize(struct mtd_info *mtd) +{ + if (mtd->oobsize == 64) + return 0x20; + else if (mtd->oobsize == 128) + return 0x38; + else if (mtd->oobsize == 256) + return 0x70; + else + return -EINVAL; +} + +static int am_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + int ecc_bytes; + + ecc_bytes = am_get_eccsize(mtd); + if (ecc_bytes < 0) + return ecc_bytes; + + region->offset = mtd->oobsize - ecc_bytes; + region->length = ecc_bytes; + + return 0; +} + +static int am_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + int ecc_bytes; + + if (section) + return -ERANGE; + + ecc_bytes = am_get_eccsize(mtd); + if (ecc_bytes < 0) + return ecc_bytes; + + /* + * It is unclear how many bytes are used for the bad block marker. We + * reserve the common two bytes here. + * + * The free area in this kind of flash is divided into chunks where the + * first 4 bytes of each chunk are unprotected. The number of chunks + * depends on the specific model. The models with 4096+256 bytes pages + * have 8 chunks, the others 4 chunks. + */ + + region->offset = 2; + region->length = mtd->oobsize - 2 - ecc_bytes; + + return 0; +} + +static const struct mtd_ooblayout_ops am_ooblayout = { + .ecc = am_ooblayout_ecc, + .free = am_ooblayout_free, +}; + +static int am_ecc_get_status(struct spinand_device *spinand, u8 status) +{ + switch (status & AM_STATUS_ECC_BITMASK) { + case AM_STATUS_ECC_NONE_DETECTED: + return 0; + + case AM_STATUS_ECC_CORRECTED: + /* + * use oobsize to determine the flash model and the maximum of + * correctable errors and return maximum - 1 by convention + */ + if (spinand->base.mtd.oobsize == 64) + return 3; + else + return 7; + + case AM_STATUS_ECC_ERRORED: + return -EBADMSG; + + case AM_STATUS_ECC_MAX_CORRECTED: + /* + * use oobsize to determine the flash model and the maximum of + * correctable errors + */ + if (spinand->base.mtd.oobsize == 64) + return 4; + else + return 8; + + default: + break; + } + + return -EINVAL; +} + +static const struct spinand_info alliancememory_spinand_table[] = { + SPINAND_INFO("AS5F34G04SND", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x2f), + NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 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(&am_ooblayout, + am_ecc_get_status)), +}; + +static const struct spinand_manufacturer_ops alliancememory_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer alliancememory_spinand_manufacturer = { + .id = SPINAND_MFR_ALLIANCEMEMORY, + .name = "AllianceMemory", + .chips = alliancememory_spinand_table, + .nchips = ARRAY_SIZE(alliancememory_spinand_table), + .ops = &alliancememory_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/ato.c b/drivers/mtd/nand/spi/ato.c new file mode 100644 index 000000000..45d38ce07 --- /dev/null +++ b/drivers/mtd/nand/spi/ato.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2022 Aidan MacDonald + * + * Author: Aidan MacDonald + */ + +#include +#include +#include + + +#define SPINAND_MFR_ATO 0x9b + + +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_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 ato25d1ga_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = (16 * section) + 8; + region->length = 8; + return 0; +} + +static int ato25d1ga_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + if (section) { + region->offset = (16 * section); + region->length = 8; + } else { + /* first byte of section 0 is reserved for the BBM */ + region->offset = 1; + region->length = 7; + } + + return 0; +} + +static const struct mtd_ooblayout_ops ato25d1ga_ooblayout = { + .ecc = ato25d1ga_ooblayout_ecc, + .free = ato25d1ga_ooblayout_free, +}; + + +static const struct spinand_info ato_spinand_table[] = { + SPINAND_INFO("ATO25D1GA", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x12), + 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), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&ato25d1ga_ooblayout, NULL)), +}; + +static const struct spinand_manufacturer_ops ato_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer ato_spinand_manufacturer = { + .id = SPINAND_MFR_ATO, + .name = "ATO", + .chips = ato_spinand_table, + .nchips = ARRAY_SIZE(ato_spinand_table), + .ops = &ato_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c new file mode 100644 index 000000000..8bf9301f2 --- /dev/null +++ b/drivers/mtd/nand/spi/core.c @@ -0,0 +1,2113 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2016-2017 Micron Technology, Inc. + * + * Authors: + * Peter Pan + * Boris Brezillon + */ + +#define pr_fmt(fmt) "spi-nand: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct spi_mem_op +spinand_fill_reset_op(struct spinand_device *spinand) +{ + 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); + if (ret) + return ret; + + *val = *spinand->scratchbuf; + return 0; +} + +int spinand_write_reg_op(struct spinand_device *spinand, u8 reg, u8 val) +{ + struct spi_mem_op op = SPINAND_OP(spinand, set_feature, + reg, spinand->scratchbuf); + + *spinand->scratchbuf = val; + return spi_mem_exec_op(spinand->spimem, &op); +} + +static int spinand_read_status(struct spinand_device *spinand, u8 *status) +{ + return spinand_read_reg_op(spinand, REG_STATUS, status); +} + +static int spinand_get_cfg(struct spinand_device *spinand, u8 *cfg) +{ + struct nand_device *nand = spinand_to_nand(spinand); + + if (WARN_ON(spinand->cur_target < 0 || + spinand->cur_target >= nand->memorg.ntargets)) + return -EINVAL; + + *cfg = spinand->cfg_cache[spinand->cur_target]; + return 0; +} + +static int spinand_set_cfg(struct spinand_device *spinand, u8 cfg) +{ + struct nand_device *nand = spinand_to_nand(spinand); + int ret; + + if (WARN_ON(spinand->cur_target < 0 || + spinand->cur_target >= nand->memorg.ntargets)) + return -EINVAL; + + if (spinand->cfg_cache[spinand->cur_target] == cfg) + return 0; + + ret = spinand_write_reg_op(spinand, REG_CFG, cfg); + if (ret) + return ret; + + spinand->cfg_cache[spinand->cur_target] = cfg; + return 0; +} + +/** + * spinand_upd_cfg() - Update the configuration register + * @spinand: the spinand device + * @mask: the mask encoding the bits to update in the config reg + * @val: the new value to apply + * + * Update the configuration register. + * + * Return: 0 on success, a negative error code otherwise. + */ +int spinand_upd_cfg(struct spinand_device *spinand, u8 mask, u8 val) +{ + int ret; + u8 cfg; + + ret = spinand_get_cfg(spinand, &cfg); + if (ret) + return ret; + + cfg &= ~mask; + cfg |= val; + + return spinand_set_cfg(spinand, cfg); +} + +/** + * spinand_select_target() - Select a specific NAND target/die + * @spinand: the spinand device + * @target: the target/die to select + * + * Select a new target/die. If chip only has one die, this function is a NOOP. + * + * Return: 0 on success, a negative error code otherwise. + */ +int spinand_select_target(struct spinand_device *spinand, unsigned int target) +{ + struct nand_device *nand = spinand_to_nand(spinand); + int ret; + + if (WARN_ON(target >= nand->memorg.ntargets)) + return -EINVAL; + + if (spinand->cur_target == target) + return 0; + + if (nand->memorg.ntargets == 1) { + spinand->cur_target = target; + return 0; + } + + ret = spinand->select_target(spinand, target); + if (ret) + return ret; + + spinand->cur_target = target; + return 0; +} + +static int spinand_read_cfg(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int target; + int ret; + + for (target = 0; target < nand->memorg.ntargets; target++) { + ret = spinand_select_target(spinand, target); + if (ret) + return ret; + + /* + * We use spinand_read_reg_op() instead of spinand_get_cfg() + * here to bypass the config cache. + */ + ret = spinand_read_reg_op(spinand, REG_CFG, + &spinand->cfg_cache[target]); + if (ret) + return ret; + } + + return 0; +} + +static int spinand_init_cfg_cache(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + struct device *dev = &spinand->spimem->spi->dev; + + spinand->cfg_cache = devm_kcalloc(dev, + nand->memorg.ntargets, + sizeof(*spinand->cfg_cache), + GFP_KERNEL); + if (!spinand->cfg_cache) + return -ENOMEM; + + return 0; +} + +static int spinand_init_quad_enable(struct spinand_device *spinand, + bool enable) +{ + return spinand_upd_cfg(spinand, CFG_QUAD_ENABLE, + enable ? CFG_QUAD_ENABLE : 0); +} + +static int spinand_ecc_enable(struct spinand_device *spinand, + bool enable) +{ + return spinand_upd_cfg(spinand, CFG_ECC_ENABLE, + enable ? CFG_ECC_ENABLE : 0); +} + +static int spinand_cont_read_enable(struct spinand_device *spinand, + bool enable) +{ + return spinand->set_cont_read(spinand, enable); +} + +static int spinand_check_ecc_status(struct spinand_device *spinand, u8 status) +{ + struct nand_device *nand = spinand_to_nand(spinand); + + if (spinand->eccinfo.get_status) + return spinand->eccinfo.get_status(spinand, status); + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_HAS_BITFLIPS: + /* + * We have no way to know exactly how many bitflips have been + * fixed, so let's return the maximum possible value so that + * wear-leveling layers move the data immediately. + */ + return nanddev_get_ecc_conf(nand)->strength; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + default: + break; + } + + return -EINVAL; +} + +static int spinand_noecc_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + return -ERANGE; +} + +static int spinand_noecc_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + /* Reserve 2 bytes for the BBM. */ + region->offset = 2; + region->length = 62; + + return 0; +} + +static const struct mtd_ooblayout_ops spinand_noecc_ooblayout = { + .ecc = spinand_noecc_ooblayout_ecc, + .free = spinand_noecc_ooblayout_free, +}; + +static int spinand_ondie_ecc_init_ctx(struct nand_device *nand) +{ + struct spinand_device *spinand = nand_to_spinand(nand); + struct mtd_info *mtd = nanddev_to_mtd(nand); + struct spinand_ondie_ecc_conf *engine_conf; + + nand->ecc.ctx.conf.engine_type = NAND_ECC_ENGINE_TYPE_ON_DIE; + nand->ecc.ctx.conf.step_size = nand->ecc.requirements.step_size; + nand->ecc.ctx.conf.strength = nand->ecc.requirements.strength; + + engine_conf = kzalloc_obj(*engine_conf); + if (!engine_conf) + return -ENOMEM; + + nand->ecc.ctx.priv = engine_conf; + + if (spinand->eccinfo.ooblayout) + mtd_set_ooblayout(mtd, spinand->eccinfo.ooblayout); + else + mtd_set_ooblayout(mtd, &spinand_noecc_ooblayout); + + return 0; +} + +static void spinand_ondie_ecc_cleanup_ctx(struct nand_device *nand) +{ + kfree(nand->ecc.ctx.priv); +} + +static int spinand_ondie_ecc_prepare_io_req(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct spinand_device *spinand = nand_to_spinand(nand); + bool enable = (req->mode != MTD_OPS_RAW); + + if (!enable && spinand->flags & SPINAND_NO_RAW_ACCESS) + return -EOPNOTSUPP; + + memset(spinand->oobbuf, 0xff, nanddev_per_page_oobsize(nand)); + + /* Only enable or disable the engine */ + return spinand_ecc_enable(spinand, enable); +} + +static int spinand_ondie_ecc_finish_io_req(struct nand_device *nand, + struct nand_page_io_req *req) +{ + struct spinand_ondie_ecc_conf *engine_conf = nand->ecc.ctx.priv; + struct spinand_device *spinand = nand_to_spinand(nand); + struct mtd_info *mtd = spinand_to_mtd(spinand); + int ret; + + if (req->mode == MTD_OPS_RAW) + return 0; + + /* Nothing to do when finishing a page write */ + if (req->type == NAND_PAGE_WRITE) + return 0; + + /* Finish a page read: check the status, report errors/bitflips */ + ret = spinand_check_ecc_status(spinand, engine_conf->status); + if (ret == -EBADMSG) { + mtd->ecc_stats.failed++; + } else if (ret > 0) { + unsigned int pages; + + /* + * Continuous reads don't allow us to get the detail, + * so we may exagerate the actual number of corrected bitflips. + */ + if (!req->continuous) + pages = 1; + else + pages = req->datalen / nanddev_page_size(nand); + + mtd->ecc_stats.corrected += ret * pages; + } + + return ret; +} + +static const struct nand_ecc_engine_ops spinand_ondie_ecc_engine_ops = { + .init_ctx = spinand_ondie_ecc_init_ctx, + .cleanup_ctx = spinand_ondie_ecc_cleanup_ctx, + .prepare_io_req = spinand_ondie_ecc_prepare_io_req, + .finish_io_req = spinand_ondie_ecc_finish_io_req, +}; + +static struct nand_ecc_engine spinand_ondie_ecc_engine = { + .ops = &spinand_ondie_ecc_engine_ops, +}; + +static void spinand_ondie_ecc_save_status(struct nand_device *nand, u8 status) +{ + struct spinand_ondie_ecc_conf *engine_conf = nand->ecc.ctx.priv; + + if (nand->ecc.ctx.conf.engine_type == NAND_ECC_ENGINE_TYPE_ON_DIE && + engine_conf) + engine_conf->status = status; +} + +int spinand_write_enable_op(struct spinand_device *spinand) +{ + struct spi_mem_op op = SPINAND_OP(spinand, wr_en); + + return spi_mem_exec_op(spinand->spimem, &op); +} + +static int spinand_load_page_op(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int row = nanddev_pos_to_row(nand, &req->pos); + 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); +} + +static int spinand_read_from_cache_op(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + struct nand_device *nand = spinand_to_nand(spinand); + struct mtd_info *mtd = spinand_to_mtd(spinand); + struct spi_mem_dirmap_desc *rdesc; + unsigned int nbytes = 0; + void *buf = NULL; + u16 column = 0; + ssize_t ret; + + if (req->datalen) { + buf = spinand->databuf; + if (!req->continuous) + nbytes = nanddev_page_size(nand); + else + nbytes = round_up(req->dataoffs + req->datalen, + nanddev_page_size(nand)); + column = 0; + } + + if (req->ooblen) { + nbytes += nanddev_per_page_oobsize(nand); + if (!buf) { + buf = spinand->oobbuf; + column = nanddev_page_size(nand); + } + } + + 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->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)); + + while (nbytes) { + ret = spi_mem_dirmap_read(rdesc, column, nbytes, buf); + if (ret < 0) + return ret; + + if (!ret || ret > nbytes) + return -EIO; + + nbytes -= ret; + column += ret; + buf += ret; + + /* + * Dirmap accesses are allowed to toggle the CS. + * Toggling the CS during a continuous read is forbidden. + */ + 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) + memcpy(req->databuf.in, spinand->databuf + req->dataoffs, + req->datalen); + + if (req->ooblen) { + if (req->mode == MTD_OPS_AUTO_OOB) + mtd_ooblayout_get_databytes(mtd, req->oobbuf.in, + spinand->oobbuf, + req->ooboffs, + req->ooblen); + else + memcpy(req->oobbuf.in, spinand->oobbuf + req->ooboffs, + req->ooblen); + } + + return 0; +} + +static int spinand_write_to_cache_op(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + struct nand_device *nand = spinand_to_nand(spinand); + struct mtd_info *mtd = spinand_to_mtd(spinand); + struct spi_mem_dirmap_desc *wdesc; + unsigned int nbytes, column = 0; + void *buf = spinand->databuf; + ssize_t ret; + + /* + * Looks like PROGRAM LOAD (AKA write cache) does not necessarily reset + * the cache content to 0xFF (depends on vendor implementation), so we + * must fill the page cache entirely even if we only want to program + * the data portion of the page, otherwise we might corrupt the BBM or + * user data previously programmed in OOB area. + * + * Only reset the data buffer manually, the OOB buffer is prepared by + * ECC engines ->prepare_io_req() callback. + */ + nbytes = nanddev_page_size(nand) + nanddev_per_page_oobsize(nand); + memset(spinand->databuf, 0xff, nanddev_page_size(nand)); + + if (req->datalen) + memcpy(spinand->databuf + req->dataoffs, req->databuf.out, + req->datalen); + + if (req->ooblen) { + if (req->mode == MTD_OPS_AUTO_OOB) + mtd_ooblayout_set_databytes(mtd, req->oobbuf.out, + spinand->oobbuf, + req->ooboffs, + req->ooblen); + else + memcpy(spinand->oobbuf + req->ooboffs, req->oobbuf.out, + req->ooblen); + } + + 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->info.op_tmpl->data.ecc = false; + + if (spinand->flags & SPINAND_HAS_PROG_PLANE_SELECT_BIT) + column |= req->pos.plane << fls(nanddev_page_size(nand)); + + while (nbytes) { + ret = spi_mem_dirmap_write(wdesc, column, nbytes, buf); + if (ret < 0) + return ret; + + if (!ret || ret > nbytes) + return -EIO; + + nbytes -= ret; + column += ret; + buf += ret; + } + + return 0; +} + +static int spinand_program_op(struct spinand_device *spinand, + const struct nand_page_io_req *req) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int row = nanddev_pos_to_row(nand, &req->pos); + struct spi_mem_op op = SPINAND_OP(spinand, prog_exec, row); + + return spi_mem_exec_op(spinand->spimem, &op); +} + +static int spinand_erase_op(struct spinand_device *spinand, + const struct nand_pos *pos) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int row = nanddev_pos_to_row(nand, pos); + struct spi_mem_op op = SPINAND_OP(spinand, blk_erase, row); + + return spi_mem_exec_op(spinand->spimem, &op); +} + +/** + * 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_OP(spinand, get_feature, + REG_STATUS, spinand->scratchbuf); + u8 status; + int ret; + + ret = spi_mem_poll_status(spinand->spimem, &op, STATUS_BUSY, 0, + initial_delay_us, + poll_delay_us, + SPINAND_WAITRDY_TIMEOUT_MS); + if (ret) + return ret; + + status = *spinand->scratchbuf; + if (!(status & STATUS_BUSY)) + goto out; + + /* + * Extra read, just in case the STATUS_READY bit has changed + * since our last check + */ + ret = spinand_read_status(spinand, &status); + if (ret) + return ret; + +out: + if (s) + *s = status; + + return status & STATUS_BUSY ? -ETIMEDOUT : 0; +} + +static int spinand_read_id_op(struct spinand_device *spinand, u8 naddr, + u8 ndummy, u8 *buf) +{ + 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); + if (!ret) + memcpy(buf, spinand->scratchbuf, SPINAND_MAX_ID_LEN); + + return ret; +} + +static int spinand_reset_op(struct spinand_device *spinand) +{ + struct spi_mem_op op = SPINAND_OP(spinand, reset); + int ret; + + ret = spi_mem_exec_op(spinand->spimem, &op); + if (ret) + return ret; + + return spinand_wait(spinand, + SPINAND_RESET_INITIAL_DELAY_US, + SPINAND_RESET_POLL_DELAY_US, + NULL); +} + +static int spinand_lock_block(struct spinand_device *spinand, u8 lock) +{ + return spinand_write_reg_op(spinand, REG_BLOCK_LOCK, lock); +} + +/** + * 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; + int ret; + + ret = nand_ecc_prepare_io_req(nand, (struct nand_page_io_req *)req); + if (ret) + return ret; + + ret = spinand_load_page_op(spinand, req); + if (ret) + return ret; + + ret = spinand_wait(spinand, + SPINAND_READ_INITIAL_DELAY_US, + SPINAND_READ_POLL_DELAY_US, + &status); + if (ret < 0) + return ret; + + spinand_ondie_ecc_save_status(nand, status); + + ret = spinand_read_from_cache_op(spinand, req); + if (ret) + return ret; + + return nand_ecc_finish_io_req(nand, (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; + int ret; + + ret = nand_ecc_prepare_io_req(nand, (struct nand_page_io_req *)req); + if (ret) + return ret; + + ret = spinand_write_enable_op(spinand); + if (ret) + return ret; + + ret = spinand_write_to_cache_op(spinand, req); + if (ret) + return ret; + + ret = spinand_program_op(spinand, req); + if (ret) + return ret; + + ret = spinand_wait(spinand, + SPINAND_WRITE_INITIAL_DELAY_US, + SPINAND_WRITE_POLL_DELAY_US, + &status); + if (ret) + return ret; + + if (status & STATUS_PROG_FAILED) + return -EIO; + + return nand_ecc_finish_io_req(nand, (struct nand_page_io_req *)req); +} + +static int spinand_mtd_regular_page_read(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops, + unsigned int *max_bitflips) +{ + 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; + unsigned int retry_mode = 0; + int ret = 0; + + old_stats = mtd->ecc_stats; + + if (ops->mode == MTD_OPS_RAW || !mtd->ooblayout) + disable_ecc = true; + + nanddev_io_for_each_page(nand, NAND_PAGE_READ, from, ops, &iter) { + if (disable_ecc) + iter.req.mode = MTD_OPS_RAW; + + ret = spinand_select_target(spinand, iter.req.pos.target); + if (ret) + break; + +read_retry: + ret = spinand_read_page(spinand, &iter.req); + if (ret < 0 && ret != -EBADMSG) + break; + + 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 { + *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) + ret = -EBADMSG; + + return ret; +} + +static int spinand_mtd_continuous_page_read(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops, + unsigned int *max_bitflips) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nanddev(mtd); + struct nand_io_iter iter; + u8 status; + int ret; + + ret = spinand_cont_read_enable(spinand, true); + if (ret) + return ret; + + /* + * The cache is divided into two halves. While one half of the cache has + * the requested data, the other half is loaded with the next chunk of data. + * Therefore, the host can read out the data continuously from page to page. + * 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); + if (ret) + goto end_cont_read; + + ret = nand_ecc_prepare_io_req(nand, &iter.req); + if (ret) + goto end_cont_read; + + ret = spinand_load_page_op(spinand, &iter.req); + if (ret) + goto end_cont_read; + + ret = spinand_wait(spinand, SPINAND_READ_INITIAL_DELAY_US, + SPINAND_READ_POLL_DELAY_US, NULL); + if (ret < 0) + goto end_cont_read; + + ret = spinand_read_from_cache_op(spinand, &iter.req); + if (ret) + goto end_cont_read; + + ops->retlen += iter.req.datalen; + + ret = spinand_read_status(spinand, &status); + if (ret) + goto end_cont_read; + + spinand_ondie_ecc_save_status(nand, status); + + ret = nand_ecc_finish_io_req(nand, &iter.req); + if (ret < 0) + goto end_cont_read; + + *max_bitflips = max_t(unsigned int, *max_bitflips, ret); + ret = 0; + } + +end_cont_read: + /* + * Once all the data has been read out, the host can either pull CS# + * high and wait for tRST or manually clear the bit in the configuration + * register to terminate the continuous read operation. We have no + * guarantee the SPI controller drivers will effectively deassert the CS + * when we expect them to, so take the register based approach. + */ + spinand_cont_read_enable(spinand, false); + + return ret; +} + +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) { + /* + * 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; + } +} + +static bool spinand_use_cont_read(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct spinand_device *spinand = nand_to_spinand(nand); + struct nand_pos start_pos, end_pos; + + if (!spinand->cont_read_possible) + return false; + + /* OOBs won't be retrieved */ + if (ops->ooblen || ops->oobbuf) + return false; + + nanddev_offs_to_pos(nand, from, &start_pos); + nanddev_offs_to_pos(nand, from + ops->len - 1, &end_pos); + + return start_pos.page < end_pos.page; +} + +static int spinand_mtd_read(struct mtd_info *mtd, loff_t from, + struct mtd_oob_ops *ops) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + struct mtd_ecc_stats old_stats; + unsigned int max_bitflips = 0; + int ret; + + mutex_lock(&spinand->lock); + + old_stats = mtd->ecc_stats; + + if (spinand_use_cont_read(mtd, from, ops)) { + ret = spinand_mtd_continuous_page_read(mtd, from, ops, &max_bitflips); + 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 += + mtd->ecc_stats.failed - old_stats.failed; + ops->stats->corrected_bitflips += + mtd->ecc_stats.corrected - old_stats.corrected; + } + + mutex_unlock(&spinand->lock); + + return ret ? ret : max_bitflips; +} + +static int spinand_mtd_write(struct mtd_info *mtd, loff_t to, + struct mtd_oob_ops *ops) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nanddev(mtd); + struct nand_io_iter iter; + bool disable_ecc = false; + int ret = 0; + + if (ops->mode == MTD_OPS_RAW || !mtd->ooblayout) + disable_ecc = true; + + mutex_lock(&spinand->lock); + + nanddev_io_for_each_page(nand, NAND_PAGE_WRITE, to, ops, &iter) { + if (disable_ecc) + iter.req.mode = MTD_OPS_RAW; + + ret = spinand_select_target(spinand, iter.req.pos.target); + if (ret) + break; + + ret = spinand_write_page(spinand, &iter.req); + if (ret) + break; + + ops->retlen += iter.req.datalen; + ops->oobretlen += iter.req.ooblen; + } + + mutex_unlock(&spinand->lock); + + return ret; +} + +static bool spinand_isbad(struct nand_device *nand, const struct nand_pos *pos) +{ + struct spinand_device *spinand = nand_to_spinand(nand); + u8 marker[2] = { }; + struct nand_page_io_req req = { + .pos = *pos, + .ooblen = sizeof(marker), + .ooboffs = 0, + .oobbuf.in = marker, + .mode = MTD_OPS_RAW, + }; + int ret; + + spinand_select_target(spinand, pos->target); + + ret = spinand_read_page(spinand, &req); + if (ret == -EOPNOTSUPP) { + /* Retry with ECC in case raw access is not supported */ + req.mode = MTD_OPS_PLACE_OOB; + spinand_read_page(spinand, &req); + } + + if (marker[0] != 0xff || marker[1] != 0xff) + return true; + + return false; +} + +static int spinand_mtd_block_isbad(struct mtd_info *mtd, loff_t offs) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct spinand_device *spinand = nand_to_spinand(nand); + struct nand_pos pos; + int ret; + + nanddev_offs_to_pos(nand, offs, &pos); + mutex_lock(&spinand->lock); + ret = nanddev_isbad(nand, &pos); + mutex_unlock(&spinand->lock); + + return ret; +} + +static int spinand_markbad(struct nand_device *nand, const struct nand_pos *pos) +{ + struct spinand_device *spinand = nand_to_spinand(nand); + u8 marker[2] = { }; + struct nand_page_io_req req = { + .pos = *pos, + .ooboffs = 0, + .ooblen = sizeof(marker), + .oobbuf.out = marker, + .mode = MTD_OPS_RAW, + }; + int ret; + + ret = spinand_select_target(spinand, pos->target); + if (ret) + return ret; + + ret = spinand_write_page(spinand, &req); + if (ret == -EOPNOTSUPP) { + /* Retry with ECC in case raw access is not supported */ + req.mode = MTD_OPS_PLACE_OOB; + ret = spinand_write_page(spinand, &req); + } + + return ret; +} + +static int spinand_mtd_block_markbad(struct mtd_info *mtd, loff_t offs) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + struct spinand_device *spinand = nand_to_spinand(nand); + struct nand_pos pos; + int ret; + + nanddev_offs_to_pos(nand, offs, &pos); + mutex_lock(&spinand->lock); + ret = nanddev_markbad(nand, &pos); + mutex_unlock(&spinand->lock); + + return ret; +} + +static int spinand_erase(struct nand_device *nand, const struct nand_pos *pos) +{ + struct spinand_device *spinand = nand_to_spinand(nand); + u8 status; + int ret; + + ret = spinand_select_target(spinand, pos->target); + if (ret) + return ret; + + ret = spinand_write_enable_op(spinand); + if (ret) + return ret; + + ret = spinand_erase_op(spinand, pos); + if (ret) + return ret; + + ret = spinand_wait(spinand, + SPINAND_ERASE_INITIAL_DELAY_US, + SPINAND_ERASE_POLL_DELAY_US, + &status); + + if (!ret && (status & STATUS_ERASE_FAILED)) + ret = -EIO; + + return ret; +} + +static int spinand_mtd_erase(struct mtd_info *mtd, + struct erase_info *einfo) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + int ret; + + mutex_lock(&spinand->lock); + ret = nanddev_mtd_erase(mtd, einfo); + mutex_unlock(&spinand->lock); + + return ret; +} + +static int spinand_mtd_block_isreserved(struct mtd_info *mtd, loff_t offs) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + struct nand_device *nand = mtd_to_nanddev(mtd); + struct nand_pos pos; + int ret; + + nanddev_offs_to_pos(nand, offs, &pos); + mutex_lock(&spinand->lock); + ret = nanddev_isreserved(nand, &pos); + mutex_unlock(&spinand->lock); + + return ret; +} + +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_desc *desc = NULL; + + 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); + } + + 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->length = nanddev_page_size(nand) + + nanddev_per_page_oobsize(nand); + desc = devm_spi_mem_dirmap_create(&spinand->spimem->spi->dev, + spinand->spimem, info); + } + + return 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; + + if (nand->ecc.engine->integration == NAND_ECC_ENGINE_INTEGRATION_PIPELINED) + enable_ecc = true; + + if (spinand->cont_read_possible && spinand->op_templates->cont_read_cache) + secondary_op = true; + + /* + * 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; + } + + /* 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 = desc; + + /* 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 = desc; + + return 0; +} + +static int spinand_create_dirmaps(struct spinand_device *spinand) +{ + struct nand_device *nand = spinand_to_nand(spinand); + int i, ret; + + spinand->dirmaps = devm_kzalloc(&spinand->spimem->spi->dev, + sizeof(*spinand->dirmaps) * + nand->memorg.planes_per_lun, + GFP_KERNEL); + if (!spinand->dirmaps) + return -ENOMEM; + + for (i = 0; i < nand->memorg.planes_per_lun; i++) { + ret = spinand_create_dirmap(spinand, i); + if (ret) + return ret; + } + + 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, + .isbad = spinand_isbad, +}; + +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, + &heyangtek_spinand_manufacturer, + ¯onix_spinand_manufacturer, + µn_spinand_manufacturer, + ¶gon_spinand_manufacturer, + &skyhigh_spinand_manufacturer, + &toshiba_spinand_manufacturer, + &winbond_spinand_manufacturer, + &xtx_spinand_manufacturer, +}; + +static int spinand_manufacturer_match(struct spinand_device *spinand, + enum spinand_readid_method rdid_method) +{ + u8 *id = spinand->id.data; + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(spinand_manufacturers); i++) { + const struct spinand_manufacturer *manufacturer = + spinand_manufacturers[i]; + + if (id[0] != manufacturer->id) + continue; + + ret = spinand_match_and_init(spinand, + manufacturer->chips, + manufacturer->nchips, + rdid_method); + if (ret < 0) + continue; + + spinand->manufacturer = manufacturer; + return 0; + } + return -EOPNOTSUPP; +} + +static int spinand_id_detect(struct spinand_device *spinand) +{ + u8 *id = spinand->id.data; + int ret; + + ret = spinand_read_id_op(spinand, 0, 0, id); + if (ret) + return ret; + ret = spinand_manufacturer_match(spinand, SPINAND_READID_METHOD_OPCODE); + if (!ret) + return 0; + + ret = spinand_read_id_op(spinand, 1, 0, id); + if (ret) + return ret; + ret = spinand_manufacturer_match(spinand, + SPINAND_READID_METHOD_OPCODE_ADDR); + if (!ret) + return 0; + + ret = spinand_read_id_op(spinand, 0, 1, id); + if (ret) + return ret; + ret = spinand_manufacturer_match(spinand, + SPINAND_READID_METHOD_OPCODE_DUMMY); + + return ret; +} + +static int spinand_manufacturer_init(struct spinand_device *spinand) +{ + int ret; + + if (spinand->manufacturer->ops->init) { + ret = spinand->manufacturer->ops->init(spinand); + if (ret) + return ret; + } + + return 0; +} + +static void spinand_manufacturer_cleanup(struct spinand_device *spinand) +{ + /* Release manufacturer private data */ + if (spinand->manufacturer->ops->cleanup) + 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, enum spinand_bus_interface iface, + const struct spinand_op_variants *variants) +{ + struct nand_device *nand = spinand_to_nand(spinand); + const struct spi_mem_op *best_variant = NULL; + u64 best_op_duration_ns = ULLONG_MAX; + unsigned int i; + + for (i = 0; i < variants->nops; i++) { + struct spi_mem_op op = variants->ops[i]; + u64 op_duration_ns = 0; + 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); + + while (nbytes) { + op.data.nbytes = nbytes; + ret = spi_mem_adjust_op_size(spinand->spimem, &op); + if (ret) + break; + + spi_mem_adjust_op_freq(spinand->spimem, &op); + + if (!spi_mem_supports_op(spinand->spimem, &op)) + break; + + nbytes -= op.data.nbytes; + + op_duration_ns += spi_mem_calc_op_duration(spinand->spimem, &op); + } + + if (!nbytes && op_duration_ns < best_op_duration_ns) { + best_op_duration_ns = op_duration_ns; + best_variant = &variants->ops[i]; + } + } + + return best_variant; +} + +/** + * spinand_match_and_init() - Try to find a match between a device ID and an + * entry in a spinand_info table + * @spinand: SPI NAND object + * @table: SPI NAND device description table + * @table_size: size of the device description table + * @rdid_method: read id method to match + * + * Match between a device ID retrieved through the READ_ID command and an + * entry in the SPI NAND description table. If a match is found, the spinand + * object will be initialized with information provided by the matching + * spinand_info entry. + * + * Return: 0 on success, a negative error code otherwise. + */ +int spinand_match_and_init(struct spinand_device *spinand, + const struct spinand_info *table, + unsigned int table_size, + enum spinand_readid_method rdid_method) +{ + u8 *id = spinand->id.data; + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int i; + int ret; + + for (i = 0; i < table_size; i++) { + const struct spinand_info *info = &table[i]; + const struct spi_mem_op *op; + + if (rdid_method != info->devid.method) + continue; + + if (memcmp(id + 1, info->devid.id, info->devid.len)) + continue; + + nand->memorg = table[i].memorg; + nanddev_set_ecc_requirements(nand, &table[i].eccreq); + spinand->eccinfo = table[i].eccinfo; + spinand->flags = table[i].flags; + spinand->id.len = 1 + table[i].devid.len; + spinand->select_target = table[i].select_target; + 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; + + /* I/O variants selection with single-spi SDR commands */ + + op = spinand_select_op_variant(spinand, SSDR, + info->op_variants.read_cache); + if (!op) + return -EOPNOTSUPP; + + spinand->ssdr_op_templates.read_cache = op; + + op = spinand_select_op_variant(spinand, SSDR, + info->op_variants.write_cache); + if (!op) + 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; + + 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, 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->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 -EOPNOTSUPP; +} + +static int spinand_detect(struct spinand_device *spinand) +{ + struct device *dev = &spinand->spimem->spi->dev; + struct nand_device *nand = spinand_to_nand(spinand); + int ret; + + ret = spinand_reset_op(spinand); + if (ret) + return ret; + + ret = spinand_id_detect(spinand); + if (ret) { + dev_err(dev, "unknown raw ID %*phN\n", SPINAND_MAX_ID_LEN, + spinand->id.data); + return ret; + } + + if (nand->memorg.ntargets > 1 && !spinand->select_target) { + dev_err(dev, + "SPI NANDs with more than one die must implement ->select_target()\n"); + return -EINVAL; + } + + dev_info(&spinand->spimem->spi->dev, + "%s SPI NAND was found.\n", spinand->manufacturer->name); + dev_info(&spinand->spimem->spi->dev, + "%llu MiB, block size: %zu KiB, page size: %zu, OOB size: %u\n", + nanddev_size(nand) >> 20, nanddev_eraseblock_size(nand) >> 10, + nanddev_page_size(nand), nanddev_per_page_oobsize(nand)); + + 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; + struct nand_device *nand = spinand_to_nand(spinand); + int ret, i; + + ret = spinand_read_cfg(spinand); + if (ret) + return ret; + + ret = spinand_upd_cfg(spinand, CFG_OTP_ENABLE, 0); + if (ret) + return ret; + + ret = spinand_manufacturer_init(spinand); + if (ret) { + dev_err(dev, + "Failed to initialize the SPI NAND chip (err = %d)\n", + ret); + 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) + goto manuf_cleanup; + + ret = spinand_lock_block(spinand, BL_ALL_UNLOCKED); + if (ret) + goto manuf_cleanup; + } + + return 0; + +manuf_cleanup: + spinand_manufacturer_cleanup(spinand); + + return ret; +} + +static void spinand_mtd_resume(struct mtd_info *mtd) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + int ret; + + ret = spinand_reset_op(spinand); + if (ret) + return; + + ret = spinand_init_flash(spinand); + if (ret) + return; + + 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; + struct mtd_info *mtd = spinand_to_mtd(spinand); + struct nand_device *nand = mtd_to_nanddev(mtd); + int ret; + + /* + * We need a scratch buffer because the spi_mem interface requires that + * buf passed in spi_mem_op->data.buf be DMA-able. + */ + spinand->scratchbuf = kzalloc(SPINAND_MAX_ID_LEN, GFP_KERNEL); + if (!spinand->scratchbuf) + return -ENOMEM; + + spinand_init_ssdr_templates(spinand); + + ret = spinand_detect(spinand); + if (ret) + goto err_free_bufs; + + /* + * Use kzalloc() instead of devm_kzalloc() here, because some drivers + * may use this buffer for DMA access. + * Memory allocated by devm_ does not guarantee DMA-safe alignment. + */ + spinand->databuf = kzalloc(nanddev_eraseblock_size(nand), + GFP_KERNEL); + if (!spinand->databuf) { + ret = -ENOMEM; + goto err_free_bufs; + } + + spinand->oobbuf = spinand->databuf + nanddev_page_size(nand); + + ret = spinand_init_cfg_cache(spinand); + if (ret) + goto err_free_bufs; + + ret = spinand_init_flash(spinand); + if (ret) + goto err_free_bufs; + + ret = nanddev_init(nand, &spinand_ops, THIS_MODULE); + if (ret) + goto err_manuf_cleanup; + + /* SPI-NAND default ECC engine is on-die */ + nand->ecc.defaults.engine_type = NAND_ECC_ENGINE_TYPE_ON_DIE; + nand->ecc.ondie_engine = &spinand_ondie_ecc_engine; + + spinand_ecc_enable(spinand, false); + ret = nanddev_ecc_engine_init(nand); + if (ret) + goto err_cleanup_nanddev; + + /* + * Continuous read can only be enabled with an on-die ECC engine, so the + * 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; + mtd->_block_isbad = spinand_mtd_block_isbad; + mtd->_block_markbad = spinand_mtd_block_markbad; + 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) + goto err_cleanup_ecc_engine; + } + + mtd->oobavail = ret; + + /* Propagate ECC information to mtd_info */ + mtd->ecc_strength = nanddev_get_ecc_conf(nand)->strength; + mtd->ecc_step_size = nanddev_get_ecc_conf(nand)->step_size; + mtd->bitflip_threshold = DIV_ROUND_UP(mtd->ecc_strength * 3, 4); + + ret = spinand_create_dirmaps(spinand); + if (ret) { + dev_err(dev, + "Failed to create direct mappings for read/write operations (err = %d)\n", + ret); + goto err_cleanup_ecc_engine; + } + + return 0; + +err_cleanup_ecc_engine: + nanddev_ecc_engine_cleanup(nand); + +err_cleanup_nanddev: + nanddev_cleanup(nand); + +err_manuf_cleanup: + spinand_manufacturer_cleanup(spinand); + +err_free_bufs: + kfree(spinand->databuf); + kfree(spinand->scratchbuf); + return ret; +} + +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); + kfree(spinand->scratchbuf); +} + +static int spinand_probe(struct spi_mem *mem) +{ + struct spinand_device *spinand; + struct mtd_info *mtd; + int ret; + + spinand = devm_kzalloc(&mem->spi->dev, sizeof(*spinand), + GFP_KERNEL); + if (!spinand) + return -ENOMEM; + + spinand->spimem = mem; + spi_mem_set_drvdata(mem, spinand); + spinand_set_of_node(spinand, mem->spi->dev.of_node); + mutex_init(&spinand->lock); + mtd = spinand_to_mtd(spinand); + mtd->dev.parent = &mem->spi->dev; + + ret = spinand_init(spinand); + if (ret) + return ret; + + ret = mtd_device_register(mtd, NULL, 0); + if (ret) + goto err_spinand_cleanup; + + return 0; + +err_spinand_cleanup: + spinand_cleanup(spinand); + + return ret; +} + +static int spinand_remove(struct spi_mem *mem) +{ + struct spinand_device *spinand; + struct mtd_info *mtd; + int ret; + + spinand = spi_mem_get_drvdata(mem); + mtd = spinand_to_mtd(spinand); + + ret = mtd_device_unregister(mtd); + if (ret) + return ret; + + spinand_cleanup(spinand); + + return 0; +} + +static const struct spi_device_id spinand_ids[] = { + { .name = "spi-nand" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(spi, spinand_ids); + +#ifdef CONFIG_OF +static const struct of_device_id spinand_of_ids[] = { + { .compatible = "spi-nand" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, spinand_of_ids); +#endif + +static struct spi_mem_driver spinand_drv = { + .spidrv = { + .id_table = spinand_ids, + .driver = { + .name = "spi-nand", + .of_match_table = of_match_ptr(spinand_of_ids), + }, + }, + .probe = spinand_probe, + .remove = spinand_remove, +}; +module_spi_mem_driver(spinand_drv); + +MODULE_DESCRIPTION("SPI NAND framework"); +MODULE_AUTHOR("Peter Pan"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/mtd/nand/spi/dosilicon.c b/drivers/mtd/nand/spi/dosilicon.c new file mode 100644 index 000000000..f99899866 --- /dev/null +++ b/drivers/mtd/nand/spi/dosilicon.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: Ahmed Naseef + */ + +#include +#include +#include + +#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 new file mode 100644 index 000000000..8607c2fde --- /dev/null +++ b/drivers/mtd/nand/spi/esmt.c @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: + * Chuanhong Guo - the main driver logic + * Martin Kurbanov - OOB layout + */ + +#include +#include +#include +#include + +/* 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_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)); + +/* + * OOB spare area map (64 bytes) + * + * Bad Block Markers + * filled by HW and kernel Reserved + * | +-----------------------+-----------------------+ + * | | | | + * | | OOB free data Area |non ECC protected | + * | +-------------|-----+-----------------|-----+-----------------|-----+ + * | | | | | | | | + * +-|---|----------+--|-----|--------------+--|-----|--------------+--|-----|--------------+ + * | | | section0 | | | section1 | | | section2 | | | section3 | + * +-v-+-v-+---+----+--v--+--v--+-----+-----+--v--+--v--+-----+-----+--v--+--v--+-----+-----+ + * | | | | | | | | | | | | | | | | | + * |0:1|2:3|4:7|8:15|16:17|18:19|20:23|24:31|32:33|34:35|36:39|40:47|48:49|50:51|52:55|56:63| + * | | | | | | | | | | | | | | | | | + * +---+---+-^-+--^-+-----+-----+--^--+--^--+-----+-----+--^--+--^--+-----+-----+--^--+--^--+ + * | | | | | | | | + * | +----------------|-----+-----------------|-----+-----------------|-----+ + * | ECC Area|(Main + Spare) - filled|by ESMT NAND HW | + * | | | | + * +---------------------+-----------------------+-----------------------+ + * OOB ECC protected Area - not used due to + * partial programming from some filesystems + * (like JFFS2 with cleanmarkers) + */ + +#define ESMT_OOB_SECTION_COUNT 4 +#define ESMT_OOB_SECTION_SIZE(nand) \ + (nanddev_per_page_oobsize(nand) / ESMT_OOB_SECTION_COUNT) +#define ESMT_OOB_FREE_SIZE(nand) \ + (ESMT_OOB_SECTION_SIZE(nand) / 2) +#define ESMT_OOB_ECC_SIZE(nand) \ + (ESMT_OOB_SECTION_SIZE(nand) - ESMT_OOB_FREE_SIZE(nand)) +#define ESMT_OOB_BBM_SIZE 2 + +static int f50l1g41lb_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + + if (section >= ESMT_OOB_SECTION_COUNT) + return -ERANGE; + + region->offset = section * ESMT_OOB_SECTION_SIZE(nand) + + ESMT_OOB_FREE_SIZE(nand); + region->length = ESMT_OOB_ECC_SIZE(nand); + + return 0; +} + +static int f50l1g41lb_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + struct nand_device *nand = mtd_to_nanddev(mtd); + + if (section >= ESMT_OOB_SECTION_COUNT) + return -ERANGE; + + /* + * Reserve space for bad blocks markers (section0) and + * reserved bytes (sections 1-3) + */ + region->offset = section * ESMT_OOB_SECTION_SIZE(nand) + 2; + + /* Use only 2 non-protected ECC bytes per each OOB section */ + region->length = 2; + + return 0; +} + +static const struct mtd_ooblayout_ops f50l1g41lb_ooblayout = { + .ecc = f50l1g41lb_ooblayout_ecc, + .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, + 0x7f, 0x7f), + 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)), + SPINAND_INFO("F50D1G41LB", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x11, 0x7f, + 0x7f, 0x7f), + 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)), + SPINAND_INFO("F50D2G41KA", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x51, 0x7f, + 0x7f, 0x7f), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&f50l1g41lb_ooblayout, NULL)), +}; + +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", + .chips = esmt_c8_spinand_table, + .nchips = ARRAY_SIZE(esmt_c8_spinand_table), + .ops = &esmt_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/fmsh.c b/drivers/mtd/nand/spi/fmsh.c new file mode 100644 index 000000000..6352e9c9e --- /dev/null +++ b/drivers/mtd/nand/spi/fmsh.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2020-2021 Rockchip Electronics Co., Ltd. + * + * Author: Dingqiang Lin + */ + +#include +#include +#include + +#define FM25G01B_STATUS_ECC_MASK (7 << 4) + #define FM25G01B_STATUS_ECC_NO_BITFLIPS (0 << 4) + #define FM25G01B_STATUS_ECC_1_3_BITFLIPS (1 << 4) + #define FM25G01B_STATUS_ECC_4_BITFLIPS (2 << 4) + #define FM25G01B_STATUS_ECC_5_BITFLIPS (3 << 4) + #define FM25G01B_STATUS_ECC_6_BITFLIPS (4 << 4) + #define FM25G01B_STATUS_ECC_7_BITFLIPS (5 << 4) + #define FM25G01B_STATUS_ECC_8_BITFLIPS (6 << 4) + #define FM25G01B_STATUS_ECC_UNCOR_ERROR (7 << 4) + +#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 SPINAND_OP_VARIANTS(fm25g_read_cache_variants, + 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 int fm25g01b_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 fm25g01b_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + /* reserve 2 bytes for the BBM */ + region->offset = 2; + region->length = 62; + + return 0; +} + +static int fm25g01b_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + switch (status & FM25G01B_STATUS_ECC_MASK) { + case FM25G01B_STATUS_ECC_NO_BITFLIPS: + return 0; + + case FM25G01B_STATUS_ECC_1_3_BITFLIPS: + return 3; + + case FM25G01B_STATUS_ECC_4_BITFLIPS: + return 4; + + case FM25G01B_STATUS_ECC_5_BITFLIPS: + return 5; + + case FM25G01B_STATUS_ECC_6_BITFLIPS: + return 6; + + case FM25G01B_STATUS_ECC_7_BITFLIPS: + return 7; + + case FM25G01B_STATUS_ECC_8_BITFLIPS: + return 8; + + case FM25G01B_STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + default: + break; + } + + return -EINVAL; +} + +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 fm25g01b_ooblayout = { + .ecc = fm25g01b_ooblayout_ecc, + .free = fm25g01b_ooblayout_free, +}; + +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("FM25G01B", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xd1), + NAND_MEMORG(1, 2048, 128, 64, 1024, 21, 1, 1, 1), + NAND_ECCREQ(8, 528), + SPINAND_INFO_OP_VARIANTS(&fm25g_read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&fm25g01b_ooblayout, + fm25g01b_ecc_get_status)), + SPINAND_INFO("FM25G02B", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xd2), + NAND_MEMORG(1, 2048, 128, 64, 2048, 41, 1, 1, 1), + NAND_ECCREQ(8, 528), + SPINAND_INFO_OP_VARIANTS(&fm25g_read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&fm25g01b_ooblayout, + fm25g01b_ecc_get_status)), + 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 new file mode 100644 index 000000000..5ff183160 --- /dev/null +++ b/drivers/mtd/nand/spi/foresee.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Copyright (c) 2023, SberDevices. All Rights Reserved. + * + * Author: Martin Kurbanov + */ + +#include +#include +#include + +#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_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 f35sqa002g_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + return -ERANGE; +} + +static int f35sqa002g_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + /* Reserve 2 bytes for the BBM. */ + region->offset = 2; + region->length = 62; + + return 0; +} + +static const struct mtd_ooblayout_ops f35sqa002g_ooblayout = { + .ecc = f35sqa002g_ooblayout_ecc, + .free = f35sqa002g_ooblayout_free, +}; + +static int f35sqa002g_ecc_get_status(struct spinand_device *spinand, u8 status) +{ + struct nand_device *nand = spinand_to_nand(spinand); + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_HAS_BITFLIPS: + return nanddev_get_ecc_conf(nand)->strength; + + default: + break; + } + + /* More than 1-bit error was detected in one or more sectors and + * cannot be corrected. + */ + 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), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(1, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&f35sqa002g_ooblayout, + f35sqa002g_ecc_get_status)), + SPINAND_INFO("F35SQA001G", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x71, 0x71), + 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), + 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 = { +}; + +const struct spinand_manufacturer foresee_spinand_manufacturer = { + .id = SPINAND_MFR_FORESEE, + .name = "FORESEE", + .chips = foresee_spinand_table, + .nchips = ARRAY_SIZE(foresee_spinand_table), + .ops = &foresee_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/gigadevice.c b/drivers/mtd/nand/spi/gigadevice.c new file mode 100644 index 000000000..a6d6d6e0c --- /dev/null +++ b/drivers/mtd/nand/spi/gigadevice.c @@ -0,0 +1,670 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: + * Chuanhong Guo + */ + +#include +#include +#include +#include + +#define SPINAND_MFR_GIGADEVICE 0xC8 + +#define GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS (1 << 4) +#define GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS (3 << 4) + +#define GD5FXGQ5XE_STATUS_ECC_1_4_BITFLIPS (1 << 4) +#define GD5FXGQ5XE_STATUS_ECC_4_BITFLIPS (3 << 4) + +#define GD5FXGQXXEXXG_REG_STATUS2 0xf0 + +#define GD5FXGQ4UXFXXG_STATUS_ECC_MASK (7 << 4) +#define GD5FXGQ4UXFXXG_STATUS_ECC_NO_BITFLIPS (0 << 4) +#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_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_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_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_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_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 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) +{ + if (section > 3) + return -ERANGE; + + region->offset = (16 * section) + 8; + region->length = 8; + + return 0; +} + +static int gd5fxgq4xa_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + if (section) { + region->offset = 16 * section; + region->length = 8; + } else { + /* section 0 has one byte reserved for bad block mark */ + region->offset = 1; + region->length = 7; + } + return 0; +} + +static const struct mtd_ooblayout_ops gd5fxgq4xa_ooblayout = { + .ecc = gd5fxgq4xa_ooblayout_ecc, + .free = gd5fxgq4xa_ooblayout_free, +}; + +static int gd5fxgq4xa_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS: + /* 1-7 bits are flipped. return the maximum. */ + return 7; + + case GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS: + return 8; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + default: + break; + } + + return -EINVAL; +} + +static int gd5fxgqx_variant2_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 gd5fxgqx_variant2_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + /* Reserve 1 bytes for the BBM. */ + region->offset = 1; + region->length = 63; + + return 0; +} + +/* Valid for Q4/Q5 and Q6 (untested) devices */ +static const struct mtd_ooblayout_ops gd5fxgqx_variant2_ooblayout = { + .ecc = gd5fxgqx_variant2_ooblayout_ecc, + .free = gd5fxgqx_variant2_ooblayout_free, +}; + +static int gd5fxgq4xc_ooblayout_256_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section) + return -ERANGE; + + oobregion->offset = 128; + oobregion->length = 128; + + return 0; +} + +static int gd5fxgq4xc_ooblayout_256_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *oobregion) +{ + if (section) + return -ERANGE; + + oobregion->offset = 1; + oobregion->length = 127; + + return 0; +} + +static const struct mtd_ooblayout_ops gd5fxgq4xc_oob_256_ops = { + .ecc = gd5fxgq4xc_ooblayout_256_ecc, + .free = gd5fxgq4xc_ooblayout_256_free, +}; + +static int gd5fxgq4uexxg_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + u8 status2; + struct spi_mem_op op = SPINAND_OP(spinand, get_feature, + GD5FXGQXXEXXG_REG_STATUS2, spinand->scratchbuf); + int ret; + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case GD5FXGQ4XA_STATUS_ECC_1_7_BITFLIPS: + /* + * Read status2 register to determine a more fine grained + * bit error status + */ + ret = spi_mem_exec_op(spinand->spimem, &op); + if (ret) + return ret; + + /* + * 4 ... 7 bits are flipped (1..4 can't be detected, so + * report the maximum of 4 in this case + */ + /* bits sorted this way (3...0): ECCS1,ECCS0,ECCSE1,ECCSE0 */ + status2 = *(spinand->scratchbuf); + return ((status & STATUS_ECC_MASK) >> 2) | + ((status2 & STATUS_ECC_MASK) >> 4); + + case GD5FXGQ4XA_STATUS_ECC_8_BITFLIPS: + return 8; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + default: + break; + } + + return -EINVAL; +} + +static int gd5fxgq5xexxg_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + u8 status2; + struct spi_mem_op op = SPINAND_OP(spinand, get_feature, + GD5FXGQXXEXXG_REG_STATUS2, spinand->scratchbuf); + int ret; + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case GD5FXGQ5XE_STATUS_ECC_1_4_BITFLIPS: + /* + * Read status2 register to determine a more fine grained + * bit error status + */ + ret = spi_mem_exec_op(spinand->spimem, &op); + if (ret) + return ret; + + /* + * 1 ... 4 bits are flipped (and corrected) + */ + /* bits sorted this way (1...0): ECCSE1, ECCSE0 */ + status2 = *(spinand->scratchbuf); + return ((status2 & STATUS_ECC_MASK) >> 4) + 1; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + default: + break; + } + + return -EINVAL; +} + +static int gd5fxgq4ufxxg_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + switch (status & GD5FXGQ4UXFXXG_STATUS_ECC_MASK) { + case GD5FXGQ4UXFXXG_STATUS_ECC_NO_BITFLIPS: + return 0; + + case GD5FXGQ4UXFXXG_STATUS_ECC_1_3_BITFLIPS: + return 3; + + case GD5FXGQ4UXFXXG_STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + default: /* (2 << 4) through (6 << 4) are 4-8 corrected errors */ + return ((status & GD5FXGQ4UXFXXG_STATUS_ECC_MASK) >> 4) + 2; + } + + return -EINVAL; +} + +static const struct spinand_info gigadevice_spinand_table[] = { + SPINAND_INFO("GD5F1GQ4xA", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xf1), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout, + gd5fxgq4xa_ecc_get_status)), + SPINAND_INFO("GD5F2GQ4xA", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xf2), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout, + gd5fxgq4xa_ecc_get_status)), + SPINAND_INFO("GD5F4GQ4xA", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xf4), + NAND_MEMORG(1, 2048, 64, 64, 4096, 80, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgq4xa_ooblayout, + gd5fxgq4xa_ecc_get_status)), + SPINAND_INFO("GD5F4GQ4RC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE, 0xa4, 0x68), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_f, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgq4xc_oob_256_ops, + gd5fxgq4ufxxg_ecc_get_status)), + SPINAND_INFO("GD5F4GQ4UC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE, 0xb4, 0x68), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_f, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgq4xc_oob_256_ops, + gd5fxgq4ufxxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ4UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xd1), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ4RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xc1), + 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(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GQ4UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xd2), + 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(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GQ4RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xc2), + 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(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ4UFxxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE, 0xb1, 0x48), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_f, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4ufxxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ5UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x51), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ5RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x41), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GQ5UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x52), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GQ5RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x42), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F4GQ6UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x55), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 2, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F4GQ6RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x45), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 2, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq5xexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GM7UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x91), + 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, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GM7RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x81), + 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, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GM7UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x92), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 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, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GM7RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x82), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 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, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F4GM8UExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x95), + NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 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, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F4GM8RExxG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x85), + NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 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, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F2GQ5xExxH", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x22), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ5RExxH", + 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_1gq5, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, + gd5fxgq4uexxg_ecc_get_status)), + SPINAND_INFO("GD5F1GQ4RExxH", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xc9), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants_1gq5, + &write_cache_variants, + &update_cache_variants), + 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 = { + .id = SPINAND_MFR_GIGADEVICE, + .name = "GigaDevice", + .chips = gigadevice_spinand_table, + .nchips = ARRAY_SIZE(gigadevice_spinand_table), + .ops = &gigadevice_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/heyangtek.c b/drivers/mtd/nand/spi/heyangtek.c new file mode 100644 index 000000000..7fc50fd3d --- /dev/null +++ b/drivers/mtd/nand/spi/heyangtek.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Authors: + * Andrey Zolotarev - the main driver logic + * Aleksei Sviridkin - adaptation to the mainline Linux kernel + * + * Based on: + * https://github.com/keenetic/kernel-49/commit/bacade569fb12bc0ad31ba09bca9b890118fbca7 + */ + +#include +#include +#include + +#define SPINAND_MFR_HEYANGTEK 0xc9 + +#define HYF1GQ4_STATUS_ECC_LIMIT_BITFLIPS (3 << 4) + +static SPINAND_OP_VARIANTS(read_cache_variants, + 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_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)); + +/* + * HYF1GQ4UDACAE is a GD5F1GQ4-compatible die, so the OOB layout is taken + * from gd5fxgq4xa: the on-die ECC parity occupies bytes 8..15 of each + * 16-byte section, the bad block marker sits in byte 0 and the remaining + * bytes are exposed as free. + */ +static int hyf1gq4_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = (16 * section) + 8; + region->length = 8; + + return 0; +} + +static int hyf1gq4_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + if (section) { + region->offset = 16 * section; + region->length = 8; + } else { + /* section 0 has one byte reserved for the bad block marker */ + region->offset = 1; + region->length = 7; + } + + return 0; +} + +static const struct mtd_ooblayout_ops hyf1gq4_ooblayout = { + .ecc = hyf1gq4_ooblayout_ecc, + .free = hyf1gq4_ooblayout_free, +}; + +static int hyf1gq4_ecc_get_status(struct spinand_device *spinand, u8 status) +{ + struct nand_device *nand = spinand_to_nand(spinand); + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + case STATUS_ECC_HAS_BITFLIPS: + /* + * The die exposes only a coarse 2-bit ECC status and has no + * register for the exact bitflip count. This code means + * "corrected, below the refresh threshold", so report half of + * the ECC strength as a representative value. + */ + return nanddev_get_ecc_conf(nand)->strength / 2; + + case HYF1GQ4_STATUS_ECC_LIMIT_BITFLIPS: + /* + * "Corrected, refresh recommended": report the full ECC + * strength so the upper layers relocate the data. + */ + return nanddev_get_ecc_conf(nand)->strength; + + default: + break; + } + + return -EINVAL; +} + +static const struct spinand_info heyangtek_spinand_table[] = { + SPINAND_INFO("HYF1GQ4UDACAE", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 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(&hyf1gq4_ooblayout, + hyf1gq4_ecc_get_status)), +}; + +static const struct spinand_manufacturer_ops heyangtek_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer heyangtek_spinand_manufacturer = { + .id = SPINAND_MFR_HEYANGTEK, + .name = "HeYangTek", + .chips = heyangtek_spinand_table, + .nchips = ARRAY_SIZE(heyangtek_spinand_table), + .ops = &heyangtek_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c new file mode 100644 index 000000000..7dfcc34e9 --- /dev/null +++ b/drivers/mtd/nand/spi/macronix.c @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 Macronix + * + * Author: Boris Brezillon + */ + +#include +#include +#include +#include + +#define SPINAND_MFR_MACRONIX 0xC2 +#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) + +/* Bitflip theshold configuration register */ +#define REG_CFG_BFT 0x10 +#define CFG_BFT(x) FIELD_PREP(GENMASK(7, 4), (x)) + +struct macronix_priv { + bool cont_read; +}; + +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(false, 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)); + +#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) +{ + return -ERANGE; +} + +static int mx35lfxge4ab_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 2; + region->length = mtd->oobsize - 2; + + return 0; +} + +static const struct mtd_ooblayout_ops mx35lfxge4ab_ooblayout = { + .ecc = mx35lfxge4ab_ooblayout_ecc, + .free = mx35lfxge4ab_ooblayout_free, +}; + +static int macronix_get_eccsr(struct spinand_device *spinand, u8 *eccsr) +{ + struct macronix_priv *priv = spinand->priv; + struct spi_mem_op op = SPINAND_OP(spinand, macronix_read_eccsr, eccsr); + int ret; + + ret = spi_mem_exec_op(spinand->spimem, &op); + if (ret) + return ret; + + /* + * ECCSR exposes the number of bitflips for the last read page in bits [3:0]. + * Continuous read compatible chips also expose the maximum number of + * bitflips for the whole (continuous) read operation in bits [7:4]. + */ + if (!priv->cont_read) + *eccsr = MACRONIX_ECCSR_BF_LAST_PAGE(*eccsr); + else + *eccsr = MACRONIX_ECCSR_BF_ACCUMULATED_PAGES(*eccsr); + + return 0; +} + +static int macronix_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + struct nand_device *nand = spinand_to_nand(spinand); + u8 eccsr; + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + case STATUS_ECC_HAS_BITFLIPS: + /* + * Let's try to retrieve the real maximum number of bitflips + * in order to avoid forcing the wear-leveling layer to move + * data around if it's not necessary. + */ + if (macronix_get_eccsr(spinand, spinand->scratchbuf)) + return nanddev_get_ecc_conf(nand)->strength; + + eccsr = *spinand->scratchbuf; + if (WARN_ON(eccsr > nanddev_get_ecc_conf(nand)->strength || !eccsr)) + return nanddev_get_ecc_conf(nand)->strength; + + return eccsr; + default: + break; + } + + return -EINVAL; +} + +static int macronix_set_cont_read(struct spinand_device *spinand, bool enable) +{ + struct macronix_priv *priv = spinand->priv; + int ret; + + ret = spinand_upd_cfg(spinand, MACRONIX_CFG_CONT_READ, + enable ? MACRONIX_CFG_CONT_READ : 0); + if (ret) + return ret; + + priv->cont_read = 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), + 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_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status)), + SPINAND_INFO("MX35LF2GE4AB", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x22), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT | + SPINAND_HAS_PROG_PLANE_SELECT_BIT | + SPINAND_HAS_READ_PLANE_SELECT_BIT, + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, NULL)), + SPINAND_INFO("MX35LF2GE4AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x26, 0x03), + NAND_MEMORG(1, 2048, 64, 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_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status), + 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), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status), + 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), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + 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), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT | + SPINAND_HAS_PROG_PLANE_SELECT_BIT, + 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), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + 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), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT | + SPINAND_HAS_PROG_PLANE_SELECT_BIT, + 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), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + 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), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status)), + SPINAND_INFO("MX31UF1GE4BC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x9e), + NAND_MEMORG(1, 2048, 64, 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_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status)), + + SPINAND_INFO("MX35LF2G14AC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x20), + NAND_MEMORG(1, 2048, 64, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT | + SPINAND_HAS_PROG_PLANE_SELECT_BIT | + SPINAND_HAS_READ_PLANE_SELECT_BIT, + SPINAND_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status)), + SPINAND_INFO("MX35UF4G24AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xb5, 0x03), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT | + SPINAND_HAS_PROG_PLANE_SELECT_BIT, + SPINAND_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + 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), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + 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), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status), + 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), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT | + SPINAND_HAS_PROG_PLANE_SELECT_BIT | + SPINAND_HAS_READ_PLANE_SELECT_BIT, + SPINAND_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status)), + SPINAND_INFO("MX35UF2G24AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xa4, 0x03), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT | + SPINAND_HAS_PROG_PLANE_SELECT_BIT, + SPINAND_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + 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), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + 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), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status), + 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), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status), + SPINAND_CONT_READ(macronix_set_cont_read)), + SPINAND_INFO("MX35UF1G14AC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x90), + 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_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status)), + SPINAND_INFO("MX35UF1G24AD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x94, 0x03), + 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_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + 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), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status), + 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), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_HAS_QE_BIT, + SPINAND_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status), + SPINAND_CONT_READ(macronix_set_cont_read)), + SPINAND_INFO("MX31LF2GE4BC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x2e), + NAND_MEMORG(1, 2048, 64, 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_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status)), + SPINAND_INFO("MX3UF2GE4BC", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xae), + NAND_MEMORG(1, 2048, 64, 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_INFO_VENDOR_OPS(¯onix_ops), + SPINAND_ECCINFO(&mx35lfxge4ab_ooblayout, + macronix_ecc_get_status)), +}; + +static int macronix_spinand_init(struct spinand_device *spinand) +{ + struct macronix_priv *priv; + + priv = kzalloc_obj(*priv); + if (!priv) + return -ENOMEM; + + spinand->priv = priv; + + return 0; +} + +static void macronix_spinand_cleanup(struct spinand_device *spinand) +{ + kfree(spinand->priv); +} + +static const struct spinand_manufacturer_ops macronix_spinand_manuf_ops = { + .init = macronix_spinand_init, + .cleanup = macronix_spinand_cleanup, +}; + +const struct spinand_manufacturer macronix_spinand_manufacturer = { + .id = SPINAND_MFR_MACRONIX, + .name = "Macronix", + .chips = macronix_spinand_table, + .nchips = ARRAY_SIZE(macronix_spinand_table), + .ops = ¯onix_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/micron.c b/drivers/mtd/nand/spi/micron.c new file mode 100644 index 000000000..36f6cbbd7 --- /dev/null +++ b/drivers/mtd/nand/spi/micron.c @@ -0,0 +1,442 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2017 Micron Technology, Inc. + * + * Authors: + * Peter Pan + */ + +#include +#include +#include +#include +#include + +#define SPINAND_MFR_MICRON 0x2c + +#define MICRON_STATUS_ECC_MASK GENMASK(6, 4) +#define MICRON_STATUS_ECC_NO_BITFLIPS (0 << 4) +#define MICRON_STATUS_ECC_1TO3_BITFLIPS (1 << 4) +#define MICRON_STATUS_ECC_4TO6_BITFLIPS (3 << 4) +#define MICRON_STATUS_ECC_7TO8_BITFLIPS (5 << 4) + +#define MICRON_CFG_CR BIT(0) + +/* + * As per datasheet, die selection is done by the 6th bit of Die + * Select Register (Address 0xD0). + */ +#define MICRON_DIE_SELECT_REG 0xD0 + +#define MICRON_SELECT_DIE(x) ((x) << 6) + +#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_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_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_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_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_1S_1S_1S_OP(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(x1_update_cache_variants, + 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) +{ + if (section) + return -ERANGE; + + region->offset = mtd->oobsize / 2; + region->length = mtd->oobsize / 2; + + return 0; +} + +static int micron_8_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + /* Reserve 2 bytes for the BBM. */ + region->offset = 2; + region->length = (mtd->oobsize / 2) - 2; + + return 0; +} + +static const struct mtd_ooblayout_ops micron_8_ooblayout = { + .ecc = micron_8_ooblayout_ecc, + .free = micron_8_ooblayout_free, +}; + +static int micron_4_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + + if (section >= spinand->base.memorg.pagesize / + mtd->ecc_step_size) + return -ERANGE; + + region->offset = (section * 16) + 8; + region->length = 8; + + return 0; +} + +static int micron_4_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + struct spinand_device *spinand = mtd_to_spinand(mtd); + + if (section >= spinand->base.memorg.pagesize / + mtd->ecc_step_size) + return -ERANGE; + + if (section) { + region->offset = 16 * section; + region->length = 8; + } else { + /* section 0 has two bytes reserved for the BBM */ + region->offset = 2; + region->length = 6; + } + + return 0; +} + +static const struct mtd_ooblayout_ops micron_4_ooblayout = { + .ecc = micron_4_ooblayout_ecc, + .free = micron_4_ooblayout_free, +}; + +static int micron_select_target(struct spinand_device *spinand, + unsigned int target) +{ + struct spi_mem_op op = SPINAND_OP(spinand, set_feature, + MICRON_DIE_SELECT_REG, spinand->scratchbuf); + + if (target > 1) + return -EINVAL; + + *spinand->scratchbuf = MICRON_SELECT_DIE(target); + + return spi_mem_exec_op(spinand->spimem, &op); +} + +static int micron_8_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + switch (status & MICRON_STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + case MICRON_STATUS_ECC_1TO3_BITFLIPS: + return 3; + + case MICRON_STATUS_ECC_4TO6_BITFLIPS: + return 6; + + case MICRON_STATUS_ECC_7TO8_BITFLIPS: + return 8; + + default: + break; + } + + 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", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x24), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + 0, + SPINAND_ECCINFO(µn_8_ooblayout, + 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), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + 0, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status)), + /* M78A 1Gb 3.3V */ + SPINAND_INFO("MT29F1G01ABAFD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + 0, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status)), + /* M78A 1Gb 1.8V */ + SPINAND_INFO("MT29F1G01ABAFD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x15), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + 0, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status)), + /* M79A 4Gb 3.3V */ + SPINAND_INFO("MT29F4G01ADAGD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x36), + NAND_MEMORG(1, 2048, 128, 64, 2048, 80, 2, 1, 2), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + 0, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status), + SPINAND_SELECT_TARGET(micron_select_target)), + /* M70A 4Gb 3.3V */ + SPINAND_INFO("MT29F4G01ABAFD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x34), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_CR_FEAT_BIT, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status)), + /* M70A 4Gb 1.8V */ + SPINAND_INFO("MT29F4G01ABBFD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_CR_FEAT_BIT, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status)), + /* M70A 8Gb 3.3V */ + SPINAND_INFO("MT29F8G01ADAFD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x46), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 2), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_CR_FEAT_BIT, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status), + SPINAND_SELECT_TARGET(micron_select_target)), + /* M70A 8Gb 1.8V */ + SPINAND_INFO("MT29F8G01ADBFD", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x47), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 2), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&quadio_read_cache_variants, + &x4_write_cache_variants, + &x4_update_cache_variants), + SPINAND_HAS_CR_FEAT_BIT, + SPINAND_ECCINFO(µn_8_ooblayout, + micron_8_ecc_get_status), + SPINAND_SELECT_TARGET(micron_select_target)), + /* M69A 2Gb 3.3V */ + SPINAND_INFO("MT29F2G01AAAED", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x9F), + NAND_MEMORG(1, 2048, 64, 64, 2048, 80, 2, 1, 1), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&x4_read_cache_variants, + &x1_write_cache_variants, + &x1_update_cache_variants), + 0, + SPINAND_ECCINFO(µn_4_ooblayout, NULL)), +}; + +static int micron_spinand_init(struct spinand_device *spinand) +{ + /* + * M70A device series enable Continuous Read feature at Power-up, + * which is not supported. Disable this bit to avoid any possible + * failure. + */ + if (spinand->flags & SPINAND_HAS_CR_FEAT_BIT) + return spinand_upd_cfg(spinand, MICRON_CFG_CR, 0); + + return 0; +} + +static const struct spinand_manufacturer_ops micron_spinand_manuf_ops = { + .init = micron_spinand_init, +}; + +const struct spinand_manufacturer micron_spinand_manufacturer = { + .id = SPINAND_MFR_MICRON, + .name = "Micron", + .chips = micron_spinand_table, + .nchips = ARRAY_SIZE(micron_spinand_table), + .ops = µn_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/otp.c b/drivers/mtd/nand/spi/otp.c new file mode 100644 index 000000000..ce41bca86 --- /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 + */ + +#include +#include + +/** + * 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 new file mode 100644 index 000000000..73bd12427 --- /dev/null +++ b/drivers/mtd/nand/spi/paragon.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Jeff Kletsky + * + * Author: Jeff Kletsky + */ + +#include +#include +#include + + +#define SPINAND_MFR_PARAGON 0xa1 + + +#define PN26G0XA_STATUS_ECC_BITMASK (3 << 4) + +#define PN26G0XA_STATUS_ECC_NONE_DETECTED (0 << 4) +#define PN26G0XA_STATUS_ECC_1_7_CORRECTED (1 << 4) +#define PN26G0XA_STATUS_ECC_ERRORED (2 << 4) +#define PN26G0XA_STATUS_ECC_8_CORRECTED (3 << 4) + + +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 pn26g0xa_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = 6 + (15 * section); /* 4 BBM + 2 user bytes */ + region->length = 13; + + return 0; +} + +static int pn26g0xa_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 4) + return -ERANGE; + + if (section == 4) { + region->offset = 64; + region->length = 64; + } else { + region->offset = 4 + (15 * section); + region->length = 2; + } + + return 0; +} + +static int pn26g0xa_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + switch (status & PN26G0XA_STATUS_ECC_BITMASK) { + case PN26G0XA_STATUS_ECC_NONE_DETECTED: + return 0; + + case PN26G0XA_STATUS_ECC_1_7_CORRECTED: + return 7; /* Return upper limit by convention */ + + case PN26G0XA_STATUS_ECC_8_CORRECTED: + return 8; + + case PN26G0XA_STATUS_ECC_ERRORED: + return -EBADMSG; + + default: + break; + } + + return -EINVAL; +} + +static const struct mtd_ooblayout_ops pn26g0xa_ooblayout = { + .ecc = pn26g0xa_ooblayout_ecc, + .free = pn26g0xa_ooblayout_free, +}; + + +static const struct spinand_info paragon_spinand_table[] = { + SPINAND_INFO("PN26G01A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe1), + NAND_MEMORG(1, 2048, 128, 64, 1024, 21, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&pn26g0xa_ooblayout, + pn26g0xa_ecc_get_status)), + SPINAND_INFO("PN26G02A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xe2), + NAND_MEMORG(1, 2048, 128, 64, 2048, 41, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&pn26g0xa_ooblayout, + pn26g0xa_ecc_get_status)), +}; + +static const struct spinand_manufacturer_ops paragon_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer paragon_spinand_manufacturer = { + .id = SPINAND_MFR_PARAGON, + .name = "Paragon", + .chips = paragon_spinand_table, + .nchips = ARRAY_SIZE(paragon_spinand_table), + .ops = ¶gon_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/skyhigh.c b/drivers/mtd/nand/spi/skyhigh.c new file mode 100644 index 000000000..bf9ce163e --- /dev/null +++ b/drivers/mtd/nand/spi/skyhigh.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2024 SkyHigh Memory Limited + * + * Author: Takahiro Kuwano + * Co-Author: KR Kim + */ + +#include +#include +#include + +#define SPINAND_MFR_SKYHIGH 0x01 +#define SKYHIGH_STATUS_ECC_1TO2_BITFLIPS (1 << 4) +#define SKYHIGH_STATUS_ECC_3TO6_BITFLIPS (2 << 4) +#define SKYHIGH_STATUS_ECC_UNCOR_ERROR (3 << 4) +#define SKYHIGH_CONFIG_PROTECT_EN BIT(1) + +static SPINAND_OP_VARIANTS(read_cache_variants, + 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_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 skyhigh_spinand_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + /* ECC bytes are stored in hidden area. */ + return -ERANGE; +} + +static int skyhigh_spinand_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + /* ECC bytes are stored in hidden area. Reserve 2 bytes for the BBM. */ + region->offset = 2; + region->length = mtd->oobsize - 2; + + return 0; +} + +static const struct mtd_ooblayout_ops skyhigh_spinand_ooblayout = { + .ecc = skyhigh_spinand_ooblayout_ecc, + .free = skyhigh_spinand_ooblayout_free, +}; + +static int skyhigh_spinand_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case SKYHIGH_STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + case SKYHIGH_STATUS_ECC_1TO2_BITFLIPS: + return 2; + + case SKYHIGH_STATUS_ECC_3TO6_BITFLIPS: + return 6; + + default: + break; + } + + return -EINVAL; +} + +static const struct spinand_info skyhigh_spinand_table[] = { + SPINAND_INFO("S35ML01G301", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x15), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(6, 32), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_NO_RAW_ACCESS, + SPINAND_ECCINFO(&skyhigh_spinand_ooblayout, + skyhigh_spinand_ecc_get_status)), + SPINAND_INFO("S35ML01G300", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x14), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(6, 32), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_NO_RAW_ACCESS, + SPINAND_ECCINFO(&skyhigh_spinand_ooblayout, + skyhigh_spinand_ecc_get_status)), + SPINAND_INFO("S35ML02G300", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x25), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 2, 1, 1), + NAND_ECCREQ(6, 32), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_NO_RAW_ACCESS, + SPINAND_ECCINFO(&skyhigh_spinand_ooblayout, + skyhigh_spinand_ecc_get_status)), + SPINAND_INFO("S35ML04G300", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x35), + NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 2, 1, 1), + NAND_ECCREQ(6, 32), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + SPINAND_NO_RAW_ACCESS, + SPINAND_ECCINFO(&skyhigh_spinand_ooblayout, + skyhigh_spinand_ecc_get_status)), +}; + +static int skyhigh_spinand_init(struct spinand_device *spinand) +{ + /* + * Config_Protect_En (bit 1 in Block Lock register) must be set to 1 + * before writing other bits. Do it here before core unlocks all blocks + * by writing block protection bits. + */ + return spinand_write_reg_op(spinand, REG_BLOCK_LOCK, + SKYHIGH_CONFIG_PROTECT_EN); +} + +static const struct spinand_manufacturer_ops skyhigh_spinand_manuf_ops = { + .init = skyhigh_spinand_init, +}; + +const struct spinand_manufacturer skyhigh_spinand_manufacturer = { + .id = SPINAND_MFR_SKYHIGH, + .name = "SkyHigh", + .chips = skyhigh_spinand_table, + .nchips = ARRAY_SIZE(skyhigh_spinand_table), + .ops = &skyhigh_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/toshiba.c b/drivers/mtd/nand/spi/toshiba.c new file mode 100644 index 000000000..ef649162e --- /dev/null +++ b/drivers/mtd/nand/spi/toshiba.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018 exceet electronics GmbH + * Copyright (c) 2018 Kontron Electronics GmbH + * + * Author: Frieder Schrempf + */ + +#include +#include +#include + +/* Kioxia is new name of Toshiba memory. */ +#define SPINAND_MFR_TOSHIBA 0x98 +#define TOSH_STATUS_ECC_HAS_BITFLIPS_T (3 << 4) + +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_x4_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_x4_variants, + 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_1S_1S_1S_OP(true, 0, NULL, 0)); + +static SPINAND_OP_VARIANTS(update_cache_variants, + 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) +{ + if (section > 0) + return -ERANGE; + + region->offset = mtd->oobsize / 2; + region->length = mtd->oobsize / 2; + + return 0; +} + +static int tx58cxgxsxraix_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 0) + return -ERANGE; + + /* 2 bytes reserved for BBM */ + region->offset = 2; + region->length = (mtd->oobsize / 2) - 2; + + return 0; +} + +static const struct mtd_ooblayout_ops tx58cxgxsxraix_ooblayout = { + .ecc = tx58cxgxsxraix_ooblayout_ecc, + .free = tx58cxgxsxraix_ooblayout_free, +}; + +static int tx58cxgxsxraix_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_OP(spinand, get_feature, + 0x30, spinand->scratchbuf); + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + case STATUS_ECC_HAS_BITFLIPS: + case TOSH_STATUS_ECC_HAS_BITFLIPS_T: + /* + * Let's try to retrieve the real maximum number of bitflips + * in order to avoid forcing the wear-leveling layer to move + * data around if it's not necessary. + */ + if (spi_mem_exec_op(spinand->spimem, &op)) + return nanddev_get_ecc_conf(nand)->strength; + + mbf = *(spinand->scratchbuf) >> 4; + + if (WARN_ON(mbf > nanddev_get_ecc_conf(nand)->strength || !mbf)) + return nanddev_get_ecc_conf(nand)->strength; + + return mbf; + + default: + break; + } + + return -EINVAL; +} + +static const struct spinand_info toshiba_spinand_table[] = { + /* 3.3V 1Gb (1st generation) */ + SPINAND_INFO("TC58CVG0S3HRAIG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xC2), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 3.3V 2Gb (1st generation) */ + SPINAND_INFO("TC58CVG1S3HRAIG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xCB), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 3.3V 4Gb (1st generation) */ + SPINAND_INFO("TC58CVG2S0HRAIG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xCD), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 1Gb (1st generation) */ + SPINAND_INFO("TC58CYG0S3HRAIG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xB2), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 2Gb (1st generation) */ + SPINAND_INFO("TC58CYG1S3HRAIG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xBB), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 4Gb (1st generation) */ + SPINAND_INFO("TC58CYG2S0HRAIG", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xBD), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + + /* + * 2nd generation serial nand has HOLD_D which is equivalent to + * QE_BIT. + */ + /* 3.3V 1Gb (2nd generation) */ + SPINAND_INFO("TC58CVG0S3HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE2), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 3.3V 2Gb (2nd generation) */ + SPINAND_INFO("TC58CVG1S3HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xEB), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 3.3V 4Gb (2nd generation) */ + SPINAND_INFO("TC58CVG2S0HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xED), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 3.3V 8Gb (2nd generation) */ + SPINAND_INFO("TH58CVG3S0HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xE4), + NAND_MEMORG(1, 4096, 256, 64, 4096, 80, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 1Gb (2nd generation) */ + SPINAND_INFO("TC58CYG0S3HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xD2), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 2Gb (2nd generation) */ + SPINAND_INFO("TC58CYG1S3HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xDB), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 4Gb (2nd generation) */ + SPINAND_INFO("TC58CYG2S0HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xDD), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 8Gb (2nd generation) */ + SPINAND_INFO("TH58CYG3S0HRAIJ", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xD4), + NAND_MEMORG(1, 4096, 256, 64, 4096, 80, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 1Gb (1st generation) */ + SPINAND_INFO("TC58NYG0S3HBAI4", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xA1), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 4Gb (1st generation) */ + SPINAND_INFO("TH58NYG2S3HBAI4", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xAC), + NAND_MEMORG(1, 2048, 128, 64, 4096, 80, 1, 2, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), + /* 1.8V 8Gb (1st generation) */ + SPINAND_INFO("TH58NYG3S0HBAI6", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xA3), + NAND_MEMORG(1, 4096, 256, 64, 4096, 80, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_x4_variants, + &update_cache_x4_variants), + SPINAND_HAS_QE_BIT, + SPINAND_ECCINFO(&tx58cxgxsxraix_ooblayout, + tx58cxgxsxraix_ecc_get_status)), +}; + +static const struct spinand_manufacturer_ops toshiba_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer toshiba_spinand_manufacturer = { + .id = SPINAND_MFR_TOSHIBA, + .name = "Toshiba", + .chips = toshiba_spinand_table, + .nchips = ARRAY_SIZE(toshiba_spinand_table), + .ops = &toshiba_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c new file mode 100644 index 000000000..9b78c1e6c --- /dev/null +++ b/drivers/mtd/nand/spi/winbond.c @@ -0,0 +1,814 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017 exceet electronics GmbH + * + * Authors: + * Frieder Schrempf + * Boris Brezillon + */ + +#include +#include +#include +#include +#include + +#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_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_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)); + +#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) +{ + if (section > 3) + return -ERANGE; + + region->offset = (16 * section) + 8; + region->length = 8; + + return 0; +} + +static int w25m02gv_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = (16 * section) + 2; + region->length = 6; + + return 0; +} + +static const struct mtd_ooblayout_ops w25m02gv_ooblayout = { + .ecc = w25m02gv_ooblayout_ecc, + .free = w25m02gv_ooblayout_free, +}; + +static int w25m02gv_select_target(struct spinand_device *spinand, + unsigned int target) +{ + struct spi_mem_op op = SPINAND_OP(spinand, winbond_select_target, + spinand->scratchbuf); + + *spinand->scratchbuf = target; + return spi_mem_exec_op(spinand->spimem, &op); +} + +static int w25n01kv_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = 64 + (8 * section); + region->length = 7; + + return 0; +} + +static int w25n02kv_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = 64 + (16 * section); + region->length = 13; + + return 0; +} + +static int w25n02kv_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section > 3) + return -ERANGE; + + region->offset = (16 * section) + 2; + region->length = 14; + + return 0; +} + +static const struct mtd_ooblayout_ops w25n01kv_ooblayout = { + .ecc = w25n01kv_ooblayout_ecc, + .free = w25n02kv_ooblayout_free, +}; + +static const struct mtd_ooblayout_ops w25n02kv_ooblayout = { + .ecc = w25n02kv_ooblayout_ecc, + .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_OP(spinand, get_feature, + 0x30, spinand->scratchbuf); + + switch (status & STATUS_ECC_MASK) { + case STATUS_ECC_NO_BITFLIPS: + return 0; + + case STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + + case STATUS_ECC_HAS_BITFLIPS: + case W25N04KV_STATUS_ECC_5_8_BITFLIPS: + /* + * Let's try to retrieve the real maximum number of bitflips + * in order to avoid forcing the wear-leveling layer to move + * data around if it's not necessary. + */ + if (spi_mem_exec_op(spinand->spimem, &op)) + return nanddev_get_ecc_conf(nand)->strength; + + mbf = *(spinand->scratchbuf) >> 4; + + if (WARN_ON(mbf > nanddev_get_ecc_conf(nand)->strength || !mbf)) + return nanddev_get_ecc_conf(nand)->strength; + + return mbf; + + default: + break; + } + + 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 */ + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 0x20), + NAND_MEMORG(1, 2048, 64, 64, 512, 10, 1, 1, 1), + NAND_ECCREQ(1, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&w25m02gv_ooblayout, NULL)), + /* 1G-bit densities */ + SPINAND_INFO("W25N01GV", /* 3.3V */ + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xaa, 0x21), + 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(&w25m02gv_ooblayout, NULL)), + SPINAND_INFO("W25N01GW", /* 1.8V */ + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 0x21), + 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(&w25m02gv_ooblayout, NULL)), + SPINAND_INFO("W25N01JW", /* high-speed 1.8V */ + 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_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), + NAND_ECCREQ(4, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &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), + NAND_MEMORG(1, 2048, 64, 64, 1024, 20, 1, 1, 2), + NAND_ECCREQ(1, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &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_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), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)), + SPINAND_INFO("W25N02KW", /* 1.8V */ + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 0x22), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&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), + NAND_MEMORG(1, 2048, 128, 64, 4096, 40, 2, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&w25n02kv_ooblayout, w25n02kv_ecc_get_status)), + SPINAND_INFO("W25N04KW", /* 1.8V */ + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0xba, 0x23), + NAND_MEMORG(1, 2048, 128, 64, 4096, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &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) +{ + struct nand_device *nand = spinand_to_nand(spinand); + unsigned int i; + + /* + * Make sure all dies are in buffer read mode and not continuous read + * mode. + */ + for (i = 0; i < nand->memorg.ntargets; i++) { + spinand_select_target(spinand, i); + spinand_upd_cfg(spinand, WINBOND_CFG_BUF_READ, + WINBOND_CFG_BUF_READ); + } + + return 0; +} + +static const struct spinand_manufacturer_ops winbond_spinand_manuf_ops = { + .init = winbond_spinand_init, +}; + +const struct spinand_manufacturer winbond_spinand_manufacturer = { + .id = SPINAND_MFR_WINBOND, + .name = "Winbond", + .chips = winbond_spinand_table, + .nchips = ARRAY_SIZE(winbond_spinand_table), + .ops = &winbond_spinand_manuf_ops, +}; diff --git a/drivers/mtd/nand/spi/xtx.c b/drivers/mtd/nand/spi/xtx.c new file mode 100644 index 000000000..5915b37b4 --- /dev/null +++ b/drivers/mtd/nand/spi/xtx.c @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: + * Felix Matouschek + */ + +#include +#include +#include +#include + +#define SPINAND_MFR_XTX 0x0B + +#define XT26G0XA_STATUS_ECC_MASK GENMASK(5, 2) +#define XT26G0XA_STATUS_ECC_NO_DETECTED (0 << 2) +#define XT26G0XA_STATUS_ECC_8_CORRECTED (3 << 4) +#define XT26G0XA_STATUS_ECC_UNCOR_ERROR (2 << 4) + +#define XT26XXXD_STATUS_ECC3_ECC2_MASK GENMASK(7, 6) +#define XT26XXXD_STATUS_ECC_NO_DETECTED (0) +#define XT26XXXD_STATUS_ECC_1_7_CORRECTED (1) +#define XT26XXXD_STATUS_ECC_8_CORRECTED (3) +#define XT26XXXD_STATUS_ECC_UNCOR_ERROR (2) + +static SPINAND_OP_VARIANTS(read_cache_variants, + 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_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 xt26g0xa_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 48; + region->length = 16; + + return 0; +} + +static int xt26g0xa_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 1; + region->length = 47; + + return 0; +} + +static const struct mtd_ooblayout_ops xt26g0xa_ooblayout = { + .ecc = xt26g0xa_ooblayout_ecc, + .free = xt26g0xa_ooblayout_free, +}; + +static int xt26g0xa_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + status = status & XT26G0XA_STATUS_ECC_MASK; + + switch (status) { + case XT26G0XA_STATUS_ECC_NO_DETECTED: + return 0; + case XT26G0XA_STATUS_ECC_8_CORRECTED: + return 8; + case XT26G0XA_STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + default: + break; + } + + /* At this point values greater than (2 << 4) are invalid */ + if (status > XT26G0XA_STATUS_ECC_UNCOR_ERROR) + return -EINVAL; + + /* (1 << 2) through (7 << 2) are 1-7 corrected errors */ + return status >> 2; +} + +static int xt26xxxd_ooblayout_ecc(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = mtd->oobsize / 2; + region->length = mtd->oobsize / 2; + + return 0; +} + +static int xt26xxxd_ooblayout_free(struct mtd_info *mtd, int section, + struct mtd_oob_region *region) +{ + if (section) + return -ERANGE; + + region->offset = 2; + region->length = mtd->oobsize / 2 - 2; + + return 0; +} + +static const struct mtd_ooblayout_ops xt26xxxd_ooblayout = { + .ecc = xt26xxxd_ooblayout_ecc, + .free = xt26xxxd_ooblayout_free, +}; + +static int xt26xxxd_ecc_get_status(struct spinand_device *spinand, + u8 status) +{ + switch (FIELD_GET(STATUS_ECC_MASK, status)) { + case XT26XXXD_STATUS_ECC_NO_DETECTED: + return 0; + case XT26XXXD_STATUS_ECC_UNCOR_ERROR: + return -EBADMSG; + case XT26XXXD_STATUS_ECC_1_7_CORRECTED: + return 4 + FIELD_GET(XT26XXXD_STATUS_ECC3_ECC2_MASK, status); + case XT26XXXD_STATUS_ECC_8_CORRECTED: + return 8; + default: + break; + } + + return -EINVAL; +} +static const struct spinand_info xtx_spinand_table[] = { + SPINAND_INFO("XT26G01A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE1), + NAND_MEMORG(1, 2048, 64, 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(&xt26g0xa_ooblayout, + xt26g0xa_ecc_get_status)), + SPINAND_INFO("XT26G02A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE2), + NAND_MEMORG(1, 2048, 64, 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(&xt26g0xa_ooblayout, + xt26g0xa_ecc_get_status)), + SPINAND_INFO("XT26G04A", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0xE3), + NAND_MEMORG(1, 2048, 64, 128, 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(&xt26g0xa_ooblayout, + xt26g0xa_ecc_get_status)), + SPINAND_INFO("XT26G01D", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x31), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&xt26xxxd_ooblayout, + xt26xxxd_ecc_get_status)), + SPINAND_INFO("XT26G11D", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x34), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&xt26xxxd_ooblayout, + xt26xxxd_ecc_get_status)), + SPINAND_INFO("XT26Q01D", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x51), + NAND_MEMORG(1, 2048, 128, 64, 1024, 20, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&xt26xxxd_ooblayout, + xt26xxxd_ecc_get_status)), + SPINAND_INFO("XT26G02D", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x32), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&xt26xxxd_ooblayout, + xt26xxxd_ecc_get_status)), + SPINAND_INFO("XT26G12D", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x35), + NAND_MEMORG(1, 2048, 128, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&xt26xxxd_ooblayout, + xt26xxxd_ecc_get_status)), + SPINAND_INFO("XT26Q02D", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 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), + 0, + SPINAND_ECCINFO(&xt26xxxd_ooblayout, + xt26xxxd_ecc_get_status)), + SPINAND_INFO("XT26G04D", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x33), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&xt26xxxd_ooblayout, + xt26xxxd_ecc_get_status)), + SPINAND_INFO("XT26Q04D", + SPINAND_ID(SPINAND_READID_METHOD_OPCODE_ADDR, 0x53), + NAND_MEMORG(1, 4096, 256, 64, 2048, 40, 1, 1, 1), + NAND_ECCREQ(8, 512), + SPINAND_INFO_OP_VARIANTS(&read_cache_variants, + &write_cache_variants, + &update_cache_variants), + 0, + SPINAND_ECCINFO(&xt26xxxd_ooblayout, + xt26xxxd_ecc_get_status)), +}; + +static const struct spinand_manufacturer_ops xtx_spinand_manuf_ops = { +}; + +const struct spinand_manufacturer xtx_spinand_manufacturer = { + .id = SPINAND_MFR_XTX, + .name = "XTX", + .chips = xtx_spinand_table, + .nchips = ARRAY_SIZE(xtx_spinand_table), + .ops = &xtx_spinand_manuf_ops, +}; -- cgit v1.3.1