From 8d836581f9b1f57ceaa2b47654754ef1260b410b Mon Sep 17 00:00:00 2001 From: Niklas Cassel Date: Wed, 16 Sep 2026 15:00:32 +0200 Subject: ata: libata-scsi: fix ata_dsm_trim_pages() kernel-doc make htmldocs fails with: Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301: ERROR: Unexpected indentation. [docutils] The Return: section of ata_dsm_trim_pages() ends its first sentence with a colon and continues with an indented bullet list. reStructuredText requires a blank line before an indented block, so docutils chokes on the list. Simply adding the missing blank line does not work either: Return: is a kernel-doc "special section", which is terminated by the first blank line, so the bullet list would end up in the Description section, detached from the sentence introducing it. Spell the two bounds out as prose instead, so that the Return: section stays self-contained. Documentation-only change. Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND") Reported-by: Thomas Huth Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/ Reviewed-by: Damien Le Moal Reviewed-by: Thomas Huth Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org Signed-off-by: Niklas Cassel --- drivers/mtd/Kconfig | 234 + drivers/mtd/Makefile | 34 + drivers/mtd/chips/Kconfig | 243 + drivers/mtd/chips/Makefile | 16 + drivers/mtd/chips/cfi_cmdset_0001.c | 2711 ++++++++ drivers/mtd/chips/cfi_cmdset_0002.c | 3112 +++++++++ drivers/mtd/chips/cfi_cmdset_0020.c | 1400 +++++ drivers/mtd/chips/cfi_probe.c | 463 ++ drivers/mtd/chips/cfi_util.c | 445 ++ drivers/mtd/chips/chipreg.c | 102 + drivers/mtd/chips/fwh_lock.h | 108 + drivers/mtd/chips/gen_probe.c | 266 + drivers/mtd/chips/jedec_probe.c | 2316 +++++++ drivers/mtd/chips/map_absent.c | 113 + drivers/mtd/chips/map_ram.c | 163 + drivers/mtd/chips/map_rom.c | 133 + drivers/mtd/devices/Kconfig | 227 + drivers/mtd/devices/Makefile | 24 + drivers/mtd/devices/bcm47xxsflash.c | 380 ++ drivers/mtd/devices/bcm47xxsflash.h | 81 + drivers/mtd/devices/block2mtd.c | 532 ++ drivers/mtd/devices/docg3.c | 2084 ++++++ drivers/mtd/devices/docg3.h | 343 + drivers/mtd/devices/mchp23k256.c | 259 + drivers/mtd/devices/mchp48l640.c | 407 ++ drivers/mtd/devices/ms02-nv.c | 306 + drivers/mtd/devices/ms02-nv.h | 101 + drivers/mtd/devices/mtd_dataflash.c | 946 +++ drivers/mtd/devices/mtd_intel_dg.c | 883 +++ drivers/mtd/devices/mtdram.c | 187 + drivers/mtd/devices/phram.c | 445 ++ drivers/mtd/devices/pmc551.c | 847 +++ drivers/mtd/devices/powernv_flash.c | 298 + drivers/mtd/devices/serial_flash_cmds.h | 49 + drivers/mtd/devices/slram.c | 354 ++ drivers/mtd/devices/spear_smi.c | 1102 ++++ drivers/mtd/devices/sst25l.c | 421 ++ drivers/mtd/devices/st_spi_fsm.c | 2144 +++++++ drivers/mtd/ftl.c | 1060 ++++ drivers/mtd/hyperbus/Kconfig | 32 + drivers/mtd/hyperbus/Makefile | 5 + drivers/mtd/hyperbus/hbmc-am654.c | 275 + drivers/mtd/hyperbus/hyperbus-core.c | 140 + drivers/mtd/hyperbus/rpc-if.c | 184 + drivers/mtd/inftlcore.c | 945 +++ drivers/mtd/inftlmount.c | 776 +++ drivers/mtd/lpddr/Kconfig | 30 + drivers/mtd/lpddr/Makefile | 8 + drivers/mtd/lpddr/lpddr2_nvm.c | 496 ++ drivers/mtd/lpddr/lpddr_cmds.c | 759 +++ drivers/mtd/lpddr/qinfo_probe.c | 234 + drivers/mtd/maps/Kconfig | 322 + drivers/mtd/maps/Makefile | 37 + drivers/mtd/maps/amd76xrom.c | 346 + drivers/mtd/maps/cfi_flagadm.c | 137 + drivers/mtd/maps/ck804xrom.c | 386 ++ drivers/mtd/maps/dc21285.c | 231 + drivers/mtd/maps/esb2rom.c | 446 ++ drivers/mtd/maps/ichxrom.c | 376 ++ drivers/mtd/maps/l440gx.c | 167 + drivers/mtd/maps/lantiq-flash.c | 198 + drivers/mtd/maps/map_funcs.c | 45 + drivers/mtd/maps/pci.c | 324 + drivers/mtd/maps/pcmciamtd.c | 754 +++ drivers/mtd/maps/physmap-core.c | 686 ++ drivers/mtd/maps/physmap-gemini.c | 200 + drivers/mtd/maps/physmap-gemini.h | 17 + drivers/mtd/maps/physmap-ixp4xx.c | 132 + drivers/mtd/maps/physmap-ixp4xx.h | 18 + drivers/mtd/maps/physmap-versatile.c | 242 + drivers/mtd/maps/physmap-versatile.h | 17 + drivers/mtd/maps/pismo.c | 284 + drivers/mtd/maps/plat-ram.c | 218 + drivers/mtd/maps/pxa2xx-flash.c | 139 + drivers/mtd/maps/sa1100-flash.c | 307 + drivers/mtd/maps/sbc_gxx.c | 224 + drivers/mtd/maps/scb2_flash.c | 234 + drivers/mtd/maps/scx200_docflash.c | 219 + drivers/mtd/maps/solutionengine.c | 95 + drivers/mtd/maps/sun_uflash.c | 155 + drivers/mtd/maps/tsunami_flash.c | 109 + drivers/mtd/maps/uclinux.c | 118 + drivers/mtd/maps/vmu-flash.c | 816 +++ drivers/mtd/mtd_blkdevs.c | 542 ++ drivers/mtd/mtd_virt_concat.c | 352 ++ drivers/mtd/mtdblock.c | 361 ++ drivers/mtd/mtdblock_ro.c | 80 + drivers/mtd/mtdchar.c | 1426 +++++ drivers/mtd/mtdconcat.c | 897 +++ drivers/mtd/mtdcore.c | 2675 ++++++++ drivers/mtd/mtdcore.h | 30 + drivers/mtd/mtdoops.c | 458 ++ drivers/mtd/mtdpart.c | 758 +++ drivers/mtd/mtdpstore.c | 579 ++ drivers/mtd/mtdsuper.c | 179 + drivers/mtd/mtdswap.c | 1495 +++++ 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 + drivers/mtd/nftlcore.c | 803 +++ drivers/mtd/nftlmount.c | 782 +++ drivers/mtd/parsers/Kconfig | 217 + drivers/mtd/parsers/Makefile | 17 + drivers/mtd/parsers/afs.c | 402 ++ drivers/mtd/parsers/bcm47xxpart.c | 316 + drivers/mtd/parsers/bcm63xxpart.c | 171 + drivers/mtd/parsers/brcm_u-boot.c | 85 + drivers/mtd/parsers/cmdlinepart.c | 428 ++ drivers/mtd/parsers/ofpart_bcm4908.c | 67 + drivers/mtd/parsers/ofpart_bcm4908.h | 9 + drivers/mtd/parsers/ofpart_core.c | 312 + drivers/mtd/parsers/ofpart_linksys_ns.c | 50 + drivers/mtd/parsers/ofpart_linksys_ns.h | 11 + drivers/mtd/parsers/parser_imagetag.c | 221 + drivers/mtd/parsers/parser_trx.c | 135 + drivers/mtd/parsers/qcomsmempart.c | 197 + drivers/mtd/parsers/redboot.c | 330 + drivers/mtd/parsers/scpart.c | 248 + drivers/mtd/parsers/sharpslpart.c | 398 ++ drivers/mtd/parsers/tplink_safeloader.c | 154 + drivers/mtd/rfd_ftl.c | 829 +++ drivers/mtd/sm_ftl.c | 1288 ++++ drivers/mtd/sm_ftl.h | 87 + drivers/mtd/spi-nor/Kconfig | 72 + drivers/mtd/spi-nor/Makefile | 20 + drivers/mtd/spi-nor/atmel.c | 252 + drivers/mtd/spi-nor/controllers/Kconfig | 18 + drivers/mtd/spi-nor/controllers/Makefile | 3 + drivers/mtd/spi-nor/controllers/hisi-sfc.c | 492 ++ drivers/mtd/spi-nor/controllers/nxp-spifi.c | 459 ++ drivers/mtd/spi-nor/core.c | 3971 ++++++++++++ drivers/mtd/spi-nor/core.h | 735 +++ drivers/mtd/spi-nor/debugfs.c | 325 + drivers/mtd/spi-nor/eon.c | 72 + drivers/mtd/spi-nor/esmt.c | 37 + drivers/mtd/spi-nor/everspin.c | 52 + drivers/mtd/spi-nor/gigadevice.c | 92 + drivers/mtd/spi-nor/intel.c | 34 + drivers/mtd/spi-nor/issi.c | 146 + drivers/mtd/spi-nor/macronix.c | 339 + drivers/mtd/spi-nor/micron-st.c | 672 ++ drivers/mtd/spi-nor/otp.c | 508 ++ drivers/mtd/spi-nor/sfdp.c | 1614 +++++ drivers/mtd/spi-nor/sfdp.h | 144 + drivers/mtd/spi-nor/spansion.c | 1176 ++++ drivers/mtd/spi-nor/sst.c | 285 + drivers/mtd/spi-nor/swp.c | 653 ++ drivers/mtd/spi-nor/sysfs.c | 113 + drivers/mtd/spi-nor/winbond.c | 505 ++ drivers/mtd/spi-nor/xmc.c | 30 + drivers/mtd/ssfdc.c | 455 ++ drivers/mtd/tests/Makefile | 19 + drivers/mtd/tests/mtd_nandecctest.c | 329 + drivers/mtd/tests/mtd_test.c | 118 + drivers/mtd/tests/mtd_test.h | 24 + drivers/mtd/tests/nandbiterrs.c | 419 ++ drivers/mtd/tests/oobtest.c | 733 +++ drivers/mtd/tests/pagetest.c | 461 ++ drivers/mtd/tests/readtest.c | 215 + drivers/mtd/tests/speedtest.c | 418 ++ drivers/mtd/tests/stresstest.c | 232 + drivers/mtd/tests/subpagetest.c | 437 ++ drivers/mtd/tests/torturetest.c | 480 ++ drivers/mtd/ubi/Kconfig | 129 + drivers/mtd/ubi/Makefile | 10 + drivers/mtd/ubi/attach.c | 1925 ++++++ drivers/mtd/ubi/block.c | 678 ++ drivers/mtd/ubi/build.c | 1632 +++++ drivers/mtd/ubi/cdev.c | 1177 ++++ drivers/mtd/ubi/debug.c | 697 +++ drivers/mtd/ubi/debug.h | 387 ++ drivers/mtd/ubi/eba.c | 1709 +++++ drivers/mtd/ubi/fastmap-wl.c | 561 ++ drivers/mtd/ubi/fastmap.c | 1648 +++++ drivers/mtd/ubi/gluebi.c | 500 ++ drivers/mtd/ubi/io.c | 1487 +++++ drivers/mtd/ubi/kapi.c | 848 +++ drivers/mtd/ubi/misc.c | 191 + drivers/mtd/ubi/nvmem.c | 191 + drivers/mtd/ubi/ubi-media.h | 503 ++ drivers/mtd/ubi/ubi.h | 1266 ++++ drivers/mtd/ubi/upd.c | 420 ++ drivers/mtd/ubi/vmt.c | 848 +++ drivers/mtd/ubi/vtbl.c | 877 +++ drivers/mtd/ubi/wl.c | 2160 +++++++ drivers/mtd/ubi/wl.h | 33 + 323 files changed, 192604 insertions(+) create mode 100644 drivers/mtd/Kconfig create mode 100644 drivers/mtd/Makefile create mode 100644 drivers/mtd/chips/Kconfig create mode 100644 drivers/mtd/chips/Makefile create mode 100644 drivers/mtd/chips/cfi_cmdset_0001.c create mode 100644 drivers/mtd/chips/cfi_cmdset_0002.c create mode 100644 drivers/mtd/chips/cfi_cmdset_0020.c create mode 100644 drivers/mtd/chips/cfi_probe.c create mode 100644 drivers/mtd/chips/cfi_util.c create mode 100644 drivers/mtd/chips/chipreg.c create mode 100644 drivers/mtd/chips/fwh_lock.h create mode 100644 drivers/mtd/chips/gen_probe.c create mode 100644 drivers/mtd/chips/jedec_probe.c create mode 100644 drivers/mtd/chips/map_absent.c create mode 100644 drivers/mtd/chips/map_ram.c create mode 100644 drivers/mtd/chips/map_rom.c create mode 100644 drivers/mtd/devices/Kconfig create mode 100644 drivers/mtd/devices/Makefile create mode 100644 drivers/mtd/devices/bcm47xxsflash.c create mode 100644 drivers/mtd/devices/bcm47xxsflash.h create mode 100644 drivers/mtd/devices/block2mtd.c create mode 100644 drivers/mtd/devices/docg3.c create mode 100644 drivers/mtd/devices/docg3.h create mode 100644 drivers/mtd/devices/mchp23k256.c create mode 100644 drivers/mtd/devices/mchp48l640.c create mode 100644 drivers/mtd/devices/ms02-nv.c create mode 100644 drivers/mtd/devices/ms02-nv.h create mode 100644 drivers/mtd/devices/mtd_dataflash.c create mode 100644 drivers/mtd/devices/mtd_intel_dg.c create mode 100644 drivers/mtd/devices/mtdram.c create mode 100644 drivers/mtd/devices/phram.c create mode 100644 drivers/mtd/devices/pmc551.c create mode 100644 drivers/mtd/devices/powernv_flash.c create mode 100644 drivers/mtd/devices/serial_flash_cmds.h create mode 100644 drivers/mtd/devices/slram.c create mode 100644 drivers/mtd/devices/spear_smi.c create mode 100644 drivers/mtd/devices/sst25l.c create mode 100644 drivers/mtd/devices/st_spi_fsm.c create mode 100644 drivers/mtd/ftl.c create mode 100644 drivers/mtd/hyperbus/Kconfig create mode 100644 drivers/mtd/hyperbus/Makefile create mode 100644 drivers/mtd/hyperbus/hbmc-am654.c create mode 100644 drivers/mtd/hyperbus/hyperbus-core.c create mode 100644 drivers/mtd/hyperbus/rpc-if.c create mode 100644 drivers/mtd/inftlcore.c create mode 100644 drivers/mtd/inftlmount.c create mode 100644 drivers/mtd/lpddr/Kconfig create mode 100644 drivers/mtd/lpddr/Makefile create mode 100644 drivers/mtd/lpddr/lpddr2_nvm.c create mode 100644 drivers/mtd/lpddr/lpddr_cmds.c create mode 100644 drivers/mtd/lpddr/qinfo_probe.c create mode 100644 drivers/mtd/maps/Kconfig create mode 100644 drivers/mtd/maps/Makefile create mode 100644 drivers/mtd/maps/amd76xrom.c create mode 100644 drivers/mtd/maps/cfi_flagadm.c create mode 100644 drivers/mtd/maps/ck804xrom.c create mode 100644 drivers/mtd/maps/dc21285.c create mode 100644 drivers/mtd/maps/esb2rom.c create mode 100644 drivers/mtd/maps/ichxrom.c create mode 100644 drivers/mtd/maps/l440gx.c create mode 100644 drivers/mtd/maps/lantiq-flash.c create mode 100644 drivers/mtd/maps/map_funcs.c create mode 100644 drivers/mtd/maps/pci.c create mode 100644 drivers/mtd/maps/pcmciamtd.c create mode 100644 drivers/mtd/maps/physmap-core.c create mode 100644 drivers/mtd/maps/physmap-gemini.c create mode 100644 drivers/mtd/maps/physmap-gemini.h create mode 100644 drivers/mtd/maps/physmap-ixp4xx.c create mode 100644 drivers/mtd/maps/physmap-ixp4xx.h create mode 100644 drivers/mtd/maps/physmap-versatile.c create mode 100644 drivers/mtd/maps/physmap-versatile.h create mode 100644 drivers/mtd/maps/pismo.c create mode 100644 drivers/mtd/maps/plat-ram.c create mode 100644 drivers/mtd/maps/pxa2xx-flash.c create mode 100644 drivers/mtd/maps/sa1100-flash.c create mode 100644 drivers/mtd/maps/sbc_gxx.c create mode 100644 drivers/mtd/maps/scb2_flash.c create mode 100644 drivers/mtd/maps/scx200_docflash.c create mode 100644 drivers/mtd/maps/solutionengine.c create mode 100644 drivers/mtd/maps/sun_uflash.c create mode 100644 drivers/mtd/maps/tsunami_flash.c create mode 100644 drivers/mtd/maps/uclinux.c create mode 100644 drivers/mtd/maps/vmu-flash.c create mode 100644 drivers/mtd/mtd_blkdevs.c create mode 100644 drivers/mtd/mtd_virt_concat.c create mode 100644 drivers/mtd/mtdblock.c create mode 100644 drivers/mtd/mtdblock_ro.c create mode 100644 drivers/mtd/mtdchar.c create mode 100644 drivers/mtd/mtdconcat.c create mode 100644 drivers/mtd/mtdcore.c create mode 100644 drivers/mtd/mtdcore.h create mode 100644 drivers/mtd/mtdoops.c create mode 100644 drivers/mtd/mtdpart.c create mode 100644 drivers/mtd/mtdpstore.c create mode 100644 drivers/mtd/mtdsuper.c create mode 100644 drivers/mtd/mtdswap.c 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 create mode 100644 drivers/mtd/nftlcore.c create mode 100644 drivers/mtd/nftlmount.c create mode 100644 drivers/mtd/parsers/Kconfig create mode 100644 drivers/mtd/parsers/Makefile create mode 100644 drivers/mtd/parsers/afs.c create mode 100644 drivers/mtd/parsers/bcm47xxpart.c create mode 100644 drivers/mtd/parsers/bcm63xxpart.c create mode 100644 drivers/mtd/parsers/brcm_u-boot.c create mode 100644 drivers/mtd/parsers/cmdlinepart.c create mode 100644 drivers/mtd/parsers/ofpart_bcm4908.c create mode 100644 drivers/mtd/parsers/ofpart_bcm4908.h create mode 100644 drivers/mtd/parsers/ofpart_core.c create mode 100644 drivers/mtd/parsers/ofpart_linksys_ns.c create mode 100644 drivers/mtd/parsers/ofpart_linksys_ns.h create mode 100644 drivers/mtd/parsers/parser_imagetag.c create mode 100644 drivers/mtd/parsers/parser_trx.c create mode 100644 drivers/mtd/parsers/qcomsmempart.c create mode 100644 drivers/mtd/parsers/redboot.c create mode 100644 drivers/mtd/parsers/scpart.c create mode 100644 drivers/mtd/parsers/sharpslpart.c create mode 100644 drivers/mtd/parsers/tplink_safeloader.c create mode 100644 drivers/mtd/rfd_ftl.c create mode 100644 drivers/mtd/sm_ftl.c create mode 100644 drivers/mtd/sm_ftl.h create mode 100644 drivers/mtd/spi-nor/Kconfig create mode 100644 drivers/mtd/spi-nor/Makefile create mode 100644 drivers/mtd/spi-nor/atmel.c create mode 100644 drivers/mtd/spi-nor/controllers/Kconfig create mode 100644 drivers/mtd/spi-nor/controllers/Makefile create mode 100644 drivers/mtd/spi-nor/controllers/hisi-sfc.c create mode 100644 drivers/mtd/spi-nor/controllers/nxp-spifi.c create mode 100644 drivers/mtd/spi-nor/core.c create mode 100644 drivers/mtd/spi-nor/core.h create mode 100644 drivers/mtd/spi-nor/debugfs.c create mode 100644 drivers/mtd/spi-nor/eon.c create mode 100644 drivers/mtd/spi-nor/esmt.c create mode 100644 drivers/mtd/spi-nor/everspin.c create mode 100644 drivers/mtd/spi-nor/gigadevice.c create mode 100644 drivers/mtd/spi-nor/intel.c create mode 100644 drivers/mtd/spi-nor/issi.c create mode 100644 drivers/mtd/spi-nor/macronix.c create mode 100644 drivers/mtd/spi-nor/micron-st.c create mode 100644 drivers/mtd/spi-nor/otp.c create mode 100644 drivers/mtd/spi-nor/sfdp.c create mode 100644 drivers/mtd/spi-nor/sfdp.h create mode 100644 drivers/mtd/spi-nor/spansion.c create mode 100644 drivers/mtd/spi-nor/sst.c create mode 100644 drivers/mtd/spi-nor/swp.c create mode 100644 drivers/mtd/spi-nor/sysfs.c create mode 100644 drivers/mtd/spi-nor/winbond.c create mode 100644 drivers/mtd/spi-nor/xmc.c create mode 100644 drivers/mtd/ssfdc.c create mode 100644 drivers/mtd/tests/Makefile create mode 100644 drivers/mtd/tests/mtd_nandecctest.c create mode 100644 drivers/mtd/tests/mtd_test.c create mode 100644 drivers/mtd/tests/mtd_test.h create mode 100644 drivers/mtd/tests/nandbiterrs.c create mode 100644 drivers/mtd/tests/oobtest.c create mode 100644 drivers/mtd/tests/pagetest.c create mode 100644 drivers/mtd/tests/readtest.c create mode 100644 drivers/mtd/tests/speedtest.c create mode 100644 drivers/mtd/tests/stresstest.c create mode 100644 drivers/mtd/tests/subpagetest.c create mode 100644 drivers/mtd/tests/torturetest.c create mode 100644 drivers/mtd/ubi/Kconfig create mode 100644 drivers/mtd/ubi/Makefile create mode 100644 drivers/mtd/ubi/attach.c create mode 100644 drivers/mtd/ubi/block.c create mode 100644 drivers/mtd/ubi/build.c create mode 100644 drivers/mtd/ubi/cdev.c create mode 100644 drivers/mtd/ubi/debug.c create mode 100644 drivers/mtd/ubi/debug.h create mode 100644 drivers/mtd/ubi/eba.c create mode 100644 drivers/mtd/ubi/fastmap-wl.c create mode 100644 drivers/mtd/ubi/fastmap.c create mode 100644 drivers/mtd/ubi/gluebi.c create mode 100644 drivers/mtd/ubi/io.c create mode 100644 drivers/mtd/ubi/kapi.c create mode 100644 drivers/mtd/ubi/misc.c create mode 100644 drivers/mtd/ubi/nvmem.c create mode 100644 drivers/mtd/ubi/ubi-media.h create mode 100644 drivers/mtd/ubi/ubi.h create mode 100644 drivers/mtd/ubi/upd.c create mode 100644 drivers/mtd/ubi/vmt.c create mode 100644 drivers/mtd/ubi/vtbl.c create mode 100644 drivers/mtd/ubi/wl.c create mode 100644 drivers/mtd/ubi/wl.h (limited to 'drivers/mtd') diff --git a/drivers/mtd/Kconfig b/drivers/mtd/Kconfig new file mode 100644 index 000000000..0421c6208 --- /dev/null +++ b/drivers/mtd/Kconfig @@ -0,0 +1,234 @@ +menuconfig MTD + tristate "Memory Technology Device (MTD) support" + imply NVMEM + help + Memory Technology Devices are flash, RAM and similar chips, often + used for solid state file systems on embedded devices. This option + will provide the generic support for MTD drivers to register + themselves with the kernel and for potential users of MTD devices + to enumerate the devices which are present and obtain a handle on + them. It will also allow you to select individual drivers for + particular hardware and users of MTD devices. If unsure, say N. + +if MTD + +config MTD_TESTS + tristate "MTD tests support (DANGEROUS)" + depends on m + help + This option includes various MTD tests into compilation. The tests + should normally be compiled as kernel modules. The modules perform + various checks and verifications when loaded. + + WARNING: some of the tests will ERASE entire MTD device which they + test. Do not use these tests unless you really know what you do. + +menu "Partition parsers" +source "drivers/mtd/parsers/Kconfig" +endmenu + +comment "User Modules And Translation Layers" + +# +# MTD block device support is select'ed if needed +# +config MTD_BLKDEVS + tristate + +config MTD_BLOCK + tristate "Caching block device access to MTD devices" + depends on BLOCK + select MTD_BLKDEVS + help + Although most flash chips have an erase size too large to be useful + as block devices, it is possible to use MTD devices which are based + on RAM chips in this manner. This block device is a user of MTD + devices performing that function. + + Note that mounting a JFFS2 filesystem doesn't require using mtdblock. + It's possible to mount a rootfs using the MTD device on the "root=" + bootargs as "root=mtd2" or "root=mtd:name_of_device". + + Later, it may be extended to perform read/erase/modify/write cycles + on flash chips to emulate a smaller block size. Needless to say, + this is very unsafe, but could be useful for file systems which are + almost never written to. + + You do not need this option for use with the DiskOnChip devices. For + those, enable NFTL support (CONFIG_NFTL) instead. + +config MTD_BLOCK_RO + tristate "Readonly block device access to MTD devices" + depends on MTD_BLOCK!=y && BLOCK + select MTD_BLKDEVS + help + This allows you to mount read-only file systems (such as cramfs) + from an MTD device, without the overhead (and danger) of the caching + driver. + + You do not need this option for use with the DiskOnChip devices. For + those, enable NFTL support (CONFIG_NFTL) instead. + +comment "Note that in some cases UBI block is preferred. See MTD_UBI_BLOCK." + depends on MTD_BLOCK || MTD_BLOCK_RO + +config FTL + tristate "FTL (Flash Translation Layer) support" + depends on BLOCK + select MTD_BLKDEVS + help + This provides support for the original Flash Translation Layer which + is part of the PCMCIA specification. It uses a kind of pseudo- + file system on a flash device to emulate a block device with + 512-byte sectors, on top of which you put a 'normal' file system. + + You may find that the algorithms used in this code are patented + unless you live in the Free World where software patents aren't + legal - in the USA you are only permitted to use this on PCMCIA + hardware, although under the terms of the GPL you're obviously + permitted to copy, modify and distribute the code as you wish. Just + not use it. + +config NFTL + tristate "NFTL (NAND Flash Translation Layer) support" + depends on BLOCK + select MTD_BLKDEVS + help + This provides support for the NAND Flash Translation Layer which is + used on M-Systems' DiskOnChip devices. It uses a kind of pseudo- + file system on a flash device to emulate a block device with + 512-byte sectors, on top of which you put a 'normal' file system. + + You may find that the algorithms used in this code are patented + unless you live in the Free World where software patents aren't + legal - in the USA you are only permitted to use this on DiskOnChip + hardware, although under the terms of the GPL you're obviously + permitted to copy, modify and distribute the code as you wish. Just + not use it. + +config NFTL_RW + bool "Write support for NFTL" + depends on NFTL + help + Support for writing to the NAND Flash Translation Layer, as used + on the DiskOnChip. + +config INFTL + tristate "INFTL (Inverse NAND Flash Translation Layer) support" + depends on BLOCK + select MTD_BLKDEVS + help + This provides support for the Inverse NAND Flash Translation + Layer which is used on M-Systems' newer DiskOnChip devices. It + uses a kind of pseudo-file system on a flash device to emulate + a block device with 512-byte sectors, on top of which you put + a 'normal' file system. + + You may find that the algorithms used in this code are patented + unless you live in the Free World where software patents aren't + legal - in the USA you are only permitted to use this on DiskOnChip + hardware, although under the terms of the GPL you're obviously + permitted to copy, modify and distribute the code as you wish. Just + not use it. + +config RFD_FTL + tristate "Resident Flash Disk (Flash Translation Layer) support" + depends on BLOCK + select MTD_BLKDEVS + help + This provides support for the flash translation layer known + as the Resident Flash Disk (RFD), as used by the Embedded BIOS + of General Software. There is a blurb at: + + http://www.gensw.com/pages/prod/bios/rfd.htm + +config SSFDC + tristate "NAND SSFDC (SmartMedia) read only translation layer" + depends on BLOCK + select MTD_BLKDEVS + help + This enables read only access to SmartMedia formatted NAND + flash. You can mount it with FAT file system. + +config SM_FTL + tristate "SmartMedia/xD new translation layer" + depends on BLOCK + select MTD_BLKDEVS + select MTD_NAND_CORE + select MTD_NAND_ECC_SW_HAMMING + help + This enables EXPERIMENTAL R/W support for SmartMedia/xD + FTL (Flash translation layer). + Write support is only lightly tested, therefore this driver + isn't recommended to use with valuable data (anyway if you have + valuable data, do backups regardless of software/hardware you + use, because you never know what will eat your data...) + If you only need R/O access, you can use older R/O driver + (CONFIG_SSFDC) + +config MTD_OOPS + tristate "Log panic/oops to an MTD buffer" + help + This enables panic and oops messages to be logged to a circular + buffer in a flash partition where it can be read back at some + later point. + +config MTD_PSTORE + tristate "Log panic/oops to an MTD buffer based on pstore" + depends on PSTORE_BLK + help + This enables panic and oops messages to be logged to a circular + buffer in a flash partition where it can be read back as files after + mounting pstore filesystem. + + If unsure, say N. + +config MTD_SWAP + tristate "Swap on MTD device support" + depends on MTD && SWAP + select MTD_BLKDEVS + help + Provides volatile block device driver on top of mtd partition + suitable for swapping. The mapping of written blocks is not saved. + The driver provides wear leveling by storing erase counter into the + OOB. + +config MTD_PARTITIONED_MASTER + bool "Retain master device when partitioned" + default n + depends on MTD + help + For historical reasons, by default, either a master is present or + several partitions are present, but not both. The concern was that + data listed in multiple partitions was dangerous; however, SCSI does + this and it is frequently useful for applications. This config option + leaves the master in even if the device is partitioned. It also makes + the parent of the partition device be the master device, rather than + what lies behind the master. + +config MTD_VIRT_CONCAT + bool "Virtual concatenated MTD devices" + depends on MTD_PARTITIONED_MASTER + help + The driver enables the creation of virtual MTD device by + concatenating multiple physical MTD devices into a single + entity. This allows for the creation of partitions larger than + the individual physical chips, extending across chip boundaries. + +source "drivers/mtd/chips/Kconfig" + +source "drivers/mtd/maps/Kconfig" + +source "drivers/mtd/devices/Kconfig" + +source "drivers/mtd/nand/Kconfig" + +source "drivers/mtd/lpddr/Kconfig" + +source "drivers/mtd/spi-nor/Kconfig" + +source "drivers/mtd/ubi/Kconfig" + +source "drivers/mtd/hyperbus/Kconfig" + +endif # MTD diff --git a/drivers/mtd/Makefile b/drivers/mtd/Makefile new file mode 100644 index 000000000..7b6dd53e8 --- /dev/null +++ b/drivers/mtd/Makefile @@ -0,0 +1,34 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the memory technology device drivers. +# + +# Core functionality. +obj-$(CONFIG_MTD) += mtd.o +mtd-y := mtdcore.o mtdsuper.o mtdconcat.o mtdpart.o mtdchar.o +mtd-$(CONFIG_MTD_VIRT_CONCAT) += mtd_virt_concat.o + +obj-y += parsers/ + +# 'Users' - code which presents functionality to userspace. +obj-$(CONFIG_MTD_BLKDEVS) += mtd_blkdevs.o +obj-$(CONFIG_MTD_BLOCK) += mtdblock.o +obj-$(CONFIG_MTD_BLOCK_RO) += mtdblock_ro.o +obj-$(CONFIG_FTL) += ftl.o +obj-$(CONFIG_NFTL) += nftl.o +obj-$(CONFIG_INFTL) += inftl.o +obj-$(CONFIG_RFD_FTL) += rfd_ftl.o +obj-$(CONFIG_SSFDC) += ssfdc.o +obj-$(CONFIG_SM_FTL) += sm_ftl.o +obj-$(CONFIG_MTD_OOPS) += mtdoops.o +obj-$(CONFIG_MTD_PSTORE) += mtdpstore.o +obj-$(CONFIG_MTD_SWAP) += mtdswap.o + +nftl-objs := nftlcore.o nftlmount.o +inftl-objs := inftlcore.o inftlmount.o + +obj-y += chips/ lpddr/ maps/ devices/ nand/ tests/ + +obj-$(CONFIG_MTD_SPI_NOR) += spi-nor/ +obj-$(CONFIG_MTD_UBI) += ubi/ +obj-$(CONFIG_MTD_HYPERBUS) += hyperbus/ diff --git a/drivers/mtd/chips/Kconfig b/drivers/mtd/chips/Kconfig new file mode 100644 index 000000000..19726ebd9 --- /dev/null +++ b/drivers/mtd/chips/Kconfig @@ -0,0 +1,243 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "RAM/ROM/Flash chip drivers" + depends on MTD!=n + +config MTD_CFI + tristate "Detect flash chips by Common Flash Interface (CFI) probe" + select MTD_GEN_PROBE + select MTD_CFI_UTIL + help + The Common Flash Interface specification was developed by Intel, + AMD and other flash manufactures that provides a universal method + for probing the capabilities of flash devices. If you wish to + support any device that is CFI-compliant, you need to enable this + option. Visit + for more information on CFI. + +config MTD_JEDECPROBE + tristate "Detect non-CFI AMD/JEDEC-compatible flash chips" + select MTD_GEN_PROBE + select MTD_CFI_UTIL + help + This option enables JEDEC-style probing of flash chips which are not + compatible with the Common Flash Interface, but will use the common + CFI-targeted flash drivers for any chips which are identified which + are in fact compatible in all but the probe method. This actually + covers most AMD/Fujitsu-compatible chips and also non-CFI + Intel chips. + +config MTD_GEN_PROBE + tristate + +config MTD_CFI_ADV_OPTIONS + bool "Flash chip driver advanced configuration options" + depends on MTD_GEN_PROBE + help + If you need to specify a specific endianness for access to flash + chips, or if you wish to reduce the size of the kernel by including + support for only specific arrangements of flash chips, say 'Y'. This + option does not directly affect the code, but will enable other + configuration options which allow you to do so. + + If unsure, say 'N'. + +choice + prompt "Flash cmd/query data swapping" + depends on MTD_CFI_ADV_OPTIONS + default MTD_CFI_NOSWAP + help + This option defines the way in which the CPU attempts to arrange + data bits when writing the 'magic' commands to the chips. Saying + 'NO', which is the default when CONFIG_MTD_CFI_ADV_OPTIONS isn't + enabled, means that the CPU will not do any swapping; the chips + are expected to be wired to the CPU in 'host-endian' form. + Specific arrangements are possible with the BIG_ENDIAN_BYTE and + LITTLE_ENDIAN_BYTE, if the bytes are reversed. + +config MTD_CFI_NOSWAP + depends on !ARCH_IXP4XX || CPU_BIG_ENDIAN + bool "NO" + +config MTD_CFI_BE_BYTE_SWAP + bool "BIG_ENDIAN_BYTE" + +config MTD_CFI_LE_BYTE_SWAP + depends on !ARCH_IXP4XX + bool "LITTLE_ENDIAN_BYTE" + +endchoice + +config MTD_CFI_GEOMETRY + bool "Specific CFI Flash geometry selection" + depends on MTD_CFI_ADV_OPTIONS + select MTD_MAP_BANK_WIDTH_1 if !(MTD_MAP_BANK_WIDTH_2 || \ + MTD_MAP_BANK_WIDTH_4 || MTD_MAP_BANK_WIDTH_8 || \ + MTD_MAP_BANK_WIDTH_16 || MTD_MAP_BANK_WIDTH_32) + select MTD_CFI_I1 if !(MTD_CFI_I2 || MTD_CFI_I4 || MTD_CFI_I8) + help + This option does not affect the code directly, but will enable + some other configuration options which would allow you to reduce + the size of the kernel by including support for only certain + arrangements of CFI chips. If unsure, say 'N' and all options + which are supported by the current code will be enabled. + +config MTD_MAP_BANK_WIDTH_1 + bool "Support 8-bit buswidth" if MTD_CFI_GEOMETRY + default y + help + If you wish to support CFI devices on a physical bus which is + 8 bits wide, say 'Y'. + +config MTD_MAP_BANK_WIDTH_2 + bool "Support 16-bit buswidth" if MTD_CFI_GEOMETRY + default y + help + If you wish to support CFI devices on a physical bus which is + 16 bits wide, say 'Y'. + +config MTD_MAP_BANK_WIDTH_4 + bool "Support 32-bit buswidth" if MTD_CFI_GEOMETRY + default y + help + If you wish to support CFI devices on a physical bus which is + 32 bits wide, say 'Y'. + +config MTD_MAP_BANK_WIDTH_8 + bool "Support 64-bit buswidth" if MTD_CFI_GEOMETRY + default n + help + If you wish to support CFI devices on a physical bus which is + 64 bits wide, say 'Y'. + +config MTD_MAP_BANK_WIDTH_16 + bool "Support 128-bit buswidth" if MTD_CFI_GEOMETRY + default n + help + If you wish to support CFI devices on a physical bus which is + 128 bits wide, say 'Y'. + +config MTD_MAP_BANK_WIDTH_32 + bool "Support 256-bit buswidth" if MTD_CFI_GEOMETRY + select MTD_COMPLEX_MAPPINGS if HAS_IOMEM + default n + help + If you wish to support CFI devices on a physical bus which is + 256 bits wide, say 'Y'. + +config MTD_CFI_I1 + bool "Support 1-chip flash interleave" if MTD_CFI_GEOMETRY + default y + help + If your flash chips are not interleaved - i.e. you only have one + flash chip addressed by each bus cycle, then say 'Y'. + +config MTD_CFI_I2 + bool "Support 2-chip flash interleave" if MTD_CFI_GEOMETRY + default y + help + If your flash chips are interleaved in pairs - i.e. you have two + flash chips addressed by each bus cycle, then say 'Y'. + +config MTD_CFI_I4 + bool "Support 4-chip flash interleave" if MTD_CFI_GEOMETRY + default n + help + If your flash chips are interleaved in fours - i.e. you have four + flash chips addressed by each bus cycle, then say 'Y'. + +config MTD_CFI_I8 + bool "Support 8-chip flash interleave" if MTD_CFI_GEOMETRY + default n + help + If your flash chips are interleaved in eights - i.e. you have eight + flash chips addressed by each bus cycle, then say 'Y'. + +config MTD_OTP + bool "Protection Registers aka one-time programmable (OTP) bits" + depends on MTD_CFI_ADV_OPTIONS + default n + help + This enables support for reading, writing and locking so called + "Protection Registers" present on some flash chips. + A subset of them are pre-programmed at the factory with a + unique set of values. The rest is user-programmable. + + The user-programmable Protection Registers contain one-time + programmable (OTP) bits; when programmed, register bits cannot be + erased. Each Protection Register can be accessed multiple times to + program individual bits, as long as the register remains unlocked. + + Each Protection Register has an associated Lock Register bit. When a + Lock Register bit is programmed, the associated Protection Register + can only be read; it can no longer be programmed. Additionally, + because the Lock Register bits themselves are OTP, when programmed, + Lock Register bits cannot be erased. Therefore, when a Protection + Register is locked, it cannot be unlocked. + + This feature should therefore be used with extreme care. Any mistake + in the programming of OTP bits will waste them. + +config MTD_CFI_INTELEXT + tristate "Support for CFI command set 0001 (Intel/Sharp chips)" + depends on MTD_GEN_PROBE + select MTD_CFI_UTIL + help + The Common Flash Interface defines a number of different command + sets which a CFI-compliant chip may claim to implement. This code + provides support for command set 0001, used on Intel StrataFlash + and other parts. + +config MTD_CFI_AMDSTD + tristate "Support for CFI command set 0002 (AMD/Fujitsu/Spansion chips)" + depends on MTD_GEN_PROBE + select MTD_CFI_UTIL + help + The Common Flash Interface defines a number of different command + sets which a CFI-compliant chip may claim to implement. This code + provides support for command set 0002, used on chips including + the AMD Am29LV320. + +config MTD_CFI_STAA + tristate "Support for CFI command set 0020 (ST (Advanced Architecture) chips)" + depends on MTD_GEN_PROBE + select MTD_CFI_UTIL + help + The Common Flash Interface defines a number of different command + sets which a CFI-compliant chip may claim to implement. This code + provides support for command set 0020. + +config MTD_CFI_UTIL + tristate + +config MTD_RAM + tristate "Support for RAM chips in bus mapping" + help + This option enables basic support for RAM chips accessed through + a bus mapping driver. + +config MTD_ROM + tristate "Support for ROM chips in bus mapping" + help + This option enables basic support for ROM chips accessed through + a bus mapping driver. + +config MTD_ABSENT + tristate "Support for absent chips in bus mapping" + help + This option enables support for a dummy probing driver used to + allocated placeholder MTD devices on systems that have socketed + or removable media. Use of this driver as a fallback chip probe + preserves the expected registration order of MTD device nodes on + the system regardless of media presence. Device nodes created + with this driver will return -ENODEV upon access. + +config MTD_XIP + bool "XIP aware MTD support" + depends on !SMP && (MTD_CFI_INTELEXT || MTD_CFI_AMDSTD) && ARCH_MTD_XIP + default y if XIP_KERNEL + help + This allows MTD support to work with flash memory which is also + used for XIP purposes. If you're not sure what this is all about + then say N. + +endmenu diff --git a/drivers/mtd/chips/Makefile b/drivers/mtd/chips/Makefile new file mode 100644 index 000000000..1f4e84f1c --- /dev/null +++ b/drivers/mtd/chips/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# linux/drivers/chips/Makefile +# + +obj-$(CONFIG_MTD) += chipreg.o +obj-$(CONFIG_MTD_CFI) += cfi_probe.o +obj-$(CONFIG_MTD_CFI_UTIL) += cfi_util.o +obj-$(CONFIG_MTD_CFI_STAA) += cfi_cmdset_0020.o +obj-$(CONFIG_MTD_CFI_AMDSTD) += cfi_cmdset_0002.o +obj-$(CONFIG_MTD_CFI_INTELEXT) += cfi_cmdset_0001.o +obj-$(CONFIG_MTD_GEN_PROBE) += gen_probe.o +obj-$(CONFIG_MTD_JEDECPROBE) += jedec_probe.o +obj-$(CONFIG_MTD_RAM) += map_ram.o +obj-$(CONFIG_MTD_ROM) += map_rom.o +obj-$(CONFIG_MTD_ABSENT) += map_absent.o diff --git a/drivers/mtd/chips/cfi_cmdset_0001.c b/drivers/mtd/chips/cfi_cmdset_0001.c new file mode 100644 index 000000000..6049ba2d6 --- /dev/null +++ b/drivers/mtd/chips/cfi_cmdset_0001.c @@ -0,0 +1,2711 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common Flash Interface support: + * Intel Extended Vendor Command Set (ID 0x0001) + * + * (C) 2000 Red Hat. + * + * + * 10/10/2000 Nicolas Pitre + * - completely revamped method functions so they are aware and + * independent of the flash geometry (buswidth, interleave, etc.) + * - scalability vs code size is completely set at compile-time + * (see include/linux/mtd/cfi.h for selection) + * - optimized write buffer method + * 02/05/2002 Christopher Hoover / + * - reworked lock/unlock/erase support for var size flash + * 21/03/2007 Rodolfo Giometti + * - auto unlock sectors on resume for auto locking flash on power up + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* #define CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE */ +/* #define CMDSET0001_DISABLE_WRITE_SUSPEND */ + +// debugging, turns off buffer write mode if set to 1 +#define FORCE_WORD_WRITE 0 + +/* Intel chips */ +#define I82802AB 0x00ad +#define I82802AC 0x00ac +#define PF38F4476 0x881c +#define M28F00AP30 0x8963 +/* STMicroelectronics chips */ +#define M50LPW080 0x002F +#define M50FLW080A 0x0080 +#define M50FLW080B 0x0081 +/* Atmel chips */ +#define AT49BV640D 0x02de +#define AT49BV640DT 0x02db +/* Sharp chips */ +#define LH28F640BFHE_PTTL90 0x00b0 +#define LH28F640BFHE_PBTL90 0x00b1 +#define LH28F640BFHE_PTTL70A 0x00b2 +#define LH28F640BFHE_PBTL70A 0x00b3 + +static int cfi_intelext_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); +static int cfi_intelext_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *); +static int cfi_intelext_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *); +static int cfi_intelext_writev(struct mtd_info *, const struct kvec *, unsigned long, loff_t, size_t *); +static int cfi_intelext_erase_varsize(struct mtd_info *, struct erase_info *); +static void cfi_intelext_sync (struct mtd_info *); +static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int cfi_intelext_is_locked(struct mtd_info *mtd, loff_t ofs, + uint64_t len); +#ifdef CONFIG_MTD_OTP +static int cfi_intelext_read_fact_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *); +static int cfi_intelext_read_user_prot_reg (struct mtd_info *, loff_t, size_t, size_t *, u_char *); +static int cfi_intelext_write_user_prot_reg(struct mtd_info *, loff_t, size_t, + size_t *, const u_char *); +static int cfi_intelext_lock_user_prot_reg (struct mtd_info *, loff_t, size_t); +static int cfi_intelext_get_fact_prot_info(struct mtd_info *, size_t, + size_t *, struct otp_info *); +static int cfi_intelext_get_user_prot_info(struct mtd_info *, size_t, + size_t *, struct otp_info *); +#endif +static int cfi_intelext_suspend (struct mtd_info *); +static void cfi_intelext_resume (struct mtd_info *); +static int cfi_intelext_reboot (struct notifier_block *, unsigned long, void *); + +static void cfi_intelext_destroy(struct mtd_info *); + +struct mtd_info *cfi_cmdset_0001(struct map_info *, int); + +static struct mtd_info *cfi_intelext_setup (struct mtd_info *); +static int cfi_intelext_partition_fixup(struct mtd_info *, struct cfi_private **); + +static int cfi_intelext_point (struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, void **virt, resource_size_t *phys); +static int cfi_intelext_unpoint(struct mtd_info *mtd, loff_t from, size_t len); + +static int chip_ready (struct map_info *map, struct flchip *chip, unsigned long adr, int mode); +static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode); +static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr); +#include "fwh_lock.h" + + + +/* + * *********** SETUP AND PROBE BITS *********** + */ + +static struct mtd_chip_driver cfi_intelext_chipdrv = { + .probe = NULL, /* Not usable directly */ + .destroy = cfi_intelext_destroy, + .name = "cfi_cmdset_0001", + .module = THIS_MODULE +}; + +/* #define DEBUG_LOCK_BITS */ +/* #define DEBUG_CFI_FEATURES */ + +#ifdef DEBUG_CFI_FEATURES +static void cfi_tell_features(struct cfi_pri_intelext *extp) +{ + int i; + printk(" Extended Query version %c.%c\n", extp->MajorVersion, extp->MinorVersion); + printk(" Feature/Command Support: %4.4X\n", extp->FeatureSupport); + printk(" - Chip Erase: %s\n", extp->FeatureSupport&1?"supported":"unsupported"); + printk(" - Suspend Erase: %s\n", extp->FeatureSupport&2?"supported":"unsupported"); + printk(" - Suspend Program: %s\n", extp->FeatureSupport&4?"supported":"unsupported"); + printk(" - Legacy Lock/Unlock: %s\n", extp->FeatureSupport&8?"supported":"unsupported"); + printk(" - Queued Erase: %s\n", extp->FeatureSupport&16?"supported":"unsupported"); + printk(" - Instant block lock: %s\n", extp->FeatureSupport&32?"supported":"unsupported"); + printk(" - Protection Bits: %s\n", extp->FeatureSupport&64?"supported":"unsupported"); + printk(" - Page-mode read: %s\n", extp->FeatureSupport&128?"supported":"unsupported"); + printk(" - Synchronous read: %s\n", extp->FeatureSupport&256?"supported":"unsupported"); + printk(" - Simultaneous operations: %s\n", extp->FeatureSupport&512?"supported":"unsupported"); + printk(" - Extended Flash Array: %s\n", extp->FeatureSupport&1024?"supported":"unsupported"); + for (i=11; i<32; i++) { + if (extp->FeatureSupport & (1<SuspendCmdSupport); + printk(" - Program after Erase Suspend: %s\n", extp->SuspendCmdSupport&1?"supported":"unsupported"); + for (i=1; i<8; i++) { + if (extp->SuspendCmdSupport & (1<BlkStatusRegMask); + printk(" - Lock Bit Active: %s\n", extp->BlkStatusRegMask&1?"yes":"no"); + printk(" - Lock-Down Bit Active: %s\n", extp->BlkStatusRegMask&2?"yes":"no"); + for (i=2; i<3; i++) { + if (extp->BlkStatusRegMask & (1<BlkStatusRegMask&16?"yes":"no"); + printk(" - EFA Lock-Down Bit: %s\n", extp->BlkStatusRegMask&32?"yes":"no"); + for (i=6; i<16; i++) { + if (extp->BlkStatusRegMask & (1<VccOptimal >> 4, extp->VccOptimal & 0xf); + if (extp->VppOptimal) + printk(" Vpp Programming Supply Optimum Program/Erase Voltage: %d.%d V\n", + extp->VppOptimal >> 4, extp->VppOptimal & 0xf); +} +#endif + +/* Atmel chips don't use the same PRI format as Intel chips */ +static void fixup_convert_atmel_pri(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *extp = cfi->cmdset_priv; + struct cfi_pri_atmel atmel_pri; + uint32_t features = 0; + + /* Reverse byteswapping */ + extp->FeatureSupport = cpu_to_le32(extp->FeatureSupport); + extp->BlkStatusRegMask = cpu_to_le16(extp->BlkStatusRegMask); + extp->ProtRegAddr = cpu_to_le16(extp->ProtRegAddr); + + memcpy(&atmel_pri, extp, sizeof(atmel_pri)); + memset((char *)extp + 5, 0, sizeof(*extp) - 5); + + printk(KERN_ERR "atmel Features: %02x\n", atmel_pri.Features); + + if (atmel_pri.Features & 0x01) /* chip erase supported */ + features |= (1<<0); + if (atmel_pri.Features & 0x02) /* erase suspend supported */ + features |= (1<<1); + if (atmel_pri.Features & 0x04) /* program suspend supported */ + features |= (1<<2); + if (atmel_pri.Features & 0x08) /* simultaneous operations supported */ + features |= (1<<9); + if (atmel_pri.Features & 0x20) /* page mode read supported */ + features |= (1<<7); + if (atmel_pri.Features & 0x40) /* queued erase supported */ + features |= (1<<4); + if (atmel_pri.Features & 0x80) /* Protection bits supported */ + features |= (1<<6); + + extp->FeatureSupport = features; + + /* burst write mode not supported */ + cfi->cfiq->BufWriteTimeoutTyp = 0; + cfi->cfiq->BufWriteTimeoutMax = 0; +} + +static void fixup_at49bv640dx_lock(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *cfip = cfi->cmdset_priv; + + cfip->FeatureSupport |= (1 << 5); + mtd->flags |= MTD_POWERUP_LOCK; +} + +#ifdef CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE +/* Some Intel Strata Flash prior to FPO revision C has bugs in this area */ +static void fixup_intel_strataflash(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *extp = cfi->cmdset_priv; + + printk(KERN_WARNING "cfi_cmdset_0001: Suspend " + "erase on write disabled.\n"); + extp->SuspendCmdSupport &= ~1; +} +#endif + +#ifdef CMDSET0001_DISABLE_WRITE_SUSPEND +static void fixup_no_write_suspend(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *cfip = cfi->cmdset_priv; + + if (cfip && (cfip->FeatureSupport&4)) { + cfip->FeatureSupport &= ~4; + printk(KERN_WARNING "cfi_cmdset_0001: write suspend disabled\n"); + } +} +#endif + +static void fixup_st_m28w320ct(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + cfi->cfiq->BufWriteTimeoutTyp = 0; /* Not supported */ + cfi->cfiq->BufWriteTimeoutMax = 0; /* Not supported */ +} + +static void fixup_st_m28w320cb(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + /* Note this is done after the region info is endian swapped */ + cfi->cfiq->EraseRegionInfo[1] = + (cfi->cfiq->EraseRegionInfo[1] & 0xffff0000) | 0x3e; +}; + +static int is_LH28F640BF(struct cfi_private *cfi) +{ + /* Sharp LH28F640BF Family */ + if (cfi->mfr == CFI_MFR_SHARP && ( + cfi->id == LH28F640BFHE_PTTL90 || cfi->id == LH28F640BFHE_PBTL90 || + cfi->id == LH28F640BFHE_PTTL70A || cfi->id == LH28F640BFHE_PBTL70A)) + return 1; + return 0; +} + +static void fixup_LH28F640BF(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *extp = cfi->cmdset_priv; + + /* Reset the Partition Configuration Register on LH28F640BF + * to a single partition (PCR = 0x000): PCR is embedded into A0-A15. */ + if (is_LH28F640BF(cfi)) { + printk(KERN_INFO "Reset Partition Config. Register: 1 Partition of 4 planes\n"); + map_write(map, CMD(0x60), 0); + map_write(map, CMD(0x04), 0); + + /* We have set one single partition thus + * Simultaneous Operations are not allowed */ + printk(KERN_INFO "cfi_cmdset_0001: Simultaneous Operations disabled\n"); + extp->FeatureSupport &= ~512; + } +} + +static void fixup_use_point(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + if (!mtd->_point && map_is_linear(map)) { + mtd->_point = cfi_intelext_point; + mtd->_unpoint = cfi_intelext_unpoint; + } +} + +static void fixup_use_write_buffers(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + if (cfi->cfiq->BufWriteTimeoutTyp) { + printk(KERN_INFO "Using buffer write method\n" ); + mtd->_write = cfi_intelext_write_buffers; + mtd->_writev = cfi_intelext_writev; + } +} + +/* + * Some chips power-up with all sectors locked by default. + */ +static void fixup_unlock_powerup_lock(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *cfip = cfi->cmdset_priv; + + if (cfip->FeatureSupport&32) { + printk(KERN_INFO "Using auto-unlock on power-up/resume\n" ); + mtd->flags |= MTD_POWERUP_LOCK; + } +} + +static struct cfi_fixup cfi_fixup_table[] = { + { CFI_MFR_ATMEL, CFI_ID_ANY, fixup_convert_atmel_pri }, + { CFI_MFR_ATMEL, AT49BV640D, fixup_at49bv640dx_lock }, + { CFI_MFR_ATMEL, AT49BV640DT, fixup_at49bv640dx_lock }, +#ifdef CMDSET0001_DISABLE_ERASE_SUSPEND_ON_WRITE + { CFI_MFR_ANY, CFI_ID_ANY, fixup_intel_strataflash }, +#endif +#ifdef CMDSET0001_DISABLE_WRITE_SUSPEND + { CFI_MFR_ANY, CFI_ID_ANY, fixup_no_write_suspend }, +#endif +#if !FORCE_WORD_WRITE + { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers }, +#endif + { CFI_MFR_ST, 0x00ba, /* M28W320CT */ fixup_st_m28w320ct }, + { CFI_MFR_ST, 0x00bb, /* M28W320CB */ fixup_st_m28w320cb }, + { CFI_MFR_INTEL, CFI_ID_ANY, fixup_unlock_powerup_lock }, + { CFI_MFR_SHARP, CFI_ID_ANY, fixup_unlock_powerup_lock }, + { CFI_MFR_SHARP, CFI_ID_ANY, fixup_LH28F640BF }, + { 0, 0, NULL } +}; + +static struct cfi_fixup jedec_fixup_table[] = { + { CFI_MFR_INTEL, I82802AB, fixup_use_fwh_lock }, + { CFI_MFR_INTEL, I82802AC, fixup_use_fwh_lock }, + { CFI_MFR_ST, M50LPW080, fixup_use_fwh_lock }, + { CFI_MFR_ST, M50FLW080A, fixup_use_fwh_lock }, + { CFI_MFR_ST, M50FLW080B, fixup_use_fwh_lock }, + { 0, 0, NULL } +}; +static struct cfi_fixup fixup_table[] = { + /* The CFI vendor ids and the JEDEC vendor IDs appear + * to be common. It is like the devices id's are as + * well. This table is to pick all cases where + * we know that is the case. + */ + { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_point }, + { 0, 0, NULL } +}; + +static void cfi_fixup_major_minor(struct cfi_private *cfi, + struct cfi_pri_intelext *extp) +{ + if (cfi->mfr == CFI_MFR_INTEL && + cfi->id == PF38F4476 && extp->MinorVersion == '3') + extp->MinorVersion = '1'; +} + +static int cfi_is_micron_28F00AP30(struct cfi_private *cfi, struct flchip *chip) +{ + /* + * Micron(was Numonyx) 1Gbit bottom boot are buggy w.r.t + * Erase Supend for their small Erase Blocks(0x8000) + */ + if (cfi->mfr == CFI_MFR_INTEL && cfi->id == M28F00AP30) + return 1; + return 0; +} + +static inline struct cfi_pri_intelext * +read_pri_intelext(struct map_info *map, __u16 adr) +{ + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *extp; + unsigned int extra_size = 0; + unsigned int extp_size = sizeof(*extp); + + again: + extp = (struct cfi_pri_intelext *)cfi_read_pri(map, adr, extp_size, "Intel/Sharp"); + if (!extp) + return NULL; + + cfi_fixup_major_minor(cfi, extp); + + if (extp->MajorVersion != '1' || + (extp->MinorVersion < '0' || extp->MinorVersion > '5')) { + printk(KERN_ERR " Unknown Intel/Sharp Extended Query " + "version %c.%c.\n", extp->MajorVersion, + extp->MinorVersion); + kfree(extp); + return NULL; + } + + /* Do some byteswapping if necessary */ + extp->FeatureSupport = le32_to_cpu(extp->FeatureSupport); + extp->BlkStatusRegMask = le16_to_cpu(extp->BlkStatusRegMask); + extp->ProtRegAddr = le16_to_cpu(extp->ProtRegAddr); + + if (extp->MinorVersion >= '0') { + extra_size = 0; + + /* Protection Register info */ + if (extp->NumProtectionFields) { + struct cfi_intelext_otpinfo *otp = + (struct cfi_intelext_otpinfo *)&extp->extra[0]; + + extra_size += (extp->NumProtectionFields - 1) * + sizeof(struct cfi_intelext_otpinfo); + + if (extp_size >= sizeof(*extp) + extra_size) { + int i; + + /* Do some byteswapping if necessary */ + for (i = 0; i < extp->NumProtectionFields - 1; i++) { + otp->ProtRegAddr = le32_to_cpu(otp->ProtRegAddr); + otp->FactGroups = le16_to_cpu(otp->FactGroups); + otp->UserGroups = le16_to_cpu(otp->UserGroups); + otp++; + } + } + } + } + + if (extp->MinorVersion >= '1') { + /* Burst Read info */ + extra_size += 2; + if (extp_size < sizeof(*extp) + extra_size) + goto need_more; + extra_size += extp->extra[extra_size - 1]; + } + + if (extp->MinorVersion >= '3') { + int nb_parts, i; + + /* Number of hardware-partitions */ + extra_size += 1; + if (extp_size < sizeof(*extp) + extra_size) + goto need_more; + nb_parts = extp->extra[extra_size - 1]; + + /* skip the sizeof(partregion) field in CFI 1.4 */ + if (extp->MinorVersion >= '4') + extra_size += 2; + + for (i = 0; i < nb_parts; i++) { + struct cfi_intelext_regioninfo *rinfo; + rinfo = (struct cfi_intelext_regioninfo *)&extp->extra[extra_size]; + extra_size += sizeof(*rinfo); + if (extp_size < sizeof(*extp) + extra_size) + goto need_more; + rinfo->NumIdentPartitions=le16_to_cpu(rinfo->NumIdentPartitions); + extra_size += (rinfo->NumBlockTypes - 1) + * sizeof(struct cfi_intelext_blockinfo); + } + + if (extp->MinorVersion >= '4') + extra_size += sizeof(struct cfi_intelext_programming_regioninfo); + + if (extp_size < sizeof(*extp) + extra_size) { + need_more: + extp_size = sizeof(*extp) + extra_size; + kfree(extp); + if (extp_size > 4096) { + printk(KERN_ERR + "%s: cfi_pri_intelext is too fat\n", + __func__); + return NULL; + } + goto again; + } + } + + return extp; +} + +struct mtd_info *cfi_cmdset_0001(struct map_info *map, int primary) +{ + struct cfi_private *cfi = map->fldrv_priv; + struct mtd_info *mtd; + int i; + + mtd = kzalloc_obj(*mtd); + if (!mtd) + return NULL; + mtd->priv = map; + mtd->type = MTD_NORFLASH; + + /* Fill in the default mtd operations */ + mtd->_erase = cfi_intelext_erase_varsize; + mtd->_read = cfi_intelext_read; + mtd->_write = cfi_intelext_write_words; + mtd->_sync = cfi_intelext_sync; + mtd->_lock = cfi_intelext_lock; + mtd->_unlock = cfi_intelext_unlock; + mtd->_is_locked = cfi_intelext_is_locked; + mtd->_suspend = cfi_intelext_suspend; + mtd->_resume = cfi_intelext_resume; + mtd->flags = MTD_CAP_NORFLASH; + mtd->name = map->name; + mtd->writesize = 1; + mtd->writebufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; + + mtd->reboot_notifier.notifier_call = cfi_intelext_reboot; + + if (cfi->cfi_mode == CFI_MODE_CFI) { + /* + * It's a real CFI chip, not one for which the probe + * routine faked a CFI structure. So we read the feature + * table from it. + */ + __u16 adr = primary?cfi->cfiq->P_ADR:cfi->cfiq->A_ADR; + struct cfi_pri_intelext *extp; + + extp = read_pri_intelext(map, adr); + if (!extp) { + kfree(mtd); + return NULL; + } + + /* Install our own private info structure */ + cfi->cmdset_priv = extp; + + cfi_fixup(mtd, cfi_fixup_table); + +#ifdef DEBUG_CFI_FEATURES + /* Tell the user about it in lots of lovely detail */ + cfi_tell_features(extp); +#endif + + if(extp->SuspendCmdSupport & 1) { + printk(KERN_NOTICE "cfi_cmdset_0001: Erase suspend on write enabled\n"); + } + } + else if (cfi->cfi_mode == CFI_MODE_JEDEC) { + /* Apply jedec specific fixups */ + cfi_fixup(mtd, jedec_fixup_table); + } + /* Apply generic fixups */ + cfi_fixup(mtd, fixup_table); + + for (i=0; i< cfi->numchips; i++) { + if (cfi->cfiq->WordWriteTimeoutTyp) + cfi->chips[i].word_write_time = + 1<cfiq->WordWriteTimeoutTyp; + else + cfi->chips[i].word_write_time = 50000; + + if (cfi->cfiq->BufWriteTimeoutTyp) + cfi->chips[i].buffer_write_time = + 1<cfiq->BufWriteTimeoutTyp; + /* No default; if it isn't specified, we won't use it */ + + if (cfi->cfiq->BlockEraseTimeoutTyp) + cfi->chips[i].erase_time = + 1000<cfiq->BlockEraseTimeoutTyp; + else + cfi->chips[i].erase_time = 2000000; + + if (cfi->cfiq->WordWriteTimeoutTyp && + cfi->cfiq->WordWriteTimeoutMax) + cfi->chips[i].word_write_time_max = + 1<<(cfi->cfiq->WordWriteTimeoutTyp + + cfi->cfiq->WordWriteTimeoutMax); + else + cfi->chips[i].word_write_time_max = 50000 * 8; + + if (cfi->cfiq->BufWriteTimeoutTyp && + cfi->cfiq->BufWriteTimeoutMax) + cfi->chips[i].buffer_write_time_max = + 1<<(cfi->cfiq->BufWriteTimeoutTyp + + cfi->cfiq->BufWriteTimeoutMax); + + if (cfi->cfiq->BlockEraseTimeoutTyp && + cfi->cfiq->BlockEraseTimeoutMax) + cfi->chips[i].erase_time_max = + 1000<<(cfi->cfiq->BlockEraseTimeoutTyp + + cfi->cfiq->BlockEraseTimeoutMax); + else + cfi->chips[i].erase_time_max = 2000000 * 8; + + cfi->chips[i].ref_point_counter = 0; + init_waitqueue_head(&(cfi->chips[i].wq)); + } + + map->fldrv = &cfi_intelext_chipdrv; + + return cfi_intelext_setup(mtd); +} +struct mtd_info *cfi_cmdset_0003(struct map_info *map, int primary) __attribute__((alias("cfi_cmdset_0001"))); +struct mtd_info *cfi_cmdset_0200(struct map_info *map, int primary) __attribute__((alias("cfi_cmdset_0001"))); +EXPORT_SYMBOL_GPL(cfi_cmdset_0001); +EXPORT_SYMBOL_GPL(cfi_cmdset_0003); +EXPORT_SYMBOL_GPL(cfi_cmdset_0200); + +static struct mtd_info *cfi_intelext_setup(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long offset = 0; + int i,j; + unsigned long devsize = (1<cfiq->DevSize) * cfi->interleave; + + //printk(KERN_DEBUG "number of CFI chips: %d\n", cfi->numchips); + + mtd->size = devsize * cfi->numchips; + + mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips; + mtd->eraseregions = kzalloc_objs(struct mtd_erase_region_info, + mtd->numeraseregions); + if (!mtd->eraseregions) + goto setup_err; + + for (i=0; icfiq->NumEraseRegions; i++) { + unsigned long ernum, ersize; + ersize = ((cfi->cfiq->EraseRegionInfo[i] >> 8) & ~0xff) * cfi->interleave; + ernum = (cfi->cfiq->EraseRegionInfo[i] & 0xffff) + 1; + + if (mtd->erasesize < ersize) { + mtd->erasesize = ersize; + } + for (j=0; jnumchips; j++) { + mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].offset = (j*devsize)+offset; + mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].erasesize = ersize; + mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].numblocks = ernum; + mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].lockmap = kmalloc(ernum / 8 + 1, GFP_KERNEL); + if (!mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].lockmap) + goto setup_err; + } + offset += (ersize * ernum); + } + + if (offset != devsize) { + /* Argh */ + printk(KERN_WARNING "Sum of regions (%lx) != total size of set of interleaved chips (%lx)\n", offset, devsize); + goto setup_err; + } + + for (i=0; inumeraseregions;i++){ + printk(KERN_DEBUG "erase region %d: offset=0x%llx,size=0x%x,blocks=%d\n", + i,(unsigned long long)mtd->eraseregions[i].offset, + mtd->eraseregions[i].erasesize, + mtd->eraseregions[i].numblocks); + } + +#ifdef CONFIG_MTD_OTP + mtd->_read_fact_prot_reg = cfi_intelext_read_fact_prot_reg; + mtd->_read_user_prot_reg = cfi_intelext_read_user_prot_reg; + mtd->_write_user_prot_reg = cfi_intelext_write_user_prot_reg; + mtd->_lock_user_prot_reg = cfi_intelext_lock_user_prot_reg; + mtd->_get_fact_prot_info = cfi_intelext_get_fact_prot_info; + mtd->_get_user_prot_info = cfi_intelext_get_user_prot_info; +#endif + + /* This function has the potential to distort the reality + a bit and therefore should be called last. */ + if (cfi_intelext_partition_fixup(mtd, &cfi) != 0) + goto setup_err; + + __module_get(THIS_MODULE); + register_reboot_notifier(&mtd->reboot_notifier); + return mtd; + + setup_err: + if (mtd->eraseregions) + for (i=0; icfiq->NumEraseRegions; i++) + for (j=0; jnumchips; j++) + kfree(mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].lockmap); + kfree(mtd->eraseregions); + kfree(mtd); + kfree(cfi->cmdset_priv); + return NULL; +} + +static int cfi_intelext_partition_fixup(struct mtd_info *mtd, + struct cfi_private **pcfi) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = *pcfi; + struct cfi_pri_intelext *extp = cfi->cmdset_priv; + + /* + * Probing of multi-partition flash chips. + * + * To support multiple partitions when available, we simply arrange + * for each of them to have their own flchip structure even if they + * are on the same physical chip. This means completely recreating + * a new cfi_private structure right here which is a blatent code + * layering violation, but this is still the least intrusive + * arrangement at this point. This can be rearranged in the future + * if someone feels motivated enough. --nico + */ + if (extp && extp->MajorVersion == '1' && extp->MinorVersion >= '3' + && extp->FeatureSupport & (1 << 9)) { + int offs = 0; + struct cfi_private *newcfi; + struct flchip *chip; + struct flchip_shared *shared; + int numregions, numparts, partshift, numvirtchips, i, j; + + /* Protection Register info */ + if (extp->NumProtectionFields) + offs = (extp->NumProtectionFields - 1) * + sizeof(struct cfi_intelext_otpinfo); + + /* Burst Read info */ + offs += extp->extra[offs+1]+2; + + /* Number of partition regions */ + numregions = extp->extra[offs]; + offs += 1; + + /* skip the sizeof(partregion) field in CFI 1.4 */ + if (extp->MinorVersion >= '4') + offs += 2; + + /* Number of hardware partitions */ + numparts = 0; + for (i = 0; i < numregions; i++) { + struct cfi_intelext_regioninfo *rinfo; + rinfo = (struct cfi_intelext_regioninfo *)&extp->extra[offs]; + numparts += rinfo->NumIdentPartitions; + offs += sizeof(*rinfo) + + (rinfo->NumBlockTypes - 1) * + sizeof(struct cfi_intelext_blockinfo); + } + + if (!numparts) + numparts = 1; + + /* Programming Region info */ + if (extp->MinorVersion >= '4') { + struct cfi_intelext_programming_regioninfo *prinfo; + prinfo = (struct cfi_intelext_programming_regioninfo *)&extp->extra[offs]; + mtd->writesize = cfi->interleave << prinfo->ProgRegShift; + mtd->flags &= ~MTD_BIT_WRITEABLE; + printk(KERN_DEBUG "%s: program region size/ctrl_valid/ctrl_inval = %d/%d/%d\n", + map->name, mtd->writesize, + cfi->interleave * prinfo->ControlValid, + cfi->interleave * prinfo->ControlInvalid); + } + + /* + * All functions below currently rely on all chips having + * the same geometry so we'll just assume that all hardware + * partitions are of the same size too. + */ + partshift = cfi->chipshift - __ffs(numparts); + + if ((1 << partshift) < mtd->erasesize) { + printk( KERN_ERR + "%s: bad number of hw partitions (%d)\n", + __func__, numparts); + return -EINVAL; + } + + numvirtchips = cfi->numchips * numparts; + newcfi = kmalloc_flex(*newcfi, chips, numvirtchips); + if (!newcfi) + return -ENOMEM; + shared = kmalloc_objs(struct flchip_shared, cfi->numchips); + if (!shared) { + kfree(newcfi); + return -ENOMEM; + } + memcpy(newcfi, cfi, sizeof(struct cfi_private)); + newcfi->numchips = numvirtchips; + newcfi->chipshift = partshift; + + chip = &newcfi->chips[0]; + for (i = 0; i < cfi->numchips; i++) { + shared[i].writing = shared[i].erasing = NULL; + mutex_init(&shared[i].lock); + for (j = 0; j < numparts; j++) { + *chip = cfi->chips[i]; + chip->start += j << partshift; + chip->priv = &shared[i]; + /* those should be reset too since + they create memory references. */ + init_waitqueue_head(&chip->wq); + mutex_init(&chip->mutex); + chip++; + } + } + + printk(KERN_DEBUG "%s: %d set(s) of %d interleaved chips " + "--> %d partitions of %d KiB\n", + map->name, cfi->numchips, cfi->interleave, + newcfi->numchips, 1<<(newcfi->chipshift-10)); + + map->fldrv_priv = newcfi; + *pcfi = newcfi; + kfree(cfi); + } + + return 0; +} + +/* + * *********** CHIP ACCESS FUNCTIONS *********** + */ +static int chip_ready (struct map_info *map, struct flchip *chip, unsigned long adr, int mode) +{ + DECLARE_WAITQUEUE(wait, current); + struct cfi_private *cfi = map->fldrv_priv; + map_word status, status_OK = CMD(0x80), status_PWS = CMD(0x01); + struct cfi_pri_intelext *cfip = cfi->cmdset_priv; + unsigned long timeo = jiffies + HZ; + + /* Prevent setting state FL_SYNCING for chip in suspended state. */ + if (mode == FL_SYNCING && chip->oldstate != FL_READY) + goto sleep; + + switch (chip->state) { + + case FL_STATUS: + for (;;) { + status = map_read(map, adr); + if (map_word_andequal(map, status, status_OK, status_OK)) + break; + + /* At this point we're fine with write operations + in other partitions as they don't conflict. */ + if (chip->priv && map_word_andequal(map, status, status_PWS, status_PWS)) + break; + + mutex_unlock(&chip->mutex); + cfi_udelay(1); + mutex_lock(&chip->mutex); + /* Someone else might have been playing with it. */ + return -EAGAIN; + } + fallthrough; + case FL_READY: + case FL_CFI_QUERY: + case FL_JEDEC_QUERY: + return 0; + + case FL_ERASING: + if (!cfip || + !(cfip->FeatureSupport & 2) || + !(mode == FL_READY || mode == FL_POINT || + (mode == FL_WRITING && (cfip->SuspendCmdSupport & 1)))) + goto sleep; + + /* Do not allow suspend iff read/write to EB address */ + if ((adr & chip->in_progress_block_mask) == + chip->in_progress_block_addr) + goto sleep; + + /* do not suspend small EBs, buggy Micron Chips */ + if (cfi_is_micron_28F00AP30(cfi, chip) && + (chip->in_progress_block_mask == ~(0x8000-1))) + goto sleep; + + /* Erase suspend */ + map_write(map, CMD(0xB0), chip->in_progress_block_addr); + + /* If the flash has finished erasing, then 'erase suspend' + * appears to make some (28F320) flash devices switch to + * 'read' mode. Make sure that we switch to 'read status' + * mode so we get the right data. --rmk + */ + map_write(map, CMD(0x70), chip->in_progress_block_addr); + chip->oldstate = FL_ERASING; + chip->state = FL_ERASE_SUSPENDING; + chip->erase_suspended = 1; + for (;;) { + status = map_read(map, chip->in_progress_block_addr); + if (map_word_andequal(map, status, status_OK, status_OK)) + break; + + if (time_after(jiffies, timeo)) { + /* Urgh. Resume and pretend we weren't here. + * Make sure we're in 'read status' mode if it had finished */ + put_chip(map, chip, adr); + printk(KERN_ERR "%s: Chip not ready after erase " + "suspended: status = 0x%lx\n", map->name, status.x[0]); + return -EIO; + } + + mutex_unlock(&chip->mutex); + cfi_udelay(1); + mutex_lock(&chip->mutex); + /* Nobody will touch it while it's in state FL_ERASE_SUSPENDING. + So we can just loop here. */ + } + chip->state = FL_STATUS; + return 0; + + case FL_XIP_WHILE_ERASING: + if (mode != FL_READY && mode != FL_POINT && + (mode != FL_WRITING || !cfip || !(cfip->SuspendCmdSupport&1))) + goto sleep; + chip->oldstate = chip->state; + chip->state = FL_READY; + return 0; + + case FL_SHUTDOWN: + /* The machine is rebooting now,so no one can get chip anymore */ + return -EIO; + case FL_POINT: + /* Only if there's no operation suspended... */ + if (mode == FL_READY && chip->oldstate == FL_READY) + return 0; + fallthrough; + default: + sleep: + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + mutex_lock(&chip->mutex); + return -EAGAIN; + } +} + +static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode) +{ + int ret; + DECLARE_WAITQUEUE(wait, current); + + retry: + if (chip->priv && + (mode == FL_WRITING || mode == FL_ERASING || mode == FL_OTP_WRITE + || mode == FL_SHUTDOWN) && chip->state != FL_SYNCING) { + /* + * OK. We have possibility for contention on the write/erase + * operations which are global to the real chip and not per + * partition. So let's fight it over in the partition which + * currently has authority on the operation. + * + * The rules are as follows: + * + * - any write operation must own shared->writing. + * + * - any erase operation must own _both_ shared->writing and + * shared->erasing. + * + * - contention arbitration is handled in the owner's context. + * + * The 'shared' struct can be read and/or written only when + * its lock is taken. + */ + struct flchip_shared *shared = chip->priv; + struct flchip *contender; + mutex_lock(&shared->lock); + contender = shared->writing; + if (contender && contender != chip) { + /* + * The engine to perform desired operation on this + * partition is already in use by someone else. + * Let's fight over it in the context of the chip + * currently using it. If it is possible to suspend, + * that other partition will do just that, otherwise + * it'll happily send us to sleep. In any case, when + * get_chip returns success we're clear to go ahead. + */ + ret = mutex_trylock(&contender->mutex); + mutex_unlock(&shared->lock); + if (!ret) + goto retry; + mutex_unlock(&chip->mutex); + ret = chip_ready(map, contender, contender->start, mode); + mutex_lock(&chip->mutex); + + if (ret == -EAGAIN) { + mutex_unlock(&contender->mutex); + goto retry; + } + if (ret) { + mutex_unlock(&contender->mutex); + return ret; + } + mutex_lock(&shared->lock); + + /* We should not own chip if it is already + * in FL_SYNCING state. Put contender and retry. */ + if (chip->state == FL_SYNCING) { + put_chip(map, contender, contender->start); + mutex_unlock(&contender->mutex); + goto retry; + } + mutex_unlock(&contender->mutex); + } + + /* Check if we already have suspended erase + * on this chip. Sleep. */ + if (mode == FL_ERASING && shared->erasing + && shared->erasing->oldstate == FL_ERASING) { + mutex_unlock(&shared->lock); + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + mutex_lock(&chip->mutex); + goto retry; + } + + /* We now own it */ + shared->writing = chip; + if (mode == FL_ERASING) + shared->erasing = chip; + mutex_unlock(&shared->lock); + } + ret = chip_ready(map, chip, adr, mode); + if (ret == -EAGAIN) + goto retry; + + return ret; +} + +static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr) +{ + struct cfi_private *cfi = map->fldrv_priv; + + if (chip->priv) { + struct flchip_shared *shared = chip->priv; + mutex_lock(&shared->lock); + if (shared->writing == chip && chip->oldstate == FL_READY) { + /* We own the ability to write, but we're done */ + shared->writing = shared->erasing; + if (shared->writing && shared->writing != chip) { + /* give back ownership to who we loaned it from */ + struct flchip *loaner = shared->writing; + mutex_lock(&loaner->mutex); + mutex_unlock(&shared->lock); + mutex_unlock(&chip->mutex); + put_chip(map, loaner, loaner->start); + mutex_lock(&chip->mutex); + mutex_unlock(&loaner->mutex); + wake_up(&chip->wq); + return; + } + shared->erasing = NULL; + shared->writing = NULL; + } else if (shared->erasing == chip && shared->writing != chip) { + /* + * We own the ability to erase without the ability + * to write, which means the erase was suspended + * and some other partition is currently writing. + * Don't let the switch below mess things up since + * we don't have ownership to resume anything. + */ + mutex_unlock(&shared->lock); + wake_up(&chip->wq); + return; + } + mutex_unlock(&shared->lock); + } + + switch(chip->oldstate) { + case FL_ERASING: + /* What if one interleaved chip has finished and the + other hasn't? The old code would leave the finished + one in READY mode. That's bad, and caused -EROFS + errors to be returned from do_erase_oneblock because + that's the only bit it checked for at the time. + As the state machine appears to explicitly allow + sending the 0x70 (Read Status) command to an erasing + chip and expecting it to be ignored, that's what we + do. */ + map_write(map, CMD(0xd0), chip->in_progress_block_addr); + map_write(map, CMD(0x70), chip->in_progress_block_addr); + chip->oldstate = FL_READY; + chip->state = FL_ERASING; + break; + + case FL_XIP_WHILE_ERASING: + chip->state = chip->oldstate; + chip->oldstate = FL_READY; + break; + + case FL_READY: + case FL_STATUS: + case FL_JEDEC_QUERY: + break; + default: + printk(KERN_ERR "%s: put_chip() called with oldstate %d!!\n", map->name, chip->oldstate); + } + wake_up(&chip->wq); +} + +#ifdef CONFIG_MTD_XIP + +/* + * No interrupt what so ever can be serviced while the flash isn't in array + * mode. This is ensured by the xip_disable() and xip_enable() functions + * enclosing any code path where the flash is known not to be in array mode. + * And within a XIP disabled code path, only functions marked with __xipram + * may be called and nothing else (it's a good thing to inspect generated + * assembly to make sure inline functions were actually inlined and that gcc + * didn't emit calls to its own support functions). Also configuring MTD CFI + * support to a single buswidth and a single interleave is also recommended. + */ + +static void xip_disable(struct map_info *map, struct flchip *chip, + unsigned long adr) +{ + /* TODO: chips with no XIP use should ignore and return */ + (void) map_read(map, adr); /* ensure mmu mapping is up to date */ + local_irq_disable(); +} + +static void __xipram xip_enable(struct map_info *map, struct flchip *chip, + unsigned long adr) +{ + struct cfi_private *cfi = map->fldrv_priv; + if (chip->state != FL_POINT && chip->state != FL_READY) { + map_write(map, CMD(0xff), adr); + chip->state = FL_READY; + } + (void) map_read(map, adr); + xip_iprefetch(); + local_irq_enable(); +} + +/* + * When a delay is required for the flash operation to complete, the + * xip_wait_for_operation() function is polling for both the given timeout + * and pending (but still masked) hardware interrupts. Whenever there is an + * interrupt pending then the flash erase or write operation is suspended, + * array mode restored and interrupts unmasked. Task scheduling might also + * happen at that point. The CPU eventually returns from the interrupt or + * the call to schedule() and the suspended flash operation is resumed for + * the remaining of the delay period. + * + * Warning: this function _will_ fool interrupt latency tracing tools. + */ + +static int __xipram xip_wait_for_operation( + struct map_info *map, struct flchip *chip, + unsigned long adr, unsigned int chip_op_time_max) +{ + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *cfip = cfi->cmdset_priv; + map_word status, OK = CMD(0x80); + unsigned long usec, suspended, start, done; + flstate_t oldstate, newstate; + + start = xip_currtime(); + usec = chip_op_time_max; + if (usec == 0) + usec = 500000; + done = 0; + + do { + cpu_relax(); + if (xip_irqpending() && cfip && + ((chip->state == FL_ERASING && (cfip->FeatureSupport&2)) || + (chip->state == FL_WRITING && (cfip->FeatureSupport&4))) && + (cfi_interleave_is_1(cfi) || chip->oldstate == FL_READY)) { + /* + * Let's suspend the erase or write operation when + * supported. Note that we currently don't try to + * suspend interleaved chips if there is already + * another operation suspended (imagine what happens + * when one chip was already done with the current + * operation while another chip suspended it, then + * we resume the whole thing at once). Yes, it + * can happen! + */ + usec -= done; + map_write(map, CMD(0xb0), adr); + map_write(map, CMD(0x70), adr); + suspended = xip_currtime(); + do { + if (xip_elapsed_since(suspended) > 100000) { + /* + * The chip doesn't want to suspend + * after waiting for 100 msecs. + * This is a critical error but there + * is not much we can do here. + */ + return -EIO; + } + status = map_read(map, adr); + } while (!map_word_andequal(map, status, OK, OK)); + + /* Suspend succeeded */ + oldstate = chip->state; + if (oldstate == FL_ERASING) { + if (!map_word_bitsset(map, status, CMD(0x40))) + break; + newstate = FL_XIP_WHILE_ERASING; + chip->erase_suspended = 1; + } else { + if (!map_word_bitsset(map, status, CMD(0x04))) + break; + newstate = FL_XIP_WHILE_WRITING; + chip->write_suspended = 1; + } + chip->state = newstate; + map_write(map, CMD(0xff), adr); + (void) map_read(map, adr); + xip_iprefetch(); + local_irq_enable(); + mutex_unlock(&chip->mutex); + xip_iprefetch(); + cond_resched(); + + /* + * We're back. However someone else might have + * decided to go write to the chip if we are in + * a suspended erase state. If so let's wait + * until it's done. + */ + mutex_lock(&chip->mutex); + while (chip->state != newstate) { + DECLARE_WAITQUEUE(wait, current); + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + mutex_lock(&chip->mutex); + } + /* Disallow XIP again */ + local_irq_disable(); + + /* Resume the write or erase operation */ + map_write(map, CMD(0xd0), adr); + map_write(map, CMD(0x70), adr); + chip->state = oldstate; + start = xip_currtime(); + } else if (usec >= 1000000/HZ) { + /* + * Try to save on CPU power when waiting delay + * is at least a system timer tick period. + * No need to be extremely accurate here. + */ + xip_cpu_idle(); + } + status = map_read(map, adr); + done = xip_elapsed_since(start); + } while (!map_word_andequal(map, status, OK, OK) + && done < usec); + + return (done >= usec) ? -ETIME : 0; +} + +/* + * The INVALIDATE_CACHED_RANGE() macro is normally used in parallel while + * the flash is actively programming or erasing since we have to poll for + * the operation to complete anyway. We can't do that in a generic way with + * a XIP setup so do it before the actual flash operation in this case + * and stub it out from INVAL_CACHE_AND_WAIT. + */ +#define XIP_INVAL_CACHED_RANGE(map, from, size) \ + INVALIDATE_CACHED_RANGE(map, from, size) + +#define INVAL_CACHE_AND_WAIT(map, chip, cmd_adr, inval_adr, inval_len, usec, usec_max) \ + xip_wait_for_operation(map, chip, cmd_adr, usec_max) + +#else + +#define xip_disable(map, chip, adr) +#define xip_enable(map, chip, adr) +#define XIP_INVAL_CACHED_RANGE(x...) +#define INVAL_CACHE_AND_WAIT inval_cache_and_wait_for_operation + +static int inval_cache_and_wait_for_operation( + struct map_info *map, struct flchip *chip, + unsigned long cmd_adr, unsigned long inval_adr, int inval_len, + unsigned int chip_op_time, unsigned int chip_op_time_max) +{ + struct cfi_private *cfi = map->fldrv_priv; + map_word status, status_OK = CMD(0x80); + int chip_state = chip->state; + unsigned int timeo, sleep_time, reset_timeo; + + mutex_unlock(&chip->mutex); + if (inval_len) + INVALIDATE_CACHED_RANGE(map, inval_adr, inval_len); + mutex_lock(&chip->mutex); + + timeo = chip_op_time_max; + if (!timeo) + timeo = 500000; + reset_timeo = timeo; + sleep_time = chip_op_time / 2; + + for (;;) { + if (chip->state != chip_state) { + /* Someone's suspended the operation: sleep */ + DECLARE_WAITQUEUE(wait, current); + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + mutex_lock(&chip->mutex); + continue; + } + + status = map_read(map, cmd_adr); + if (map_word_andequal(map, status, status_OK, status_OK)) + break; + + if (chip->erase_suspended && chip_state == FL_ERASING) { + /* Erase suspend occurred while sleep: reset timeout */ + timeo = reset_timeo; + chip->erase_suspended = 0; + } + if (chip->write_suspended && chip_state == FL_WRITING) { + /* Write suspend occurred while sleep: reset timeout */ + timeo = reset_timeo; + chip->write_suspended = 0; + } + if (!timeo) { + map_write(map, CMD(0x70), cmd_adr); + chip->state = FL_STATUS; + return -ETIME; + } + + /* OK Still waiting. Drop the lock, wait a while and retry. */ + mutex_unlock(&chip->mutex); + if (sleep_time >= 1000000/HZ) { + /* + * Half of the normal delay still remaining + * can be performed with a sleeping delay instead + * of busy waiting. + */ + msleep(sleep_time/1000); + timeo -= sleep_time; + sleep_time = 1000000/HZ; + } else { + udelay(1); + cond_resched(); + timeo--; + } + mutex_lock(&chip->mutex); + } + + /* Done and happy. */ + chip->state = FL_STATUS; + return 0; +} + +#endif + +#define WAIT_TIMEOUT(map, chip, adr, udelay, udelay_max) \ + INVAL_CACHE_AND_WAIT(map, chip, adr, 0, 0, udelay, udelay_max); + + +static int do_point_onechip (struct map_info *map, struct flchip *chip, loff_t adr, size_t len) +{ + unsigned long cmd_addr; + struct cfi_private *cfi = map->fldrv_priv; + int ret; + + adr += chip->start; + + /* Ensure cmd read/writes are aligned. */ + cmd_addr = adr & ~(map_bankwidth(map)-1); + + mutex_lock(&chip->mutex); + + ret = get_chip(map, chip, cmd_addr, FL_POINT); + + if (!ret) { + if (chip->state != FL_POINT && chip->state != FL_READY) + map_write(map, CMD(0xff), cmd_addr); + + chip->state = FL_POINT; + chip->ref_point_counter++; + } + mutex_unlock(&chip->mutex); + + return ret; +} + +static int cfi_intelext_point(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, void **virt, resource_size_t *phys) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long ofs, last_end = 0; + int chipnum; + int ret; + + if (!map->virt) + return -EINVAL; + + /* Now lock the chip(s) to POINT state */ + + /* ofs: offset within the first chip that the first read should start */ + chipnum = (from >> cfi->chipshift); + ofs = from - (chipnum << cfi->chipshift); + + *virt = map->virt + cfi->chips[chipnum].start + ofs; + if (phys) + *phys = map->phys + cfi->chips[chipnum].start + ofs; + + while (len) { + unsigned long thislen; + + if (chipnum >= cfi->numchips) + break; + + /* We cannot point across chips that are virtually disjoint */ + if (!last_end) + last_end = cfi->chips[chipnum].start; + else if (cfi->chips[chipnum].start != last_end) + break; + + if ((len + ofs -1) >> cfi->chipshift) + thislen = (1<chipshift) - ofs; + else + thislen = len; + + ret = do_point_onechip(map, &cfi->chips[chipnum], ofs, thislen); + if (ret) + break; + + *retlen += thislen; + len -= thislen; + + ofs = 0; + last_end += 1 << cfi->chipshift; + chipnum++; + } + return 0; +} + +static int cfi_intelext_unpoint(struct mtd_info *mtd, loff_t from, size_t len) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long ofs; + int chipnum, err = 0; + + /* Now unlock the chip(s) POINT state */ + + /* ofs: offset within the first chip that the first read should start */ + chipnum = (from >> cfi->chipshift); + ofs = from - (chipnum << cfi->chipshift); + + while (len && !err) { + unsigned long thislen; + struct flchip *chip; + + chip = &cfi->chips[chipnum]; + if (chipnum >= cfi->numchips) + break; + + if ((len + ofs -1) >> cfi->chipshift) + thislen = (1<chipshift) - ofs; + else + thislen = len; + + mutex_lock(&chip->mutex); + if (chip->state == FL_POINT) { + chip->ref_point_counter--; + if(chip->ref_point_counter == 0) + chip->state = FL_READY; + } else { + printk(KERN_ERR "%s: Error: unpoint called on non pointed region\n", map->name); + err = -EINVAL; + } + + put_chip(map, chip, chip->start); + mutex_unlock(&chip->mutex); + + len -= thislen; + ofs = 0; + chipnum++; + } + + return err; +} + +static inline int do_read_onechip(struct map_info *map, struct flchip *chip, loff_t adr, size_t len, u_char *buf) +{ + unsigned long cmd_addr; + struct cfi_private *cfi = map->fldrv_priv; + int ret; + + adr += chip->start; + + /* Ensure cmd read/writes are aligned. */ + cmd_addr = adr & ~(map_bankwidth(map)-1); + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, cmd_addr, FL_READY); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + if (chip->state != FL_POINT && chip->state != FL_READY) { + map_write(map, CMD(0xff), cmd_addr); + + chip->state = FL_READY; + } + + map_copy_from(map, buf, adr, len); + + put_chip(map, chip, cmd_addr); + + mutex_unlock(&chip->mutex); + return 0; +} + +static int cfi_intelext_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long ofs; + int chipnum; + int ret = 0; + + /* ofs: offset within the first chip that the first read should start */ + chipnum = (from >> cfi->chipshift); + ofs = from - (chipnum << cfi->chipshift); + + while (len) { + unsigned long thislen; + + if (chipnum >= cfi->numchips) + break; + + if ((len + ofs -1) >> cfi->chipshift) + thislen = (1<chipshift) - ofs; + else + thislen = len; + + ret = do_read_onechip(map, &cfi->chips[chipnum], ofs, thislen, buf); + if (ret) + break; + + *retlen += thislen; + len -= thislen; + buf += thislen; + + ofs = 0; + chipnum++; + } + return ret; +} + +static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip, + unsigned long adr, map_word datum, int mode) +{ + struct cfi_private *cfi = map->fldrv_priv; + map_word status, write_cmd; + int ret; + + adr += chip->start; + + switch (mode) { + case FL_WRITING: + write_cmd = (cfi->cfiq->P_ID != P_ID_INTEL_PERFORMANCE) ? CMD(0x40) : CMD(0x41); + break; + case FL_OTP_WRITE: + write_cmd = CMD(0xc0); + break; + default: + return -EINVAL; + } + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, adr, mode); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + XIP_INVAL_CACHED_RANGE(map, adr, map_bankwidth(map)); + ENABLE_VPP(map); + xip_disable(map, chip, adr); + map_write(map, write_cmd, adr); + map_write(map, datum, adr); + chip->state = mode; + + ret = INVAL_CACHE_AND_WAIT(map, chip, adr, + adr, map_bankwidth(map), + chip->word_write_time, + chip->word_write_time_max); + if (ret) { + xip_enable(map, chip, adr); + printk(KERN_ERR "%s: word write error (status timeout)\n", map->name); + goto out; + } + + /* check for errors */ + status = map_read(map, adr); + if (map_word_bitsset(map, status, CMD(0x1a))) { + unsigned long chipstatus = MERGESTATUS(status); + + /* reset status */ + map_write(map, CMD(0x50), adr); + map_write(map, CMD(0x70), adr); + xip_enable(map, chip, adr); + + if (chipstatus & 0x02) { + ret = -EROFS; + } else if (chipstatus & 0x08) { + printk(KERN_ERR "%s: word write error (bad VPP)\n", map->name); + ret = -EIO; + } else { + printk(KERN_ERR "%s: word write error (status 0x%lx)\n", map->name, chipstatus); + ret = -EINVAL; + } + + goto out; + } + + xip_enable(map, chip, adr); + out: DISABLE_VPP(map); + put_chip(map, chip, adr); + mutex_unlock(&chip->mutex); + return ret; +} + + +static int cfi_intelext_write_words (struct mtd_info *mtd, loff_t to , size_t len, size_t *retlen, const u_char *buf) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int ret; + int chipnum; + unsigned long ofs; + + chipnum = to >> cfi->chipshift; + ofs = to - (chipnum << cfi->chipshift); + + /* If it's not bus-aligned, do the first byte write */ + if (ofs & (map_bankwidth(map)-1)) { + unsigned long bus_ofs = ofs & ~(map_bankwidth(map)-1); + int gap = ofs - bus_ofs; + int n; + map_word datum; + + n = min_t(int, len, map_bankwidth(map)-gap); + datum = map_word_ff(map); + datum = map_word_load_partial(map, datum, buf, gap, n); + + ret = do_write_oneword(map, &cfi->chips[chipnum], + bus_ofs, datum, FL_WRITING); + if (ret) + return ret; + + len -= n; + ofs += n; + buf += n; + (*retlen) += n; + + if (ofs >> cfi->chipshift) { + chipnum ++; + ofs = 0; + if (chipnum == cfi->numchips) + return 0; + } + } + + while(len >= map_bankwidth(map)) { + map_word datum = map_word_load(map, buf); + + ret = do_write_oneword(map, &cfi->chips[chipnum], + ofs, datum, FL_WRITING); + if (ret) + return ret; + + ofs += map_bankwidth(map); + buf += map_bankwidth(map); + (*retlen) += map_bankwidth(map); + len -= map_bankwidth(map); + + if (ofs >> cfi->chipshift) { + chipnum ++; + ofs = 0; + if (chipnum == cfi->numchips) + return 0; + } + } + + if (len & (map_bankwidth(map)-1)) { + map_word datum; + + datum = map_word_ff(map); + datum = map_word_load_partial(map, datum, buf, 0, len); + + ret = do_write_oneword(map, &cfi->chips[chipnum], + ofs, datum, FL_WRITING); + if (ret) + return ret; + + (*retlen) += len; + } + + return 0; +} + + +static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, + unsigned long adr, const struct kvec **pvec, + unsigned long *pvec_seek, int len) +{ + struct cfi_private *cfi = map->fldrv_priv; + map_word status, write_cmd, datum; + unsigned long cmd_adr; + int ret, wbufsize, word_gap, words; + const struct kvec *vec; + unsigned long vec_seek; + unsigned long initial_adr; + int initial_len = len; + + wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; + adr += chip->start; + initial_adr = adr; + cmd_adr = adr & ~(wbufsize-1); + + /* Sharp LH28F640BF chips need the first address for the + * Page Buffer Program command. See Table 5 of + * LH28F320BF, LH28F640BF, LH28F128BF Series (Appendix FUM00701) */ + if (is_LH28F640BF(cfi)) + cmd_adr = adr; + + /* Let's determine this according to the interleave only once */ + write_cmd = (cfi->cfiq->P_ID != P_ID_INTEL_PERFORMANCE) ? CMD(0xe8) : CMD(0xe9); + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, cmd_adr, FL_WRITING); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + XIP_INVAL_CACHED_RANGE(map, initial_adr, initial_len); + ENABLE_VPP(map); + xip_disable(map, chip, cmd_adr); + + /* §4.8 of the 28FxxxJ3A datasheet says "Any time SR.4 and/or SR.5 is set + [...], the device will not accept any more Write to Buffer commands". + So we must check here and reset those bits if they're set. Otherwise + we're just pissing in the wind */ + if (chip->state != FL_STATUS) { + map_write(map, CMD(0x70), cmd_adr); + chip->state = FL_STATUS; + } + status = map_read(map, cmd_adr); + if (map_word_bitsset(map, status, CMD(0x30))) { + xip_enable(map, chip, cmd_adr); + printk(KERN_WARNING "SR.4 or SR.5 bits set in buffer write (status %lx). Clearing.\n", status.x[0]); + xip_disable(map, chip, cmd_adr); + map_write(map, CMD(0x50), cmd_adr); + map_write(map, CMD(0x70), cmd_adr); + } + + chip->state = FL_WRITING_TO_BUFFER; + map_write(map, write_cmd, cmd_adr); + ret = WAIT_TIMEOUT(map, chip, cmd_adr, 0, 0); + if (ret) { + /* Argh. Not ready for write to buffer */ + map_word Xstatus = map_read(map, cmd_adr); + map_write(map, CMD(0x70), cmd_adr); + chip->state = FL_STATUS; + status = map_read(map, cmd_adr); + map_write(map, CMD(0x50), cmd_adr); + map_write(map, CMD(0x70), cmd_adr); + xip_enable(map, chip, cmd_adr); + printk(KERN_ERR "%s: Chip not ready for buffer write. Xstatus = %lx, status = %lx\n", + map->name, Xstatus.x[0], status.x[0]); + goto out; + } + + /* Figure out the number of words to write */ + word_gap = (-adr & (map_bankwidth(map)-1)); + words = DIV_ROUND_UP(len - word_gap, map_bankwidth(map)); + if (!word_gap) { + words--; + } else { + word_gap = map_bankwidth(map) - word_gap; + adr -= word_gap; + datum = map_word_ff(map); + } + + /* Write length of data to come */ + map_write(map, CMD(words), cmd_adr ); + + /* Write data */ + vec = *pvec; + vec_seek = *pvec_seek; + do { + int n = map_bankwidth(map) - word_gap; + if (n > vec->iov_len - vec_seek) + n = vec->iov_len - vec_seek; + if (n > len) + n = len; + + if (!word_gap && len < map_bankwidth(map)) + datum = map_word_ff(map); + + datum = map_word_load_partial(map, datum, + vec->iov_base + vec_seek, + word_gap, n); + + len -= n; + word_gap += n; + if (!len || word_gap == map_bankwidth(map)) { + map_write(map, datum, adr); + adr += map_bankwidth(map); + word_gap = 0; + } + + vec_seek += n; + if (vec_seek == vec->iov_len) { + vec++; + vec_seek = 0; + } + } while (len); + *pvec = vec; + *pvec_seek = vec_seek; + + /* GO GO GO */ + map_write(map, CMD(0xd0), cmd_adr); + chip->state = FL_WRITING; + + ret = INVAL_CACHE_AND_WAIT(map, chip, cmd_adr, + initial_adr, initial_len, + chip->buffer_write_time, + chip->buffer_write_time_max); + if (ret) { + map_write(map, CMD(0x70), cmd_adr); + chip->state = FL_STATUS; + xip_enable(map, chip, cmd_adr); + printk(KERN_ERR "%s: buffer write error (status timeout)\n", map->name); + goto out; + } + + /* check for errors */ + status = map_read(map, cmd_adr); + if (map_word_bitsset(map, status, CMD(0x1a))) { + unsigned long chipstatus = MERGESTATUS(status); + + /* reset status */ + map_write(map, CMD(0x50), cmd_adr); + map_write(map, CMD(0x70), cmd_adr); + xip_enable(map, chip, cmd_adr); + + if (chipstatus & 0x02) { + ret = -EROFS; + } else if (chipstatus & 0x08) { + printk(KERN_ERR "%s: buffer write error (bad VPP)\n", map->name); + ret = -EIO; + } else { + printk(KERN_ERR "%s: buffer write error (status 0x%lx)\n", map->name, chipstatus); + ret = -EINVAL; + } + + goto out; + } + + xip_enable(map, chip, cmd_adr); + out: DISABLE_VPP(map); + put_chip(map, chip, cmd_adr); + mutex_unlock(&chip->mutex); + return ret; +} + +static int cfi_intelext_writev (struct mtd_info *mtd, const struct kvec *vecs, + unsigned long count, loff_t to, size_t *retlen) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; + int ret; + int chipnum; + unsigned long ofs, vec_seek, i; + size_t len = 0; + + for (i = 0; i < count; i++) + len += vecs[i].iov_len; + + if (!len) + return 0; + + chipnum = to >> cfi->chipshift; + ofs = to - (chipnum << cfi->chipshift); + vec_seek = 0; + + do { + /* We must not cross write block boundaries */ + int size = wbufsize - (ofs & (wbufsize-1)); + + if (size > len) + size = len; + ret = do_write_buffer(map, &cfi->chips[chipnum], + ofs, &vecs, &vec_seek, size); + if (ret) + return ret; + + ofs += size; + (*retlen) += size; + len -= size; + + if (ofs >> cfi->chipshift) { + chipnum ++; + ofs = 0; + if (chipnum == cfi->numchips) + return 0; + } + + /* Be nice and reschedule with the chip in a usable state for other + processes. */ + cond_resched(); + + } while (len); + + return 0; +} + +static int cfi_intelext_write_buffers (struct mtd_info *mtd, loff_t to, + size_t len, size_t *retlen, const u_char *buf) +{ + struct kvec vec; + + vec.iov_base = (void *) buf; + vec.iov_len = len; + + return cfi_intelext_writev(mtd, &vec, 1, to, retlen); +} + +static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip, + unsigned long adr, int len, void *thunk) +{ + struct cfi_private *cfi = map->fldrv_priv; + map_word status; + int retries = 3; + int ret; + + adr += chip->start; + + retry: + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, adr, FL_ERASING); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + XIP_INVAL_CACHED_RANGE(map, adr, len); + ENABLE_VPP(map); + xip_disable(map, chip, adr); + + /* Clear the status register first */ + map_write(map, CMD(0x50), adr); + + /* Now erase */ + map_write(map, CMD(0x20), adr); + map_write(map, CMD(0xD0), adr); + chip->state = FL_ERASING; + chip->erase_suspended = 0; + chip->in_progress_block_addr = adr; + chip->in_progress_block_mask = ~(len - 1); + + ret = INVAL_CACHE_AND_WAIT(map, chip, adr, + adr, len, + chip->erase_time, + chip->erase_time_max); + if (ret) { + map_write(map, CMD(0x70), adr); + chip->state = FL_STATUS; + xip_enable(map, chip, adr); + printk(KERN_ERR "%s: block erase error: (status timeout)\n", map->name); + goto out; + } + + /* We've broken this before. It doesn't hurt to be safe */ + map_write(map, CMD(0x70), adr); + chip->state = FL_STATUS; + status = map_read(map, adr); + + /* check for errors */ + if (map_word_bitsset(map, status, CMD(0x3a))) { + unsigned long chipstatus = MERGESTATUS(status); + + /* Reset the error bits */ + map_write(map, CMD(0x50), adr); + map_write(map, CMD(0x70), adr); + xip_enable(map, chip, adr); + + if ((chipstatus & 0x30) == 0x30) { + printk(KERN_ERR "%s: block erase error: (bad command sequence, status 0x%lx)\n", map->name, chipstatus); + ret = -EINVAL; + } else if (chipstatus & 0x02) { + /* Protection bit set */ + ret = -EROFS; + } else if (chipstatus & 0x8) { + /* Voltage */ + printk(KERN_ERR "%s: block erase error: (bad VPP)\n", map->name); + ret = -EIO; + } else if (chipstatus & 0x20 && retries--) { + printk(KERN_DEBUG "block erase failed at 0x%08lx: status 0x%lx. Retrying...\n", adr, chipstatus); + DISABLE_VPP(map); + put_chip(map, chip, adr); + mutex_unlock(&chip->mutex); + goto retry; + } else { + printk(KERN_ERR "%s: block erase failed at 0x%08lx (status 0x%lx)\n", map->name, adr, chipstatus); + ret = -EIO; + } + + goto out; + } + + xip_enable(map, chip, adr); + out: DISABLE_VPP(map); + put_chip(map, chip, adr); + mutex_unlock(&chip->mutex); + return ret; +} + +static int cfi_intelext_erase_varsize(struct mtd_info *mtd, struct erase_info *instr) +{ + return cfi_varsize_frob(mtd, do_erase_oneblock, instr->addr, + instr->len, NULL); +} + +static void cfi_intelext_sync (struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int i; + struct flchip *chip; + int ret = 0; + + for (i=0; !ret && inumchips; i++) { + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, chip->start, FL_SYNCING); + + if (!ret) { + chip->oldstate = chip->state; + chip->state = FL_SYNCING; + /* No need to wake_up() on this state change - + * as the whole point is that nobody can do anything + * with the chip now anyway. + */ + } + mutex_unlock(&chip->mutex); + } + + /* Unlock the chips again */ + + for (i--; i >=0; i--) { + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + + if (chip->state == FL_SYNCING) { + chip->state = chip->oldstate; + chip->oldstate = FL_READY; + wake_up(&chip->wq); + } + mutex_unlock(&chip->mutex); + } +} + +static int __xipram do_getlockstatus_oneblock(struct map_info *map, + struct flchip *chip, + unsigned long adr, + int len, void *thunk) +{ + struct cfi_private *cfi = map->fldrv_priv; + int status, ofs_factor = cfi->interleave * cfi->device_type; + + adr += chip->start; + xip_disable(map, chip, adr+(2*ofs_factor)); + map_write(map, CMD(0x90), adr+(2*ofs_factor)); + chip->state = FL_JEDEC_QUERY; + status = cfi_read_query(map, adr+(2*ofs_factor)); + xip_enable(map, chip, 0); + return status; +} + +#ifdef DEBUG_LOCK_BITS +static int __xipram do_printlockstatus_oneblock(struct map_info *map, + struct flchip *chip, + unsigned long adr, + int len, void *thunk) +{ + printk(KERN_DEBUG "block status register for 0x%08lx is %x\n", + adr, do_getlockstatus_oneblock(map, chip, adr, len, thunk)); + return 0; +} +#endif + +#define DO_XXLOCK_ONEBLOCK_LOCK ((void *) 1) +#define DO_XXLOCK_ONEBLOCK_UNLOCK ((void *) 2) + +static int __xipram do_xxlock_oneblock(struct map_info *map, struct flchip *chip, + unsigned long adr, int len, void *thunk) +{ + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *extp = cfi->cmdset_priv; + int mdelay; + int ret; + + adr += chip->start; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, adr, FL_LOCKING); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + ENABLE_VPP(map); + xip_disable(map, chip, adr); + + map_write(map, CMD(0x60), adr); + if (thunk == DO_XXLOCK_ONEBLOCK_LOCK) { + map_write(map, CMD(0x01), adr); + chip->state = FL_LOCKING; + } else if (thunk == DO_XXLOCK_ONEBLOCK_UNLOCK) { + map_write(map, CMD(0xD0), adr); + chip->state = FL_UNLOCKING; + } else + BUG(); + + /* + * If Instant Individual Block Locking supported then no need + * to delay. + */ + /* + * Unlocking may take up to 1.4 seconds on some Intel flashes. So + * lets use a max of 1.5 seconds (1500ms) as timeout. + * + * See "Clear Block Lock-Bits Time" on page 40 in + * "3 Volt Intel StrataFlash Memory" 28F128J3,28F640J3,28F320J3 manual + * from February 2003 + */ + mdelay = (!extp || !(extp->FeatureSupport & (1 << 5))) ? 1500 : 0; + + ret = WAIT_TIMEOUT(map, chip, adr, mdelay, mdelay * 1000); + if (ret) { + map_write(map, CMD(0x70), adr); + chip->state = FL_STATUS; + xip_enable(map, chip, adr); + printk(KERN_ERR "%s: block unlock error: (status timeout)\n", map->name); + goto out; + } + + xip_enable(map, chip, adr); + out: DISABLE_VPP(map); + put_chip(map, chip, adr); + mutex_unlock(&chip->mutex); + return ret; +} + +static int cfi_intelext_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + int ret; + +#ifdef DEBUG_LOCK_BITS + printk(KERN_DEBUG "%s: lock status before, ofs=0x%08llx, len=0x%08X\n", + __func__, ofs, len); + cfi_varsize_frob(mtd, do_printlockstatus_oneblock, + ofs, len, NULL); +#endif + + ret = cfi_varsize_frob(mtd, do_xxlock_oneblock, + ofs, len, DO_XXLOCK_ONEBLOCK_LOCK); + +#ifdef DEBUG_LOCK_BITS + printk(KERN_DEBUG "%s: lock status after, ret=%d\n", + __func__, ret); + cfi_varsize_frob(mtd, do_printlockstatus_oneblock, + ofs, len, NULL); +#endif + + return ret; +} + +static int cfi_intelext_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + int ret; + +#ifdef DEBUG_LOCK_BITS + printk(KERN_DEBUG "%s: lock status before, ofs=0x%08llx, len=0x%08X\n", + __func__, ofs, len); + cfi_varsize_frob(mtd, do_printlockstatus_oneblock, + ofs, len, NULL); +#endif + + ret = cfi_varsize_frob(mtd, do_xxlock_oneblock, + ofs, len, DO_XXLOCK_ONEBLOCK_UNLOCK); + +#ifdef DEBUG_LOCK_BITS + printk(KERN_DEBUG "%s: lock status after, ret=%d\n", + __func__, ret); + cfi_varsize_frob(mtd, do_printlockstatus_oneblock, + ofs, len, NULL); +#endif + + return ret; +} + +static int cfi_intelext_is_locked(struct mtd_info *mtd, loff_t ofs, + uint64_t len) +{ + return cfi_varsize_frob(mtd, do_getlockstatus_oneblock, + ofs, len, NULL) ? 1 : 0; +} + +#ifdef CONFIG_MTD_OTP + +typedef int (*otp_op_t)(struct map_info *map, struct flchip *chip, + u_long data_offset, u_char *buf, u_int size, + u_long prot_offset, u_int groupno, u_int groupsize); + +static int __xipram +do_otp_read(struct map_info *map, struct flchip *chip, u_long offset, + u_char *buf, u_int size, u_long prot, u_int grpno, u_int grpsz) +{ + struct cfi_private *cfi = map->fldrv_priv; + int ret; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, chip->start, FL_JEDEC_QUERY); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + /* let's ensure we're not reading back cached data from array mode */ + INVALIDATE_CACHED_RANGE(map, chip->start + offset, size); + + xip_disable(map, chip, chip->start); + if (chip->state != FL_JEDEC_QUERY) { + map_write(map, CMD(0x90), chip->start); + chip->state = FL_JEDEC_QUERY; + } + map_copy_from(map, buf, chip->start + offset, size); + xip_enable(map, chip, chip->start); + + /* then ensure we don't keep OTP data in the cache */ + INVALIDATE_CACHED_RANGE(map, chip->start + offset, size); + + put_chip(map, chip, chip->start); + mutex_unlock(&chip->mutex); + return 0; +} + +static int +do_otp_write(struct map_info *map, struct flchip *chip, u_long offset, + u_char *buf, u_int size, u_long prot, u_int grpno, u_int grpsz) +{ + int ret; + + while (size) { + unsigned long bus_ofs = offset & ~(map_bankwidth(map)-1); + int gap = offset - bus_ofs; + int n = min_t(int, size, map_bankwidth(map)-gap); + map_word datum = map_word_ff(map); + + datum = map_word_load_partial(map, datum, buf, gap, n); + ret = do_write_oneword(map, chip, bus_ofs, datum, FL_OTP_WRITE); + if (ret) + return ret; + + offset += n; + buf += n; + size -= n; + } + + return 0; +} + +static int +do_otp_lock(struct map_info *map, struct flchip *chip, u_long offset, + u_char *buf, u_int size, u_long prot, u_int grpno, u_int grpsz) +{ + struct cfi_private *cfi = map->fldrv_priv; + map_word datum; + + /* make sure area matches group boundaries */ + if (size != grpsz) + return -EXDEV; + + datum = map_word_ff(map); + datum = map_word_clr(map, datum, CMD(1 << grpno)); + return do_write_oneword(map, chip, prot, datum, FL_OTP_WRITE); +} + +static int cfi_intelext_otp_walk(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf, + otp_op_t action, int user_regs) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *extp = cfi->cmdset_priv; + struct flchip *chip; + struct cfi_intelext_otpinfo *otp; + u_long devsize, reg_prot_offset, data_offset; + u_int chip_num, chip_step, field, reg_fact_size, reg_user_size; + u_int groups, groupno, groupsize, reg_fact_groups, reg_user_groups; + int ret; + + *retlen = 0; + + /* Check that we actually have some OTP registers */ + if (!extp || !(extp->FeatureSupport & 64) || !extp->NumProtectionFields) + return -ENODATA; + + /* we need real chips here not virtual ones */ + devsize = (1 << cfi->cfiq->DevSize) * cfi->interleave; + chip_step = devsize >> cfi->chipshift; + chip_num = 0; + + /* Some chips have OTP located in the _top_ partition only. + For example: Intel 28F256L18T (T means top-parameter device) */ + if (cfi->mfr == CFI_MFR_INTEL) { + switch (cfi->id) { + case 0x880b: + case 0x880c: + case 0x880d: + chip_num = chip_step - 1; + } + } + + for ( ; chip_num < cfi->numchips; chip_num += chip_step) { + chip = &cfi->chips[chip_num]; + otp = (struct cfi_intelext_otpinfo *)&extp->extra[0]; + + /* first OTP region */ + field = 0; + reg_prot_offset = extp->ProtRegAddr; + reg_fact_groups = 1; + reg_fact_size = 1 << extp->FactProtRegSize; + reg_user_groups = 1; + reg_user_size = 1 << extp->UserProtRegSize; + + while (len > 0) { + /* flash geometry fixup */ + data_offset = reg_prot_offset + 1; + data_offset *= cfi->interleave * cfi->device_type; + reg_prot_offset *= cfi->interleave * cfi->device_type; + reg_fact_size *= cfi->interleave; + reg_user_size *= cfi->interleave; + + if (user_regs) { + groups = reg_user_groups; + groupsize = reg_user_size; + /* skip over factory reg area */ + groupno = reg_fact_groups; + data_offset += reg_fact_groups * reg_fact_size; + } else { + groups = reg_fact_groups; + groupsize = reg_fact_size; + groupno = 0; + } + + while (len > 0 && groups > 0) { + if (!action) { + /* + * Special case: if action is NULL + * we fill buf with otp_info records. + */ + struct otp_info *otpinfo; + map_word lockword; + len -= sizeof(struct otp_info); + if (len <= 0) + return -ENOSPC; + ret = do_otp_read(map, chip, + reg_prot_offset, + (u_char *)&lockword, + map_bankwidth(map), + 0, 0, 0); + if (ret) + return ret; + otpinfo = (struct otp_info *)buf; + otpinfo->start = from; + otpinfo->length = groupsize; + otpinfo->locked = + !map_word_bitsset(map, lockword, + CMD(1 << groupno)); + from += groupsize; + buf += sizeof(*otpinfo); + *retlen += sizeof(*otpinfo); + } else if (from >= groupsize) { + from -= groupsize; + data_offset += groupsize; + } else { + int size = groupsize; + data_offset += from; + size -= from; + from = 0; + if (size > len) + size = len; + ret = action(map, chip, data_offset, + buf, size, reg_prot_offset, + groupno, groupsize); + if (ret < 0) + return ret; + buf += size; + len -= size; + *retlen += size; + data_offset += size; + } + groupno++; + groups--; + } + + /* next OTP region */ + if (++field == extp->NumProtectionFields) + break; + reg_prot_offset = otp->ProtRegAddr; + reg_fact_groups = otp->FactGroups; + reg_fact_size = 1 << otp->FactProtRegSize; + reg_user_groups = otp->UserGroups; + reg_user_size = 1 << otp->UserProtRegSize; + otp++; + } + } + + return 0; +} + +static int cfi_intelext_read_fact_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, + u_char *buf) +{ + return cfi_intelext_otp_walk(mtd, from, len, retlen, + buf, do_otp_read, 0); +} + +static int cfi_intelext_read_user_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, + u_char *buf) +{ + return cfi_intelext_otp_walk(mtd, from, len, retlen, + buf, do_otp_read, 1); +} + +static int cfi_intelext_write_user_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, + const u_char *buf) +{ + return cfi_intelext_otp_walk(mtd, from, len, retlen, + (u_char *)buf, do_otp_write, 1); +} + +static int cfi_intelext_lock_user_prot_reg(struct mtd_info *mtd, + loff_t from, size_t len) +{ + size_t retlen; + return cfi_intelext_otp_walk(mtd, from, len, &retlen, + NULL, do_otp_lock, 1); +} + +static int cfi_intelext_get_fact_prot_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf) + +{ + return cfi_intelext_otp_walk(mtd, 0, len, retlen, (u_char *)buf, + NULL, 0); +} + +static int cfi_intelext_get_user_prot_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf) +{ + return cfi_intelext_otp_walk(mtd, 0, len, retlen, (u_char *)buf, + NULL, 1); +} + +#endif + +static void cfi_intelext_save_locks(struct mtd_info *mtd) +{ + struct mtd_erase_region_info *region; + int block, status, i; + unsigned long adr; + size_t len; + + for (i = 0; i < mtd->numeraseregions; i++) { + region = &mtd->eraseregions[i]; + if (!region->lockmap) + continue; + + for (block = 0; block < region->numblocks; block++){ + len = region->erasesize; + adr = region->offset + block * len; + + status = cfi_varsize_frob(mtd, + do_getlockstatus_oneblock, adr, len, NULL); + if (status) + set_bit(block, region->lockmap); + else + clear_bit(block, region->lockmap); + } + } +} + +static int cfi_intelext_suspend(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *extp = cfi->cmdset_priv; + int i; + struct flchip *chip; + int ret = 0; + + if ((mtd->flags & MTD_POWERUP_LOCK) + && extp && (extp->FeatureSupport & (1 << 5))) + cfi_intelext_save_locks(mtd); + + for (i=0; !ret && inumchips; i++) { + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + + switch (chip->state) { + case FL_READY: + case FL_STATUS: + case FL_CFI_QUERY: + case FL_JEDEC_QUERY: + if (chip->oldstate == FL_READY) { + /* place the chip in a known state before suspend */ + map_write(map, CMD(0xFF), cfi->chips[i].start); + chip->oldstate = chip->state; + chip->state = FL_PM_SUSPENDED; + /* No need to wake_up() on this state change - + * as the whole point is that nobody can do anything + * with the chip now anyway. + */ + } else { + /* There seems to be an operation pending. We must wait for it. */ + printk(KERN_NOTICE "Flash device refused suspend due to pending operation (oldstate %d)\n", chip->oldstate); + ret = -EAGAIN; + } + break; + default: + /* Should we actually wait? Once upon a time these routines weren't + allowed to. Or should we return -EAGAIN, because the upper layers + ought to have already shut down anything which was using the device + anyway? The latter for now. */ + printk(KERN_NOTICE "Flash device refused suspend due to active operation (state %d)\n", chip->state); + ret = -EAGAIN; + break; + case FL_PM_SUSPENDED: + case FL_SHUTDOWN: + /* + * Already suspended, or put into array mode by the + * reboot notifier ahead of an imminent power-off. + * Either way there is nothing to do. + */ + break; + } + mutex_unlock(&chip->mutex); + } + + /* Unlock the chips again */ + + if (ret) { + for (i--; i >=0; i--) { + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + + if (chip->state == FL_PM_SUSPENDED) { + /* No need to force it into a known state here, + because we're returning failure, and it didn't + get power cycled */ + chip->state = chip->oldstate; + chip->oldstate = FL_READY; + wake_up(&chip->wq); + } + mutex_unlock(&chip->mutex); + } + } + + return ret; +} + +static void cfi_intelext_restore_locks(struct mtd_info *mtd) +{ + struct mtd_erase_region_info *region; + int block, i; + unsigned long adr; + size_t len; + + for (i = 0; i < mtd->numeraseregions; i++) { + region = &mtd->eraseregions[i]; + if (!region->lockmap) + continue; + + for_each_clear_bit(block, region->lockmap, region->numblocks) { + len = region->erasesize; + adr = region->offset + block * len; + cfi_intelext_unlock(mtd, adr, len); + } + } +} + +static void cfi_intelext_resume(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_intelext *extp = cfi->cmdset_priv; + int i; + struct flchip *chip; + + for (i=0; inumchips; i++) { + + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + + /* Go to known state. Chip may have been power cycled */ + if (chip->state == FL_PM_SUSPENDED) { + /* Refresh LH28F640BF Partition Config. Register */ + fixup_LH28F640BF(mtd); + map_write(map, CMD(0xFF), cfi->chips[i].start); + chip->oldstate = chip->state = FL_READY; + wake_up(&chip->wq); + } + + mutex_unlock(&chip->mutex); + } + + if ((mtd->flags & MTD_POWERUP_LOCK) + && extp && (extp->FeatureSupport & (1 << 5))) + cfi_intelext_restore_locks(mtd); +} + +static int cfi_intelext_reset(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int i, ret; + + for (i=0; i < cfi->numchips; i++) { + struct flchip *chip = &cfi->chips[i]; + + /* force the completion of any ongoing operation + and switch to array mode so any bootloader in + flash is accessible for soft reboot. */ + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, chip->start, FL_SHUTDOWN); + if (!ret) { + map_write(map, CMD(0xff), chip->start); + chip->state = FL_SHUTDOWN; + put_chip(map, chip, chip->start); + } + mutex_unlock(&chip->mutex); + } + + return 0; +} + +static int cfi_intelext_reboot(struct notifier_block *nb, unsigned long val, + void *v) +{ + struct mtd_info *mtd; + + mtd = container_of(nb, struct mtd_info, reboot_notifier); + cfi_intelext_reset(mtd); + return NOTIFY_DONE; +} + +static void cfi_intelext_destroy(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct mtd_erase_region_info *region; + int i; + cfi_intelext_reset(mtd); + unregister_reboot_notifier(&mtd->reboot_notifier); + kfree(cfi->cmdset_priv); + kfree(cfi->cfiq); + kfree(cfi->chips[0].priv); + kfree(cfi); + for (i = 0; i < mtd->numeraseregions; i++) { + region = &mtd->eraseregions[i]; + kfree(region->lockmap); + } + kfree(mtd->eraseregions); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Woodhouse et al."); +MODULE_DESCRIPTION("MTD chip driver for Intel/Sharp flash chips"); +MODULE_ALIAS("cfi_cmdset_0003"); +MODULE_ALIAS("cfi_cmdset_0200"); diff --git a/drivers/mtd/chips/cfi_cmdset_0002.c b/drivers/mtd/chips/cfi_cmdset_0002.c new file mode 100644 index 000000000..517db2f27 --- /dev/null +++ b/drivers/mtd/chips/cfi_cmdset_0002.c @@ -0,0 +1,3112 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common Flash Interface support: + * AMD & Fujitsu Standard Vendor Command Set (ID 0x0002) + * + * Copyright (C) 2000 Crossnet Co. + * Copyright (C) 2004 Arcom Control Systems Ltd + * Copyright (C) 2005 MontaVista Software Inc. + * + * 2_by_8 routines added by Simon Munton + * + * 4_by_16 work by Carolyn J. Smith + * + * XIP support hooks by Vitaly Wool (based on code for Intel flash + * by Nicolas Pitre) + * + * 25/09/2008 Christopher Moore: TopBottom fixup for many Macronix with CFI V1.0 + * + * Occasionally maintained by Thayne Harbaugh tharbaugh at lnxi dot com + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AMD_BOOTLOC_BUG +#define FORCE_WORD_WRITE 0 + +#define MAX_RETRIES 3 + +#define SST49LF004B 0x0060 +#define SST49LF040B 0x0050 +#define SST49LF008A 0x005a +#define AT49BV6416 0x00d6 +#define S29GL064N_MN12 0x0c01 + +/* + * Status Register bit description. Used by flash devices that don't + * support DQ polling (e.g. HyperFlash) + */ +#define CFI_SR_DRB BIT(7) +#define CFI_SR_ESB BIT(5) +#define CFI_SR_PSB BIT(4) +#define CFI_SR_WBASB BIT(3) +#define CFI_SR_SLSB BIT(1) + +enum cfi_quirks { + CFI_QUIRK_DQ_TRUE_DATA = BIT(0), +}; + +static int cfi_amdstd_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); +static int cfi_amdstd_write_words(struct mtd_info *, loff_t, size_t, size_t *, const u_char *); +#if !FORCE_WORD_WRITE +static int cfi_amdstd_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *); +#endif +static int cfi_amdstd_erase_chip(struct mtd_info *, struct erase_info *); +static int cfi_amdstd_erase_varsize(struct mtd_info *, struct erase_info *); +static void cfi_amdstd_sync (struct mtd_info *); +static int cfi_amdstd_suspend (struct mtd_info *); +static void cfi_amdstd_resume (struct mtd_info *); +static int cfi_amdstd_reboot(struct notifier_block *, unsigned long, void *); +static int cfi_amdstd_get_fact_prot_info(struct mtd_info *, size_t, + size_t *, struct otp_info *); +static int cfi_amdstd_get_user_prot_info(struct mtd_info *, size_t, + size_t *, struct otp_info *); +static int cfi_amdstd_secsi_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); +static int cfi_amdstd_read_fact_prot_reg(struct mtd_info *, loff_t, size_t, + size_t *, u_char *); +static int cfi_amdstd_read_user_prot_reg(struct mtd_info *, loff_t, size_t, + size_t *, u_char *); +static int cfi_amdstd_write_user_prot_reg(struct mtd_info *, loff_t, size_t, + size_t *, const u_char *); +static int cfi_amdstd_lock_user_prot_reg(struct mtd_info *, loff_t, size_t); + +static int cfi_amdstd_panic_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf); + +static void cfi_amdstd_destroy(struct mtd_info *); + +struct mtd_info *cfi_cmdset_0002(struct map_info *, int); +static struct mtd_info *cfi_amdstd_setup (struct mtd_info *); + +static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode); +static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr); +#include "fwh_lock.h" + +static int cfi_atmel_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int cfi_atmel_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len); + +static int cfi_ppb_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int cfi_ppb_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int cfi_ppb_is_locked(struct mtd_info *mtd, loff_t ofs, uint64_t len); + +static struct mtd_chip_driver cfi_amdstd_chipdrv = { + .probe = NULL, /* Not usable directly */ + .destroy = cfi_amdstd_destroy, + .name = "cfi_cmdset_0002", + .module = THIS_MODULE +}; + +/* + * Use status register to poll for Erase/write completion when DQ is not + * supported. This is indicated by Bit[1:0] of SoftwareFeatures field in + * CFI Primary Vendor-Specific Extended Query table 1.5 + */ +static int cfi_use_status_reg(struct cfi_private *cfi) +{ + struct cfi_pri_amdstd *extp = cfi->cmdset_priv; + u8 poll_mask = CFI_POLL_STATUS_REG | CFI_POLL_DQ; + + return extp && extp->MinorVersion >= '5' && + (extp->SoftwareFeatures & poll_mask) == CFI_POLL_STATUS_REG; +} + +static int cfi_check_err_status(struct map_info *map, struct flchip *chip, + unsigned long adr) +{ + struct cfi_private *cfi = map->fldrv_priv; + map_word status; + + if (!cfi_use_status_reg(cfi)) + return 0; + + cfi_send_gen_cmd(0x70, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + status = map_read(map, adr); + + /* The error bits are invalid while the chip's busy */ + if (!map_word_bitsset(map, status, CMD(CFI_SR_DRB))) + return 0; + + if (map_word_bitsset(map, status, CMD(0x3a))) { + unsigned long chipstatus = MERGESTATUS(status); + + if (chipstatus & CFI_SR_ESB) + pr_err("%s erase operation failed, status %lx\n", + map->name, chipstatus); + if (chipstatus & CFI_SR_PSB) + pr_err("%s program operation failed, status %lx\n", + map->name, chipstatus); + if (chipstatus & CFI_SR_WBASB) + pr_err("%s buffer program command aborted, status %lx\n", + map->name, chipstatus); + if (chipstatus & CFI_SR_SLSB) + pr_err("%s sector write protected, status %lx\n", + map->name, chipstatus); + + /* Erase/Program status bits are set on the operation failure */ + if (chipstatus & (CFI_SR_ESB | CFI_SR_PSB)) + return 1; + } + return 0; +} + +/* #define DEBUG_CFI_FEATURES */ + + +#ifdef DEBUG_CFI_FEATURES +static void cfi_tell_features(struct cfi_pri_amdstd *extp) +{ + const char* erase_suspend[3] = { + "Not supported", "Read only", "Read/write" + }; + const char* top_bottom[6] = { + "No WP", "8x8KiB sectors at top & bottom, no WP", + "Bottom boot", "Top boot", + "Uniform, Bottom WP", "Uniform, Top WP" + }; + + printk(" Silicon revision: %d\n", extp->SiliconRevision >> 1); + printk(" Address sensitive unlock: %s\n", + (extp->SiliconRevision & 1) ? "Not required" : "Required"); + + if (extp->EraseSuspend < ARRAY_SIZE(erase_suspend)) + printk(" Erase Suspend: %s\n", erase_suspend[extp->EraseSuspend]); + else + printk(" Erase Suspend: Unknown value %d\n", extp->EraseSuspend); + + if (extp->BlkProt == 0) + printk(" Block protection: Not supported\n"); + else + printk(" Block protection: %d sectors per group\n", extp->BlkProt); + + + printk(" Temporary block unprotect: %s\n", + extp->TmpBlkUnprotect ? "Supported" : "Not supported"); + printk(" Block protect/unprotect scheme: %d\n", extp->BlkProtUnprot); + printk(" Number of simultaneous operations: %d\n", extp->SimultaneousOps); + printk(" Burst mode: %s\n", + extp->BurstMode ? "Supported" : "Not supported"); + if (extp->PageMode == 0) + printk(" Page mode: Not supported\n"); + else + printk(" Page mode: %d word page\n", extp->PageMode << 2); + + printk(" Vpp Supply Minimum Program/Erase Voltage: %d.%d V\n", + extp->VppMin >> 4, extp->VppMin & 0xf); + printk(" Vpp Supply Maximum Program/Erase Voltage: %d.%d V\n", + extp->VppMax >> 4, extp->VppMax & 0xf); + + if (extp->TopBottom < ARRAY_SIZE(top_bottom)) + printk(" Top/Bottom Boot Block: %s\n", top_bottom[extp->TopBottom]); + else + printk(" Top/Bottom Boot Block: Unknown value %d\n", extp->TopBottom); +} +#endif + +#ifdef AMD_BOOTLOC_BUG +/* Wheee. Bring me the head of someone at AMD. */ +static void fixup_amd_bootblock(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_amdstd *extp = cfi->cmdset_priv; + __u8 major = extp->MajorVersion; + __u8 minor = extp->MinorVersion; + + if (((major << 8) | minor) < 0x3131) { + /* CFI version 1.0 => don't trust bootloc */ + + pr_debug("%s: JEDEC Vendor ID is 0x%02X Device ID is 0x%02X\n", + map->name, cfi->mfr, cfi->id); + + /* AFAICS all 29LV400 with a bottom boot block have a device ID + * of 0x22BA in 16-bit mode and 0xBA in 8-bit mode. + * These were badly detected as they have the 0x80 bit set + * so treat them as a special case. + */ + if (((cfi->id == 0xBA) || (cfi->id == 0x22BA)) && + + /* Macronix added CFI to their 2nd generation + * MX29LV400C B/T but AFAICS no other 29LV400 (AMD, + * Fujitsu, Spansion, EON, ESI and older Macronix) + * has CFI. + * + * Therefore also check the manufacturer. + * This reduces the risk of false detection due to + * the 8-bit device ID. + */ + (cfi->mfr == CFI_MFR_MACRONIX)) { + pr_debug("%s: Macronix MX29LV400C with bottom boot block" + " detected\n", map->name); + extp->TopBottom = 2; /* bottom boot */ + } else + if (cfi->id & 0x80) { + printk(KERN_WARNING "%s: JEDEC Device ID is 0x%02X. Assuming broken CFI table.\n", map->name, cfi->id); + extp->TopBottom = 3; /* top boot */ + } else { + extp->TopBottom = 2; /* bottom boot */ + } + + pr_debug("%s: AMD CFI PRI V%c.%c has no boot block field;" + " deduced %s from Device ID\n", map->name, major, minor, + extp->TopBottom == 2 ? "bottom" : "top"); + } +} +#endif + +#if !FORCE_WORD_WRITE +static void fixup_use_write_buffers(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + if (cfi->mfr == CFI_MFR_AMD && cfi->id == 0x2201) + return; + + if (cfi->cfiq->BufWriteTimeoutTyp) { + pr_debug("Using buffer write method\n"); + mtd->_write = cfi_amdstd_write_buffers; + } +} +#endif /* !FORCE_WORD_WRITE */ + +/* Atmel chips don't use the same PRI format as AMD chips */ +static void fixup_convert_atmel_pri(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_amdstd *extp = cfi->cmdset_priv; + struct cfi_pri_atmel atmel_pri; + + memcpy(&atmel_pri, extp, sizeof(atmel_pri)); + memset((char *)extp + 5, 0, sizeof(*extp) - 5); + + if (atmel_pri.Features & 0x02) + extp->EraseSuspend = 2; + + /* Some chips got it backwards... */ + if (cfi->id == AT49BV6416) { + if (atmel_pri.BottomBoot) + extp->TopBottom = 3; + else + extp->TopBottom = 2; + } else { + if (atmel_pri.BottomBoot) + extp->TopBottom = 2; + else + extp->TopBottom = 3; + } + + /* burst write mode not supported */ + cfi->cfiq->BufWriteTimeoutTyp = 0; + cfi->cfiq->BufWriteTimeoutMax = 0; +} + +static void fixup_use_secsi(struct mtd_info *mtd) +{ + /* Setup for chips with a secsi area */ + mtd->_read_user_prot_reg = cfi_amdstd_secsi_read; + mtd->_read_fact_prot_reg = cfi_amdstd_secsi_read; +} + +static void fixup_use_erase_chip(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + if ((cfi->cfiq->NumEraseRegions == 1) && + ((cfi->cfiq->EraseRegionInfo[0] & 0xffff) == 0)) { + mtd->_erase = cfi_amdstd_erase_chip; + } + +} + +/* + * Some Atmel chips (e.g. the AT49BV6416) power-up with all sectors + * locked by default. + */ +static void fixup_use_atmel_lock(struct mtd_info *mtd) +{ + mtd->_lock = cfi_atmel_lock; + mtd->_unlock = cfi_atmel_unlock; + mtd->flags |= MTD_POWERUP_LOCK; +} + +static void fixup_old_sst_eraseregion(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + /* + * These flashes report two separate eraseblock regions based on the + * sector_erase-size and block_erase-size, although they both operate on the + * same memory. This is not allowed according to CFI, so we just pick the + * sector_erase-size. + */ + cfi->cfiq->NumEraseRegions = 1; +} + +static void fixup_sst39vf(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + fixup_old_sst_eraseregion(mtd); + + cfi->addr_unlock1 = 0x5555; + cfi->addr_unlock2 = 0x2AAA; +} + +static void fixup_sst39vf_rev_b(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + fixup_old_sst_eraseregion(mtd); + + cfi->addr_unlock1 = 0x555; + cfi->addr_unlock2 = 0x2AA; + + cfi->sector_erase_cmd = CMD(0x50); +} + +static void fixup_sst38vf640x_sectorsize(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + fixup_sst39vf_rev_b(mtd); + + /* + * CFI reports 1024 sectors (0x03ff+1) of 64KBytes (0x0100*256) where + * it should report a size of 8KBytes (0x0020*256). + */ + cfi->cfiq->EraseRegionInfo[0] = 0x002003ff; + pr_warn("%s: Bad 38VF640x CFI data; adjusting sector size from 64 to 8KiB\n", + mtd->name); +} + +static void fixup_s29gl064n_sectors(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + if ((cfi->cfiq->EraseRegionInfo[0] & 0xffff) == 0x003f) { + cfi->cfiq->EraseRegionInfo[0] |= 0x0040; + pr_warn("%s: Bad S29GL064N CFI data; adjust from 64 to 128 sectors\n", + mtd->name); + } +} + +static void fixup_s29gl032n_sectors(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + if ((cfi->cfiq->EraseRegionInfo[1] & 0xffff) == 0x007e) { + cfi->cfiq->EraseRegionInfo[1] &= ~0x0040; + pr_warn("%s: Bad S29GL032N CFI data; adjust from 127 to 63 sectors\n", + mtd->name); + } +} + +static void fixup_s29ns512p_sectors(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + /* + * S29NS512P flash uses more than 8bits to report number of sectors, + * which is not permitted by CFI. + */ + cfi->cfiq->EraseRegionInfo[0] = 0x020001ff; + pr_warn("%s: Bad S29NS512P CFI data; adjust to 512 sectors\n", + mtd->name); +} + +static void fixup_quirks(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + if (cfi->mfr == CFI_MFR_AMD && cfi->id == S29GL064N_MN12) + cfi->quirks |= CFI_QUIRK_DQ_TRUE_DATA; +} + +/* Used to fix CFI-Tables of chips without Extended Query Tables */ +static struct cfi_fixup cfi_nopri_fixup_table[] = { + { CFI_MFR_SST, 0x234a, fixup_sst39vf }, /* SST39VF1602 */ + { CFI_MFR_SST, 0x234b, fixup_sst39vf }, /* SST39VF1601 */ + { CFI_MFR_SST, 0x235a, fixup_sst39vf }, /* SST39VF3202 */ + { CFI_MFR_SST, 0x235b, fixup_sst39vf }, /* SST39VF3201 */ + { CFI_MFR_SST, 0x235c, fixup_sst39vf_rev_b }, /* SST39VF3202B */ + { CFI_MFR_SST, 0x235d, fixup_sst39vf_rev_b }, /* SST39VF3201B */ + { CFI_MFR_SST, 0x236c, fixup_sst39vf_rev_b }, /* SST39VF6402B */ + { CFI_MFR_SST, 0x236d, fixup_sst39vf_rev_b }, /* SST39VF6401B */ + { 0, 0, NULL } +}; + +static struct cfi_fixup cfi_fixup_table[] = { + { CFI_MFR_ATMEL, CFI_ID_ANY, fixup_convert_atmel_pri }, +#ifdef AMD_BOOTLOC_BUG + { CFI_MFR_AMD, CFI_ID_ANY, fixup_amd_bootblock }, + { CFI_MFR_AMIC, CFI_ID_ANY, fixup_amd_bootblock }, + { CFI_MFR_MACRONIX, CFI_ID_ANY, fixup_amd_bootblock }, +#endif + { CFI_MFR_AMD, 0x0050, fixup_use_secsi }, + { CFI_MFR_AMD, 0x0053, fixup_use_secsi }, + { CFI_MFR_AMD, 0x0055, fixup_use_secsi }, + { CFI_MFR_AMD, 0x0056, fixup_use_secsi }, + { CFI_MFR_AMD, 0x005C, fixup_use_secsi }, + { CFI_MFR_AMD, 0x005F, fixup_use_secsi }, + { CFI_MFR_AMD, S29GL064N_MN12, fixup_s29gl064n_sectors }, + { CFI_MFR_AMD, 0x1301, fixup_s29gl064n_sectors }, + { CFI_MFR_AMD, 0x1a00, fixup_s29gl032n_sectors }, + { CFI_MFR_AMD, 0x1a01, fixup_s29gl032n_sectors }, + { CFI_MFR_AMD, 0x3f00, fixup_s29ns512p_sectors }, + { CFI_MFR_SST, 0x536a, fixup_sst38vf640x_sectorsize }, /* SST38VF6402 */ + { CFI_MFR_SST, 0x536b, fixup_sst38vf640x_sectorsize }, /* SST38VF6401 */ + { CFI_MFR_SST, 0x536c, fixup_sst38vf640x_sectorsize }, /* SST38VF6404 */ + { CFI_MFR_SST, 0x536d, fixup_sst38vf640x_sectorsize }, /* SST38VF6403 */ +#if !FORCE_WORD_WRITE + { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_write_buffers }, +#endif + { CFI_MFR_ANY, CFI_ID_ANY, fixup_quirks }, + { 0, 0, NULL } +}; +static struct cfi_fixup jedec_fixup_table[] = { + { CFI_MFR_SST, SST49LF004B, fixup_use_fwh_lock }, + { CFI_MFR_SST, SST49LF040B, fixup_use_fwh_lock }, + { CFI_MFR_SST, SST49LF008A, fixup_use_fwh_lock }, + { 0, 0, NULL } +}; + +static struct cfi_fixup fixup_table[] = { + /* The CFI vendor ids and the JEDEC vendor IDs appear + * to be common. It is like the devices id's are as + * well. This table is to pick all cases where + * we know that is the case. + */ + { CFI_MFR_ANY, CFI_ID_ANY, fixup_use_erase_chip }, + { CFI_MFR_ATMEL, AT49BV6416, fixup_use_atmel_lock }, + { 0, 0, NULL } +}; + + +static void cfi_fixup_major_minor(struct cfi_private *cfi, + struct cfi_pri_amdstd *extp) +{ + if (cfi->mfr == CFI_MFR_SAMSUNG) { + if ((extp->MajorVersion == '0' && extp->MinorVersion == '0') || + (extp->MajorVersion == '3' && extp->MinorVersion == '3')) { + /* + * Samsung K8P2815UQB and K8D6x16UxM chips + * report major=0 / minor=0. + * K8D3x16UxC chips report major=3 / minor=3. + */ + printk(KERN_NOTICE " Fixing Samsung's Amd/Fujitsu" + " Extended Query version to 1.%c\n", + extp->MinorVersion); + extp->MajorVersion = '1'; + } + } + + /* + * SST 38VF640x chips report major=0xFF / minor=0xFF. + */ + if (cfi->mfr == CFI_MFR_SST && (cfi->id >> 4) == 0x0536) { + extp->MajorVersion = '1'; + extp->MinorVersion = '0'; + } +} + +static int is_m29ew(struct cfi_private *cfi) +{ + if (cfi->mfr == CFI_MFR_INTEL && + ((cfi->device_type == CFI_DEVICETYPE_X8 && (cfi->id & 0xff) == 0x7e) || + (cfi->device_type == CFI_DEVICETYPE_X16 && cfi->id == 0x227e))) + return 1; + return 0; +} + +/* + * From TN-13-07: Patching the Linux Kernel and U-Boot for M29 Flash, page 20: + * Some revisions of the M29EW suffer from erase suspend hang ups. In + * particular, it can occur when the sequence + * Erase Confirm -> Suspend -> Program -> Resume + * causes a lockup due to internal timing issues. The consequence is that the + * erase cannot be resumed without inserting a dummy command after programming + * and prior to resuming. [...] The work-around is to issue a dummy write cycle + * that writes an F0 command code before the RESUME command. + */ +static void cfi_fixup_m29ew_erase_suspend(struct map_info *map, + unsigned long adr) +{ + struct cfi_private *cfi = map->fldrv_priv; + /* before resume, insert a dummy 0xF0 cycle for Micron M29EW devices */ + if (is_m29ew(cfi)) + map_write(map, CMD(0xF0), adr); +} + +/* + * From TN-13-07: Patching the Linux Kernel and U-Boot for M29 Flash, page 22: + * + * Some revisions of the M29EW (for example, A1 and A2 step revisions) + * are affected by a problem that could cause a hang up when an ERASE SUSPEND + * command is issued after an ERASE RESUME operation without waiting for a + * minimum delay. The result is that once the ERASE seems to be completed + * (no bits are toggling), the contents of the Flash memory block on which + * the erase was ongoing could be inconsistent with the expected values + * (typically, the array value is stuck to the 0xC0, 0xC4, 0x80, or 0x84 + * values), causing a consequent failure of the ERASE operation. + * The occurrence of this issue could be high, especially when file system + * operations on the Flash are intensive. As a result, it is recommended + * that a patch be applied. Intensive file system operations can cause many + * calls to the garbage routine to free Flash space (also by erasing physical + * Flash blocks) and as a result, many consecutive SUSPEND and RESUME + * commands can occur. The problem disappears when a delay is inserted after + * the RESUME command by using the udelay() function available in Linux. + * The DELAY value must be tuned based on the customer's platform. + * The maximum value that fixes the problem in all cases is 500us. + * But, in our experience, a delay of 30 µs to 50 µs is sufficient + * in most cases. + * We have chosen 500µs because this latency is acceptable. + */ +static void cfi_fixup_m29ew_delay_after_resume(struct cfi_private *cfi) +{ + /* + * Resolving the Delay After Resume Issue see Micron TN-13-07 + * Worst case delay must be 500µs but 30-50µs should be ok as well + */ + if (is_m29ew(cfi)) + cfi_udelay(500); +} + +struct mtd_info *cfi_cmdset_0002(struct map_info *map, int primary) +{ + struct cfi_private *cfi = map->fldrv_priv; + struct device_node __maybe_unused *np = map->device_node; + struct mtd_info *mtd; + int i; + + mtd = kzalloc_obj(*mtd); + if (!mtd) + return NULL; + mtd->priv = map; + mtd->type = MTD_NORFLASH; + + /* Fill in the default mtd operations */ + mtd->_erase = cfi_amdstd_erase_varsize; + mtd->_write = cfi_amdstd_write_words; + mtd->_read = cfi_amdstd_read; + mtd->_sync = cfi_amdstd_sync; + mtd->_suspend = cfi_amdstd_suspend; + mtd->_resume = cfi_amdstd_resume; + mtd->_read_user_prot_reg = cfi_amdstd_read_user_prot_reg; + mtd->_read_fact_prot_reg = cfi_amdstd_read_fact_prot_reg; + mtd->_get_fact_prot_info = cfi_amdstd_get_fact_prot_info; + mtd->_get_user_prot_info = cfi_amdstd_get_user_prot_info; + mtd->_write_user_prot_reg = cfi_amdstd_write_user_prot_reg; + mtd->_lock_user_prot_reg = cfi_amdstd_lock_user_prot_reg; + mtd->flags = MTD_CAP_NORFLASH; + mtd->name = map->name; + mtd->writesize = 1; + mtd->writebufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; + + pr_debug("MTD %s(): write buffer size %d\n", __func__, + mtd->writebufsize); + + mtd->_panic_write = cfi_amdstd_panic_write; + mtd->reboot_notifier.notifier_call = cfi_amdstd_reboot; + + if (cfi->cfi_mode==CFI_MODE_CFI){ + unsigned char bootloc; + __u16 adr = primary?cfi->cfiq->P_ADR:cfi->cfiq->A_ADR; + struct cfi_pri_amdstd *extp; + + extp = (struct cfi_pri_amdstd*)cfi_read_pri(map, adr, sizeof(*extp), "Amd/Fujitsu"); + if (extp) { + /* + * It's a real CFI chip, not one for which the probe + * routine faked a CFI structure. + */ + cfi_fixup_major_minor(cfi, extp); + + /* + * Valid primary extension versions are: 1.0, 1.1, 1.2, 1.3, 1.4, 1.5 + * see: http://cs.ozerki.net/zap/pub/axim-x5/docs/cfi_r20.pdf, page 19 + * http://www.spansion.com/Support/AppNotes/cfi_100_20011201.pdf + * http://www.spansion.com/Support/Datasheets/s29ws-p_00_a12_e.pdf + * http://www.spansion.com/Support/Datasheets/S29GL_128S_01GS_00_02_e.pdf + */ + if (extp->MajorVersion != '1' || + (extp->MajorVersion == '1' && (extp->MinorVersion < '0' || extp->MinorVersion > '5'))) { + printk(KERN_ERR " Unknown Amd/Fujitsu Extended Query " + "version %c.%c (%#02x/%#02x).\n", + extp->MajorVersion, extp->MinorVersion, + extp->MajorVersion, extp->MinorVersion); + kfree(extp); + goto free_mtd; + } + + printk(KERN_INFO " Amd/Fujitsu Extended Query version %c.%c.\n", + extp->MajorVersion, extp->MinorVersion); + + /* Install our own private info structure */ + cfi->cmdset_priv = extp; + + /* Apply cfi device specific fixups */ + cfi_fixup(mtd, cfi_fixup_table); + +#ifdef DEBUG_CFI_FEATURES + /* Tell the user about it in lots of lovely detail */ + cfi_tell_features(extp); +#endif + +#ifdef CONFIG_OF + if (np && of_property_read_bool( + np, "use-advanced-sector-protection") + && extp->BlkProtUnprot == 8) { + printk(KERN_INFO " Advanced Sector Protection (PPB Locking) supported\n"); + mtd->_lock = cfi_ppb_lock; + mtd->_unlock = cfi_ppb_unlock; + mtd->_is_locked = cfi_ppb_is_locked; + } +#endif + + bootloc = extp->TopBottom; + if ((bootloc < 2) || (bootloc > 5)) { + printk(KERN_WARNING "%s: CFI contains unrecognised boot " + "bank location (%d). Assuming bottom.\n", + map->name, bootloc); + bootloc = 2; + } + + if (bootloc == 3 && cfi->cfiq->NumEraseRegions > 1) { + printk(KERN_WARNING "%s: Swapping erase regions for top-boot CFI table.\n", map->name); + + for (i=0; icfiq->NumEraseRegions / 2; i++) { + int j = (cfi->cfiq->NumEraseRegions-1)-i; + + swap(cfi->cfiq->EraseRegionInfo[i], + cfi->cfiq->EraseRegionInfo[j]); + } + } + /* Set the default CFI lock/unlock addresses */ + cfi->addr_unlock1 = 0x555; + cfi->addr_unlock2 = 0x2aa; + } + cfi_fixup(mtd, cfi_nopri_fixup_table); + + if (!cfi->addr_unlock1 || !cfi->addr_unlock2) + goto free_mtd; + + } /* CFI mode */ + else if (cfi->cfi_mode == CFI_MODE_JEDEC) { + /* Apply jedec specific fixups */ + cfi_fixup(mtd, jedec_fixup_table); + } + /* Apply generic fixups */ + cfi_fixup(mtd, fixup_table); + + for (i=0; i< cfi->numchips; i++) { + cfi->chips[i].word_write_time = 1<cfiq->WordWriteTimeoutTyp; + cfi->chips[i].buffer_write_time = 1<cfiq->BufWriteTimeoutTyp; + cfi->chips[i].erase_time = 1<cfiq->BlockEraseTimeoutTyp; + /* + * First calculate the timeout max according to timeout field + * of struct cfi_ident that probed from chip's CFI aera, if + * available. Specify a minimum of 2000us, in case the CFI data + * is wrong. + */ + if (cfi->cfiq->BufWriteTimeoutTyp && + cfi->cfiq->BufWriteTimeoutMax) + cfi->chips[i].buffer_write_time_max = + 1 << (cfi->cfiq->BufWriteTimeoutTyp + + cfi->cfiq->BufWriteTimeoutMax); + else + cfi->chips[i].buffer_write_time_max = 0; + + cfi->chips[i].buffer_write_time_max = + max(cfi->chips[i].buffer_write_time_max, 2000); + + cfi->chips[i].ref_point_counter = 0; + init_waitqueue_head(&(cfi->chips[i].wq)); + } + + map->fldrv = &cfi_amdstd_chipdrv; + + return cfi_amdstd_setup(mtd); + +free_mtd: + kfree(mtd); + return NULL; +} +struct mtd_info *cfi_cmdset_0006(struct map_info *map, int primary) __attribute__((alias("cfi_cmdset_0002"))); +struct mtd_info *cfi_cmdset_0701(struct map_info *map, int primary) __attribute__((alias("cfi_cmdset_0002"))); +EXPORT_SYMBOL_GPL(cfi_cmdset_0002); +EXPORT_SYMBOL_GPL(cfi_cmdset_0006); +EXPORT_SYMBOL_GPL(cfi_cmdset_0701); + +static struct mtd_info *cfi_amdstd_setup(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long devsize = (1<cfiq->DevSize) * cfi->interleave; + unsigned long offset = 0; + int i,j; + + printk(KERN_NOTICE "number of %s chips: %d\n", + (cfi->cfi_mode == CFI_MODE_CFI)?"CFI":"JEDEC",cfi->numchips); + /* Select the correct geometry setup */ + mtd->size = devsize * cfi->numchips; + + mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips; + mtd->eraseregions = kmalloc_objs(struct mtd_erase_region_info, + mtd->numeraseregions); + if (!mtd->eraseregions) + goto setup_err; + + for (i=0; icfiq->NumEraseRegions; i++) { + unsigned long ernum, ersize; + ersize = ((cfi->cfiq->EraseRegionInfo[i] >> 8) & ~0xff) * cfi->interleave; + ernum = (cfi->cfiq->EraseRegionInfo[i] & 0xffff) + 1; + + if (mtd->erasesize < ersize) { + mtd->erasesize = ersize; + } + for (j=0; jnumchips; j++) { + mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].offset = (j*devsize)+offset; + mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].erasesize = ersize; + mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].numblocks = ernum; + } + offset += (ersize * ernum); + } + if (offset != devsize) { + /* Argh */ + printk(KERN_WARNING "Sum of regions (%lx) != total size of set of interleaved chips (%lx)\n", offset, devsize); + goto setup_err; + } + + __module_get(THIS_MODULE); + register_reboot_notifier(&mtd->reboot_notifier); + return mtd; + + setup_err: + kfree(mtd->eraseregions); + kfree(mtd); + kfree(cfi->cmdset_priv); + return NULL; +} + +/* + * Return true if the chip is ready and has the correct value. + * + * Ready is one of: read mode, query mode, erase-suspend-read mode (in any + * non-suspended sector) and is indicated by no toggle bits toggling. + * + * Error are indicated by toggling bits or bits held with the wrong value, + * or with bits toggling. + * + * Note that anything more complicated than checking if no bits are toggling + * (including checking DQ5 for an error status) is tricky to get working + * correctly and is therefore not done (particularly with interleaved chips + * as each chip must be checked independently of the others). + */ +static int __xipram chip_ready(struct map_info *map, struct flchip *chip, + unsigned long addr, map_word *expected) +{ + struct cfi_private *cfi = map->fldrv_priv; + map_word oldd, curd; + int ret; + + if (cfi_use_status_reg(cfi)) { + map_word ready = CMD(CFI_SR_DRB); + /* + * For chips that support status register, check device + * ready bit + */ + cfi_send_gen_cmd(0x70, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + curd = map_read(map, addr); + + return map_word_andequal(map, curd, ready, ready); + } + + oldd = map_read(map, addr); + curd = map_read(map, addr); + + ret = map_word_equal(map, oldd, curd); + + if (!ret || !expected) + return ret; + + return map_word_equal(map, curd, *expected); +} + +static int __xipram chip_good(struct map_info *map, struct flchip *chip, + unsigned long addr, map_word *expected) +{ + struct cfi_private *cfi = map->fldrv_priv; + map_word *datum = expected; + + if (cfi->quirks & CFI_QUIRK_DQ_TRUE_DATA) + datum = NULL; + + return chip_ready(map, chip, addr, datum); +} + +static int get_chip(struct map_info *map, struct flchip *chip, unsigned long adr, int mode) +{ + DECLARE_WAITQUEUE(wait, current); + struct cfi_private *cfi = map->fldrv_priv; + unsigned long timeo; + struct cfi_pri_amdstd *cfip = (struct cfi_pri_amdstd *)cfi->cmdset_priv; + + resettime: + timeo = jiffies + HZ; + retry: + switch (chip->state) { + + case FL_STATUS: + for (;;) { + if (chip_ready(map, chip, adr, NULL)) + break; + + if (time_after(jiffies, timeo)) { + printk(KERN_ERR "Waiting for chip to be ready timed out.\n"); + return -EIO; + } + mutex_unlock(&chip->mutex); + cfi_udelay(1); + mutex_lock(&chip->mutex); + /* Someone else might have been playing with it. */ + goto retry; + } + return 0; + + case FL_READY: + case FL_CFI_QUERY: + case FL_JEDEC_QUERY: + return 0; + + case FL_ERASING: + if (!cfip || !(cfip->EraseSuspend & (0x1|0x2)) || + !(mode == FL_READY || mode == FL_POINT || + (mode == FL_WRITING && (cfip->EraseSuspend & 0x2)))) + goto sleep; + + /* Do not allow suspend iff read/write to EB address */ + if ((adr & chip->in_progress_block_mask) == + chip->in_progress_block_addr) + goto sleep; + + /* Erase suspend */ + /* It's harmless to issue the Erase-Suspend and Erase-Resume + * commands when the erase algorithm isn't in progress. */ + map_write(map, CMD(0xB0), chip->in_progress_block_addr); + chip->oldstate = FL_ERASING; + chip->state = FL_ERASE_SUSPENDING; + chip->erase_suspended = 1; + for (;;) { + if (chip_ready(map, chip, adr, NULL)) + break; + + if (time_after(jiffies, timeo)) { + /* Should have suspended the erase by now. + * Send an Erase-Resume command as either + * there was an error (so leave the erase + * routine to recover from it) or we trying to + * use the erase-in-progress sector. */ + put_chip(map, chip, adr); + printk(KERN_ERR "MTD %s(): chip not ready after erase suspend\n", __func__); + return -EIO; + } + + mutex_unlock(&chip->mutex); + cfi_udelay(1); + mutex_lock(&chip->mutex); + /* Nobody will touch it while it's in state FL_ERASE_SUSPENDING. + So we can just loop here. */ + } + chip->state = FL_READY; + return 0; + + case FL_XIP_WHILE_ERASING: + if (mode != FL_READY && mode != FL_POINT && + (!cfip || !(cfip->EraseSuspend&2))) + goto sleep; + chip->oldstate = chip->state; + chip->state = FL_READY; + return 0; + + case FL_SHUTDOWN: + /* The machine is rebooting */ + return -EIO; + + case FL_POINT: + /* Only if there's no operation suspended... */ + if (mode == FL_READY && chip->oldstate == FL_READY) + return 0; + fallthrough; + default: + sleep: + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + mutex_lock(&chip->mutex); + goto resettime; + } +} + + +static void put_chip(struct map_info *map, struct flchip *chip, unsigned long adr) +{ + struct cfi_private *cfi = map->fldrv_priv; + + switch(chip->oldstate) { + case FL_ERASING: + cfi_fixup_m29ew_erase_suspend(map, + chip->in_progress_block_addr); + map_write(map, cfi->sector_erase_cmd, chip->in_progress_block_addr); + cfi_fixup_m29ew_delay_after_resume(cfi); + chip->oldstate = FL_READY; + chip->state = FL_ERASING; + break; + + case FL_XIP_WHILE_ERASING: + chip->state = chip->oldstate; + chip->oldstate = FL_READY; + break; + + case FL_READY: + case FL_STATUS: + break; + default: + printk(KERN_ERR "MTD: put_chip() called with oldstate %d!!\n", chip->oldstate); + } + wake_up(&chip->wq); +} + +#ifdef CONFIG_MTD_XIP + +/* + * No interrupt what so ever can be serviced while the flash isn't in array + * mode. This is ensured by the xip_disable() and xip_enable() functions + * enclosing any code path where the flash is known not to be in array mode. + * And within a XIP disabled code path, only functions marked with __xipram + * may be called and nothing else (it's a good thing to inspect generated + * assembly to make sure inline functions were actually inlined and that gcc + * didn't emit calls to its own support functions). Also configuring MTD CFI + * support to a single buswidth and a single interleave is also recommended. + */ + +static void xip_disable(struct map_info *map, struct flchip *chip, + unsigned long adr) +{ + /* TODO: chips with no XIP use should ignore and return */ + (void) map_read(map, adr); /* ensure mmu mapping is up to date */ + local_irq_disable(); +} + +static void __xipram xip_enable(struct map_info *map, struct flchip *chip, + unsigned long adr) +{ + struct cfi_private *cfi = map->fldrv_priv; + + if (chip->state != FL_POINT && chip->state != FL_READY) { + map_write(map, CMD(0xf0), adr); + chip->state = FL_READY; + } + (void) map_read(map, adr); + xip_iprefetch(); + local_irq_enable(); +} + +/* + * When a delay is required for the flash operation to complete, the + * xip_udelay() function is polling for both the given timeout and pending + * (but still masked) hardware interrupts. Whenever there is an interrupt + * pending then the flash erase operation is suspended, array mode restored + * and interrupts unmasked. Task scheduling might also happen at that + * point. The CPU eventually returns from the interrupt or the call to + * schedule() and the suspended flash operation is resumed for the remaining + * of the delay period. + * + * Warning: this function _will_ fool interrupt latency tracing tools. + */ + +static void __xipram xip_udelay(struct map_info *map, struct flchip *chip, + unsigned long adr, int usec) +{ + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_pri_amdstd *extp = cfi->cmdset_priv; + map_word status, OK = CMD(0x80); + unsigned long suspended, start = xip_currtime(); + flstate_t oldstate; + + do { + cpu_relax(); + if (xip_irqpending() && extp && + ((chip->state == FL_ERASING && (extp->EraseSuspend & 2))) && + (cfi_interleave_is_1(cfi) || chip->oldstate == FL_READY)) { + /* + * Let's suspend the erase operation when supported. + * Note that we currently don't try to suspend + * interleaved chips if there is already another + * operation suspended (imagine what happens + * when one chip was already done with the current + * operation while another chip suspended it, then + * we resume the whole thing at once). Yes, it + * can happen! + */ + map_write(map, CMD(0xb0), adr); + usec -= xip_elapsed_since(start); + suspended = xip_currtime(); + do { + if (xip_elapsed_since(suspended) > 100000) { + /* + * The chip doesn't want to suspend + * after waiting for 100 msecs. + * This is a critical error but there + * is not much we can do here. + */ + return; + } + status = map_read(map, adr); + } while (!map_word_andequal(map, status, OK, OK)); + + /* Suspend succeeded */ + oldstate = chip->state; + if (!map_word_bitsset(map, status, CMD(0x40))) + break; + chip->state = FL_XIP_WHILE_ERASING; + chip->erase_suspended = 1; + map_write(map, CMD(0xf0), adr); + (void) map_read(map, adr); + xip_iprefetch(); + local_irq_enable(); + mutex_unlock(&chip->mutex); + xip_iprefetch(); + cond_resched(); + + /* + * We're back. However someone else might have + * decided to go write to the chip if we are in + * a suspended erase state. If so let's wait + * until it's done. + */ + mutex_lock(&chip->mutex); + while (chip->state != FL_XIP_WHILE_ERASING) { + DECLARE_WAITQUEUE(wait, current); + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + mutex_lock(&chip->mutex); + } + /* Disallow XIP again */ + local_irq_disable(); + + /* Correct Erase Suspend Hangups for M29EW */ + cfi_fixup_m29ew_erase_suspend(map, adr); + /* Resume the write or erase operation */ + map_write(map, cfi->sector_erase_cmd, adr); + chip->state = oldstate; + start = xip_currtime(); + } else if (usec >= 1000000/HZ) { + /* + * Try to save on CPU power when waiting delay + * is at least a system timer tick period. + * No need to be extremely accurate here. + */ + xip_cpu_idle(); + } + status = map_read(map, adr); + } while (!map_word_andequal(map, status, OK, OK) + && xip_elapsed_since(start) < usec); +} + +#define UDELAY(map, chip, adr, usec) xip_udelay(map, chip, adr, usec) + +/* + * The INVALIDATE_CACHED_RANGE() macro is normally used in parallel while + * the flash is actively programming or erasing since we have to poll for + * the operation to complete anyway. We can't do that in a generic way with + * a XIP setup so do it before the actual flash operation in this case + * and stub it out from INVALIDATE_CACHE_UDELAY. + */ +#define XIP_INVAL_CACHED_RANGE(map, from, size) \ + INVALIDATE_CACHED_RANGE(map, from, size) + +#define INVALIDATE_CACHE_UDELAY(map, chip, adr, len, usec) \ + UDELAY(map, chip, adr, usec) + +/* + * Extra notes: + * + * Activating this XIP support changes the way the code works a bit. For + * example the code to suspend the current process when concurrent access + * happens is never executed because xip_udelay() will always return with the + * same chip state as it was entered with. This is why there is no care for + * the presence of add_wait_queue() or schedule() calls from within a couple + * xip_disable()'d areas of code, like in do_erase_oneblock for example. + * The queueing and scheduling are always happening within xip_udelay(). + * + * Similarly, get_chip() and put_chip() just happen to always be executed + * with chip->state set to FL_READY (or FL_XIP_WHILE_*) where flash state + * is in array mode, therefore never executing many cases therein and not + * causing any problem with XIP. + */ + +#else + +#define xip_disable(map, chip, adr) +#define xip_enable(map, chip, adr) +#define XIP_INVAL_CACHED_RANGE(x...) + +#define UDELAY(map, chip, adr, usec) \ +do { \ + mutex_unlock(&chip->mutex); \ + cfi_udelay(usec); \ + mutex_lock(&chip->mutex); \ +} while (0) + +#define INVALIDATE_CACHE_UDELAY(map, chip, adr, len, usec) \ +do { \ + mutex_unlock(&chip->mutex); \ + INVALIDATE_CACHED_RANGE(map, adr, len); \ + cfi_udelay(usec); \ + mutex_lock(&chip->mutex); \ +} while (0) + +#endif + +static inline int do_read_onechip(struct map_info *map, struct flchip *chip, loff_t adr, size_t len, u_char *buf) +{ + unsigned long cmd_addr; + struct cfi_private *cfi = map->fldrv_priv; + int ret; + + adr += chip->start; + + /* Ensure cmd read/writes are aligned. */ + cmd_addr = adr & ~(map_bankwidth(map)-1); + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, cmd_addr, FL_READY); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + if (chip->state != FL_POINT && chip->state != FL_READY) { + map_write(map, CMD(0xf0), cmd_addr); + chip->state = FL_READY; + } + + map_copy_from(map, buf, adr, len); + + put_chip(map, chip, cmd_addr); + + mutex_unlock(&chip->mutex); + return 0; +} + + +static int cfi_amdstd_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long ofs; + int chipnum; + int ret = 0; + + /* ofs: offset within the first chip that the first read should start */ + chipnum = (from >> cfi->chipshift); + ofs = from - (chipnum << cfi->chipshift); + + while (len) { + unsigned long thislen; + + if (chipnum >= cfi->numchips) + break; + + if ((len + ofs -1) >> cfi->chipshift) + thislen = (1<chipshift) - ofs; + else + thislen = len; + + ret = do_read_onechip(map, &cfi->chips[chipnum], ofs, thislen, buf); + if (ret) + break; + + *retlen += thislen; + len -= thislen; + buf += thislen; + + ofs = 0; + chipnum++; + } + return ret; +} + +typedef int (*otp_op_t)(struct map_info *map, struct flchip *chip, + loff_t adr, size_t len, u_char *buf, size_t grouplen); + +static inline void otp_enter(struct map_info *map, struct flchip *chip, + loff_t adr, size_t len) +{ + struct cfi_private *cfi = map->fldrv_priv; + + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x88, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + + INVALIDATE_CACHED_RANGE(map, chip->start + adr, len); +} + +static inline void otp_exit(struct map_info *map, struct flchip *chip, + loff_t adr, size_t len) +{ + struct cfi_private *cfi = map->fldrv_priv; + + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x90, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x00, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + + INVALIDATE_CACHED_RANGE(map, chip->start + adr, len); +} + +static inline int do_read_secsi_onechip(struct map_info *map, + struct flchip *chip, loff_t adr, + size_t len, u_char *buf, + size_t grouplen) +{ + DECLARE_WAITQUEUE(wait, current); + + retry: + mutex_lock(&chip->mutex); + + if (chip->state != FL_READY){ + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + + mutex_unlock(&chip->mutex); + + schedule(); + remove_wait_queue(&chip->wq, &wait); + + goto retry; + } + + adr += chip->start; + + chip->state = FL_READY; + + otp_enter(map, chip, adr, len); + map_copy_from(map, buf, adr, len); + otp_exit(map, chip, adr, len); + + wake_up(&chip->wq); + mutex_unlock(&chip->mutex); + + return 0; +} + +static int cfi_amdstd_secsi_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long ofs; + int chipnum; + int ret = 0; + + /* ofs: offset within the first chip that the first read should start */ + /* 8 secsi bytes per chip */ + chipnum=from>>3; + ofs=from & 7; + + while (len) { + unsigned long thislen; + + if (chipnum >= cfi->numchips) + break; + + if ((len + ofs -1) >> 3) + thislen = (1<<3) - ofs; + else + thislen = len; + + ret = do_read_secsi_onechip(map, &cfi->chips[chipnum], ofs, + thislen, buf, 0); + if (ret) + break; + + *retlen += thislen; + len -= thislen; + buf += thislen; + + ofs = 0; + chipnum++; + } + return ret; +} + +static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip, + unsigned long adr, map_word datum, + int mode); + +static int do_otp_write(struct map_info *map, struct flchip *chip, loff_t adr, + size_t len, u_char *buf, size_t grouplen) +{ + int ret; + while (len) { + unsigned long bus_ofs = adr & ~(map_bankwidth(map)-1); + int gap = adr - bus_ofs; + int n = min_t(int, len, map_bankwidth(map) - gap); + map_word datum = map_word_ff(map); + + if (n != map_bankwidth(map)) { + /* partial write of a word, load old contents */ + otp_enter(map, chip, bus_ofs, map_bankwidth(map)); + datum = map_read(map, bus_ofs); + otp_exit(map, chip, bus_ofs, map_bankwidth(map)); + } + + datum = map_word_load_partial(map, datum, buf, gap, n); + ret = do_write_oneword(map, chip, bus_ofs, datum, FL_OTP_WRITE); + if (ret) + return ret; + + adr += n; + buf += n; + len -= n; + } + + return 0; +} + +static int do_otp_lock(struct map_info *map, struct flchip *chip, loff_t adr, + size_t len, u_char *buf, size_t grouplen) +{ + struct cfi_private *cfi = map->fldrv_priv; + uint8_t lockreg; + unsigned long timeo; + int ret; + + /* make sure area matches group boundaries */ + if ((adr != 0) || (len != grouplen)) + return -EINVAL; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, chip->start, FL_LOCKING); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + chip->state = FL_LOCKING; + + /* Enter lock register command */ + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x40, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + + /* read lock register */ + lockreg = cfi_read_query(map, 0); + + /* set bit 0 to protect extended memory block */ + lockreg &= ~0x01; + + /* set bit 0 to protect extended memory block */ + /* write lock register */ + map_write(map, CMD(0xA0), chip->start); + map_write(map, CMD(lockreg), chip->start); + + /* wait for chip to become ready */ + timeo = jiffies + msecs_to_jiffies(2); + for (;;) { + if (chip_ready(map, chip, adr, NULL)) + break; + + if (time_after(jiffies, timeo)) { + pr_err("Waiting for chip to be ready timed out.\n"); + ret = -EIO; + break; + } + UDELAY(map, chip, 0, 1); + } + + /* exit protection commands */ + map_write(map, CMD(0x90), chip->start); + map_write(map, CMD(0x00), chip->start); + + chip->state = FL_READY; + put_chip(map, chip, chip->start); + mutex_unlock(&chip->mutex); + + return ret; +} + +static int cfi_amdstd_otp_walk(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf, + otp_op_t action, int user_regs) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int ofs_factor = cfi->interleave * cfi->device_type; + unsigned long base; + int chipnum; + struct flchip *chip; + uint8_t otp, lockreg; + int ret; + + size_t user_size, factory_size, otpsize; + loff_t user_offset, factory_offset, otpoffset; + int user_locked = 0, otplocked; + + *retlen = 0; + + for (chipnum = 0; chipnum < cfi->numchips; chipnum++) { + chip = &cfi->chips[chipnum]; + factory_size = 0; + user_size = 0; + + /* Micron M29EW family */ + if (is_m29ew(cfi)) { + base = chip->start; + + /* check whether secsi area is factory locked + or user lockable */ + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, base, FL_CFI_QUERY); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + cfi_qry_mode_on(base, map, cfi); + otp = cfi_read_query(map, base + 0x3 * ofs_factor); + cfi_qry_mode_off(base, map, cfi); + put_chip(map, chip, base); + mutex_unlock(&chip->mutex); + + if (otp & 0x80) { + /* factory locked */ + factory_offset = 0; + factory_size = 0x100; + } else { + /* customer lockable */ + user_offset = 0; + user_size = 0x100; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, base, FL_LOCKING); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + /* Enter lock register command */ + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, + chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, + chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x40, cfi->addr_unlock1, + chip->start, map, cfi, + cfi->device_type, NULL); + /* read lock register */ + lockreg = cfi_read_query(map, 0); + /* exit protection commands */ + map_write(map, CMD(0x90), chip->start); + map_write(map, CMD(0x00), chip->start); + put_chip(map, chip, chip->start); + mutex_unlock(&chip->mutex); + + user_locked = ((lockreg & 0x01) == 0x00); + } + } + + otpsize = user_regs ? user_size : factory_size; + if (!otpsize) + continue; + otpoffset = user_regs ? user_offset : factory_offset; + otplocked = user_regs ? user_locked : 1; + + if (!action) { + /* return otpinfo */ + struct otp_info *otpinfo; + len -= sizeof(*otpinfo); + if (len <= 0) + return -ENOSPC; + otpinfo = (struct otp_info *)buf; + otpinfo->start = from; + otpinfo->length = otpsize; + otpinfo->locked = otplocked; + buf += sizeof(*otpinfo); + *retlen += sizeof(*otpinfo); + from += otpsize; + } else if ((from < otpsize) && (len > 0)) { + size_t size; + size = (len < otpsize - from) ? len : otpsize - from; + ret = action(map, chip, otpoffset + from, size, buf, + otpsize); + if (ret < 0) + return ret; + + buf += size; + len -= size; + *retlen += size; + from = 0; + } else { + from -= otpsize; + } + } + return 0; +} + +static int cfi_amdstd_get_fact_prot_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf) +{ + return cfi_amdstd_otp_walk(mtd, 0, len, retlen, (u_char *)buf, + NULL, 0); +} + +static int cfi_amdstd_get_user_prot_info(struct mtd_info *mtd, size_t len, + size_t *retlen, struct otp_info *buf) +{ + return cfi_amdstd_otp_walk(mtd, 0, len, retlen, (u_char *)buf, + NULL, 1); +} + +static int cfi_amdstd_read_fact_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, + u_char *buf) +{ + return cfi_amdstd_otp_walk(mtd, from, len, retlen, + buf, do_read_secsi_onechip, 0); +} + +static int cfi_amdstd_read_user_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, + u_char *buf) +{ + return cfi_amdstd_otp_walk(mtd, from, len, retlen, + buf, do_read_secsi_onechip, 1); +} + +static int cfi_amdstd_write_user_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len, size_t *retlen, + const u_char *buf) +{ + return cfi_amdstd_otp_walk(mtd, from, len, retlen, (u_char *)buf, + do_otp_write, 1); +} + +static int cfi_amdstd_lock_user_prot_reg(struct mtd_info *mtd, loff_t from, + size_t len) +{ + size_t retlen; + return cfi_amdstd_otp_walk(mtd, from, len, &retlen, NULL, + do_otp_lock, 1); +} + +static int __xipram do_write_oneword_once(struct map_info *map, + struct flchip *chip, + unsigned long adr, map_word datum, + int mode, struct cfi_private *cfi) +{ + unsigned long timeo; + /* + * We use a 1ms + 1 jiffies generic timeout for writes (most devices + * have a max write time of a few hundreds usec). However, we should + * use the maximum timeout value given by the chip at probe time + * instead. Unfortunately, struct flchip does have a field for + * maximum timeout, only for typical which can be far too short + * depending of the conditions. The ' + 1' is to avoid having a + * timeout of 0 jiffies if HZ is smaller than 1000. + */ + unsigned long uWriteTimeout = (HZ / 1000) + 1; + int ret = 0; + + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xA0, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); + map_write(map, datum, adr); + chip->state = mode; + + INVALIDATE_CACHE_UDELAY(map, chip, + adr, map_bankwidth(map), + chip->word_write_time); + + /* See comment above for timeout value. */ + timeo = jiffies + uWriteTimeout; + for (;;) { + if (chip->state != mode) { + /* Someone's suspended the write. Sleep */ + DECLARE_WAITQUEUE(wait, current); + + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + timeo = jiffies + (HZ / 2); /* FIXME */ + mutex_lock(&chip->mutex); + continue; + } + + /* + * We check "time_after" and "!chip_good" before checking + * "chip_good" to avoid the failure due to scheduling. + */ + if (time_after(jiffies, timeo) && + !chip_good(map, chip, adr, &datum)) { + xip_enable(map, chip, adr); + printk(KERN_WARNING "MTD %s(): software timeout\n", __func__); + xip_disable(map, chip, adr); + ret = -EIO; + break; + } + + if (chip_good(map, chip, adr, &datum)) { + if (cfi_check_err_status(map, chip, adr)) + ret = -EIO; + break; + } + + /* Latency issues. Drop the lock, wait a while and retry */ + UDELAY(map, chip, adr, 1); + } + + return ret; +} + +static int __xipram do_write_oneword_start(struct map_info *map, + struct flchip *chip, + unsigned long adr, int mode) +{ + int ret; + + mutex_lock(&chip->mutex); + + ret = get_chip(map, chip, adr, mode); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + if (mode == FL_OTP_WRITE) + otp_enter(map, chip, adr, map_bankwidth(map)); + + return ret; +} + +static void __xipram do_write_oneword_done(struct map_info *map, + struct flchip *chip, + unsigned long adr, int mode) +{ + if (mode == FL_OTP_WRITE) + otp_exit(map, chip, adr, map_bankwidth(map)); + + chip->state = FL_READY; + DISABLE_VPP(map); + put_chip(map, chip, adr); + + mutex_unlock(&chip->mutex); +} + +static int __xipram do_write_oneword_retry(struct map_info *map, + struct flchip *chip, + unsigned long adr, map_word datum, + int mode) +{ + struct cfi_private *cfi = map->fldrv_priv; + int ret = 0; + map_word oldd; + int retry_cnt = 0; + + /* + * Check for a NOP for the case when the datum to write is already + * present - it saves time and works around buggy chips that corrupt + * data at other locations when 0xff is written to a location that + * already contains 0xff. + */ + oldd = map_read(map, adr); + if (map_word_equal(map, oldd, datum)) { + pr_debug("MTD %s(): NOP\n", __func__); + return ret; + } + + XIP_INVAL_CACHED_RANGE(map, adr, map_bankwidth(map)); + ENABLE_VPP(map); + xip_disable(map, chip, adr); + + retry: + ret = do_write_oneword_once(map, chip, adr, datum, mode, cfi); + if (ret) { + /* reset on all failures. */ + map_write(map, CMD(0xF0), chip->start); + /* FIXME - should have reset delay before continuing */ + + if (++retry_cnt <= MAX_RETRIES) + goto retry; + } + xip_enable(map, chip, adr); + + return ret; +} + +static int __xipram do_write_oneword(struct map_info *map, struct flchip *chip, + unsigned long adr, map_word datum, + int mode) +{ + int ret; + + adr += chip->start; + + pr_debug("MTD %s(): WRITE 0x%.8lx(0x%.8lx)\n", __func__, adr, + datum.x[0]); + + ret = do_write_oneword_start(map, chip, adr, mode); + if (ret) + return ret; + + ret = do_write_oneword_retry(map, chip, adr, datum, mode); + + do_write_oneword_done(map, chip, adr, mode); + + return ret; +} + + +static int cfi_amdstd_write_words(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int ret; + int chipnum; + unsigned long ofs, chipstart; + DECLARE_WAITQUEUE(wait, current); + + chipnum = to >> cfi->chipshift; + ofs = to - (chipnum << cfi->chipshift); + chipstart = cfi->chips[chipnum].start; + + /* If it's not bus-aligned, do the first byte write */ + if (ofs & (map_bankwidth(map)-1)) { + unsigned long bus_ofs = ofs & ~(map_bankwidth(map)-1); + int i = ofs - bus_ofs; + int n = 0; + map_word tmp_buf; + + retry: + mutex_lock(&cfi->chips[chipnum].mutex); + + if (cfi->chips[chipnum].state != FL_READY) { + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&cfi->chips[chipnum].wq, &wait); + + mutex_unlock(&cfi->chips[chipnum].mutex); + + schedule(); + remove_wait_queue(&cfi->chips[chipnum].wq, &wait); + goto retry; + } + + /* Load 'tmp_buf' with old contents of flash */ + tmp_buf = map_read(map, bus_ofs+chipstart); + + mutex_unlock(&cfi->chips[chipnum].mutex); + + /* Number of bytes to copy from buffer */ + n = min_t(int, len, map_bankwidth(map)-i); + + tmp_buf = map_word_load_partial(map, tmp_buf, buf, i, n); + + ret = do_write_oneword(map, &cfi->chips[chipnum], + bus_ofs, tmp_buf, FL_WRITING); + if (ret) + return ret; + + ofs += n; + buf += n; + (*retlen) += n; + len -= n; + + if (ofs >> cfi->chipshift) { + chipnum ++; + ofs = 0; + if (chipnum == cfi->numchips) + return 0; + } + } + + /* We are now aligned, write as much as possible */ + while(len >= map_bankwidth(map)) { + map_word datum; + + datum = map_word_load(map, buf); + + ret = do_write_oneword(map, &cfi->chips[chipnum], + ofs, datum, FL_WRITING); + if (ret) + return ret; + + ofs += map_bankwidth(map); + buf += map_bankwidth(map); + (*retlen) += map_bankwidth(map); + len -= map_bankwidth(map); + + if (ofs >> cfi->chipshift) { + chipnum ++; + ofs = 0; + if (chipnum == cfi->numchips) + return 0; + chipstart = cfi->chips[chipnum].start; + } + } + + /* Write the trailing bytes if any */ + if (len & (map_bankwidth(map)-1)) { + map_word tmp_buf; + + retry1: + mutex_lock(&cfi->chips[chipnum].mutex); + + if (cfi->chips[chipnum].state != FL_READY) { + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&cfi->chips[chipnum].wq, &wait); + + mutex_unlock(&cfi->chips[chipnum].mutex); + + schedule(); + remove_wait_queue(&cfi->chips[chipnum].wq, &wait); + goto retry1; + } + + tmp_buf = map_read(map, ofs + chipstart); + + mutex_unlock(&cfi->chips[chipnum].mutex); + + tmp_buf = map_word_load_partial(map, tmp_buf, buf, 0, len); + + ret = do_write_oneword(map, &cfi->chips[chipnum], + ofs, tmp_buf, FL_WRITING); + if (ret) + return ret; + + (*retlen) += len; + } + + return 0; +} + +#if !FORCE_WORD_WRITE +static int __xipram do_write_buffer_wait(struct map_info *map, + struct flchip *chip, unsigned long adr, + map_word datum) +{ + unsigned long timeo; + unsigned long u_write_timeout; + int ret = 0; + + /* + * Timeout is calculated according to CFI data, if available. + * See more comments in cfi_cmdset_0002(). + */ + u_write_timeout = usecs_to_jiffies(chip->buffer_write_time_max); + timeo = jiffies + u_write_timeout; + + for (;;) { + if (chip->state != FL_WRITING) { + /* Someone's suspended the write. Sleep */ + DECLARE_WAITQUEUE(wait, current); + + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + timeo = jiffies + (HZ / 2); /* FIXME */ + mutex_lock(&chip->mutex); + continue; + } + + /* + * We check "time_after" and "!chip_good" before checking + * "chip_good" to avoid the failure due to scheduling. + */ + if (time_after(jiffies, timeo) && + !chip_good(map, chip, adr, &datum)) { + pr_err("MTD %s(): software timeout, address:0x%.8lx.\n", + __func__, adr); + ret = -EIO; + break; + } + + if (chip_good(map, chip, adr, &datum)) { + if (cfi_check_err_status(map, chip, adr)) + ret = -EIO; + break; + } + + /* Latency issues. Drop the lock, wait a while and retry */ + UDELAY(map, chip, adr, 1); + } + + return ret; +} + +static void __xipram do_write_buffer_reset(struct map_info *map, + struct flchip *chip, + struct cfi_private *cfi) +{ + /* + * Recovery from write-buffer programming failures requires + * the write-to-buffer-reset sequence. Since the last part + * of the sequence also works as a normal reset, we can run + * the same commands regardless of why we are here. + * See e.g. + * http://www.spansion.com/Support/Application%20Notes/MirrorBit_Write_Buffer_Prog_Page_Buffer_Read_AN.pdf + */ + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0xF0, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + + /* FIXME - should have reset delay before continuing */ +} + +/* + * FIXME: interleaved mode not tested, and probably not supported! + */ +static int __xipram do_write_buffer(struct map_info *map, struct flchip *chip, + unsigned long adr, const u_char *buf, + int len) +{ + struct cfi_private *cfi = map->fldrv_priv; + int ret; + unsigned long cmd_adr; + int z, words; + map_word datum; + + adr += chip->start; + cmd_adr = adr; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, adr, FL_WRITING); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + datum = map_word_load(map, buf); + + pr_debug("MTD %s(): WRITE 0x%.8lx(0x%.8lx)\n", + __func__, adr, datum.x[0]); + + XIP_INVAL_CACHED_RANGE(map, adr, len); + ENABLE_VPP(map); + xip_disable(map, chip, cmd_adr); + + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL); + + /* Write Buffer Load */ + map_write(map, CMD(0x25), cmd_adr); + + chip->state = FL_WRITING_TO_BUFFER; + + /* Write length of data to come */ + words = len / map_bankwidth(map); + map_write(map, CMD(words - 1), cmd_adr); + /* Write data */ + z = 0; + while(z < words * map_bankwidth(map)) { + datum = map_word_load(map, buf); + map_write(map, datum, adr + z); + + z += map_bankwidth(map); + buf += map_bankwidth(map); + } + z -= map_bankwidth(map); + + adr += z; + + /* Write Buffer Program Confirm: GO GO GO */ + map_write(map, CMD(0x29), cmd_adr); + chip->state = FL_WRITING; + + INVALIDATE_CACHE_UDELAY(map, chip, + adr, map_bankwidth(map), + chip->word_write_time); + + ret = do_write_buffer_wait(map, chip, adr, datum); + if (ret) + do_write_buffer_reset(map, chip, cfi); + + xip_enable(map, chip, adr); + + chip->state = FL_READY; + DISABLE_VPP(map); + put_chip(map, chip, adr); + mutex_unlock(&chip->mutex); + + return ret; +} + + +static int cfi_amdstd_write_buffers(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; + int ret; + int chipnum; + unsigned long ofs; + + chipnum = to >> cfi->chipshift; + ofs = to - (chipnum << cfi->chipshift); + + /* If it's not bus-aligned, do the first word write */ + if (ofs & (map_bankwidth(map)-1)) { + size_t local_len = (-ofs)&(map_bankwidth(map)-1); + if (local_len > len) + local_len = len; + ret = cfi_amdstd_write_words(mtd, ofs + (chipnum<chipshift), + local_len, retlen, buf); + if (ret) + return ret; + ofs += local_len; + buf += local_len; + len -= local_len; + + if (ofs >> cfi->chipshift) { + chipnum ++; + ofs = 0; + if (chipnum == cfi->numchips) + return 0; + } + } + + /* Write buffer is worth it only if more than one word to write... */ + while (len >= map_bankwidth(map) * 2) { + /* We must not cross write block boundaries */ + int size = wbufsize - (ofs & (wbufsize-1)); + + if (size > len) + size = len; + if (size % map_bankwidth(map)) + size -= size % map_bankwidth(map); + + ret = do_write_buffer(map, &cfi->chips[chipnum], + ofs, buf, size); + if (ret) + return ret; + + ofs += size; + buf += size; + (*retlen) += size; + len -= size; + + if (ofs >> cfi->chipshift) { + chipnum ++; + ofs = 0; + if (chipnum == cfi->numchips) + return 0; + } + } + + if (len) { + size_t retlen_dregs = 0; + + ret = cfi_amdstd_write_words(mtd, ofs + (chipnum<chipshift), + len, &retlen_dregs, buf); + + *retlen += retlen_dregs; + return ret; + } + + return 0; +} +#endif /* !FORCE_WORD_WRITE */ + +/* + * Wait for the flash chip to become ready to write data + * + * This is only called during the panic_write() path. When panic_write() + * is called, the kernel is in the process of a panic, and will soon be + * dead. Therefore we don't take any locks, and attempt to get access + * to the chip as soon as possible. + */ +static int cfi_amdstd_panic_wait(struct map_info *map, struct flchip *chip, + unsigned long adr) +{ + struct cfi_private *cfi = map->fldrv_priv; + int retries = 10; + int i; + + /* + * If the driver thinks the chip is idle, and no toggle bits + * are changing, then the chip is actually idle for sure. + */ + if (chip->state == FL_READY && chip_ready(map, chip, adr, NULL)) + return 0; + + /* + * Try several times to reset the chip and then wait for it + * to become idle. The upper limit of a few milliseconds of + * delay isn't a big problem: the kernel is dying anyway. It + * is more important to save the messages. + */ + while (retries > 0) { + const unsigned long timeo = (HZ / 1000) + 1; + + /* send the reset command */ + map_write(map, CMD(0xF0), chip->start); + + /* wait for the chip to become ready */ + for (i = 0; i < jiffies_to_usecs(timeo); i++) { + if (chip_ready(map, chip, adr, NULL)) + return 0; + + udelay(1); + } + + retries--; + } + + /* the chip never became ready */ + return -EBUSY; +} + +/* + * Write out one word of data to a single flash chip during a kernel panic + * + * This is only called during the panic_write() path. When panic_write() + * is called, the kernel is in the process of a panic, and will soon be + * dead. Therefore we don't take any locks, and attempt to get access + * to the chip as soon as possible. + * + * The implementation of this routine is intentionally similar to + * do_write_oneword(), in order to ease code maintenance. + */ +static int do_panic_write_oneword(struct map_info *map, struct flchip *chip, + unsigned long adr, map_word datum) +{ + const unsigned long uWriteTimeout = (HZ / 1000) + 1; + struct cfi_private *cfi = map->fldrv_priv; + int retry_cnt = 0; + map_word oldd; + int ret; + int i; + + adr += chip->start; + + ret = cfi_amdstd_panic_wait(map, chip, adr); + if (ret) + return ret; + + pr_debug("MTD %s(): PANIC WRITE 0x%.8lx(0x%.8lx)\n", + __func__, adr, datum.x[0]); + + /* + * Check for a NOP for the case when the datum to write is already + * present - it saves time and works around buggy chips that corrupt + * data at other locations when 0xff is written to a location that + * already contains 0xff. + */ + oldd = map_read(map, adr); + if (map_word_equal(map, oldd, datum)) { + pr_debug("MTD %s(): NOP\n", __func__); + goto op_done; + } + + ENABLE_VPP(map); + +retry: + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xA0, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); + map_write(map, datum, adr); + + for (i = 0; i < jiffies_to_usecs(uWriteTimeout); i++) { + if (chip_ready(map, chip, adr, NULL)) + break; + + udelay(1); + } + + if (!chip_ready(map, chip, adr, &datum) || + cfi_check_err_status(map, chip, adr)) { + /* reset on all failures. */ + map_write(map, CMD(0xF0), chip->start); + /* FIXME - should have reset delay before continuing */ + + if (++retry_cnt <= MAX_RETRIES) + goto retry; + + ret = -EIO; + } + +op_done: + DISABLE_VPP(map); + return ret; +} + +/* + * Write out some data during a kernel panic + * + * This is used by the mtdoops driver to save the dying messages from a + * kernel which has panic'd. + * + * This routine ignores all of the locking used throughout the rest of the + * driver, in order to ensure that the data gets written out no matter what + * state this driver (and the flash chip itself) was in when the kernel crashed. + * + * The implementation of this routine is intentionally similar to + * cfi_amdstd_write_words(), in order to ease code maintenance. + */ +static int cfi_amdstd_panic_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long ofs, chipstart; + int ret; + int chipnum; + + chipnum = to >> cfi->chipshift; + ofs = to - (chipnum << cfi->chipshift); + chipstart = cfi->chips[chipnum].start; + + /* If it's not bus aligned, do the first byte write */ + if (ofs & (map_bankwidth(map) - 1)) { + unsigned long bus_ofs = ofs & ~(map_bankwidth(map) - 1); + int i = ofs - bus_ofs; + int n = 0; + map_word tmp_buf; + + ret = cfi_amdstd_panic_wait(map, &cfi->chips[chipnum], bus_ofs); + if (ret) + return ret; + + /* Load 'tmp_buf' with old contents of flash */ + tmp_buf = map_read(map, bus_ofs + chipstart); + + /* Number of bytes to copy from buffer */ + n = min_t(int, len, map_bankwidth(map) - i); + + tmp_buf = map_word_load_partial(map, tmp_buf, buf, i, n); + + ret = do_panic_write_oneword(map, &cfi->chips[chipnum], + bus_ofs, tmp_buf); + if (ret) + return ret; + + ofs += n; + buf += n; + (*retlen) += n; + len -= n; + + if (ofs >> cfi->chipshift) { + chipnum++; + ofs = 0; + if (chipnum == cfi->numchips) + return 0; + } + } + + /* We are now aligned, write as much as possible */ + while (len >= map_bankwidth(map)) { + map_word datum; + + datum = map_word_load(map, buf); + + ret = do_panic_write_oneword(map, &cfi->chips[chipnum], + ofs, datum); + if (ret) + return ret; + + ofs += map_bankwidth(map); + buf += map_bankwidth(map); + (*retlen) += map_bankwidth(map); + len -= map_bankwidth(map); + + if (ofs >> cfi->chipshift) { + chipnum++; + ofs = 0; + if (chipnum == cfi->numchips) + return 0; + + chipstart = cfi->chips[chipnum].start; + } + } + + /* Write the trailing bytes if any */ + if (len & (map_bankwidth(map) - 1)) { + map_word tmp_buf; + + ret = cfi_amdstd_panic_wait(map, &cfi->chips[chipnum], ofs); + if (ret) + return ret; + + tmp_buf = map_read(map, ofs + chipstart); + + tmp_buf = map_word_load_partial(map, tmp_buf, buf, 0, len); + + ret = do_panic_write_oneword(map, &cfi->chips[chipnum], + ofs, tmp_buf); + if (ret) + return ret; + + (*retlen) += len; + } + + return 0; +} + + +/* + * Handle devices with one erase region, that only implement + * the chip erase command. + */ +static int __xipram do_erase_chip(struct map_info *map, struct flchip *chip) +{ + struct cfi_private *cfi = map->fldrv_priv; + unsigned long timeo; + unsigned long int adr; + DECLARE_WAITQUEUE(wait, current); + int ret; + int retry_cnt = 0; + map_word datum = map_word_ff(map); + + adr = cfi->addr_unlock1; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, adr, FL_ERASING); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + pr_debug("MTD %s(): ERASE 0x%.8lx\n", + __func__, chip->start); + + XIP_INVAL_CACHED_RANGE(map, adr, map->size); + ENABLE_VPP(map); + xip_disable(map, chip, adr); + + retry: + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x80, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x10, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); + + chip->state = FL_ERASING; + chip->erase_suspended = 0; + chip->in_progress_block_addr = adr; + chip->in_progress_block_mask = ~(map->size - 1); + + INVALIDATE_CACHE_UDELAY(map, chip, + adr, map->size, + chip->erase_time*500); + + timeo = jiffies + (HZ*20); + + for (;;) { + if (chip->state != FL_ERASING) { + /* Someone's suspended the erase. Sleep */ + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + mutex_lock(&chip->mutex); + continue; + } + if (chip->erase_suspended) { + /* This erase was suspended and resumed. + Adjust the timeout */ + timeo = jiffies + (HZ*20); /* FIXME */ + chip->erase_suspended = 0; + } + + if (chip_ready(map, chip, adr, &datum)) { + if (cfi_check_err_status(map, chip, adr)) + ret = -EIO; + break; + } + + if (time_after(jiffies, timeo)) { + printk(KERN_WARNING "MTD %s(): software timeout\n", + __func__); + ret = -EIO; + break; + } + + /* Latency issues. Drop the lock, wait a while and retry */ + UDELAY(map, chip, adr, 1000000/HZ); + } + /* Did we succeed? */ + if (ret) { + /* reset on all failures. */ + map_write(map, CMD(0xF0), chip->start); + /* FIXME - should have reset delay before continuing */ + + if (++retry_cnt <= MAX_RETRIES) { + ret = 0; + goto retry; + } + } + + chip->state = FL_READY; + xip_enable(map, chip, adr); + DISABLE_VPP(map); + put_chip(map, chip, adr); + mutex_unlock(&chip->mutex); + + return ret; +} + + +static int __xipram do_erase_oneblock(struct map_info *map, struct flchip *chip, unsigned long adr, int len, void *thunk) +{ + struct cfi_private *cfi = map->fldrv_priv; + unsigned long timeo; + DECLARE_WAITQUEUE(wait, current); + int ret; + int retry_cnt = 0; + map_word datum = map_word_ff(map); + + adr += chip->start; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, adr, FL_ERASING); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + pr_debug("MTD %s(): ERASE 0x%.8lx\n", + __func__, adr); + + XIP_INVAL_CACHED_RANGE(map, adr, len); + ENABLE_VPP(map); + xip_disable(map, chip, adr); + + retry: + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x80, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, cfi->device_type, NULL); + map_write(map, cfi->sector_erase_cmd, adr); + + chip->state = FL_ERASING; + chip->erase_suspended = 0; + chip->in_progress_block_addr = adr; + chip->in_progress_block_mask = ~(len - 1); + + INVALIDATE_CACHE_UDELAY(map, chip, + adr, len, + chip->erase_time*500); + + timeo = jiffies + (HZ*20); + + for (;;) { + if (chip->state != FL_ERASING) { + /* Someone's suspended the erase. Sleep */ + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + mutex_lock(&chip->mutex); + continue; + } + if (chip->erase_suspended) { + /* This erase was suspended and resumed. + Adjust the timeout */ + timeo = jiffies + (HZ*20); /* FIXME */ + chip->erase_suspended = 0; + } + + if (chip_ready(map, chip, adr, &datum)) { + if (cfi_check_err_status(map, chip, adr)) + ret = -EIO; + break; + } + + if (time_after(jiffies, timeo)) { + printk(KERN_WARNING "MTD %s(): software timeout\n", + __func__); + ret = -EIO; + break; + } + + /* Latency issues. Drop the lock, wait a while and retry */ + UDELAY(map, chip, adr, 1000000/HZ); + } + /* Did we succeed? */ + if (ret) { + /* reset on all failures. */ + map_write(map, CMD(0xF0), chip->start); + /* FIXME - should have reset delay before continuing */ + + if (++retry_cnt <= MAX_RETRIES) { + ret = 0; + goto retry; + } + } + + chip->state = FL_READY; + xip_enable(map, chip, adr); + DISABLE_VPP(map); + put_chip(map, chip, adr); + mutex_unlock(&chip->mutex); + return ret; +} + + +static int cfi_amdstd_erase_varsize(struct mtd_info *mtd, struct erase_info *instr) +{ + return cfi_varsize_frob(mtd, do_erase_oneblock, instr->addr, + instr->len, NULL); +} + + +static int cfi_amdstd_erase_chip(struct mtd_info *mtd, struct erase_info *instr) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + if (instr->addr != 0) + return -EINVAL; + + if (instr->len != mtd->size) + return -EINVAL; + + return do_erase_chip(map, &cfi->chips[0]); +} + +static int do_atmel_lock(struct map_info *map, struct flchip *chip, + unsigned long adr, int len, void *thunk) +{ + struct cfi_private *cfi = map->fldrv_priv; + int ret; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, adr + chip->start, FL_LOCKING); + if (ret) + goto out_unlock; + chip->state = FL_LOCKING; + + pr_debug("MTD %s(): LOCK 0x%08lx len %d\n", __func__, adr, len); + + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x80, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, + cfi->device_type, NULL); + map_write(map, CMD(0x40), chip->start + adr); + + chip->state = FL_READY; + put_chip(map, chip, adr + chip->start); + ret = 0; + +out_unlock: + mutex_unlock(&chip->mutex); + return ret; +} + +static int do_atmel_unlock(struct map_info *map, struct flchip *chip, + unsigned long adr, int len, void *thunk) +{ + struct cfi_private *cfi = map->fldrv_priv; + int ret; + + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, adr + chip->start, FL_UNLOCKING); + if (ret) + goto out_unlock; + chip->state = FL_UNLOCKING; + + pr_debug("MTD %s(): LOCK 0x%08lx len %d\n", __func__, adr, len); + + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + map_write(map, CMD(0x70), adr); + + chip->state = FL_READY; + put_chip(map, chip, adr + chip->start); + ret = 0; + +out_unlock: + mutex_unlock(&chip->mutex); + return ret; +} + +static int cfi_atmel_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + return cfi_varsize_frob(mtd, do_atmel_lock, ofs, len, NULL); +} + +static int cfi_atmel_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + return cfi_varsize_frob(mtd, do_atmel_unlock, ofs, len, NULL); +} + +/* + * Advanced Sector Protection - PPB (Persistent Protection Bit) locking + */ + +struct ppb_lock { + struct flchip *chip; + unsigned long adr; + int locked; +}; + +#define DO_XXLOCK_ONEBLOCK_LOCK ((void *)1) +#define DO_XXLOCK_ONEBLOCK_UNLOCK ((void *)2) +#define DO_XXLOCK_ONEBLOCK_GETLOCK ((void *)3) + +static int __maybe_unused do_ppb_xxlock(struct map_info *map, + struct flchip *chip, + unsigned long adr, int len, void *thunk) +{ + struct cfi_private *cfi = map->fldrv_priv; + unsigned long timeo; + int ret; + + adr += chip->start; + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, adr, FL_LOCKING); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + pr_debug("MTD %s(): XXLOCK 0x%08lx len %d\n", __func__, adr, len); + + cfi_send_gen_cmd(0xAA, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, chip->start, map, cfi, + cfi->device_type, NULL); + /* PPB entry command */ + cfi_send_gen_cmd(0xC0, cfi->addr_unlock1, chip->start, map, cfi, + cfi->device_type, NULL); + + if (thunk == DO_XXLOCK_ONEBLOCK_LOCK) { + chip->state = FL_LOCKING; + map_write(map, CMD(0xA0), adr); + map_write(map, CMD(0x00), adr); + } else if (thunk == DO_XXLOCK_ONEBLOCK_UNLOCK) { + /* + * Unlocking of one specific sector is not supported, so we + * have to unlock all sectors of this device instead + */ + chip->state = FL_UNLOCKING; + map_write(map, CMD(0x80), chip->start); + map_write(map, CMD(0x30), chip->start); + } else if (thunk == DO_XXLOCK_ONEBLOCK_GETLOCK) { + chip->state = FL_JEDEC_QUERY; + /* Return locked status: 0->locked, 1->unlocked */ + ret = !cfi_read_query(map, adr); + } else + BUG(); + + /* + * Wait for some time as unlocking of all sectors takes quite long + */ + timeo = jiffies + msecs_to_jiffies(2000); /* 2s max (un)locking */ + for (;;) { + if (chip_ready(map, chip, adr, NULL)) + break; + + if (time_after(jiffies, timeo)) { + printk(KERN_ERR "Waiting for chip to be ready timed out.\n"); + ret = -EIO; + break; + } + + UDELAY(map, chip, adr, 1); + } + + /* Exit BC commands */ + map_write(map, CMD(0x90), chip->start); + map_write(map, CMD(0x00), chip->start); + + chip->state = FL_READY; + put_chip(map, chip, adr); + mutex_unlock(&chip->mutex); + + return ret; +} + +static int __maybe_unused cfi_ppb_lock(struct mtd_info *mtd, loff_t ofs, + uint64_t len) +{ + return cfi_varsize_frob(mtd, do_ppb_xxlock, ofs, len, + DO_XXLOCK_ONEBLOCK_LOCK); +} + +static int __maybe_unused cfi_ppb_unlock(struct mtd_info *mtd, loff_t ofs, + uint64_t len) +{ + struct mtd_erase_region_info *regions = mtd->eraseregions; + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + struct ppb_lock *sect; + unsigned long adr; + loff_t offset; + uint64_t length; + int chipnum; + int i; + int sectors; + int ret; + int max_sectors; + + /* + * PPB unlocking always unlocks all sectors of the flash chip. + * We need to re-lock all previously locked sectors. So lets + * first check the locking status of all sectors and save + * it for future use. + */ + max_sectors = 0; + for (i = 0; i < mtd->numeraseregions; i++) + max_sectors += regions[i].numblocks; + + sect = kzalloc_objs(struct ppb_lock, max_sectors); + if (!sect) + return -ENOMEM; + + /* + * This code to walk all sectors is a slightly modified version + * of the cfi_varsize_frob() code. + */ + i = 0; + chipnum = 0; + adr = 0; + sectors = 0; + offset = 0; + length = mtd->size; + + while (length) { + int size = regions[i].erasesize; + + /* + * Only test sectors that shall not be unlocked. The other + * sectors shall be unlocked, so lets keep their locking + * status at "unlocked" (locked=0) for the final re-locking. + */ + if ((offset < ofs) || (offset >= (ofs + len))) { + sect[sectors].chip = &cfi->chips[chipnum]; + sect[sectors].adr = adr; + sect[sectors].locked = do_ppb_xxlock( + map, &cfi->chips[chipnum], adr, 0, + DO_XXLOCK_ONEBLOCK_GETLOCK); + } + + adr += size; + offset += size; + length -= size; + + if (offset == regions[i].offset + size * regions[i].numblocks) + i++; + + if (adr >> cfi->chipshift) { + if (offset >= (ofs + len)) + break; + adr = 0; + chipnum++; + + if (chipnum >= cfi->numchips) + break; + } + + sectors++; + if (sectors >= max_sectors) { + printk(KERN_ERR "Only %d sectors for PPB locking supported!\n", + max_sectors); + kfree(sect); + return -EINVAL; + } + } + + /* Now unlock the whole chip */ + ret = cfi_varsize_frob(mtd, do_ppb_xxlock, ofs, len, + DO_XXLOCK_ONEBLOCK_UNLOCK); + if (ret) { + kfree(sect); + return ret; + } + + /* + * PPB unlocking always unlocks all sectors of the flash chip. + * We need to re-lock all previously locked sectors. + */ + for (i = 0; i < sectors; i++) { + if (sect[i].locked) + do_ppb_xxlock(map, sect[i].chip, sect[i].adr, 0, + DO_XXLOCK_ONEBLOCK_LOCK); + } + + kfree(sect); + return ret; +} + +static int __maybe_unused cfi_ppb_is_locked(struct mtd_info *mtd, loff_t ofs, + uint64_t len) +{ + return cfi_varsize_frob(mtd, do_ppb_xxlock, ofs, len, + DO_XXLOCK_ONEBLOCK_GETLOCK) ? 1 : 0; +} + +static void cfi_amdstd_sync (struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int i; + struct flchip *chip; + int ret = 0; + DECLARE_WAITQUEUE(wait, current); + + for (i=0; !ret && inumchips; i++) { + chip = &cfi->chips[i]; + + retry: + mutex_lock(&chip->mutex); + + switch(chip->state) { + case FL_READY: + case FL_STATUS: + case FL_CFI_QUERY: + case FL_JEDEC_QUERY: + chip->oldstate = chip->state; + chip->state = FL_SYNCING; + /* No need to wake_up() on this state change - + * as the whole point is that nobody can do anything + * with the chip now anyway. + */ + fallthrough; + case FL_SYNCING: + mutex_unlock(&chip->mutex); + break; + + default: + /* Not an idle state */ + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + + mutex_unlock(&chip->mutex); + + schedule(); + + remove_wait_queue(&chip->wq, &wait); + + goto retry; + } + } + + /* Unlock the chips again */ + + for (i--; i >=0; i--) { + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + + if (chip->state == FL_SYNCING) { + chip->state = chip->oldstate; + wake_up(&chip->wq); + } + mutex_unlock(&chip->mutex); + } +} + + +static int cfi_amdstd_suspend(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int i; + struct flchip *chip; + int ret = 0; + + for (i=0; !ret && inumchips; i++) { + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + + switch(chip->state) { + case FL_READY: + case FL_STATUS: + case FL_CFI_QUERY: + case FL_JEDEC_QUERY: + chip->oldstate = chip->state; + chip->state = FL_PM_SUSPENDED; + /* No need to wake_up() on this state change - + * as the whole point is that nobody can do anything + * with the chip now anyway. + */ + break; + case FL_PM_SUSPENDED: + break; + + default: + ret = -EAGAIN; + break; + } + mutex_unlock(&chip->mutex); + } + + /* Unlock the chips again */ + + if (ret) { + for (i--; i >=0; i--) { + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + + if (chip->state == FL_PM_SUSPENDED) { + chip->state = chip->oldstate; + wake_up(&chip->wq); + } + mutex_unlock(&chip->mutex); + } + } + + return ret; +} + + +static void cfi_amdstd_resume(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int i; + struct flchip *chip; + + for (i=0; inumchips; i++) { + + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + + if (chip->state == FL_PM_SUSPENDED) { + chip->state = FL_READY; + map_write(map, CMD(0xF0), chip->start); + wake_up(&chip->wq); + } + else + printk(KERN_ERR "Argh. Chip not in PM_SUSPENDED state upon resume()\n"); + + mutex_unlock(&chip->mutex); + } +} + + +/* + * Ensure that the flash device is put back into read array mode before + * unloading the driver or rebooting. On some systems, rebooting while + * the flash is in query/program/erase mode will prevent the CPU from + * fetching the bootloader code, requiring a hard reset or power cycle. + */ +static int cfi_amdstd_reset(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int i, ret; + struct flchip *chip; + + for (i = 0; i < cfi->numchips; i++) { + + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + + ret = get_chip(map, chip, chip->start, FL_SHUTDOWN); + if (!ret) { + map_write(map, CMD(0xF0), chip->start); + chip->state = FL_SHUTDOWN; + put_chip(map, chip, chip->start); + } + + mutex_unlock(&chip->mutex); + } + + return 0; +} + + +static int cfi_amdstd_reboot(struct notifier_block *nb, unsigned long val, + void *v) +{ + struct mtd_info *mtd; + + mtd = container_of(nb, struct mtd_info, reboot_notifier); + cfi_amdstd_reset(mtd); + return NOTIFY_DONE; +} + + +static void cfi_amdstd_destroy(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + + cfi_amdstd_reset(mtd); + unregister_reboot_notifier(&mtd->reboot_notifier); + kfree(cfi->cmdset_priv); + kfree(cfi->cfiq); + kfree(cfi); + kfree(mtd->eraseregions); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Crossnet Co. et al."); +MODULE_DESCRIPTION("MTD chip driver for AMD/Fujitsu flash chips"); +MODULE_ALIAS("cfi_cmdset_0006"); +MODULE_ALIAS("cfi_cmdset_0701"); diff --git a/drivers/mtd/chips/cfi_cmdset_0020.c b/drivers/mtd/chips/cfi_cmdset_0020.c new file mode 100644 index 000000000..593ac65a7 --- /dev/null +++ b/drivers/mtd/chips/cfi_cmdset_0020.c @@ -0,0 +1,1400 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common Flash Interface support: + * ST Advanced Architecture Command Set (ID 0x0020) + * + * (C) 2000 Red Hat. + * + * 10/10/2000 Nicolas Pitre + * - completely revamped method functions so they are aware and + * independent of the flash geometry (buswidth, interleave, etc.) + * - scalability vs code size is completely set at compile-time + * (see include/linux/mtd/cfi.h for selection) + * - optimized write buffer method + * 06/21/2002 Joern Engel and others + * - modified Intel Command Set 0x0001 to support ST Advanced Architecture + * (command set 0x0020) + * - added a writev function + * 07/13/2005 Joern Engel + * - Plugged memory leak in cfi_staa_writev(). + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +static int cfi_staa_read(struct mtd_info *, loff_t, size_t, size_t *, u_char *); +static int cfi_staa_write_buffers(struct mtd_info *, loff_t, size_t, size_t *, const u_char *); +static int cfi_staa_writev(struct mtd_info *mtd, const struct kvec *vecs, + unsigned long count, loff_t to, size_t *retlen); +static int cfi_staa_erase_varsize(struct mtd_info *, struct erase_info *); +static void cfi_staa_sync (struct mtd_info *); +static int cfi_staa_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int cfi_staa_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len); +static int cfi_staa_suspend (struct mtd_info *); +static void cfi_staa_resume (struct mtd_info *); + +static void cfi_staa_destroy(struct mtd_info *); + +struct mtd_info *cfi_cmdset_0020(struct map_info *, int); + +static struct mtd_info *cfi_staa_setup (struct map_info *); + +static struct mtd_chip_driver cfi_staa_chipdrv = { + .probe = NULL, /* Not usable directly */ + .destroy = cfi_staa_destroy, + .name = "cfi_cmdset_0020", + .module = THIS_MODULE +}; + +/* #define DEBUG_LOCK_BITS */ +//#define DEBUG_CFI_FEATURES + +#ifdef DEBUG_CFI_FEATURES +static void cfi_tell_features(struct cfi_pri_intelext *extp) +{ + int i; + printk(" Feature/Command Support: %4.4X\n", extp->FeatureSupport); + printk(" - Chip Erase: %s\n", extp->FeatureSupport&1?"supported":"unsupported"); + printk(" - Suspend Erase: %s\n", extp->FeatureSupport&2?"supported":"unsupported"); + printk(" - Suspend Program: %s\n", extp->FeatureSupport&4?"supported":"unsupported"); + printk(" - Legacy Lock/Unlock: %s\n", extp->FeatureSupport&8?"supported":"unsupported"); + printk(" - Queued Erase: %s\n", extp->FeatureSupport&16?"supported":"unsupported"); + printk(" - Instant block lock: %s\n", extp->FeatureSupport&32?"supported":"unsupported"); + printk(" - Protection Bits: %s\n", extp->FeatureSupport&64?"supported":"unsupported"); + printk(" - Page-mode read: %s\n", extp->FeatureSupport&128?"supported":"unsupported"); + printk(" - Synchronous read: %s\n", extp->FeatureSupport&256?"supported":"unsupported"); + for (i=9; i<32; i++) { + if (extp->FeatureSupport & (1<SuspendCmdSupport); + printk(" - Program after Erase Suspend: %s\n", extp->SuspendCmdSupport&1?"supported":"unsupported"); + for (i=1; i<8; i++) { + if (extp->SuspendCmdSupport & (1<BlkStatusRegMask); + printk(" - Lock Bit Active: %s\n", extp->BlkStatusRegMask&1?"yes":"no"); + printk(" - Valid Bit Active: %s\n", extp->BlkStatusRegMask&2?"yes":"no"); + for (i=2; i<16; i++) { + if (extp->BlkStatusRegMask & (1<VccOptimal >> 8, extp->VccOptimal & 0xf); + if (extp->VppOptimal) + printk(" Vpp Programming Supply Optimum Program/Erase Voltage: %d.%d V\n", + extp->VppOptimal >> 8, extp->VppOptimal & 0xf); +} +#endif + +/* This routine is made available to other mtd code via + * inter_module_register. It must only be accessed through + * inter_module_get which will bump the use count of this module. The + * addresses passed back in cfi are valid as long as the use count of + * this module is non-zero, i.e. between inter_module_get and + * inter_module_put. Keith Owens 29 Oct 2000. + */ +struct mtd_info *cfi_cmdset_0020(struct map_info *map, int primary) +{ + struct cfi_private *cfi = map->fldrv_priv; + int i; + + if (cfi->cfi_mode) { + /* + * It's a real CFI chip, not one for which the probe + * routine faked a CFI structure. So we read the feature + * table from it. + */ + __u16 adr = primary?cfi->cfiq->P_ADR:cfi->cfiq->A_ADR; + struct cfi_pri_intelext *extp; + + extp = (struct cfi_pri_intelext*)cfi_read_pri(map, adr, sizeof(*extp), "ST Microelectronics"); + if (!extp) + return NULL; + + if (extp->MajorVersion != '1' || + (extp->MinorVersion < '0' || extp->MinorVersion > '3')) { + printk(KERN_ERR " Unknown ST Microelectronics" + " Extended Query version %c.%c.\n", + extp->MajorVersion, extp->MinorVersion); + kfree(extp); + return NULL; + } + + /* Do some byteswapping if necessary */ + extp->FeatureSupport = cfi32_to_cpu(map, extp->FeatureSupport); + extp->BlkStatusRegMask = cfi32_to_cpu(map, + extp->BlkStatusRegMask); + +#ifdef DEBUG_CFI_FEATURES + /* Tell the user about it in lots of lovely detail */ + cfi_tell_features(extp); +#endif + + /* Install our own private info structure */ + cfi->cmdset_priv = extp; + } + + for (i=0; i< cfi->numchips; i++) { + cfi->chips[i].word_write_time = 128; + cfi->chips[i].buffer_write_time = 128; + cfi->chips[i].erase_time = 1024; + cfi->chips[i].ref_point_counter = 0; + init_waitqueue_head(&(cfi->chips[i].wq)); + } + + return cfi_staa_setup(map); +} +EXPORT_SYMBOL_GPL(cfi_cmdset_0020); + +static struct mtd_info *cfi_staa_setup(struct map_info *map) +{ + struct cfi_private *cfi = map->fldrv_priv; + struct mtd_info *mtd; + unsigned long offset = 0; + int i,j; + unsigned long devsize = (1<cfiq->DevSize) * cfi->interleave; + + mtd = kzalloc_obj(*mtd); + //printk(KERN_DEBUG "number of CFI chips: %d\n", cfi->numchips); + if (!mtd) + goto free_cmdset_priv; + + mtd->priv = map; + mtd->type = MTD_NORFLASH; + mtd->size = devsize * cfi->numchips; + + mtd->numeraseregions = cfi->cfiq->NumEraseRegions * cfi->numchips; + mtd->eraseregions = kmalloc_objs(struct mtd_erase_region_info, + mtd->numeraseregions); + if (!mtd->eraseregions) + goto free_mtd; + + for (i=0; icfiq->NumEraseRegions; i++) { + unsigned long ernum, ersize; + ersize = ((cfi->cfiq->EraseRegionInfo[i] >> 8) & ~0xff) * cfi->interleave; + ernum = (cfi->cfiq->EraseRegionInfo[i] & 0xffff) + 1; + + if (mtd->erasesize < ersize) { + mtd->erasesize = ersize; + } + for (j=0; jnumchips; j++) { + mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].offset = (j*devsize)+offset; + mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].erasesize = ersize; + mtd->eraseregions[(j*cfi->cfiq->NumEraseRegions)+i].numblocks = ernum; + } + offset += (ersize * ernum); + } + + if (offset != devsize) { + /* Argh */ + printk(KERN_WARNING "Sum of regions (%lx) != total size of set of interleaved chips (%lx)\n", offset, devsize); + kfree(mtd->eraseregions); + goto free_mtd; + } + + for (i=0; inumeraseregions;i++){ + printk(KERN_DEBUG "%d: offset=0x%llx,size=0x%x,blocks=%d\n", + i, (unsigned long long)mtd->eraseregions[i].offset, + mtd->eraseregions[i].erasesize, + mtd->eraseregions[i].numblocks); + } + + /* Also select the correct geometry setup too */ + mtd->_erase = cfi_staa_erase_varsize; + mtd->_read = cfi_staa_read; + mtd->_write = cfi_staa_write_buffers; + mtd->_writev = cfi_staa_writev; + mtd->_sync = cfi_staa_sync; + mtd->_lock = cfi_staa_lock; + mtd->_unlock = cfi_staa_unlock; + mtd->_suspend = cfi_staa_suspend; + mtd->_resume = cfi_staa_resume; + mtd->flags = MTD_CAP_NORFLASH & ~MTD_BIT_WRITEABLE; + mtd->writesize = 8; /* FIXME: Should be 0 for STMicro flashes w/out ECC */ + mtd->writebufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; + map->fldrv = &cfi_staa_chipdrv; + __module_get(THIS_MODULE); + mtd->name = map->name; + return mtd; + +free_mtd: + kfree(mtd); +free_cmdset_priv: + kfree(cfi->cmdset_priv); + return NULL; +} + + +static inline int do_read_onechip(struct map_info *map, struct flchip *chip, loff_t adr, size_t len, u_char *buf) +{ + map_word status, status_OK; + unsigned long timeo; + DECLARE_WAITQUEUE(wait, current); + int suspended = 0; + unsigned long cmd_addr; + struct cfi_private *cfi = map->fldrv_priv; + + adr += chip->start; + + /* Ensure cmd read/writes are aligned. */ + cmd_addr = adr & ~(map_bankwidth(map)-1); + + /* Let's determine this according to the interleave only once */ + status_OK = CMD(0x80); + + timeo = jiffies + HZ; + retry: + mutex_lock(&chip->mutex); + + /* Check that the chip's ready to talk to us. + * If it's in FL_ERASING state, suspend it and make it talk now. + */ + switch (chip->state) { + case FL_ERASING: + if (!(((struct cfi_pri_intelext *)cfi->cmdset_priv)->FeatureSupport & 2)) + goto sleep; /* We don't support erase suspend */ + + map_write (map, CMD(0xb0), cmd_addr); + /* If the flash has finished erasing, then 'erase suspend' + * appears to make some (28F320) flash devices switch to + * 'read' mode. Make sure that we switch to 'read status' + * mode so we get the right data. --rmk + */ + map_write(map, CMD(0x70), cmd_addr); + chip->oldstate = FL_ERASING; + chip->state = FL_ERASE_SUSPENDING; + // printk("Erase suspending at 0x%lx\n", cmd_addr); + for (;;) { + status = map_read(map, cmd_addr); + if (map_word_andequal(map, status, status_OK, status_OK)) + break; + + if (time_after(jiffies, timeo)) { + /* Urgh */ + map_write(map, CMD(0xd0), cmd_addr); + /* make sure we're in 'read status' mode */ + map_write(map, CMD(0x70), cmd_addr); + chip->state = FL_ERASING; + wake_up(&chip->wq); + mutex_unlock(&chip->mutex); + printk(KERN_ERR "Chip not ready after erase " + "suspended: status = 0x%lx\n", status.x[0]); + return -EIO; + } + + mutex_unlock(&chip->mutex); + cfi_udelay(1); + mutex_lock(&chip->mutex); + } + + suspended = 1; + map_write(map, CMD(0xff), cmd_addr); + chip->state = FL_READY; + break; + +#if 0 + case FL_WRITING: + /* Not quite yet */ +#endif + + case FL_READY: + break; + + case FL_CFI_QUERY: + case FL_JEDEC_QUERY: + map_write(map, CMD(0x70), cmd_addr); + chip->state = FL_STATUS; + fallthrough; + case FL_STATUS: + status = map_read(map, cmd_addr); + if (map_word_andequal(map, status, status_OK, status_OK)) { + map_write(map, CMD(0xff), cmd_addr); + chip->state = FL_READY; + break; + } + + /* Urgh. Chip not yet ready to talk to us. */ + if (time_after(jiffies, timeo)) { + mutex_unlock(&chip->mutex); + printk(KERN_ERR "waiting for chip to be ready timed out in read. WSM status = %lx\n", status.x[0]); + return -EIO; + } + + /* Latency issues. Drop the lock, wait a while and retry */ + mutex_unlock(&chip->mutex); + cfi_udelay(1); + goto retry; + + default: + sleep: + /* Stick ourselves on a wait queue to be woken when + someone changes the status */ + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + timeo = jiffies + HZ; + goto retry; + } + + map_copy_from(map, buf, adr, len); + + if (suspended) { + chip->state = chip->oldstate; + /* What if one interleaved chip has finished and the + other hasn't? The old code would leave the finished + one in READY mode. That's bad, and caused -EROFS + errors to be returned from do_erase_oneblock because + that's the only bit it checked for at the time. + As the state machine appears to explicitly allow + sending the 0x70 (Read Status) command to an erasing + chip and expecting it to be ignored, that's what we + do. */ + map_write(map, CMD(0xd0), cmd_addr); + map_write(map, CMD(0x70), cmd_addr); + } + + wake_up(&chip->wq); + mutex_unlock(&chip->mutex); + return 0; +} + +static int cfi_staa_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long ofs; + int chipnum; + int ret = 0; + + /* ofs: offset within the first chip that the first read should start */ + chipnum = (from >> cfi->chipshift); + ofs = from - (chipnum << cfi->chipshift); + + while (len) { + unsigned long thislen; + + if (chipnum >= cfi->numchips) + break; + + if ((len + ofs -1) >> cfi->chipshift) + thislen = (1<chipshift) - ofs; + else + thislen = len; + + ret = do_read_onechip(map, &cfi->chips[chipnum], ofs, thislen, buf); + if (ret) + break; + + *retlen += thislen; + len -= thislen; + buf += thislen; + + ofs = 0; + chipnum++; + } + return ret; +} + +static int do_write_buffer(struct map_info *map, struct flchip *chip, + unsigned long adr, const u_char *buf, int len) +{ + struct cfi_private *cfi = map->fldrv_priv; + map_word status, status_OK; + unsigned long cmd_adr, timeo; + DECLARE_WAITQUEUE(wait, current); + int wbufsize, z; + + /* M58LW064A requires bus alignment for buffer wriets -- saw */ + if (adr & (map_bankwidth(map)-1)) + return -EINVAL; + + wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; + adr += chip->start; + cmd_adr = adr & ~(wbufsize-1); + + /* Let's determine this according to the interleave only once */ + status_OK = CMD(0x80); + + timeo = jiffies + HZ; + retry: + +#ifdef DEBUG_CFI_FEATURES + printk("%s: chip->state[%d]\n", __func__, chip->state); +#endif + mutex_lock(&chip->mutex); + + /* Check that the chip's ready to talk to us. + * Later, we can actually think about interrupting it + * if it's in FL_ERASING state. + * Not just yet, though. + */ + switch (chip->state) { + case FL_READY: + break; + + case FL_CFI_QUERY: + case FL_JEDEC_QUERY: + map_write(map, CMD(0x70), cmd_adr); + chip->state = FL_STATUS; +#ifdef DEBUG_CFI_FEATURES + printk("%s: 1 status[%x]\n", __func__, map_read(map, cmd_adr)); +#endif + fallthrough; + case FL_STATUS: + status = map_read(map, cmd_adr); + if (map_word_andequal(map, status, status_OK, status_OK)) + break; + /* Urgh. Chip not yet ready to talk to us. */ + if (time_after(jiffies, timeo)) { + mutex_unlock(&chip->mutex); + printk(KERN_ERR "waiting for chip to be ready timed out in buffer write Xstatus = %lx, status = %lx\n", + status.x[0], map_read(map, cmd_adr).x[0]); + return -EIO; + } + + /* Latency issues. Drop the lock, wait a while and retry */ + mutex_unlock(&chip->mutex); + cfi_udelay(1); + goto retry; + + default: + /* Stick ourselves on a wait queue to be woken when + someone changes the status */ + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + timeo = jiffies + HZ; + goto retry; + } + + ENABLE_VPP(map); + map_write(map, CMD(0xe8), cmd_adr); + chip->state = FL_WRITING_TO_BUFFER; + + z = 0; + for (;;) { + status = map_read(map, cmd_adr); + if (map_word_andequal(map, status, status_OK, status_OK)) + break; + + mutex_unlock(&chip->mutex); + cfi_udelay(1); + mutex_lock(&chip->mutex); + + if (++z > 100) { + /* Argh. Not ready for write to buffer */ + DISABLE_VPP(map); + map_write(map, CMD(0x70), cmd_adr); + chip->state = FL_STATUS; + mutex_unlock(&chip->mutex); + printk(KERN_ERR "Chip not ready for buffer write. Xstatus = %lx\n", status.x[0]); + return -EIO; + } + } + + /* Write length of data to come */ + map_write(map, CMD(len/map_bankwidth(map)-1), cmd_adr ); + + /* Write data */ + for (z = 0; z < len; + z += map_bankwidth(map), buf += map_bankwidth(map)) { + map_word d; + d = map_word_load(map, buf); + map_write(map, d, adr+z); + } + /* GO GO GO */ + map_write(map, CMD(0xd0), cmd_adr); + chip->state = FL_WRITING; + + mutex_unlock(&chip->mutex); + cfi_udelay(chip->buffer_write_time); + mutex_lock(&chip->mutex); + + timeo = jiffies + (HZ/2); + z = 0; + for (;;) { + if (chip->state != FL_WRITING) { + /* Someone's suspended the write. Sleep */ + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + timeo = jiffies + (HZ / 2); /* FIXME */ + mutex_lock(&chip->mutex); + continue; + } + + status = map_read(map, cmd_adr); + if (map_word_andequal(map, status, status_OK, status_OK)) + break; + + /* OK Still waiting */ + if (time_after(jiffies, timeo)) { + /* clear status */ + map_write(map, CMD(0x50), cmd_adr); + /* put back into read status register mode */ + map_write(map, CMD(0x70), adr); + chip->state = FL_STATUS; + DISABLE_VPP(map); + mutex_unlock(&chip->mutex); + printk(KERN_ERR "waiting for chip to be ready timed out in bufwrite\n"); + return -EIO; + } + + /* Latency issues. Drop the lock, wait a while and retry */ + mutex_unlock(&chip->mutex); + cfi_udelay(1); + z++; + mutex_lock(&chip->mutex); + } + if (!z) { + chip->buffer_write_time--; + if (!chip->buffer_write_time) + chip->buffer_write_time++; + } + if (z > 1) + chip->buffer_write_time++; + + /* Done and happy. */ + DISABLE_VPP(map); + chip->state = FL_STATUS; + + /* check for errors: 'lock bit', 'VPP', 'dead cell'/'unerased cell' or 'incorrect cmd' -- saw */ + if (map_word_bitsset(map, status, CMD(0x3a))) { +#ifdef DEBUG_CFI_FEATURES + printk("%s: 2 status[%lx]\n", __func__, status.x[0]); +#endif + /* clear status */ + map_write(map, CMD(0x50), cmd_adr); + /* put back into read status register mode */ + map_write(map, CMD(0x70), adr); + wake_up(&chip->wq); + mutex_unlock(&chip->mutex); + return map_word_bitsset(map, status, CMD(0x02)) ? -EROFS : -EIO; + } + wake_up(&chip->wq); + mutex_unlock(&chip->mutex); + + return 0; +} + +static int cfi_staa_write_buffers (struct mtd_info *mtd, loff_t to, + size_t len, size_t *retlen, const u_char *buf) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int wbufsize = cfi_interleave(cfi) << cfi->cfiq->MaxBufWriteSize; + int ret; + int chipnum; + unsigned long ofs; + + chipnum = to >> cfi->chipshift; + ofs = to - (chipnum << cfi->chipshift); + +#ifdef DEBUG_CFI_FEATURES + printk("%s: map_bankwidth(map)[%x]\n", __func__, map_bankwidth(map)); + printk("%s: chipnum[%x] wbufsize[%x]\n", __func__, chipnum, wbufsize); + printk("%s: ofs[%x] len[%x]\n", __func__, ofs, len); +#endif + + /* Write buffer is worth it only if more than one word to write... */ + while (len > 0) { + /* We must not cross write block boundaries */ + int size = wbufsize - (ofs & (wbufsize-1)); + + if (size > len) + size = len; + + ret = do_write_buffer(map, &cfi->chips[chipnum], + ofs, buf, size); + if (ret) + return ret; + + ofs += size; + buf += size; + (*retlen) += size; + len -= size; + + if (ofs >> cfi->chipshift) { + chipnum ++; + ofs = 0; + if (chipnum == cfi->numchips) + return 0; + } + } + + return 0; +} + +/* + * Writev for ECC-Flashes is a little more complicated. We need to maintain + * a small buffer for this. + * XXX: If the buffer size is not a multiple of 2, this will break + */ +#define ECCBUF_SIZE (mtd->writesize) +#define ECCBUF_DIV(x) ((x) & ~(ECCBUF_SIZE - 1)) +#define ECCBUF_MOD(x) ((x) & (ECCBUF_SIZE - 1)) +static int +cfi_staa_writev(struct mtd_info *mtd, const struct kvec *vecs, + unsigned long count, loff_t to, size_t *retlen) +{ + unsigned long i; + size_t totlen = 0, thislen; + int ret = 0; + size_t buflen = 0; + char *buffer; + + if (!ECCBUF_SIZE) { + /* We should fall back to a general writev implementation. + * Until that is written, just break. + */ + return -EIO; + } + buffer = kmalloc(ECCBUF_SIZE, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + for (i=0; ifldrv_priv; + map_word status, status_OK; + unsigned long timeo; + int retries = 3; + DECLARE_WAITQUEUE(wait, current); + int ret = 0; + + adr += chip->start; + + /* Let's determine this according to the interleave only once */ + status_OK = CMD(0x80); + + timeo = jiffies + HZ; +retry: + mutex_lock(&chip->mutex); + + /* Check that the chip's ready to talk to us. */ + switch (chip->state) { + case FL_CFI_QUERY: + case FL_JEDEC_QUERY: + case FL_READY: + map_write(map, CMD(0x70), adr); + chip->state = FL_STATUS; + fallthrough; + case FL_STATUS: + status = map_read(map, adr); + if (map_word_andequal(map, status, status_OK, status_OK)) + break; + + /* Urgh. Chip not yet ready to talk to us. */ + if (time_after(jiffies, timeo)) { + mutex_unlock(&chip->mutex); + printk(KERN_ERR "waiting for chip to be ready timed out in erase\n"); + return -EIO; + } + + /* Latency issues. Drop the lock, wait a while and retry */ + mutex_unlock(&chip->mutex); + cfi_udelay(1); + goto retry; + + default: + /* Stick ourselves on a wait queue to be woken when + someone changes the status */ + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + timeo = jiffies + HZ; + goto retry; + } + + ENABLE_VPP(map); + /* Clear the status register first */ + map_write(map, CMD(0x50), adr); + + /* Now erase */ + map_write(map, CMD(0x20), adr); + map_write(map, CMD(0xD0), adr); + chip->state = FL_ERASING; + + mutex_unlock(&chip->mutex); + msleep(1000); + mutex_lock(&chip->mutex); + + /* FIXME. Use a timer to check this, and return immediately. */ + /* Once the state machine's known to be working I'll do that */ + + timeo = jiffies + (HZ*20); + for (;;) { + if (chip->state != FL_ERASING) { + /* Someone's suspended the erase. Sleep */ + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + timeo = jiffies + (HZ*20); /* FIXME */ + mutex_lock(&chip->mutex); + continue; + } + + status = map_read(map, adr); + if (map_word_andequal(map, status, status_OK, status_OK)) + break; + + /* OK Still waiting */ + if (time_after(jiffies, timeo)) { + map_write(map, CMD(0x70), adr); + chip->state = FL_STATUS; + printk(KERN_ERR "waiting for erase to complete timed out. Xstatus = %lx, status = %lx.\n", status.x[0], map_read(map, adr).x[0]); + DISABLE_VPP(map); + mutex_unlock(&chip->mutex); + return -EIO; + } + + /* Latency issues. Drop the lock, wait a while and retry */ + mutex_unlock(&chip->mutex); + cfi_udelay(1); + mutex_lock(&chip->mutex); + } + + DISABLE_VPP(map); + ret = 0; + + /* We've broken this before. It doesn't hurt to be safe */ + map_write(map, CMD(0x70), adr); + chip->state = FL_STATUS; + status = map_read(map, adr); + + /* check for lock bit */ + if (map_word_bitsset(map, status, CMD(0x3a))) { + unsigned char chipstatus = status.x[0]; + if (!map_word_equal(map, status, CMD(chipstatus))) { + int i, w; + for (w=0; w> (cfi->device_type * 8); + } + } + printk(KERN_WARNING "Status is not identical for all chips: 0x%lx. Merging to give 0x%02x\n", + status.x[0], chipstatus); + } + /* Reset the error bits */ + map_write(map, CMD(0x50), adr); + map_write(map, CMD(0x70), adr); + + if ((chipstatus & 0x30) == 0x30) { + printk(KERN_NOTICE "Chip reports improper command sequence: status 0x%x\n", chipstatus); + ret = -EIO; + } else if (chipstatus & 0x02) { + /* Protection bit set */ + ret = -EROFS; + } else if (chipstatus & 0x8) { + /* Voltage */ + printk(KERN_WARNING "Chip reports voltage low on erase: status 0x%x\n", chipstatus); + ret = -EIO; + } else if (chipstatus & 0x20) { + if (retries--) { + printk(KERN_DEBUG "Chip erase failed at 0x%08lx: status 0x%x. Retrying...\n", adr, chipstatus); + timeo = jiffies + HZ; + chip->state = FL_STATUS; + mutex_unlock(&chip->mutex); + goto retry; + } + printk(KERN_DEBUG "Chip erase failed at 0x%08lx: status 0x%x\n", adr, chipstatus); + ret = -EIO; + } + } + + wake_up(&chip->wq); + mutex_unlock(&chip->mutex); + return ret; +} + +static int cfi_staa_erase_varsize(struct mtd_info *mtd, + struct erase_info *instr) +{ struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long adr, len; + int chipnum, ret; + int i, first; + struct mtd_erase_region_info *regions = mtd->eraseregions; + + /* Check that both start and end of the requested erase are + * aligned with the erasesize at the appropriate addresses. + */ + + i = 0; + + /* Skip all erase regions which are ended before the start of + the requested erase. Actually, to save on the calculations, + we skip to the first erase region which starts after the + start of the requested erase, and then go back one. + */ + + while (i < mtd->numeraseregions && instr->addr >= regions[i].offset) + i++; + i--; + + /* OK, now i is pointing at the erase region in which this + erase request starts. Check the start of the requested + erase range is aligned with the erase size which is in + effect here. + */ + + if (instr->addr & (regions[i].erasesize-1)) + return -EINVAL; + + /* Remember the erase region we start on */ + first = i; + + /* Next, check that the end of the requested erase is aligned + * with the erase region at that address. + */ + + while (inumeraseregions && (instr->addr + instr->len) >= regions[i].offset) + i++; + + /* As before, drop back one to point at the region in which + the address actually falls + */ + i--; + + if ((instr->addr + instr->len) & (regions[i].erasesize-1)) + return -EINVAL; + + chipnum = instr->addr >> cfi->chipshift; + adr = instr->addr - (chipnum << cfi->chipshift); + len = instr->len; + + i=first; + + while(len) { + ret = do_erase_oneblock(map, &cfi->chips[chipnum], adr); + + if (ret) + return ret; + + adr += regions[i].erasesize; + len -= regions[i].erasesize; + + if (adr % (1<< cfi->chipshift) == (((unsigned long)regions[i].offset + (regions[i].erasesize * regions[i].numblocks)) %( 1<< cfi->chipshift))) + i++; + + if (adr >> cfi->chipshift) { + adr = 0; + chipnum++; + + if (chipnum >= cfi->numchips) + break; + } + } + + return 0; +} + +static void cfi_staa_sync (struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int i; + struct flchip *chip; + int ret = 0; + DECLARE_WAITQUEUE(wait, current); + + for (i=0; !ret && inumchips; i++) { + chip = &cfi->chips[i]; + + retry: + mutex_lock(&chip->mutex); + + switch(chip->state) { + case FL_READY: + case FL_STATUS: + case FL_CFI_QUERY: + case FL_JEDEC_QUERY: + chip->oldstate = chip->state; + chip->state = FL_SYNCING; + /* No need to wake_up() on this state change - + * as the whole point is that nobody can do anything + * with the chip now anyway. + */ + fallthrough; + case FL_SYNCING: + mutex_unlock(&chip->mutex); + break; + + default: + /* Not an idle state */ + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + + goto retry; + } + } + + /* Unlock the chips again */ + + for (i--; i >=0; i--) { + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + + if (chip->state == FL_SYNCING) { + chip->state = chip->oldstate; + wake_up(&chip->wq); + } + mutex_unlock(&chip->mutex); + } +} + +static inline int do_lock_oneblock(struct map_info *map, struct flchip *chip, unsigned long adr) +{ + struct cfi_private *cfi = map->fldrv_priv; + map_word status, status_OK; + unsigned long timeo = jiffies + HZ; + DECLARE_WAITQUEUE(wait, current); + + adr += chip->start; + + /* Let's determine this according to the interleave only once */ + status_OK = CMD(0x80); + + timeo = jiffies + HZ; +retry: + mutex_lock(&chip->mutex); + + /* Check that the chip's ready to talk to us. */ + switch (chip->state) { + case FL_CFI_QUERY: + case FL_JEDEC_QUERY: + case FL_READY: + map_write(map, CMD(0x70), adr); + chip->state = FL_STATUS; + fallthrough; + case FL_STATUS: + status = map_read(map, adr); + if (map_word_andequal(map, status, status_OK, status_OK)) + break; + + /* Urgh. Chip not yet ready to talk to us. */ + if (time_after(jiffies, timeo)) { + mutex_unlock(&chip->mutex); + printk(KERN_ERR "waiting for chip to be ready timed out in lock\n"); + return -EIO; + } + + /* Latency issues. Drop the lock, wait a while and retry */ + mutex_unlock(&chip->mutex); + cfi_udelay(1); + goto retry; + + default: + /* Stick ourselves on a wait queue to be woken when + someone changes the status */ + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + timeo = jiffies + HZ; + goto retry; + } + + ENABLE_VPP(map); + map_write(map, CMD(0x60), adr); + map_write(map, CMD(0x01), adr); + chip->state = FL_LOCKING; + + mutex_unlock(&chip->mutex); + msleep(1000); + mutex_lock(&chip->mutex); + + /* FIXME. Use a timer to check this, and return immediately. */ + /* Once the state machine's known to be working I'll do that */ + + timeo = jiffies + (HZ*2); + for (;;) { + + status = map_read(map, adr); + if (map_word_andequal(map, status, status_OK, status_OK)) + break; + + /* OK Still waiting */ + if (time_after(jiffies, timeo)) { + map_write(map, CMD(0x70), adr); + chip->state = FL_STATUS; + printk(KERN_ERR "waiting for lock to complete timed out. Xstatus = %lx, status = %lx.\n", status.x[0], map_read(map, adr).x[0]); + DISABLE_VPP(map); + mutex_unlock(&chip->mutex); + return -EIO; + } + + /* Latency issues. Drop the lock, wait a while and retry */ + mutex_unlock(&chip->mutex); + cfi_udelay(1); + mutex_lock(&chip->mutex); + } + + /* Done and happy. */ + chip->state = FL_STATUS; + DISABLE_VPP(map); + wake_up(&chip->wq); + mutex_unlock(&chip->mutex); + return 0; +} +static int cfi_staa_lock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long adr; + int chipnum, ret; +#ifdef DEBUG_LOCK_BITS + int ofs_factor = cfi->interleave * cfi->device_type; +#endif + + if (ofs & (mtd->erasesize - 1)) + return -EINVAL; + + if (len & (mtd->erasesize -1)) + return -EINVAL; + + chipnum = ofs >> cfi->chipshift; + adr = ofs - (chipnum << cfi->chipshift); + + while(len) { + +#ifdef DEBUG_LOCK_BITS + cfi_send_gen_cmd(0x90, 0x55, 0, map, cfi, cfi->device_type, NULL); + printk("before lock: block status register is %x\n",cfi_read_query(map, adr+(2*ofs_factor))); + cfi_send_gen_cmd(0xff, 0x55, 0, map, cfi, cfi->device_type, NULL); +#endif + + ret = do_lock_oneblock(map, &cfi->chips[chipnum], adr); + +#ifdef DEBUG_LOCK_BITS + cfi_send_gen_cmd(0x90, 0x55, 0, map, cfi, cfi->device_type, NULL); + printk("after lock: block status register is %x\n",cfi_read_query(map, adr+(2*ofs_factor))); + cfi_send_gen_cmd(0xff, 0x55, 0, map, cfi, cfi->device_type, NULL); +#endif + + if (ret) + return ret; + + adr += mtd->erasesize; + len -= mtd->erasesize; + + if (adr >> cfi->chipshift) { + adr = 0; + chipnum++; + + if (chipnum >= cfi->numchips) + break; + } + } + return 0; +} +static inline int do_unlock_oneblock(struct map_info *map, struct flchip *chip, unsigned long adr) +{ + struct cfi_private *cfi = map->fldrv_priv; + map_word status, status_OK; + unsigned long timeo = jiffies + HZ; + DECLARE_WAITQUEUE(wait, current); + + adr += chip->start; + + /* Let's determine this according to the interleave only once */ + status_OK = CMD(0x80); + + timeo = jiffies + HZ; +retry: + mutex_lock(&chip->mutex); + + /* Check that the chip's ready to talk to us. */ + switch (chip->state) { + case FL_CFI_QUERY: + case FL_JEDEC_QUERY: + case FL_READY: + map_write(map, CMD(0x70), adr); + chip->state = FL_STATUS; + fallthrough; + case FL_STATUS: + status = map_read(map, adr); + if (map_word_andequal(map, status, status_OK, status_OK)) + break; + + /* Urgh. Chip not yet ready to talk to us. */ + if (time_after(jiffies, timeo)) { + mutex_unlock(&chip->mutex); + printk(KERN_ERR "waiting for chip to be ready timed out in unlock\n"); + return -EIO; + } + + /* Latency issues. Drop the lock, wait a while and retry */ + mutex_unlock(&chip->mutex); + cfi_udelay(1); + goto retry; + + default: + /* Stick ourselves on a wait queue to be woken when + someone changes the status */ + set_current_state(TASK_UNINTERRUPTIBLE); + add_wait_queue(&chip->wq, &wait); + mutex_unlock(&chip->mutex); + schedule(); + remove_wait_queue(&chip->wq, &wait); + timeo = jiffies + HZ; + goto retry; + } + + ENABLE_VPP(map); + map_write(map, CMD(0x60), adr); + map_write(map, CMD(0xD0), adr); + chip->state = FL_UNLOCKING; + + mutex_unlock(&chip->mutex); + msleep(1000); + mutex_lock(&chip->mutex); + + /* FIXME. Use a timer to check this, and return immediately. */ + /* Once the state machine's known to be working I'll do that */ + + timeo = jiffies + (HZ*2); + for (;;) { + + status = map_read(map, adr); + if (map_word_andequal(map, status, status_OK, status_OK)) + break; + + /* OK Still waiting */ + if (time_after(jiffies, timeo)) { + map_write(map, CMD(0x70), adr); + chip->state = FL_STATUS; + printk(KERN_ERR "waiting for unlock to complete timed out. Xstatus = %lx, status = %lx.\n", status.x[0], map_read(map, adr).x[0]); + DISABLE_VPP(map); + mutex_unlock(&chip->mutex); + return -EIO; + } + + /* Latency issues. Drop the unlock, wait a while and retry */ + mutex_unlock(&chip->mutex); + cfi_udelay(1); + mutex_lock(&chip->mutex); + } + + /* Done and happy. */ + chip->state = FL_STATUS; + DISABLE_VPP(map); + wake_up(&chip->wq); + mutex_unlock(&chip->mutex); + return 0; +} +static int cfi_staa_unlock(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long adr; + int chipnum, ret; +#ifdef DEBUG_LOCK_BITS + int ofs_factor = cfi->interleave * cfi->device_type; +#endif + + chipnum = ofs >> cfi->chipshift; + adr = ofs - (chipnum << cfi->chipshift); + +#ifdef DEBUG_LOCK_BITS + { + unsigned long temp_adr = adr; + unsigned long temp_len = len; + + cfi_send_gen_cmd(0x90, 0x55, 0, map, cfi, cfi->device_type, NULL); + while (temp_len) { + printk("before unlock %x: block status register is %x\n",temp_adr,cfi_read_query(map, temp_adr+(2*ofs_factor))); + temp_adr += mtd->erasesize; + temp_len -= mtd->erasesize; + } + cfi_send_gen_cmd(0xff, 0x55, 0, map, cfi, cfi->device_type, NULL); + } +#endif + + ret = do_unlock_oneblock(map, &cfi->chips[chipnum], adr); + +#ifdef DEBUG_LOCK_BITS + cfi_send_gen_cmd(0x90, 0x55, 0, map, cfi, cfi->device_type, NULL); + printk("after unlock: block status register is %x\n",cfi_read_query(map, adr+(2*ofs_factor))); + cfi_send_gen_cmd(0xff, 0x55, 0, map, cfi, cfi->device_type, NULL); +#endif + + return ret; +} + +static int cfi_staa_suspend(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int i; + struct flchip *chip; + int ret = 0; + + for (i=0; !ret && inumchips; i++) { + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + + switch(chip->state) { + case FL_READY: + case FL_STATUS: + case FL_CFI_QUERY: + case FL_JEDEC_QUERY: + chip->oldstate = chip->state; + chip->state = FL_PM_SUSPENDED; + /* No need to wake_up() on this state change - + * as the whole point is that nobody can do anything + * with the chip now anyway. + */ + break; + + case FL_PM_SUSPENDED: + break; + + default: + ret = -EAGAIN; + break; + } + mutex_unlock(&chip->mutex); + } + + /* Unlock the chips again */ + + if (ret) { + for (i--; i >=0; i--) { + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + + if (chip->state == FL_PM_SUSPENDED) { + /* No need to force it into a known state here, + because we're returning failure, and it didn't + get power cycled */ + chip->state = chip->oldstate; + wake_up(&chip->wq); + } + mutex_unlock(&chip->mutex); + } + } + + return ret; +} + +static void cfi_staa_resume(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + int i; + struct flchip *chip; + + for (i=0; inumchips; i++) { + + chip = &cfi->chips[i]; + + mutex_lock(&chip->mutex); + + /* Go to known state. Chip may have been power cycled */ + if (chip->state == FL_PM_SUSPENDED) { + map_write(map, CMD(0xFF), 0); + chip->state = FL_READY; + wake_up(&chip->wq); + } + + mutex_unlock(&chip->mutex); + } +} + +static void cfi_staa_destroy(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + kfree(cfi->cmdset_priv); + kfree(cfi); +} + +MODULE_DESCRIPTION("MTD chip driver for ST Advanced Architecture Command Set (ID 0x0020)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/chips/cfi_probe.c b/drivers/mtd/chips/cfi_probe.c new file mode 100644 index 000000000..bdc04a6ec --- /dev/null +++ b/drivers/mtd/chips/cfi_probe.c @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + Common Flash Interface probe code. + (C) 2000 Red Hat. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +//#define DEBUG_CFI + +#ifdef DEBUG_CFI +static void print_cfi_ident(struct cfi_ident *); +#endif + +static int cfi_probe_chip(struct map_info *map, __u32 base, + unsigned long *chip_map, struct cfi_private *cfi); +static int cfi_chip_setup(struct map_info *map, struct cfi_private *cfi); + +struct mtd_info *cfi_probe(struct map_info *map); + +#ifdef CONFIG_MTD_XIP + +/* only needed for short periods, so this is rather simple */ +#define xip_disable() local_irq_disable() + +#define xip_allowed(base, map) \ +do { \ + (void) map_read(map, base); \ + xip_iprefetch(); \ + local_irq_enable(); \ +} while (0) + +#define xip_enable(base, map, cfi) \ +do { \ + cfi_qry_mode_off(base, map, cfi); \ + xip_allowed(base, map); \ +} while (0) + +#define xip_disable_qry(base, map, cfi) \ +do { \ + xip_disable(); \ + cfi_qry_mode_on(base, map, cfi); \ +} while (0) + +#else + +#define xip_disable() do { } while (0) +#define xip_allowed(base, map) do { } while (0) +#define xip_enable(base, map, cfi) do { } while (0) +#define xip_disable_qry(base, map, cfi) do { } while (0) + +#endif + +/* + * This fixup occurs immediately after reading the CFI structure and can affect + * the number of chips detected, unlike cfi_fixup, which occurs after an + * mtd_info structure has been created for the chip. + */ +struct cfi_early_fixup { + uint16_t mfr; + uint16_t id; + void (*fixup)(struct cfi_private *cfi); +}; + +static void cfi_early_fixup(struct cfi_private *cfi, + const struct cfi_early_fixup *fixups) +{ + const struct cfi_early_fixup *f; + + for (f = fixups; f->fixup; f++) { + if (((f->mfr == CFI_MFR_ANY) || (f->mfr == cfi->mfr)) && + ((f->id == CFI_ID_ANY) || (f->id == cfi->id))) { + f->fixup(cfi); + } + } +} + +/* check for QRY. + in: interleave,type,mode + ret: table index, <0 for error + */ + +static int __xipram cfi_probe_chip(struct map_info *map, __u32 base, + unsigned long *chip_map, struct cfi_private *cfi) +{ + int i; + + if ((base + 0) >= map->size) { + printk(KERN_NOTICE + "Probe at base[0x00](0x%08lx) past the end of the map(0x%08lx)\n", + (unsigned long)base, map->size -1); + return 0; + } + if ((base + 0xff) >= map->size) { + printk(KERN_NOTICE + "Probe at base[0x55](0x%08lx) past the end of the map(0x%08lx)\n", + (unsigned long)base + 0x55, map->size -1); + return 0; + } + + xip_disable(); + if (!cfi_qry_mode_on(base, map, cfi)) { + xip_enable(base, map, cfi); + return 0; + } + + if (!cfi->numchips) { + /* This is the first time we're called. Set up the CFI + stuff accordingly and return */ + return cfi_chip_setup(map, cfi); + } + + /* Check each previous chip to see if it's an alias */ + for (i=0; i < (base >> cfi->chipshift); i++) { + unsigned long start; + if(!test_bit(i, chip_map)) { + /* Skip location; no valid chip at this address */ + continue; + } + start = i << cfi->chipshift; + /* This chip should be in read mode if it's one + we've already touched. */ + if (cfi_qry_present(map, start, cfi)) { + /* Eep. This chip also had the QRY marker. + * Is it an alias for the new one? */ + cfi_qry_mode_off(start, map, cfi); + + /* If the QRY marker goes away, it's an alias */ + if (!cfi_qry_present(map, start, cfi)) { + xip_allowed(base, map); + printk(KERN_DEBUG "%s: Found an alias at 0x%x for the chip at 0x%lx\n", + map->name, base, start); + return 0; + } + /* Yes, it's actually got QRY for data. Most + * unfortunate. Stick the new chip in read mode + * too and if it's the same, assume it's an alias. */ + /* FIXME: Use other modes to do a proper check */ + cfi_qry_mode_off(base, map, cfi); + + if (cfi_qry_present(map, base, cfi)) { + xip_allowed(base, map); + printk(KERN_DEBUG "%s: Found an alias at 0x%x for the chip at 0x%lx\n", + map->name, base, start); + return 0; + } + } + } + + /* OK, if we got to here, then none of the previous chips appear to + be aliases for the current one. */ + set_bit((base >> cfi->chipshift), chip_map); /* Update chip map */ + cfi->numchips++; + + /* Put it back into Read Mode */ + cfi_qry_mode_off(base, map, cfi); + xip_allowed(base, map); + + printk(KERN_INFO "%s: Found %d x%d devices at 0x%x in %d-bit bank\n", + map->name, cfi->interleave, cfi->device_type*8, base, + map->bankwidth*8); + + return 1; +} + +static void fixup_s70gl02gs_chips(struct cfi_private *cfi) +{ + /* + * S70GL02GS flash reports a single 256 MiB chip, but is really made up + * of two 128 MiB chips with 1024 sectors each. + */ + cfi->cfiq->DevSize = 27; + cfi->cfiq->EraseRegionInfo[0] = 0x20003ff; + pr_warn("Bad S70GL02GS CFI data; adjust to detect 2 chips\n"); +} + +static const struct cfi_early_fixup cfi_early_fixup_table[] = { + { CFI_MFR_AMD, 0x4801, fixup_s70gl02gs_chips }, + { }, +}; + +static int __xipram cfi_chip_setup(struct map_info *map, + struct cfi_private *cfi) +{ + int ofs_factor = cfi->interleave*cfi->device_type; + __u32 base = 0; + int num_erase_regions = cfi_read_query(map, base + (0x10 + 28)*ofs_factor); + int i; + int addr_unlock1 = 0x555, addr_unlock2 = 0x2AA; + + xip_enable(base, map, cfi); +#ifdef DEBUG_CFI + printk("Number of erase regions: %d\n", num_erase_regions); +#endif + if (!num_erase_regions) + return 0; + + cfi->cfiq = kmalloc_flex(*cfi->cfiq, EraseRegionInfo, num_erase_regions); + if (!cfi->cfiq) + return 0; + + memset(cfi->cfiq,0,sizeof(struct cfi_ident)); + + cfi->cfi_mode = CFI_MODE_CFI; + + cfi->sector_erase_cmd = CMD(0x30); + + /* Read the CFI info structure */ + xip_disable_qry(base, map, cfi); + for (i=0; i<(sizeof(struct cfi_ident) + num_erase_regions * 4); i++) + ((unsigned char *)cfi->cfiq)[i] = cfi_read_query(map,base + (0x10 + i)*ofs_factor); + + /* Do any necessary byteswapping */ + cfi->cfiq->P_ID = le16_to_cpu(cfi->cfiq->P_ID); + + cfi->cfiq->P_ADR = le16_to_cpu(cfi->cfiq->P_ADR); + cfi->cfiq->A_ID = le16_to_cpu(cfi->cfiq->A_ID); + cfi->cfiq->A_ADR = le16_to_cpu(cfi->cfiq->A_ADR); + cfi->cfiq->InterfaceDesc = le16_to_cpu(cfi->cfiq->InterfaceDesc); + cfi->cfiq->MaxBufWriteSize = le16_to_cpu(cfi->cfiq->MaxBufWriteSize); + +#ifdef DEBUG_CFI + /* Dump the information therein */ + print_cfi_ident(cfi->cfiq); +#endif + + for (i=0; icfiq->NumEraseRegions; i++) { + cfi->cfiq->EraseRegionInfo[i] = le32_to_cpu(cfi->cfiq->EraseRegionInfo[i]); + +#ifdef DEBUG_CFI + printk(" Erase Region #%d: BlockSize 0x%4.4X bytes, %d blocks\n", + i, (cfi->cfiq->EraseRegionInfo[i] >> 8) & ~0xff, + (cfi->cfiq->EraseRegionInfo[i] & 0xffff) + 1); +#endif + } + + if (cfi->cfiq->P_ID == P_ID_SST_OLD) { + addr_unlock1 = 0x5555; + addr_unlock2 = 0x2AAA; + } + + /* + * Note we put the device back into Read Mode BEFORE going into Auto + * Select Mode, as some devices support nesting of modes, others + * don't. This way should always work. + * On cmdset 0001 the writes of 0xaa and 0x55 are not needed, and + * so should be treated as nops or illegal (and so put the device + * back into Read Mode, which is a nop in this case). + */ + cfi_send_gen_cmd(0xf0, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xaa, addr_unlock1, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, addr_unlock2, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x90, addr_unlock1, base, map, cfi, cfi->device_type, NULL); + cfi->mfr = cfi_read_query16(map, base); + cfi->id = cfi_read_query16(map, base + ofs_factor); + + /* Get AMD/Spansion extended JEDEC ID */ + if (cfi->mfr == CFI_MFR_AMD && (cfi->id & 0xff) == 0x7e) + cfi->id = cfi_read_query(map, base + 0xe * ofs_factor) << 8 | + cfi_read_query(map, base + 0xf * ofs_factor); + + /* Put it back into Read Mode */ + cfi_qry_mode_off(base, map, cfi); + xip_allowed(base, map); + + cfi_early_fixup(cfi, cfi_early_fixup_table); + + printk(KERN_INFO "%s: Found %d x%d devices at 0x%x in %d-bit bank. Manufacturer ID %#08x Chip ID %#08x\n", + map->name, cfi->interleave, cfi->device_type*8, base, + map->bankwidth*8, cfi->mfr, cfi->id); + + return 1; +} + +#ifdef DEBUG_CFI +static char *vendorname(__u16 vendor) +{ + switch (vendor) { + case P_ID_NONE: + return "None"; + + case P_ID_INTEL_EXT: + return "Intel/Sharp Extended"; + + case P_ID_AMD_STD: + return "AMD/Fujitsu Standard"; + + case P_ID_INTEL_STD: + return "Intel/Sharp Standard"; + + case P_ID_AMD_EXT: + return "AMD/Fujitsu Extended"; + + case P_ID_WINBOND: + return "Winbond Standard"; + + case P_ID_ST_ADV: + return "ST Advanced"; + + case P_ID_MITSUBISHI_STD: + return "Mitsubishi Standard"; + + case P_ID_MITSUBISHI_EXT: + return "Mitsubishi Extended"; + + case P_ID_SST_PAGE: + return "SST Page Write"; + + case P_ID_SST_OLD: + return "SST 39VF160x/39VF320x"; + + case P_ID_INTEL_PERFORMANCE: + return "Intel Performance Code"; + + case P_ID_INTEL_DATA: + return "Intel Data"; + + case P_ID_RESERVED: + return "Not Allowed / Reserved for Future Use"; + + default: + return "Unknown"; + } +} + + +static void print_cfi_ident(struct cfi_ident *cfip) +{ +#if 0 + if (cfip->qry[0] != 'Q' || cfip->qry[1] != 'R' || cfip->qry[2] != 'Y') { + printk("Invalid CFI ident structure.\n"); + return; + } +#endif + printk("Primary Vendor Command Set: %4.4X (%s)\n", cfip->P_ID, vendorname(cfip->P_ID)); + if (cfip->P_ADR) + printk("Primary Algorithm Table at %4.4X\n", cfip->P_ADR); + else + printk("No Primary Algorithm Table\n"); + + printk("Alternative Vendor Command Set: %4.4X (%s)\n", cfip->A_ID, vendorname(cfip->A_ID)); + if (cfip->A_ADR) + printk("Alternate Algorithm Table at %4.4X\n", cfip->A_ADR); + else + printk("No Alternate Algorithm Table\n"); + + + printk("Vcc Minimum: %2d.%d V\n", cfip->VccMin >> 4, cfip->VccMin & 0xf); + printk("Vcc Maximum: %2d.%d V\n", cfip->VccMax >> 4, cfip->VccMax & 0xf); + if (cfip->VppMin) { + printk("Vpp Minimum: %2d.%d V\n", cfip->VppMin >> 4, cfip->VppMin & 0xf); + printk("Vpp Maximum: %2d.%d V\n", cfip->VppMax >> 4, cfip->VppMax & 0xf); + } + else + printk("No Vpp line\n"); + + printk("Typical byte/word write timeout: %d µs\n", 1<WordWriteTimeoutTyp); + printk("Maximum byte/word write timeout: %d µs\n", (1<WordWriteTimeoutMax) * (1<WordWriteTimeoutTyp)); + + if (cfip->BufWriteTimeoutTyp || cfip->BufWriteTimeoutMax) { + printk("Typical full buffer write timeout: %d µs\n", 1<BufWriteTimeoutTyp); + printk("Maximum full buffer write timeout: %d µs\n", (1<BufWriteTimeoutMax) * (1<BufWriteTimeoutTyp)); + } + else + printk("Full buffer write not supported\n"); + + printk("Typical block erase timeout: %d ms\n", 1<BlockEraseTimeoutTyp); + printk("Maximum block erase timeout: %d ms\n", (1<BlockEraseTimeoutMax) * (1<BlockEraseTimeoutTyp)); + if (cfip->ChipEraseTimeoutTyp || cfip->ChipEraseTimeoutMax) { + printk("Typical chip erase timeout: %d ms\n", 1<ChipEraseTimeoutTyp); + printk("Maximum chip erase timeout: %d ms\n", (1<ChipEraseTimeoutMax) * (1<ChipEraseTimeoutTyp)); + } + else + printk("Chip erase not supported\n"); + + printk("Device size: 0x%X bytes (%d MiB)\n", 1 << cfip->DevSize, 1<< (cfip->DevSize - 20)); + printk("Flash Device Interface description: 0x%4.4X\n", cfip->InterfaceDesc); + switch(cfip->InterfaceDesc) { + case CFI_INTERFACE_X8_ASYNC: + printk(" - x8-only asynchronous interface\n"); + break; + + case CFI_INTERFACE_X16_ASYNC: + printk(" - x16-only asynchronous interface\n"); + break; + + case CFI_INTERFACE_X8_BY_X16_ASYNC: + printk(" - supports x8 and x16 via BYTE# with asynchronous interface\n"); + break; + + case CFI_INTERFACE_X32_ASYNC: + printk(" - x32-only asynchronous interface\n"); + break; + + case CFI_INTERFACE_X16_BY_X32_ASYNC: + printk(" - supports x16 and x32 via Word# with asynchronous interface\n"); + break; + + case CFI_INTERFACE_NOT_ALLOWED: + printk(" - Not Allowed / Reserved\n"); + break; + + default: + printk(" - Unknown\n"); + break; + } + + printk("Max. bytes in buffer write: 0x%x\n", 1<< cfip->MaxBufWriteSize); + printk("Number of Erase Block Regions: %d\n", cfip->NumEraseRegions); + +} +#endif /* DEBUG_CFI */ + +static struct chip_probe cfi_chip_probe = { + .name = "CFI", + .probe_chip = cfi_probe_chip +}; + +struct mtd_info *cfi_probe(struct map_info *map) +{ + /* + * Just use the generic probe stuff to call our CFI-specific + * chip_probe routine in all the possible permutations, etc. + */ + return mtd_do_chip_probe(map, &cfi_chip_probe); +} + +static struct mtd_chip_driver cfi_chipdrv = { + .probe = cfi_probe, + .name = "cfi_probe", + .module = THIS_MODULE +}; + +static int __init cfi_probe_init(void) +{ + register_mtd_chip_driver(&cfi_chipdrv); + return 0; +} + +static void __exit cfi_probe_exit(void) +{ + unregister_mtd_chip_driver(&cfi_chipdrv); +} + +module_init(cfi_probe_init); +module_exit(cfi_probe_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Woodhouse et al."); +MODULE_DESCRIPTION("Probe code for CFI-compliant flash chips"); diff --git a/drivers/mtd/chips/cfi_util.c b/drivers/mtd/chips/cfi_util.c new file mode 100644 index 000000000..ef0aa6890 --- /dev/null +++ b/drivers/mtd/chips/cfi_util.c @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common Flash Interface support: + * Generic utility functions not dependent on command set + * + * Copyright (C) 2002 Red Hat + * Copyright (C) 2003 STMicroelectronics Limited + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +void cfi_udelay(int us) +{ + if (us >= 1000) { + msleep(DIV_ROUND_UP(us, 1000)); + } else { + udelay(us); + cond_resched(); + } +} +EXPORT_SYMBOL(cfi_udelay); + +/* + * Returns the command address according to the given geometry. + */ +uint32_t cfi_build_cmd_addr(uint32_t cmd_ofs, + struct map_info *map, struct cfi_private *cfi) +{ + unsigned bankwidth = map_bankwidth(map); + unsigned interleave = cfi_interleave(cfi); + unsigned type = cfi->device_type; + uint32_t addr; + + addr = (cmd_ofs * type) * interleave; + + /* Modify the unlock address if we are in compatibility mode. + * For 16bit devices on 8 bit busses + * and 32bit devices on 16 bit busses + * set the low bit of the alternating bit sequence of the address. + */ + if (((type * interleave) > bankwidth) && ((cmd_ofs & 0xff) == 0xaa)) + addr |= (type >> 1)*interleave; + + return addr; +} +EXPORT_SYMBOL(cfi_build_cmd_addr); + +/* + * Transforms the CFI command for the given geometry (bus width & interleave). + * It looks too long to be inline, but in the common case it should almost all + * get optimised away. + */ +map_word cfi_build_cmd(u_long cmd, struct map_info *map, struct cfi_private *cfi) +{ + map_word val = { {0} }; + int wordwidth, words_per_bus, chip_mode, chips_per_word; + unsigned long onecmd; + int i; + + /* We do it this way to give the compiler a fighting chance + of optimising away all the crap for 'bankwidth' larger than + an unsigned long, in the common case where that support is + disabled */ + if (map_bankwidth_is_large(map)) { + wordwidth = sizeof(unsigned long); + words_per_bus = (map_bankwidth(map)) / wordwidth; // i.e. normally 1 + } else { + wordwidth = map_bankwidth(map); + words_per_bus = 1; + } + + chip_mode = map_bankwidth(map) / cfi_interleave(cfi); + chips_per_word = wordwidth * cfi_interleave(cfi) / map_bankwidth(map); + + /* First, determine what the bit-pattern should be for a single + device, according to chip mode and endianness... */ + switch (chip_mode) { + default: BUG(); + case 1: + onecmd = cmd; + break; + case 2: + onecmd = cpu_to_cfi16(map, cmd); + break; + case 4: + onecmd = cpu_to_cfi32(map, cmd); + break; + } + + /* Now replicate it across the size of an unsigned long, or + just to the bus width as appropriate */ + switch (chips_per_word) { + default: BUG(); +#if BITS_PER_LONG >= 64 + case 8: + onecmd |= (onecmd << (chip_mode * 32)); + fallthrough; +#endif + case 4: + onecmd |= (onecmd << (chip_mode * 16)); + fallthrough; + case 2: + onecmd |= (onecmd << (chip_mode * 8)); + fallthrough; + case 1: + ; + } + + /* And finally, for the multi-word case, replicate it + in all words in the structure */ + for (i=0; i < words_per_bus; i++) { + val.x[i] = onecmd; + } + + return val; +} +EXPORT_SYMBOL(cfi_build_cmd); + +unsigned long cfi_merge_status(map_word val, struct map_info *map, + struct cfi_private *cfi) +{ + int wordwidth, words_per_bus, chip_mode, chips_per_word; + unsigned long onestat, res = 0; + int i; + + /* We do it this way to give the compiler a fighting chance + of optimising away all the crap for 'bankwidth' larger than + an unsigned long, in the common case where that support is + disabled */ + if (map_bankwidth_is_large(map)) { + wordwidth = sizeof(unsigned long); + words_per_bus = (map_bankwidth(map)) / wordwidth; // i.e. normally 1 + } else { + wordwidth = map_bankwidth(map); + words_per_bus = 1; + } + + chip_mode = map_bankwidth(map) / cfi_interleave(cfi); + chips_per_word = wordwidth * cfi_interleave(cfi) / map_bankwidth(map); + + onestat = val.x[0]; + /* Or all status words together */ + for (i=1; i < words_per_bus; i++) { + onestat |= val.x[i]; + } + + res = onestat; + switch(chips_per_word) { + default: BUG(); +#if BITS_PER_LONG >= 64 + case 8: + res |= (onestat >> (chip_mode * 32)); + fallthrough; +#endif + case 4: + res |= (onestat >> (chip_mode * 16)); + fallthrough; + case 2: + res |= (onestat >> (chip_mode * 8)); + fallthrough; + case 1: + ; + } + + /* Last, determine what the bit-pattern should be for a single + device, according to chip mode and endianness... */ + switch (chip_mode) { + case 1: + break; + case 2: + res = cfi16_to_cpu(map, res); + break; + case 4: + res = cfi32_to_cpu(map, res); + break; + default: BUG(); + } + return res; +} +EXPORT_SYMBOL(cfi_merge_status); + +/* + * Sends a CFI command to a bank of flash for the given geometry. + * + * Returns the offset in flash where the command was written. + * If prev_val is non-null, it will be set to the value at the command address, + * before the command was written. + */ +uint32_t cfi_send_gen_cmd(u_char cmd, uint32_t cmd_addr, uint32_t base, + struct map_info *map, struct cfi_private *cfi, + int type, map_word *prev_val) +{ + map_word val; + uint32_t addr = base + cfi_build_cmd_addr(cmd_addr, map, cfi); + val = cfi_build_cmd(cmd, map, cfi); + + if (prev_val) + *prev_val = map_read(map, addr); + + map_write(map, val, addr); + + return addr - base; +} +EXPORT_SYMBOL(cfi_send_gen_cmd); + +int __xipram cfi_qry_present(struct map_info *map, __u32 base, + struct cfi_private *cfi) +{ + int osf = cfi->interleave * cfi->device_type; /* scale factor */ + map_word val[3]; + map_word qry[3]; + + qry[0] = cfi_build_cmd('Q', map, cfi); + qry[1] = cfi_build_cmd('R', map, cfi); + qry[2] = cfi_build_cmd('Y', map, cfi); + + val[0] = map_read(map, base + osf*0x10); + val[1] = map_read(map, base + osf*0x11); + val[2] = map_read(map, base + osf*0x12); + + if (!map_word_equal(map, qry[0], val[0])) + return 0; + + if (!map_word_equal(map, qry[1], val[1])) + return 0; + + if (!map_word_equal(map, qry[2], val[2])) + return 0; + + return 1; /* "QRY" found */ +} +EXPORT_SYMBOL_GPL(cfi_qry_present); + +int __xipram cfi_qry_mode_on(uint32_t base, struct map_info *map, + struct cfi_private *cfi) +{ + cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); + if (cfi_qry_present(map, base, cfi)) + return 1; + /* QRY not found probably we deal with some odd CFI chips */ + /* Some revisions of some old Intel chips? */ + cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x98, 0x55, base, map, cfi, cfi->device_type, NULL); + if (cfi_qry_present(map, base, cfi)) + return 1; + /* ST M29DW chips */ + cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x98, 0x555, base, map, cfi, cfi->device_type, NULL); + if (cfi_qry_present(map, base, cfi)) + return 1; + /* some old SST chips, e.g. 39VF160x/39VF320x */ + cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xAA, 0x5555, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, 0x2AAA, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x98, 0x5555, base, map, cfi, cfi->device_type, NULL); + if (cfi_qry_present(map, base, cfi)) + return 1; + /* SST 39VF640xB */ + cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xAA, 0x555, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, 0x2AA, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x98, 0x555, base, map, cfi, cfi->device_type, NULL); + if (cfi_qry_present(map, base, cfi)) + return 1; + /* QRY not found */ + return 0; +} +EXPORT_SYMBOL_GPL(cfi_qry_mode_on); + +void __xipram cfi_qry_mode_off(uint32_t base, struct map_info *map, + struct cfi_private *cfi) +{ + cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); + /* M29W128G flashes require an additional reset command + when exit qry mode */ + if ((cfi->mfr == CFI_MFR_ST) && (cfi->id == 0x227E || cfi->id == 0x7E)) + cfi_send_gen_cmd(0xF0, 0, base, map, cfi, cfi->device_type, NULL); +} +EXPORT_SYMBOL_GPL(cfi_qry_mode_off); + +struct cfi_extquery * +__xipram cfi_read_pri(struct map_info *map, __u16 adr, __u16 size, const char* name) +{ + struct cfi_private *cfi = map->fldrv_priv; + __u32 base = 0; // cfi->chips[0].start; + int ofs_factor = cfi->interleave * cfi->device_type; + int i; + struct cfi_extquery *extp = NULL; + + if (!adr) + goto out; + + printk(KERN_INFO "%s Extended Query Table at 0x%4.4X\n", name, adr); + + extp = kmalloc(size, GFP_KERNEL); + if (!extp) + goto out; + +#ifdef CONFIG_MTD_XIP + local_irq_disable(); +#endif + + /* Switch it into Query Mode */ + cfi_qry_mode_on(base, map, cfi); + /* Read in the Extended Query Table */ + for (i=0; ipriv; + struct cfi_private *cfi = map->fldrv_priv; + struct cfi_fixup *f; + + for (f=fixups; f->fixup; f++) { + if (((f->mfr == CFI_MFR_ANY) || (f->mfr == cfi->mfr)) && + ((f->id == CFI_ID_ANY) || (f->id == cfi->id))) { + f->fixup(mtd); + } + } +} + +EXPORT_SYMBOL(cfi_fixup); + +int cfi_varsize_frob(struct mtd_info *mtd, varsize_frob_t frob, + loff_t ofs, size_t len, void *thunk) +{ + struct map_info *map = mtd->priv; + struct cfi_private *cfi = map->fldrv_priv; + unsigned long adr; + int chipnum, ret = 0; + int i, first; + struct mtd_erase_region_info *regions = mtd->eraseregions; + + /* Check that both start and end of the requested erase are + * aligned with the erasesize at the appropriate addresses. + */ + + i = 0; + + /* Skip all erase regions which are ended before the start of + the requested erase. Actually, to save on the calculations, + we skip to the first erase region which starts after the + start of the requested erase, and then go back one. + */ + + while (i < mtd->numeraseregions && ofs >= regions[i].offset) + i++; + i--; + + /* OK, now i is pointing at the erase region in which this + erase request starts. Check the start of the requested + erase range is aligned with the erase size which is in + effect here. + */ + + if (ofs & (regions[i].erasesize-1)) + return -EINVAL; + + /* Remember the erase region we start on */ + first = i; + + /* Next, check that the end of the requested erase is aligned + * with the erase region at that address. + */ + + while (inumeraseregions && (ofs + len) >= regions[i].offset) + i++; + + /* As before, drop back one to point at the region in which + the address actually falls + */ + i--; + + if ((ofs + len) & (regions[i].erasesize-1)) + return -EINVAL; + + chipnum = ofs >> cfi->chipshift; + adr = ofs - (chipnum << cfi->chipshift); + + i=first; + + while(len) { + int size = regions[i].erasesize; + + ret = (*frob)(map, &cfi->chips[chipnum], adr, size, thunk); + + if (ret) + return ret; + + adr += size; + ofs += size; + len -= size; + + if (ofs == regions[i].offset + size * regions[i].numblocks) + i++; + + if (adr >> cfi->chipshift) { + adr = 0; + chipnum++; + + if (chipnum >= cfi->numchips) + break; + } + } + + return 0; +} + +EXPORT_SYMBOL(cfi_varsize_frob); + +MODULE_DESCRIPTION("Common Flash Interface Generic utility functions"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mtd/chips/chipreg.c b/drivers/mtd/chips/chipreg.c new file mode 100644 index 000000000..a05e10368 --- /dev/null +++ b/drivers/mtd/chips/chipreg.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Registration for chip drivers + * + */ + +#include +#include +#include +#include +#include +#include +#include + +static DEFINE_SPINLOCK(chip_drvs_lock); +static LIST_HEAD(chip_drvs_list); + +void register_mtd_chip_driver(struct mtd_chip_driver *drv) +{ + spin_lock(&chip_drvs_lock); + list_add(&drv->list, &chip_drvs_list); + spin_unlock(&chip_drvs_lock); +} + +void unregister_mtd_chip_driver(struct mtd_chip_driver *drv) +{ + spin_lock(&chip_drvs_lock); + list_del(&drv->list); + spin_unlock(&chip_drvs_lock); +} + +static struct mtd_chip_driver *get_mtd_chip_driver (const char *name) +{ + struct mtd_chip_driver *ret = NULL, *this; + + spin_lock(&chip_drvs_lock); + + list_for_each_entry(this, &chip_drvs_list, list) { + if (!strcmp(this->name, name)) { + ret = this; + break; + } + } + if (ret && !try_module_get(ret->module)) + ret = NULL; + + spin_unlock(&chip_drvs_lock); + + return ret; +} + + /* Hide all the horrid details, like some silly person taking + get_module_symbol() away from us, from the caller. */ + +struct mtd_info *do_map_probe(const char *name, struct map_info *map) +{ + struct mtd_chip_driver *drv; + struct mtd_info *ret; + + drv = get_mtd_chip_driver(name); + + if (!drv && !request_module("%s", name)) + drv = get_mtd_chip_driver(name); + + if (!drv) + return NULL; + + ret = drv->probe(map); + + /* We decrease the use count here. It may have been a + probe-only module, which is no longer required from this + point, having given us a handle on (and increased the use + count of) the actual driver code. + */ + module_put(drv->module); + + return ret; +} +/* + * Destroy an MTD device which was created for a map device. + * Make sure the MTD device is already unregistered before calling this + */ +void map_destroy(struct mtd_info *mtd) +{ + struct map_info *map = mtd->priv; + + if (map->fldrv->destroy) + map->fldrv->destroy(mtd); + + module_put(map->fldrv->module); + + kfree(mtd); +} + +EXPORT_SYMBOL(register_mtd_chip_driver); +EXPORT_SYMBOL(unregister_mtd_chip_driver); +EXPORT_SYMBOL(do_map_probe); +EXPORT_SYMBOL(map_destroy); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Woodhouse "); +MODULE_DESCRIPTION("Core routines for registering and invoking MTD chip drivers"); diff --git a/drivers/mtd/chips/fwh_lock.h b/drivers/mtd/chips/fwh_lock.h new file mode 100644 index 000000000..53e6b2d59 --- /dev/null +++ b/drivers/mtd/chips/fwh_lock.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef FWH_LOCK_H +#define FWH_LOCK_H + + +enum fwh_lock_state { + FWH_UNLOCKED = 0, + FWH_DENY_WRITE = 1, + FWH_IMMUTABLE = 2, + FWH_DENY_READ = 4, +}; + +struct fwh_xxlock_thunk { + enum fwh_lock_state val; + flstate_t state; +}; + + +#define FWH_XXLOCK_ONEBLOCK_LOCK ((struct fwh_xxlock_thunk){ FWH_DENY_WRITE, FL_LOCKING}) +#define FWH_XXLOCK_ONEBLOCK_UNLOCK ((struct fwh_xxlock_thunk){ FWH_UNLOCKED, FL_UNLOCKING}) + +/* + * This locking/unlock is specific to firmware hub parts. Only one + * is known that supports the Intel command set. Firmware + * hub parts cannot be interleaved as they are on the LPC bus + * so this code has not been tested with interleaved chips, + * and will likely fail in that context. + */ +static int fwh_xxlock_oneblock(struct map_info *map, struct flchip *chip, + unsigned long adr, int len, void *thunk) +{ + struct cfi_private *cfi = map->fldrv_priv; + struct fwh_xxlock_thunk *xxlt = (struct fwh_xxlock_thunk *)thunk; + int ret; + + /* Refuse the operation if the we cannot look behind the chip */ + if (chip->start < 0x400000) { + pr_debug( "MTD %s(): chip->start: %lx wanted >= 0x400000\n", + __func__, chip->start ); + return -EIO; + } + /* + * lock block registers: + * - on 64k boundariesand + * - bit 1 set high + * - block lock registers are 4MiB lower - overflow subtract (danger) + * + * The address manipulation is first done on the logical address + * which is 0 at the start of the chip, and then the offset of + * the individual chip is addted to it. Any other order a weird + * map offset could cause problems. + */ + adr = (adr & ~0xffffUL) | 0x2; + adr += chip->start - 0x400000; + + /* + * This is easy because these are writes to registers and not writes + * to flash memory - that means that we don't have to check status + * and timeout. + */ + mutex_lock(&chip->mutex); + ret = get_chip(map, chip, adr, FL_LOCKING); + if (ret) { + mutex_unlock(&chip->mutex); + return ret; + } + + chip->oldstate = chip->state; + chip->state = xxlt->state; + map_write(map, CMD(xxlt->val), adr); + + /* Done and happy. */ + chip->state = chip->oldstate; + put_chip(map, chip, adr); + mutex_unlock(&chip->mutex); + return 0; +} + + +static int fwh_lock_varsize(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + int ret; + + ret = cfi_varsize_frob(mtd, fwh_xxlock_oneblock, ofs, len, + (void *)&FWH_XXLOCK_ONEBLOCK_LOCK); + + return ret; +} + + +static int fwh_unlock_varsize(struct mtd_info *mtd, loff_t ofs, uint64_t len) +{ + int ret; + + ret = cfi_varsize_frob(mtd, fwh_xxlock_oneblock, ofs, len, + (void *)&FWH_XXLOCK_ONEBLOCK_UNLOCK); + + return ret; +} + +static void fixup_use_fwh_lock(struct mtd_info *mtd) +{ + printk(KERN_NOTICE "using fwh lock/unlock method\n"); + /* Setup for the chips with the fwh lock method */ + mtd->_lock = fwh_lock_varsize; + mtd->_unlock = fwh_unlock_varsize; +} +#endif /* FWH_LOCK_H */ diff --git a/drivers/mtd/chips/gen_probe.c b/drivers/mtd/chips/gen_probe.c new file mode 100644 index 000000000..433194a76 --- /dev/null +++ b/drivers/mtd/chips/gen_probe.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Routines common to all CFI-type probes. + * (C) 2001-2003 Red Hat, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +static struct mtd_info *check_cmd_set(struct map_info *, int); +static struct cfi_private *genprobe_ident_chips(struct map_info *map, + struct chip_probe *cp); +static int genprobe_new_chip(struct map_info *map, struct chip_probe *cp, + struct cfi_private *cfi); + +struct mtd_info *mtd_do_chip_probe(struct map_info *map, struct chip_probe *cp) +{ + struct mtd_info *mtd; + struct cfi_private *cfi; + + /* First probe the map to see if we have CFI stuff there. */ + cfi = genprobe_ident_chips(map, cp); + + if (!cfi) + return NULL; + + map->fldrv_priv = cfi; + /* OK we liked it. Now find a driver for the command set it talks */ + + mtd = check_cmd_set(map, 1); /* First the primary cmdset */ + if (!mtd) + mtd = check_cmd_set(map, 0); /* Then the secondary */ + + if (mtd) { + if (mtd->size > map->size) { + printk(KERN_WARNING "Reducing visibility of %ldKiB chip to %ldKiB\n", + (unsigned long)mtd->size >> 10, + (unsigned long)map->size >> 10); + mtd->size = map->size; + } + return mtd; + } + + printk(KERN_WARNING"gen_probe: No supported Vendor Command Set found\n"); + + kfree(cfi->cfiq); + kfree(cfi); + map->fldrv_priv = NULL; + return NULL; +} +EXPORT_SYMBOL(mtd_do_chip_probe); + + +static struct cfi_private *genprobe_ident_chips(struct map_info *map, struct chip_probe *cp) +{ + struct cfi_private cfi; + struct cfi_private *retcfi; + unsigned long *chip_map; + int max_chips; + int i, j; + + memset(&cfi, 0, sizeof(cfi)); + + /* Call the probetype-specific code with all permutations of + interleave and device type, etc. */ + if (!genprobe_new_chip(map, cp, &cfi)) { + /* The probe didn't like it */ + pr_debug("%s: Found no %s device at location zero\n", + cp->name, map->name); + return NULL; + } + +#if 0 /* Let the CFI probe routine do this sanity check. The Intel and AMD + probe routines won't ever return a broken CFI structure anyway, + because they make them up themselves. + */ + if (cfi.cfiq->NumEraseRegions == 0) { + printk(KERN_WARNING "Number of erase regions is zero\n"); + kfree(cfi.cfiq); + return NULL; + } +#endif + cfi.chipshift = cfi.cfiq->DevSize; + + if (cfi_interleave_is_1(&cfi)) { + ; + } else if (cfi_interleave_is_2(&cfi)) { + cfi.chipshift++; + } else if (cfi_interleave_is_4((&cfi))) { + cfi.chipshift += 2; + } else if (cfi_interleave_is_8(&cfi)) { + cfi.chipshift += 3; + } else { + BUG(); + } + + cfi.numchips = 1; + + /* + * Allocate memory for bitmap of valid chips. + * Align bitmap storage size to full byte. + */ + max_chips = map->size >> cfi.chipshift; + if (!max_chips) { + printk(KERN_WARNING "NOR chip too large to fit in mapping. Attempting to cope...\n"); + max_chips = 1; + } + + chip_map = bitmap_zalloc(max_chips, GFP_KERNEL); + if (!chip_map) { + kfree(cfi.cfiq); + return NULL; + } + + set_bit(0, chip_map); /* Mark first chip valid */ + + /* + * Now probe for other chips, checking sensibly for aliases while + * we're at it. The new_chip probe above should have let the first + * chip in read mode. + */ + + for (i = 1; i < max_chips; i++) { + cp->probe_chip(map, i << cfi.chipshift, chip_map, &cfi); + } + + /* + * Now allocate the space for the structures we need to return to + * our caller, and copy the appropriate data into them. + */ + + retcfi = kmalloc_flex(*retcfi, chips, cfi.numchips); + + if (!retcfi) { + kfree(cfi.cfiq); + bitmap_free(chip_map); + return NULL; + } + + memcpy(retcfi, &cfi, sizeof(cfi)); + memset(&retcfi->chips[0], 0, sizeof(struct flchip) * cfi.numchips); + + for (i = 0, j = 0; (j < cfi.numchips) && (i < max_chips); i++) { + if(test_bit(i, chip_map)) { + struct flchip *pchip = &retcfi->chips[j++]; + + pchip->start = (i << cfi.chipshift); + pchip->state = FL_READY; + init_waitqueue_head(&pchip->wq); + mutex_init(&pchip->mutex); + } + } + + bitmap_free(chip_map); + return retcfi; +} + + +static int genprobe_new_chip(struct map_info *map, struct chip_probe *cp, + struct cfi_private *cfi) +{ + int min_chips = (map_bankwidth(map)/4?:1); /* At most 4-bytes wide. */ + int max_chips = map_bankwidth(map); /* And minimum 1 */ + int nr_chips, type; + + for (nr_chips = max_chips; nr_chips >= min_chips; nr_chips >>= 1) { + + if (!cfi_interleave_supported(nr_chips)) + continue; + + cfi->interleave = nr_chips; + + /* Minimum device size. Don't look for one 8-bit device + in a 16-bit bus, etc. */ + type = map_bankwidth(map) / nr_chips; + + for (; type <= CFI_DEVICETYPE_X32; type<<=1) { + cfi->device_type = type; + + if (cp->probe_chip(map, 0, NULL, cfi)) + return 1; + } + } + return 0; +} + +typedef struct mtd_info *cfi_cmdset_fn_t(struct map_info *, int); + +extern cfi_cmdset_fn_t cfi_cmdset_0001; +extern cfi_cmdset_fn_t cfi_cmdset_0002; +extern cfi_cmdset_fn_t cfi_cmdset_0020; + +static inline struct mtd_info *cfi_cmdset_unknown(struct map_info *map, + int primary) +{ + struct cfi_private *cfi = map->fldrv_priv; + __u16 type = primary?cfi->cfiq->P_ID:cfi->cfiq->A_ID; +#ifdef CONFIG_MODULES + cfi_cmdset_fn_t *probe_function; + char *probename; + + probename = kasprintf(GFP_KERNEL, "cfi_cmdset_%4.4X", type); + if (!probename) + return NULL; + + probe_function = __symbol_get(probename); + if (!probe_function) { + request_module("cfi_cmdset_%4.4X", type); + probe_function = __symbol_get(probename); + } + kfree(probename); + + if (probe_function) { + struct mtd_info *mtd; + + mtd = (*probe_function)(map, primary); + /* If it was happy, it'll have increased its own use count */ + symbol_put_addr(probe_function); + return mtd; + } +#endif + printk(KERN_NOTICE "Support for command set %04X not present\n", type); + + return NULL; +} + +static struct mtd_info *check_cmd_set(struct map_info *map, int primary) +{ + struct cfi_private *cfi = map->fldrv_priv; + __u16 type = primary?cfi->cfiq->P_ID:cfi->cfiq->A_ID; + + if (type == P_ID_NONE || type == P_ID_RESERVED) + return NULL; + + switch(type){ + /* We need these for the !CONFIG_MODULES case, + because symbol_get() doesn't work there */ +#ifdef CONFIG_MTD_CFI_INTELEXT + case P_ID_INTEL_EXT: + case P_ID_INTEL_STD: + case P_ID_INTEL_PERFORMANCE: + return cfi_cmdset_0001(map, primary); +#endif +#ifdef CONFIG_MTD_CFI_AMDSTD + case P_ID_AMD_STD: + case P_ID_SST_OLD: + case P_ID_WINBOND: + return cfi_cmdset_0002(map, primary); +#endif +#ifdef CONFIG_MTD_CFI_STAA + case P_ID_ST_ADV: + return cfi_cmdset_0020(map, primary); +#endif + default: + return cfi_cmdset_unknown(map, primary); + } +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Woodhouse "); +MODULE_DESCRIPTION("Helper routines for flash chip probe code"); diff --git a/drivers/mtd/chips/jedec_probe.c b/drivers/mtd/chips/jedec_probe.c new file mode 100644 index 000000000..a49ef682d --- /dev/null +++ b/drivers/mtd/chips/jedec_probe.c @@ -0,0 +1,2316 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + Common Flash Interface probe code. + (C) 2000 Red Hat. + See JEDEC (http://www.jedec.org/) standard JESD21C (section 3.5) + for the standard this probe goes back to. + + Occasionally maintained by Thayne Harbaugh tharbaugh at lnxi dot com +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* AMD */ +#define AM29DL800BB 0x22CB +#define AM29DL800BT 0x224A + +#define AM29F800BB 0x2258 +#define AM29F800BT 0x22D6 +#define AM29LV400BB 0x22BA +#define AM29LV400BT 0x22B9 +#define AM29LV800BB 0x225B +#define AM29LV800BT 0x22DA +#define AM29LV160DT 0x22C4 +#define AM29LV160DB 0x2249 +#define AM29F017D 0x003D +#define AM29F016D 0x00AD +#define AM29F080 0x00D5 +#define AM29F040 0x00A4 +#define AM29LV040B 0x004F +#define AM29F032B 0x0041 +#define AM29F002T 0x00B0 +#define AM29SL800DB 0x226B +#define AM29SL800DT 0x22EA + +/* Atmel */ +#define AT49BV512 0x0003 +#define AT29LV512 0x003d +#define AT49BV16X 0x00C0 +#define AT49BV16XT 0x00C2 +#define AT49BV32X 0x00C8 +#define AT49BV32XT 0x00C9 + +/* Eon */ +#define EN29LV400AT 0x22B9 +#define EN29LV400AB 0x22BA +#define EN29SL800BB 0x226B +#define EN29SL800BT 0x22EA + +/* Fujitsu */ +#define MBM29F040C 0x00A4 +#define MBM29F800BA 0x2258 +#define MBM29LV650UE 0x22D7 +#define MBM29LV320TE 0x22F6 +#define MBM29LV320BE 0x22F9 +#define MBM29LV160TE 0x22C4 +#define MBM29LV160BE 0x2249 +#define MBM29LV800BA 0x225B +#define MBM29LV800TA 0x22DA +#define MBM29LV400TC 0x22B9 +#define MBM29LV400BC 0x22BA + +/* Hyundai */ +#define HY29F002T 0x00B0 + +/* Intel */ +#define I28F004B3T 0x00d4 +#define I28F004B3B 0x00d5 +#define I28F400B3T 0x8894 +#define I28F400B3B 0x8895 +#define I28F008S5 0x00a6 +#define I28F016S5 0x00a0 +#define I28F008SA 0x00a2 +#define I28F008B3T 0x00d2 +#define I28F008B3B 0x00d3 +#define I28F800B3T 0x8892 +#define I28F800B3B 0x8893 +#define I28F016S3 0x00aa +#define I28F016B3T 0x00d0 +#define I28F016B3B 0x00d1 +#define I28F160B3T 0x8890 +#define I28F160B3B 0x8891 +#define I28F320B3T 0x8896 +#define I28F320B3B 0x8897 +#define I28F640B3T 0x8898 +#define I28F640B3B 0x8899 +#define I28F640C3B 0x88CD +#define I28F160F3T 0x88F3 +#define I28F160F3B 0x88F4 +#define I28F160C3T 0x88C2 +#define I28F160C3B 0x88C3 +#define I82802AB 0x00ad +#define I82802AC 0x00ac + +/* Macronix */ +#define MX29LV040C 0x004F +#define MX29LV160T 0x22C4 +#define MX29LV160B 0x2249 +#define MX29F040 0x00A4 +#define MX29F016 0x00AD +#define MX29F002T 0x00B0 +#define MX29F004T 0x0045 +#define MX29F004B 0x0046 + +/* NEC */ +#define UPD29F064115 0x221C + +/* PMC */ +#define PM49FL002 0x006D +#define PM49FL004 0x006E +#define PM49FL008 0x006A + +/* Sharp */ +#define LH28F640BF 0x00B0 + +/* ST - www.st.com */ +#define M29F800AB 0x0058 +#define M29W800DT 0x22D7 +#define M29W800DB 0x225B +#define M29W400DT 0x00EE +#define M29W400DB 0x00EF +#define M29W160DT 0x22C4 +#define M29W160DB 0x2249 +#define M29W040B 0x00E3 +#define M50FW040 0x002C +#define M50FW080 0x002D +#define M50FW016 0x002E +#define M50LPW080 0x002F +#define M50FLW080A 0x0080 +#define M50FLW080B 0x0081 +#define PSD4256G6V 0x00e9 + +/* SST */ +#define SST29EE020 0x0010 +#define SST29LE020 0x0012 +#define SST29EE512 0x005d +#define SST29LE512 0x003d +#define SST39LF800 0x2781 +#define SST39LF160 0x2782 +#define SST39VF1601 0x234b +#define SST39VF3201 0x235b +#define SST39WF1601 0x274b +#define SST39WF1602 0x274a +#define SST39LF512 0x00D4 +#define SST39LF010 0x00D5 +#define SST39LF020 0x00D6 +#define SST39LF040 0x00D7 +#define SST39SF010A 0x00B5 +#define SST39SF020A 0x00B6 +#define SST39SF040 0x00B7 +#define SST49LF004B 0x0060 +#define SST49LF040B 0x0050 +#define SST49LF008A 0x005a +#define SST49LF030A 0x001C +#define SST49LF040A 0x0051 +#define SST49LF080A 0x005B +#define SST36VF3203 0x7354 + +/* Toshiba */ +#define TC58FVT160 0x00C2 +#define TC58FVB160 0x0043 +#define TC58FVT321 0x009A +#define TC58FVB321 0x009C +#define TC58FVT641 0x0093 +#define TC58FVB641 0x0095 + +/* Winbond */ +#define W49V002A 0x00b0 + + +/* + * Unlock address sets for AMD command sets. + * Intel command sets use the MTD_UADDR_UNNECESSARY. + * Each identifier, except MTD_UADDR_UNNECESSARY, and + * MTD_UADDR_NO_SUPPORT must be defined below in unlock_addrs[]. + * MTD_UADDR_NOT_SUPPORTED must be 0 so that structure + * initialization need not require initializing all of the + * unlock addresses for all bit widths. + */ +enum uaddr { + MTD_UADDR_NOT_SUPPORTED = 0, /* data width not supported */ + MTD_UADDR_0x0555_0x02AA, + MTD_UADDR_0x0555_0x0AAA, + MTD_UADDR_0x5555_0x2AAA, + MTD_UADDR_0x0AAA_0x0554, + MTD_UADDR_0x0AAA_0x0555, + MTD_UADDR_0xAAAA_0x5555, + MTD_UADDR_DONT_CARE, /* Requires an arbitrary address */ + MTD_UADDR_UNNECESSARY, /* Does not require any address */ +}; + + +struct unlock_addr { + uint32_t addr1; + uint32_t addr2; +}; + + +/* + * I don't like the fact that the first entry in unlock_addrs[] + * exists, but is for MTD_UADDR_NOT_SUPPORTED - and, therefore, + * should not be used. The problem is that structures with + * initializers have extra fields initialized to 0. It is _very_ + * desirable to have the unlock address entries for unsupported + * data widths automatically initialized - that means that + * MTD_UADDR_NOT_SUPPORTED must be 0 and the first entry here + * must go unused. + */ +static const struct unlock_addr unlock_addrs[] = { + [MTD_UADDR_NOT_SUPPORTED] = { + .addr1 = 0xffff, + .addr2 = 0xffff + }, + + [MTD_UADDR_0x0555_0x02AA] = { + .addr1 = 0x0555, + .addr2 = 0x02aa + }, + + [MTD_UADDR_0x0555_0x0AAA] = { + .addr1 = 0x0555, + .addr2 = 0x0aaa + }, + + [MTD_UADDR_0x5555_0x2AAA] = { + .addr1 = 0x5555, + .addr2 = 0x2aaa + }, + + [MTD_UADDR_0x0AAA_0x0554] = { + .addr1 = 0x0AAA, + .addr2 = 0x0554 + }, + + [MTD_UADDR_0x0AAA_0x0555] = { + .addr1 = 0x0AAA, + .addr2 = 0x0555 + }, + + [MTD_UADDR_0xAAAA_0x5555] = { + .addr1 = 0xaaaa, + .addr2 = 0x5555 + }, + + [MTD_UADDR_DONT_CARE] = { + .addr1 = 0x0000, /* Doesn't matter which address */ + .addr2 = 0x0000 /* is used - must be last entry */ + }, + + [MTD_UADDR_UNNECESSARY] = { + .addr1 = 0x0000, + .addr2 = 0x0000 + } +}; + +struct amd_flash_info { + const char *name; + const uint16_t mfr_id; + const uint16_t dev_id; + const uint8_t dev_size; + const uint8_t nr_regions; + const uint16_t cmd_set; + const uint32_t regions[6]; + const uint8_t devtypes; /* Bitmask for x8, x16 etc. */ + const uint8_t uaddr; /* unlock addrs for 8, 16, 32, 64 */ +}; + +#define ERASEINFO(size,blocks) (size<<8)|(blocks-1) + +#define SIZE_64KiB 16 +#define SIZE_128KiB 17 +#define SIZE_256KiB 18 +#define SIZE_512KiB 19 +#define SIZE_1MiB 20 +#define SIZE_2MiB 21 +#define SIZE_4MiB 22 +#define SIZE_8MiB 23 + + +/* + * Please keep this list ordered by manufacturer! + * Fortunately, the list isn't searched often and so a + * slow, linear search isn't so bad. + */ +static const struct amd_flash_info jedec_table[] = { + { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29F032B, + .name = "AMD AM29F032B", + .uaddr = MTD_UADDR_0x0555_0x02AA, + .devtypes = CFI_DEVICETYPE_X8, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,64) + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29LV160DT, + .name = "AMD AM29LV160DT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,31), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1) + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29LV160DB, + .name = "AMD AM29LV160DB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,31) + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29LV400BB, + .name = "AMD AM29LV400BB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,7) + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29LV400BT, + .name = "AMD AM29LV400BT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,7), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1) + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29LV800BB, + .name = "AMD AM29LV800BB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,15), + } + }, { +/* add DL */ + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29DL800BB, + .name = "AMD AM29DL800BB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 6, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,4), + ERASEINFO(0x08000,1), + ERASEINFO(0x04000,1), + ERASEINFO(0x10000,14) + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29DL800BT, + .name = "AMD AM29DL800BT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 6, + .regions = { + ERASEINFO(0x10000,14), + ERASEINFO(0x04000,1), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,4), + ERASEINFO(0x08000,1), + ERASEINFO(0x04000,1) + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29F800BB, + .name = "AMD AM29F800BB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,15), + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29LV800BT, + .name = "AMD AM29LV800BT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,15), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1) + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29F800BT, + .name = "AMD AM29F800BT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,15), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1) + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29F017D, + .name = "AMD AM29F017D", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_DONT_CARE, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,32), + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29F016D, + .name = "AMD AM29F016D", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,32), + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29F080, + .name = "AMD AM29F080", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,16), + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29F040, + .name = "AMD AM29F040", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,8), + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29LV040B, + .name = "AMD AM29LV040B", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,8), + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29F002T, + .name = "AMD AM29F002T", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,3), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1), + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29SL800DT, + .name = "AMD AM29SL800DT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,15), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1), + } + }, { + .mfr_id = CFI_MFR_AMD, + .dev_id = AM29SL800DB, + .name = "AMD AM29SL800DB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,15), + } + }, { + .mfr_id = CFI_MFR_ATMEL, + .dev_id = AT49BV512, + .name = "Atmel AT49BV512", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_64KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,1) + } + }, { + .mfr_id = CFI_MFR_ATMEL, + .dev_id = AT29LV512, + .name = "Atmel AT29LV512", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_64KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x80,256), + ERASEINFO(0x80,256) + } + }, { + .mfr_id = CFI_MFR_ATMEL, + .dev_id = AT49BV16X, + .name = "Atmel AT49BV16X", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x0AAA, /* ???? */ + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000,8), + ERASEINFO(0x10000,31) + } + }, { + .mfr_id = CFI_MFR_ATMEL, + .dev_id = AT49BV16XT, + .name = "Atmel AT49BV16XT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x0AAA, /* ???? */ + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000,31), + ERASEINFO(0x02000,8) + } + }, { + .mfr_id = CFI_MFR_ATMEL, + .dev_id = AT49BV32X, + .name = "Atmel AT49BV32X", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x0AAA, /* ???? */ + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000,8), + ERASEINFO(0x10000,63) + } + }, { + .mfr_id = CFI_MFR_ATMEL, + .dev_id = AT49BV32XT, + .name = "Atmel AT49BV32XT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x0AAA, /* ???? */ + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000,63), + ERASEINFO(0x02000,8) + } + }, { + .mfr_id = CFI_MFR_EON, + .dev_id = EN29LV400AT, + .name = "Eon EN29LV400AT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,7), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1), + } + }, { + .mfr_id = CFI_MFR_EON, + .dev_id = EN29LV400AB, + .name = "Eon EN29LV400AB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,7), + } + }, { + .mfr_id = CFI_MFR_EON, + .dev_id = EN29SL800BT, + .name = "Eon EN29SL800BT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,15), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1), + } + }, { + .mfr_id = CFI_MFR_EON, + .dev_id = EN29SL800BB, + .name = "Eon EN29SL800BB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,15), + } + }, { + .mfr_id = CFI_MFR_FUJITSU, + .dev_id = MBM29F040C, + .name = "Fujitsu MBM29F040C", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,8) + } + }, { + .mfr_id = CFI_MFR_FUJITSU, + .dev_id = MBM29F800BA, + .name = "Fujitsu MBM29F800BA", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,15), + } + }, { + .mfr_id = CFI_MFR_FUJITSU, + .dev_id = MBM29LV650UE, + .name = "Fujitsu MBM29LV650UE", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_DONT_CARE, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,128) + } + }, { + .mfr_id = CFI_MFR_FUJITSU, + .dev_id = MBM29LV320TE, + .name = "Fujitsu MBM29LV320TE", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000,63), + ERASEINFO(0x02000,8) + } + }, { + .mfr_id = CFI_MFR_FUJITSU, + .dev_id = MBM29LV320BE, + .name = "Fujitsu MBM29LV320BE", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000,8), + ERASEINFO(0x10000,63) + } + }, { + .mfr_id = CFI_MFR_FUJITSU, + .dev_id = MBM29LV160TE, + .name = "Fujitsu MBM29LV160TE", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,31), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1) + } + }, { + .mfr_id = CFI_MFR_FUJITSU, + .dev_id = MBM29LV160BE, + .name = "Fujitsu MBM29LV160BE", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,31) + } + }, { + .mfr_id = CFI_MFR_FUJITSU, + .dev_id = MBM29LV800BA, + .name = "Fujitsu MBM29LV800BA", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,15) + } + }, { + .mfr_id = CFI_MFR_FUJITSU, + .dev_id = MBM29LV800TA, + .name = "Fujitsu MBM29LV800TA", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,15), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1) + } + }, { + .mfr_id = CFI_MFR_FUJITSU, + .dev_id = MBM29LV400BC, + .name = "Fujitsu MBM29LV400BC", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,7) + } + }, { + .mfr_id = CFI_MFR_FUJITSU, + .dev_id = MBM29LV400TC, + .name = "Fujitsu MBM29LV400TC", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,7), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1) + } + }, { + .mfr_id = CFI_MFR_HYUNDAI, + .dev_id = HY29F002T, + .name = "Hyundai HY29F002T", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,3), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F004B3B, + .name = "Intel 28F004B3B", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000, 8), + ERASEINFO(0x10000, 7), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F004B3T, + .name = "Intel 28F004B3T", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000, 7), + ERASEINFO(0x02000, 8), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F400B3B, + .name = "Intel 28F400B3B", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000, 8), + ERASEINFO(0x10000, 7), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F400B3T, + .name = "Intel 28F400B3T", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000, 7), + ERASEINFO(0x02000, 8), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F008B3B, + .name = "Intel 28F008B3B", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000, 8), + ERASEINFO(0x10000, 15), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F008B3T, + .name = "Intel 28F008B3T", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000, 15), + ERASEINFO(0x02000, 8), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F008S5, + .name = "Intel 28F008S5", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,16), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F016S5, + .name = "Intel 28F016S5", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,32), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F008SA, + .name = "Intel 28F008SA", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000, 16), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F800B3B, + .name = "Intel 28F800B3B", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000, 8), + ERASEINFO(0x10000, 15), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F800B3T, + .name = "Intel 28F800B3T", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000, 15), + ERASEINFO(0x02000, 8), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F016B3B, + .name = "Intel 28F016B3B", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000, 8), + ERASEINFO(0x10000, 31), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F016S3, + .name = "Intel I28F016S3", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000, 32), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F016B3T, + .name = "Intel 28F016B3T", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000, 31), + ERASEINFO(0x02000, 8), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F160B3B, + .name = "Intel 28F160B3B", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000, 8), + ERASEINFO(0x10000, 31), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F160B3T, + .name = "Intel 28F160B3T", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000, 31), + ERASEINFO(0x02000, 8), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F320B3B, + .name = "Intel 28F320B3B", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000, 8), + ERASEINFO(0x10000, 63), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F320B3T, + .name = "Intel 28F320B3T", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000, 63), + ERASEINFO(0x02000, 8), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F640B3B, + .name = "Intel 28F640B3B", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000, 8), + ERASEINFO(0x10000, 127), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F640B3T, + .name = "Intel 28F640B3T", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000, 127), + ERASEINFO(0x02000, 8), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I28F640C3B, + .name = "Intel 28F640C3B", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_INTEL_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000, 8), + ERASEINFO(0x10000, 127), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I82802AB, + .name = "Intel 82802AB", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,8), + } + }, { + .mfr_id = CFI_MFR_INTEL, + .dev_id = I82802AC, + .name = "Intel 82802AC", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,16), + } + }, { + .mfr_id = CFI_MFR_MACRONIX, + .dev_id = MX29LV040C, + .name = "Macronix MX29LV040C", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,8), + } + }, { + .mfr_id = CFI_MFR_MACRONIX, + .dev_id = MX29LV160T, + .name = "MXIC MX29LV160T", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,31), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1) + } + }, { + .mfr_id = CFI_MFR_NEC, + .dev_id = UPD29F064115, + .name = "NEC uPD29F064115", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_0xAAAA_0x5555, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 3, + .regions = { + ERASEINFO(0x2000,8), + ERASEINFO(0x10000,126), + ERASEINFO(0x2000,8), + } + }, { + .mfr_id = CFI_MFR_MACRONIX, + .dev_id = MX29LV160B, + .name = "MXIC MX29LV160B", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,31) + } + }, { + .mfr_id = CFI_MFR_MACRONIX, + .dev_id = MX29F040, + .name = "Macronix MX29F040", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,8), + } + }, { + .mfr_id = CFI_MFR_MACRONIX, + .dev_id = MX29F016, + .name = "Macronix MX29F016", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,32), + } + }, { + .mfr_id = CFI_MFR_MACRONIX, + .dev_id = MX29F004T, + .name = "Macronix MX29F004T", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,7), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1), + } + }, { + .mfr_id = CFI_MFR_MACRONIX, + .dev_id = MX29F004B, + .name = "Macronix MX29F004B", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,7), + } + }, { + .mfr_id = CFI_MFR_MACRONIX, + .dev_id = MX29F002T, + .name = "Macronix MX29F002T", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,3), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1), + } + }, { + .mfr_id = CFI_MFR_PMC, + .dev_id = PM49FL002, + .name = "PMC Pm49FL002", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO( 0x01000, 64 ) + } + }, { + .mfr_id = CFI_MFR_PMC, + .dev_id = PM49FL004, + .name = "PMC Pm49FL004", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO( 0x01000, 128 ) + } + }, { + .mfr_id = CFI_MFR_PMC, + .dev_id = PM49FL008, + .name = "PMC Pm49FL008", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO( 0x01000, 256 ) + } + }, { + .mfr_id = CFI_MFR_SHARP, + .dev_id = LH28F640BF, + .name = "LH28F640BF", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000, 127), + ERASEINFO(0x02000, 8), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST39LF512, + .name = "SST 39LF512", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_64KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,16), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST39LF010, + .name = "SST 39LF010", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_128KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,32), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST29EE020, + .name = "SST 29EE020", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_SST_PAGE, + .nr_regions = 1, + .regions = {ERASEINFO(0x01000,64), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST29LE020, + .name = "SST 29LE020", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_SST_PAGE, + .nr_regions = 1, + .regions = {ERASEINFO(0x01000,64), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST39LF020, + .name = "SST 39LF020", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,64), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST39LF040, + .name = "SST 39LF040", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,128), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST39SF010A, + .name = "SST 39SF010A", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_128KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,32), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST39SF020A, + .name = "SST 39SF020A", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,64), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST39SF040, + .name = "SST 39SF040", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,128), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST49LF040B, + .name = "SST 49LF040B", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,128), + } + }, { + + .mfr_id = CFI_MFR_SST, + .dev_id = SST49LF004B, + .name = "SST 49LF004B", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,128), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST49LF008A, + .name = "SST 49LF008A", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,256), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST49LF030A, + .name = "SST 49LF030A", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,96), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST49LF040A, + .name = "SST 49LF040A", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,128), + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST49LF080A, + .name = "SST 49LF080A", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x01000,256), + } + }, { + .mfr_id = CFI_MFR_SST, /* should be CFI */ + .dev_id = SST39LF160, + .name = "SST 39LF160", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_0xAAAA_0x5555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x1000,256), + ERASEINFO(0x1000,256) + } + }, { + .mfr_id = CFI_MFR_SST, /* should be CFI */ + .dev_id = SST39VF1601, + .name = "SST 39VF1601", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_0xAAAA_0x5555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x1000,256), + ERASEINFO(0x1000,256) + } + }, { + /* CFI is broken: reports AMD_STD, but needs custom uaddr */ + .mfr_id = CFI_MFR_SST, + .dev_id = SST39WF1601, + .name = "SST 39WF1601", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_0xAAAA_0x5555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x1000,256), + ERASEINFO(0x1000,256) + } + }, { + /* CFI is broken: reports AMD_STD, but needs custom uaddr */ + .mfr_id = CFI_MFR_SST, + .dev_id = SST39WF1602, + .name = "SST 39WF1602", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_0xAAAA_0x5555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x1000,256), + ERASEINFO(0x1000,256) + } + }, { + .mfr_id = CFI_MFR_SST, /* should be CFI */ + .dev_id = SST39VF3201, + .name = "SST 39VF3201", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_0xAAAA_0x5555, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x1000,256), + ERASEINFO(0x1000,256), + ERASEINFO(0x1000,256), + ERASEINFO(0x1000,256) + } + }, { + .mfr_id = CFI_MFR_SST, + .dev_id = SST36VF3203, + .name = "SST 36VF3203", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,64), + } + }, { + .mfr_id = CFI_MFR_ST, + .dev_id = M29F800AB, + .name = "ST M29F800AB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,15), + } + }, { + .mfr_id = CFI_MFR_ST, /* FIXME - CFI device? */ + .dev_id = M29W800DT, + .name = "ST M29W800DT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,15), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1) + } + }, { + .mfr_id = CFI_MFR_ST, /* FIXME - CFI device? */ + .dev_id = M29W800DB, + .name = "ST M29W800DB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,15) + } + }, { + .mfr_id = CFI_MFR_ST, + .dev_id = M29W400DT, + .name = "ST M29W400DT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,7), + ERASEINFO(0x02000,1), + ERASEINFO(0x08000,2), + ERASEINFO(0x10000,1) + } + }, { + .mfr_id = CFI_MFR_ST, + .dev_id = M29W400DB, + .name = "ST M29W400DB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,7) + } + }, { + .mfr_id = CFI_MFR_ST, /* FIXME - CFI device? */ + .dev_id = M29W160DT, + .name = "ST M29W160DT", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, /* ???? */ + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,31), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1) + } + }, { + .mfr_id = CFI_MFR_ST, /* FIXME - CFI device? */ + .dev_id = M29W160DB, + .name = "ST M29W160DB", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, /* ???? */ + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,31) + } + }, { + .mfr_id = CFI_MFR_ST, + .dev_id = M29W040B, + .name = "ST M29W040B", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0555_0x02AA, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,8), + } + }, { + .mfr_id = CFI_MFR_ST, + .dev_id = M50FW040, + .name = "ST M50FW040", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_512KiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,8), + } + }, { + .mfr_id = CFI_MFR_ST, + .dev_id = M50FW080, + .name = "ST M50FW080", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,16), + } + }, { + .mfr_id = CFI_MFR_ST, + .dev_id = M50FW016, + .name = "ST M50FW016", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,32), + } + }, { + .mfr_id = CFI_MFR_ST, + .dev_id = M50LPW080, + .name = "ST M50LPW080", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,16), + }, + }, { + .mfr_id = CFI_MFR_ST, + .dev_id = M50FLW080A, + .name = "ST M50FLW080A", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 4, + .regions = { + ERASEINFO(0x1000,16), + ERASEINFO(0x10000,13), + ERASEINFO(0x1000,16), + ERASEINFO(0x1000,16), + } + }, { + .mfr_id = CFI_MFR_ST, + .dev_id = M50FLW080B, + .name = "ST M50FLW080B", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_UNNECESSARY, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_INTEL_EXT, + .nr_regions = 4, + .regions = { + ERASEINFO(0x1000,16), + ERASEINFO(0x1000,16), + ERASEINFO(0x10000,13), + ERASEINFO(0x1000,16), + } + }, { + .mfr_id = 0xff00 | CFI_MFR_ST, + .dev_id = 0xff00 | PSD4256G6V, + .name = "ST PSD4256G6V", + .devtypes = CFI_DEVICETYPE_X16, + .uaddr = MTD_UADDR_0x0AAA_0x0554, + .dev_size = SIZE_1MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 1, + .regions = { + ERASEINFO(0x10000,16), + } + }, { + .mfr_id = CFI_MFR_TOSHIBA, + .dev_id = TC58FVT160, + .name = "Toshiba TC58FVT160", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000,31), + ERASEINFO(0x08000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x04000,1) + } + }, { + .mfr_id = CFI_MFR_TOSHIBA, + .dev_id = TC58FVB160, + .name = "Toshiba TC58FVB160", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_2MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x04000,1), + ERASEINFO(0x02000,2), + ERASEINFO(0x08000,1), + ERASEINFO(0x10000,31) + } + }, { + .mfr_id = CFI_MFR_TOSHIBA, + .dev_id = TC58FVB321, + .name = "Toshiba TC58FVB321", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000,8), + ERASEINFO(0x10000,63) + } + }, { + .mfr_id = CFI_MFR_TOSHIBA, + .dev_id = TC58FVT321, + .name = "Toshiba TC58FVT321", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_4MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000,63), + ERASEINFO(0x02000,8) + } + }, { + .mfr_id = CFI_MFR_TOSHIBA, + .dev_id = TC58FVB641, + .name = "Toshiba TC58FVB641", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x02000,8), + ERASEINFO(0x10000,127) + } + }, { + .mfr_id = CFI_MFR_TOSHIBA, + .dev_id = TC58FVT641, + .name = "Toshiba TC58FVT641", + .devtypes = CFI_DEVICETYPE_X16|CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x0AAA_0x0555, + .dev_size = SIZE_8MiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 2, + .regions = { + ERASEINFO(0x10000,127), + ERASEINFO(0x02000,8) + } + }, { + .mfr_id = CFI_MFR_WINBOND, + .dev_id = W49V002A, + .name = "Winbond W49V002A", + .devtypes = CFI_DEVICETYPE_X8, + .uaddr = MTD_UADDR_0x5555_0x2AAA, + .dev_size = SIZE_256KiB, + .cmd_set = P_ID_AMD_STD, + .nr_regions = 4, + .regions = { + ERASEINFO(0x10000, 3), + ERASEINFO(0x08000, 1), + ERASEINFO(0x02000, 2), + ERASEINFO(0x04000, 1), + } + } +}; + +static inline u32 jedec_read_mfr(struct map_info *map, uint32_t base, + struct cfi_private *cfi) +{ + map_word result; + unsigned long mask; + int bank = 0; + + /* According to JEDEC "Standard Manufacturer's Identification Code" + * (http://www.jedec.org/download/search/jep106W.pdf) + * several first banks can contain 0x7f instead of actual ID + */ + do { + uint32_t ofs = cfi_build_cmd_addr(0 + (bank << 8), map, cfi); + mask = (1ULL << (cfi->device_type * 8)) - 1; + if (ofs >= map->size) + return 0; + result = map_read(map, base + ofs); + bank++; + } while ((result.x[0] & mask) == CFI_MFR_CONTINUATION); + + return result.x[0] & mask; +} + +static inline u32 jedec_read_id(struct map_info *map, uint32_t base, + struct cfi_private *cfi) +{ + map_word result; + unsigned long mask; + u32 ofs = cfi_build_cmd_addr(1, map, cfi); + mask = (1ULL << (cfi->device_type * 8)) - 1; + result = map_read(map, base + ofs); + return result.x[0] & mask; +} + +static void jedec_reset(u32 base, struct map_info *map, struct cfi_private *cfi) +{ + /* Reset */ + + /* after checking the datasheets for SST, MACRONIX and ATMEL + * (oh and incidentaly the jedec spec - 3.5.3.3) the reset + * sequence is *supposed* to be 0xaa at 0x5555, 0x55 at + * 0x2aaa, 0xF0 at 0x5555 this will not affect the AMD chips + * as they will ignore the writes and don't care what address + * the F0 is written to */ + if (cfi->addr_unlock1) { + pr_debug("reset unlock called %x %x\n", + cfi->addr_unlock1,cfi->addr_unlock2); + cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, cfi->device_type, NULL); + } + + cfi_send_gen_cmd(0xF0, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL); + /* Some misdesigned Intel chips do not respond for 0xF0 for a reset, + * so ensure we're in read mode. Send both the Intel and the AMD command + * for this. Intel uses 0xff for this, AMD uses 0xff for NOP, so + * this should be safe. + */ + cfi_send_gen_cmd(0xFF, 0, base, map, cfi, cfi->device_type, NULL); + /* FIXME - should have reset delay before continuing */ +} + + +static int cfi_jedec_setup(struct map_info *map, struct cfi_private *cfi, int index) +{ + int i,num_erase_regions; + uint8_t uaddr; + + if (!(jedec_table[index].devtypes & cfi->device_type)) { + pr_debug("Rejecting potential %s with incompatible %d-bit device type\n", + jedec_table[index].name, 4 * (1<device_type)); + return 0; + } + + printk(KERN_INFO "Found: %s\n",jedec_table[index].name); + + num_erase_regions = jedec_table[index].nr_regions; + + cfi->cfiq = kmalloc_flex(*cfi->cfiq, EraseRegionInfo, num_erase_regions); + if (!cfi->cfiq) { + //xx printk(KERN_WARNING "%s: kmalloc failed for CFI ident structure\n", map->name); + return 0; + } + + memset(cfi->cfiq, 0, sizeof(struct cfi_ident)); + + cfi->cfiq->P_ID = jedec_table[index].cmd_set; + cfi->cfiq->NumEraseRegions = jedec_table[index].nr_regions; + cfi->cfiq->DevSize = jedec_table[index].dev_size; + cfi->cfi_mode = CFI_MODE_JEDEC; + cfi->sector_erase_cmd = CMD(0x30); + + for (i=0; icfiq->EraseRegionInfo[i] = jedec_table[index].regions[i]; + } + cfi->cmdset_priv = NULL; + + /* This may be redundant for some cases, but it doesn't hurt */ + cfi->mfr = jedec_table[index].mfr_id; + cfi->id = jedec_table[index].dev_id; + + uaddr = jedec_table[index].uaddr; + + /* The table has unlock addresses in _bytes_, and we try not to let + our brains explode when we see the datasheets talking about address + lines numbered from A-1 to A18. The CFI table has unlock addresses + in device-words according to the mode the device is connected in */ + cfi->addr_unlock1 = unlock_addrs[uaddr].addr1 / cfi->device_type; + cfi->addr_unlock2 = unlock_addrs[uaddr].addr2 / cfi->device_type; + + return 1; /* ok */ +} + + +/* + * There is a BIG problem properly ID'ing the JEDEC device and guaranteeing + * the mapped address, unlock addresses, and proper chip ID. This function + * attempts to minimize errors. It is doubtfull that this probe will ever + * be perfect - consequently there should be some module parameters that + * could be manually specified to force the chip info. + */ +static inline int jedec_match( uint32_t base, + struct map_info *map, + struct cfi_private *cfi, + const struct amd_flash_info *finfo ) +{ + int rc = 0; /* failure until all tests pass */ + u32 mfr, id; + uint8_t uaddr; + + /* + * The IDs must match. For X16 and X32 devices operating in + * a lower width ( X8 or X16 ), the device ID's are usually just + * the lower byte(s) of the larger device ID for wider mode. If + * a part is found that doesn't fit this assumption (device id for + * smaller width mode is completely unrealated to full-width mode) + * then the jedec_table[] will have to be augmented with the IDs + * for different widths. + */ + switch (cfi->device_type) { + case CFI_DEVICETYPE_X8: + mfr = (uint8_t)finfo->mfr_id; + id = (uint8_t)finfo->dev_id; + + /* bjd: it seems that if we do this, we can end up + * detecting 16bit flashes as an 8bit device, even though + * there aren't. + */ + if (finfo->dev_id > 0xff) { + pr_debug("%s(): ID is not 8bit\n", + __func__); + goto match_done; + } + break; + case CFI_DEVICETYPE_X16: + mfr = (uint16_t)finfo->mfr_id; + id = (uint16_t)finfo->dev_id; + break; + case CFI_DEVICETYPE_X32: + mfr = (uint16_t)finfo->mfr_id; + id = (uint32_t)finfo->dev_id; + break; + default: + printk(KERN_WARNING + "MTD %s(): Unsupported device type %d\n", + __func__, cfi->device_type); + goto match_done; + } + if ( cfi->mfr != mfr || cfi->id != id ) { + goto match_done; + } + + /* the part size must fit in the memory window */ + pr_debug("MTD %s(): Check fit 0x%.8x + 0x%.8x = 0x%.8x\n", + __func__, base, 1 << finfo->dev_size, base + (1 << finfo->dev_size) ); + if ( base + cfi_interleave(cfi) * ( 1 << finfo->dev_size ) > map->size ) { + pr_debug("MTD %s(): 0x%.4x 0x%.4x %dKiB doesn't fit\n", + __func__, finfo->mfr_id, finfo->dev_id, + 1 << finfo->dev_size ); + goto match_done; + } + + if (! (finfo->devtypes & cfi->device_type)) + goto match_done; + + uaddr = finfo->uaddr; + + pr_debug("MTD %s(): check unlock addrs 0x%.4x 0x%.4x\n", + __func__, cfi->addr_unlock1, cfi->addr_unlock2 ); + if ( MTD_UADDR_UNNECESSARY != uaddr && MTD_UADDR_DONT_CARE != uaddr + && ( unlock_addrs[uaddr].addr1 / cfi->device_type != cfi->addr_unlock1 || + unlock_addrs[uaddr].addr2 / cfi->device_type != cfi->addr_unlock2 ) ) { + pr_debug("MTD %s(): 0x%.4x 0x%.4x did not match\n", + __func__, + unlock_addrs[uaddr].addr1, + unlock_addrs[uaddr].addr2); + goto match_done; + } + + /* + * Make sure the ID's disappear when the device is taken out of + * ID mode. The only time this should fail when it should succeed + * is when the ID's are written as data to the same + * addresses. For this rare and unfortunate case the chip + * cannot be probed correctly. + * FIXME - write a driver that takes all of the chip info as + * module parameters, doesn't probe but forces a load. + */ + pr_debug("MTD %s(): check ID's disappear when not in ID mode\n", + __func__ ); + jedec_reset( base, map, cfi ); + mfr = jedec_read_mfr( map, base, cfi ); + id = jedec_read_id( map, base, cfi ); + if ( mfr == cfi->mfr && id == cfi->id ) { + pr_debug("MTD %s(): ID 0x%.2x:0x%.2x did not change after reset:\n" + "You might need to manually specify JEDEC parameters.\n", + __func__, cfi->mfr, cfi->id ); + goto match_done; + } + + /* all tests passed - mark as success */ + rc = 1; + + /* + * Put the device back in ID mode - only need to do this if we + * were truly frobbing a real device. + */ + pr_debug("MTD %s(): return to ID mode\n", __func__ ); + if (cfi->addr_unlock1) { + cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, cfi->device_type, NULL); + } + cfi_send_gen_cmd(0x90, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL); + /* FIXME - should have a delay before continuing */ + + match_done: + return rc; +} + + +static int jedec_probe_chip(struct map_info *map, __u32 base, + unsigned long *chip_map, struct cfi_private *cfi) +{ + int i; + enum uaddr uaddr_idx = MTD_UADDR_NOT_SUPPORTED; + u32 probe_offset1, probe_offset2; + + retry: + if (!cfi->numchips) { + uaddr_idx++; + + if (MTD_UADDR_UNNECESSARY == uaddr_idx) + return 0; + + cfi->addr_unlock1 = unlock_addrs[uaddr_idx].addr1 / cfi->device_type; + cfi->addr_unlock2 = unlock_addrs[uaddr_idx].addr2 / cfi->device_type; + } + + /* Make certain we aren't probing past the end of map */ + if (base >= map->size) { + printk(KERN_NOTICE + "Probe at base(0x%08x) past the end of the map(0x%08lx)\n", + base, map->size -1); + return 0; + + } + /* Ensure the unlock addresses we try stay inside the map */ + probe_offset1 = cfi_build_cmd_addr(cfi->addr_unlock1, map, cfi); + probe_offset2 = cfi_build_cmd_addr(cfi->addr_unlock2, map, cfi); + if ( ((base + probe_offset1 + map_bankwidth(map)) >= map->size) || + ((base + probe_offset2 + map_bankwidth(map)) >= map->size)) + goto retry; + + /* Reset */ + jedec_reset(base, map, cfi); + + /* Autoselect Mode */ + if(cfi->addr_unlock1) { + cfi_send_gen_cmd(0xaa, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL); + cfi_send_gen_cmd(0x55, cfi->addr_unlock2, base, map, cfi, cfi->device_type, NULL); + } + cfi_send_gen_cmd(0x90, cfi->addr_unlock1, base, map, cfi, cfi->device_type, NULL); + /* FIXME - should have a delay before continuing */ + + if (!cfi->numchips) { + /* This is the first time we're called. Set up the CFI + stuff accordingly and return */ + + cfi->mfr = jedec_read_mfr(map, base, cfi); + cfi->id = jedec_read_id(map, base, cfi); + pr_debug("Search for id:(%02x %02x) interleave(%d) type(%d)\n", + cfi->mfr, cfi->id, cfi_interleave(cfi), cfi->device_type); + for (i = 0; i < ARRAY_SIZE(jedec_table); i++) { + if ( jedec_match( base, map, cfi, &jedec_table[i] ) ) { + pr_debug("MTD %s(): matched device 0x%x,0x%x unlock_addrs: 0x%.4x 0x%.4x\n", + __func__, cfi->mfr, cfi->id, + cfi->addr_unlock1, cfi->addr_unlock2 ); + if (!cfi_jedec_setup(map, cfi, i)) + return 0; + goto ok_out; + } + } + goto retry; + } else { + uint16_t mfr; + uint16_t id; + + /* Make sure it is a chip of the same manufacturer and id */ + mfr = jedec_read_mfr(map, base, cfi); + id = jedec_read_id(map, base, cfi); + + if ((mfr != cfi->mfr) || (id != cfi->id)) { + printk(KERN_DEBUG "%s: Found different chip or no chip at all (mfr 0x%x, id 0x%x) at 0x%x\n", + map->name, mfr, id, base); + jedec_reset(base, map, cfi); + return 0; + } + } + + /* Check each previous chip locations to see if it's an alias */ + for (i=0; i < (base >> cfi->chipshift); i++) { + unsigned long start; + if(!test_bit(i, chip_map)) { + continue; /* Skip location; no valid chip at this address */ + } + start = i << cfi->chipshift; + if (jedec_read_mfr(map, start, cfi) == cfi->mfr && + jedec_read_id(map, start, cfi) == cfi->id) { + /* Eep. This chip also looks like it's in autoselect mode. + Is it an alias for the new one? */ + jedec_reset(start, map, cfi); + + /* If the device IDs go away, it's an alias */ + if (jedec_read_mfr(map, base, cfi) != cfi->mfr || + jedec_read_id(map, base, cfi) != cfi->id) { + printk(KERN_DEBUG "%s: Found an alias at 0x%x for the chip at 0x%lx\n", + map->name, base, start); + return 0; + } + + /* Yes, it's actually got the device IDs as data. Most + * unfortunate. Stick the new chip in read mode + * too and if it's the same, assume it's an alias. */ + /* FIXME: Use other modes to do a proper check */ + jedec_reset(base, map, cfi); + if (jedec_read_mfr(map, base, cfi) == cfi->mfr && + jedec_read_id(map, base, cfi) == cfi->id) { + printk(KERN_DEBUG "%s: Found an alias at 0x%x for the chip at 0x%lx\n", + map->name, base, start); + return 0; + } + } + } + + /* OK, if we got to here, then none of the previous chips appear to + be aliases for the current one. */ + set_bit((base >> cfi->chipshift), chip_map); /* Update chip map */ + cfi->numchips++; + +ok_out: + /* Put it back into Read Mode */ + jedec_reset(base, map, cfi); + + printk(KERN_INFO "%s: Found %d x%d devices at 0x%x in %d-bit bank\n", + map->name, cfi_interleave(cfi), cfi->device_type*8, base, + map->bankwidth*8); + + return 1; +} + +static struct chip_probe jedec_chip_probe = { + .name = "JEDEC", + .probe_chip = jedec_probe_chip +}; + +static struct mtd_info *jedec_probe(struct map_info *map) +{ + /* + * Just use the generic probe stuff to call our CFI-specific + * chip_probe routine in all the possible permutations, etc. + */ + return mtd_do_chip_probe(map, &jedec_chip_probe); +} + +static struct mtd_chip_driver jedec_chipdrv = { + .probe = jedec_probe, + .name = "jedec_probe", + .module = THIS_MODULE +}; + +static int __init jedec_probe_init(void) +{ + register_mtd_chip_driver(&jedec_chipdrv); + return 0; +} + +static void __exit jedec_probe_exit(void) +{ + unregister_mtd_chip_driver(&jedec_chipdrv); +} + +module_init(jedec_probe_init); +module_exit(jedec_probe_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Erwin Authried et al."); +MODULE_DESCRIPTION("Probe code for JEDEC-compliant flash chips"); diff --git a/drivers/mtd/chips/map_absent.c b/drivers/mtd/chips/map_absent.c new file mode 100644 index 000000000..1804a44b3 --- /dev/null +++ b/drivers/mtd/chips/map_absent.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Common code to handle absent "placeholder" devices + * Copyright 2001 Resilience Corporation + * + * This map driver is used to allocate "placeholder" MTD + * devices on systems that have socketed/removable media. + * Use of this driver as a fallback preserves the expected + * registration of MTD device nodes regardless of probe outcome. + * A usage example is as follows: + * + * my_dev[i] = do_map_probe("cfi", &my_map[i]); + * if(NULL == my_dev[i]) { + * my_dev[i] = do_map_probe("map_absent", &my_map[i]); + * } + * + * Any device 'probed' with this driver will return -ENODEV + * upon open. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static int map_absent_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); +static int map_absent_write (struct mtd_info *, loff_t, size_t, size_t *, const u_char *); +static int map_absent_erase (struct mtd_info *, struct erase_info *); +static void map_absent_sync (struct mtd_info *); +static struct mtd_info *map_absent_probe(struct map_info *map); +static void map_absent_destroy (struct mtd_info *); + + +static struct mtd_chip_driver map_absent_chipdrv = { + .probe = map_absent_probe, + .destroy = map_absent_destroy, + .name = "map_absent", + .module = THIS_MODULE +}; + +static struct mtd_info *map_absent_probe(struct map_info *map) +{ + struct mtd_info *mtd; + + mtd = kzalloc_obj(*mtd); + if (!mtd) { + return NULL; + } + + map->fldrv = &map_absent_chipdrv; + mtd->priv = map; + mtd->name = map->name; + mtd->type = MTD_ABSENT; + mtd->size = map->size; + mtd->_erase = map_absent_erase; + mtd->_read = map_absent_read; + mtd->_write = map_absent_write; + mtd->_sync = map_absent_sync; + mtd->flags = 0; + mtd->erasesize = PAGE_SIZE; + mtd->writesize = 1; + + __module_get(THIS_MODULE); + return mtd; +} + + +static int map_absent_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) +{ + return -ENODEV; +} + +static int map_absent_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) +{ + return -ENODEV; +} + +static int map_absent_erase(struct mtd_info *mtd, struct erase_info *instr) +{ + return -ENODEV; +} + +static void map_absent_sync(struct mtd_info *mtd) +{ + /* nop */ +} + +static void map_absent_destroy(struct mtd_info *mtd) +{ + /* nop */ +} + +static int __init map_absent_init(void) +{ + register_mtd_chip_driver(&map_absent_chipdrv); + return 0; +} + +static void __exit map_absent_exit(void) +{ + unregister_mtd_chip_driver(&map_absent_chipdrv); +} + +module_init(map_absent_init); +module_exit(map_absent_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Resilience Corporation - Eric Brower "); +MODULE_DESCRIPTION("Placeholder MTD chip driver for 'absent' chips"); diff --git a/drivers/mtd/chips/map_ram.c b/drivers/mtd/chips/map_ram.c new file mode 100644 index 000000000..2dde70e74 --- /dev/null +++ b/drivers/mtd/chips/map_ram.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common code to handle map devices which are simple RAM + * (C) 2000 Red Hat. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +static int mapram_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); +static int mapram_write (struct mtd_info *, loff_t, size_t, size_t *, const u_char *); +static int mapram_erase (struct mtd_info *, struct erase_info *); +static void mapram_nop (struct mtd_info *); +static struct mtd_info *map_ram_probe(struct map_info *map); +static int mapram_point (struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, void **virt, resource_size_t *phys); +static int mapram_unpoint(struct mtd_info *mtd, loff_t from, size_t len); + + +static struct mtd_chip_driver mapram_chipdrv = { + .probe = map_ram_probe, + .name = "map_ram", + .module = THIS_MODULE +}; + +static struct mtd_info *map_ram_probe(struct map_info *map) +{ + struct mtd_info *mtd; + + /* Check the first byte is RAM */ +#if 0 + map_write8(map, 0x55, 0); + if (map_read8(map, 0) != 0x55) + return NULL; + + map_write8(map, 0xAA, 0); + if (map_read8(map, 0) != 0xAA) + return NULL; + + /* Check the last byte is RAM */ + map_write8(map, 0x55, map->size-1); + if (map_read8(map, map->size-1) != 0x55) + return NULL; + + map_write8(map, 0xAA, map->size-1); + if (map_read8(map, map->size-1) != 0xAA) + return NULL; +#endif + /* OK. It seems to be RAM. */ + + mtd = kzalloc_obj(*mtd); + if (!mtd) + return NULL; + + map->fldrv = &mapram_chipdrv; + mtd->priv = map; + mtd->name = map->name; + mtd->type = MTD_RAM; + mtd->size = map->size; + mtd->_erase = mapram_erase; + mtd->_read = mapram_read; + mtd->_write = mapram_write; + mtd->_panic_write = mapram_write; + mtd->_sync = mapram_nop; + mtd->flags = MTD_CAP_RAM; + mtd->writesize = 1; + + /* Disable direct access when NO_XIP is set */ + if (map->phys != NO_XIP) { + mtd->_point = mapram_point; + mtd->_unpoint = mapram_unpoint; + } + + mtd->erasesize = PAGE_SIZE; + while(mtd->size & (mtd->erasesize - 1)) + mtd->erasesize >>= 1; + + __module_get(THIS_MODULE); + return mtd; +} + +static int mapram_point(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, void **virt, resource_size_t *phys) +{ + struct map_info *map = mtd->priv; + + if (!map->virt) + return -EINVAL; + *virt = map->virt + from; + if (phys) + *phys = map->phys + from; + *retlen = len; + return 0; +} + +static int mapram_unpoint(struct mtd_info *mtd, loff_t from, size_t len) +{ + return 0; +} + +static int mapram_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) +{ + struct map_info *map = mtd->priv; + + map_copy_from(map, buf, from, len); + *retlen = len; + return 0; +} + +static int mapram_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) +{ + struct map_info *map = mtd->priv; + + map_copy_to(map, to, buf, len); + *retlen = len; + return 0; +} + +static int mapram_erase (struct mtd_info *mtd, struct erase_info *instr) +{ + /* Yeah, it's inefficient. Who cares? It's faster than a _real_ + flash erase. */ + struct map_info *map = mtd->priv; + map_word allff; + unsigned long i; + + allff = map_word_ff(map); + for (i=0; ilen; i += map_bankwidth(map)) + map_write(map, allff, instr->addr + i); + return 0; +} + +static void mapram_nop(struct mtd_info *mtd) +{ + /* Nothing to see here */ +} + +static int __init map_ram_init(void) +{ + register_mtd_chip_driver(&mapram_chipdrv); + return 0; +} + +static void __exit map_ram_exit(void) +{ + unregister_mtd_chip_driver(&mapram_chipdrv); +} + +module_init(map_ram_init); +module_exit(map_ram_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Woodhouse "); +MODULE_DESCRIPTION("MTD chip driver for RAM chips"); diff --git a/drivers/mtd/chips/map_rom.c b/drivers/mtd/chips/map_rom.c new file mode 100644 index 000000000..dbc34e886 --- /dev/null +++ b/drivers/mtd/chips/map_rom.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common code to handle map devices which are simple ROM + * (C) 2000 Red Hat. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int maprom_read (struct mtd_info *, loff_t, size_t, size_t *, u_char *); +static int maprom_write (struct mtd_info *, loff_t, size_t, size_t *, const u_char *); +static void maprom_nop (struct mtd_info *); +static struct mtd_info *map_rom_probe(struct map_info *map); +static int maprom_erase (struct mtd_info *mtd, struct erase_info *info); +static int maprom_point (struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, void **virt, resource_size_t *phys); +static int maprom_unpoint(struct mtd_info *mtd, loff_t from, size_t len); + + +static struct mtd_chip_driver maprom_chipdrv = { + .probe = map_rom_probe, + .name = "map_rom", + .module = THIS_MODULE +}; + +static unsigned int default_erasesize(struct map_info *map) +{ + const __be32 *erase_size = NULL; + + erase_size = of_get_property(map->device_node, "erase-size", NULL); + + return !erase_size ? map->size : be32_to_cpu(*erase_size); +} + +static struct mtd_info *map_rom_probe(struct map_info *map) +{ + struct mtd_info *mtd; + + mtd = kzalloc_obj(*mtd); + if (!mtd) + return NULL; + + map->fldrv = &maprom_chipdrv; + mtd->priv = map; + mtd->name = map->name; + mtd->type = MTD_ROM; + mtd->size = map->size; + mtd->_point = maprom_point; + mtd->_unpoint = maprom_unpoint; + mtd->_read = maprom_read; + mtd->_write = maprom_write; + mtd->_sync = maprom_nop; + mtd->_erase = maprom_erase; + mtd->flags = MTD_CAP_ROM; + mtd->erasesize = default_erasesize(map); + mtd->writesize = 1; + mtd->writebufsize = 1; + + __module_get(THIS_MODULE); + return mtd; +} + + +static int maprom_point(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, void **virt, resource_size_t *phys) +{ + struct map_info *map = mtd->priv; + + if (!map->virt) + return -EINVAL; + *virt = map->virt + from; + if (phys) + *phys = map->phys + from; + *retlen = len; + return 0; +} + +static int maprom_unpoint(struct mtd_info *mtd, loff_t from, size_t len) +{ + return 0; +} + +static int maprom_read (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) +{ + struct map_info *map = mtd->priv; + + map_copy_from(map, buf, from, len); + *retlen = len; + return 0; +} + +static void maprom_nop(struct mtd_info *mtd) +{ + /* Nothing to see here */ +} + +static int maprom_write (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) +{ + return -EROFS; +} + +static int maprom_erase (struct mtd_info *mtd, struct erase_info *info) +{ + /* We do our best 8) */ + return -EROFS; +} + +static int __init map_rom_init(void) +{ + register_mtd_chip_driver(&maprom_chipdrv); + return 0; +} + +static void __exit map_rom_exit(void) +{ + unregister_mtd_chip_driver(&maprom_chipdrv); +} + +module_init(map_rom_init); +module_exit(map_rom_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Woodhouse "); +MODULE_DESCRIPTION("MTD chip driver for ROM chips"); diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig new file mode 100644 index 000000000..e518dfeee --- /dev/null +++ b/drivers/mtd/devices/Kconfig @@ -0,0 +1,227 @@ +# SPDX-License-Identifier: GPL-2.0-only +menu "Self-contained MTD device drivers" + depends on MTD!=n + depends on HAS_IOMEM + +config MTD_PMC551 + tristate "Ramix PMC551 PCI Mezzanine RAM card support" + depends on PCI + help + This provides a MTD device driver for the Ramix PMC551 RAM PCI card + from Ramix Inc. . + These devices come in memory configurations from 32M - 1G. If you + have one, you probably want to enable this. + + If this driver is compiled as a module you get the ability to select + the size of the aperture window pointing into the devices memory. + What this means is that if you have a 1G card, normally the kernel + will use a 1G memory map as its view of the device. As a module, + you can select a 1M window into the memory and the driver will + "slide" the window around the PMC551's memory. This was + particularly useful on the 2.2 kernels on PPC architectures as there + was limited kernel space to deal with. + +config MTD_PMC551_BUGFIX + bool "PMC551 256M DRAM Bugfix" + depends on MTD_PMC551 + help + Some of Ramix's PMC551 boards with 256M configurations have invalid + column and row mux values. This option will fix them, but will + break other memory configurations. If unsure say N. + +config MTD_PMC551_DEBUG + bool "PMC551 Debugging" + depends on MTD_PMC551 + help + This option makes the PMC551 more verbose during its operation and + is only really useful if you are developing on this driver or + suspect a possible hardware or driver bug. If unsure say N. + +config MTD_MS02NV + tristate "DEC MS02-NV NVRAM module support" + depends on MACH_DECSTATION + help + This is an MTD driver for the DEC's MS02-NV (54-20948-01) battery + backed-up NVRAM module. The module was originally meant as an NFS + accelerator. Say Y here if you have a DECstation 5000/2x0 or a + DECsystem 5900 equipped with such a module. + + If you want to compile this driver as a module ( = code which can be + inserted in and removed from the running kernel whenever you want), + say M here and read . + The module will be called ms02-nv. + +config MTD_DATAFLASH + tristate "Support for AT45xxx DataFlash" + depends on SPI_MASTER + help + This enables access to AT45xxx DataFlash chips, using SPI. + Sometimes DataFlash chips are packaged inside MMC-format + cards; at this writing, the MMC stack won't handle those. + +config MTD_DATAFLASH_WRITE_VERIFY + bool "Verify DataFlash page writes" + depends on MTD_DATAFLASH + help + This adds an extra check when data is written to the flash. + It may help if you are verifying chip setup (timings etc) on + your board. 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_DATAFLASH_OTP + bool "DataFlash OTP support (Security Register)" + depends on MTD_DATAFLASH + help + Newer DataFlash chips (revisions C and D) support 128 bytes of + one-time-programmable (OTP) data. The first half may be written + (once) with up to 64 bytes of data, such as a serial number or + other key product data. The second half is programmed with a + unique-to-each-chip bit pattern at the factory. + +config MTD_MCHP23K256 + tristate "Microchip 23K256 SRAM" + depends on SPI_MASTER + help + This enables access to Microchip 23K256 SRAM chips, using SPI. + + Set up your spi devices with the right board-specific + platform data, or a device tree description if you want to + specify device partitioning + +config MTD_MCHP48L640 + tristate "Microchip 48L640 EERAM" + depends on SPI_MASTER + help + This enables access to Microchip 48L640 EERAM chips, using SPI. + +config MTD_SPEAR_SMI + tristate "SPEAR MTD NOR Support through SMI controller" + depends on PLAT_SPEAR || COMPILE_TEST + default PLAT_SPEAR + help + This enable SNOR support on SPEAR platforms using SMI controller + +config MTD_SST25L + tristate "Support SST25L (non JEDEC) SPI Flash chips" + depends on SPI_MASTER + help + This enables access to the non JEDEC SST25L SPI flash chips, used + for program and data storage. + + Set up your spi devices with the right board-specific platform data, + if you want to specify device partitioning. + +config MTD_BCM47XXSFLASH + tristate "Support for serial flash on BCMA bus" + depends on BCMA_SFLASH && (MIPS || ARM) + help + BCMA bus can have various flash memories attached, they are + registered by bcma as platform devices. This enables driver for + serial flash memories. + +config MTD_SLRAM + tristate "Uncached system RAM" + help + If your CPU cannot cache all of the physical memory in your machine, + you can still use it for storage or swap by using this driver to + present it to the system as a Memory Technology Device. + +config MTD_PHRAM + tristate "Physical system RAM" + help + This is a re-implementation of the slram driver above. + + Use this driver to access physical memory that the kernel proper + doesn't have access to, memory beyond the mem=xxx limit, nvram, + memory on the video card, etc... + +config MTD_MTDRAM + tristate "Test driver using RAM" + help + This enables a test MTD device driver which uses vmalloc() to + provide storage. You probably want to say 'N' unless you're + testing stuff. + +config MTDRAM_TOTAL_SIZE + int "MTDRAM device size in KiB" + depends on MTD_MTDRAM + default "4096" + help + This allows you to configure the total size of the MTD device + emulated by the MTDRAM driver. If the MTDRAM driver is built + as a module, it is also possible to specify this as a parameter when + loading the module. + +config MTDRAM_ERASE_SIZE + int "MTDRAM erase block size in KiB" + depends on MTD_MTDRAM + default "128" + help + This allows you to configure the size of the erase blocks in the + device emulated by the MTDRAM driver. If the MTDRAM driver is built + as a module, it is also possible to specify this as a parameter when + loading the module. + +config MTD_BLOCK2MTD + tristate "MTD using block device" + depends on BLOCK + help + This driver allows a block device to appear as an MTD. It would + generally be used in the following cases: + + Using Compact Flash as an MTD, these usually present themselves to + the system as an ATA drive. + Testing MTD users (eg JFFS2) on large media and media that might + be removed during a write (using the floppy drive). + +config MTD_POWERNV_FLASH + tristate "powernv flash MTD driver" + depends on PPC_POWERNV + help + This provides an MTD device to access flash on powernv OPAL + platforms from Linux. This device abstracts away the + firmware interface for flash access. + +config MTD_INTEL_DG + tristate "Intel Discrete Graphics non-volatile memory driver" + depends on AUXILIARY_BUS && MTD + depends on DRM_I915!=n || DRM_XE!=n || COMPILE_TEST + help + This provides an MTD device to access Intel Discrete Graphics + non-volatile memory. + + To compile this driver as a module, choose M here: the module + will be called mtd-intel-dg. + +comment "Disk-On-Chip Device Drivers" + +config MTD_DOCG3 + tristate "M-Systems Disk-On-Chip G3" + select BCH + select BCH_CONST_PARAMS if !MTD_NAND_ECC_SW_BCH + select BITREVERSE + help + This provides an MTD device driver for the M-Systems DiskOnChip + G3 devices. + + The driver provides access to G3 DiskOnChip, distributed by + M-Systems and now Sandisk. The support is very experimental, + and doesn't give access to any write operations. + +config MTD_ST_SPI_FSM + tristate "ST Microelectronics SPI FSM Serial Flash Controller" + depends on ARCH_STI + help + This provides an MTD device driver for the ST Microelectronics + SPI Fast Sequence Mode (FSM) Serial Flash Controller and support + for a subset of connected Serial Flash devices. + +if MTD_DOCG3 +config BCH_CONST_M + default 14 +config BCH_CONST_T + default 4 +endif + +endmenu diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile new file mode 100644 index 000000000..9fe4ce9cf --- /dev/null +++ b/drivers/mtd/devices/Makefile @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# linux/drivers/mtd/devices/Makefile +# + +obj-$(CONFIG_MTD_DOCG3) += docg3.o +obj-$(CONFIG_MTD_SLRAM) += slram.o +obj-$(CONFIG_MTD_PHRAM) += phram.o +obj-$(CONFIG_MTD_PMC551) += pmc551.o +obj-$(CONFIG_MTD_MS02NV) += ms02-nv.o +obj-$(CONFIG_MTD_MTDRAM) += mtdram.o +obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o +obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o +obj-$(CONFIG_MTD_MCHP23K256) += mchp23k256.o +obj-$(CONFIG_MTD_MCHP48L640) += mchp48l640.o +obj-$(CONFIG_MTD_SPEAR_SMI) += spear_smi.o +obj-$(CONFIG_MTD_SST25L) += sst25l.o +obj-$(CONFIG_MTD_BCM47XXSFLASH) += bcm47xxsflash.o +obj-$(CONFIG_MTD_ST_SPI_FSM) += st_spi_fsm.o +obj-$(CONFIG_MTD_POWERNV_FLASH) += powernv_flash.o +obj-$(CONFIG_MTD_INTEL_DG) += mtd_intel_dg.o + + +CFLAGS_docg3.o += -I$(src) diff --git a/drivers/mtd/devices/bcm47xxsflash.c b/drivers/mtd/devices/bcm47xxsflash.c new file mode 100644 index 000000000..2edc46741 --- /dev/null +++ b/drivers/mtd/devices/bcm47xxsflash.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bcm47xxsflash.h" + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Serial flash driver for BCMA bus"); + +static const char * const probes[] = { "bcm47xxpart", NULL }; + +/************************************************** + * Various helpers + **************************************************/ + +static void bcm47xxsflash_cmd(struct bcm47xxsflash *b47s, u32 opcode) +{ + int i; + + b47s->cc_write(b47s, BCMA_CC_FLASHCTL, BCMA_CC_FLASHCTL_START | opcode); + for (i = 0; i < 1000; i++) { + if (!(b47s->cc_read(b47s, BCMA_CC_FLASHCTL) & + BCMA_CC_FLASHCTL_BUSY)) + return; + cpu_relax(); + } + pr_err("Control command failed (timeout)!\n"); +} + +static int bcm47xxsflash_poll(struct bcm47xxsflash *b47s, int timeout) +{ + unsigned long deadline = jiffies + timeout; + + do { + switch (b47s->type) { + case BCM47XXSFLASH_TYPE_ST: + bcm47xxsflash_cmd(b47s, OPCODE_ST_RDSR); + if (!(b47s->cc_read(b47s, BCMA_CC_FLASHDATA) & + SR_ST_WIP)) + return 0; + break; + case BCM47XXSFLASH_TYPE_ATMEL: + bcm47xxsflash_cmd(b47s, OPCODE_AT_STATUS); + if (b47s->cc_read(b47s, BCMA_CC_FLASHDATA) & + SR_AT_READY) + return 0; + break; + } + + cpu_relax(); + udelay(1); + } while (!time_after_eq(jiffies, deadline)); + + pr_err("Timeout waiting for flash to be ready!\n"); + + return -EBUSY; +} + +/************************************************** + * MTD ops + **************************************************/ + +static int bcm47xxsflash_erase(struct mtd_info *mtd, struct erase_info *erase) +{ + struct bcm47xxsflash *b47s = mtd->priv; + + switch (b47s->type) { + case BCM47XXSFLASH_TYPE_ST: + bcm47xxsflash_cmd(b47s, OPCODE_ST_WREN); + b47s->cc_write(b47s, BCMA_CC_FLASHADDR, erase->addr); + /* Newer flashes have "sub-sectors" which can be erased + * independently with a new command: ST_SSE. The ST_SE command + * erases 64KB just as before. + */ + if (b47s->blocksize < (64 * 1024)) + bcm47xxsflash_cmd(b47s, OPCODE_ST_SSE); + else + bcm47xxsflash_cmd(b47s, OPCODE_ST_SE); + break; + case BCM47XXSFLASH_TYPE_ATMEL: + b47s->cc_write(b47s, BCMA_CC_FLASHADDR, erase->addr << 1); + bcm47xxsflash_cmd(b47s, OPCODE_AT_PAGE_ERASE); + break; + } + + return bcm47xxsflash_poll(b47s, HZ); +} + +static int bcm47xxsflash_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct bcm47xxsflash *b47s = mtd->priv; + size_t orig_len = len; + + /* Check address range */ + if ((from + len) > mtd->size) + return -EINVAL; + + /* Read as much as possible using fast MMIO window */ + if (from < BCM47XXSFLASH_WINDOW_SZ) { + size_t memcpy_len; + + memcpy_len = min(len, (size_t)(BCM47XXSFLASH_WINDOW_SZ - from)); + memcpy_fromio(buf, b47s->window + from, memcpy_len); + from += memcpy_len; + len -= memcpy_len; + buf += memcpy_len; + } + + /* Use indirect access for content out of the window */ + for (; len; len--) { + b47s->cc_write(b47s, BCMA_CC_FLASHADDR, from++); + bcm47xxsflash_cmd(b47s, OPCODE_ST_READ4B); + *buf++ = b47s->cc_read(b47s, BCMA_CC_FLASHDATA); + } + + *retlen = orig_len; + + return orig_len; +} + +static int bcm47xxsflash_write_st(struct mtd_info *mtd, u32 offset, size_t len, + const u_char *buf) +{ + struct bcm47xxsflash *b47s = mtd->priv; + int written = 0; + + /* Enable writes */ + bcm47xxsflash_cmd(b47s, OPCODE_ST_WREN); + + /* Write first byte */ + b47s->cc_write(b47s, BCMA_CC_FLASHADDR, offset); + b47s->cc_write(b47s, BCMA_CC_FLASHDATA, *buf++); + + /* Program page */ + if (b47s->bcma_cc->core->id.rev < 20) { + bcm47xxsflash_cmd(b47s, OPCODE_ST_PP); + return 1; /* 1B written */ + } + + /* Program page and set CSA (on newer chips we can continue writing) */ + bcm47xxsflash_cmd(b47s, OPCODE_ST_CSA | OPCODE_ST_PP); + offset++; + len--; + written++; + + while (len > 0) { + /* Page boundary, another function call is needed */ + if ((offset & 0xFF) == 0) + break; + + bcm47xxsflash_cmd(b47s, OPCODE_ST_CSA | *buf++); + offset++; + len--; + written++; + } + + /* All done, drop CSA & poll */ + b47s->cc_write(b47s, BCMA_CC_FLASHCTL, 0); + udelay(1); + if (bcm47xxsflash_poll(b47s, HZ / 10)) + pr_err("Flash rejected dropping CSA\n"); + + return written; +} + +static int bcm47xxsflash_write_at(struct mtd_info *mtd, u32 offset, size_t len, + const u_char *buf) +{ + struct bcm47xxsflash *b47s = mtd->priv; + u32 mask = b47s->blocksize - 1; + u32 page = (offset & ~mask) << 1; + u32 byte = offset & mask; + int written = 0; + + /* If we don't overwrite whole page, read it to the buffer first */ + if (byte || (len < b47s->blocksize)) { + int err; + + b47s->cc_write(b47s, BCMA_CC_FLASHADDR, page); + bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_LOAD); + /* 250 us for AT45DB321B */ + err = bcm47xxsflash_poll(b47s, HZ / 1000); + if (err) { + pr_err("Timeout reading page 0x%X info buffer\n", page); + return err; + } + } + + /* Change buffer content with our data */ + while (len > 0) { + /* Page boundary, another function call is needed */ + if (byte == b47s->blocksize) + break; + + b47s->cc_write(b47s, BCMA_CC_FLASHADDR, byte++); + b47s->cc_write(b47s, BCMA_CC_FLASHDATA, *buf++); + bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_WRITE); + len--; + written++; + } + + /* Program page with the buffer content */ + b47s->cc_write(b47s, BCMA_CC_FLASHADDR, page); + bcm47xxsflash_cmd(b47s, OPCODE_AT_BUF1_PROGRAM); + + return written; +} + +static int bcm47xxsflash_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct bcm47xxsflash *b47s = mtd->priv; + int written; + + /* Writing functions can return without writing all passed data, for + * example when the hardware is too old or when we git page boundary. + */ + while (len > 0) { + switch (b47s->type) { + case BCM47XXSFLASH_TYPE_ST: + written = bcm47xxsflash_write_st(mtd, to, len, buf); + break; + case BCM47XXSFLASH_TYPE_ATMEL: + written = bcm47xxsflash_write_at(mtd, to, len, buf); + break; + default: + BUG_ON(1); + } + if (written < 0) { + pr_err("Error writing at offset 0x%llX\n", to); + return written; + } + to += (loff_t)written; + len -= written; + *retlen += written; + buf += written; + } + + return 0; +} + +static void bcm47xxsflash_fill_mtd(struct bcm47xxsflash *b47s, + struct device *dev) +{ + struct mtd_info *mtd = &b47s->mtd; + + mtd->priv = b47s; + mtd->dev.parent = dev; + mtd->name = "bcm47xxsflash"; + + mtd->type = MTD_NORFLASH; + mtd->flags = MTD_CAP_NORFLASH; + mtd->size = b47s->size; + mtd->erasesize = b47s->blocksize; + mtd->writesize = 1; + mtd->writebufsize = 1; + + mtd->_erase = bcm47xxsflash_erase; + mtd->_read = bcm47xxsflash_read; + mtd->_write = bcm47xxsflash_write; +} + +/************************************************** + * BCMA + **************************************************/ + +static int bcm47xxsflash_bcma_cc_read(struct bcm47xxsflash *b47s, u16 offset) +{ + return bcma_cc_read32(b47s->bcma_cc, offset); +} + +static void bcm47xxsflash_bcma_cc_write(struct bcm47xxsflash *b47s, u16 offset, + u32 value) +{ + bcma_cc_write32(b47s->bcma_cc, offset, value); +} + +static int bcm47xxsflash_bcma_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct bcma_sflash *sflash = dev_get_platdata(dev); + struct bcm47xxsflash *b47s; + struct resource *res; + int err; + + b47s = devm_kzalloc(dev, sizeof(*b47s), GFP_KERNEL); + if (!b47s) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "invalid resource\n"); + return -EINVAL; + } + if (!devm_request_mem_region(dev, res->start, resource_size(res), + res->name)) { + dev_err(dev, "can't request region for resource %pR\n", res); + return -EBUSY; + } + + b47s->bcma_cc = container_of(sflash, struct bcma_drv_cc, sflash); + b47s->cc_read = bcm47xxsflash_bcma_cc_read; + b47s->cc_write = bcm47xxsflash_bcma_cc_write; + + /* + * On old MIPS devices cache was magically invalidated when needed, + * allowing us to use cached access and gain some performance. Trying + * the same on ARM based BCM53573 results in flash corruptions, we need + * to use uncached access for it. + * + * It may be arch specific, but right now there is only 1 ARM SoC using + * this driver, so let's follow Broadcom's reference code and check + * ChipCommon revision. + */ + if (b47s->bcma_cc->core->id.rev == 54) + b47s->window = ioremap(res->start, resource_size(res)); + else + b47s->window = ioremap_cache(res->start, resource_size(res)); + if (!b47s->window) { + dev_err(dev, "ioremap failed for resource %pR\n", res); + return -ENOMEM; + } + + switch (b47s->bcma_cc->capabilities & BCMA_CC_CAP_FLASHT) { + case BCMA_CC_FLASHT_STSER: + b47s->type = BCM47XXSFLASH_TYPE_ST; + break; + case BCMA_CC_FLASHT_ATSER: + b47s->type = BCM47XXSFLASH_TYPE_ATMEL; + break; + } + + b47s->blocksize = sflash->blocksize; + b47s->numblocks = sflash->numblocks; + b47s->size = sflash->size; + bcm47xxsflash_fill_mtd(b47s, &pdev->dev); + + platform_set_drvdata(pdev, b47s); + + err = mtd_device_parse_register(&b47s->mtd, probes, NULL, NULL, 0); + if (err) { + pr_err("Failed to register MTD device: %d\n", err); + iounmap(b47s->window); + return err; + } + + if (bcm47xxsflash_poll(b47s, HZ / 10)) + pr_warn("Serial flash busy\n"); + + return 0; +} + +static void bcm47xxsflash_bcma_remove(struct platform_device *pdev) +{ + struct bcm47xxsflash *b47s = platform_get_drvdata(pdev); + + mtd_device_unregister(&b47s->mtd); + iounmap(b47s->window); +} + +static struct platform_driver bcma_sflash_driver = { + .probe = bcm47xxsflash_bcma_probe, + .remove = bcm47xxsflash_bcma_remove, + .driver = { + .name = "bcma_sflash", + }, +}; + +/************************************************** + * Init + **************************************************/ + +module_platform_driver(bcma_sflash_driver); diff --git a/drivers/mtd/devices/bcm47xxsflash.h b/drivers/mtd/devices/bcm47xxsflash.h new file mode 100644 index 000000000..fef0d5e42 --- /dev/null +++ b/drivers/mtd/devices/bcm47xxsflash.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __BCM47XXSFLASH_H +#define __BCM47XXSFLASH_H + +#include + +#define BCM47XXSFLASH_WINDOW_SZ SZ_16M + +/* Used for ST flashes only. */ +#define OPCODE_ST_WREN 0x0006 /* Write Enable */ +#define OPCODE_ST_WRDIS 0x0004 /* Write Disable */ +#define OPCODE_ST_RDSR 0x0105 /* Read Status Register */ +#define OPCODE_ST_WRSR 0x0101 /* Write Status Register */ +#define OPCODE_ST_READ 0x0303 /* Read Data Bytes */ +#define OPCODE_ST_PP 0x0302 /* Page Program */ +#define OPCODE_ST_SE 0x02d8 /* Sector Erase */ +#define OPCODE_ST_BE 0x00c7 /* Bulk Erase */ +#define OPCODE_ST_DP 0x00b9 /* Deep Power-down */ +#define OPCODE_ST_RES 0x03ab /* Read Electronic Signature */ +#define OPCODE_ST_CSA 0x1000 /* Keep chip select asserted */ +#define OPCODE_ST_SSE 0x0220 /* Sub-sector Erase */ +#define OPCODE_ST_READ4B 0x6313 /* Read Data Bytes in 4Byte addressing mode */ + +/* Used for Atmel flashes only. */ +#define OPCODE_AT_READ 0x07e8 +#define OPCODE_AT_PAGE_READ 0x07d2 +#define OPCODE_AT_STATUS 0x01d7 +#define OPCODE_AT_BUF1_WRITE 0x0384 +#define OPCODE_AT_BUF2_WRITE 0x0387 +#define OPCODE_AT_BUF1_ERASE_PROGRAM 0x0283 +#define OPCODE_AT_BUF2_ERASE_PROGRAM 0x0286 +#define OPCODE_AT_BUF1_PROGRAM 0x0288 +#define OPCODE_AT_BUF2_PROGRAM 0x0289 +#define OPCODE_AT_PAGE_ERASE 0x0281 +#define OPCODE_AT_BLOCK_ERASE 0x0250 +#define OPCODE_AT_BUF1_WRITE_ERASE_PROGRAM 0x0382 +#define OPCODE_AT_BUF2_WRITE_ERASE_PROGRAM 0x0385 +#define OPCODE_AT_BUF1_LOAD 0x0253 +#define OPCODE_AT_BUF2_LOAD 0x0255 +#define OPCODE_AT_BUF1_COMPARE 0x0260 +#define OPCODE_AT_BUF2_COMPARE 0x0261 +#define OPCODE_AT_BUF1_REPROGRAM 0x0258 +#define OPCODE_AT_BUF2_REPROGRAM 0x0259 + +/* Status register bits for ST flashes */ +#define SR_ST_WIP 0x01 /* Write In Progress */ +#define SR_ST_WEL 0x02 /* Write Enable Latch */ +#define SR_ST_BP_MASK 0x1c /* Block Protect */ +#define SR_ST_BP_SHIFT 2 +#define SR_ST_SRWD 0x80 /* Status Register Write Disable */ + +/* Status register bits for Atmel flashes */ +#define SR_AT_READY 0x80 +#define SR_AT_MISMATCH 0x40 +#define SR_AT_ID_MASK 0x38 +#define SR_AT_ID_SHIFT 3 + +struct bcma_drv_cc; + +enum bcm47xxsflash_type { + BCM47XXSFLASH_TYPE_ATMEL, + BCM47XXSFLASH_TYPE_ST, +}; + +struct bcm47xxsflash { + struct bcma_drv_cc *bcma_cc; + int (*cc_read)(struct bcm47xxsflash *b47s, u16 offset); + void (*cc_write)(struct bcm47xxsflash *b47s, u16 offset, u32 value); + + enum bcm47xxsflash_type type; + + void __iomem *window; + + u32 blocksize; + u16 numblocks; + u32 size; + + struct mtd_info mtd; +}; + +#endif /* BCM47XXSFLASH */ diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c new file mode 100644 index 000000000..03e80b2c4 --- /dev/null +++ b/drivers/mtd/devices/block2mtd.c @@ -0,0 +1,532 @@ +/* + * block2mtd.c - create an mtd from a block device + * + * Copyright (C) 2001,2002 Simon Evans + * Copyright (C) 2004-2006 Joern Engel + * + * Licence: GPL + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +/* + * When the first attempt at device initialization fails, we may need to + * wait a little bit and retry. This timeout, by default 3 seconds, gives + * device time to start up. Required on BCM2708 and a few other chipsets. + */ +#define MTD_DEFAULT_TIMEOUT 3 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Maximum number of comma-separated items in the 'block2mtd=' parameter */ +#define BLOCK2MTD_PARAM_MAX_COUNT 3 + +/* Info for the block device */ +struct block2mtd_dev { + struct list_head list; + struct file *bdev_file; + struct mtd_info mtd; + struct mutex write_mutex; +}; + + +/* Static info about the MTD, used in cleanup_module */ +static LIST_HEAD(blkmtd_device_list); + + +static struct page *page_read(struct address_space *mapping, pgoff_t index) +{ + return read_mapping_page(mapping, index, NULL); +} + +/* erase a specified part of the device */ +static int _block2mtd_erase(struct block2mtd_dev *dev, loff_t to, size_t len) +{ + struct address_space *mapping = dev->bdev_file->f_mapping; + struct page *page; + pgoff_t index = to >> PAGE_SHIFT; // page index + int pages = len >> PAGE_SHIFT; + u_long *p; + u_long *max; + + while (pages) { + page = page_read(mapping, index); + if (IS_ERR(page)) + return PTR_ERR(page); + + max = page_address(page) + PAGE_SIZE; + for (p=page_address(page); ppriv; + size_t from = instr->addr; + size_t len = instr->len; + int err; + + mutex_lock(&dev->write_mutex); + err = _block2mtd_erase(dev, from, len); + mutex_unlock(&dev->write_mutex); + if (err) + pr_err("erase failed err = %d\n", err); + + return err; +} + + +static int block2mtd_read(struct mtd_info *mtd, loff_t from, size_t len, + size_t *retlen, u_char *buf) +{ + struct block2mtd_dev *dev = mtd->priv; + struct address_space *mapping = dev->bdev_file->f_mapping; + struct page *page; + pgoff_t index = from >> PAGE_SHIFT; + int offset = from & (PAGE_SIZE-1); + int cpylen; + + while (len) { + if ((offset + len) > PAGE_SIZE) + cpylen = PAGE_SIZE - offset; // multiple pages + else + cpylen = len; // this page + len = len - cpylen; + + page = page_read(mapping, index); + if (IS_ERR(page)) + return PTR_ERR(page); + + memcpy(buf, page_address(page) + offset, cpylen); + put_page(page); + + if (retlen) + *retlen += cpylen; + buf += cpylen; + offset = 0; + index++; + } + return 0; +} + + +/* write data to the underlying device */ +static int _block2mtd_write(struct block2mtd_dev *dev, const u_char *buf, + loff_t to, size_t len, size_t *retlen) +{ + struct page *page; + struct address_space *mapping = dev->bdev_file->f_mapping; + pgoff_t index = to >> PAGE_SHIFT; // page index + int offset = to & ~PAGE_MASK; // page offset + int cpylen; + + while (len) { + if ((offset+len) > PAGE_SIZE) + cpylen = PAGE_SIZE - offset; // multiple pages + else + cpylen = len; // this page + len = len - cpylen; + + page = page_read(mapping, index); + if (IS_ERR(page)) + return PTR_ERR(page); + + if (memcmp(page_address(page)+offset, buf, cpylen)) { + lock_page(page); + memcpy(page_address(page) + offset, buf, cpylen); + set_page_dirty(page); + unlock_page(page); + balance_dirty_pages_ratelimited(mapping); + } + put_page(page); + + if (retlen) + *retlen += cpylen; + + buf += cpylen; + offset = 0; + index++; + } + return 0; +} + + +static int block2mtd_write(struct mtd_info *mtd, loff_t to, size_t len, + size_t *retlen, const u_char *buf) +{ + struct block2mtd_dev *dev = mtd->priv; + int err; + + mutex_lock(&dev->write_mutex); + err = _block2mtd_write(dev, buf, to, len, retlen); + mutex_unlock(&dev->write_mutex); + if (err > 0) + err = 0; + return err; +} + + +/* sync the device - wait until the write queue is empty */ +static void block2mtd_sync(struct mtd_info *mtd) +{ + struct block2mtd_dev *dev = mtd->priv; + sync_blockdev(file_bdev(dev->bdev_file)); + return; +} + + +static void block2mtd_free_device(struct block2mtd_dev *dev) +{ + if (!dev) + return; + + kfree(dev->mtd.name); + + if (dev->bdev_file) { + invalidate_mapping_pages(dev->bdev_file->f_mapping, 0, -1); + bdev_fput(dev->bdev_file); + } + + kfree(dev); +} + +/* + * This function is marked __ref because it calls the __init marked + * early_lookup_bdev when called from the early boot code. + */ +static struct file __ref *mdtblock_early_get_bdev(const char *devname, + blk_mode_t mode, int timeout, struct block2mtd_dev *dev) +{ + struct file *bdev_file = ERR_PTR(-ENODEV); +#ifndef MODULE + int i; + + /* + * We can't use early_lookup_bdev from a running system. + */ + if (system_state >= SYSTEM_RUNNING) + return bdev_file; + + /* + * We might not have the root device mounted at this point. + * Try to resolve the device name by other means. + */ + for (i = 0; i <= timeout; i++) { + dev_t devt; + + if (i) + /* + * Calling wait_for_device_probe in the first loop + * was not enough, sleep for a bit in subsequent + * go-arounds. + */ + msleep(1000); + wait_for_device_probe(); + + if (!early_lookup_bdev(devname, &devt)) { + bdev_file = bdev_file_open_by_dev(devt, mode, dev, NULL); + if (!IS_ERR(bdev_file)) + break; + } + } +#endif + return bdev_file; +} + +static struct block2mtd_dev *add_device(char *devname, int erase_size, + char *label, int timeout) +{ + const blk_mode_t mode = BLK_OPEN_READ | BLK_OPEN_WRITE; + struct file *bdev_file; + struct block_device *bdev; + struct block2mtd_dev *dev; + loff_t size; + char *name; + + if (!devname) + return NULL; + + dev = kzalloc_obj(struct block2mtd_dev); + if (!dev) + return NULL; + + /* Get a handle on the device */ + bdev_file = bdev_file_open_by_path(devname, mode, dev, NULL); + if (IS_ERR(bdev_file)) + bdev_file = mdtblock_early_get_bdev(devname, mode, timeout, + dev); + if (IS_ERR(bdev_file)) { + pr_err("error: cannot open device %s\n", devname); + goto err_free_block2mtd; + } + dev->bdev_file = bdev_file; + bdev = file_bdev(bdev_file); + + if (MAJOR(bdev->bd_dev) == MTD_BLOCK_MAJOR) { + pr_err("attempting to use an MTD device as a block device\n"); + goto err_free_block2mtd; + } + + size = bdev_nr_bytes(bdev); + if ((long)size % erase_size) { + pr_err("erasesize must be a divisor of device size\n"); + goto err_free_block2mtd; + } + + mutex_init(&dev->write_mutex); + + /* Setup the MTD structure */ + /* make the name contain the block device in */ + if (!label) + name = kasprintf(GFP_KERNEL, "block2mtd: %s", devname); + else + name = kstrdup(label, GFP_KERNEL); + if (!name) + goto err_destroy_mutex; + + dev->mtd.name = name; + + dev->mtd.size = size & PAGE_MASK; + dev->mtd.erasesize = erase_size; + dev->mtd.writesize = 1; + dev->mtd.writebufsize = PAGE_SIZE; + dev->mtd.type = MTD_RAM; + dev->mtd.flags = MTD_CAP_RAM; + dev->mtd._erase = block2mtd_erase; + dev->mtd._write = block2mtd_write; + dev->mtd._sync = block2mtd_sync; + dev->mtd._read = block2mtd_read; + dev->mtd.priv = dev; + dev->mtd.owner = THIS_MODULE; + + if (mtd_device_register(&dev->mtd, NULL, 0)) { + /* Device didn't get added, so free the entry */ + goto err_destroy_mutex; + } + + list_add(&dev->list, &blkmtd_device_list); + pr_info("mtd%d: [%s] erase_size = %dKiB [%d]\n", + dev->mtd.index, + label ? label : dev->mtd.name + strlen("block2mtd: "), + dev->mtd.erasesize >> 10, dev->mtd.erasesize); + return dev; + +err_destroy_mutex: + mutex_destroy(&dev->write_mutex); +err_free_block2mtd: + block2mtd_free_device(dev); + return NULL; +} + + +/* This function works similar to reguler strtoul. In addition, it + * allows some suffixes for a more human-readable number format: + * ki, Ki, kiB, KiB - multiply result with 1024 + * Mi, MiB - multiply result with 1024^2 + * Gi, GiB - multiply result with 1024^3 + */ +static int ustrtoul(const char *cp, char **endp, unsigned int base) +{ + unsigned long result = simple_strtoul(cp, endp, base); + switch (**endp) { + case 'G' : + result *= 1024; + fallthrough; + case 'M': + result *= 1024; + fallthrough; + case 'K': + case 'k': + result *= 1024; + /* By dwmw2 editorial decree, "ki", "Mi" or "Gi" are to be used. */ + if ((*endp)[1] == 'i') { + if ((*endp)[2] == 'B') + (*endp) += 3; + else + (*endp) += 2; + } + } + return result; +} + + +static int parse_num(size_t *num, const char *token) +{ + char *endp; + size_t n; + + n = (size_t) ustrtoul(token, &endp, 0); + if (*endp) + return -EINVAL; + + *num = n; + return 0; +} + + +static inline void kill_final_newline(char *str) +{ + char *newline = strrchr(str, '\n'); + if (newline && !newline[1]) + *newline = 0; +} + + +#ifndef MODULE +static int block2mtd_init_called = 0; +/* 80 for device, 12 for erase size */ +static char block2mtd_paramline[80 + 12]; +#endif + +static int block2mtd_setup2(const char *val) +{ + /* 80 for device, 12 for erase size, 80 for name, 8 for timeout */ + char buf[80 + 12 + 80 + 8]; + char *str = buf; + char *token[BLOCK2MTD_PARAM_MAX_COUNT]; + char *name; + char *label = NULL; + size_t erase_size = PAGE_SIZE; + unsigned long timeout = MTD_DEFAULT_TIMEOUT; + int i, ret; + + if (strnlen(val, sizeof(buf)) >= sizeof(buf)) { + pr_err("parameter too long\n"); + return 0; + } + + strcpy(str, val); + kill_final_newline(str); + + for (i = 0; i < BLOCK2MTD_PARAM_MAX_COUNT; i++) + token[i] = strsep(&str, ","); + + if (str) { + pr_err("too many arguments\n"); + return 0; + } + + if (!token[0]) { + pr_err("no argument\n"); + return 0; + } + + name = token[0]; + if (strlen(name) + 1 > 80) { + pr_err("device name too long\n"); + return 0; + } + + /* Optional argument when custom label is used */ + if (token[1] && strlen(token[1])) { + ret = parse_num(&erase_size, token[1]); + if (ret) { + pr_err("illegal erase size\n"); + return 0; + } + } + + if (token[2]) { + label = token[2]; + pr_info("Using custom MTD label '%s' for dev %s\n", label, name); + } + + add_device(name, erase_size, label, timeout); + + return 0; +} + + +static int block2mtd_setup(const char *val, const struct kernel_param *kp) +{ +#ifdef MODULE + return block2mtd_setup2(val); +#else + /* If more parameters are later passed in via + /sys/module/block2mtd/parameters/block2mtd + and block2mtd_init() has already been called, + we can parse the argument now. */ + + if (block2mtd_init_called) + return block2mtd_setup2(val); + + /* During early boot stage, we only save the parameters + here. We must parse them later: if the param passed + from kernel boot command line, block2mtd_setup() is + called so early that it is not possible to resolve + the device (even kmalloc() fails). Deter that work to + block2mtd_setup2(). */ + + strscpy(block2mtd_paramline, val, sizeof(block2mtd_paramline)); + + return 0; +#endif +} + + +module_param_call(block2mtd, block2mtd_setup, NULL, NULL, 0200); +MODULE_PARM_DESC(block2mtd, "Device to use. \"block2mtd=[,[][,